Bringing dependencies in with FetchContent

CMake · Practical · 12 min

It makes a project buildable with just CMake and a compiler, which is ideal for small dependencies and test frameworks.

FetchContent downloads a dependency at configure time and adds it to your build as if it were a subdirectory. Its targets then exist in your project:

include(FetchContent)
FetchContent_Declare(fmt
  GIT_REPOSITORY https://github.com/fmtlib/fmt.git
  GIT_TAG 11.1.4)
FetchContent_MakeAvailable(fmt)
target_link_libraries(app PRIVATE fmt::fmt)

Good practice:

  • Pin an exact tag or, better, a commit hash. Downloading a URL archive with URL_HASH SHA256=... is faster than cloning and verifiable.
  • Add FIND_PACKAGE_ARGS (3.24+) so FetchContent_MakeAvailable first tries find_package and only downloads when the package isn't installed. Distributions and package managers can then supply their own copy.
  • FETCHCONTENT_FULLY_DISCONNECTED=ON or FETCHCONTENT_SOURCE_DIR_<NAME> let offline builds and local checkouts override the download.

Trade-offs: the dependency is built from source in every fresh build tree, its options and warnings mix with yours, and two dependencies fetching different versions of a third library will conflict (the first declaration wins).

For a few small libraries and test frameworks (GoogleTest, Catch2) FetchContent is great. For many or heavy dependencies (Boost, Qt, OpenSSL) use a package manager like vcpkg or Conan, with find_package in the CMakeLists.

Example

cmake_minimum_required(VERSION 3.25)
project(cli LANGUAGES CXX)

include(FetchContent)
FetchContent_Declare(fmt
  URL https://github.com/fmtlib/fmt/archive/refs/tags/11.1.4.tar.gz
  FIND_PACKAGE_ARGS 11)          # use an installed fmt >= 11 if there is one
FetchContent_Declare(googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG v1.16.0
  GIT_SHALLOW TRUE)
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)    # options of the dependency
FetchContent_MakeAvailable(fmt googletest)

add_executable(cli main.cpp)
target_link_libraries(cli PRIVATE fmt::fmt)

add_executable(cli_test test.cpp)
target_link_libraries(cli_test PRIVATE GTest::gtest_main)

Remember

  • FetchContent_Declare + FetchContent_MakeAvailable add a dependency's targets to your build
  • Pin versions; FIND_PACKAGE_ARGS prefers an installed copy
  • Use a package manager for many or heavy dependencies

Watch out for

  • Fetching a branch name like main, so builds change silently whenever upstream pushes.

Check yourself

  1. What does FIND_PACKAGE_ARGS in FetchContent_Declare do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cmake.org/cmake/help/latest/module/FetchContent.html