Distributing C++20 modules as source

Hi all,

This is a follow-up of Advice on C++20 modules (Boost) - #13 by ben.boeckel. I’m writing a proof of concept with Boost, where every library becomes a module.

Just to illustrate, this is would be the CMake code for Boost.Variant2, which depends on Boost.assert, Boost.config and Boost.mp11:

  add_library(boost_variant2)
  target_sources(boost_variant2 PUBLIC FILE_SET CXX_MODULES FILES modules/variant2.cxx)
  target_include_directories(boost_variant2 PUBLIC include)
  target_compile_features(boost_variant2 PUBLIC cxx_std_23)
  set_target_properties(boost_variant2 PROPERTIES CXX_MODULE_STD 1)
  target_compile_definitions(boost_variant2 PRIVATE BOOST_CXX20_MODULE)
  target_compile_options(boost_variant2 PUBLIC -Wno-include-angled-in-module-purview) # TODO: scope this to clang
  target_link_libraries(boost_variant2
    PUBLIC
      Boost::assert
      Boost::config
      Boost::mp11
  )

Where targets like Boost::mp11 and Boost::assert are libraries added to the project just like the one shown above (and are thus C++20 modules, too). Everything uses the experimental import std feature in CMake.

The libraries get installed by the Boost main CMake script, with calls akin to:

  install(TARGETS ${LIB} EXPORT ${LIB}-targets
    # explicit destination specification required for 3.13, 3.14 no longer needs it
    RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
    LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
    ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
    PRIVATE_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    FILE_SET CXX_MODULES DESTINATION .
  )

While this is good, most of the above libraries are header-only. The add_library call generates a static library containing little more than the module initializer. Ideally, the modules for these libraries should follow a mechanism similar to standard library modules: we would distribute the headers and the module files, and the modules would be built entirely by the consuming project, using the optimization level, language dialect and other flags required by the target - following the same mechanics as standard modules.

I appreciate that this is still a work in progress even for standard modules, so it may still be a long way. But I’d like to know whether something like I describe is on the roadmap.

Syntax like the following would be convenient:

# Don't build any library straight away. Build it as required by dependent targets
add_library(boost_variant2 INTERFACE)
target_sources(boost_variant2 INTERFACE FILE_SET CXX_MODULES FILES modules/variant2.cxx)

Any suggestions on how to do this would be welcome.

Thanks,
Ruben.

sorry for late response.

I see at boost modules code like this:

// Copyright (c) 2016-2025 Antony Polukhin
//
// 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)

// To compile manually use a command like the following:
// clang++ -I ../include -std=c++20 --precompile -x c++-module any.cppm

module;

#include <boost/assert.hpp>
#include <boost/config.hpp>
#include <boost/throw_exception.hpp>

// XXX #include <boost/type_index.hpp>
import boost.type_index;

#ifdef BOOST_ANY_USE_STD_MODULE
import std;
#else
#include <memory>
#include <stdexcept>
#include <typeinfo>
#include <type_traits>
#include <utility>
#endif

#define BOOST_ANY_INTERFACE_UNIT

export module boost.any;

#ifdef __clang__
#   pragma clang diagnostic ignored "-Winclude-angled-in-module-purview"
#endif

#include <boost/any.hpp>
#include <boost/any/basic_any.hpp>
#include <boost/any/unique_any.hpp>

And I am wondering why the include of std headers are needed if import std; is not available?

From clangd, (clang tidy) I get warnings, this headers are not directly used!

And I have tested all interface header, if they are self contained:

# Copyright 2019 Mike Dev
# Distributed under the Boost Software License, Version 1.0.
# See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt

cmake_minimum_required(VERSION 3.25...4.2)
project(boost_any VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX)

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_COMPILER_IMPORT_STD)
        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()

#XXX target_include_directories(boost_any ${__scope} include)

target_sources(
    boost_any
    ${__scope}
    FILE_SET headers_public
    TYPE HEADERS
    BASE_DIRS include
    FILES
        include/boost/any/bad_any_cast.hpp
        include/boost/any/basic_any.hpp
        include/boost/any/fwd.hpp
        include/boost/any/unique_any.hpp
        include/boost/any/detail/config.hpp
        include/boost/any/detail/placeholder.hpp
)
target_link_libraries(
    boost_any
    ${__scope}
    Boost::config
    Boost::throw_exception
    Boost::type_index
)

add_library(Boost::any ALIAS boost_any)

if(BUILD_TESTING)
    add_subdirectory(test)
endif()

independent for the #include <>, the import of other modules does not work:

