CPM.cmake is a cross-platform CMake script that adds dependency management capabilities to CMake. It's built as a thin wrapper around CMake's
module that adds version control, caching, a simple API
.
Manage everything
Any downloadable project or resource can be added as a version-controlled dependency through CPM, it is not necessary to modify or package anything. Projects using modern CMake are automatically configured and their targets can be used immediately. For everything else, the targets can be created manually after the dependency has been downloaded (see the
below for examples).
Further reading
CPM: An Awesome Dependency Manager for C++ with CMake
CMake and the Future of C++ Package Management
Full CMakeLists Example
cmake_minimum_required(VERSION3.14FATAL_ERROR) # create projectproject(MyProject) # add executableadd_executable(mainmain.cpp) # add dependenciesinclude(cmake/CPM.cmake) CPMAddPackage("gh:fmtlib/fmt#7.1.3") CPMAddPackage("gh:nlohmann/[email protected]") CPMAddPackage("gh:catchorg/[email protected]") # link dependenciestarget_link_libraries(mainfmt::fmtnlohmann_json::nlohmann_jsonCatch2::Catch2WithMain)See the
for complete examples with source code and check
or in the
for example snippets.
Adding CPM
To add CPM to your current project, simply add the
of CPM.cmake or get_cpm.cmake to your project's cmake directory. The command below will perform this automatically.
mkdir -p cmake wget -O cmake/CPM.cmake https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmakeOr using PowerShell:
New-Item-ItemType Directory -Force -Path "cmake"Invoke-WebRequest-Uri "https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmake"-OutFile "cmake/CPM.cmake"You can also download CPM.cmake directly from your project's CMakeLists.txt. See the
for more details.
Usage
After CPM.cmake has been
to your project, the function CPMAddPackage can be used to fetch and configure a dependency. Afterwards, any targets defined in the dependency can be used directly. CPMAddPackage takes the following named parameters.
CPMAddPackage( NAME# The unique name of the dependency (should be the exported target's name)VERSION# The minimum version of the dependency (optional, defaults to 0)PATCHES# Patch files to be applied sequentially using patch and PATCH_OPTIONS (optional)OPTIONS# Configuration options passed to the dependency (optional)DOWNLOAD_ONLY# If set to YES, the project is downloaded, but not configured (optional, default NO)[...]# Origin parameters forwarded to FetchContent_Declare, see below )The origin may be specified by a GIT_REPOSITORY, but other sources, such as direct URLs, are
. If GIT_TAG hasn't been explicitly specified it defaults to v(VERSION), a common convention for git projects. On the other hand, if VERSION hasn't been explicitly specified, CPM can automatically identify the version from the git tag in some common cases. GIT_TAG can also be set to a specific commit or a branch name such as master, however this isn't recommended, as such packages will only be updated when the cache is cleared.
PATCHES takes a list of patch files to apply sequentially. For a basic example, see
. We recommend that if you use PATCHES, you also set CPM_SOURCE_CACHE. See
.
If an additional optional parameter EXCLUDE_FROM_ALL is set to a truthy value, then any targets defined inside the dependency won't be built by default. See the
for details.
If an additional optional parameter SYSTEM is set to a truthy value, the SYSTEM directory property of the subdirectory added will be set to true. See the
and
target property for details.
A shorthand syntax is also supported:
# A git package from a given uri with a versionCPMAddPackage("uri@version") # A git package from a given uri with a git tag or commit hashCPMAddPackage("uri#tag") # A git package with both version and tag providedCPMAddPackage("uri@version#tag")In the shorthand syntax if the URI is of the form gh:user/name, it is interpreted as GitHub URI and converted to https://github.com/user/name.git. If the URI is of the form gl:user/name, it is interpreted as a
URI and converted to https://gitlab.com/user/name.git. If the URI is of the form bb:user/name, it is interpreted as a
URI and converted to https://bitbucket.org/user/name.git. Otherwise the URI used verbatim as a git URL. All packages added using the shorthand syntax will be added using the
and
flag.
The single-argument syntax also works for URLs:
# An archive package from a given url. The version is inferredCPMAddPackage("https://example.com/my-package-1.2.3.zip") # An archive package from a given url with an MD5 hash providedCPMAddPackage("https://example.com/my-package-1.2.3.zip#MD5=68e20f674a48be38d60e129f600faf7d") # An archive package from a given url. The version is explicitly givenCPMAddPackage("https://example.com/[email protected]")Supply chain security best practice: For maximum security and reproducibility, always prefer specifying immutable git commit hashes instead of tags or branches when referencing dependencies. Tags and branches can be changed or moved, but commit hashes always refer to the same code. You can specify a commit hash before the version in the URI, e.g. gh:user/repo#<commit>@<version>. This ensures your builds are protected from unexpected upstream changes or attacks.
Additionally, if needed, extra arguments can be provided while using single argument syntax by using the shorthand syntax with the URI specifier.
CPMAddPackage( URI"gh:nlohmann/[email protected]"OPTIONS"JSON_BuildTests OFF" )The URI argument must be the first argument to CPMAddPackage. URI automatically sets EXCLUDE_FROM_ALL YES and SYSTEM YES. If this is not desired, EXCLUDE_FROM_ALL NO and SYSTEM NO can be set afterwards.
After calling CPMAddPackage, the following variables are defined in the local scope, where <dependency> is the name of the dependency.
<dependency>_SOURCE_DIR is the path to the source of the dependency.
<dependency>_BINARY_DIR is the path to the build directory of the dependency.
<dependency>_ADDED is set to YES if the dependency has not been added before, otherwise it is set to NO.
CPM_LAST_PACKAGE_NAME is set to the determined name of the last added dependency (equivalent to <dependency>).
For using CPM.cmake projects with external package managers, such as conan or vcpkg, setting the variable
will make CPM.cmake try to add a package through find_package first, and add it from source if it doesn't succeed.
In rare cases, this behaviour may be desirable by default. The function CPMFindPackage will try to find a local dependency via CMake's find_package and fallback to CPMAddPackage, if the dependency is not found.
Updating CPM
To update CPM to the newest version, update the script in the project's root directory, for example by running the same command as for
. Dependencies using CPM will automatically use the updated script of the outermost project.
Advantages
Small and reusable projects CPM takes care of all project dependencies, allowing developers to focus on creating small, well-tested libraries.
Cross-Platform CPM adds projects directly at the configure stage and is compatible with all CMake toolchains and generators.
Reproducible builds By versioning dependencies via git commits or tags it is ensured that a project will always be buildable.
Recursive dependencies Ensures that no dependency is added twice and all are added in the minimum required version.
Plug-and-play No need to install anything. Just add the script to your project and you're good to go.
No packaging required Simply add all external sources as a dependency.
Simple source distribution CPM makes including projects with source files and dependencies easy, reducing the need for monolithic header files or git submodules.
Limitations
No pre-built binaries For every new build directory, all dependencies are initially downloaded and built from scratch. To avoid extra downloads it is recommend to set the
environmental variable. Using a caching compiler such as
can drastically reduce build time.
Dependent on good CMakeLists Many libraries do not have CMakeLists that work well for subprojects. Luckily this is slowly changing, however, until then, some manual configuration may be required (see the snippets
for examples). For best practices on preparing projects for CPM, see the
.
First version used In diamond-shaped dependency graphs (e.g. A depends on [email protected] and B, which itself depends on [email protected] the first added dependency will be used (in this case [email protected]). In this case, B requires a newer version of C than A, so CPM will emit a warning. This can be easily resolved by adding a new version of the dependency in the outermost project, or by introducing a
.
Some CMake policies set to NEW Including CPM.cmake will lead to several CMake policies being set to NEW. Users which need the old behavior will need to manually modify their CMake code to ensure they're set to OLD at the appropriate places. The policies are:
and
. They make setting package options from CMPAddPackage possible.
It allows for proper package rebuilds of packages which are archives, source cache is not used, and the package URL is changed to an older version.
Relative paths provided to GIT_REPOSITORY are treated as relative to the parent project's remote.
For projects with more complex needs and where an extra setup step doesn't matter, it may be worth to check out an external C++ package manager such as
,
or
. Dependencies added with CPMFindPackage should work with external package managers. Additionally, the option
will enable find_package for all CPM dependencies.
Comparison to FindPackage
The usual way to add libraries in CMake projects is to call find_package(<PackageName>) and to link against libraries defined in a <PackageName>_LIBRARIES variable. While simple, this may lead to unpredictable builds, as it requires the library to be installed on the system and it is unclear which version of the library has been added. Additionally, it is difficult to cross-compile projects (e.g. for mobile), as the dependencies will need to be rebuilt manually for each targeted architecture.
CPM.cmake allows dependencies to be unambiguously defined and builds them from source. Note that the behaviour differs from find_package, as variables exported to the parent scope (such as <PackageName>_LIBRARIES) will not be visible after adding a package using CPM.cmake. The behaviour can be
, if required.
Comparison to pure FetchContent / ExternalProject
CPM.cmake is a wrapper for CMake's FetchContent module and adds a number of features that turn it into a useful dependency manager. The most notable features are:
A simpler to use API
Version checking: CPM.cmake will check the version number of any added dependency and emit a warning if another dependency requires a more recent version.
Offline builds: CPM.cmake will override CMake's download and update commands, which allows new builds to be configured while offline if all dependencies
.
Automatic shallow clone: if a version tag (e.g. v2.2.0) is provided and CPM_SOURCE_CACHE is used, CPM.cmake will perform a shallow clone of the dependency, which should be significantly faster while using less storage than a full clone.
Overridable: all CPMAddPackage can be configured to use find_package by setting a
, making it easy to integrate into projects that may require local versioning through the system's package manager.
for easier transitive dependency management.
Dependencies can be overridden
using CMake CLI parameters.
ExternalProject works similarly as FetchContent, however waits with adding dependencies until build time. This has a quite a few disadvantages, especially as it makes using custom toolchains / cross-compiling very difficult and can lead to problems with nested dependencies.
Options
CPM_SOURCE_CACHE
To avoid re-downloading dependencies, CPM has an option CPM_SOURCE_CACHE that can be passed to CMake as -DCPM_SOURCE_CACHE=<path to an external download directory>. This will also allow projects to be configured offline, as long as the dependencies have been added to the cache before. It may also be defined system-wide as an environmental variable, e.g. by exporting CPM_SOURCE_CACHE in your .bashrc or .bash_profile.
export CPM_SOURCE_CACHE=$HOME/.cache/CPMNote that passing the variable as a configure option to CMake will always override the value set by the environmental variable.
You can use CPM_SOURCE_CACHE on GitHub Actions workflows
and combine it with ccache, to make your CI faster. See the
for more info.
The directory where the version for a project is stored is by default the hash of the arguments to CPMAddPackage(). If for instance the patch command uses external files, the directory name can be set with the argument CUSTOM_CACHE_KEY.
CPM_DOWNLOAD_ALL
If set, CPM will forward all calls to CPMFindPackage as CPMAddPackage. This is useful to create reproducible builds or to determine if the source parameters have all been set correctly. This can also be set as an environmental variable. This can be controlled on a per package basis with the CPM_DOWNLOAD_<dependency name> variable.
CPM_USE_LOCAL_PACKAGES
CPM can be configured to use find_package to search for locally installed dependencies first by setting the CMake option CPM_USE_LOCAL_PACKAGES.
If the option CPM_LOCAL_PACKAGES_ONLY is set, CPM will emit an error if the dependency is not found locally. These options can also be set as environmental variables.
In the case that find_package requires additional arguments, the parameter FIND_PACKAGE_ARGUMENTS may be specified in the CPMAddPackage call. The value of this parameter will be forwarded to find_package.
Note that this does not apply to dependencies that have been defined with a truthy FORCE parameter. These will be added as defined.
CPM_DONT_UPDATE_MODULE_PATH
By default, CPM will override any find_package commands to use the CPM downloaded version. This is equivalent to the OVERRIDE_FIND_PACKAGE FetchContent option, which has no effect in CPM. To disable this behaviour set the CPM_DONT_UPDATE_MODULE_PATH option. This will not work for find_package(CONFIG) in CMake versions before 3.24.
CPM_USE_NAMED_CACHE_DIRECTORIES
If set, CPM use additional directory level in cache to improve readability of packages names in IDEs like CLion. It changes cache structure, so all dependencies are downloaded again. There is no problem to mix both structures in one cache directory but then there may be 2 copies of some dependencies. This can also be set as an environmental variable.
Local package override
Library developers are often in the situation where they work on a locally checked out dependency at the same time as on a consumer project. It is possible to override the consumer's dependency with the version by supplying the CMake option CPM_<dependency name>_SOURCE set to the absolute path of the local library. For example, to use the local version of the dependency Dep at the path /path/to/dep, the consumer can be built with the following command.
cmake -Bbuild -DCPM_Dep_SOURCE=/path/to/depEnvironment-based local package overrides are also available via CPM_<dependency name>_SOURCE. For example:
export CPM_Dep_SOURCE=/path/to/dep cmake -BbuildIf both the CMake option and environment variable are set, the explicit CMake option takes precedence.
Package lock
In large projects with many transitive dependencies, it can be useful to introduce a package lock file. This will list all CPM.cmake dependencies and can be used to update dependencies without modifying the original CMakeLists.txt. To use a package lock, add the following line directly after including CPM.cmake.
CPMUsePackageLock(package-lock.cmake)To create or update the package lock file, build the cpm-update-package-lock target.
cmake -Bbuild cmake --build build --target cpm-update-package-lockSee the
for more info.
Private repositories and CI
When using CPM.cmake with private repositories, there may be a need to provide an
to be able to clone other projects. Instead of providing the token in CMake, we recommend to provide the regular URL and use
to rewrite the URLs to include the token.
As an example, you could include one of the following in your CI script.
# Github git config --global url."https://${USERNAME}:${TOKEN}@github.com".insteadOf "https://github.com"# GitLab git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com".insteadOf "https://gitlab.com"Built with CPM.cmake
Some amazing projects that are built using the CPM.cmake package manager. If you know others, feel free to add them here through a PR.
OTTO - The Open Source GrooveBox
liblava - Modern Vulkan library
klogg - fast advanced log explorer
Methane Kit - modern 3D graphics rendering framework
JNGL - easy to use cross-platform 2D game library
AALTITOAD - verifier and simulator for Tick Tock Automata
ada - WHATWG-compliant and fast URL parser written in modern C++
codon - A high-performance, zero-overhead, extensible Python compiler using LLVM
Snippets
These examples demonstrate how to include some well-known projects with CPM. See the
for more snippets.
CPMAddPackage("gh:catchorg/[email protected]")
CPMAddPackage("gh:ericniebler/range-v3#0.12.0")
# as the tag is in an unusual format, we need to explicitly specify the versionCPMAddPackage("gh:jbeder/yaml-cpp#[email protected]")
CPMAddPackage( URI"gh:nlohmann/[email protected]"OPTIONS"JSON_BuildTests OFF" )
Boost is a large project and will take a while to download. Using CPM_SOURCE_CACHE is strongly recommended. Cloning moves much more data than a source archive, so this sample will use a compressed source archive (tar.xz) release from Boost's github page.
# boost is a huge project and directly downloading the 'alternate release'# from github is much faster than recursively cloning the repo.CPMAddPackage( NAMEBoostVERSION1.84.0URLhttps://github.com/boostorg/boost/releases/download/boost-1.84.0/boost-1.84.0.tar.xzURL_HASHSHA256=2e64e5d79a738d0fa6fb546c6e5c2bd28f88d268a2a080546f74e5ff98f29d0eOPTIONS"BOOST_ENABLE_CMAKE ON" )For a working example of using CPM to download and configure the Boost C++ Libraries see
.
# the install option has to be explicitly set to allow installationCPMAddPackage( URI"gh:jarro2783/[email protected]"OPTIONS"CXXOPTS_BUILD_EXAMPLES NO""CXXOPTS_BUILD_TESTS NO""CXXOPTS_ENABLE_INSTALL YES" )
CPMAddPackage( URI"gh:google/[email protected]"OPTIONS"BENCHMARK_ENABLE_TESTING Off" ) if(benchmark_ADDED) # enable c++11 to avoid compilation errorsset_target_properties(benchmark PROPERTIES CXX_STANDARD 11) endif()
CPMAddPackage( NAMEluaGIT_REPOSITORYhttps://github.com/lua/lua.gitVERSION5.3.5DOWNLOAD_ONLYYES ) if (lua_ADDED) # lua has no CMake support, so we create our own targetFILE(GLOBlua_sources${lua_SOURCE_DIR}/*.c) list(REMOVE_ITEM lua_sources "${lua_SOURCE_DIR}/lua.c""${lua_SOURCE_DIR}/luac.c") add_library(luaSTATIC${lua_sources}) target_include_directories(luaPUBLIC $<BUILD_INTERFACE:${lua_SOURCE_DIR}> ) endif()For a full example on using CPM to download and configure lua with sol2 see
.
Full Examples
See the
for full examples with source code and check out the
for many more example snippets.
Source Archives from GitHub
Using a compressed source archive is usually much faster than a shallow clone. Optionally, you can verify the integrity using
or similar. Setting the hash is useful to ensure a specific source is imported, especially since tags, branches, and archives can change.
Let's look at adding
to a project:
CPMAddPackage( NAMEspdlogURLhttps://github.com/gabime/spdlog/archive/refs/tags/v1.12.0.zipURL_HASHSHA256=6174bf8885287422a6c6a0312eb8a30e8d22bcfcee7c48a6d02d1835d7769232 )URL_HASH is optional, but it's a good idea for releases.
Supply chain security best practice: When using source archives, always specify a hash (e.g., SHA256) with the URL_HASH option. This ensures the downloaded archive matches the expected content and helps prevent supply chain attacks. Never rely solely on URLs or tags for security.
Identifying the URL
Information for determining the URL is found
.
Release
Not every software package provides releases, but for those that do, they can be found on the release page of the project. In a browser, the URL of the specific release is determined in a browser is determined by right clicking and selecting Copy link address (or similar) for the desired release. This is the value you will use in the URL section.
This is the URL for spdlog release 1.13.0 in zip format: https://github.com/gabime/spdlog/archive/refs/tags/v1.13.0.zip
Branch
The URL for branches is non-obvious from a browser. But it's still fairly easy to figure it out. The format is as follows:
https://github.com/<user>/<name>/archive/refs/heads/<branch-name>.<archive-type>
Archive type can be one of tar.gz or zip.
The URL for branch v2.x of spdlog is: https://github.com/gabime/spdlog/archive/refs/heads/v2.x.tar.gz
Tag
Tags are similar, but with this format:
https://github.com/<user>/<name>/archive/refs/tags/<tag-name>.<archive-type>
Tag v1.8.5 of spdlog is this:
https://github.com/gabime/spdlog/archive/refs/tags/v1.8.5.tar.gz
Exactly like the release.
Commit
If a specific commit contains the code you need, it's defined as follows:
https://github.com/<user>/<name>/archive/<commit-hash>.<archive-type>
Example: https://github.com/gabime/spdlog/archive/c1569a3d293a6b511ecb9c18b2298826c9578d9f.tar.gz
Determining the Hash
The following snippet illustrates determining the SHA256 hash on a linux machine using wget and sha256sum:
wget https://github.com/gabime/spdlog/archive/refs/tags/v1.13.0.zip -O - | sha256sumThe following snippet illustrates determining the SHA256 hash on a Windows machine using Powershell:
Get-FileHash-InputStream ((Invoke-WebRequest'https://github.com/gabime/spdlog/archive/refs/tags/v1.13.0.zip').RawContentStream)