fmt
Integrating fmt with CMake: compiled, header-only and module builds
Treat fmt deployment as a dependency and ABI decision. Pin the source, select one CMake target and distinguish a local header-only probe from a production build matrix.
What you will learn
- Deploy a dependency, not an invented service
- Make the build choice explicit
- Verify the artifact you will actually ship
Before you start
- Basic C++ values, references, strings and build targets
- Ability to distinguish a documented expectation from a measured result
Trace concrete fmt behavior and choose an integration with explicit output, lifetime and verification boundaries.
Key takeaways
- Choose one explicit CMake target and pin the dependency revision.
- A C++11 baseline does not imply all compile-time or module features are available.
- Header-only fixture results do not prove installed, shared-library or module deployments.
Deploy a dependency, not an invented service
The deployable unit is your application plus its chosen fmt integration. The official getting-started document offers a compiled target, fmt::fmt, a header-only target, fmt::fmt-header-only, and an optional module target. Compiled and module integration are recommended there for build-time reasons; the article does not attach an unmeasured speedup to your application.
For reproducibility, vendor or fetch an exact revision and record the toolchain and options used to consume it. The pinned source advertises a C++11 baseline for the ordinary CMake target, while the teaching fixture intentionally uses C++20 to exercise literal consteval checks. A library’s minimum supported language level does not imply that every optional feature works at that level.
Make the build choice explicit
The CMake fragment below assumes the pinned repository is already present at third_party/fmt. It disables documentation, upstream tests, installation targets and module integration for this small consumer example. The application target links fmt::fmt rather than manually collecting include directories and hoping the correct binary appears at link time. It is a reference integration, not a claim that this machine ran a complete CMake build.
Header-only integration propagates FMT_HEADER_ONLY through its target and includes implementation code from the headers. It avoids a separate fmt library artifact but can increase repeated compilation work. For native modules, the inspected CMake path depends on generator and compiler checks; its GNU native-module condition requires GCC 15, despite an earlier nearby comment mentioning GCC 14. Read the condition that executes, not only the comment.
Verify the artifact you will actually ship
BUILD_SHARED_LIBS affects the library form, and a static library embedded into another shared object may need position-independent code on relevant platforms. Keep compiler ABI, runtime linkage, build mode and dependency revision consistent across producer and consumer. A successful include check cannot detect every linker or deployed-library mismatch.
The bounded editorial fixture uses six source headers whose bytes were checked against the pinned Git tree. It does not replace testing your CMake generator, installed package, shared-library loader, C++ module path or target platform. Before release, run your own application tests in the same integration mode that will ship and record the resulting artifact identity.
Implementation steps
- 1
Place the verified revision under third_party/fmt.
- 2
Select the compiled, header-only or module integration deliberately.
- 3
Build and test the consumer with the intended compiler and runtime linkage.
- 4
Check the deployed artifact rather than relying on successful header discovery.
Copy-ready example
cmake_minimum_required(VERSION 3.28)
project(fmt_demo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(FMT_DOC OFF CACHE BOOL "" FORCE)
set(FMT_TEST OFF CACHE BOOL "" FORCE)
set(FMT_INSTALL OFF CACHE BOOL "" FORCE)
set(FMT_MODULE OFF CACHE BOOL "" FORCE)
add_subdirectory(third_party/fmt EXCLUDE_FROM_ALL)
add_executable(fmt_demo main.cc)
target_link_libraries(fmt_demo PRIVATE fmt::fmt)Frequently asked questions
Is the header-only target always the fastest way to build?
No. It is convenient, but repeated compilation is a separate cost. The upstream guide recommends compiled or module integration for improved build times; measure your own build.
Did the editorial check validate CMake modules and a production ABI?
No. Those require a separate compiler/generator and application deployment matrix; the bounded header-only fixture does not establish them.
Sources
- CMakeLists.txtSource checked 2026-09-08
- doc/get-started.mdSource checked 2026-09-08
- include/fmt/core.hSource checked 2026-09-08
- include/fmt/format.hSource checked 2026-09-08