Help/cmake-cxxmodules: document the implementation

This commit is contained in:
Ben Boeckel committed 2026-01-12 12:58:03 -05:00
1 parent 7006c2fb85
commit e9e94de9cb
1 file changed
+276
+276
View File
@@ -37,6 +37,8 @@ strategy, which is the most visible modules-related change for CMake users in
the context of the build. CMake provides multiple ways to control the
scanning behavior of source files.
.. _cxxmodules-scanning-control:
Scanning Control
================
@@ -410,6 +412,257 @@ within the target.
.. _`P1689R5`: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html
Implementation Details
----------------------
This section describes how CMake actually structures the build graph, the data
passed between various parts, and the files which contain that data. It is
intended to be used both as functional documentation and as a guide to help
those debugging a module build to understand where to locate various bits of
data.
.. note::
This section documents internal implementation details that may be useful
for :manual:`toolchain file <cmake-toolchains(7)>` authors or during
debugging of a module-related issue. Projects should not need to inspect
or modify any of the variables, properties, files, or targets mentioned
here.
Toolchain (scanning)
^^^^^^^^^^^^^^^^^^^^
Compilers which support modules must also provide a scanning tool. This will
usually be either the compiler itself with some extra flags or a tool shipped
with the compiler. The command template for scanning is stored in the
``CMAKE_CXX_SCANDEP_SOURCE`` variable. The command is expected to write
`P1689R5`_ results to the ``<DYNDEP_FILE>`` placeholder. Additionally, the
command should provide any :term:`discovered dependencies` to the
``<DEP_FILE>`` placeholder. This allows :term:`build tools <build tool>` to
rerun the scan if any of the dependencies of the scanning command change.
Additionally, toolchains should set the following variables:
* ``CMAKE_CXX_MODULE_MAP_FORMAT``: The format of the :term:`module map`
describing where dependent :term:`BMI` files for imported modules exist
during compilation. Must be one of ``gcc``, ``clang``, or ``msvc``.
* ``CMAKE_CXX_MODULE_MAP_FLAG``: The arguments used to inform the compiler of
the :term:`module map` file. It should use the ``<MODULE_MAP_FILE>``
placeholder.
* ``CMAKE_CXX_MODULE_BMI_ONLY_FLAG``: The arguments used to compile only a
:term:`BMI` file from a :term:`module interface unit`. This is used when
consuming modules from external projects to compile :term:`BMI` files for
use within the current build.
If a toolchain does not provide the ``CMAKE_CXX_MODULE_BMI_ONLY_FLAG``, it
will not be able to consume modules provided by ``IMPORTED`` targets.
Toolchain (``import std``)
^^^^^^^^^^^^^^^^^^^^^^^^^^
If the toolchain supports ``import std``, it must also provide a toolchain
identification module named ``${CMAKE_CXX_COMPILER_ID}-CXX-CXXImportStd``.
.. note::
Currently only CMake may provide these files due to the way they are
included. Once ``import std`` is no longer experimental, external
toolchains may provide support independently as well.
This module must provide the ``_cmake_cxx_import_std`` command. It will be
passed two arguments: the version of the C++ standard (e.g., ``23``) and the
name of a variable in which to place the result of its ``import std`` support.
The variable should be filled in with CMake source code which declares the
``__CMAKE::CXX${std}`` target, where ``${std}`` is the version passed in. If
the target cannot be made, the source code should instead set the
``CMAKE_CXX${std}_COMPILER_IMPORT_STD_NOT_FOUND_MESSAGE`` variable to the
reason that ``import std`` is not supported in the current configuration.
Note that CMake will guard the returned code with conditional checks to ensure
that the target is only defined once.
Ideally, the ``__CMAKE::CXX${std}`` target will be an ``IMPORTED``
``INTERFACE`` target with the ``std`` module sources attached to it. However,
it may be necessary to compile objects for some implementations. Object files
are required when there are symbols expected to be provided by the consumer of
the module by compiling it. There is a concern that, if this happens, more
than once within a program, this will result in duplication of these symbols
which may violate the :term:`ODR` for them.
As an example, if consumers of a module are expected to provide symbols for
that module, the use of the module is then a global property of the program
and cannot be abstracted away. Imagine that a library exposes a C API but
uses a C++ module internally. If it is supposed to provide the module
symbols, anything using the C API needs to cooperate with its internal module
usage if it wants to use the same module for its own purposes. If both end up
providing symbols for the imported module, there may be conflicts.
Configure
^^^^^^^^^
During the configure step, CMake needs to track which sources care about
modules at all. See :ref:`Scanning Control <cxxmodules-scanning-control>` for
how each source determines whether it cares about modules or not. CMake
tracks these in its internal target representation structure (``cmTarget``).
The set of sources which need to be scanned may be modified using the
:command:`target_sources`, :command:`target_compile_features`, and
:command:`set_property` commands.
Additionally, targets may use the :prop_tgt:`CXX_MODULE_STD` target property
to indicate that ``import std`` is desired within the target's sources.
Generate
^^^^^^^^
During generation, CMake needs to add additional rules to ensure that the
sources providing modules can be built before sources that import those
modules. Since CMake uses a :term:`static build`, the build graph must
contain all possible commands for scanning and module generation. The
dependency edges between commands to ensure that modules are provided will
then ensure that the build graph executes correctly. This means that, while
all sources may get scanned, only modules that are actually used will be
generated.
The first step CMake performs is to generate a :term:`synthetic target` for
each unique usage of a module-providing target. These targets are based on
other targets, but provide only :term:`BMI` files for other targets rather
than object files. This is because the compatibility of :term:`BMI` files is
extremely narrow and cannot be shared between arbitrary ``import`` instances.
Due to the internal workings of toolchains, there can generally only be a
single set of settings for a variety of flags for any one compilation,
including :term:`BMI` files for imported modules. As an example, the C++
standard in use needs to be consistent across all modules, but there are many
settings which may cause incompatibilities.
.. note::
CMake currently assumes that all usages are compatible and will only create
one set of :term:`BMIs <BMI>` for each target. This may cause build
failures where multiple :term:`BMI` files are required, but CMake only
provides one set. See `CMake Issue 25916`_ for progress on removing this
assumption.
.. _`CMake Issue 25916`: https://gitlab.kitware.com/cmake/cmake/-/issues/25916
Once all of the :term:`synthetic targets <synthetic target>` are created,
CMake looks at each target that has any source that might use C++ modules and
creates a command to :term:`scan` each of them. This command will output a
`P1689R5`_-formatted file describing the C++ modules it uses and provides (if
any). It will also create a command to :term:`collate` module dependencies
for the eligible compilations. This command depends on the :term:`scan`
results of all eligible sources, information about the target itself, as well
as the :term:`collate` results of any dependent targets which provide C++
modules. The :term:`collate` step uses a target-specific
``CXXDependInfo.json`` file which contains the following information:
- ``compiler-*``: basic compiler information (``id``, ``frontend-variant``,
and ``simulate-id``) which is used to generate correctly formatted paths
when generating paths for the compiler
- ``cxx-modules``: a map of object files to the ``FILE_SET`` information,
which is used to enforce :term:`module visibility` and generate install
rules for :term:`module interface unit` sources
- ``module-dir``: where to place :term:`BMI` files for this target
- ``dir-{cur,top}-{src,bld}``: the source (``src``) and build (``bld``)
directories for the current directory (``cur``) and the top (``top``) of the
project, used to compute accurate relative paths for the :term:`build tool`
dynamic dependencies
- ``exports``: The list of exports which both contain the target and are
providing C++ module information, used to provide accurate module properties
on ``IMPORTED`` targets from the exported targets.
- ``bmi-installation``: installation information, used to generate install
scripts for :term:`BMI` files
- ``database-info``: information required to generate :term:`build database`
information if requested by :prop_tgt:`EXPORT_BUILD_DATABASE`
- ``sources``: list of other source files in the target, used to add to the
:term:`build database` if requested
- ``config``: the configuration for the target, used to set the appropriate
properties in generated export files
- ``language``: the language (e.g., C++ or Fortran) the
:term:`collation <collate>` metadata file is describing
- ``include-dirs`` and ``forward-modules-from-target-dirs``: unused for C++
For each compilation, CMake will also provide a :term:`module map` which will
be created during the build by the :term:`collate` command. How this is
provided to the compiler is specified by the ``CMAKE_CXX_MODULE_MAP_FORMAT``
and ``CMAKE_CXX_MODULE_MAP_FLAG`` toolchain variables.
Scan
^^^^
The compiler is expected to implement the :term:`scan` command. This is
because only the compiler itself can reliably answer preprocessor predicates
like ``__has_builtin`` in order to provide accurate module usage information
in the face of arbitrary flags that may be used when compiling sources.
CMake names these files with the ``.ddi`` extension, which stands for "dynamic
dependency information". These files are in `P1689R5`_ format and are used by
the :term:`collate` command to perform its tasks.
Collate
^^^^^^^
The :term:`collate` command performs the bulk of the work to make C++ modules
work within the build graph. It consumes the following files as input:
- ``CXXDependInfo.json`` from the generate step
- ``.ddi`` files from the :term:`scanning <scan>` results of the target's
sources
- ``CXXModules.json`` files output from eligible dependent targets'
:term:`collate` commands
It uses the information from these files to generate:
- ``CXX.dd`` files to inform the :term:`build tool` of dependencies that exist
between the compilation of a source and the :term:`BMI` files of the modules
that it imports
- ``CXXModules.json`` files for use in :term:`collate` commands of depending
targets
- ``*.modmap`` files for each compilation to find :term:`BMI` files for
imported modules
- ``install-cxx-module-bmi-$<CONFIG>.cmake`` scripts for the installation of
any :term:`BMI` files (included by the ``install`` scripts)
- ``target-*-$<CONFIG>.cmake`` export files for any exports of the target to
provide the :prop_tgt:`IMPORTED_CXX_MODULES_<CONFIG>` properties
- ``CXX_build_database.json`` :term:`build database` files for the target when
the its :prop_tgt:`EXPORT_BUILD_DATABASE` property is set
During its processing, it enforces the following guarantees:
- :term:`BMI` usage is consistent
- :term:`module visibility` is respected
C++ modules have the rule that only a single module of a given name may
exist within a program. This is not exactly enforceable with the existence of
private modules, but it is enforceable for public modules. The enforcement is
done by the :term:`collate` command. Part of the ``CXXModules.json`` files is
the set of modules that are transitively imported by each module it provides.
When a module is then imported, the :term:`collate` command ensures that all
modules with a given name agree upon a given :term:`BMI` file to provide that
module.
Compile
^^^^^^^
Compilation uses the :term:`module map` file generated by the :term:`collate`
command to find imported modules during compilation. Because CMake only
provides the locations of modules that are discovered by the :term:`scan`
command, any modules missed by it will not be provided to the compilation.
It is possible for toolchains to reject the :term:`BMI` file that CMake
provides to a compilation as incompatible. This is because CMake assumes that
all usages are compatible at the moment. See `CMake Issue 25916`_ for
progress on removing this assumption.
Install
^^^^^^^
During installation, install scripts which have been written by the
:term:`collate` command during the build are included so that any :term:`BMI`
files are installed as needed. These need to be generated, as it is not
known what the :term:`BMI` file names will be during CMake's generation
(because CMake names the :term:`BMI` files after the module name itself).
These install scripts are included with the ``OPTIONAL`` keyword, so an
incomplete build may result in an incomplete installation as well.
Alternative Designs
-------------------
@@ -595,6 +848,10 @@ Module Compilation Glossary
Compiled Module Interface. Alternative name for :term:`BMI` used by some
compilers.
build database
A JSON file containing compilation commands, module dependencies, and
grouping information. Used for IDE integration and build analysis.
build system
A tool that facilitates the building of software which includes a model
of how components of the build relate to each other. For example, CMake,
@@ -613,6 +870,10 @@ Module Compilation Glossary
ensure correct compilation order and to provide metadata for other parts
of the build (e.g., installation or a :term:`build database`).
discovered dependencies
Dependencies found during the processing of a command that do not need to
be explicitly declared.
dynamic dependencies
Dependencies which require a separate command to detect so that a further
command may have its dependencies satisfied.
@@ -652,10 +913,17 @@ Module Compilation Glossary
using ``export module``. Such a unit may or may not be also be a
:term:`partition unit`.
module map
A compiler-specific file mapping module names to BMI locations.
module visibility
CMake's enforcement of access rules for modules based on their
declaration scope (PUBLIC/PRIVATE).
ODR
One Definition Rule. The C++ requirement that any entity be defined
exactly once per program.
partition unit
A :term:`translation unit` which describes a module with a partition name
(i.e., `module MODNAME:PARTITION;`). The partition may or may not use
@@ -671,11 +939,19 @@ Module Compilation Glossary
The process of analyzing a :term:`translation unit` to discover module
imports and exports.
static build
A build configuration where all compilation rules are determined at
generate time.
strong module ownership
C++ implementations have settled on a model where the module "owns" the
symbols declared within it. In practice, this means that the module name
is included into the symbol mangling of entities declared within it.
synthetic target
A CMake-generated build target used to supply :term:`BMIs <BMI>` to a
specific user of a module-providing target.
translation unit
The smallest component of a compilation for a C++ program. Generally,
there is one translation unit per source file. C++ source files which do