diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 736670e..2c94852 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -10,10 +10,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Add ticket to inbox - uses: technote-space/create-project-card-action@v1 + uses: actions/add-to-project@v0.5.0 with: - PROJECT: Core development - COLUMN: Inbox - GITHUB_TOKEN: ${{ secrets.TRIAGE_TOKEN }} - CHECK_ORG_PROJECT: true + project-url: https://github.com/orgs/gazebosim/projects/7 + github-token: ${{ secrets.TRIAGE_TOKEN }} diff --git a/BUILD.bazel b/BUILD.bazel index b1dda26..db18e8c 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,10 +1,10 @@ load( - "//gz_bazel:build_defs.bzl", + "@gz//bazel/skylark:build_defs.bzl", "GZ_FEATURES", "GZ_VISIBILITY", - "generate_include_header", - "gz_config_header", + "gz_configure_header", "gz_export_header", + "gz_include_header", ) package( @@ -12,24 +12,15 @@ package( features = GZ_FEATURES, ) -licenses(["notice"]) +licenses(["notice"]) # Apache-2.0 exports_files(["LICENSE"]) -PROJECT_NAME = "gz-utils" - -PROJECT_MAJOR = 2 - -PROJECT_MINOR = 0 - -PROJECT_PATCH = 0 - -gz_config_header( +gz_configure_header( name = "config", src = "include/gz/utils/config.hh.in", cmakelists = ["CMakeLists.txt"], - project_name = PROJECT_NAME, - project_version = (PROJECT_MAJOR, PROJECT_MINOR, PROJECT_PATCH), + package = "utils", ) gz_export_header( @@ -41,10 +32,10 @@ gz_export_header( public_headers_no_gen = glob([ "include/gz/utils/*.hh", - "include/gz/utils/detail/*.hh" + "include/gz/utils/detail/*.hh", ]) -generate_include_header( +gz_include_header( name = "utilshh_genrule", out = "include/gz/utils.hh", hdrs = public_headers_no_gen + [ @@ -60,7 +51,8 @@ public_headers = public_headers_no_gen + [ ] cc_library( - name = "gz_utils", + name = "utils", + srcs = ["src/Environment.cc"], hdrs = public_headers, includes = ["include"], ) @@ -75,7 +67,7 @@ cc_library( "test/integration/implptr/implptr_test_classes.hh", ], includes = ["test/integration/implptr"], - deps = ["gz_utils"], + deps = [":utils"], ) cc_test( @@ -85,6 +77,25 @@ cc_test( ":implptr_test_classes", "@gtest", "@gtest//:gtest_main", - ] + ], +) + +cc_test( + name = "Environment_TEST", + srcs = ["src/Environment_TEST.cc"], + deps = [ + ":utils", + "@gtest", + "@gtest//:gtest_main", + ], ) +cc_test( + name = "NeverDestroyed_TEST", + srcs = ["src/NeverDestroyed_TEST.cc"], + deps = [ + ":utils", + "@gtest", + "@gtest//:gtest_main", + ], +) diff --git a/COPYING b/COPYING deleted file mode 100644 index 4909afd..0000000 --- a/COPYING +++ /dev/null @@ -1,178 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - diff --git a/Changelog.md b/Changelog.md index 6bc1959..7d86038 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,7 +4,36 @@ ## Gazebo Utils 2.x -## Gazebo Utils 2.0.0 (20XX-XX-XX) +## Gazebo Utils 2.1.0 (2023-09-26) + +1. Documentation fixes + * [Pull request #104](https://github.com/gazebosim/gz-utils/pull/104) + * [Pull request #103](https://github.com/gazebosim/gz-utils/pull/103) + +1. Infrastructure + * [Pull request #102](https://github.com/gazebosim/gz-utils/pull/102) + * [Pull request #101](https://github.com/gazebosim/gz-utils/pull/101) + * [Pull request #85](https://github.com/gazebosim/gz-utils/pull/85) + +1. Extra test macros for ARM32/ARM64 + * [Pull request #99](https://github.com/gazebosim/gz-utils/pull/99) + +1. Add a utility for spawning subprocesses + * [Pull request #98](https://github.com/gazebosim/gz-utils/pull/98) + +1. Support for bazel in Garden + * [Pull request #95](https://github.com/gazebosim/gz-utils/pull/95) + +1. Rename COPYING to LICENSE + * [Pull request #93](https://github.com/gazebosim/gz-utils/pull/93) + +1. Add missing config.hh include to gz headers + * [Pull request #90](https://github.com/gazebosim/gz-utils/pull/90) + +1. ign -> gz Migrate Ignition headers + * [Pull request #84](https://github.com/gazebosim/gz-utils/pull/84) + +## Gazebo Utils 2.0.0 (2022-09-22) 1. Improve install instructions * [Pull request #80](https://github.com/gazebosim/gz-utils/pull/80) diff --git a/LICENSE b/LICENSE index bd33c43..4909afd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,15 +1,178 @@ -Software License Agreement (Apache License) + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright 2012 Open Source Robotics Foundation + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/README.md b/README.md index 0ca9288..09c724f 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Gazebo Utils provides a wide range of functionality, including: # Install -See the [installation tutorial](https://gazebosim.org/api/utils/2.0/install.html). +See the [installation tutorial](https://gazebosim.org/api/utils/2/install.html). # Usage @@ -89,7 +89,7 @@ You can also generate the documentation from a clone of this repository by follo Follow these steps to run tests and static code analysis in your clone of this repository. -1. Follow the [source install instruction](https://gazebosim.org/libs/utils#source-install). +1. Follow the [source install instruction](https://gazebosim.org/api/utils/2/install.html#source-install). 2. Run tests. @@ -109,6 +109,7 @@ Refer to the following table for information about important directories and fil ``` gz-utils +├── cli Gazebo CLI component. Vendored from https://github.com/CLIUtils/CLI11/ ├── examples Example programs. ├── include/gz/utils Header files. ├── src Source files and unit tests. diff --git a/cli/BUILD.bazel b/cli/BUILD.bazel index 9b64df6..096f09e 100644 --- a/cli/BUILD.bazel +++ b/cli/BUILD.bazel @@ -1,32 +1,21 @@ load( - "//gz_bazel:build_defs.bzl", + "@gz//bazel/skylark:build_defs.bzl", "GZ_VISIBILITY", ) -package( - default_visibility = GZ_VISIBILITY, -) - public_headers = [ - "include/gz/utils/cli/App.hpp", - "include/gz/utils/cli/CLI.hpp", - "include/gz/utils/cli/ConfigFwd.hpp", - "include/gz/utils/cli/Config.hpp", - "include/gz/utils/cli/Error.hpp", - "include/gz/utils/cli/FormatterFwd.hpp", - "include/gz/utils/cli/Formatter.hpp", - "include/gz/utils/cli/Macros.hpp", - "include/gz/utils/cli/Option.hpp", - "include/gz/utils/cli/Split.hpp", - "include/gz/utils/cli/StringTools.hpp", - "include/gz/utils/cli/Timer.hpp", - "include/gz/utils/cli/TypeTools.hpp", - "include/gz/utils/cli/Validators.hpp", - "include/gz/utils/cli/Version.hpp", -] + "include/gz/utils/cli/GzFormatter.hpp", +] + glob([ + "include/external-cli/gz/utils/cli/*.hpp", +]) cc_library( name = "cli", hdrs = public_headers, - includes = ["include"], + includes = [ + "include", + "include/external-cli", + ], + visibility = GZ_VISIBILITY, + deps = ["@cli11"], ) diff --git a/include/gz/utils/ExtraTestMacros.hh b/include/gz/utils/ExtraTestMacros.hh index c773ef1..45ae3cc 100644 --- a/include/gz/utils/ExtraTestMacros.hh +++ b/include/gz/utils/ExtraTestMacros.hh @@ -68,4 +68,36 @@ #define GZ_UTILS_TEST_DISABLED_ON_LINUX(TestName) \ DETAIL_GZ_UTILS_TEST_DISABLED_ON_LINUX(TestName) +/// \brief Restrict the execution of the test to just the ARM32 architecture +/// Other platforms will get the test compiled but it won't be run +/// as part of the test suite execution. +/// The macro uses the Disabled_ prefix provided by googletest. See +/// https://chromium.googlesource.com/external/github.com/google/googletest/+/HEAD/googletest/docs/advanced.md +#define GZ_UTILS_TEST_ENABLED_ONLY_ON_ARM32(TestName) \ + DETAIL_GZ_UTILS_TEST_ENABLED_ONLY_ON_ARM32(TestName) + +/// \brief Restrict the execution of the test for the ARM32 architecture +/// The test will be compiled on ARM32 too but will never be run as +/// part of the test suite. The macro uses the Disabled_ prefix provided +/// by googletest. See +/// https://chromium.googlesource.com/external/github.com/google/googletest/+/HEAD/googletest/docs/advanced.md +#define GZ_UTILS_TEST_DISABLED_ON_ARM32(TestName) \ + DETAIL_GZ_UTILS_TEST_DISABLED_ON_ARM32(TestName) + +/// \brief Restrict the execution of the test to just the ARM64 architecture +/// Other platforms will get the test compiled but it won't be run +/// as part of the test suite execution. +/// The macro uses the Disabled_ prefix provided by googletest. See +/// https://chromium.googlesource.com/external/github.com/google/googletest/+/HEAD/googletest/docs/advanced.md +#define GZ_UTILS_TEST_ENABLED_ONLY_ON_ARM64(TestName) \ + DETAIL_GZ_UTILS_TEST_ENABLED_ONLY_ON_ARM64(TestName) + +/// \brief Restrict the execution of the test for the ARM64 architecture +/// The test will be compiled on Linux too but will never be run as +/// part of the test suite. The macro uses the Disabled_ prefix provided +/// by googletest. See +/// https://chromium.googlesource.com/external/github.com/google/googletest/+/HEAD/googletest/docs/advanced.md +#define GZ_UTILS_TEST_DISABLED_ON_ARM64(TestName) \ + DETAIL_GZ_UTILS_TEST_DISABLED_ON_ARM64(TestName) + #endif // GZ_UTILS_EXTRATESTMACROS_HH diff --git a/include/gz/utils/Subprocess.hh b/include/gz/utils/Subprocess.hh new file mode 100644 index 0000000..787daf5 --- /dev/null +++ b/include/gz/utils/Subprocess.hh @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2023 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef GZ_UTILS__SUBPROCESS_HH_ +#define GZ_UTILS__SUBPROCESS_HH_ + +#include "detail/subprocess.h" +#include +#include + +#include + +// This header contains a wrapper to the cross-platform subprocess library. +// It can be used to spawn processes to interact with, primarily for testing. + +namespace gz +{ +namespace utils +{ + +class Subprocess +{ + public: Subprocess(const std::vector &_commandLine, + const std::vector &_environment = {}): + commandLine(_commandLine), + environment(_environment) + { + this->Create(); + } + + private: void Create() + { + if (this->process) + return; + + this->process = new subprocess_s; + + std::vector commandLineCstr; + for (const auto &val : this->commandLine) + { + commandLineCstr.push_back(val.c_str()); + } + commandLineCstr.push_back(nullptr); + + std::vector environmentCstr; + for (const auto &val : this->environment) + { + environmentCstr.push_back(val.c_str()); + } + environmentCstr.push_back(nullptr); + + int ret = -1; + if (this->environment.size()) + { + ret = subprocess_create_ex(commandLineCstr.data(), + 0, environmentCstr.data(), this->process); + } + else + { + ret = subprocess_create(commandLineCstr.data(), 0, this->process); + } + + if (0 != ret) + { + std::cerr << "failed to create subprocess" << std::endl; + delete this->process; + this->process = nullptr; + } + } + + public: ~Subprocess() + { + if (this->process) + subprocess_destroy(this->process); + delete this->process; + } + + public: std::string Stdout() + { + std::string result{""}; + if (this->process) + { + auto p_stdout = subprocess_stdout(this->process); + char buffer[128]; + while (!feof(p_stdout)) + { + if (fgets(buffer, 128, p_stdout) != NULL) + result += buffer; + } + } + return result; + } + + public: std::string Stderr() + { + std::string result{""}; + if (this->process) + { + auto p_stdout = subprocess_stderr(this->process); + char buffer[128]; + while (!feof(p_stdout)) + { + if (fgets(buffer, 128, p_stdout) != NULL) + result += buffer; + } + } + return result; + + } + + public: bool Alive() + { + if (this->process) + return subprocess_alive(this->process); + else + return false; + } + + public: bool Terminate() + { + if (this->process) + return subprocess_terminate(this->process) != 0; + else + return false; + } + + public: int Join() + { + int return_code = -1; + if (this->process) + { + auto ret = subprocess_join(this->process, &return_code); + if (ret != 0) + { + std::cerr << "Failed to join subprocess" << std::endl; + } + } + + return return_code; + } + + protected: std::vector commandLine; + + protected: std::vector environment; + + protected: subprocess_s * process {nullptr}; +}; +} // namespace utils +} // namespace gz + +#endif // GZ_UTILS__SUBPROCESS_HH_ diff --git a/include/gz/utils/detail/ExtraTestMacros.hh b/include/gz/utils/detail/ExtraTestMacros.hh index d26530c..5ec0879 100644 --- a/include/gz/utils/detail/ExtraTestMacros.hh +++ b/include/gz/utils/detail/ExtraTestMacros.hh @@ -70,5 +70,37 @@ #endif // defined __linux__ +#if defined __arm__ || defined _M_ARM + + #define DETAIL_GZ_UTILS_TEST_DISABLED_ON_ARM32(TestName) \ + DETAIL_GZ_UTILS_ADD_DISABLED_PREFIX(TestName) + #define DETAIL_GZ_UTILS_TEST_ENABLED_ONLY_ON_ARM32(TestName) \ + TestName + +#else + + #define DETAIL_GZ_UTILS_TEST_DISABLED_ON_ARM32(TestName) \ + TestName + #define DETAIL_GZ_UTILS_TEST_ENABLED_ONLY_ON_ARM32(TestName) \ + DETAIL_GZ_UTILS_ADD_DISABLED_PREFIX(TestName) + +#endif // defined __arm__ + +#if defined __aarch64__ || defined _M_ARM64 + + #define DETAIL_GZ_UTILS_TEST_DISABLED_ON_ARM64(TestName) \ + DETAIL_GZ_UTILS_ADD_DISABLED_PREFIX(TestName) + #define DETAIL_GZ_UTILS_TEST_ENABLED_ONLY_ON_ARM64(TestName) \ + TestName + +#else + + #define DETAIL_GZ_UTILS_TEST_DISABLED_ON_ARM64(TestName) \ + TestName + #define DETAIL_GZ_UTILS_TEST_ENABLED_ONLY_ON_ARM64(TestName) \ + DETAIL_GZ_UTILS_ADD_DISABLED_PREFIX(TestName) + +#endif // defined __aarch64__ + #endif // GZ_UTILS_DETAIL_EXTRATESTMACROS_HH diff --git a/include/gz/utils/detail/subprocess.h b/include/gz/utils/detail/subprocess.h new file mode 100644 index 0000000..7077ffe --- /dev/null +++ b/include/gz/utils/detail/subprocess.h @@ -0,0 +1,1181 @@ +/* + The latest version of this library is available on GitHub; + https://github.com/sheredom/subprocess.h +*/ + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ + +#ifndef SHEREDOM_SUBPROCESS_H_INCLUDED +#define SHEREDOM_SUBPROCESS_H_INCLUDED + +#if defined(_MSC_VER) +#pragma warning(push, 1) + +/* disable warning: '__cplusplus' is not defined as a preprocessor macro, + * replacing with '0' for '#if/#elif' */ +#pragma warning(disable : 4668) +#endif + +#include +#include + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(_MSC_VER) +#define subprocess_pure +#define subprocess_weak __inline +#define subprocess_tls __declspec(thread) +#elif defined(__MINGW32__) +#define subprocess_pure __attribute__((pure)) +#define subprocess_weak static __attribute__((used)) +#define subprocess_tls __thread +#elif defined(__clang__) || defined(__GNUC__) +#define subprocess_pure __attribute__((pure)) +#define subprocess_weak __attribute__((weak)) +#define subprocess_tls __thread +#else +#error Non clang, non gcc, non MSVC compiler found! +#endif + +struct subprocess_s; + +enum subprocess_option_e { + // stdout and stderr are the same FILE. + subprocess_option_combined_stdout_stderr = 0x1, + + // The child process should inherit the environment variables of the parent. + subprocess_option_inherit_environment = 0x2, + + // Enable asynchronous reading of stdout/stderr before it has completed. + subprocess_option_enable_async = 0x4, + + // Enable the child process to be spawned with no window visible if supported + // by the platform. + subprocess_option_no_window = 0x8, + + // Search for program names in the PATH variable. Always enabled on Windows. + // Note: this will **not** search for paths in any provided custom environment + // and instead uses the PATH of the spawning process. + subprocess_option_search_user_path = 0x10 +}; + +#if defined(__cplusplus) +extern "C" { +#endif + +/// @brief Create a process. +/// @param command_line An array of strings for the command line to execute for +/// this process. The last element must be NULL to signify the end of the array. +/// The memory backing this parameter only needs to persist until this function +/// returns. +/// @param options A bit field of subprocess_option_e's to pass. +/// @param out_process The newly created process. +/// @return On success zero is returned. +subprocess_weak int subprocess_create(const char *const command_line[], + int options, + struct subprocess_s *const out_process); + +/// @brief Create a process (extended create). +/// @param command_line An array of strings for the command line to execute for +/// this process. The last element must be NULL to signify the end of the array. +/// The memory backing this parameter only needs to persist until this function +/// returns. +/// @param options A bit field of subprocess_option_e's to pass. +/// @param environment An optional array of strings for the environment to use +/// for a child process (each element of the form FOO=BAR). The last element +/// must be NULL to signify the end of the array. +/// @param out_process The newly created process. +/// @return On success zero is returned. +/// +/// If `options` contains `subprocess_option_inherit_environment`, then +/// `environment` must be NULL. +subprocess_weak int +subprocess_create_ex(const char *const command_line[], int options, + const char *const environment[], + struct subprocess_s *const out_process); + +/// @brief Get the standard input file for a process. +/// @param process The process to query. +/// @return The file for standard input of the process. +/// +/// The file returned can be written to by the parent process to feed data to +/// the standard input of the process. +subprocess_pure subprocess_weak FILE * +subprocess_stdin(const struct subprocess_s *const process); + +/// @brief Get the standard output file for a process. +/// @param process The process to query. +/// @return The file for standard output of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard output of the child process. +subprocess_pure subprocess_weak FILE * +subprocess_stdout(const struct subprocess_s *const process); + +/// @brief Get the standard error file for a process. +/// @param process The process to query. +/// @return The file for standard error of the process. +/// +/// The file returned can be read from by the parent process to read data from +/// the standard error of the child process. +/// +/// If the process was created with the subprocess_option_combined_stdout_stderr +/// option bit set, this function will return NULL, and the subprocess_stdout +/// function should be used for both the standard output and error combined. +subprocess_pure subprocess_weak FILE * +subprocess_stderr(const struct subprocess_s *const process); + +/// @brief Wait for a process to finish execution. +/// @param process The process to wait for. +/// @param out_return_code The return code of the returned process (can be +/// NULL). +/// @return On success zero is returned. +/// +/// Joining a process will close the stdin pipe to the process. +subprocess_weak int subprocess_join(struct subprocess_s *const process, + int *const out_return_code); + +/// @brief Destroy a previously created process. +/// @param process The process to destroy. +/// @return On success zero is returned. +/// +/// If the process to be destroyed had not finished execution, it may out live +/// the parent process. +subprocess_weak int subprocess_destroy(struct subprocess_s *const process); + +/// @brief Terminate a previously created process. +/// @param process The process to terminate. +/// @return On success zero is returned. +/// +/// If the process to be destroyed had not finished execution, it will be +/// terminated (i.e killed). +subprocess_weak int subprocess_terminate(struct subprocess_s *const process); + +/// @brief Read the standard output from the child process. +/// @param process The process to read from. +/// @param buffer The buffer to read into. +/// @param size The maximum number of bytes to read. +/// @return The number of bytes actually read into buffer. Can only be 0 if the +/// process has complete. +/// +/// The only safe way to read from the standard output of a process during it's +/// execution is to use the `subprocess_option_enable_async` option in +/// conjuction with this method. +subprocess_weak unsigned +subprocess_read_stdout(struct subprocess_s *const process, char *const buffer, + unsigned size); + +/// @brief Read the standard error from the child process. +/// @param process The process to read from. +/// @param buffer The buffer to read into. +/// @param size The maximum number of bytes to read. +/// @return The number of bytes actually read into buffer. Can only be 0 if the +/// process has complete. +/// +/// The only safe way to read from the standard error of a process during it's +/// execution is to use the `subprocess_option_enable_async` option in +/// conjuction with this method. +subprocess_weak unsigned +subprocess_read_stderr(struct subprocess_s *const process, char *const buffer, + unsigned size); + +/// @brief Returns if the subprocess is currently still alive and executing. +/// @param process The process to check. +/// @return If the process is still alive non-zero is returned. +subprocess_weak int subprocess_alive(struct subprocess_s *const process); + +#if defined(__cplusplus) +#define SUBPROCESS_CAST(type, x) static_cast(x) +#define SUBPROCESS_PTR_CAST(type, x) reinterpret_cast(x) +#define SUBPROCESS_CONST_CAST(type, x) const_cast(x) +#define SUBPROCESS_NULL NULL +#else +#define SUBPROCESS_CAST(type, x) ((type)(x)) +#define SUBPROCESS_PTR_CAST(type, x) ((type)(x)) +#define SUBPROCESS_CONST_CAST(type, x) ((type)(x)) +#define SUBPROCESS_NULL 0 +#endif + +#if !defined(_WIN32) +#include +#include +#include +#include +#include +#include +#endif + +#if defined(_WIN32) + +#if (_MSC_VER < 1920) +#ifdef _WIN64 +typedef __int64 subprocess_intptr_t; +typedef unsigned __int64 subprocess_size_t; +#else +typedef int subprocess_intptr_t; +typedef unsigned int subprocess_size_t; +#endif +#else +#include + +typedef intptr_t subprocess_intptr_t; +typedef size_t subprocess_size_t; +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-identifier" +#endif + +typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION; +typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES; +typedef struct _STARTUPINFOA *LPSTARTUPINFOA; +typedef struct _OVERLAPPED *LPOVERLAPPED; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#ifdef _MSC_VER +#pragma warning(push, 1) +#endif +#ifdef __MINGW32__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#endif + +struct subprocess_subprocess_information_s { + void *hProcess; + void *hThread; + unsigned long dwProcessId; + unsigned long dwThreadId; +}; + +struct subprocess_security_attributes_s { + unsigned long nLength; + void *lpSecurityDescriptor; + int bInheritHandle; +}; + +struct subprocess_startup_info_s { + unsigned long cb; + char *lpReserved; + char *lpDesktop; + char *lpTitle; + unsigned long dwX; + unsigned long dwY; + unsigned long dwXSize; + unsigned long dwYSize; + unsigned long dwXCountChars; + unsigned long dwYCountChars; + unsigned long dwFillAttribute; + unsigned long dwFlags; + unsigned short wShowWindow; + unsigned short cbReserved2; + unsigned char *lpReserved2; + void *hStdInput; + void *hStdOutput; + void *hStdError; +}; + +struct subprocess_overlapped_s { + uintptr_t Internal; + uintptr_t InternalHigh; + union { + struct { + unsigned long Offset; + unsigned long OffsetHigh; + } DUMMYSTRUCTNAME; + void *Pointer; + } DUMMYUNIONNAME; + + void *hEvent; +}; + +#ifdef __MINGW32__ +#pragma GCC diagnostic pop +#endif +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +__declspec(dllimport) unsigned long __stdcall GetLastError(void); +__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long, + unsigned long); +__declspec(dllimport) int __stdcall CreatePipe(void **, void **, + LPSECURITY_ATTRIBUTES, + unsigned long); +__declspec(dllimport) void *__stdcall CreateNamedPipeA( + const char *, unsigned long, unsigned long, unsigned long, unsigned long, + unsigned long, unsigned long, LPSECURITY_ATTRIBUTES); +__declspec(dllimport) int __stdcall ReadFile(void *, void *, unsigned long, + unsigned long *, LPOVERLAPPED); +__declspec(dllimport) unsigned long __stdcall GetCurrentProcessId(void); +__declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +__declspec(dllimport) void *__stdcall CreateFileA(const char *, unsigned long, + unsigned long, + LPSECURITY_ATTRIBUTES, + unsigned long, unsigned long, + void *); +__declspec(dllimport) void *__stdcall CreateEventA(LPSECURITY_ATTRIBUTES, int, + int, const char *); +__declspec(dllimport) int __stdcall CreateProcessA( + const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int, + unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION); +__declspec(dllimport) int __stdcall CloseHandle(void *); +__declspec(dllimport) unsigned long __stdcall WaitForSingleObject( + void *, unsigned long); +__declspec(dllimport) int __stdcall GetExitCodeProcess( + void *, unsigned long *lpExitCode); +__declspec(dllimport) int __stdcall TerminateProcess(void *, unsigned int); +__declspec(dllimport) unsigned long __stdcall WaitForMultipleObjects( + unsigned long, void *const *, int, unsigned long); +__declspec(dllimport) int __stdcall GetOverlappedResult(void *, LPOVERLAPPED, + unsigned long *, int); + +#if defined(_DLL) +#define SUBPROCESS_DLLIMPORT __declspec(dllimport) +#else +#define SUBPROCESS_DLLIMPORT +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-identifier" +#endif + +SUBPROCESS_DLLIMPORT int __cdecl _fileno(FILE *); +SUBPROCESS_DLLIMPORT int __cdecl _open_osfhandle(subprocess_intptr_t, int); +SUBPROCESS_DLLIMPORT subprocess_intptr_t __cdecl _get_osfhandle(int); + +#ifndef __MINGW32__ +void *__cdecl _alloca(subprocess_size_t); +#else +#include +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#else +typedef size_t subprocess_size_t; +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif +struct subprocess_s { + FILE *stdin_file; + FILE *stdout_file; + FILE *stderr_file; + +#if defined(_WIN32) + void *hProcess; + void *hStdInput; + void *hEventOutput; + void *hEventError; +#else + pid_t child; + int return_status; +#endif + + subprocess_size_t alive; +}; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#if defined(_WIN32) +subprocess_weak int subprocess_create_named_pipe_helper(void **rd, void **wr); +int subprocess_create_named_pipe_helper(void **rd, void **wr) { + const unsigned long pipeAccessInbound = 0x00000001; + const unsigned long fileFlagOverlapped = 0x40000000; + const unsigned long pipeTypeByte = 0x00000000; + const unsigned long pipeWait = 0x00000000; + const unsigned long genericWrite = 0x40000000; + const unsigned long openExisting = 3; + const unsigned long fileAttributeNormal = 0x00000080; + const void *const invalidHandleValue = + SUBPROCESS_PTR_CAST(void *, ~(SUBPROCESS_CAST(subprocess_intptr_t, 0))); + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char name[256] = {0}; + static subprocess_tls long index = 0; + const long unique = index++; + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#pragma warning(push, 1) +#pragma warning(disable : 4996) + _snprintf(name, sizeof(name) - 1, + "\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld", + GetCurrentProcessId(), GetCurrentThreadId(), unique); +#pragma warning(pop) +#else + snprintf(name, sizeof(name) - 1, + "\\\\.\\pipe\\sheredom_subprocess_h.%08lx.%08lx.%ld", + GetCurrentProcessId(), GetCurrentThreadId(), unique); +#endif + + *rd = + CreateNamedPipeA(name, pipeAccessInbound | fileFlagOverlapped, + pipeTypeByte | pipeWait, 1, 4096, 4096, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr)); + + if (invalidHandleValue == *rd) { + return -1; + } + + *wr = CreateFileA(name, genericWrite, SUBPROCESS_NULL, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + openExisting, fileAttributeNormal, SUBPROCESS_NULL); + + if (invalidHandleValue == *wr) { + return -1; + } + + return 0; +} +#endif + +int subprocess_create(const char *const commandLine[], int options, + struct subprocess_s *const out_process) { + return subprocess_create_ex(commandLine, options, SUBPROCESS_NULL, + out_process); +} + +int subprocess_create_ex(const char *const commandLine[], int options, + const char *const environment[], + struct subprocess_s *const out_process) { +#if defined(_WIN32) + int fd; + void *rd, *wr; + char *commandLineCombined; + subprocess_size_t len; + int i, j; + int need_quoting; + unsigned long flags = 0; + const unsigned long startFUseStdHandles = 0x00000100; + const unsigned long handleFlagInherit = 0x00000001; + const unsigned long createNoWindow = 0x08000000; + struct subprocess_subprocess_information_s processInfo; + struct subprocess_security_attributes_s saAttr = {sizeof(saAttr), + SUBPROCESS_NULL, 1}; + char *used_environment = SUBPROCESS_NULL; + struct subprocess_startup_info_s startInfo = {0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL, + SUBPROCESS_NULL}; + + startInfo.cb = sizeof(startInfo); + startInfo.dwFlags = startFUseStdHandles; + + if (subprocess_option_no_window == (options & subprocess_option_no_window)) { + flags |= createNoWindow; + } + + if (subprocess_option_inherit_environment != + (options & subprocess_option_inherit_environment)) { + if (SUBPROCESS_NULL == environment) { + used_environment = SUBPROCESS_CONST_CAST(char *, "\0\0"); + } else { + // We always end with two null terminators. + len = 2; + + for (i = 0; environment[i]; i++) { + for (j = 0; '\0' != environment[i][j]; j++) { + len++; + } + + // For the null terminator too. + len++; + } + + used_environment = SUBPROCESS_CAST(char *, _alloca(len)); + + // Re-use len for the insertion position + len = 0; + + for (i = 0; environment[i]; i++) { + for (j = 0; '\0' != environment[i][j]; j++) { + used_environment[len++] = environment[i][j]; + } + + used_environment[len++] = '\0'; + } + + // End with the two null terminators. + used_environment[len++] = '\0'; + used_environment[len++] = '\0'; + } + } else { + if (SUBPROCESS_NULL != environment) { + return -1; + } + } + + if (!CreatePipe(&rd, &wr, SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), + 0)) { + return -1; + } + + if (!SetHandleInformation(wr, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, wr), 0); + + if (-1 != fd) { + out_process->stdin_file = _fdopen(fd, "wb"); + + if (SUBPROCESS_NULL == out_process->stdin_file) { + return -1; + } + } + + startInfo.hStdInput = rd; + + if (options & subprocess_option_enable_async) { + if (subprocess_create_named_pipe_helper(&rd, &wr)) { + return -1; + } + } else { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { + return -1; + } + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); + + if (-1 != fd) { + out_process->stdout_file = _fdopen(fd, "rb"); + + if (SUBPROCESS_NULL == out_process->stdout_file) { + return -1; + } + } + + startInfo.hStdOutput = wr; + + if (subprocess_option_combined_stdout_stderr == + (options & subprocess_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + startInfo.hStdError = startInfo.hStdOutput; + } else { + if (options & subprocess_option_enable_async) { + if (subprocess_create_named_pipe_helper(&rd, &wr)) { + return -1; + } + } else { + if (!CreatePipe(&rd, &wr, + SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 0)) { + return -1; + } + } + + if (!SetHandleInformation(rd, handleFlagInherit, 0)) { + return -1; + } + + fd = _open_osfhandle(SUBPROCESS_PTR_CAST(subprocess_intptr_t, rd), 0); + + if (-1 != fd) { + out_process->stderr_file = _fdopen(fd, "rb"); + + if (SUBPROCESS_NULL == out_process->stderr_file) { + return -1; + } + } + + startInfo.hStdError = wr; + } + + if (options & subprocess_option_enable_async) { + out_process->hEventOutput = + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); + out_process->hEventError = + CreateEventA(SUBPROCESS_PTR_CAST(LPSECURITY_ATTRIBUTES, &saAttr), 1, 1, + SUBPROCESS_NULL); + } else { + out_process->hEventOutput = SUBPROCESS_NULL; + out_process->hEventError = SUBPROCESS_NULL; + } + + // Combine commandLine together into a single string + len = 0; + for (i = 0; commandLine[i]; i++) { + // for the trailing \0 + len++; + + // Quote the argument if it has a space in it + if (strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL) + len += 2; + + for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + len++; + } + + break; + case '"': + len++; + break; + } + len++; + } + } + + commandLineCombined = SUBPROCESS_CAST(char *, _alloca(len)); + + if (!commandLineCombined) { + return -1; + } + + // Gonna re-use len to store the write index into commandLineCombined + len = 0; + + for (i = 0; commandLine[i]; i++) { + if (0 != i) { + commandLineCombined[len++] = ' '; + } + + need_quoting = strpbrk(commandLine[i], "\t\v ") != SUBPROCESS_NULL; + if (need_quoting) { + commandLineCombined[len++] = '"'; + } + + for (j = 0; '\0' != commandLine[i][j]; j++) { + switch (commandLine[i][j]) { + default: + break; + case '\\': + if (commandLine[i][j + 1] == '"') { + commandLineCombined[len++] = '\\'; + } + + break; + case '"': + commandLineCombined[len++] = '\\'; + break; + } + + commandLineCombined[len++] = commandLine[i][j]; + } + if (need_quoting) { + commandLineCombined[len++] = '"'; + } + } + + commandLineCombined[len] = '\0'; + + if (!CreateProcessA( + SUBPROCESS_NULL, + commandLineCombined, // command line + SUBPROCESS_NULL, // process security attributes + SUBPROCESS_NULL, // primary thread security attributes + 1, // handles are inherited + flags, // creation flags + used_environment, // used environment + SUBPROCESS_NULL, // use parent's current directory + SUBPROCESS_PTR_CAST(LPSTARTUPINFOA, + &startInfo), // STARTUPINFO pointer + SUBPROCESS_PTR_CAST(LPPROCESS_INFORMATION, &processInfo))) { + return -1; + } + + out_process->hProcess = processInfo.hProcess; + + out_process->hStdInput = startInfo.hStdInput; + + // We don't need the handle of the primary thread in the called process. + CloseHandle(processInfo.hThread); + + if (SUBPROCESS_NULL != startInfo.hStdOutput) { + CloseHandle(startInfo.hStdOutput); + + if (startInfo.hStdError != startInfo.hStdOutput) { + CloseHandle(startInfo.hStdError); + } + } + + out_process->alive = 1; + + return 0; +#else + int stdinfd[2]; + int stdoutfd[2]; + int stderrfd[2]; + pid_t child; + extern char **environ; + char *const empty_environment[1] = {SUBPROCESS_NULL}; + posix_spawn_file_actions_t actions; + char *const *used_environment; + + if (subprocess_option_inherit_environment == + (options & subprocess_option_inherit_environment)) { + if (SUBPROCESS_NULL != environment) { + return -1; + } + } + + if (0 != pipe(stdinfd)) { + return -1; + } + + if (0 != pipe(stdoutfd)) { + return -1; + } + + if (subprocess_option_combined_stdout_stderr != + (options & subprocess_option_combined_stdout_stderr)) { + if (0 != pipe(stderrfd)) { + return -1; + } + } + + if (environment) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + used_environment = (char *const *)environment; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } else if (subprocess_option_inherit_environment == + (options & subprocess_option_inherit_environment)) { + used_environment = environ; + } else { + used_environment = empty_environment; + } + + if (0 != posix_spawn_file_actions_init(&actions)) { + return -1; + } + + // Close the stdin write end + if (0 != posix_spawn_file_actions_addclose(&actions, stdinfd[1])) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + // Map the read end to stdin + if (0 != + posix_spawn_file_actions_adddup2(&actions, stdinfd[0], STDIN_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + // Close the stdout read end + if (0 != posix_spawn_file_actions_addclose(&actions, stdoutfd[0])) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + // Map the write end to stdout + if (0 != + posix_spawn_file_actions_adddup2(&actions, stdoutfd[1], STDOUT_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + + if (subprocess_option_combined_stdout_stderr == + (options & subprocess_option_combined_stdout_stderr)) { + if (0 != posix_spawn_file_actions_adddup2(&actions, STDOUT_FILENO, + STDERR_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } else { + // Close the stderr read end + if (0 != posix_spawn_file_actions_addclose(&actions, stderrfd[0])) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + // Map the write end to stdout + if (0 != posix_spawn_file_actions_adddup2(&actions, stderrfd[1], + STDERR_FILENO)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif + if (subprocess_option_search_user_path == + (options & subprocess_option_search_user_path)) { + if (0 != posix_spawnp(&child, commandLine[0], &actions, SUBPROCESS_NULL, + (char *const *)commandLine, used_environment)) { + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } else { + if (0 != posix_spawn(&child, commandLine[0], &actions, SUBPROCESS_NULL, + (char *const *)commandLine, used_environment)) { + + posix_spawn_file_actions_destroy(&actions); + return -1; + } + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + // Close the stdin read end + close(stdinfd[0]); + // Store the stdin write end + out_process->stdin_file = fdopen(stdinfd[1], "wb"); + + // Close the stdout write end + close(stdoutfd[1]); + // Store the stdout read end + out_process->stdout_file = fdopen(stdoutfd[0], "rb"); + + if (subprocess_option_combined_stdout_stderr == + (options & subprocess_option_combined_stdout_stderr)) { + out_process->stderr_file = out_process->stdout_file; + } else { + // Close the stderr write end + close(stderrfd[1]); + // Store the stderr read end + out_process->stderr_file = fdopen(stderrfd[0], "rb"); + } + + // Store the child's pid + out_process->child = child; + + out_process->alive = 1; + + posix_spawn_file_actions_destroy(&actions); + return 0; +#endif +} + +FILE *subprocess_stdin(const struct subprocess_s *const process) { + return process->stdin_file; +} + +FILE *subprocess_stdout(const struct subprocess_s *const process) { + return process->stdout_file; +} + +FILE *subprocess_stderr(const struct subprocess_s *const process) { + if (process->stdout_file != process->stderr_file) { + return process->stderr_file; + } else { + return SUBPROCESS_NULL; + } +} + +int subprocess_join(struct subprocess_s *const process, + int *const out_return_code) { +#if defined(_WIN32) + const unsigned long infinite = 0xFFFFFFFF; + + if (process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = SUBPROCESS_NULL; + } + + if (process->hStdInput) { + CloseHandle(process->hStdInput); + process->hStdInput = SUBPROCESS_NULL; + } + + WaitForSingleObject(process->hProcess, infinite); + + if (out_return_code) { + if (!GetExitCodeProcess( + process->hProcess, + SUBPROCESS_PTR_CAST(unsigned long *, out_return_code))) { + return -1; + } + } + + process->alive = 0; + + return 0; +#else + int status; + + if (process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = SUBPROCESS_NULL; + } + + if (process->child) { + if (process->child != waitpid(process->child, &status, 0)) { + return -1; + } + + process->child = 0; + + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + process->alive = 0; + } + + if (out_return_code) { + *out_return_code = process->return_status; + } + + return 0; +#endif +} + +int subprocess_destroy(struct subprocess_s *const process) { + if (process->stdin_file) { + fclose(process->stdin_file); + process->stdin_file = SUBPROCESS_NULL; + } + + if (process->stdout_file) { + fclose(process->stdout_file); + + if (process->stdout_file != process->stderr_file) { + fclose(process->stderr_file); + } + + process->stdout_file = SUBPROCESS_NULL; + process->stderr_file = SUBPROCESS_NULL; + } + +#if defined(_WIN32) + if (process->hProcess) { + CloseHandle(process->hProcess); + process->hProcess = SUBPROCESS_NULL; + + if (process->hStdInput) { + CloseHandle(process->hStdInput); + } + + if (process->hEventOutput) { + CloseHandle(process->hEventOutput); + } + + if (process->hEventError) { + CloseHandle(process->hEventError); + } + } +#endif + + return 0; +} + +int subprocess_terminate(struct subprocess_s *const process) { +#if defined(_WIN32) + unsigned int killed_process_exit_code; + int success_terminate; + int windows_call_result; + + killed_process_exit_code = 99; + windows_call_result = + TerminateProcess(process->hProcess, killed_process_exit_code); + success_terminate = (windows_call_result == 0) ? 1 : 0; + return success_terminate; +#else + int result; + result = kill(process->child, 9); + return result; +#endif +} + +unsigned subprocess_read_stdout(struct subprocess_s *const process, + char *const buffer, unsigned size) { +#if defined(_WIN32) + void *handle; + unsigned long bytes_read = 0; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; + overlapped.hEvent = process->hEventOutput; + + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stdout_file))); + + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { + const unsigned long errorIoPending = 997; + unsigned long error = GetLastError(); + + // Means we've got an async read! + if (error == errorIoPending) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { + const unsigned long errorIoIncomplete = 996; + const unsigned long errorHandleEOF = 38; + error = GetLastError(); + + if ((error != errorIoIncomplete) && (error != errorHandleEOF)) { + return 0; + } + } + } + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#else + const int fd = fileno(process->stdout_file); + const ssize_t bytes_read = read(fd, buffer, size); + + if (bytes_read < 0) { + return 0; + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#endif +} + +unsigned subprocess_read_stderr(struct subprocess_s *const process, + char *const buffer, unsigned size) { +#if defined(_WIN32) + void *handle; + unsigned long bytes_read = 0; + struct subprocess_overlapped_s overlapped = {0, 0, {{0, 0}}, SUBPROCESS_NULL}; + overlapped.hEvent = process->hEventError; + + handle = SUBPROCESS_PTR_CAST(void *, + _get_osfhandle(_fileno(process->stderr_file))); + + if (!ReadFile(handle, buffer, size, &bytes_read, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped))) { + const unsigned long errorIoPending = 997; + unsigned long error = GetLastError(); + + // Means we've got an async read! + if (error == errorIoPending) { + if (!GetOverlappedResult(handle, + SUBPROCESS_PTR_CAST(LPOVERLAPPED, &overlapped), + &bytes_read, 1)) { + const unsigned long errorIoIncomplete = 996; + const unsigned long errorHandleEOF = 38; + error = GetLastError(); + + if ((error != errorIoIncomplete) && (error != errorHandleEOF)) { + return 0; + } + } + } + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#else + const int fd = fileno(process->stderr_file); + const ssize_t bytes_read = read(fd, buffer, size); + + if (bytes_read < 0) { + return 0; + } + + return SUBPROCESS_CAST(unsigned, bytes_read); +#endif +} + +int subprocess_alive(struct subprocess_s *const process) { + int is_alive = SUBPROCESS_CAST(int, process->alive); + + if (!is_alive) { + return 0; + } +#if defined(_WIN32) + { + const unsigned long zero = 0x0; + const unsigned long wait_object_0 = 0x00000000L; + + is_alive = wait_object_0 != WaitForSingleObject(process->hProcess, zero); + } +#else + { + int status; + is_alive = 0 == waitpid(process->child, &status, WNOHANG); + + // If the process was successfully waited on we need to cleanup now. + if (!is_alive) { + if (WIFEXITED(status)) { + process->return_status = WEXITSTATUS(status); + } else { + process->return_status = EXIT_FAILURE; + } + + // Since we've already successfully waited on the process, we need to wipe + // the child now. + process->child = 0; + + if (subprocess_join(process, SUBPROCESS_NULL)) { + return -1; + } + } + } +#endif + + if (!is_alive) { + process->alive = 0; + } + + return is_alive; +} + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif /* SHEREDOM_SUBPROCESS_H_INCLUDED */ diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index 2795209..61225df 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -11,4 +11,15 @@ endif() gz_build_tests(TYPE INTEGRATION SOURCES ${tests}) +add_executable(subprocess_executable subprocess/subprocess_main.cc) +target_include_directories(subprocess_executable PUBLIC + ${PROJECT_SOURCE_DIR}/cli/include/ + ${PROJECT_SOURCE_DIR}/cli/include/vendored-cli/) +target_link_libraries(subprocess_executable ${PROJECT_LIBRARY_TARGET_NAME}) + +if(TARGET INTEGRATION_subprocess_TEST) + target_compile_definitions(INTEGRATION_subprocess_TEST PRIVATE + "SUBPROCESS_EXECUTABLE_PATH=\"$\"") +endif() + add_subdirectory(implptr) diff --git a/test/integration/subprocess/subprocess_main.cc b/test/integration/subprocess/subprocess_main.cc new file mode 100644 index 0000000..436172e --- /dev/null +++ b/test/integration/subprocess/subprocess_main.cc @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2023 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include + +class OutputSink +{ + public: OutputSink(const std::string &_dest): + dest(_dest) + { + } + + public: void Write(const std::string& val) + { + if (dest == "cout" || dest == "both") + { + std::cout << val << std::endl; + + } + else if (dest == "cerr" || dest == "both") + { + std::cerr << val << std::endl; + } + return; + } + + private: std::string dest; +}; + +////////////////////////////////////////////////// +int main(int argc, char **argv) +{ + CLI::App app("Subprocess test app"); + + std::string output; + app.add_option("--output", output, "output destination"); + + int iters = 0; + app.add_option("--iterations", iters, "number of iterations to run"); + + int iter_ms = 0; + app.add_option("--iteration-ms", iter_ms, "length of one iteration"); + + CLI11_PARSE(app, argc, argv); + + auto sink = OutputSink(output); + for (int ii = 0; ii < iters; ++ii) + { + sink.Write("Iteration: " + std::to_string(ii)); + std::this_thread::sleep_for(std::chrono::milliseconds(iter_ms)); + } + + std::string env_var; + if(gz::utils::env("ENV_VAR", env_var)) + { + sink.Write("ENV_VAR=" + env_var); + } + return 0; +} diff --git a/test/integration/subprocess_TEST.cc b/test/integration/subprocess_TEST.cc new file mode 100644 index 0000000..32c6ed4 --- /dev/null +++ b/test/integration/subprocess_TEST.cc @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ + +#include + +#include + +#include +#include + +using Subprocess = gz::utils::Subprocess; + +static constexpr const char * kExecutablePath = SUBPROCESS_EXECUTABLE_PATH; + +///////////////////////////////////////////////// +TEST(Subprocess, CreateInvalid) +{ + auto proc = Subprocess({"an_executable_that_will_never_exist"}); + EXPECT_FALSE(proc.Alive()); +} + +///////////////////////////////////////////////// +TEST(Subprocess, CreateValid) +{ + auto proc = Subprocess({kExecutablePath, "--help"}); + + // Block until the executable is done + auto ret = proc.Join(); + EXPECT_EQ(0u, ret); + + EXPECT_FALSE(proc.Alive()); + + auto cout = proc.Stdout(); + auto cerr = proc.Stdout(); + + EXPECT_FALSE(cout.empty()); + EXPECT_TRUE(cerr.empty()); +} + +///////////////////////////////////////////////// +TEST(Subprocess, Cout) +{ + auto proc = Subprocess({kExecutablePath, + "--output=cout", + "--iterations=10", + "--iteration-ms=10"}); + + EXPECT_TRUE(proc.Alive()); + + // Block until the executable is done + auto ret = proc.Join(); + EXPECT_EQ(0u, ret); + + auto cout = proc.Stdout(); + auto cerr = proc.Stderr(); + EXPECT_FALSE(cout.empty()); + EXPECT_TRUE(cerr.empty()); +} + +///////////////////////////////////////////////// +TEST(Subprocess, Cerr) +{ + auto proc = Subprocess({kExecutablePath, + "--output=cerr", + "--iterations=10", + "--iteration-ms=10"}); + EXPECT_TRUE(proc.Alive()); + + // Block until the executable is done + auto ret = proc.Join(); + EXPECT_EQ(0u, ret); + + EXPECT_FALSE(proc.Alive()); + auto cout = proc.Stdout(); + auto cerr = proc.Stderr(); + EXPECT_TRUE(cout.empty()); + EXPECT_FALSE(cerr.empty()); +} + +///////////////////////////////////////////////// +TEST(Subprocess, Environment) +{ + { + auto proc = Subprocess({kExecutablePath, "--output=cout"}, + {"ENV_VAR=foobar"}); + // Block until the executable is done + auto ret = proc.Join(); + EXPECT_EQ(0u, ret); + + auto cout = proc.Stdout(); + EXPECT_NE(std::string::npos, cout.find("ENV_VAR=foobar")); + } + + { + auto proc = Subprocess({kExecutablePath, "--output=cerr"}, + {"ENV_VAR=foobar2"}); + // Block until the executable is done + auto ret = proc.Join(); + EXPECT_EQ(0u, ret); + + auto cerr = proc.Stderr(); + EXPECT_NE(std::string::npos, cerr.find("ENV_VAR=foobar2")); + } + + { + auto proc = Subprocess({kExecutablePath}, + {"ENV_VAR=foobar3"}); + // Block until the executable is done + auto ret = proc.Join(); + EXPECT_EQ(0u, ret); + + auto cout = proc.Stdout(); + auto cerr = proc.Stderr(); + EXPECT_EQ(std::string::npos, cerr.find("ENV_VAR=foobar3")); + EXPECT_EQ(std::string::npos, cout.find("ENV_VAR=foobar3")); + } +} diff --git a/tutorials/installation.md b/tutorials/installation.md index 59eafc5..0e0f183 100644 --- a/tutorials/installation.md +++ b/tutorials/installation.md @@ -7,13 +7,13 @@ Windows via either a binary distribution or from source. [Install](#install) -* [Binary Install](#binary-install) +- [Binary Install](#binary-install) -* [Source Install](#source-install) +- [Source Install](#source-install) - * [Prerequisites](#prerequisites) + - [Prerequisites](#prerequisites) - * [Building from Source](#building-from-source) + - [Building from Source](#building-from-source) # Install @@ -39,7 +39,7 @@ wget http://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add - Install Gazebo Utils: -``` +```{.sh} sudo apt install libgz-utils<#>-dev ``` @@ -49,15 +49,17 @@ which version you need. ### macOS On macOS, add OSRF packages: - ``` - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - brew tap osrf/simulation - ``` + +```{.sh} +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +brew tap osrf/simulation +``` Install Gazebo utils: - ``` - brew install gz-utils<#> - ``` + +```{.sh} +brew install gz-utils<#> +``` Be sure to replace `<#>` with a number value, such as 1 or 2, depending on which version you need. @@ -68,13 +70,15 @@ Install [Conda package management system](https://docs.conda.io/projects/conda/e Miniconda suffices. Create if necessary, and activate a Conda environment: -``` + +```{.sh} conda create -n gz-ws conda activate gz-ws ``` Install `gz-utils`: -``` + +```{.sh} conda install libgz-utils<#> --channel conda-forge ``` @@ -89,89 +93,101 @@ necessary prerequisites followed by building from source. ### Ubuntu 1. Install tools - ``` - sudo apt install -y build-essential cmake git gnupg lsb-release wget - ``` + + ```{.sh} + sudo apt install -y build-essential cmake git gnupg lsb-release wget + ``` 2. Clone the repository - ``` - git clone https://github.com/gazebosim/gz-utils -b gz-utils<#> - ``` - Be sure to replace `<#>` with a number value, such as 1 or 2, depending on - which version you need. + ```{.sh} + git clone https://github.com/gazebosim/gz-utils -b gz-utils<#> + ``` + + Be sure to replace `<#>` with a number value, such as 1 or 2, depending on + which version you need. 3. Install dependencies - ``` - sudo apt -y install \ - $(sort -u $(find . -iname 'packages-'`lsb_release -cs`'.apt' -o -iname 'packages.apt' | tr '\n' ' ')) - ``` + ```{.sh} + sudo apt -y install \ + $(sort -u $(find . -iname 'packages-'`lsb_release -cs`'.apt' -o -iname 'packages.apt' | tr '\n' ' ')) + ``` 4. Configure and build - ``` - cd gz-utils; mkdir build; cd build; cmake ..; make - ``` + ```{.sh} + cd gz-utils; mkdir build; cd build; cmake ..; make + ``` 5. Optionally, install Gazebo Utils - ``` - sudo make install - ``` + ```{.sh} + sudo make install + ``` ### macOS 1. Clone the repository - ``` - git clone https://github.com/gazebosim/gz-utils -b gz-utils<#> - ``` - Be sure to replace `<#>` with a number value, such as 1 or 2, depending on - which version you need. + + ```{.sh} + git clone https://github.com/gazebosim/gz-utils -b gz-utils<#> + ``` + + Be sure to replace `<#>` with a number value, such as 1 or 2, depending on + which version you need. 2. Install dependencies - ``` - brew install --only-dependencies gz-utils<#> - ``` - Be sure to replace `<#>` with a number value, such as 1 or 2, depending on - which version you need. + + ```{.sh} + brew install --only-dependencies gz-utils<#> + ``` + + Be sure to replace `<#>` with a number value, such as 1 or 2, depending on + which version you need. 3. Configure and build - ``` - cd gz-utils - mkdir build - cd build - cmake .. - make - ``` + + ```{.sh} + cd gz-utils + mkdir build + cd build + cmake .. + make + ``` 4. Optionally, install - ``` - sudo make install - ``` + + ```{.sh} + sudo make install + ``` + ### Windows This assumes you have created and activated a Conda environment while installing the Prerequisites. 1. Navigate to where you would like to build the library, and clone the repository. - ``` - # Optionally, append `-b gz-utils#` (replace # with a number) to check out a specific version - git clone https://github.com/gazebosim/gz-utils.git - ``` + + ```{.sh} + # Optionally, append `-b gz-utils#` (replace # with a number) to check out a specific version + git clone https://github.com/gazebosim/gz-utils.git + ``` 2. Configure and build - ``` - cd gz-utils - mkdir build - cd build - cmake .. -DBUILD_TESTING=OFF # Optionally, -DCMAKE_INSTALL_PREFIX=path\to\install - cmake --build . --config Release - ``` + + ```{.sh} + cd gz-utils + mkdir build + cd build + cmake .. -DBUILD_TESTING=OFF # Optionally, -DCMAKE_INSTALL_PREFIX=path\to\install + cmake --build . --config Release + ``` 3. Optionally, install - ``` - cmake --install . --config Release - ``` + + ```{.sh} + cmake --install . --config Release + ``` # Documentation @@ -180,28 +196,32 @@ API and tutorials can be found at [https://gazebosim.org/libs/utils](https://gaz You can also generate the documentation from a clone of this repository by following these steps. 1. You will need Doxygen. On Ubuntu Doxygen can be installed using - ``` - sudo apt-get install doxygen - ``` + + ```{.sh} + sudo apt-get install doxygen + ``` 2. Clone the repository - ``` - git clone https://github.com/gazebosim/gz-utils - ``` + + ```{.sh} + git clone https://github.com/gazebosim/gz-utils + ``` 3. Configure and build the documentation. - ``` - cd gz-utils - mkdir build - cd build - cmake ../ - make doc - ``` + + ```{.sh} + cd gz-utils + mkdir build + cd build + cmake ../ + make doc + ``` 4. View the documentation by running the following command from the build directory. - ``` - firefox doxygen/html/index.html - ``` + + ```{.sh} + firefox doxygen/html/index.html + ``` # Testing @@ -210,11 +230,13 @@ Follow these steps to run tests and static code analysis in your clone of this r 1. Follow the [source install instruction](#source-install). 2. Run tests. - ``` - make test - ``` + + ```{.sh} + make test + ``` 3. Static code checker. - ``` - make codecheck - ``` + + ```{.sh} + make codecheck + ```