Lesson 18 · Organize your project
Fetch dependencies with FetchContent
Pull a dependency straight from git into your build with FetchContent, pinned by commit hash, with the recommended FetchContent_MakeAvailable.
Sometimes a dependency is not installed on the system, and you just want the build to work. FetchContent downloads it, usually from git, and builds it as part of your project, like an add_subdirectory you did not write.
The pattern
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.16.0
)
FetchContent_MakeAvailable(googletest)
After that, googletest’s targets exist, GTest::gtest, GTest::gtest_main, and you link them as if you had written the subdirectory yourself. That is the documented two-step. FetchContent_Declare describes what to get. FetchContent_MakeAvailable fetches it and adds it to your build.
Why
This is the zero-install dependency flow. Clone, configure, build, done. The download is pinned (next section), so the same commit builds everywhere. No system package installs, no version drift.
Pin by commit hash, not branch
Pin by commit hash. A branch changes under you, a tag can move. A hash never does.
# Reliable. A specific, immutable commit.
GIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e
# Less reliable. "latest" changes under you. A tag is better than a branch,
# but a tag can move. A commit hash cannot.
GIT_TAG v1.16.0
Pitfall
GIT_TAG mainmeans your build changes every time upstream pushes. “It worked yesterday” becomes a real bug. Hash-pin dependencies you do not own. It is the difference between reproducible and lucky.
When to use FetchContent versus find_package
| Situation | Use |
|---|---|
| Library installed on the system or a package manager | find_package (lesson 17) |
| Small, self-contained, build-anywhere dependency | FetchContent |
| Both, depending on the user’s machine | FetchContent’s OVERRIDE_FIND_PACKAGE below |
OVERRIDE_FIND_PACKAGE (CMake 3.24+) is the best of both. If someone calls find_package(fmt), use the fetched copy instead.
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 11.1.4
OVERRIDE_FIND_PACKAGE
)
FetchContent_MakeAvailable(fmt)
# Later code can now write find_package(fmt) and get the fetched one.
Important details
- First declare wins. If two subdirectories declare the same dependency, the first
FetchContent_Declarein the whole project decides. Later declarations with different tags are ignored, with a warning. Declare shared dependencies at the top level. - Local override for development. Set
FETCHCONTENT_SOURCE_DIR_FMTto a local checkout and CMake uses it instead of downloading. - Timestamps. If a downloaded archive’s file timestamps break your build (the rebuild-all syndrome), use
DOWNLOAD_EXTRACT_TIMESTAMP TRUE(policy CMP0135).
One caution
FetchContent builds the dependency inside your build, so the lesson-7 leak rules apply. The fetched project’s global settings become yours. Prefer dependencies that behave, and most do, since the ecosystem standardized on targets for exactly this reason.
Tip
FetchContent caches the download inside the build directory and only re-fetches when the declared content changes or the build directory is new. Configure once, and later builds are offline-fast. CI servers with network restrictions can pre-populate the cache with
FETCHCONTENT_BASE_DIR(see the docs).
Read the docs