C++20 modules in beman project libraries

Ha, despite supporting all the flags for handling modules, and despite advertising being based on Clang 17, the latest AppleClang actually doesn’t parse C++20 modules syntax at all under any conditions. Ie, it doesn’t know about the module keyword no matter what set of flag magic you pass it.

Well so much for that plan.

This is precisely why I had the compiler detection logic in the scope cmake – yes I’m afraid it feels like a horrible hack, but well things often don’t quite live up to whats on the tin. In the case of scope I only found Clang19+ and g++15 to be viable (msvc is probably fine but scope isn’t ported there yet). Clang 17 at this point is a quite old compiler so I’m not suprised that it doesn’t work.

FYI: see Distributing C++20 modules as source - #4 by ClausKlein - Usage - CMake Discourse

From Boost any how to check if import std; is possible:

if(BOOST_USE_MODULES)
    add_library(boost_any)
    target_sources(
        boost_any
        PUBLIC
            FILE_SET modules_public
                TYPE CXX_MODULES
                FILES ${CMAKE_CURRENT_LIST_DIR}/modules/boost_any.cppm
    )

    target_compile_features(boost_any PUBLIC cxx_std_20)
    target_compile_definitions(boost_any PUBLIC BOOST_USE_MODULES)
    if(${CMAKE_CXX_STANDARD} IN_LIST CMAKE_CXX_COMPILER_IMPORT_STD)
        set(CMAKE_CXX_MODULE_STD ON)
        target_compile_definitions(boost_any PRIVATE BOOST_ANY_USE_STD_MODULE)
        message(STATUS "Using `import std;`")
    else()
        message(WARNING "`import std;` is not available")
    endif()
    set(__scope PUBLIC)
else()
    set(CMAKE_VERIFY_INTERFACE_HEADER_SETS ON)
    add_library(boost_any INTERFACE)
    set(__scope INTERFACE)
endif()
# ...

Simple and clear, too if one module depends on other boost modules!

So this is the essence of the check, but where does the cmake_cxx_compiler_import_std come from? And what’s in it? It looks like it’s the standard version, which isn’t enough to be correctly enable support. Specifically clang-18, g++14 will accept c++23 (version for import std) but don’t correctly support modules, let alone import std. As I said above, I think there’s currently support in 4-5 compilers clang-19+, g++15+, and msvc (not sure on versions here).

Set this option to yes, and you get a clear cmake-configure error if you use the wrong toolchain, cmake generator, or c++ std version

Ok well obviously we could copy whatever cmake logic they’re doing there, but it almost seems binary in a way we don’t want perhaps. Specifically, just because I have the ability to enable modules doesn’t mean that I wouldn’t want to have include based options as well.

So let me ask it this way. Would you recommend that we spend time now trying to do something similar to Boost? I can see that we’re going to need similar logic even after cmake provides support for import std in a released version. Specifically, conditionally compiling modules and import std type code only if it’s a supported compiler and cxx version. Thoughts?

No, boost try to work with cmake 3.13 … 3.x, for me 3.30…4.2 would be right today.

But beman wants to compile with older gcc and clang compilers, which is also a NoGo for me.

MSVS 2022, clang-20, and gcc-15 is currently my minimum requirement!

1 Like

This comes from cmake:

bash-5.3$ make build/compile_commands.json
cmake -S . -B build -G Ninja --log-level=DEBUG -D CMAKE_BUILD_TYPE=Release \
	 -D CMAKE_EXPERIMENTAL_CXX_IMPORT_STD="d0edc3af-4c50-42ea-a356-e2862fe7a444" \
	 -D CMAKE_CXX_STDLIB_MODULES_JSON=/usr/local/Cellar/llvm/21.1.6/lib/c++/libc++.modules.json \
	 -D CMAKE_CXX_STANDARD=23 -D CMAKE_CXX_EXTENSIONS=ON -D CMAKE_CXX_STANDARD_REQUIRED=ON \
	 -D BUILD_SHARED_LIBS=ON -D BOOST_INCLUDE_LIBRARIES='any;type_index' \
	 -D BOOST_USE_MODULES=ON -D CMAKE_CXX_MODULE_STD=ON \
	 -D CMAKE_INSTALL_MESSAGE=LAZY # XXX -D BOOST_SKIP_INSTALL_RULES=ON # --fresh
