Introduction
In CMake, FetchContent
module is used to fetch and add an external cmake project or subdirectory to our project at the configure step. The process is as if add_subdirectory()
is run for them.
Due to this,
- All the targets of external project become available in our project.
- When we build our project, the external is built as a subdirectory of our project.
- If a target of our project links to the external project, the interface and public include directories are also passed to our target. Therefore, we can use the external project’s public and interface headers in the C++ code of our target.
How to use
We can give the url of a git repository as an external project. I created an example repo on my Github account for this:
# To include FetchContent functions
include(FetchContent)
# Give all details of the external project
FetchContent_Declare(
geometry # a custom name to refer to in MakeAvailable
GIT_REPOSITORY https://github.com/sorush-khajepor/geometry.git
GIT_TAG 1.0
)
# The external is downloaded by this:
FetchContent_MakeAvailable(geometry)
The source code of geometry
is downloaded and unpacked in
build/_deps/geometrygithub-src
and it is built in
build/_deps/geometrygithub-build
In the example below, we are giving the URL of a zip file:
include(FetchContent)
FetchContent_Declare(
geometry
URL https://github.com/sorush-khajepor/geometry/archive/refs/tags/1.0.zip
# set files' timestamps to time of extraction
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(geometry)
Note that, it is automatically unzipped. There are many download and git repository options mentioned in Download Step Options of CMake Manual for ExternalProject_Add()
which is valid for FetchContent
.
If the external project is on a local storage, you can fetch it as:
FetchContent_Declare(
mylib
SOURCE_DIR /absolute/path/to/mylib_source
)
FetchContent_MakeAvailable(mylib)
Repeated dependency
I explain this with an example. We have a build system that contains project A and project B. Project A depends on project B and Eigen library, and project B depends on Eigen library. Both projects run FetchContent separately, what will happen?
Both will use the same downloaded Eigen. The first FetchContent_MakeAvailable(Eigen)
will download Eigen, the next one will be ignored even if it has different details.
Also, ensure the name of a fetched content is universal (preferably the name of its project without prefix or postfix) in our build system. In other words, differnt projects in our build system fetch it with the same name. For instance, for prject A, we have:
FetchContent_Declare(
Eigen
# details
)
FetchContent_MakeAvailable(Eigen)
And for project B we have
FetchContent_Declare(
Eigen34
# details
)
FetchContent_MakeAvailable(Eigen2)
As Eigen and Eigen34 are different names, CMake downloads both and includes them as subdirectories, then we get conflicts.
Avoid install
If a fetched content has install instructions, it will be installed along our project. If we are linking statically against the fetched content, we may not need its library at install step. To disable install step of a fetched content we use option EXCLUDE_FROM_ALL
that results in removing the fetched project from ALL
target. It also removes the fetched project from IDEs. Note that with this option, the fetched content will be built if a target depepends on it (which is the goal of fetched content), but not by default.
FetchContent VS Find_Package
FetchContent is for when our a project depends on an external CMake source project that we want to build along our project.
However, Find_Package()
is for when the dependency is already built and installed somewhere.
FetchContent VS ExternalProject_Add
ExternalProject_Add()
treats the external code as a completely separate build. It downloads, configures, builds, and installs the project in its own build tree. Our main project can then depend on the installed artifacts (e.g., libraries or binaries) rather than the source code itself (FetchedContent way).
ExternalProject_Add()
is useful for complex scenarios where the external project has its own build system or requires a different configuration than your main project.
Another detailed difference is that FetchContent_MakeAvailable()
downloads immidiately at the configure step, but ExternalProject_Add()
defers the download to the build step. Therefore, if you need to use your material at the configure step, use FetchContent.
Fetch but not add_subdirectory()
FetchContent can also be used to download a content that is not a CMake project like some data files. But if the content is a CMake project, it will be added to the build. To avoid that:
- The first option is to use FetchContent and have it in the build, but exclude it from default build by using
EXCLUDE_FROM_ALL
. - The second option is
ExternalProject_Add()
. - The third option is that instead of
FetchContent_MakeAvailable()
, we can useFetchContent_Populate()
to download the content and skipadd_subdirectory()
command. However, CMake documentation is strongly against usingFetchContent_Populate()
. - The forth option is to use
file(DOWNLOAD)
andfile(ARCHIVE_EXTRACT)
command. Forfile(DOWNLOAD)
, to avoid re-downloading with every CMake call, useEXPECTED_HASH
.