Using Dependencies Guide

Introduction

Projects will frequently depend on other projects, assets, and artifacts. CMake provides a number of ways to incorporate such things into the build. Projects and users have the flexibility to choose between methods that best suit their needs.

The primary methods of bringing dependencies into the build are the find_package() command and the FetchContent module. The FindPkgConfig module is also sometimes used, although it lacks some of the integration of the other two and is not discussed any further in this guide.

Dependencies can also be made available by a custom dependency provider. This might be a third party package manager, or it might be custom code implemented by the developer. Dependency providers co-operate with the primary methods mentioned above to extend their flexibility.

Using Pre-built Packages With find_package()

A package needed by the project may already be built and available at some location on the user's system. That package might have also been built by CMake, or it could have used a different build system entirely. It might even just be a collection of files that didn't need to be built at all. CMake provides the find_package() command for these scenarios. It searches well-known locations, along with additional hints and paths provided by the project or user. It also supports package components and packages being optional. Result variables are provided to allow the project to customize its own behavior according to whether the package or specific components were found.

In most cases, projects should generally use the Basic Signature. Most of the time, this will involve just the package name, maybe a version constraint, and the REQUIRED keyword if the dependency is not optional. A set of package components may also be specified.

Examples of find_package() basic signature
find_package(Catch2)
find_package(GTest REQUIRED)
find_package(Boost 1.79 COMPONENTS date_time)

The find_package() command supports two main methods for carrying out the search:

Config mode

With this method, the command looks for files that are typically provided by the package itself. This is the more reliable method of the two, since the package details should always be in sync with the package.

Module mode

Not all packages are CMake-aware. Many don't provide the files needed to support config mode. For such cases, a Find module file can be provided separately, either by the project or by CMake. A Find module is typically a heuristic implementation which knows what the package normally provides and how to present that package to the project. Since Find modules are usually distributed separately from the package, they are not as reliable. They are typically maintained separately, and they are likely to follow different release schedules, so they can easily become out-of-date.

Depending on the arguments used, find_package() may use one or both of the above methods. By restricting the options to just the basic signature, both config mode and module mode can be used to satisfy the dependency. The presence of other options may restrict the call to using only one of the two methods, potentially reducing the command's ability to find the dependency. See the find_package() documentation for full details about this complex topic.

For both search methods, the user can also set cache variables on the cmake(1) command line or in the ccmake(1) or cmake-gui(1) UI tools to influence and override where to find packages. See the User Interaction Guide for more on how to set cache variables.

Config-file packages

The preferred way for a third party to provide executables, libraries, headers, and other files for use with CMake is to provide config files. These are text files shipped with the package, which define CMake targets, variables, commands, and so on. The config file is an ordinary CMake script, which is read in by the find_package() command.

The config files can usually be found in a directory whose name matches the pattern lib/cmake/<PackageName>, although they may be in other locations instead (see Config Mode Search Procedure). The <PackageName> is usually the first argument to the find_package() command, and it may even be the only argument. Alternative names can also be specified with the NAMES option:

Providing alternative names when finding a package
find_package(SomeThing
  NAMES
    SameThingOtherName   # Another name for the package
    SomeThing            # Also still look for its canonical name
)

The config file must be named either <PackageName>Config.cmake or <LowercasePackageName>-config.cmake (the former is used for the remainder of this guide, but both are supported). This file is the entry point to the package for CMake. A separate optional file named <PackageName>ConfigVersion.cmake or <LowercasePackageName>-config-version.cmake may also exist in the same directory. This file is used by CMake to determine whether the version of the package satisfies any version constraint included in the call to find_package(). It is optional to specify a version when calling find_package(), even if a <PackageName>ConfigVersion.cmake file is present.

If the <PackageName>Config.cmake file is found and any version constraint is satisfied, the find_package() command considers the package to be found, and the entire package is assumed to be complete as designed.

There may be additional files providing CMake commands or Imported Targets for you to use. CMake does not enforce any naming convention for these files. They are related to the primary <PackageName>Config.cmake file by use of the CMake include() command. The <PackageName>Config.cmake file would typically include these for you, so they won't usually require any additional step other than the call to find_package().

If the location of the package is in a directory known to CMake, the find_package() call should succeed. The directories known to CMake are platform-specific. For example, packages installed on Linux with a standard system package manager will be found in the /usr prefix automatically. Packages installed in Program Files on Windows will similarly be found automatically.

Packages will not be found automatically without help if they are in locations not known to CMake, such as /opt/mylib or $HOME/dev/prefix. This is a normal situation, and CMake provides several ways for users to specify where to find such libraries.