# ...
-- Check for working CXX compiler: /usr/local/Cellar/llvm/21.1.6/bin/clang++ - skipped
-- Detecting CXX compile features
CMake Warning (dev) at /usr/local/share/cmake/Modules/Compiler/CMakeCommonCompilerMacros.cmake:248 (cmake_language):
  CMake's support for `import std;` in C++23 and newer is experimental.  It
  is meant only for experimentation and feedback to CMake developers.
Call Stack (most recent call first):
  /usr/local/share/cmake/Modules/CMakeDetermineCompilerSupport.cmake:113 (cmake_create_cxx_import_std)
  /usr/local/share/cmake/Modules/CMakeTestCXXCompiler.cmake:83 (CMAKE_DETERMINE_COMPILER_SUPPORT)
  CMakeLists.txt:20 (project)
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Detecting CXX compile features - done
-- CMAKE_CXX_COMPILER_IMPORT_STD=23;26
-- CMAKE_CXX_STANDARD=23
-- CMAKE_CXX_MODULE_STD=ON
-- CMAKE_CXX_SCAN_FOR_MODULES=ON
-- Boost: using system layout: include, bin, lib, lib/cmake, share
-- Boost: using CMake 4.2.0
-- Boost: Release build, shared libraries, MPI OFF, Python OFF, testing OFF
-- Boost: libraries included: any;type_index
-- Scanning dependencies: any;type_index
-- Scanning dependencies: config;throw_exception;container_hash
-- Scanning dependencies: assert;describe;mp11
-- Adding Boost library 'any'
-- Using `import std;`
-- Enabling installation for 'any'
-- boost_install_target: 'boost_any' has INTERFACE_CXX_MODULE_SETS=modules_public
# ...
set_property(TARGET __cmake_cxx23 PROPERTY CXX_SCAN_FOR_MODULES 1)
set_property(TARGET __cmake_cxx23 PROPERTY CXX_MODULE_STD 0)
target_compile_features(__cmake_cxx23 PUBLIC cxx_std_23)
target_sources(__cmake_cxx23
  PUBLIC
  FILE_SET std TYPE CXX_MODULES
    BASE_DIRS  "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Tools/MSVC/14.44.35207/bin/../modules"
    FILES  "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Tools/MSVC/14.44.35207/bin/../modules/std.ixx" "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Tools/MSVC/14.44.35207/bin/../modules/std.compat.ixx")
endif ()
add_library(__CMAKE::CXX23 ALIAS __cmake_cxx23)
endif ()
if (TARGET "__CMAKE::CXX23")
  list(APPEND CMAKE_CXX_COMPILER_IMPORT_STD "23")
endif ()


 PARENT_SCOPE )
C:/hostedtoolcache/windows/Python/3.13.9/x64/Lib/site-packages/cmake/data/share/cmake-4.2/Modules/CMakeDetermineCompilerSupport.cmake(131):  message(CHECK_PASS done )
-- Detecting CXX compile features - done
-- CMAKE_CXX_COMPILER_IMPORT_STD=23
-- CMAKE_CXX_MODULE_STD=ON
-- ALGO_USE_MODULES=ON
-- CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES=
-- Performing Test COMPILER_HAS_DEPRECATED_ATTR
-- Performing Test COMPILER_HAS_DEPRECATED_ATTR - Failed
-- Performing Test COMPILER_HAS_DEPRECATED
-- Performing Test COMPILER_HAS_DEPRECATED - Success
-- Configuring done (6.5s)
CMake Warning:
  Manually-specified variables were not used by the project:

    BUILD_MCSS_DOCS
    cmake-init-modules_DEVELOPER_MODE


