diff --git a/.codacy.yml b/.codacy.yml index 8fd2cc96a..75a734f5d 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -9,8 +9,11 @@ engines: coverage: enabled: false cppcheck: - enabled: false - language: c++ + enabled: true + options: + suppress: + - missingIncludeSystem + languages: ignore: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f0a56cb7a..1cbd607ca 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -314,6 +314,29 @@ jobs: - name: Run tests run: ctest --output-on-failure -L Packaging working-directory: build + + install-module: + name: install module tests + runs-on: ubuntu-latest + container: gcc:15 + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: ./.github/actions/quick_cmake + with: + cmake-version: "4.2" + - uses: seanmiddleditch/gha-setup-ninja@master + - name: Configure + run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCLI11_MODULE_TESTS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install -DCMAKE_CXX_STANDARD=23 + - name: Build + run: cmake --build build -j2 + - name: install + run: cmake --install build + - name: Run tests + run: ctest --output-on-failure -L Packaging + working-directory: build + cmake-config-ubuntu-2204: name: CMake config check (Ubuntu 22.04) @@ -449,3 +472,9 @@ jobs: with: cmake-version: "4.1" if: success() || failure() + + - name: Check CMake 4.2 + uses: ./.github/actions/quick_cmake + with: + cmake-version: "4.2" + if: success() || failure() diff --git a/CMakeLists.txt b/CMakeLists.txt index ad9e201c7..bbd5b0964 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.14...4.1) +cmake_minimum_required(VERSION 3.14...4.2) # Note: this is a header only library. If you have an older CMake than 3.14, # just add the CLI11/include directory and that's all you need to do. @@ -65,6 +65,9 @@ endif() option(CLI11_WARNINGS_AS_ERRORS "Turn all warnings into errors (for CI)") option(CLI11_SINGLE_FILE "Generate a single header file") option(CLI11_PRECOMPILED "Generate a precompiled static library instead of a header-only" OFF) +cmake_dependent_option(CLI11_MODULE "Modify some code to support inclusion of CLI11 in a module" + OFF "CMAKE_CXX_STANDARD GREATER_EQUAL 20" OFF) + cmake_dependent_option(CLI11_SANITIZERS "Download the sanitizers CMake config" OFF "NOT CMAKE_VERSION VERSION_LESS 3.15" OFF) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1ad5890ea..1c9f3e396 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -184,6 +184,13 @@ jobs: vmImage: "ubuntu-latest" strategy: matrix: + gcc15_23: + containerImage: gcc:15 + cli11.std: 23 + cli11.options: + -DCMAKE_CXX_FLAGS="-Wstrict-overflow=5" + -DCLI11_ENABLE_EXTRA_VALIDATORS=1 -DCLI11_MODULE=ON + gcc13_17: containerImage: gcc:13 cli11.std: 17 diff --git a/book/chapters/installation.md b/book/chapters/installation.md index 322daf712..49a27b76f 100644 --- a/book/chapters/installation.md +++ b/book/chapters/installation.md @@ -176,19 +176,21 @@ Total Test time (real) = 0.34 sec For the curious, the CMake options and defaults are listed below. Most options default to off if CLI11 is used as a subdirectory in another project. -| Option | Description | -| ------------------------------ | -------------------------------------------------------------------------------- | -| `CLI11_SINGLE_FILE=ON` | Build the `CLI11.hpp` file from the sources. Requires Python (version 3 or 2.7). | -| `CLI11_PRECOMPILED=OFF` | generate a precompiled static library instead of header-only | -| `CLI11_SINGLE_FILE_TESTS=OFF` | Run the tests on the generated single file version as well | -| `CLI11_BUILD_DOCS=ON` | build CLI11 documentation and book | -| `CLI11_BUILD_EXAMPLES=ON` | Build the example programs. | -| `CLI11_BUILD_EXAMPLES_JSON=ON` | Build some additional example using json libraries | -| `CLI11_INSTALL=ON` | install CLI11 to the install folder during the install process | -| `CLI11_FULL_INSTALL=ON` | install all CLI11 headers/libraries regardless of other settings | -| `CLI11_FORCE_LIBCXX=OFF` | use libc++ instead of libstdc++ if building with clang on linux | -| `CLI11_CUDA_TESTS=OFF` | build the tests with NVCC | -| `CLI11_BUILD_TESTS=ON` | Build the tests. | +| Option | Description | +| --------------------------------- | -------------------------------------------------------------------------------- | +| `CLI11_SINGLE_FILE=ON` | Build the `CLI11.hpp` file from the sources. Requires Python (version 3 or 2.7). | +| `CLI11_PRECOMPILED=OFF` | Generate a precompiled static library instead of header-only | +| `CLI11_INSTALL_PACKAGE_TESTS=OFF` | Run tests checking the installation | +| `CLI11_MODULE_TEST=OFF` | Run a test checking that CLI11 works with modules | +| `CLI11_SINGLE_FILE_TESTS=OFF` | Run the tests on the generated single file version as well | +| `CLI11_BUILD_DOCS=ON` | Build CLI11 documentation and book | +| `CLI11_BUILD_EXAMPLES=ON` | Build the example programs. | +| `CLI11_BUILD_EXAMPLES_JSON=ON` | Build some additional example using json libraries | +| `CLI11_INSTALL=ON` | Install CLI11 to the install folder during the install process | +| `CLI11_FULL_INSTALL=ON` | Install all CLI11 headers/libraries regardless of other settings | +| `CLI11_FORCE_LIBCXX=OFF` | Use libc++ instead of libstdc++ if building with clang on linux | +| `CLI11_CUDA_TESTS=OFF` | Build the tests with NVCC | +| `CLI11_BUILD_TESTS=ON` | Build the tests. | [^1]: Docker is being used to create a pristine disposable environment; there is diff --git a/include/CLI/Macros.hpp b/include/CLI/Macros.hpp index 97dff7403..8caff0cc6 100644 --- a/include/CLI/Macros.hpp +++ b/include/CLI/Macros.hpp @@ -175,4 +175,11 @@ #else #define CLI11_INLINE inline #endif + +/** Module inline to support module operations**/ +#if defined CLI11_CPP17 +#define CLI11_MODULE_INLINE inline +#else +#define CLI11_MODULE_INLINE static +#endif // [CLI11:macros_hpp:end] diff --git a/include/CLI/StringTools.hpp b/include/CLI/StringTools.hpp index d7f5ce1ab..5d7601d12 100644 --- a/include/CLI/StringTools.hpp +++ b/include/CLI/StringTools.hpp @@ -45,7 +45,7 @@ using enums::operator<<; namespace detail { /// a constant defining an expected max vector size defined to be a big number that could be multiplied by 4 and not /// produce overflow for some expected uses -constexpr int expected_max_vector_size{1 << 29}; +CLI11_MODULE_INLINE constexpr int expected_max_vector_size{1 << 29}; // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c /// Split a string by a delim CLI11_INLINE std::vector split(const std::string &s, char delim); diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 20ca59b3b..54cbb4168 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -36,7 +36,7 @@ namespace detail { enum class enabler : std::uint8_t {}; /// An instance to use in EnableIf -constexpr enabler dummy = {}; +CLI11_MODULE_INLINE constexpr enabler dummy = {}; } // namespace detail /// A copy of enable_if_t from C++14, compatible with C++11. diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp index c8b33cbff..db9fb5d91 100644 --- a/include/CLI/impl/App_inl.hpp +++ b/include/CLI/impl/App_inl.hpp @@ -1272,15 +1272,18 @@ CLI11_INLINE void App::_process_help_flags(CallbackPriority priority, bool trigg const Option *help_ptr = get_help_ptr(); const Option *help_all_ptr = get_help_all_ptr(); - if(help_ptr != nullptr && help_ptr->count() > 0 && help_ptr->get_callback_priority() == priority) + if(help_ptr != nullptr && help_ptr->count() > 0 && help_ptr->get_callback_priority() == priority) { trigger_help = true; - if(help_all_ptr != nullptr && help_all_ptr->count() > 0 && help_all_ptr->get_callback_priority() == priority) + } + if(help_all_ptr != nullptr && help_all_ptr->count() > 0 && help_all_ptr->get_callback_priority() == priority) { trigger_all_help = true; + } // If there were parsed subcommands, call those. First subcommand wins if there are multiple ones. if(!parsed_subcommands_.empty()) { - for(const App *sub : parsed_subcommands_) + for(const App *sub : parsed_subcommands_) { sub->_process_help_flags(priority, trigger_help, trigger_all_help); + } // Only the final subcommand should call for help. All help wins over help. } else if(trigger_all_help) { diff --git a/include/CLI/impl/StringTools_inl.hpp b/include/CLI/impl/StringTools_inl.hpp index 7773ae5b3..d0a622346 100644 --- a/include/CLI/impl/StringTools_inl.hpp +++ b/include/CLI/impl/StringTools_inl.hpp @@ -185,10 +185,10 @@ find_member(std::string name, const std::vector names, bool ignore_ return (it != std::end(names)) ? (it - std::begin(names)) : (-1); } -static const std::string escapedChars("\b\t\n\f\r\"\\"); -static const std::string escapedCharsCode("btnfr\"\\"); -static const std::string bracketChars{"\"'`[(<{"}; -static const std::string matchBracketChars("\"'`])>}"); +CLI11_MODULE_INLINE const std::string &escapedChars("\b\t\n\f\r\"\\"); +CLI11_MODULE_INLINE const std::string &escapedCharsCode("btnfr\"\\"); +CLI11_MODULE_INLINE const std::string &bracketChars("\"'`[(<{"); +CLI11_MODULE_INLINE const std::string &matchBracketChars("\"'`])>}"); CLI11_INLINE bool has_escapable_character(const std::string &str) { return (str.find_first_of(escapedChars) != std::string::npos); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7ec6218a1..3ff293e7a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,10 @@ else() set(SYSTEM_INCL "SYSTEM") endif() +if(CLI11_MODULE) + target_compile_definitions(CLI11 ${PUBLIC_OR_INTERFACE} -DCLI11_MODULE=1) +endif() + # Duplicated because CMake adds the current source dir if you don't. target_include_directories( CLI11 ${SYSTEM_INCL} ${PUBLIC_OR_INTERFACE} $ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e05183ef..3156291fe 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -357,6 +357,24 @@ if(CLI11_INSTALL_PACKAGE_TESTS) set_property(TEST find-package-testsC PROPERTY LABELS Packaging) set_property(TEST find-package-testsC PROPERTY DEPENDS find-package-testsB) + if(CLI11_MODULE_TESTS) + add_test( + find-package-module + ${CMAKE_CTEST_COMMAND} + --build-and-test + "${CMAKE_CURRENT_SOURCE_DIR}/module_test" + "${CMAKE_CURRENT_BINARY_DIR}/module_test" + --build-generator + "Ninja" + --build-options + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCLI11_DIR=${CMAKE_INSTALL_PREFIX}" + ${package_test_command}) + + set_property(TEST find-package-module PROPERTY LABELS Packaging) + endif() + if(NOT MSVC) # Tests for other CMake projects using the package_config files add_test( diff --git a/tests/module_test/CMakeLists.txt b/tests/module_test/CMakeLists.txt new file mode 100644 index 000000000..85b39c3f1 --- /dev/null +++ b/tests/module_test/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.14...4.2) + +project(CLI11-module-test) + +include(CTest) + +if(CLI11_DIR) + set(CMAKE_PREFIX_PATH ${CLI11_DIR}) +endif() + +set(CMAKE_CXX_STANDARD 23) +# Test the CLI11 CMake package config +find_package(CLI11 2.5 REQUIRED) + +# Test the target +add_executable(module-test module_test.cpp) + +target_sources(module-test PUBLIC FILE_SET cmodule TYPE CXX_MODULES FILES cmodule.ixx) + +target_link_libraries(module-test CLI11::CLI11) +target_compile_definitions(module-test PUBLIC -DCLI11_MODULE=1) +target_compile_options(module-test PUBLIC -fmodules-ts) + +add_test(NAME module-test1 COMMAND module-test one) +set_property(TEST module-test1 PROPERTY PASS_REGULAR_EXPRESSION "OK: export module") diff --git a/tests/module_test/LICENSE b/tests/module_test/LICENSE new file mode 100644 index 000000000..8d2e24afc --- /dev/null +++ b/tests/module_test/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 scivision + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 OR COPYRIGHT HOLDERS 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. diff --git a/tests/module_test/cmodule.ixx b/tests/module_test/cmodule.ixx new file mode 100644 index 000000000..7da9e84c7 --- /dev/null +++ b/tests/module_test/cmodule.ixx @@ -0,0 +1,20 @@ +// Copyright (c) 2024 scivision +// Copyright (c) 2025 University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: MIT + +// modified from https://github.com/iTrooz/CppModules/blob/cli11 for use in CLI11 tests + +module; + +#include + +export module cmodule; + +export void foo(CLI::App *app) {} + +export int add(int a, int b) { return a + b; } + +export int subtract(int a, int b) { return a - b; } diff --git a/tests/module_test/module_test.cpp b/tests/module_test/module_test.cpp new file mode 100644 index 000000000..243324fac --- /dev/null +++ b/tests/module_test/module_test.cpp @@ -0,0 +1,30 @@ +// Copyright (c) 2024 scivision +// Copyright (c) 2025 University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: MIT + +// modified from https://github.com/iTrooz/CppModules/blob/cli11 for use in CLI11 tests + +#include +#include +#include + +import cmodule; + +int main() { + int a = 1; + int b = 2; + + int absum = add(a, b); + int abdif = subtract(a, b); + + assert(a + b == absum); + assert(a - b == abdif); + + // used this instead of to work with older compilers that may choke on implicit includes + printf("OK: export module\n"); + + return EXIT_SUCCESS; +}