The CMAKE_PREFIX_PATH variable may be set when invoking CMake. It is treated as a list of base paths in which to search for config files. A package installed in /opt/somepackage will typically install config files such as /opt/somepackage/lib/cmake/somePackage/SomePackageConfig.cmake. In that case, /opt/somepackage should be added to CMAKE_PREFIX_PATH.

The environment variable CMAKE_PREFIX_PATH may also be populated with prefixes to search for packages. Like the PATH environment variable, this is a list, but it needs to use the platform-specific environment variable list item separator (: on Unix and ; on Windows).

The CMAKE_PREFIX_PATH variable provides convenience in cases where multiple prefixes need to be specified, or when multiple packages are available under the same prefix. Paths to packages may also be specified by setting variables matching <PackageName>_DIR, such as SomePackage_DIR. Note that this is not a prefix, but should be a full path to a directory containing a config-style package file, such as /opt/somepackage/lib/cmake/SomePackage in the above example. See the find_package() documentation for other CMake variables and environment variables that can affect the search.

Find Module Files

Packages which do not provide config files can still be found with the find_package() command, if a FindSomePackage.cmake file is available. These Find module files are different to config files in that:

  1. Find module files should not be provided by the package itself.

  2. The availability of a Find<PackageName>.cmake file does not indicate the availability of the package, or any particular part of the package.

  3. CMake does not search the locations specified in the CMAKE_PREFIX_PATH variable for Find<PackageName>.cmake files. Instead, CMake searches for such files in the locations given by the CMAKE_MODULE_PATH variable. It is common for users to set the CMAKE_MODULE_PATH when running CMake, and it is common for CMake projects to append to CMAKE_MODULE_PATH to allow use of local Find module files.

  4. CMake ships Find<PackageName>.cmake files for some third party packages. These files are a maintenance burden for CMake, and it is not unusual for these to fall behind the latest releases of the packages they are associated with. In general, new Find modules are not added to CMake any more. Projects should encourage the upstream packages to provide a config file where possible. If that is unsuccessful, the project should provide its own Find module for the package.

See Find Modules for a detailed discussion of how to write a Find module file.

Imported Targets

Both config files and Find module files can define Imported Targets. These will typically have names of the form SomePrefix::ThingName. Where these are available, the project should prefer to use them instead of any CMake variables that may also be provided. Such targets typically carry usage requirements and apply things like header search paths, compiler definitions, etc. automatically to other targets that link to them (e.g. using target_link_libraries()). This is both more robust and more convenient than trying to apply the same things manually using variables. Check the documentation for the package or Find module to see what imported targets it defines, if any.

Imported targets should also encapsulate any configuration-specific paths. This includes the location of binaries (libraries, executables), compiler flags, and any other configuration-dependent quantities. Find modules may be less reliable in providing these details than config files.

A complete example which finds a third party package and uses a library from it might look like the following:

cmake_minimum_required(VERSION 3.10)
project(MyExeProject VERSION 1.0.0)

# Make project-provided Find modules available
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

find_package(SomePackage REQUIRED)
add_executable(MyExe main.cpp)
target_link_libraries(MyExe PRIVATE SomePrefix::LibName)

Note that the above call to find_package() could be resolved by a config file or a Find module. It uses only the basic arguments supported by the Basic Signature. A FindSomePackage.cmake file in the ${CMAKE_CURRENT_SOURCE_DIR}/cmake directory would allow the find_package() command to succeed using module mode, for example. If no such module file is present, the system would be searched for a config file.

Downloading And Building From Source With FetchContent

Dependencies do not necessarily have to be pre-built in order to use them with CMake. They can be built from sources as part of the main project. The FetchContent module provides functionality to download content (typically sources, but can be anything) and add it to the main project if the dependency also uses CMake. The dependency's sources will be built along with the rest of the project, just as though the sources were part of the project's own sources.

The general pattern is that the project should first declare all the dependencies it wants to use, then ask for them to be made available. The following demonstrates the principle (see Examples for more):

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
)
FetchContent_Declare(
  Catch2
  GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  GIT_TAG        605a34765aa5d5ecbf476b4598a862ada971b0cc # v3.0.1
)
FetchContent_MakeAvailable(googletest Catch2)

Various download methods are supported, including downloading and extracting archives from a URL (a range of archive formats are supported), and a number of repository formats including Git, Subversion, and Mercurial. Custom download, update, and patch commands can also be used to support arbitrary use cases.