bash-5.3$ pwd
/Users/clausklein/Workspace/cpp/boost-git/libs/any
bash-5.3$ ninja -C ../../build boost_any
ninja: Entering directory `../../build'
[0/2] Re-checking globbed directories...
[1/2] Re-running CMake...
-- The CXX compiler identification is GNU 15.2.0
-- Checking whether CXX compiler has -isysroot
-- Checking whether CXX compiler has -isysroot - yes
-- Checking whether CXX compiler supports OSX deployment target flag
-- Checking whether CXX compiler supports OSX deployment target flag - yes
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/local/bin/g++-15 - skipped
-- Detecting CXX compile features
CMake Warning (dev) at /Users/clausklein/.local/share/cmake-4.2/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):
  /Users/clausklein/.local/share/cmake-4.2/Modules/CMakeDetermineCompilerSupport.cmake:113 (cmake_create_cxx_import_std)
  /Users/clausklein/.local/share/cmake-4.2/Modules/CMakeTestCXXCompiler.cmake:83 (CMAKE_DETERMINE_COMPILER_SUPPORT)
  CMakeLists.txt:13 (project)
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Detecting CXX compile features - done
-- Boost: using system layout: include, bin, lib, lib/cmake, share
-- Boost: using CMake 4.2.0-gd4539f6
-- Boost: Release build, static libraries, MPI OFF, Python OFF, testing OFF
-- Using `import std;`
-- Boost.Charconv: quadmath support ON
-- The C compiler identification is GNU 15.2.0
-- Checking whether C compiler has -isysroot
-- Checking whether C compiler has -isysroot - yes
-- Checking whether C compiler supports OSX deployment target flag
-- Checking whether C compiler supports OSX deployment target flag - yes
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/local/bin/gcc-15 - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Boost.Context: architecture x86_64, binary format mach-o, ABI sysv, assembler gas, suffix .S, implementation fcontext
-- The ASM compiler identification is GNU
-- Found assembler: /usr/local/bin/gcc-15
-- Boost.Fiber: NUMA target OS is none
-- Skipping Boost library 'graph_parallel', BOOST_ENABLE_MPI is OFF
-- Boost.Iostreams: ZLIB ON, BZip2 ON, LZMA ON (multithreaded), Zstd ON
-- Boost.Locale: iconv ON, ICU OFF, POSIX ON, std ON, winapi OFF
-- Boost.Math: standalone mode OFF
-- Skipping Boost library 'mpi', BOOST_ENABLE_MPI is OFF
-- Boost.Multiprecision: standalone mode OFF
-- Skipping Boost library 'parameter_python', BOOST_ENABLE_PYTHON is OFF
-- Using `import std;`
-- Skipping Boost library 'property_map_parallel', BOOST_ENABLE_MPI is OFF
-- Skipping Boost library 'python', BOOST_ENABLE_PYTHON is OFF
-- Boost.Stacktrace: noop ON, backtrace OFF, addr2line ON, basic ON, windbg OFF, windbg_cached OFF, from_exception ON
-- Boost.Thread: threading API is pthread
-- Using `import std;`
-- Configuring done (10.9s)
-- Generating done (0.3s)
-- Build files have been written to: /Users/clausklein/Workspace/cpp/boost-git/build
[0/4] Re-checking globbed directories...
[4/10] Building CXX object libs/any/CMakeFiles/boost_any.dir/modules/boost_any.cppm.o
FAILED: [code=1] libs/any/CMakeFiles/boost_any.dir/modules/boost_any.cppm.o libs/any/CMakeFiles/boost_any.dir/boost.any.gcm 
ccache /usr/local/bin/g++-15 -DBOOST_ANY_USE_STD_MODULE -DBOOST_USE_MODULES -I/Users/clausklein/Workspace/cpp/boost-git/libs/any/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/config/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/throw_exception/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/assert/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/type_index/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/container_hash/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/describe/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/mp11/include -O3 -DNDEBUG -std=gnu++23 -fvisibility=hidden -fvisibility-inlines-hidden -MD -MT libs/any/CMakeFiles/boost_any.dir/modules/boost_any.cppm.o -MF libs/any/CMakeFiles/boost_any.dir/modules/boost_any.cppm.o.d -fmodules-ts -fmodule-mapper=libs/any/CMakeFiles/boost_any.dir/modules/boost_any.cppm.o.modmap -MD -fdeps-format=p1689r5 -x c++ -o libs/any/CMakeFiles/boost_any.dir/modules/boost_any.cppm.o -c /Users/clausklein/Workspace/cpp/boost-git/libs/any/modules/boost_any.cppm
/Users/clausklein/Workspace/cpp/boost-git/libs/any/modules/boost_any.cppm:15:1: fatal error: unknown compiled module interface: no such module
   15 | import boost_type_index; <<<<<<<<<<<<<<<< wrong module name! CK
      | ^~~~~~
compilation terminated.
[5/10] Building CXX object libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o
FAILED: [code=1] libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o libs/type_index/CMakeFiles/boost_type_index.dir/boost.type_index.gcm 
ccache /usr/local/bin/g++-15 -DBOOST_TYPE_INDEX_USE_STD_MODULE -DBOOST_USE_MODULES -I/Users/clausklein/Workspace/cpp/boost-git/libs/type_index/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/config/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/container_hash/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/describe/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/mp11/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/throw_exception/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/assert/include -O3 -DNDEBUG -std=gnu++23 -fvisibility=hidden -fvisibility-inlines-hidden -MD -MT libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o -MF libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o.d -fmodules-ts -fmodule-mapper=libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o.modmap -MD -fdeps-format=p1689r5 -x c++ -o libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o -c /Users/clausklein/Workspace/cpp/boost-git/libs/type_index/modules/boost_type_index.cppm
/Users/clausklein/Workspace/cpp/boost-git/libs/type_index/modules/boost_type_index.cppm:42:1: fatal error: unknown compiled module interface: no such module
   42 | import std;
      | ^~~~~~
compilation terminated.
ninja: build stopped: subcommand failed.
bash-5.3$ cmake --version
cmake version 4.2.0-gd4539f6

CMake suite maintained and supported by Kitware (kitware.com/cmake).
bash-5.3$ ninja -C ../../build boost_type_index 
ninja: Entering directory `../../build'
[0/2] Re-checking globbed directories...
[1/3] Building CXX object libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o
FAILED: [code=1] libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o libs/type_index/CMakeFiles/boost_type_index.dir/boost.type_index.gcm 
ccache /usr/local/bin/g++-15 -DBOOST_TYPE_INDEX_USE_STD_MODULE -DBOOST_USE_MODULES -I/Users/clausklein/Workspace/cpp/boost-git/libs/type_index/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/config/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/container_hash/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/describe/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/mp11/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/throw_exception/include -I/Users/clausklein/Workspace/cpp/boost-git/libs/assert/include -O3 -DNDEBUG -std=gnu++23 -fvisibility=hidden -fvisibility-inlines-hidden -MD -MT libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o -MF libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o.d -fmodules-ts -fmodule-mapper=libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o.modmap -MD -fdeps-format=p1689r5 -x c++ -o libs/type_index/CMakeFiles/boost_type_index.dir/modules/boost_type_index.cppm.o -c /Users/clausklein/Workspace/cpp/boost-git/libs/type_index/modules/boost_type_index.cppm
/Users/clausklein/Workspace/cpp/boost-git/libs/type_index/modules/boost_type_index.cppm:42:1: fatal error: unknown compiled module interface: no such module
   42 | import std;
      | ^~~~~~
