How to Integrate fmtlib/fmt as a CMake Subproject Using add_subdirectory
Use add_subdirectory() to embed the fmt repository into your CMake build graph, then link your targets against either fmt::fmt for the compiled library or fmt::fmt-header-only for header-only usage.
The {fmt} library provides fast, type-safe string formatting for C++17 and newer projects. Integrating fmtlib/fmt as a CMake subproject using add_subdirectory() vendors the source code inside your repository, guaranteeing reproducible builds while automatically inheriting the library's compile options and SIMD settings through the root CMakeLists.txt.
Why Use add_subdirectory() for fmtlib?
Embedding fmt via add_subdirectory() treats the library as part of your build tree rather than an external dependency. According to the fmtlib/fmt source code, the root CMakeLists.txt defines the add_library(fmt ...) and add_library(fmt-header-only INTERFACE) targets, which propagate include directories and compiler flags automatically. This method eliminates the need to manually copy headers or maintain separate installation steps, keeping the include/fmt/format.h API surface available immediately.
Step-by-Step Integration Guide
1. Add the fmt Source Tree
Clone or copy the fmtlib/fmt repository into a subdirectory of your project, typically third_party/fmt or external/fmt. This directory contains the top-level CMakeLists.txt that defines the build rules.
2. Configure Build Options (Optional)
Set CMake cache variables before the add_subdirectory() call to customize fmt's behavior. Key options defined in the root CMakeLists.txt include:
FMT_INSTALL– Set toOFFto prevent fmt from appearing in your project's install set (recommended for embedded usage).FMT_PEDANTIC– Enables extra compiler warnings for stricter code analysis.FMT_WERROR– Treats warnings as errors during compilation.FMT_USE_CPP17– Enables C++17-specific features in the library.
3. Import the Subproject and Link Targets
Call add_subdirectory() with the path to fmt, then link the exported targets to your executables or libraries. The target fmt::fmt links the compiled library (from src/format.cc), while fmt::fmt-header-only provides an interface target for header-only usage.
Understanding the CMake Targets
The fmtlib/fmt CMakeLists.txt defines two primary targets under the fmt:: namespace:
fmt::fmt– The compiled library target. Building this target compilessrc/format.ccand links it statically or shared depending on theBUILD_SHARED_LIBSsetting.fmt::fmt-header-only– AnINTERFACElibrary that addsinclude/to your include directories. Use this when you prefer to include<fmt/format.h>directly without linking a separate binary.
Both targets automatically handle transitive dependencies and compiler feature requirements.
Complete Working Example
Assume the following directory structure:
my_project/
├── CMakeLists.txt
├── src/
│ └── main.cpp
└── third_party/
└── fmt/
├── CMakeLists.txt
├── include/
└── src/
Your top-level CMakeLists.txt should contain:
cmake_minimum_required(VERSION 3.14)
project(MyApp LANGUAGES CXX)
# Configure fmt options before importing
set(FMT_INSTALL OFF)
set(FMT_PEDANTIC ON CACHE BOOL "" FORCE)
# Integrate fmt as a subproject
add_subdirectory(third_party/fmt)
# Define your executable
add_executable(my_app src/main.cpp)
# Link against the compiled fmt library
target_link_libraries(my_app PRIVATE fmt::fmt)
# For header-only mode, use instead:
# target_link_libraries(my_app PRIVATE fmt::fmt-header-only)
Create src/main.cpp with the following content:
#include <fmt/format.h>
int main() {
fmt::print("Hello, {}!\n", "CMake");
return 0;
}
Build the project:
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build .
The build system first compiles the fmt library (if you selected fmt::fmt), then builds your application with the correct include paths inherited from the subproject.
Key Files in the fmt Repository
Understanding these files helps debug integration issues:
CMakeLists.txt(repo root) – Definesadd_library(fmt ...)and the installation logic. It checks forFMT_INSTALLand sets compiler flags.include/fmt/format.h– The primary header exposingfmt::print,fmt::format, and the core API.src/format.cc– The implementation file for the compiled library, compiled when usingfmt::fmt.
Summary
- Use
add_subdirectory(path/to/fmt)to import thefmtlib/fmtbuild system into your CMake project. - Link
fmt::fmtfor compiled library usage orfmt::fmt-header-onlyfor header-only integration. - Set variables like
FMT_INSTALLandFMT_PEDANTICbeforeadd_subdirectory()to control subproject behavior. - The root
CMakeLists.txthandles all include directories and compiler flags automatically; no manualtarget_include_directories()is required.
Frequently Asked Questions
What is the difference between fmt::fmt and fmt::fmt-header-only?
fmt::fmt is a compiled library target that builds src/format.cc into a static or shared library, offering the fastest compile times for translation units that include fmt headers. fmt::fmt-header-only is an INTERFACE library that adds include/ to your include path, causing the implementation to be compiled into every translation unit that uses it. Choose fmt::fmt for faster builds and fmt::fmt-header-only for simplicity in single-file projects.
How do I disable fmt library installation when used as a subproject?
Set set(FMT_INSTALL OFF CACHE BOOL "" FORCE) before calling add_subdirectory(third_party/fmt). This prevents the subproject from adding install rules to your project's export set, keeping your installation clean and focused on your own artifacts.
Can I use fmt header-only mode without compiling the library?
Yes. Link against fmt::fmt-header-only instead of fmt::fmt. This target is defined as an INTERFACE library in the root CMakeLists.txt and exposes the headers under include/fmt/ without compiling src/format.cc. Note that this increases compile times for targets that include <fmt/format.h>.
Where are the fmt public headers located in the repository?
The public API resides in include/fmt/, specifically include/fmt/format.h for the core formatting functions and include/fmt/core.h for lightweight includes. When using add_subdirectory(), these paths are automatically added to your target's include directories via the fmt::fmt or fmt::fmt-header-only interface properties.
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 →