CMake FetchContent vs. ExternalProject: When to Pull Dependencies In‑Source
Discover how CMake’s FetchContent can simplify dependency handling, the trade‑offs compared to ExternalProject, a hands‑on fmt example, and practical tips for keeping your build clean and reproducible.
16 Jan 2026, 12:36 UTC

The Problem of Dependency Management in CMake
Modern C++ projects often rely on third‑party libraries. Traditionally, a project would either vend the source, use system‑wide packages, or create a separate ExternalProject that pulls, builds, and installs the dependency into a dedicated directory. Each approach has friction: vendoring bloats the repository; system packages can be out of sync; ExternalProject adds a separate build tree and requires runtime path adjustments.
Why FetchContent Matters
Starting with CMake 3.11, FetchContent lets you download a dependency during the configure phase and make it part of the current build tree. The dependency is treated like any other source file: it can be compiled, statically linked, and inspected by CMake’s target_include_directories or target_link_libraries. This eliminates a second build step and keeps the dependency visible to the configure logic.
Benefits at a Glance
- In‑source fetching removes the need for a separate install step.
- Dependencies become part of the current CMake graph, enabling compile‑time checks.
- Faster incremental builds because there’s no separate project for the library.
- Cleaner build trees; everything lives under
build/orout/.
Fetching vs. Building: A Practical Comparison
Below is a side‑by‑side outline of what happens with each method for a small library like fmt (a header‑only and optional compiled library). The comparison focuses on the number of stages, build directories, and typical runtime effects.
| Method | Stages | Build Tree | Runtime Linking |
|---|---|---|---|
| FetchContent | Configure → Build → Link | Single tree (source + dependency) | Static or dynamic, as requested |
| ExternalProject | Configure → Separate build → Install → Configure → Build → Link | Two trees (main + external) | Requires rpath or full path to the installed library |
Concrete Example: Pulling fmt with FetchContent
Assume you have a project that needs fmt for logging. The following CMakeLists.txt demonstrates a minimal setup that fetches, builds, and links fmt at configure time.
cmake_minimum_required(VERSION 3.11)
project(MyApp LANGUAGES CXX)
# 1. Enable FetchContent module
include(FetchContent)
# 2. Declare the dependency
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 9.1.0 # Pin a concrete version
)
# 3. Make the content available
FetchContent_MakeAvailable(fmt)
# 4. Add your executable
add_executable(my_app src/main.cpp)
# 5. Link fmt
target_link_libraries(my_app PRIVATE fmt::fmt)
Running the build:
# Ensure CMake 3.11+
cmake --version
# Configure and build
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
Verification steps:
- Check that
build/fmt-build/exists – the dependency is built in‑place. - Run the binary and confirm it prints formatted output.
- Inspect
build/CMakeCache.txtforFETCHCONTENT_SOURCE_DIR_FMTto see the fetched path.
Trade‑offs and Caveats
While FetchContent streamlines many workflows, it introduces its own considerations:
- Configure Time: Pulling large libraries or many dependencies can noticeably increase the initial configure step, which may affect CI pipelines. A quick test is to measure
cmake -S . -B buildduration with and without the dependency. - Source Tree Pollution: The fetched source is added to the same directory structure as your project. If you ship the repository, the dependency source remains visible, which may be undesirable for privacy or size reasons.
- Name Collisions: Two dependencies that expose the same target name can clash. Use unique target names or namespaces.
- Reproducibility: Because the fetch happens during configure, you must pin versions explicitly (as shown with
GIT_TAG) to avoid accidental upgrades. - Large Libraries: For heavy dependencies, consider
ExternalProjector system packages to keep the main build tree lean.
Actionable Takeaways
- Use FetchContent for small to medium libraries that you want to compile in‑source and keep the build tree tidy.
- Pin versions explicitly with
GIT_TAGorURL_HASHto guarantee reproducible builds. - Measure configure time when adding new dependencies; if it becomes a bottleneck, switch to
ExternalProjector pre‑built binaries. - Keep the dependency source out of the main repo by adding the fetched directory to
.gitignoreif you plan to commit the source. - Document the fetch strategy in your README so new contributors understand how dependencies are resolved.
In summary, FetchContent offers a clean, in‑source way to manage dependencies that fits well for many C++ projects. By weighing configure time, build tree cleanliness, and reproducibility, you can decide when it’s the right tool versus a more isolated ExternalProject approach.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.