How to Build fmtlib as a C++ Module Library with FMT_MODULE
Enable the FMT_MODULE CMake option when configuring {fmt} to generate the fmt::fmt-module target, allowing downstream projects to import the library via import fmt; instead of including individual headers.
The {fmt} library (fmtlib/fmt) provides first-class support for C++20 modules, enabling faster compilation and cleaner dependency management compared to traditional header inclusion. By toggling the FMT_MODULE option during CMake configuration, you instruct the build system to compile the library as a named module rather than a static or shared library. This article explains the toolchain requirements, build configuration, and consumption patterns based on the current implementation in the main branch.
Prerequisites for C++20 Module Support
Before enabling FMT_MODULE, your build environment must satisfy strict toolchain requirements. In CMakeLists.txt (lines 53-73), the project detects whether the compiler and generator support the C++ module interface by checking the CMake version, generator type, and compiler capabilities:
- CMake ≥ 3.28
- C++ Standard ≥ 20 (
CMAKE_CXX_STANDARD=20) - Generators: Ninja ≥ 1.11 or Visual Studio ≥ 17.4
- Compilers: GCC ≥ 15, Clang ≥ 16, or MSVC ≥ 1934
If these conditions are met, the internal variable FMT_USE_CMAKE_MODULES evaluates to ON, making the module build available.
Enabling the FMT_MODULE CMake Option
The FMT_MODULE option is defined in CMakeLists.txt (lines 86-88) and defaults to the value of FMT_USE_CMAKE_MODULES. When set to ON, CMake creates the fmt::fmt-module target (around line 330) instead of the standard fmt::fmt target.
Configure the build with the following command:
git clone https://github.com/fmtlib/fmt.git
cd fmt
cmake -B build -S . \
-DCMAKE_CXX_STANDARD=20 \
-DFMT_MODULE=ON \
-G Ninja
cmake --build build
If your environment does not satisfy the module prerequisites, FMT_MODULE automatically falls back to OFF, and the build proceeds as a conventional static library.
Module Implementation in src/fmt.cc
The module interface is defined in src/fmt.cc. The file begins with a global module fragment followed by the export declaration (lines 1-12 and 108-110):
module;
#include <__config> // Global module fragment for includes
export module fmt;
// All public headers are included here to expose their symbols
#include <fmt/format.h>
#include <fmt/ostream.h>
// ... additional headers
This structure ensures that all library symbols—including fmt::print, fmt::format, and formatting utilities—are encapsulated within the fmt named module. The export module fmt statement marks this translation unit as the primary interface for consumers.
Consuming the fmt Module in Your Project
After building {fmt} with FMT_MODULE enabled, consuming projects link against fmt::fmt-module and import the module in their source code.
CMake Configuration
In your project's CMakeLists.txt, locate the package and link to the module target:
cmake_minimum_required(VERSION 3.28)
project(my_app LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
find_package(fmt REQUIRED)
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE fmt::fmt-module)
C++ Source Code
Replace traditional #include directives with the import fmt statement:
import fmt;
int main() {
fmt::print("The answer is {}.\n", 42);
return 0;
}
Using import fmt; eliminates the need for #include <fmt/format.h> or other header files, enabling the compiler to perform cross-translation-unit optimizations and reducing compile times in large projects.
Summary
- FMT_MODULE is a CMake option in
fmtlib/fmtthat enables C++20 module compilation when toggledON. - The option requires CMake ≥ 3.28, C++20, and recent versions of GCC, Clang, or MSVC with module-capable generators like Ninja 1.11+.
- The module interface is implemented in
src/fmt.ccusingexport module fmt;and includes all public headers within the module purview. - Consumers link against fmt::fmt-module and use
import fmt;to access the API without header includes. - If toolchain requirements are unmet, the build automatically falls back to the traditional
fmt::fmtstatic library target.
Frequently Asked Questions
What happens if I enable FMT_MODULE with an unsupported compiler?
CMake will detect the incompatibility in CMakeLists.txt (lines 53-73) and silently disable FMT_MODULE, falling back to building {fmt} as a standard static or shared library (fmt::fmt). Your build will succeed, but you will not be able to use import fmt; and must instead include the header files traditionally.
Can I mix header includes and module imports in the same project?
No, mixing import fmt with #include <fmt/format.h> in the same project can lead to One Definition Rule (ODR) violations and conflicting symbol definitions. The test/module-test.cc file in the repository demonstrates proper isolated usage of the module target. Choose one approach per translation unit and consistently link against either fmt::fmt-module or fmt::fmt.
Does using the module target change the fmt API?
No, the API remains identical. Whether you include headers or import the module, functions like fmt::format, fmt::print, and custom formatting extensions work exactly the same. The only difference is the import mechanism: import fmt; replaces the need for multiple #include directives and provides better build performance through the module interface.
How do I verify that the module is being built correctly?
Check that CMake reports FMT_MODULE: ON during configuration, and verify that the build generates a module interface file (.gcm for GCC, .pcm for Clang, or .ifc for MSVC). You can also inspect the generated build files to confirm that src/fmt.cc is being compiled with the module keyword rather than as a standard source file, and that your executable links against the fmt::fmt-module target rather than fmt::fmt.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →