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 main means 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

SituationUse
Library installed on the system or a package managerfind_package (lesson 17)
Small, self-contained, build-anywhere dependencyFetchContent
Both, depending on the user’s machineFetchContent’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_Declare in 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_FMT to 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