-- Generating done (0.0s)
-- Build files have been written to: D:/a/cmake-init-modules/cmake-init-modules/build/dev


Agree – I think 3.28 is the absolute minimum, but happy with 3.30 as well.

I guess this is where I don’t see it as β€˜binary’. For include based things maybe the older compilers are fine, but clearly not for modules.

bingo – this is the set we need to enable, although I’ve also had success with clang-19 I’m happy to restrict further. modules, unfortunately is still on the bleeding edge.

I have prepared a prototype to test with CXX_MODULES with import std;

see Feature/prepare cxx modules support by ClausKlein Β· Pull Request #194 Β· bemanproject/execution Β· GitHub

what is the right cmake export package tree:

bash-5.3$ tree _CPack_Packages/Darwin/TGZ/beman_execution-0.0.1-Darwin/lib/cmake/
_CPack_Packages/Darwin/TGZ/beman_execution-0.0.1-Darwin/lib/cmake/
└── beman_execution
    β”œβ”€β”€ beman_execution-config-version.cmake
    β”œβ”€β”€ beman_execution-config.cmake
    β”œβ”€β”€ beman_execution-targets-debug.cmake
    β”œβ”€β”€ beman_execution-targets.cmake
    β”œβ”€β”€ cxx-modules
    β”‚   └── execution.cppm
    β”œβ”€β”€ cxx-modules-beman_execution-targets-Debug.cmake
    β”œβ”€β”€ cxx-modules-beman_execution-targets.cmake
    └── target-execution-Debug.cmake

3 directories, 8 files
bash-5.3$ 

or this:

bash-5.3$ tree _CPack_Packages/Darwin/TGZ/beman.scope-0.0.1-Darwin/
_CPack_Packages/Darwin/TGZ/beman.scope-0.0.1-Darwin/
β”œβ”€β”€ include
β”‚   └── beman
β”‚       └── scope
β”‚           β”œβ”€β”€ beman.scope.cppm
β”‚           └── scope.hpp
└── lib
    β”œβ”€β”€ cmake
    β”‚   └── beman.scope
    β”‚       β”œβ”€β”€ beman.scope-config-version.cmake
    β”‚       β”œβ”€β”€ beman.scope-config.cmake
    β”‚       β”œβ”€β”€ beman.scope-targets-release.cmake
    β”‚       β”œβ”€β”€ beman.scope-targets.cmake
    β”‚       β”œβ”€β”€ bmi-GNU_Release
    β”‚       β”‚   └── beman.scope.gcm
    β”‚       └── cxx-modules
    β”‚           β”œβ”€β”€ cxx-modules-beman.scope-targets-Release.cmake
    β”‚           β”œβ”€β”€ cxx-modules-beman.scope-targets.cmake
    β”‚           └── target-scope-Release.cmake
    └── libbeman.scope.a

9 directories, 11 files
bash-5.3$ 

or even like this:

bash-5.3$ tree fmt/
fmt/
β”œβ”€β”€ bmi-Clang_
β”‚   └── fmt.pcm
β”œβ”€β”€ bmi-Clang_Relase
β”‚   └── fmt.pcm
β”œβ”€β”€ cxx-modules-fmtTargets-Relase.cmake
β”œβ”€β”€ cxx-modules-fmtTargets-noconfig.cmake
β”œβ”€β”€ cxx-modules-fmtTargets.cmake
β”œβ”€β”€ fmtConfig.cmake
β”œβ”€β”€ fmtConfigVersion.cmake
β”œβ”€β”€ fmtTargets-release.cmake
β”œβ”€β”€ fmtTargets.cmake
β”œβ”€β”€ module
β”‚   β”œβ”€β”€ fmt.cppm
β”‚   β”œβ”€β”€ format.cc
β”‚   └── os.cc
β”œβ”€β”€ target-fmt-Relase.cmake
└── target-fmt-noconfig.cmake

4 directories, 14 files
bash-5.3$ 

Good question. Scope is intended to be header only so the libbeman.scope.a shouldn’t be there. The include looks right – although I guess fmt does something different. If I’m understanding fmt is including the binary from the module which surprises me. idk about the cmake aspect of things in the package. Maybe @vito.gamberini has thoughts.

Please note the Beman Standard CMake recommendations has a gab. It needs to define who to add and install CXX_MODULES.

Boost is working on it i.e. see my boost-make patch:

From fcb4edacb36a4037889cf9ac7a0005eec249e658 Mon Sep 17 00:00:00 2001
From: ClausKlein <claus.klein@arcormail.de>
Date: Thu, 4 Dec 2025 07:09:56 +0100
Subject: [PATCH] Feat: Add support to install FILE_SET too

---
 include/BoostInstall.cmake | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/include/BoostInstall.cmake b/include/BoostInstall.cmake
index ca0997c..d7f0149 100644
--- a/include/BoostInstall.cmake
+++ b/include/BoostInstall.cmake
@@ -337,6 +337,16 @@ function(boost_install_target)
     string(APPEND CONFIG_INSTALL_DIR "-static")
   endif()
 
+  get_target_property(INTERFACE_CXX_MODULE_SETS ${LIB} INTERFACE_CXX_MODULE_SETS)
+  if(INTERFACE_CXX_MODULE_SETS)
+    boost_message(DEBUG "boost_install_target: '${__TARGET}' has INTERFACE_CXX_MODULE_SETS=${INTERFACE_CXX_MODULE_SETS}")
+    set(__INSTALL_CXX_MODULES FILE_SET ${INTERFACE_CXX_MODULE_SETS} DESTINATION ${CONFIG_INSTALL_DIR})
+  endif()
+  get_target_property(HEADER_SET ${LIB} HEADER_SET)
+  if(HEADER_SET)
+    boost_message(DEBUG "boost_install_target: '${__TARGET}' has HEADER_SET=${HEADER_SET}")
+  endif()
+
   install(TARGETS ${LIB} EXPORT ${LIB}-targets
     # explicit destination specification required for 3.13, 3.14 no longer needs it
     RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
@@ -344,8 +354,16 @@ function(boost_install_target)
     ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
     PRIVATE_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
     PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
+    # explicit needed if used starting with cmake v3.28
+    FILE_SET CXX_MODULES DESTINATION ${CONFIG_INSTALL_DIR}
+    ${__INSTALL_CXX_MODULES}
+    CXX_MODULES_BMI DESTINATION ${CONFIG_INSTALL_DIR}/bmi-${CMAKE_CXX_COMPILER_ID}_$<CONFIG>
+    # explicit needed if used starting with cmake v3.23
+    FILE_SET HEADERS
+    FILE_SET ${HEADER_SET}
   )
 
+  # TODO(CK): what is this for?
   export(TARGETS ${LIB} NAMESPACE Boost:: FILE export/${LIB}-targets.cmake)
 
   if(MSVC)
@@ -362,7 +380,9 @@ function(boost_install_target)
     __boost_install_update_sources(${LIB} ${__EXTRA_DIRECTORY} ${__EXTRA_INSTALL_DIRECTORY})
   endif()
 
-  install(EXPORT ${LIB}-targets DESTINATION "${CONFIG_INSTALL_DIR}" NAMESPACE Boost:: FILE ${LIB}-targets.cmake)
+  install(EXPORT ${LIB}-targets DESTINATION "${CONFIG_INSTALL_DIR}" NAMESPACE Boost:: FILE ${LIB}-targets.cmake
+    CXX_MODULES_DIRECTORY .
+  )
 
   set_target_properties(${LIB} PROPERTIES _boost_is_installed ON)