When a dependency is added to the project with FetchContent, the project links to the dependency's targets just like any other target from the project. If the dependency provides namespaced targets of the form SomePrefix::ThingName, the project should link to those rather than to any non-namespaced targets. See the next section for why this is recommended.

Not all dependencies can be brought into the project this way. Some dependencies define targets whose names clash with other targets from the project or other dependencies. Concrete executable and library targets created by add_executable() and add_library() are global, so each one must be unique across the whole build. If a dependency would add a clashing target name, it cannot be brought directly into the build with this method.

FetchContent And find_package() Integration

New in version 3.24.

Some dependencies support being added by either find_package() or FetchContent. Such dependencies must ensure they define the same namespaced targets in both installed and built-from-source scenarios. A consuming project then links to those namespaced targets and can handle both scenarios transparently, as long as the project does not use anything else that isn't provided by both methods.

The project can indicate it is happy to accept a dependency by either method using the FIND_PACKAGE_ARGS option to FetchContent_Declare(). This allows FetchContent_MakeAvailable() to try satisfying the dependency with a call to find_package() first, using the arguments after the FIND_PACKAGE_ARGS keyword, if any. If that doesn't find the dependency, it is built from source as described previously instead.

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
  FIND_PACKAGE_ARGS NAMES GTest
)
FetchContent_MakeAvailable(googletest)

add_executable(ThingUnitTest thing_ut.cpp)
target_link_libraries(ThingUnitTest GTest::gtest_main)

The above example calls find_package(googletest NAMES GTest) first. CMake provides a FindGTest module, so if that finds a GTest package installed somewhere, it will make it available, and the dependency will not be built from source. If no GTest package is found, it will be built from source. In either case, the GTest::gtest_main target is expected to be defined, so we link our unit test executable to that target.

High-level control is also available through the FETCHCONTENT_TRY_FIND_PACKAGE_MODE variable. This can be set to NEVER to disable all redirection to find_package(). It can be set to ALWAYS to try find_package() even if FIND_PACKAGE_ARGS was not specified (this should be used with caution).

The project might also decide that a particular dependency must be built from source. This might be needed if a patched or unreleased version of the dependency is required, or to satisfy some policy that requires all dependencies to be built from source. The project can enforce this by adding the OVERRIDE_FIND_PACKAGE keyword to FetchContent_Declare(). A call to find_package() for that dependency will then be redirected to FetchContent_MakeAvailable() instead.

include(FetchContent)
FetchContent_Declare(
  Catch2
  URL https://intranet.mycomp.com/vendored/Catch2_2.13.4_patched.tgz
  URL_HASH MD5=abc123...
  OVERRIDE_FIND_PACKAGE
)

# The following is automatically redirected to FetchContent_MakeAvailable(Catch2)
find_package(Catch2)

For more advanced use cases, see the CMAKE_FIND_PACKAGE_REDIRECTS_DIR variable.

Dependency Providers

New in version 3.24.

The preceding section discussed techniques that projects can use to specify their dependencies. Ideally, the project shouldn't really care where a dependency comes from, as long as it provides the things it expects (often just some imported targets). The project says what it needs and may also specify where to get it from, in the absence of any other details, so that it can still be built out-of-the-box.

The developer, on the other hand, may be much more interested in controlling how a dependency is provided to the project. You might want to use a particular version of a package that you built yourself. You might want to use a third party package manager. You might want to redirect some requests to a different URL on a system you control for security or performance reasons. CMake supports these sort of scenarios through Dependency Providers.

A dependency provider can be set to intercept find_package() and FetchContent_MakeAvailable() calls. The provider is given an opportunity to satisfy such requests before falling back to the built-in implementation if the provider doesn't fulfill it.

Only one dependency provider can be set, and it can only be set at a very specific point early in the CMake run. The CMAKE_PROJECT_TOP_LEVEL_INCLUDES variable lists CMake files that will be read while processing the first project() call (and only that call). This is the only time a dependency provider may be set. At most, one single provider is expected to be used throughout the whole project.

For some scenarios, the user wouldn't need to know the details of how the dependency provider is set. A third party may provide a file that can be added to CMAKE_PROJECT_TOP_LEVEL_INCLUDES, which will set up the dependency provider on the user's behalf. This is the recommended approach for package managers. The developer can use such a file like so:

cmake -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=/path/to/package_manager/setup.cmake ...

For details on how to implement your own custom dependency provider, see the cmake_language(SET_DEPENDENCY_PROVIDER) command.