compilation terminated.
ninja: build stopped: subcommand failed.
bash-5.3$ 

OK, import std; need more love (CMAKE_CXX_MODULE_STD=ON), then it works:

# Standard stuff

.SUFFIXES:

MAKEFLAGS+= --no-builtin-rules  # Disable the built-in implicit rules.
# MAKEFLAGS+= --warn-undefined-variables        # Warn when an undefined variable is referenced.
# MAKEFLAGS+= --include-dir=$(CURDIR)/conan     # Search DIRECTORY for included makefiles (*.mk).

export hostSystemName=$(shell uname)

ifeq (${hostSystemName},Darwin)
  export LLVM_PREFIX=$(shell brew --prefix llvm)
  export LLVM_DIR=$(shell realpath ${LLVM_PREFIX})
  export PATH:=${LLVM_DIR}/bin:${PATH}

  # export CMAKE_CXX_STDLIB_MODULES_JSON=${LLVM_DIR}/lib/c++/libc++.modules.json
  # export CXX=clang++
  # export LDFLAGS=-L$(LLVM_DIR)/lib/c++ -lc++abi -lc++ -lc++experimental
  # export GCOV="llvm-cov gcov"

  ### TODO: to test g++-15:
  export CXX:=g++-15
  export CXXFLAGS:=-stdlib=libstdc++
  export GCC_PREFIX=$(shell brew --prefix gcc)
  export GCC_DIR=$(shell realpath ${GCC_PREFIX})
  export CMAKE_CXX_STDLIB_MODULES_JSON=${GCC_DIR}/lib/gcc/current/libstdc++.modules.json
