CMAKE-CXXMODULES(7)
๐ฆ CMAKE-CXXMODULES(7) โ the CMake C++ Modules Support Reference, but make it make sense
okay bestie, welcome to cmake-cxxmodules(7), the CMake manual page that explains C++ Modules support. Added in version 3.28. โจ
So here's the tea: C++ 20 introduced the concept of "modules" to the language. The design requires build systems to order compilations to satisfy import statements reliably. CMake's implementation asks the compiler to scan source files for module dependencies during the build, collates scanning results to infer ordering constraints, and tells the build tool how to dynamically update the build graph. iconic behaviour, honestly ๐
๐ธ COMPILATION STRATEGY
With C++ modules, compiling a set of C++ sources is no longer embarrassingly parallel. no cap, that era is over ๐ญ That is, any given source may require the compilation of another source file first in order to provide a "BMI" (or "CMI") that C++ compilers use to satisfy import statements in other sources.
With included headers, sources could share their declarations so that any consumers could compile independently. With modules, the compiler now generates BMI files during compilation based on the contents of the source file and its export statements. This means that, to ensure a correct build without having to regenerate the build graph (by running configure and generate steps) for every source change, the correct ordering must be determined from the source files during the build phase. bestie the build graph is doing cardio now ๐
Build systems must be able to order these compilations within the build graph. There are multiple strategies that are suitable for this, but each has advantages and disadvantages. CMake uses a "scanning" step 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. ๐
โจ SCANNING CONTROL โจ
Whether or not sources get scanned for C++ module usage is dependent on the following queries. The first query that provides a decision of whether to scan or not is used. (first answer wins, no take-backs, periodt)
- If the source file belongs to a file set of type
CXX_MODULES, it will be scanned. ๐ฅ - If the target does not use at least C++ 20, it will not be scanned.
- If the source file is not the language
CXX, it will not be scanned. - If the source file belongs to a file set that is not of type
CXX_MODULES, and theCXX_SCAN_FOR_MODULESfile set property is set, its value will be used. - If the
CXX_SCAN_FOR_MODULESsource file property is set in the target's directory, its value will be used. - If the
CXX_SCAN_FOR_MODULEStarget property is set, its value will be used. Set theCMAKE_CXX_SCAN_FOR_MODULESvariable to initialize this property on all targets as they are created. ๐ซถ - Otherwise, the source file will be scanned if the compiler and generator support scanning. See policy
CMP0155.
heads up! Note that any scanned source will be excluded from any unity build (see UNITY_BUILD) because module-related statements can only happen at one place within a C++ translation unit. literally one spot only, the compiler said so ๐
๐ง COMPILER SUPPORT
The list of compilers for which CMake supports scanning sources for C++ modules includes: ๐
- MSVC toolset
14.34and newer (provided with Visual Studio17.4and newer) - LLVM/Clang
16.0and newer- Added in version 4.4:
clang-clversion19.1and newer, except with the Visual Studio Generators (i.e., theClangCltoolset).
- Added in version 4.4:
- GCC
14and newer
๐ IMPORT STD SUPPORT
Support for import std is limited to the following toolchain and standard library combinations: โจ
- Clang
18.1.2and newer with standard librarylibc++orlibstdc++ - MSVC toolset
14.36and newer (provided with Visual Studio17.6and newer) - GCC
15and newer
Note: Ubuntu prior to26.04ships brokenlibstdc++.modules.jsonfiles. See Ubuntu issue 2141579 โhttps://bugs.launchpad.net/ubuntu/+source/gcc-15/+bug/2141579. not us catching strays ๐ญ
The CMAKE_CXX_COMPILER_IMPORT_STD variable lists standard levels which have support for import std in the active C++ toolchain. ๐
Additionally, only the Ninja Generators currently support import std at this time because Visual Studio Generators do not support building BMIs for IMPORTED targets. the VS generators are sitting this one out, respectfully
Note: This support is provided only when experimental support forimport stdhas been enabled by theCMAKE_EXPERIMENTAL_CXX_IMPORT_STDgate. ๐
๐ GENERATOR SUPPORT
The list of generators which support scanning sources for C++ modules includes: ๐
NinjaNinja Multi-ConfigVisual Studio 17 2022Visual Studio 18 2026
Note that the Ninja Generators require ninja 1.11 or newer. (update ur ninja bestie, i'm begging) ๐
๐ Limitations
There are a number of known limitations of the current C++ module support in CMake. Known limitations or bugs in compilers are not listed here, as these can change over time. compiler drama is not our drama
For all generators:
- Header units are not supported. ๐
For the Visual Studio Generators:
- Only Visual Studio 2022 and MSVC toolsets
14.34(Visual Studio17.4) and newer are supported. - Exporting or installing BMI or module information is not supported.
- Compiling BMIs from
IMPORTEDtargets with C++ modules (includingimport std) is not supported. - Use of modules provided by
PRIVATEsources fromPUBLICmodule sources is not diagnosed.
Separately, as a design choice, CMake does not express configuration-agnostic module maps for imported targets. The IMPORTED_CXX_MODULES_<CONFIG> target property is always tied to a specific configuration. This can lead to some friction when importing/exporting targets from/to configuration-unaware build systems. Future work will alleviate this restriction. we love a lil roadmap moment ๐ฆ
๐ USAGE
๐ฉน Troubleshooting CMake
Okay so this section is literally here to answer the questions everyone keeps asking about how CMake actually does the thing, and to help you โจdiagnoseโจ or explain errors in CMake's C++ modules support. Think of it as the group chat FAQ. ๐ซถ
๐ File Extension Support
Big news bestie: CMake imposes no requirements upon file extensions for modules of any unit type. None. Zero. Go off.
There are definitely preferences that differ between toolchains โ like .ixx on MSVC and .cppm on Clang โ but there is no universally agreed-upon extension. So instead, CMake only requires that the file be recognized as a CXX-language source file. By default, any recognized extension will suffice, but the LANGUAGE property may be used with any other extension as well. iykyk ๐
๐ท๏ธ File Name Requirements
The name of a module has no relation to the name or path of the file in which its declaration resides. The C++ standard has no requirements here and neither does CMake. Periodt.
That said โ it may be useful to have some pattern in use within a project for easier navigation within environments that lack IDE-like "find symbol" functionality (e.g., on code review platforms). Just a vibe suggestion, not a rule. ๐ธ
๐ Scanning Without Modules
A super common problem for projects that have not yet adopted modules is unnecessary scanning of sources. ๐ญ This typically happens when a C++20 project becomes aware of CMake 3.28, or a 3.28-aware project starts using C++20. Either case ends up setting CMP0155 to NEW, which enables scanning of C++ sources with C++20 or newer by default.
The easiest way for projects to turn this off is to add:
set(CMAKE_CXX_SCAN_FOR_MODULES 0)
near the top of their top-level CMakeLists.txt file. ๐
Note that it should not be in the cache, as it may otherwise affect projects using it via FetchContent. Also pay attention to vendored projects which may want to enable scanning for their own sources โ because this would change the default for them as well. ๐
๐ Debugging Module Builds
This section aims to help diagnose or explain common errors that may arise on the build side of CMake's C++ modules support. Buckle up. ๐ข
๐ Import Cycles
The C++ standard does not allow for cycles in the import graph of a translation unit; therefore, CMake does not either. Currently, CMake will leave it to the build tool to detect this based on the dynamic dependencies used to order module compilations. CMake Issue 26119 (https://gitlab.kitware.com/cmake/cmake/-/issues/26119) tracks the desire to improve the user experience in this case. So likeโฆ it's known, bestie. ๐
๐งฉ Internal Module Partition Extension
Storytime ๐ง โ when the implementation of building C++ modules was first investigated, it appeared as though there existed a type of translation unit that represented the intersection of a partition unit and an implementation unit. Initial CMake designs included specific support for these translation units; however, after a closer reading of the standard, these did not actually exist. ๐ญ
These units would have had module M:part; as their module declaration statement. The problem is that this is also the exact syntax also used for declaring module partitions that do not contribute to the external interface of the primary module. Only MSVC supports this distinction. Other compilers do not, and will treat such files as an internal partition unit โ and then CMake will raise an error that a module-providing C++ source must be in a FILE_SET of type CXX_MODULES.
The fix is to not use the extension, as it provides no further expressivity over not using the extension. All implementation unit source files should instead only use module M; as their module declaration statement, regardless of what partition the defined entities are declared within. As an example:
// module-interface.cpp
export module M;
export int foo();
// module-impl.cpp
module M:part; // module M:part; looks like an internal partition
int foo() { return 42; }
Instead use explicit interface/implementation separation:
// module-interface.cpp
export module M;
export int foo();
// module-impl.cpp
module M;
int foo() { return 42; }
๐ Module Visibility
CMake enforces module visibility between and within targets. This essentially means that a module (say, I) provided from a PRIVATE FILE_SET on a target T may not be imported by:
- other targets depending on
T; or - modules provided from a
PUBLICFILE_SETon targetTitself.
This is because, in general, all imported entities from a module must also be importable by all potential importers of that module. Even if module I is only used within parts of a module without the export keyword, it may affect things within it in such a way that consumers of the module need to be able to transitively import it to work correctly. And since CMake uses the module visibility to determine whether to install module interface units, a PRIVATE module interface unit will not be installed โ meaning that usage of any installed module which imports I would not work. ๐
Instead, import PRIVATE C++ modules only from within an implementation unit, as these are not exposed to consumers of any module. Slay. ๐
๐ DESIGN
The design of CMake's C++ module support makes a number of trade-offs compared to other designs. First, CMake's chosen design will be covered. Later sections cover alternative designs that were not chosen for CMake's implementation.
Overall, the designs fall somewhere along two axes:
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโ
โ Explicit Dynamic โ Explicit Static โ Explicit Fixed โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโค
โ Implicit Dynamic โ Implicit Static โ Implicit Fixed โ
โโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโ
- Explicit builds control which modules are visible to each translation unit directly. For example, when compiling a source requiring a module
M, the compiler will be given information which states the exact BMI file to use when importing theMmodule. - Implicit builds can control module visibility as well, but do so by instead grouping BMIs into directories which are then searched for files to satisfy import statements in the source file.
- Static builds use a static set of build commands in order to complete the build. There must be support to add edges between nodes at build time.
- Dynamic builds may create new build commands during the build and schedule any discovered work during the build.
- Fixed builds are generated with all module dependencies already known.
๐ฏ Design Goals
CMake's implementation of building C++ modules focuses on the following design goals:
- Correct Builds
- Deterministic Builds
- Support Generated Sources
- Static Communication
- Minimize Regeneration
โ Correct Builds
Above all else, an incorrect build is a frustrating experience for all involved. ๐ค A build which does not detect errors and instead lets a build with detectable problems run to completion is a good way to start wild goose chase debugging sessions. CMake errs on the side of avoiding such situations. Periodt.
๐ฒ Deterministic Builds
Given an on-disk state of a build, it should be possible to determine what steps will happen next. This does not mean that the exact order of rules within the build that can be run concurrently is deterministic โ instead it means that the set of work to be done and its results are deterministic. For example, if there is no dependency between tasks A and B, A should have no effects on the execution of B and vice versa. Iconic, honestly. โจ
๐ ๏ธ Support Generated Sources
Code generation is prevalent in the C++ ecosystem, so only supporting modules in files whose content is known at configure time is not suitable. Without supporting generated sources which use or provide modules, code generation tools are effectively cut off from the use of modules, and any dependencies of generated sources must also provide non-modular ways of using their interfaces (i.e., provide headers). Given that all C++ implementations use strong module ownership for symbol mangling, this is problematic when such interfaces end up referring to compiled symbols in other libraries. ๐ฌ
๐จ Static Communication
All communication between different steps of the build should be handled statically. Given the build tools that CMake supports, it is challenging to establish a controlled lifetime for a companion tool that needs to interact during compilation. Neither make nor ninja offer a way to start a tool at the beginning of a build and ensure it is stopped at the end. Instead, communication with compilers is managed through input and output files, using dependencies in the build tool to keep everything up-to-date. This approach enables standard debugging strategies for builds and allows developers to run build commands directly when investigating issues, without needing to account for other tools running in the background. ๐
โป๏ธ Minimize Regeneration
Active development of a build with modules should not require the build graph to be regenerated on every change. This means that the module dependencies must be constructed after the build graph is available. Without this, a correct build would need to regenerate the build graph any time a module-aware source file is edited, as any changes may alter module dependencies.
It also means that all module-aware sources must be known at configure time (even if they do not yet exist) so that the build graph can include the commands to scan for their dependencies. ๐
Note: There is a known issue with ninja which can result in an erroneous detection of a dependency cycle when the dependency order between two sources reverses (i.e.,aimportingbbecomesbimportinga) between two builds. See ninja issue 2666 (https://github.com/ninja-build/ninja/issues/2666) for details. ๐
๐ง Use Case Considerations
The design goals described above constrain the implementation. Additionally, mixed configurations are supported by CMake via multi-config generators such as Ninja Multi-Config and Visual Studio Generators. This section describes how CMake addresses these constraints. ๐ซถ
๐ญ Selected Design
Okay so the big-brain move CMake pulls here: it "scans" your sources to pull out the ordering dependency tea โ โ like, which file needs which module before it can even think about compiling โ and then it updates the build graph by adding new edges between the edges that already exist. Sneaky? A little. Effective? Extremely. ๐
How it actually goes down: take the per-source scan results (those are the P1689R5 files, linked at <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html>), then "collate" them for each target along with info from that target's dependencies. The collator's main-character job is generating the "module map" files that get handed to each compile rule โ those carry the paths to the BMIs needed to satisfy your import statements โ and then it tips off the build tool about which dependencies are needed to satisfy those imports during compilation. ๐ซถ
The collator also uses build-time info to generate install rules for the module interface units, their BMIs, and properties for any exported targets with C++ modules. And it's the bouncer at the club enforcing that PRIVATE modules may not be used by other targets or by any PUBLIC module interface unit within the target. No gate-crashing, bestie. ๐ซ
๐จ Implementation Details
Warning: the implementation details are not a stable interface. Each version of CMake may revise them without any attempt at providing compatibility. External toolchain maintainers are responsible for updating their implementations for each version of CMake they support. ๐ฌ
This section is the behind-the-scenes tour: how CMake actually structures the build graph, what data gets passed between the various parts, and which files hold that data. It's meant to work double duty โ functional documentation and a guide for anyone debugging a module build who's like "where tf is this bit of data living??" ๐
Note: this section documents internal implementation details that may be useful for toolchain file <#manual:cmake-toolchains(7)> authors or while you're debugging a module-related issue. Projects should not need to inspect or modify any of the variables, properties, files, or targets mentioned here. Like, stay out of the kitchen unless you're the chef. ๐ฉโ๐ณ
๐ ๏ธ Toolchain (scanning)
Compilers that support modules gotta also bring a scanning tool to the party. Usually that's either the compiler itself with some extra flags, or a tool that ships with the compiler. The command template for scanning lives in the CMAKE_CXX_SCANDEP_SOURCE variable. That command is expected to write P1689R5 <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html> results into the <DYNDEP_FILE> placeholder. โจ
On top of that, the command should hand any discovered dependencies over to the <DEP_FILE> placeholder โ that's what lets build tools re-run the scan if any of the scanning command's own dependencies change. Very "keep your receipts" of it. ๐งพ
Toolchains should also set these variables, bestie, write them down:
CMAKE_CXX_MODULE_MAP_FORMAT: the format of the module map describing where dependent BMI files for imported modules exist during compilation. Must be one ofgcc,clang, ormsvc. Pick exactly one, no vibes-based guessing. ๐ฏCMAKE_CXX_MODULE_MAP_FLAG: the arguments used to inform the compiler of the module map file. It should use the<MODULE_MAP_FILE>placeholder.CMAKE_CXX_COMPILE_BMI: the command template to compile a BMI file from a module interface unit. Used whenCMAKE_CXX_MODULE_BMI_ONLY_FLAGis not completely additive to an object compilation template.CMAKE_CXX_MODULE_BMI_ONLY_FLAG: the arguments used to compile only a BMI file from a module interface unit. This is what gets used when consuming modules from external projects to compile BMI files for use within the current build.
Heads up: if a toolchain doesn't provideCMAKE_CXX_COMPILE_BMIorCMAKE_CXX_MODULE_BMI_ONLY_FLAG, it will not be able to consume modules provided byIMPORTEDtargets. That's just how it is, no cap. ๐
โ๏ธ Configure
During the configure step, CMake needs to keep track of which sources care about modules at all. (Peep Scanning Control for how each source decides whether it cares.) CMake tracks these in its internal target representation structure (cmTarget). The set of sources that need to be scanned may be modified using the target_sources() <#command:target_sources>, target_compile_features() <#command:target_compile_features>, and set_property() <#command:set_property> commands. ๐
Also, targets can use the CXX_MODULE_STD <#prop_tgt:CXX_MODULE_STD> target property to say "yeah I want import std in this target's sources." Very demure, very mindful. ๐ธ
๐๏ธ Generate
During generation, CMake needs to add extra rules so that the sources providing modules get built before the sources importing those modules. Since CMake uses a static build, the build graph has to contain all possible commands for scanning and module generation. The dependency edges between commands โ the ones that make sure modules actually get provided โ are what make the build graph execute correctly. Which means: even though all sources may get scanned, only the modules that are actually used will get generated. Efficient queen. ๐
The first thing CMake does is generate any synthetic targets needed for a module-providing target. These bad boys are based on the module provider, but they produce only BMI files for consumers rather than object files. Why, you ask? Because BMI file compatibility is extremely narrow and cannot be shared between arbitrary import instances. It's giving exclusive VIP list. ๐๏ธ
Because of how toolchains work internally, there can generally only be a single set of settings for a whole variety of flags for any one compilation โ including BMI files for imported modules. For example: the C++ standard in use needs to be consistent across all modules imported to a given translation unit. Mixing standards causes incompatibilities, and nobody wants that mess. ๐ญ
Added in version 4.4: Prior to CMake 4.4, CMake just assumed all usages were compatible and would only create one set of BMIs for each module-providing target. This could cause build failures due to incompatible BMI usage. In CMake 4.4 and later, CMake attempts to determine BMI compatibility on a target-by-target basis and will generate synthetic targets to provide compatible BMIs for consumers. Growth. ๐
The generated synthetic target is a little hybrid cutie โ a mix of properties from both the provider of the module and the consumer. COMPILE_FEATURES <#prop_tgt:COMPILE_FEATURES> and COMPILE_OPTIONS <#prop_tgt:COMPILE_OPTIONS> are adopted from the consumer, while all other relevant properties are inherited from the provider. Best of both worlds, fr fr. ๐ฆ
Once all the synthetic targets are created, CMake looks at every target that has any source that might use C++ modules and creates a command to scan each one of them. That command outputs a P1689R5 <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html>-formatted file describing the C++ modules it uses and provides (if any). It also creates a command to collate module dependencies for the eligible compilations. That command depends on the scan results of all eligible sources, information about the target itself, and the collate results of any dependent targets that provide C++ modules. ๐งฉ
The collate step uses a target-specific CXXDependInfo.json file, and that file contains this whole info dump:
compiler-*: basic compiler information (id,frontend-variant, andsimulate-id) which is used to generate correctly formatted paths when generating paths for the compilercxx-modules: a map of object files to theFILE_SETinformation, which is used to enforce module visibility and generate install rules for module interface unit sourcesmodule-dir: where to place BMI files for this targetdir-{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 build tool dynamic dependenciesexports: the list of exports which both contain the target and are providing C++ module information, used to provide accurate module properties onIMPORTEDtargets from the exported targetsbmi-installation: installation information, used to generate install scripts for BMI filesdatabase-info: information required to generate build database information if requested byEXPORT_BUILD_DATABASE<#prop_tgt:EXPORT_BUILD_DATABASE>sources: list of other source files in the target, used to add to the build database if requestedconfig: the configuration for the target, used to set the appropriate properties in generated export fileslanguage: the language (e.g., C++ or Fortran) the collation metadata file is describinginclude-dirsandforward-modules-from-target-dirs: unused for C++. Yeah, they're just chilling there. ๐
Each entry in the cxx-modules map records the following:
bmi-only(bool):Trueif only the BMI, not the source of the BMI, is availablecompile-features(list[string]): cmake-compile-features(7) <#manual:cmake-compile-features(7)> used to build the objectcompile-options(list[string]): compilation options/flags used to build the object, except for those derived fromcompile-featuresdefinitions(list[string]): preprocessor defines used to build the objectdestination(string): intended install destination of the source fileinclude-directories(list[string]): include directories used to build the objectname(string): name of the file set which owns the source filerelative-directory(string): base path relative to which the source file will be relocated into the install destinationsource(string): path to the source filetype(string): type of the file set which owns the source filevisibility(string): visibility of the file set which owns the source file
For each compilation, CMake will also provide a module map which gets created during the build by the collate command. How that gets handed 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 scan command. Why? Because only the compiler itself can reliably answer preprocessor predicates like __has_builtin in order to give accurate module usage information โ especially in the face of arbitrary flags that may be used when compiling sources. Nobody else can read the compiler's mind like that. ๐ง
CMake names these files with the .ddi extension, which stands for "dynamic dependency information" (iykyk). These files are in P1689R5 <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1689r5.html> format and are used by the collate command to do its thing. ๐๏ธ
๐งต Collate
The collate command does the bulk of the work to make C++ modules function inside the build graph. It's the main character of this whole operation, honestly. ๐ It consumes the following files as input:
CXXDependInfo.jsonfrom the generate step.ddifiles from the scanning results of the target's sourcesCXXModules.jsonfiles output from eligible dependent targets' collate commands
It uses the information from these files to generate:
CXX.ddfiles, fr fr โ these are the ones that tell the build tool about the dependencies that exist between compiling a source and the BMI files of the modules it imports ๐CXXModules.jsonfiles, which get used in the collate commands of depending targets โจ*.modmapfiles, one per compilation, so the compilation can actually find the BMI files for the modules it imports ๐install-cxx-module-bmi-$<CONFIG>.cmakescripts, for installing any BMI files (these get included by the install scripts) ๐target-*-$<CONFIG>.cmakeexport files, for any exports of the target, so they can provide theIMPORTED_CXX_MODULES_<CONFIG>property ๐CXX_build_database.jsonbuild database files for the target, but only when itsEXPORT_BUILD_DATABASEproperty is set ๐
And like, during all of its processing, it enforces these guarantees (no cap):
- BMI usage is consistent โ
- module visibility is respected โ
So here's the C++ modules tea: only a single module of a given name may exist within a program. Iconic rule, lowkey hard to enforce when private modules are in the picture, but it is enforceable for public modules, and the enforcement is done by the collate command. Part of the CXXModules.json files is the set of modules that are transitively imported by each module it provides. So when a module gets imported, the collate command makes sure that all modules with a given name agree upon a given BMI file to provide that module. Periodt. ๐ซถ
๐ ๏ธ Compile
Compilation uses the module map file that the collate command generated to find imported modules during compilation. And here's the catch, bestie: because CMake only provides the locations of modules that are discovered by the scan command, any modules it missed will not be provided to the compilation. Missed by the scan = not showing up to the party. ๐
๐ฆ Install
During installation, the install scripts that were written by the collate command during the build get included, so that any BMI files are installed as needed. These have to be generated, because it is literally not known what the BMI file names will be during CMake's generation (that's because CMake names the BMI files after the module name itself, iykyk).
These install scripts are included with the OPTIONAL keyword, so an incomplete build may result in an incomplete installation as well. Heads up! ๐ญ
๐ Alternative Designs
There are alternative designs that CMake does not implement. This section is here to give a brief overview and explain why they weren't chosen for CMake's implementation. Grab a snack, it's giving design discourse. ๐
๐ญ Implicit Builds
An implicit build does module builds using compile-time search paths, which makes the implementation of the build simpler. This is certainly something that can be made to work โ but CMake's goals exclude it as a solution.
So when a build uses search directory management, the compiler is directed to place module output files into a specified directory. Those directories then get provided as search paths to any compilation that's allowed to use the modules within them.
This strategy risks running into problems with the Correct Builds goal, and it stems from the hazard of stale files just... chilling in the search directories. Since the build system is unaware of the actual files being written, it's difficult to know which files are allowed to be deleted (e.g., using ninja -t cleandead to remove outputs ninja has encountered but are no longer generated). Removal of intermediate files may also cause the build to become stuck if the outputs aren't known to the build system beyond consumers reporting "usage of file X".
There's also a need to at least do some level of ordering of BMI generation commands that share an output directory. Separate directories may be used to order groups of modules (e.g., one directory per target); otherwise, modules within the same directory may not assume that other modules writing to the shared directory will complete first. And if module paths are grouped accurately according to the module dependency graph, it's a small step to being an explicit build where the files are directly specified. Slay, but not what CMake picked. ๐
๐ Static Scanning
A fixed build performs a scan while generating the build graph and includes the necessary dependencies up-front. In CMake's case, it would look at the source files during the generate phase and add the dependencies directly to the build graph. This is more likely to be suitable for a build system that is also its own build tool, where build graph manipulation can be done cooperatively.
No matter whether it's integrated or not, this strategy necessitates either a suitable C++ parser to extract the information in the first place, or toolchain cooperation to obtain it. While module dependency information is available to a simpler C++ parser, dependencies may be hidden behind preprocessor conditionals that need to be understood in order to be accurate. Of course, choosing to not support preprocessor conditionals around import statements is also an option, but this may severely limit external library support.
For CMake, this strategy would mean that any change to a module-aware source file may need to trigger regeneration of the build graph. A benign edit would at least need to trigger the check for changed imports, but may skip actually regenerating if it's unchanged. This may be less critical for a build system that's also its own build tool, but it's a direct violation of the Minimize Regeneration goal. ๐
Plus, CMake's Support Generated Sources goal would be unsupportable with this strategy. CMake could defer scanning until the generated files are available, but those sources cannot be compiled until such a scan has been performed. That would mean there'd be some unbounded (but finite) number of regenerations of the build graph as sources become available. Not the vibe. ๐ญ
๐ฎ Module Mapping Service
Another strategy is to run a service alongside the build that can act as an oracle for where to place and discover modules. The compiler is instructed to query the service with questions such as "this source is exporting module X" and "this source is importing module Y" and receive the path to either create or find the BMI, respectively. In this case, the service dynamically implements the collation logic. Very main character of it, honestly. ๐
Of particular note: this conflicts with the Deterministic Builds and Static Communication goals, because the on-disk state may not match the actual state, and coordinating the lifetime of the build tool itself with the service is difficult. The primary missing feature is some signal when a build session starts and ends, so that such a service can know in what context it's answering requests. There also needs to be a way to resume a session and detect when a session is invalidated. No build tool that CMake supports today has such features. ๐
There are also hazards which conflict with the Correct Builds goal. When a module is imported, the compiler waits for a response before continuing. However, there's no guarantee that a (visible) module of that name even exists, so it may wait indefinitely. While waiting for a compilation to report that it creates that module, it may run into a dependency cycle which leaves the compilations hanging until some resource limit is reached (probably time, or that all possible providers of the module have not reported a module of that name). And while these compilations are waiting on answers, there's the question of how they affect the parallelism limits of the build tool in use. Do compilations waiting on an answer count towards the limit and block other compilations from launching to potentially discover the module? If they do not, what about other resources that may be held in use by those compilations (e.g., memory or available file descriptors)? Big questions, no answers, moving on. ๐ฆ
๐ธ POSSIBLE FUTURE ENHANCEMENTS ๐ธ
This section documents possible future enhancements to CMake's support of C++ modules. Nothing here is a guarantee of future implementation, and the ordering is arbitrary. Manifesting, basically. โจ
๐ Batch Scanning
It's possible to scan all sources within a target at once, which should be faster when sources share transitive includes. This does have side effects for incremental builds, though, because the update of any source in the target means that all sources in the target are scanned again. But given how much faster scanning can be, it should be negligible to do such "extra" scanning, assuming that unchanged results do not trigger recompilations. Obsessed with the efficiency tbh. ๐ซถ
๐ง BMI Modification Optimization
Currently, as with object files, compilers always update a BMI file even if the contents have not changed. Because modules increase the potential scope of "non-changes" to cause (conceptually) unnecessary recompilation, it might be useful to avoid recompilation of module consumers if the BMI file has not changed.
This might be achieved by wrapping the compilation to juggle the BMI through a cmake -E copy_if_different pass with ninja's restat = 1 feature, to avoid recompiling importers if the BMI file doesn't actually change. Cute little workaround, ngl. ๐
๐ฆ Easier Source Specification
The initial implementation of CMake's module support had used the "just list sources; CMake will figure it out" pattern. However, this ran into issues related to other metadata requirements. These were discovered while implementing CMake support beyond just building the modules-using code.
Conflicts with Separate BMI Generation on a single target, as that requires knowledge of all BMI-generating rules at generate time. ๐ฌ
๐ Separate BMI Generation
CMake currently uses a single rule to generate both the BMI and the object file for a compilation. At least Clang supports compiling an object directly from the BMI. This would be beneficial because BMI generation is typically faster than compilation, and generating the BMI as a separate step allows importers to start compiling without waiting for the object to also be generated. Iconic behaviour, honestly. ๐ฅ
This is not supported in the current implementation, as only Clang supports generating an object directly from the BMI. Other compilers either do not support such a two-phase generation (GCC) or need to start object compilation from the source again.
Conflicts with Easier Source Specification on a single target, because CMake must know all BMI-generating sources at generate time rather than build time to create the two-phase rules. It's giving catch-22. ๐ญ
๐ซถ MODULE COMPILATION GLOSSARY (the vibes-to-vocab translation) ๐ซถ
Okay bestie, this is the part where we define all the words we've been throwing around. It's giving flashcards but make it cute. No cap, this is the stuff you actually need to know so you don't get lost in the group chat. โจ
๐ BMI
Built Module Interface, bb. It's a compiler-generated binary representation of a C++ module's interface that consumers of the module literally require to build. The file extensions vary by compiler, so don't @ me about the suffix.
๐ CMI
Compiled Module Interface. It's just an alternative name for BMI used by some compilers โ same energy, different label. Lowkey iconic that we have two names for one thing.
๐ธ build database
A JSON file containing compilation commands, module dependencies, and grouping information. Used for IDE integration and build analysis. Basically the receipts of your build, all organized and pretty. ๐ฅ
๐ฆ 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, Meson, build2, and more. It knows how the pieces fit, iykyk.
โญ build tool
A build graph execution tool. For example, ninja and make. Some build tools are also their own build system โ overachievers, honestly. ๐
๐ง C++ module
A C++20 language feature for describing the API of a piece of software. Intended as a replacement for headers for this purpose. We love a glow-up era. ๐
๐ collate
The process of aggregating module information from scanned sources to ensure correct compilation order and to provide metadata for other parts of the build (e.g., installation or a build database). It's the "let's get everyone on the same page" step, fr fr.
๐ discovered dependencies
Dependencies found during the processing of a command that do not need to be explicitly declared. They just show up. Surprise, bestie. ๐
๐ซ dynamic dependencies
Dependencies which require a separate command to detect so that a further command may have its dependencies satisfied. Extra step, extra drama, but necessary.
๐ฅ embarrassingly parallel
A set of tasks which, due to having minimal dependencies between them, can be easily divided into many independent tasks that can be executed concurrently. Not me being obsessed with how beautiful that is โ the whole squad can run at once. โจ
๐ explicit build
A build strategy where module dependencies are explicitly specified rather than discovered. You said what you said, and that's the whole point.
๐ fixed build
A build strategy where all module dependencies are computed and inserted directly into the build graph. Everything's locked in at generate time โ zero mystery, all main character energy. ๐
๐ง header unit
A header file which is used via an import statement rather than an #include preprocessor directive. Implementations may provide support for treating #include as import as well. Glow-up for headers, honestly.
๐ธ implementation unit
A C++ translation unit that implements module entities declared in a module interface unit. The behind-the-scenes icon doing the actual work. ๐
๐ฆ implicit build
A build strategy where module dependencies are discovered by searching for BMI files during compilation. It just finds them. No cap.
โญ internal partition unit
A translation unit which contains a partition name and is not exported from the primary module interface unit. It's giving backstage pass, not VIP list. ๐
๐ module interface unit
A translation unit that declares a module's public interface using export module. Such a unit may or may not be also be a partition unit. Either way, it's the face of the operation.
๐ module map
A compiler-specific file mapping module names to BMI locations. A little directory, basically. ๐บ๏ธ
๐ซ module visibility
CMake's enforcement of access rules for modules based on their declaration scope (PUBLIC/PRIVATE). Yes, there ARE rules, and CMake WILL hold you to them. ๐ญ
๐ ODR
One Definition Rule. The C++ requirement that any entity be defined exactly once per program. Break it and the whole thing falls apart โ that's not a vibe, that's a disaster. ๐
๐ง partition unit
A translation unit which describes a module with a partition name (i.e., module MODNAME:PARTITION;). The partition may or may not use the export keyword. If it does, it is also a module interface unit; otherwise, it is a internal partition unit.
๐ primary module interface unit
A module interface unit which exports a named module that is not a partition unit. The head honcho. The main character. ๐
๐ธ scan
The process of analyzing a translation unit to discover module imports and exports. Detective mode activated. ๐ต๏ธโโ๏ธ
๐ฆ static build
A build configuration where all compilation rules are determined at generate time. Everything's decided up front โ no last-minute plot twists.
โญ 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. It's giving "that's MY symbol, hands off." ๐
๐ synthetic target
A CMake-generated build target used to supply BMIs to a specific user of a module-providing target. A lil helper target, made to order. ๐ซถ
๐ 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 not use C++ modules may be combined into a single translation unit. Tiny but mighty. โจ
๐ Copyright ๐
2000-2026 Kitware, Inc. and Contributors
And that's the whole page, bestie!! ๐ You made it to the end โ I'm so proud of you, genuinely. Go forth and module responsibly. ๐
This lil masterpiece came from CMake source version 4.4.5, dated Oct 28, 2026, and it lives at CMAKE-CXXMODULES(7) forever and ever. ๐๐ฆ
๐ฆ where this page comes from
- arch rolling: cmake 4.4.5-1 ยท BSD-3-Clause
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms