diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e4f7142c..7685d0282 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -612,3 +612,116 @@ jobs: run: | cd ../boost-root/__build__ ctest --output-on-failure --no-tests=error -C Release + + posix-cmake-modules: + strategy: + fail-fast: false + matrix: + include: + - name: "gcc-15" + packages: "g++-15 ninja-build" + cmake-args: "-DCMAKE_CXX_COMPILER=g++-15" + - name: "clang-21" + packages: "clang-21 ninja-build" + cmake-args: "-DCMAKE_CXX_COMPILER=clang++-21" + + name: CMake modules ${{ matrix.name }} + + runs-on: ubuntu-latest + container: ubuntu:26.04 + + defaults: + run: + shell: bash + + steps: + - name: Setup container environment + run: | + apt-get update + apt-get -y install sudo python3 git g++ cmake + + - name: Install packages + run: | + sudo apt-get update + sudo apt-get -y install ${{matrix.packages}} + + - uses: actions/checkout@v6 + + - name: Setup Boost + run: | + echo GITHUB_REPOSITORY: $GITHUB_REPOSITORY + LIBRARY=${GITHUB_REPOSITORY#*/} + echo LIBRARY: $LIBRARY + echo "LIBRARY=$LIBRARY" >> $GITHUB_ENV + echo GITHUB_BASE_REF: $GITHUB_BASE_REF + echo GITHUB_REF: $GITHUB_REF + REF=${GITHUB_BASE_REF:-$GITHUB_REF} + REF=${REF#refs/heads/} + echo REF: $REF + BOOST_BRANCH=develop && [ "$REF" == "master" ] && BOOST_BRANCH=master || true + echo BOOST_BRANCH: $BOOST_BRANCH + cd .. + git clone -b $BOOST_BRANCH --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + cp -r $GITHUB_WORKSPACE/* libs/$LIBRARY + git submodule update --init tools/boostdep + python3 tools/boostdep/depinst/depinst.py --git_args "--jobs 3" $LIBRARY + + - name: Configure + run: | + cd ../boost-root + mkdir __build__ && cd __build__ + cmake -DBOOST_INCLUDE_LIBRARIES=$LIBRARY -DBUILD_TESTING=ON -DBOOST_PROGRAM_OPTIONS_BUILD_MODULES=ON -DCMAKE_CXX_STANDARD=20 ${{matrix.cmake-args}} -G Ninja .. + + - name: Build and run module test + run: | + cd ../boost-root/__build__ + cmake --build . --target program_options_module_test + ctest --output-on-failure --no-tests=error -R program_options_module_test + + windows-cmake-modules: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v6 + + - name: Setup Boost + shell: cmd + run: | + echo GITHUB_REPOSITORY: %GITHUB_REPOSITORY% + for /f %%i in ("%GITHUB_REPOSITORY%") do set LIBRARY=%%~nxi + echo LIBRARY: %LIBRARY% + echo LIBRARY=%LIBRARY%>>%GITHUB_ENV% + echo GITHUB_BASE_REF: %GITHUB_BASE_REF% + echo GITHUB_REF: %GITHUB_REF% + if "%GITHUB_BASE_REF%" == "" set GITHUB_BASE_REF=%GITHUB_REF% + set BOOST_BRANCH=develop + for /f %%i in ("%GITHUB_BASE_REF%") do if "%%~nxi" == "master" set BOOST_BRANCH=master + echo BOOST_BRANCH: %BOOST_BRANCH% + cd .. + git clone -b %BOOST_BRANCH% --depth 1 https://github.com/boostorg/boost.git boost-root + cd boost-root + xcopy /s /e /q %GITHUB_WORKSPACE% libs\%LIBRARY%\ + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py --git_args "--jobs 3" %LIBRARY% + + - name: Configure + shell: cmd + run: | + cd ../boost-root + mkdir __build__ && cd __build__ + cmake -DBOOST_INCLUDE_LIBRARIES=%LIBRARY% -DBUILD_TESTING=ON -DBOOST_PROGRAM_OPTIONS_BUILD_MODULES=ON -G "Visual Studio 17 2022" .. + + - name: Build and run module test (Debug) + shell: cmd + run: | + cd ../boost-root/__build__ + cmake --build . --config Debug --target program_options_module_test + ctest --output-on-failure --no-tests=error -C Debug -R program_options_module_test + + - name: Build and run module test (Release) + shell: cmd + run: | + cd ../boost-root/__build__ + cmake --build . --config Release --target program_options_module_test + ctest --output-on-failure --no-tests=error -C Release -R program_options_module_test diff --git a/CMakeLists.txt b/CMakeLists.txt index 024966ee2..013165dfe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,136 @@ else() target_compile_definitions(boost_program_options PUBLIC BOOST_PROGRAM_OPTIONS_STATIC_LINK) endif() +# C++20 module interface for the library ('import boost.program_options;'). +# Entirely optional and off by default: the header-based interface is unchanged. + +option(BOOST_PROGRAM_OPTIONS_BUILD_MODULES "Build the C++20 module interface (boost.program_options)" OFF) + +# Modules mode of the boostorg/config PR #535 pattern ("Adds support for +# C++20 modules"): the standard-library compatibility guards in the headers +# and sources route their #include <...> directives to 'import std;'. +# Requires the module build above and a toolchain-provided std module. +# Note: full activation also needs the PR #535 ecosystem (modularized +# dependencies): under this flag, already-converted dependencies such as +# Boost.TypeIndex switch their headers to importing their own modules, +# which supersedes the textual preloads used here until then. +option(BOOST_USE_MODULES "Modules mode: consume the standard library through 'import std;' (boostorg/config#535 pattern)" OFF) + +if(BOOST_USE_MODULES AND NOT BOOST_PROGRAM_OPTIONS_BUILD_MODULES) + set(BOOST_PROGRAM_OPTIONS_BUILD_MODULES ON) +endif() + +if(BOOST_PROGRAM_OPTIONS_BUILD_MODULES) + + if(CMAKE_GENERATOR MATCHES "Visual Studio") + if(CMAKE_VERSION VERSION_LESS 3.30) + message(FATAL_ERROR "boost_program_options_modules: the ${CMAKE_GENERATOR} generator requires CMake 3.30 or later for C++20 modules.") + endif() + elseif(CMAKE_VERSION VERSION_LESS 3.28) + message(FATAL_ERROR "boost_program_options_modules: building C++20 modules requires CMake 3.28 or later and a Ninja generator.") + endif() + + # The module interface plus the library sources, compiled as module + # implementation units (BOOST_PROGRAM_OPTIONS_MODULE_UNIT in src/*.cpp): + # declarations in the module purview attach to the module, and their + # definitions must carry the same attachment (some compilers mangle + # module-attached functions differently), so they cannot be taken from + # the classic library. In classic builds the same sources compile + # unchanged. + add_library(boost_program_options_modules + src/cmdline.cpp + src/config_file.cpp + src/convert.cpp + src/options_description.cpp + src/parsers.cpp + src/positional_options.cpp + src/split.cpp + src/utf8_codecvt_facet.cpp + src/value_semantic.cpp + src/variables_map.cpp + src/winmain.cpp + ) + add_library(Boost::program_options_modules ALIAS boost_program_options_modules) + + target_sources(boost_program_options_modules + PUBLIC FILE_SET CXX_MODULES + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/modules" + FILES modules/boost_program_options.cppm + ) + + target_compile_features(boost_program_options_modules PUBLIC cxx_std_20) + + # Scan all sources of the target: the src/*.cpp module implementation + # units are recognized as such and compiled against the interface's BMI. + set_target_properties(boost_program_options_modules PROPERTIES CXX_SCAN_FOR_MODULES ON) + + target_include_directories(boost_program_options_modules PRIVATE include) + + target_compile_definitions(boost_program_options_modules + PUBLIC BOOST_PROGRAM_OPTIONS_NO_LIB + PRIVATE BOOST_PROGRAM_OPTIONS_SOURCE BOOST_PROGRAM_OPTIONS_MODULE_UNIT + ) + + # Same dependencies as the classic library. + target_link_libraries(boost_program_options_modules + PUBLIC + Boost::any + Boost::config + Boost::core + Boost::detail + Boost::function + Boost::iterator + Boost::lexical_cast + Boost::smart_ptr + Boost::throw_exception + Boost::type_traits + PRIVATE + Boost::bind + Boost::tokenizer + ) + + # Standard-library consumption of the module units. + # + # BOOST_USE_MODULES (the flag used by the compatibility guards, following + # boostorg/config PR #535) activates the modules mode: the guards replace + # the textual standard includes with 'import std;'. It requires the + # std module (CMake 3.30+, -DCMAKE_CXX_MODULE_STD=ON and a C++ standard + # listed in CMAKE_CXX_COMPILER_IMPORT_STD). + # + # Without it, the standard library is preloaded textually in the global + # module fragment of each module unit, and 'import std;' is additionally + # used when the toolchain provides it (like in other modularized Boost + # libraries). + if(BOOST_USE_MODULES) + if(NOT (CMAKE_CXX_MODULE_STD AND CMAKE_CXX_STANDARD IN_LIST CMAKE_CXX_COMPILER_IMPORT_STD)) + message(FATAL_ERROR + "BOOST_USE_MODULES requires the C++ std module: use CMake 3.30+, set " + "-DCMAKE_CXX_MODULE_STD=ON, choose a -DCMAKE_CXX_STANDARD=... listed in " + "CMAKE_CXX_COMPILER_IMPORT_STD, and, where required by the CMake version, " + "set CMAKE_EXPERIMENTAL_CXX_IMPORT_STD.") + endif() + set_target_properties(boost_program_options_modules PROPERTIES CXX_MODULE_STD ON) + target_compile_definitions(boost_program_options_modules + PRIVATE BOOST_USE_MODULES + PUBLIC BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) + message(STATUS "boost_program_options_modules: modules mode (BOOST_USE_MODULES), 'import std;'") + elseif(CMAKE_CXX_MODULE_STD AND CMAKE_CXX_STANDARD IN_LIST CMAKE_CXX_COMPILER_IMPORT_STD) + set_target_properties(boost_program_options_modules PROPERTIES CXX_MODULE_STD ON) + target_compile_definitions(boost_program_options_modules PUBLIC BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) + message(STATUS "boost_program_options_modules: using 'import std;' (C++${CMAKE_CXX_STANDARD})") + else() + message(STATUS "boost_program_options_modules: using textual standard library preloads") + endif() + + if(NOT BOOST_SKIP_INSTALL_RULES AND NOT CMAKE_SKIP_INSTALL_RULES) + # Install the module interface source next to the headers, so that it is + # available to build systems that do not produce BMIs from installed + # packages (e.g. MSBuild projects adding the .cppm/.ixx directly). + install(FILES modules/boost_program_options.cppm DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/boost") + endif() + +endif() + if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt") add_subdirectory(test) diff --git a/README.md b/README.md index 220d1bfda..9c28ab964 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,57 @@ Distributed under the [Boost Software License, Version 1.0](http://www.boost.org * C++11 * Requires Linking +* Optional C++20 module interface (`import boost.program_options;`) + +### C++20 module support + +The library ships an optional C++20 module interface, so that instead of + +```cpp +#include +``` + +you can write + +```cpp +import boost.program_options; +``` + +and keep using the `boost::program_options` namespace unchanged. + +The interface (`modules/boost_program_options.cppm`) preloads the library's +dependencies in the global module fragment and includes the umbrella header +in the module purview inside an `export { ... }` block, so the public +declarations are exported without modifying any header. The library's +sources in `src/*.cpp` carry a `BOOST_PROGRAM_OPTIONS_MODULE_UNIT` +conditional that compiles them, in the module build, as module +implementation units providing matching module-attached definitions; in +classic builds the same files compile unchanged. (The header export-block +style of Boost.Any/Boost.PFR is not needed for this: no header carries +module machinery at all.) + +The standard-header includes in the library's headers and sources are +wrapped in a compatibility guard following the pattern of +`` from [boostorg/config PR +#535](https://github.com/boostorg/config/pull/535) ("Adds support for +C++20 modules", not merged yet): plain `#include <...>` today, `import +std;` in the modules mode (`-DBOOST_USE_MODULES=ON`, which requires a +toolchain-provided std module). Once that PR is merged, the guards are +to be replaced with `` (TODO comments at each +guard). + +The module is not built by default. Enable it by configuring CMake with +`-DBOOST_PROGRAM_OPTIONS_BUILD_MODULES=ON`, which creates the additional +`Boost::program_options_modules` target (it links the classic compiled +library automatically). Building the module requires CMake 3.28+ with the +Ninja generator (CMake 3.30+ with the Visual Studio generator) and a compiler +with C++20 modules support. The `test/module_test.cpp` test verifies that the +exported interface is complete and functional, without including any Boost +header. + +Macros (e.g. `BOOST_PROGRAM_OPTIONS_VERSION`) are not exported by C++20 +modules; include the classic headers where macros are needed. See +`doc/modules.xml` for details. ### Development @@ -29,15 +80,16 @@ changes to re-align with the evolution of Boost C++ Libraries and C++. ### Directories -| Name | Purpose | -| --------- | ------------------------------ | -| `build` | build script for link library | -| `ci` | continuous integration scripts | -| `doc` | documentation | -| `example` | use case examples | -| `include` | headers | -| `src` | source code for link library | -| `test` | unit tests | +| Name | Purpose | +| --------- | -------------------------------- | +| `build` | build script for link library | +| `ci` | continuous integration scripts | +| `doc` | documentation | +| `example` | use case examples | +| `include` | headers | +| `modules` | C++20 module interface | +| `src` | source code for link library | +| `test` | unit tests | ### More information diff --git a/doc/modules.xml b/doc/modules.xml new file mode 100644 index 000000000..a511b57d3 --- /dev/null +++ b/doc/modules.xml @@ -0,0 +1,100 @@ + + + %entities; +]> +
+ + Using C++20 Modules + + modules + import + + For projects compiled with C++20 or later, the library can be + consumed through a C++20 module interface, as an alternative to including + the classic headers: + + + +import boost.program_options; + +int main(int argc, char* argv[]) +{ + namespace po = boost::program_options; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help,h", "produce help message") + ("compression", po::value<int>(), "set compression level") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) { + std::cout << desc; + } +} + + + + The module, boost.program_options, exports the same + declarations in namespace boost::program_options that are + declared by the umbrella header <boost/program_options.hpp>, + so existing code only needs to replace the + #include with an import. A minimal, compilable + example is provided in modules/usage_sample.cpp. + + The interface (modules/boost_program_options.cppm) + preloads the library's dependencies in the global module fragment and + includes the umbrella header in the module purview inside an + export { ... } block, so the public declarations are + exported without modifying any header. The library's sources in + src/*.cpp carry a BOOST_PROGRAM_OPTIONS_MODULE_UNIT + conditional that compiles them, in the module build, as module + implementation units providing matching module-attached definitions; in + classic builds the same files compile unchanged. + +
+ Building and Using + + The module interface is entirely optional. It is built by CMake + when the BOOST_PROGRAM_OPTIONS_BUILD_MODULES option is + enabled, producing an additional + Boost::program_options_modules target which also links the + classic compiled library. Building modules requires CMake 3.28 or later + with the Ninja generator, or CMake 3.30 or later with the Visual Studio + generator, and a compiler with C++20 modules support (MSVC 19.28+, + GCC 15+, or Clang 16+). For example: + + + +cmake -DBOOST_INCLUDE_LIBRARIES=program_options \ + -DBOOST_PROGRAM_OPTIONS_BUILD_MODULES=ON \ + -G Ninja .. +cmake --build . --target tests +ctest --output-on-failure + + + + The import statement only makes the library's + declarations available. As usual with modules, macros such as + BOOST_PROGRAM_OPTIONS_VERSION are not exported; code that + needs those macros must include the classic headers. + + The standard-header includes of the library's headers and + sources are wrapped in a compatibility guard following the pattern + of <boost/config/std/...> from boostorg/config + PR #535 (Adds support for C++20 modules): a plain + #include today, and import std; in the + modules mode (-DBOOST_USE_MODULES=ON, which requires a + toolchain-provided std module). Once that PR is merged, the guards + should be replaced with the Boost.Config headers (see the TODO + comments at each guard). + +
+ +
diff --git a/doc/program_options.xml b/doc/program_options.xml index c9455683b..704e6a042 100644 --- a/doc/program_options.xml +++ b/doc/program_options.xml @@ -80,6 +80,7 @@ + diff --git a/include/boost/program_options/detail/cmdline.hpp b/include/boost/program_options/detail/cmdline.hpp index dce407c0b..eb0d538b5 100644 --- a/include/boost/program_options/detail/cmdline.hpp +++ b/include/boost/program_options/detail/cmdline.hpp @@ -19,8 +19,18 @@ #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include +#endif // standard library compatibility guard #if defined(BOOST_MSVC) # pragma warning (push) diff --git a/include/boost/program_options/detail/config_file.hpp b/include/boost/program_options/detail/config_file.hpp index 808bec7f1..a4a2074ca 100644 --- a/include/boost/program_options/detail/config_file.hpp +++ b/include/boost/program_options/detail/config_file.hpp @@ -7,9 +7,19 @@ #ifndef BOOST_CONFIG_FILE_VP_2003_01_02 #define BOOST_CONFIG_FILE_VP_2003_01_02 +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include +#endif // standard library compatibility guard #include #include diff --git a/include/boost/program_options/detail/convert.hpp b/include/boost/program_options/detail/convert.hpp index a22dd6f37..97ad90ab7 100644 --- a/include/boost/program_options/detail/convert.hpp +++ b/include/boost/program_options/detail/convert.hpp @@ -12,15 +12,45 @@ #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include +#endif // standard library compatibility guard // for mbstate_t +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include +#endif // standard library compatibility guard #if defined(BOOST_NO_STDC_NAMESPACE) +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include +#endif // standard library compatibility guard namespace std { using ::mbstate_t; @@ -86,9 +116,19 @@ namespace boost { } +#else +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif #else #include #include +#endif // standard library compatibility guard namespace boost{ namespace program_options{ BOOST_PROGRAM_OPTIONS_DECL std::string to_internal(const std::string&); diff --git a/include/boost/program_options/detail/parsers.hpp b/include/boost/program_options/detail/parsers.hpp index cdda539ca..1c55d7b6b 100644 --- a/include/boost/program_options/detail/parsers.hpp +++ b/include/boost/program_options/detail/parsers.hpp @@ -8,7 +8,13 @@ #include +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include +#endif // standard library compatibility guard namespace boost { namespace program_options { diff --git a/include/boost/program_options/detail/value_semantic.hpp b/include/boost/program_options/detail/value_semantic.hpp index 27b7c241b..8f31251a7 100644 --- a/include/boost/program_options/detail/value_semantic.hpp +++ b/include/boost/program_options/detail/value_semantic.hpp @@ -9,7 +9,13 @@ #include #ifndef BOOST_NO_CXX17_HDR_OPTIONAL +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else # include +#endif // standard library compatibility guard #endif // forward declaration diff --git a/include/boost/program_options/environment_iterator.hpp b/include/boost/program_options/environment_iterator.hpp index 933919d15..69bc0dcb2 100644 --- a/include/boost/program_options/environment_iterator.hpp +++ b/include/boost/program_options/environment_iterator.hpp @@ -8,9 +8,19 @@ #include "eof_iterator.hpp" +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include +#endif // standard library compatibility guard namespace boost { diff --git a/include/boost/program_options/errors.hpp b/include/boost/program_options/errors.hpp index 953db1366..cc8ba227b 100644 --- a/include/boost/program_options/errors.hpp +++ b/include/boost/program_options/errors.hpp @@ -9,10 +9,20 @@ #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include #include +#endif // standard library compatibility guard #if defined(BOOST_MSVC) diff --git a/include/boost/program_options/option.hpp b/include/boost/program_options/option.hpp index 427678fa7..6f1d15567 100644 --- a/include/boost/program_options/option.hpp +++ b/include/boost/program_options/option.hpp @@ -8,8 +8,18 @@ #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include +#endif // standard library compatibility guard namespace boost { namespace program_options { diff --git a/include/boost/program_options/options_description.hpp b/include/boost/program_options/options_description.hpp index da2c4bbfe..cb86bac9d 100644 --- a/include/boost/program_options/options_description.hpp +++ b/include/boost/program_options/options_description.hpp @@ -17,6 +17,15 @@ #include #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include @@ -25,6 +34,7 @@ #include #include +#endif // standard library compatibility guard #if defined(BOOST_MSVC) # pragma warning (push) diff --git a/include/boost/program_options/parsers.hpp b/include/boost/program_options/parsers.hpp index c86b53746..16e36d4b7 100644 --- a/include/boost/program_options/parsers.hpp +++ b/include/boost/program_options/parsers.hpp @@ -13,9 +13,19 @@ #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include +#endif // standard library compatibility guard #if defined(BOOST_MSVC) # pragma warning (push) diff --git a/include/boost/program_options/positional_options.hpp b/include/boost/program_options/positional_options.hpp index c19c9250e..4134d6dde 100644 --- a/include/boost/program_options/positional_options.hpp +++ b/include/boost/program_options/positional_options.hpp @@ -8,8 +8,18 @@ #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include +#endif // standard library compatibility guard #if defined(BOOST_MSVC) # pragma warning (push) diff --git a/include/boost/program_options/value_semantic.hpp b/include/boost/program_options/value_semantic.hpp index 717f2cd99..e9c10d7a2 100644 --- a/include/boost/program_options/value_semantic.hpp +++ b/include/boost/program_options/value_semantic.hpp @@ -13,10 +13,20 @@ #include #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include #include +#endif // standard library compatibility guard namespace boost { namespace program_options { diff --git a/include/boost/program_options/variables_map.hpp b/include/boost/program_options/variables_map.hpp index 3d7662931..3f256f927 100644 --- a/include/boost/program_options/variables_map.hpp +++ b/include/boost/program_options/variables_map.hpp @@ -12,9 +12,19 @@ #include #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include +#endif // standard library compatibility guard #if defined(BOOST_MSVC) # pragma warning (push) diff --git a/modules/boost_program_options.cppm b/modules/boost_program_options.cppm new file mode 100644 index 000000000..d570ba0de --- /dev/null +++ b/modules/boost_program_options.cppm @@ -0,0 +1,124 @@ +// Copyright Sorush Sabzpour 2026. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// C++20 module interface for Boost.ProgramOptions. +// +// Consumers can replace +// #include +// with +// import boost.program_options; +// and keep using the boost::program_options namespace as before. +// +// Implementation notes: +// +// * The dependencies of the library's headers are preloaded textually in +// the global module fragment below, and the library's own umbrella +// header is then included in the module purview inside an export block, +// which exports its declarations without modifying any header. +// +// * Unlike Boost.Any or Boost.PFR, this library is compiled: entities +// declared in a module purview are attached to the module, and some +// compilers (e.g. MSVC) mangle their out-of-line definitions with a +// module qualifier, so they cannot be provided by the classic, +// non-modular library. The module build therefore compiles the library +// sources in src/*.cpp as module implementation units (see the +// BOOST_PROGRAM_OPTIONS_MODULE_UNIT conditionals in those files); the +// classic build of the same files is unchanged. +// +// * Macros (e.g. BOOST_PROGRAM_OPTIONS_VERSION) are not exported by C++20 +// modules. Code that needs them must keep including the classic headers. +// +// * The module covers everything reachable from . +// The auxiliary iterator headers +// and are generic +// utilities outside of that umbrella header and are not exported here. + +module; + +// Boost dependencies used by the library's headers. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif // standard library compatibility guard + +// Consume the standard library through 'import std;' as well when the +// toolchain provides it (BOOST_PROGRAM_OPTIONS_USE_STD_MODULE, set by CMake +// like in other modularized Boost libraries). The import must come after +// the textual includes above: on MSVC, a textual standard header parsed +// after 'import std;' clashes with the declarations already provided by +// the std module. The textual includes stay in both modes because the +// library's (unconverted) headers include standard headers textually; they +// set the include guards that make those purview includes no-ops, keeping +// the standard library attached to the global module. Libraries whose +// headers are converted to compatibility headers (e.g. Boost.Any) can +// replace them with 'import std;' alone. +#ifdef BOOST_PROGRAM_OPTIONS_USE_STD_MODULE +import std; +#endif + +export module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW + +// Some compilers prune global-module-fragment declarations from the BMI. +// The preloaded boost::any_cast template is instantiated in importing +// translation units (e.g. by variable_value::as()) and looks up +// boost::typeindex::operator== by ADL; such global-module declarations +// must be exported by the module to be usable there (MSVC: error C2679). +export namespace boost { namespace typeindex { +using ::boost::typeindex::operator==; +using ::boost::typeindex::operator!=; +}} + +// The library's umbrella header, in the module purview: its declarations +// are attached to this module and exported by the enclosing block. The +// includes of the preloaded dependencies above are no-ops here (include +// guards), so nothing foreign is attached. +export { +#include +} + +// Internal headers used by the implementation units: included here once +// (unexported) so that every unit of the module sees the same declarations +// instead of re-including them textually. +#include +#include diff --git a/modules/usage_sample.cpp b/modules/usage_sample.cpp new file mode 100644 index 000000000..904ab61e0 --- /dev/null +++ b/modules/usage_sample.cpp @@ -0,0 +1,43 @@ +// Copyright Sorush Sabzpour 2026. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// A minimal sample of consuming the library through the C++20 module +// interface. To compile it manually, build the module interface first +// (e.g. with CMake and BOOST_PROGRAM_OPTIONS_BUILD_MODULES=ON), then use +// a command like the following: +// +// MSVC: +// cl /std:c++20 /EHsc ^ +// /reference boost.program_options=/boost.program_options.ifc ^ +// usage_sample.cpp /link /libboost_program_options*.lib +// +// Clang: +// clang++ -std=c++20 -fprebuilt-module-path=. usage_sample.cpp \ +// -lboost_program_options + +#include + +import boost.program_options; + +int main(int argc, char* argv[]) +{ + namespace po = boost::program_options; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help,h", "produce help message") + ("compression", po::value(), "set compression level") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + if (vm.count("help")) + { + std::cout << desc; + return 0; + } +} diff --git a/src/cmdline.cpp b/src/cmdline.cpp index 8f4ae64e4..9ecbb9770 100644 --- a/src/cmdline.cpp +++ b/src/cmdline.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2002-2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -10,15 +19,32 @@ #include +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include #include #include #include #include +#endif #include #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +// 'assert' is a macro, which 'import std;' does not provide; keep the +// macro-only header textual in the global module fragment. +#include + +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include @@ -30,6 +56,15 @@ #include #include +#endif // standard library compatibility guard + +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif using namespace boost::placeholders; @@ -715,3 +750,5 @@ namespace boost { namespace program_options { namespace detail { }}} + + diff --git a/src/config_file.cpp b/src/config_file.cpp index 7cd18acdf..aea739aba 100644 --- a/src/config_file.cpp +++ b/src/config_file.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2002-2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -8,14 +17,40 @@ #endif #include +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include #include #include +#endif #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +// 'assert' is a macro, which 'import std;' does not provide; keep the +// macro-only header textual in the global module fragment. +#include + +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include +#endif // standard library compatibility guard + +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif namespace boost { namespace program_options { namespace detail { @@ -171,8 +206,18 @@ namespace boost { namespace program_options { namespace detail { #if 0 using boost::program_options::config_file; +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include +#endif // standard library compatibility guard int main() { @@ -199,3 +244,5 @@ int main() } } #endif + + diff --git a/src/convert.cpp b/src/convert.cpp index fbe2758b1..b50047bfe 100644 --- a/src/convert.cpp +++ b/src/convert.cpp @@ -1,8 +1,27 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#ifdef BOOST_USE_MODULES +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include @@ -10,6 +29,7 @@ #include #include #include +#endif // standard library compatibility guard #include @@ -17,12 +37,31 @@ # define BOOST_PROGRAM_OPTIONS_SOURCE #endif #include +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include +#endif #include #include #include +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +#if defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +// The standard module declarations: like the other module units, this one +// needs an explicit 'import std;' after its textual includes (MSVC requires +// textual includes before the import), and this file's standard headers sit +// above the Boost dependencies, so the import is issued here. +import std; +#endif +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif + using namespace std; using namespace boost::placeholders; @@ -162,3 +201,5 @@ namespace boost { } + + diff --git a/src/options_description.cpp b/src/options_description.cpp index 0efdc3388..c644e7801 100644 --- a/src/options_description.cpp +++ b/src/options_description.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2002-2004. // Copyright Bertolt Mildner 2004. // Distributed under the Boost Software License, Version 1.0. @@ -8,10 +17,14 @@ # define BOOST_PROGRAM_OPTIONS_SOURCE #endif #include +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include // FIXME: this is only to get multiple_occurrences class // should move that to a separate headers. #include +#endif #include @@ -19,12 +32,34 @@ #include #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +// 'assert' is a macro, which 'import std;' does not provide; keep the +// macro-only header textual in the global module fragment. +#include + +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include #include #include #include #include +#endif // standard library compatibility guard + +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif using namespace std; namespace boost { namespace program_options { @@ -698,3 +733,5 @@ namespace boost { namespace program_options { } }} + + diff --git a/src/parsers.cpp b/src/parsers.cpp index 4dac608d3..77611dca0 100644 --- a/src/parsers.cpp +++ b/src/parsers.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2002-2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -10,6 +19,9 @@ # define BOOST_PROGRAM_OPTIONS_SOURCE #endif #include +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include #include #include @@ -17,21 +29,77 @@ #include #include #include +#endif #include #include +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// Declarations the classic build gets transitively through the +// library's own headers (skipped above in this mode). +#include +#include +#endif + +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include +#endif // standard library compatibility guard + +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif #if !defined(__GNUC__) || __GNUC__ < 3 +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include +#endif // standard library compatibility guard +#else +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif #else #include +#endif // standard library compatibility guard #endif #ifdef _WIN32 +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include +#endif // standard library compatibility guard #else #include #endif @@ -259,3 +327,5 @@ namespace boost { namespace program_options { }} + + diff --git a/src/positional_options.cpp b/src/positional_options.cpp index cb6a14524..2e4d89c9f 100644 --- a/src/positional_options.cpp +++ b/src/positional_options.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -8,11 +17,37 @@ #endif #include +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include +#endif #include +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +// 'assert' is a macro, which 'import std;' does not provide; keep the +// macro-only header textual in the global module fragment. +#include + +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include +#endif // standard library compatibility guard + +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif namespace boost { namespace program_options { @@ -53,3 +88,5 @@ namespace boost { namespace program_options { }} + + diff --git a/src/split.cpp b/src/split.cpp index c674403ff..749351f8d 100644 --- a/src/split.cpp +++ b/src/split.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Sascha Ochsenknecht 2009. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -7,11 +16,39 @@ # define BOOST_PROGRAM_OPTIONS_SOURCE #endif +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include +#endif #include +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// Declarations the classic build gets transitively through the +// library's own headers (skipped above in this mode). +#include +#endif + +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include +#endif // standard library compatibility guard + +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif namespace boost { namespace program_options { namespace detail { @@ -62,3 +99,5 @@ namespace boost { namespace program_options { }} // namespace + + diff --git a/src/utf8_codecvt_facet.cpp b/src/utf8_codecvt_facet.cpp index 075e4aebb..426372af3 100644 --- a/src/utf8_codecvt_facet.cpp +++ b/src/utf8_codecvt_facet.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -21,3 +30,13 @@ #undef BOOST_UTF8_END_NAMESPACE #undef BOOST_UTF8_DECL +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif + + + diff --git a/src/value_semantic.cpp b/src/value_semantic.cpp index c4feb688e..4eea6b4a0 100644 --- a/src/value_semantic.cpp +++ b/src/value_semantic.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -7,12 +16,41 @@ # define BOOST_PROGRAM_OPTIONS_SOURCE #endif #include +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include #include #include +#endif +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// Declarations the classic build gets transitively through the +// library's own headers (skipped above in this mode). +#include +#include +#endif + +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include #include +#endif // standard library compatibility guard + +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif namespace boost { namespace program_options { @@ -430,3 +468,5 @@ namespace boost { namespace program_options { } }} + + diff --git a/src/variables_map.cpp b/src/variables_map.cpp index 94ac6148c..9786959a7 100644 --- a/src/variables_map.cpp +++ b/src/variables_map.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2002-2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -7,13 +16,45 @@ # define BOOST_PROGRAM_OPTIONS_SOURCE #endif #include +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include #include #include #include +#endif +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// Declarations the classic build gets transitively through the +// library's own headers (skipped above in this mode). +#include +#endif + +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +// 'assert' is a macro, which 'import std;' does not provide; keep the +// macro-only header textual in the global module fragment. #include +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else +#include +#endif // standard library compatibility guard + +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif + namespace boost { namespace program_options { using namespace std; @@ -247,3 +288,5 @@ namespace boost { namespace program_options { } }} + + diff --git a/src/winmain.cpp b/src/winmain.cpp index 31c8adcb7..6371f8683 100644 --- a/src/winmain.cpp +++ b/src/winmain.cpp @@ -1,3 +1,12 @@ +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// In the boost.program_options module build, this translation unit is a module +// implementation unit. The global module fragment starts here: the textual +// includes below attach their declarations to the global module, the library's +// own declaration headers are skipped (their declarations come from the +// imported primary interface), and the module declaration closes the fragment. +module; +#endif + // Copyright Vladimir Prus 2002-2004. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -6,8 +15,39 @@ #ifndef BOOST_PROGRAM_OPTIONS_SOURCE # define BOOST_PROGRAM_OPTIONS_SOURCE #endif +// The library's own declaration headers are skipped in module builds: +// the imported primary interface provides them. +#ifndef BOOST_PROGRAM_OPTIONS_MODULE_UNIT #include +#endif +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +// Declarations the classic build gets transitively through the +// library's own headers (skipped above in this mode). +#include +#include +#include +#include +#endif + +// Standard library compatibility guard, following the pattern of +// from boostorg/config PR #535 ("Adds support for +// C++20 modules"). TODO: when that PR is merged, replace the textual +// includes with (see modules/boost_program_options.cppm). +#if defined(BOOST_USE_MODULES) || defined(BOOST_PROGRAM_OPTIONS_USE_STD_MODULE) +#ifndef BOOST_IN_MODULE_PURVIEW +import std; +#endif +#else #include +#endif // standard library compatibility guard + +#ifdef BOOST_PROGRAM_OPTIONS_MODULE_UNIT +module boost.program_options; + +// Purview marker of the boost.program_options module units, per the +// convention used by (boostorg/config PR #535). +#define BOOST_IN_MODULE_PURVIEW +#endif using std::size_t; @@ -102,3 +142,5 @@ namespace boost { namespace program_options { }} #endif + + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ea8832ec1..14dd14e4a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,6 +4,21 @@ include(BoostTest OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST) +# C++20 module test. Does not use Boost.Test, and is only built when the +# module interface target exists. + +if(BOOST_PROGRAM_OPTIONS_BUILD_MODULES AND TARGET Boost::program_options_modules) + + add_executable(program_options_module_test module_test.cpp) + target_link_libraries(program_options_module_test PRIVATE Boost::program_options_modules) + set_target_properties(program_options_module_test PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_SCAN_FOR_MODULES ON) + add_test(NAME program_options_module_test COMMAND program_options_module_test) + +endif() + if(NOT HAVE_BOOST_TEST) return() endif() diff --git a/test/module_test.cpp b/test/module_test.cpp new file mode 100644 index 000000000..de0f7e787 --- /dev/null +++ b/test/module_test.cpp @@ -0,0 +1,222 @@ +// Copyright Sorush Sabzpour 2026. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// Checks that the C++20 module interface exports a usable library. +// No Boost header is included on purpose: everything must come from +// 'import boost.program_options;'. + +// Standard library: textual includes by default; when the module was built +// with 'import std' (BOOST_PROGRAM_OPTIONS_USE_STD_MODULE), the standard +// module comes with the module and the textual includes would clash with it. +#ifdef BOOST_PROGRAM_OPTIONS_USE_STD_MODULE +import std; +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +import boost.program_options; + +// The test is built in both Debug and Release configurations, so plain +// assert() would be a no-op in Release. Use an always-active check. +// (Uses only facilities provided by 'import std', no macros.) +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::cerr << "CHECK failed: " << #cond << " (" << __FILE__ \ + << ":" << __LINE__ << ")\n"; \ + std::abort(); \ + } \ + } while (0) + +static void test_command_line() +{ + using namespace boost::program_options; + + options_description desc("Allowed options"); + desc.add_options() + ("help,h", "produce help message") + ("compression", value()->default_value(5), "set compression level") + ("email", value >()->composing(), "email address") + ("output,o", value()->implicit_value("default"), "output file") + ("verbose", bool_switch(), "verbose output") + ("input", value >(), "input file") + ; + + positional_options_description positional; + positional.add("input", 2); + + char arg0[] = "program"; + char arg1[] = "--compression"; + char arg2[] = "7"; + char arg3[] = "--email"; + char arg4[] = "a@example.com"; + char arg5[] = "-ovalue"; + char arg6[] = "file1"; + char arg7[] = "file2"; + char* argv[] = { arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 }; + + variables_map vm; + store( + command_line_parser(sizeof(argv) / sizeof(argv[0]), argv) + .options(desc) + .positional(positional) + .style(command_line_style::unix_style) + .run(), + vm); + notify(vm); + + CHECK(vm.count("compression") == 1); + CHECK(vm["compression"].as() == 7); + CHECK(!vm["compression"].defaulted()); + + CHECK(vm.count("email") == 1); + CHECK(vm["email"].as >().size() == 1); + + CHECK(vm.count("output") == 1); + CHECK(vm["output"].as() == "value"); + + // bool_switch registers a default value of false, which store() applies. + CHECK(vm.count("verbose") == 1); + CHECK(vm["verbose"].defaulted()); + CHECK(!vm["verbose"].as()); + CHECK(vm.count("input") == 1); + CHECK(vm["input"].as >().size() == 2); + CHECK(vm.count("help") == 0); + + // Description printing exercises the ADL-found operator<<. + std::ostringstream os; + os << desc; + CHECK(os.str().find("Allowed options") != std::string::npos); + CHECK(os.str().find("compression") != std::string::npos); + + // parse_command_line and defaulted values. + char* argv2[] = { arg0 }; + variables_map vm2; + store(parse_command_line(1, argv2, desc), vm2); + notify(vm2); + CHECK(vm2["compression"].as() == 5); + CHECK(vm2["compression"].defaulted()); + + // Unregistered options. + char unknown[] = "--no-such-option"; + char* argv3[] = { arg0, unknown }; + parsed_options parsed + = command_line_parser(2, argv3).options(desc).allow_unregistered().run(); + std::vector tokens + = collect_unrecognized(parsed.options, exclude_positional); + CHECK(tokens.size() == 1 && tokens[0] == unknown); + + // Exceptions. + bool caught = false; + try + { + store(parse_command_line(2, argv3, desc), vm2); + } + catch (const unknown_option& e) + { + caught = true; + std::string message = e.what(); + CHECK(message.find("no-such-option") != std::string::npos); + } + CHECK(caught); + + char badValue[] = "--compression"; + char notANumber[] = "not-a-number"; + char* argv4[] = { arg0, badValue, notANumber }; + caught = false; + try + { + store(parse_command_line(3, argv4, desc), vm2); + } + catch (const invalid_option_value& e) + { + caught = true; + // Reachability of the error hierarchy. + const error_with_option_name& base = e; + CHECK(base.what() != 0); + } + CHECK(caught); +} + +static void test_config_file() +{ + using namespace boost::program_options; + + const char* configPath = "program_options_module_test.cfg"; + { + std::ofstream config(configPath); + config << "# a comment\n" + << "compression = 9\n" + << "email = x@example.com\n" + << "email = y@example.com\n"; + } + + options_description desc("Allowed options"); + desc.add_options() + ("compression", value(), "set compression level") + ("email", value >()->composing(), "email address") + ; + + variables_map vm; + store(parse_config_file(configPath, desc), vm); + notify(vm); + + CHECK(vm.count("compression") == 1); + CHECK(vm["compression"].as() == 9); + CHECK(vm.count("email") == 1); + CHECK(vm["email"].as >().size() == 2); + + std::remove(configPath); +} + +// Every toolchain with C++20 modules support provides std::wstring, so the +// wide-character interface (including the basic_parsed_options +// specialization) is tested unconditionally. +static void test_wide_command_line() +{ + using namespace boost::program_options; + + options_description desc("Allowed options"); + desc.add_options() + ("compression", value(), "set compression level") + ; + + wchar_t arg0[] = L"program"; + wchar_t arg1[] = L"--compression"; + wchar_t arg2[] = L"3"; + wchar_t* argv[] = { arg0, arg1, arg2 }; + + // Uses basic_parsed_options, an explicit specialization that + // must be exported by the module. + variables_map vm; + store(wcommand_line_parser(3, argv).options(desc).run(), vm); + notify(vm); + + CHECK(vm.count("compression") == 1); + CHECK(vm["compression"].as() == 3); +} + +int main() +{ + test_command_line(); + test_config_file(); + test_wide_command_line(); + + // Small namespace-level utilities. + using boost::program_options::strip_prefixes; + CHECK(strip_prefixes("--foo-bar") == "foo-bar"); + CHECK(strip_prefixes("foo") == "foo"); + + CHECK(boost::program_options::split_unix("a b c").size() == 3); + + std::cout << "program_options module test: PASS\n"; + return 0; +}