How to Distribute C++ Dynamic Libraries with C++20 Modules using CMake
Introduction: The Shift from Headers to C++20 Modules
For decades, C++ developers have distributed dynamic libraries using the traditional dynamic library file (such as .so, .dll, or .dylib) alongside a set of header files (.h or .hpp). However, with C++20 modules, header files are no longer required to expose component interfaces. This shift introduces a practical question: What is the canonical way to package and distribute a C++ dynamic library when relying purely on modules?
Understanding BMIs vs. Module Source Files
To distribute module-based C++ libraries effectively, it is critical to distinguish between Binary Module Interfaces (BMIs) and Module Interface Source Units (files with extensions like .cppm, .ixx, or .cpp):
- BMIs (Binary Module Interfaces): These are compiled artifacts generated by the compiler during build time (e.g.,
.pcmfiles in Clang,.ifcin MSVC). BMIs are compiler-specific, version-dependent, and sensitive to build flags. Therefore, shipping pre-compiled BMIs is not supported or recommended. - Module Interface Sources: These contain the source code declaring
export module <name>;. The canonical approach is to distribute your dynamic library binary along with these module interface source files.
The Modern CMake Solution (CMake 3.28+)
Starting with version 3.28, CMake introduced official, first-class support for C++ modules export and distribution using the FILE_SET TYPE CXX_MODULES feature. This allows build systems to build the dynamic binary and install the module interface sources so downstream consumers can build the required BMIs automatically matching their compiler flags.
Example: Exporting a Library Target with Modules
Below is a standard CMakeLists.txt configuration for creating, installing, and exporting a C++ dynamic library using C++20 modules: