This issue is to track the proposed way to specify sources for C++ modules. Header units will be mentioned, but probably have some other corner cases to work out that will need better implementations to see where any differences are that may affect CMake APIs. Hopefully they end up being orthogonal to named module APIs :fingers_crossed: . Search for Q:
to find questions that still need answered (my gut feelings are at the end as parentheticals).
Background
C++ module TUs
There are 4 types of module TUs in the C++ standard (MSVC adds another kind as well):
-
Module Interface Unit: contains
export module X;
-
Module Partition Unit (exported): contains
export module X:part;
; must beexport
ed from the main module interface (IFNDR otherwise) -
Module Partition Unit: contains
module X:part;
; must not beexport
ed from the main module interface (IFNDR otherwise) -
Module Implementation Unit: contains
module X;
and implements the interface of the module
MSVC’s additional TU type is:
-
Module Partition Implementation Unit: contains
module X:part;
but implements the interface of the partition. This is not supported in GCC.
Because the only difference is based on file contents, CMake would need to first scan a source file to classify it and generate a flag for use by the scanning and compilation in MSVC’s model. However, with there being no syntactic difference between the non-export
ed Module Partition Unit TU types.
MSVC requirements
See this post. First, MSVC requires the -interface
flag for any TU with an export module
(partition or otherwise). Additionally, a Module Partition Unit requires the -internalPartition
flag if it is not an implementation unit. Implementation units do not have any associated flags with them. Due to this, CMake must also know the classification of the modules.
Even though this is an MSVC-ism, it is not inconceivable that CMake may error on at least interface units not being classified properly. As the other compilers do not support the fifth kind (yet?), a warning may be emitted if such things are detected (they go against “only one TU may have a given name” rule if they’re not known to be implementation units).
Source listing
This builds on top of CMake 3.23’s FILE_SET
. That is, in order to specify C++20 modules, one must use FILE_SETS
to list the sources. This is to facilitate the extra information that MSVC needs to know about TUs before scanning.
The proposal is:
target_sources(target_with_cxx_modules
PUBLIC
FILE_SET public_modules TYPE CXX_MODULES FILES
m.cpp m_part_exported.cpp
FILE_SET public_module_partitions TYPE CXX_MODULE_IMPLEMENTATION_PARTITIONS FILES
m_part_not_exported.cpp
FILE_SET public_header_units TYPE CXX_MODULE_HEADERS FILES
importable_header.h
PRIVATE
# no fileset required for implementations
m_impl.cpp m_part_exported_impl.cpp
m_part_not_exported_impl.cpp # MSVC only
FILE_SET private_modules TYPE CXX_MODULES FILES
p.cpp p_part_exported.cpp
FILE_SET private_header_units TYPE CXX_MODULE_HEADERS FILES
p_importable_header.h)
Note that if any private sources end up being visible from a public module (this is essentially “private modules cannot be transitively imported from a public module”), that is an error because such files have been indicated to not be installed. This is intended to help projects avoid shipping module code that need not be visible. Modules provided by private sources will also not be available to other targets in the project (that is, the collator will not communicate their existence to dependent libraries).
It is unclear to me what INTERFACE
module units (named or headers) actually mean. We cannot scan them as part of this target because they are not this target’s modules. Because we do not scan them, we do not know how to specify them in IMPORTED_CXX_MODULE*
properties (see below) later.
Q: Do we just punt and say that INTERFACE
is not a valid visibility for CXX_MODULE*
fileset types (my gut says to make this an error)?
Building
See this paper for the overall strategy. Implemented on my fork for MSVC 2022 and a patched GCC.
Not much needs to change here (besides the extra information the collator will need to handle and organize).
Target properties
CMake can put all of this together for non-IMPORTED
targets. However, there will need to be a way for IMPORTED
targets to provide this information. The proposed properties are:
set_property(TARGET Imported::Target APPEND PROPERTY
IMPORTED_CXX_MODULES
"name_of_module=${_IMPORT_PREFIX}/path/to/module/interface.cpp:${_IMPORT_PREFIX}/path/to/precompiled/module.bmi"
"name_of_module:partition=${_IMPORT_PREFIX}/path/to/module/partition/interface.cpp:${_IMPORT_PREFIX}/path/to/precompiled/module-partition.bmi")
set_property(TARGET Imported::Target APPEND PROPERTY
IMPORTED_CXX_MODULE_HEADERS
"${_IMPORT_PREFIX}/path/to/importable/header/unit.h:${_IMPORT_PREFIX}/path/to/precompiled/header/unit/module.bmi")
This will need to be written out at build time because this information is not known during the configure stage (namely the actual name of modules). The paths they will be at will be known, so we can statically generate include(OPTIONAL)
calls (I feel like OPTIONAL
is required because the of things like EXCLUDE_FROM_ALL
). It will be the job of the collator for each target to write out this information for each export (build and install). For installation exports, the DESTINATION
for the file sets will need to be passed to the collator so that it will know where they will exist.
Installation
And speaking of DESTINATION
bits, module interface cmake_install.cmake
code can be known ahead of time because the only sets of files eligible for installation are those that are PUBLIC
(or INTERFACE
if that is allowed). BMI installation will require the collator to write out locations. Since MSVC requires them to be some kind of BMI-generating TU, they will generally be needed in the importer (transitively). Therefore the collator can error if a module unit does not provide something.
install(TARGETS target_with_cxx_modules
# Q: Should this also install `CXX_MODULE_INTERNAL_PARTITIONS` or should it have its own scope (the type still matters on installation, but does the destination/component ever need to be distinct)?
CXX_MODULES DESTINATION somewhere COMPONENT cxx_module_interfaces
# Q: Should we re-use `HEADERS` or make a new `CXX_MODULE_HEADERS` scope (they're still headers after all)?
HEADERS DESTINATION over/the COMPONENT headers
# Q: Is there a better name for this?
CXX_MODULE_BMIS DESTINATION rainbow COMPONENT cxx_module_bmis)