else ifeq (${hostSystemName},Linux)
  export LLVM_DIR=/usr/lib/llvm-20
  export PATH:=${LLVM_DIR}/bin:${PATH}
  export CXX=clang++-20
endif

.PHONY: all clean distclean

all: build
	ln -sf build/compile_commands.json .
	ninja -C build

build: CMakeLists.txt
	cmake -S . -B build -G Ninja -D CMAKE_SKIP_INSTALL_RULES=ON \
		-D CMAKE_EXPERIMENTAL_CXX_IMPORT_STD="d0edc3af-4c50-42ea-a356-e2862fe7a444" \
		-D BOOST_USE_MODULES=ON -D CMAKE_CXX_MODULE_STD=ON \
		-D CMAKE_CXX_STANDARD=23 -D CMAKE_CXX_EXTENSIONS=ON -D CMAKE_CXX_STANDARD_REQUIRED=ON \
		-D CMAKE_CXX_STDLIB_MODULES_JSON=${CMAKE_CXX_STDLIB_MODULES_JSON} \
		-D BUILD_TESTING=ON -D BOOST_INCLUDE_LIBRARIES='any;type_index;test' # XXX --fresh

distclean: clean
	rm -rf build

# Anything we don't know how to build will use this rule.
% ::
	ninja -C build $(@)

bash-5.3$ make distclean
rm -rf build
bash-5.3$ make
cmake -S . -B build -G Ninja -D BOOST_USE_MODULES=ON -D CMAKE_SKIP_INSTALL_RULES=ON \
		-D CMAKE_EXPERIMENTAL_CXX_IMPORT_STD="d0edc3af-4c50-42ea-a356-e2862fe7a444" \
		-D CMAKE_CXX_STANDARD=23 -D CMAKE_CXX_EXTENSIONS=ON -D CMAKE_CXX_STANDARD_REQUIRED=ON \
		-D CMAKE_CXX_STDLIB_MODULES_JSON=/usr/local/Cellar/gcc/15.2.0/lib/gcc/current/libstdc++.modules.json \
		-D BOOST_INCLUDE_LIBRARIES='any' # XXX --fresh
-- The CXX compiler identification is GNU 15.2.0
-- Checking whether CXX compiler has -isysroot
-- Checking whether CXX compiler has -isysroot - yes
-- Checking whether CXX compiler supports OSX deployment target flag
-- Checking whether CXX compiler supports OSX deployment target flag - yes
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/local/bin/g++-15 - skipped
-- Detecting CXX compile features
CMake Warning (dev) at /Users/clausklein/.local/share/cmake-4.2/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):
  /Users/clausklein/.local/share/cmake-4.2/Modules/CMakeDetermineCompilerSupport.cmake:113 (cmake_create_cxx_import_std)
  /Users/clausklein/.local/share/cmake-4.2/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_MODULE_STD=ON
-- CMAKE_CXX_STANDARD=23
-- CMAKE_CXX_SCAN_FOR_MODULES=ON
-- Boost: using system layout: include, bin, lib, lib/cmake, share
-- Boost: using CMake 4.2.0-gd4539f6
-- Boost: Release build, static libraries, MPI OFF, Python OFF, testing OFF
-- Boost: libraries included: any
-- Using `import std;`
-- Using `import std;`
-- Configuring done (1.0s)
-- Generating done (0.0s)
-- Build files have been written to: /Users/clausklein/Workspace/cpp/boost-git/build
ln -sf build/compile_commands.json .
ninja -C build
ninja: Entering directory `build'
[0/2] Re-checking globbed directories...
[12/13] Linking CXX static library stage/lib/libboost_any.a
bash-5.3$ 

see Feat: modernize CMake files by ClausKlein · Pull Request #35 · boostorg/any · GitHub

Boost.Any is not part of the modularization effort I started. I targeted Mp11, Charconv, Core, Assert, Config and ThrowException. These are still in feature branches unfortunately. Links to the branches here: C++20 modules and Boost: a prototype

Anthony (the maintainer of Boost.Any) took a step forward and implemented some module support for his library himself. While most of it goes along the guidelines I proposed, there are some divergences (e.g. I don’t think supporting modules when import std is not available is worth it).

There are some things that can’t work until the common infrastructure of Boost adds support for modules. For instance, installing occurs in Boost.CMake: GitHub - anarthal/boost-cmake at feature/cxx20-modules

Last time I checked with the maintainers of the repos I linked above, they wanted to wait until CMake’s import std support went stable before merging any PRs. Is this something you think could happen soon?

see my collected understanding here

C++20/23 Modules – Quick Reference Cheat Sheet