With the modern programming languages, has turned common has a way to manage dependencies of your projects, either using npm, yarn, pip, or cargo this process is easy. However, when using C or C++ this process is not trivial. With this in mind, I thought to write this post about how I automate the download and configuration of libraries from Git repositories using the CMake.
Some solutions were created to solve this problem, such as conan and vcpkg but it loads another dependency installed on your system and requires your dependency to be indexed by its server.
The FetchContent module
CMake is a build system that automates the generation of Makefiles that define the compilation process of a source code project. It provides various modules that help with configuration in many ways. One of these modules is FetchContent, which helps to fetch external content at configuration time, ie when CMake is generating the Makefile.
It is quite simple to use it, in the example below we have added GoogleTest to our project:
include(FetchContent)
FetchContent_Declare(GoogleTest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG master
)
FetchContent_MakeAvailable(GoogleTest)
After that, we have the GoogleTest project as well as its libraries accessible at configuration time, so we can link any executable (or library) with GoogleTest, for example:
# create the main target for run the tests
add_executable(mytests
test/main.cpp
test/awesome_test.cpp)
# link with the googletest main library
target_link_libraries(mytests gtest_main)
Create a CMake function
To automate this process, I usually create a simple function that concentrates the code that uses FetchContent, this makes the CMake script more readable and easier to add new dependencies.
function(GET_DEPENDENCY D_NAME D_URL D_TAG)
message(CHECK_START "Configuring ${D_NAME}")
FetchContent_Declare(${D_NAME}
GIT_REPOSITORY ${D_URL}
GIT_TAG ${D_TAG}
)
FetchContent_MakeAvailable(${D_NAME})
endfunction()
So I can use the get_dependency
function passing three parameters:
-
D_NAME
: the name to refers to this dependency. -
D_URL
: the link remote Git repository (example:https://github.com/...
-
D_TAG
: the Git tag to get, may be the commit hash or version (examples:de6e5fa
,v1.0.2
, etc).
get_dependency(GoogleTest "https://github.com/google/googletest.git" master)
To add more dependencies...
get_dependency(spdlog "https://github.com/gabime/spdlog" v1.8.5)
get_dependency(CLI11 "https://github.com/CLIUtils/CLI11" v1.9.1)
get_dependency(GoogleTest "https://github.com/google/googletest" master)
When I use this configuration, it becomes much easier to add and remove dependencies on external libraries, especially since I use open source libraries on Github. This also helps in team design by not having to have a complex set of steps to set up the development environment on the developers' different machines. It also brings a benefit to using CI (Continuous Integration).
How it works
When you run the configuration command, such as
cmake -S . -B <build-dir>
CMake downloads the source code from D_URL
and store into a directory <build-dir>/_deps
. Inside the directory, FetchContent create three sub-directories: ${D_NAME}-src
, ${D_NAME}-build
and ${D_NAME}-subbuild
.
Example with GoogleTest:
<build-dir>/
| _deps/
| | googletest-src/
| | googletest-subbuild/
| | googletest-build/
-
src
: source code of the dependency (from Git repository). -
build
: CMakeFiles directory of the dependency. -
subbuild
: created to perform the population of content.
For details, see FecthContent documentation.
Top comments (0)