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 to OFF to 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.

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 compiles src/format.cc and links it statically or shared depending on the BUILD_SHARED_LIBS setting.
  • fmt::fmt-header-only – An INTERFACE library that adds include/ 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) – Defines add_library(fmt ...) and the installation logic. It checks for FMT_INSTALL and sets compiler flags.
  • include/fmt/format.h – The primary header exposing fmt::print, fmt::format, and the core API.
  • src/format.cc – The implementation file for the compiled library, compiled when using fmt::fmt.

Summary

  • Use add_subdirectory(path/to/fmt) to import the fmtlib/fmt build system into your CMake project.
  • Link fmt::fmt for compiled library usage or fmt::fmt-header-only for header-only integration.
  • Set variables like FMT_INSTALL and FMT_PEDANTIC before add_subdirectory() to control subproject behavior.
  • The root CMakeLists.txt handles all include directories and compiler flags automatically; no manual target_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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →