Recipe
Add a library your project depends on
Use when: your code needs a library from outside the project, such as {fmt} or a JSON parser, and the build should get it by itself, with nothing installed by hand first
Goal: the library's :: target, pinned to one exact version and linked into your own target, building the same way on every machine
Taught in FetchContent, building a dependency from source · a later section extends this recipe with more cases
Quick recipe
-
1
Look up the library's home page or repository. Note where its source can be downloaded and the exact release you want.
Example
{fmt} lives at
https://github.com/fmtlib/fmt.git, and its release12.2.0is a tag. Its commit hash is1be298e1bd68957e4cd352e1f676f00e07dcfb57, if you'd rather pin the commit.- it's a Git repository with release tags — you'll use
GIT_REPOSITORYwithGIT_TAGset to the release tag, plusGIT_SHALLOW TRUE. - you'd rather pin a full commit hash — you'll use
GIT_REPOSITORYwithGIT_TAGset to the hash, and noGIT_SHALLOW. A shallow clone only fetches the tip of a branch or tag, so the commit wouldn't be there. - it publishes release archives — you'll use
URLwithURL_HASH SHA256=and the archive's 64-character fingerprint. - the source is already on disk, like the lab's minijson — you'll use
SOURCE_DIRwith that folder. - some machines already have the library installed, through vcpkg, Conan or the system — add
FIND_PACKAGE_ARGS(CMake 3.24 or newer) to the declaration, so an installed copy is used and the download is only the fallback.
- Not yet: it's a big library, such as Boost, Qt or OpenSSL, that would take minutes to rebuild in every fresh build folder — covered in vcpkg in manifest mode
Why: a tag or a commit names one fixed version of the code. A branch such as
mastermeans "whatever is there today," so two people building a month apart would get different code. - it's a Git repository with release tags — you'll use
-
2
Write
include(FetchContent), then oneFetchContent_Declarewith a name and the source you found in Step 1.Example
include(FetchContent) FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 12.2.0 GIT_SHALLOW TRUE )Why:
includeis what defines theFetchContent_commands. Declaring only records where the source comes from. Nothing is downloaded yet. -
3
Call
FetchContent_MakeAvailablewith the same name.Example
FetchContent_MakeAvailable(fmt)On the first configure, this downloads {fmt} into
build/_deps/fmt-src/and runs {fmt}'s ownCMakeLists.txtas part of your build.Why: running the library's
CMakeLists.txtinside your build is what creates its targets, such asfmt::fmt, so you can link them. -
4
Find the target name the library creates. Its README usually says, or look for the
ALIASline in itsCMakeLists.txt. Then link it withtarget_link_libraries.Example
minijson's
CMakeLists.txthasadd_library(minijson::minijson ALIAS minijson), so the target isminijson::minijson. The lab'squery.hincludesminijson.hpp, so:target_link_libraries(jsontool_core PUBLIC minijson::minijson)For {fmt}, used only inside
main.cpp:target_link_libraries(app PRIVATE fmt::fmt)- only your
.cppfiles include the library's headers — link itPRIVATE. - one of your own headers includes the library's headers — link it
PUBLIC, so anyone using your target gets the library's include folder too.
Why: linking the target brings its usage requirements, such as the include folder, along with its compiled code. You never write an include path or a library file by hand.
- only your
-
5
Configure and build as usual:
cmake -S . -B build, thencmake --build build. The download happens during the configure step only.Example
After the first configure,
build/_deps/holdsfmt-src/(the downloaded source) andfmt-build/(where it's compiled). The compile line formain.cppnow has an include folder insidefmt-src/, which came from linkingfmt::fmt.- you're online, or the source is a
SOURCE_DIR— nothing extra to do. - you're offline and
build/_deps/is already full — configure with-DFETCHCONTENT_FULLY_DISCONNECTED=ON, so nothing tries the network. - you're offline in a fresh build folder — point the dependency at a copy on disk with
-DFETCHCONTENT_SOURCE_DIR_FMT=<folder>(the name in capitals).
Why: FetchContent only works while CMake configures.
cmake --buildnever downloads anything, so an offline build of an already-configured folder just works. - you're online, or the source is a
Common mistakes
- Pinning with
GIT_TAG master, a branch that moves, so builds stop matching each other. - Pairing a commit hash with
GIT_SHALLOW TRUE. The shallow clone holds only one commit, the tip of a branch or tag, so checking out any other commit fails at configure time. - Adding
find_package(fmt)as well, after a plain declaration. FetchContent already made the target, and nothing is installed forfind_packageto find. (A declaration withFIND_PACKAGE_ARGSorOVERRIDE_FIND_PACKAGEmakes a laterfind_package(fmt)succeed, as described under "Installed copy first".) - Calling
FetchContent_Declarebeforeinclude(FetchContent). - Linking a library
PRIVATEwhen your own public header includes it, so code that uses your target can't find the header. - Writing a
URLwithoutURL_HASH, so a changed or cut-short download is compiled without anyone noticing.