FetchContent Module Overview
The FetchContent module (CMake 3.11+) allows you to declare external dependencies that CMake will download and incorporate at configure time. Unlike find_package() which requires dependencies to be pre-installed, FetchContent makes your project self-contained — anyone can clone and build without manually installing dependencies first.
cmake -B build). The downloaded sources become part of your build tree, compiled alongside your own code with the same compiler and settings.
FetchContent_Declare and FetchContent_MakeAvailable
The two essential commands form a declare-then-populate pattern:
# CMakeLists.txt — Basic FetchContent pattern
cmake_minimum_required(VERSION 3.16)
project(FetchDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
# 1. Include the module
include(FetchContent)
# 2. Declare what to fetch (does NOT download yet)
FetchContent_Declare(
json # Logical name (lowercase)
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3 # Pin to specific release
)
# 3. Make it available (downloads if needed, then add_subdirectory)
FetchContent_MakeAvailable(json)
# 4. Use it — nlohmann_json provides the target
add_executable(app main.cpp)
target_link_libraries(app PRIVATE nlohmann_json::nlohmann_json)
flowchart TD
A["include(FetchContent)"] --> B["FetchContent_Declare(dep
GIT_REPOSITORY ... GIT_TAG ...)"]
B --> C["FetchContent_MakeAvailable(dep)"]
C --> D{Already
populated?}
D -->|Yes| E["Skip download
Use cached source"]
D -->|No| F["Download from
GIT_REPOSITORY/URL"]
F --> G["add_subdirectory(
source_dir, binary_dir)"]
E --> G
G --> H["Dependency targets
now available"]
H --> I["target_link_libraries(
app PRIVATE dep::dep)"]
The separation between FetchContent_Declare and FetchContent_MakeAvailable is intentional — it allows parent projects to override declarations before population occurs.
Fetching from Git
GIT_REPOSITORY and GIT_TAG
Git is the most common source for FetchContent. Key parameters:
# CMakeLists.txt — Git fetch with all common options
cmake_minimum_required(VERSION 3.16)
project(GitFetch LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.13.0 # Tag (human-readable)
GIT_SHALLOW TRUE # Don't fetch full history
GIT_PROGRESS TRUE # Show download progress
)
FetchContent_MakeAvailable(spdlog)
add_executable(logger main.cpp)
target_link_libraries(logger PRIVATE spdlog::spdlog)
Pinning to Commits vs Tags
You can pin to tags, branches, or exact commit hashes. Each has trade-offs:
# CMakeLists.txt — Different pinning strategies
cmake_minimum_required(VERSION 3.16)
project(PinDemo LANGUAGES CXX)
include(FetchContent)
# Option 1: Tag (readable, but mutable if author force-pushes)
FetchContent_Declare(fmt_tag
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
)
# Option 2: Commit SHA (immutable, reproducible, but unreadable)
FetchContent_Declare(fmt_commit
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 0c9fce2ffefecfdce794e1859584e25f9110670e # v10.2.1
)
# Option 3: Branch (NEVER do this for reproducible builds)
# FetchContent_Declare(fmt_branch
# GIT_REPOSITORY https://github.com/fmtlib/fmt.git
# GIT_TAG master # BAD: changes constantly
# )
main, master, develop) in production projects. Your build will fetch different code on different days, breaking reproducibility. Always use tags or commit SHAs.
Fetching Archives
URL and URL_HASH
For projects that publish release tarballs (faster than git clone), use URL-based fetching:
# CMakeLists.txt — Fetching from archive URL
cmake_minimum_required(VERSION 3.16)
project(ArchiveFetch LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
# Download a release tarball (faster than git clone)
FetchContent_Declare(
catch2
URL https://github.com/catchorg/Catch2/archive/refs/tags/v3.5.2.tar.gz
URL_HASH SHA256=269543a0f4a5cd7f4b6013d5f03ea4e14c93a6e12a03146cd7445e0f30ed3f40
)
FetchContent_MakeAvailable(catch2)
# Catch2 provides Catch2::Catch2WithMain target
add_executable(tests test_main.cpp)
target_link_libraries(tests PRIVATE Catch2::Catch2WithMain)
URL_HASH with archive downloads. This ensures the downloaded file hasn't been tampered with and provides a deterministic build. Without it, a compromised CDN could inject malicious code.
FetchContent vs find_package
When to Use Each
Both find_package() and FetchContent solve dependency management, but they serve different needs:
| Criteria | find_package() | FetchContent |
|---|---|---|
| Dependency installed? | Required beforehand | Downloaded automatically |
| Build integration | Uses pre-built binaries | Compiled from source |
| Configure speed | Fast (already built) | Slower (downloads + compiles) |
| Reproducibility | Depends on system | Fully reproducible |
| Best for | System libs, large frameworks | Small-medium libs, testing |
Dependency Provider Pattern (CMake 3.24+)
CMake 3.24 introduced dependency providers — a mechanism that lets find_package() transparently fall back to FetchContent when a system package isn't found:
flowchart TD
A["find_package(fmt REQUIRED)"] --> B["Dependency Provider
intercepts call"]
B --> C{System package
available?}
C -->|Yes| D["Use system fmt
(pre-installed)"]
C -->|No| E["FetchContent_Declare
+ MakeAvailable"]
E --> F["Download & build
fmt from source"]
D --> G["fmt::fmt target
available"]
F --> G
# cmake/dependencies.cmake — Dependency provider setup (CMake 3.24+)
cmake_minimum_required(VERSION 3.24)
include(FetchContent)
# This macro is called by CMake for every find_package() call
macro(myproject_provide_dependency method package_name)
if("${package_name}" STREQUAL "fmt")
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
FIND_PACKAGE_ARGS 10.0 # Version for find_package fallback
)
FetchContent_MakeAvailable(fmt)
endif()
endmacro()
cmake_language(
SET_DEPENDENCY_PROVIDER myproject_provide_dependency
SUPPORTED_METHODS FIND_PACKAGE FETCHCONTENT_MAKEAVAILABLE_SERIAL
)
# CMakeLists.txt — Using FIND_PACKAGE_ARGS (simpler approach, CMake 3.24+)
cmake_minimum_required(VERSION 3.24)
project(ProviderDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
# Declare with FIND_PACKAGE_ARGS — try find_package first, fetch if missing
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
FIND_PACKAGE_ARGS 10.0 CONFIG # Args passed to find_package()
)
# This tries find_package(fmt 10.0 CONFIG) first
# If not found, falls back to fetching from Git
FetchContent_MakeAvailable(fmt)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE fmt::fmt)
FETCHCONTENT_BASE_DIR
By default, FetchContent downloads everything into the build directory. You can change this to share downloads across multiple build configurations or projects:
# Share downloads across Debug and Release builds
cmake -B build-debug -DCMAKE_BUILD_TYPE=Debug \
-DFETCHCONTENT_BASE_DIR=/tmp/cmake-deps -S .
cmake -B build-release -DCMAKE_BUILD_TYPE=Release \
-DFETCHCONTENT_BASE_DIR=/tmp/cmake-deps -S .
# Both builds share the same downloaded sources (compiled separately)
# CMakeLists.txt — Setting base dir programmatically
cmake_minimum_required(VERSION 3.16)
project(BaseDirDemo LANGUAGES CXX)
# Cache downloads outside build tree for persistence
set(FETCHCONTENT_BASE_DIR "${CMAKE_SOURCE_DIR}/_deps"
CACHE PATH "FetchContent download directory")
include(FetchContent)
FetchContent_Declare(json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
)
FetchContent_MakeAvailable(json)
Dependency Options Override
Setting Options Before MakeAvailable
Many libraries expose CMake options (BUILD_TESTING, BUILD_SHARED_LIBS, etc.). You can override these before calling FetchContent_MakeAvailable:
# CMakeLists.txt — Overriding dependency options
cmake_minimum_required(VERSION 3.16)
project(OptionsDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
# Disable spdlog's tests and examples to speed up build
set(SPDLOG_BUILD_EXAMPLE OFF CACHE BOOL "" FORCE)
set(SPDLOG_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(SPDLOG_BUILD_BENCH OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.13.0
)
FetchContent_MakeAvailable(spdlog)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE spdlog::spdlog)
Silencing Compiler Warnings from Third-Party Code
When fetching libraries, their code may trigger compiler warnings with your strict flags. Use SYSTEM to suppress them.
# CMakeLists.txt — Marking fetched deps as SYSTEM (CMake 3.25+)
cmake_minimum_required(VERSION 3.25)
project(SystemDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
# Mark all FetchContent deps as SYSTEM to suppress their warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror")
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
SYSTEM # Treat includes as system headers (CMake 3.25+)
)
FetchContent_MakeAvailable(json)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE nlohmann_json::nlohmann_json)
OVERRIDE_FIND_PACKAGE
The OVERRIDE_FIND_PACKAGE option (CMake 3.24+) makes subsequent find_package() calls for the same package use the fetched version instead of searching the system:
# CMakeLists.txt — OVERRIDE_FIND_PACKAGE pattern
cmake_minimum_required(VERSION 3.24)
project(OverrideDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
# Declare with OVERRIDE_FIND_PACKAGE
FetchContent_Declare(
GTest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
OVERRIDE_FIND_PACKAGE # Any find_package(GTest) now uses this
)
# Later code (or subdirectories) that call find_package(GTest)
# will automatically get the fetched version
find_package(GTest REQUIRED)
add_executable(tests test_main.cpp)
target_link_libraries(tests PRIVATE GTest::gtest_main)
Multiple Dependencies
Projects typically have multiple dependencies. Declare them all, then populate them together:
# CMakeLists.txt — Multiple dependencies
cmake_minimum_required(VERSION 3.16)
project(MultiDep LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
# Declare all dependencies first
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
GIT_SHALLOW TRUE
)
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.13.0
GIT_SHALLOW TRUE
)
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
GIT_SHALLOW TRUE
)
# spdlog depends on fmt — set this before making available
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE)
# Populate all at once (order matters if there are interdependencies)
FetchContent_MakeAvailable(fmt spdlog json)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE
fmt::fmt
spdlog::spdlog
nlohmann_json::nlohmann_json
)
fmt must be listed before spdlog in FetchContent_MakeAvailable because spdlog can optionally use an external fmt.
FetchContent with GoogleTest
GoogleTest is the most common FetchContent use case. Here's a complete, working example:
Building and Running Tests with Fetched GTest
Set up a project that fetches GoogleTest, writes tests, and runs them with CTest — all from a clean checkout.
# CMakeLists.txt — Complete GoogleTest with FetchContent
cmake_minimum_required(VERSION 3.16)
project(MyProject LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
# === Production code ===
add_library(mathlib src/math.cpp)
target_include_directories(mathlib PUBLIC include)
add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE mathlib)
# === Testing ===
enable_testing()
include(FetchContent)
# Prevent GTest from installing alongside our project
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(googletest)
# Include GoogleTest module for gtest_discover_tests
include(GoogleTest)
add_executable(math_tests tests/test_math.cpp)
target_link_libraries(math_tests PRIVATE
mathlib
GTest::gtest_main
)
# Automatically discover and register test cases with CTest
gtest_discover_tests(math_tests)
// include/math.h
#pragma once
int add(int a, int b);
int factorial(int n);
// src/math.cpp
#include "math.h"
#include <stdexcept>
int add(int a, int b) { return a + b; }
int factorial(int n) {
if (n < 0) throw std::invalid_argument("negative input");
if (n == 0) return 1;
return n * factorial(n - 1);
}
// tests/test_math.cpp
#include <gtest/gtest.h>
#include "math.h"
TEST(AddTest, PositiveNumbers) {
EXPECT_EQ(add(2, 3), 5);
EXPECT_EQ(add(0, 0), 0);
}
TEST(AddTest, NegativeNumbers) {
EXPECT_EQ(add(-1, -1), -2);
EXPECT_EQ(add(-1, 1), 0);
}
TEST(FactorialTest, BaseCase) {
EXPECT_EQ(factorial(0), 1);
EXPECT_EQ(factorial(1), 1);
}
TEST(FactorialTest, NormalCases) {
EXPECT_EQ(factorial(5), 120);
EXPECT_EQ(factorial(10), 3628800);
}
TEST(FactorialTest, NegativeThrows) {
EXPECT_THROW(factorial(-1), std::invalid_argument);
}
# Build and run tests
cmake -B build -S .
cmake --build build
cd build && ctest --output-on-failure
FetchContent with fmt and spdlog
A common real-world combination: fmt (formatting) and spdlog (logging, built on fmt). This demonstrates handling inter-dependency between fetched libraries:
Header-Only and Compiled Dependencies Together
Fetch fmt as a compiled library and configure spdlog to use the external fmt instead of its bundled copy.
# CMakeLists.txt — fmt + spdlog with external fmt
cmake_minimum_required(VERSION 3.16)
project(LoggingDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
# === Fetch fmt first (spdlog depends on it) ===
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
GIT_SHALLOW TRUE
)
# === Fetch spdlog, configured to use external fmt ===
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "Use external fmt" FORCE)
set(SPDLOG_BUILD_EXAMPLE OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.13.0
GIT_SHALLOW TRUE
)
# Order: fmt before spdlog (dependency ordering)
FetchContent_MakeAvailable(fmt spdlog)
# === Application ===
add_executable(app main.cpp)
target_link_libraries(app PRIVATE spdlog::spdlog fmt::fmt)
// main.cpp — Using fmt and spdlog together
#include <fmt/core.h>
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <chrono>
int main() {
// Direct fmt usage
std::string greeting = fmt::format("Hello, {}!", "CMake");
fmt::print("{}\n", greeting);
// spdlog console logging
spdlog::info("Application started at {}",
std::chrono::system_clock::now());
// spdlog file logging
auto file_logger = spdlog::basic_logger_mt("file", "app.log");
file_logger->info("Logging to file works!");
spdlog::warn("This is a warning with value: {}", 42);
return 0;
}
# Build and run
cmake -B build -S .
cmake --build build --parallel
./build/app
Best Practices and Pitfalls
FetchContent is powerful but has sharp edges. Follow these guidelines for maintainable builds:
- Always pin versions — Use tags or commit SHAs, never branch names
- Use GIT_SHALLOW TRUE — Dramatically faster clones for large repositories
- Disable unnecessary targets — Set BUILD_TESTING, BUILD_EXAMPLES to OFF
- Use URL_HASH for archives — Integrity verification against supply chain attacks
- Prefer FIND_PACKAGE_ARGS (3.24+) — Try system packages first, fetch as fallback
- Use SYSTEM (3.25+) — Suppress warnings from third-party code
# CMakeLists.txt — Offline/reproducible build support
cmake_minimum_required(VERSION 3.16)
project(OfflineDemo LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
include(FetchContent)
# FETCHCONTENT_FULLY_DISCONNECTED=ON skips all downloads
# (requires sources already populated in FETCHCONTENT_BASE_DIR)
# Useful for CI with pre-cached deps or air-gapped environments
# FETCHCONTENT_UPDATES_DISCONNECTED=ON skips update checks
# (uses previously downloaded sources without checking for updates)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(fmt)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE fmt::fmt)
# First build: downloads dependencies
cmake -B build -S .
cmake --build build
# Subsequent builds: skip network access
cmake -B build -DFETCHCONTENT_UPDATES_DISCONNECTED=ON -S .
# Fully offline (CI with pre-populated cache)
cmake -B build \
-DFETCHCONTENT_FULLY_DISCONNECTED=ON \
-DFETCHCONTENT_BASE_DIR=/ci-cache/cmake-deps \
-S .
- Name collisions — If two fetched libraries use the same target name internally, you'll get conflicts. Use
FetchContent_GetPropertiesto check before declaring. - Slow CI builds — Every CI run re-downloads dependencies. Use
FETCHCONTENT_BASE_DIRwith a persistent cache directory. - ABI incompatibility — Fetched libraries are compiled with your project's flags. If you change
CMAKE_CXX_STANDARDorCMAKE_POSITION_INDEPENDENT_CODE, all deps recompile. - Install contamination — Fetched deps may install their own targets/headers alongside yours. Set
INSTALL_*=OFFoptions or useEXCLUDE_FROM_ALL. - Version conflicts — If dep A and dep B both fetch different versions of dep C, the first one declared wins. Plan your dependency tree carefully.
# CMakeLists.txt — Guarding against double-population
cmake_minimum_required(VERSION 3.16)
project(GuardDemo LANGUAGES CXX)
include(FetchContent)
# Check if already declared/populated (useful in multi-project setups)
FetchContent_GetProperties(fmt)
if(NOT fmt_POPULATED)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
)
FetchContent_MakeAvailable(fmt)
endif()
add_executable(app main.cpp)
target_link_libraries(app PRIVATE fmt::fmt)
Conclusion & Next Steps
FetchContent transforms CMake dependency management from "install everything manually" to "clone and build." The key takeaways:
- FetchContent_Declare + FetchContent_MakeAvailable is the core two-step pattern
- Pin to tags or commit SHAs for reproducible builds — never branch names
- Use GIT_SHALLOW and URL_HASH for performance and security
- Set dependency options before MakeAvailable to control what gets built
- FIND_PACKAGE_ARGS (3.24+) enables graceful system-or-fetch fallback
- FETCHCONTENT_FULLY_DISCONNECTED enables offline and cached CI builds
- Watch for name collisions and version conflicts in complex dependency trees