Choosing Between FetchContent and ExternalProject in CMake: A Decision Guide
Decide between CMake's FetchContent and ExternalProject for dependency management. Compare build trees, isolation, speed, and reproducibility. Includes a practical CMakeLists.txt example and validation steps.
25 Aug 2025, 18:57 UTC

Decision Problem
When a CMake‑based project needs external libraries, the two most common mechanisms are FetchContent and ExternalProject. Both allow a project to download, build, and link dependencies, but they differ in isolation, build time, and reproducibility. The decision boils down to:
- Do you want the dependency to live in the same build tree as your project?
- Is the dependency simple enough to be built in‑place, or does it have its own complex build system?
- How important is incremental build speed versus reproducible, isolated builds?
Constraints
- FetchContent requires CMake ≥ 3.11 and network access unless the source is already cached.
- ExternalProject has been available since early CMake releases but may need the
ExternalProjectmodule and network access. - Both mechanisms rely on the availability of a Git or HTTP client during configuration.
- Version pinning is essential for reproducibility; floating tags can break builds.
Comparison Table
| Option | Build Tree | Isolation | Build Time | Reproducibility | Typical Use Cases |
|---|---|---|---|---|---|
| FetchContent | Same (in‑place) | Low | Fast | Depends on source commit | Small libraries, header‑only libs, single‑project deps |
| ExternalProject | Separate | High | Slower | High (clean separate tree) | Complex libs, multi‑step builds, external CI pipelines |
Trade‑Offs
- FetchContent:
- Pros: Faster incremental builds, easier to integrate, fewer directories.
- Cons: Ties dependency into main tree, harder to clean, version pinning can be problematic if the same source is used elsewhere.
- ExternalProject:
- Pros: Clear separation, better caching, easier to rebuild independently.
- Cons: Extra build directories, slower initial builds, more complex to link into targets.
Concrete Implementation
Below is a CMakeLists.txt that demonstrates both mechanisms: fmt is fetched with FetchContent, and zlib is brought in via ExternalProject. The final executable app links against both.
cmake_minimum_required(VERSION 3.15)
project(DependencyDemo CXX)
# ------------------------------------------------------------------
# 1. FetchContent: fmt (header‑only, lightweight)
# ------------------------------------------------------------------
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 9.1.0
)
FetchContent_MakeAvailable(fmt)
# ------------------------------------------------------------------
# 2. ExternalProject: zlib (has its own CMakeLists.txt)
# ------------------------------------------------------------------
include(ExternalProject)
ExternalProject_Add(
zlib_proj
PREFIX ${CMAKE_BINARY_DIR}/zlib
GIT_REPOSITORY https://github.com/madler/zlib.git
GIT_TAG v1.3.1
CONFIGURE_COMMAND "cmake -S . -B build -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/zlib/install"
BUILD_COMMAND "cmake --build build"
INSTALL_COMMAND "cmake --build build --target install"
BUILD_IN_SOURCE 0
)
# ------------------------------------------------------------------
# 3. Target that uses both dependencies
# ------------------------------------------------------------------
add_executable(app src/main.cpp)
# Link fmt (in‑place) directly
target_link_libraries(app PRIVATE fmt::fmt)
# Link zlib through the ExternalProject install directory
add_dependencies(app zlib_proj)
target_include_directories(app PRIVATE ${CMAKE_BINARY_DIR}/zlib/install/include)
target_link_libraries(app PRIVATE ${CMAKE_BINARY_DIR}/zlib/install/lib/libz.a)
Replace src/main.cpp with your own source. The ExternalProject build runs before app is built, ensuring the zlib library is available.
Validation Steps
- Configure:
cmake -S . -B buildLook for messages such as
Fetching fmt from GitHubandConfiguring zlib in ExternalProject. - Check build directories:
ls build/fmt # Should contain fmt source and build files ls build/zlib # Should contain zlib source and build files - Build target:
cmake --build build --target app - Inspect binary links (Linux example):
ldd build/appVerify that
libfmt.soandlibz.so(or static equivalents) appear in the list. - Optional: Build the dependency independently:
cmake --build build --target fmt cmake --build build --target zlib_proj
Practical Tips
- Always pin to a specific commit or tag; avoid
masterormainbranches. - For reproducible builds, add
--no-check-certificateif your environment has SSL issues. - Use
ExternalProject_Add_StepTargetsto expose custom targets for debugging. - Clean external artifacts explicitly:
cmake --build build --target clean_zlib_projif you add a custom clean step.
When to Choose Which?
Use FetchContent when:
- The dependency is small, header‑only, or can be built with a simple
add_subdirectorystyle interface. - You want fast incremental builds and tighter integration.
- Your project targets a modern CMake (≥ 3.11).
Use ExternalProject when:
- The dependency has its own complex build system or requires separate toolchains.
- You need a clean separation for CI pipelines or packaging.
- Reproducibility and isolation are priorities, even at the cost of build time.
Summary
Both FetchContent and ExternalProject solve the same problem from different angles. The right choice depends on the size and complexity of the dependency, build speed requirements, and how isolated you want the dependency to be. By following the decision guide above and the concrete example, you can integrate external libraries into your CMake project with confidence.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.