Dependency Tracking¶
One of the biggest features of rapids-cmake is that it can track dependencies ( find_package, cpm ),
allowing projects to easily generate <project>-config.cmake files with correct dependency requirements.
In a normal CMake project, public dependencies need to be recorded in two locations: the original CMakeLists.txt
file and the generated <project>-config.cmake. This dual source of truth increases
developer burden, and adds a common source of error.
rapids-cmake
is designed to remove this dual source of truth by expanding the concept of Modern CMake Usage Requirements to include external packages.
This is done via the BUILD_EXPORT_SET
( maps to <BUILD_INTERFACE> ) and INSTALL_EXPORT_SET
( maps to <INSTALL_INTERFACE> ) keywords on commands such as rapids_find_package()
and rapids_cpm_find()
.
Let’s go over an example of how these come together to make dependency tracking for projects easy.
rapids_find_package(Threads REQUIRED
BUILD_EXPORT_SET example-targets
INSTALL_EXPORT_SET example-targets
)
rapids_cpm_find(nlohmann_json 3.9.1
BUILD_EXPORT_SET example-targets
)
add_library(example src.cpp)
target_link_libraries(example
PUBLIC Threads::Threads
$<BUILD_INTERFACE:nlohmann_json::nlohmann_json>
)
install(TARGETS example
DESTINATION lib
EXPORT example-targets
)
set(doc_string [=[Provide targets for the example library.]=])
rapids_export(INSTALL example
EXPORT_SET example-targets
NAMESPACE example::
DOCUMENTATION doc_string
)
rapids_export(BUILD example
EXPORT_SET example-targets
NAMESPACE example::
DOCUMENTATION doc_string
)
Tracking find_package¶
rapids_find_package(Threads REQUIRED
BUILD_EXPORT_SET example-targets
INSTALL_EXPORT_SET example-targets
)
The rapids_find_package(<PackageName>)
combines the find_package
command and dependency tracking.
This example records that the Threads package is required by both the export set example-targets for both the install and build configuration.
This means that when rapids_export()
is called the generated example-config.cmake file will include the call
find_dependency(Threads), removing the need for developers to maintain that dual source of truth.
The rapids_find_package(<PackageName>)
command also supports non-required finds correctly. In those cases rapids-cmake only records
the dependency when the underlying find_package
command is successful.
It is common for projects to have dependencies for which CMake doesn’t have a Find<Package>. In those cases projects will have a custom
Find<Package> that they need to use, and install for consumers. Rapids-cmake tries to help projects simplify this process with the commands
rapids_find_generate_module()
and rapids_export_package()
.
The rapids_find_generate_module()
allows projects to automatically generate a Find<Package> and encode via the BUILD_EXPORT_SET
and INSTALL_EXPORT_SET parameters when the generated module should also be installed and added to CMAKE_MODULE_PATH so that consumers can use it.
If you already have an existing Find<Package> written, rapids_export_package()
simplifies the process of installing the module and
making sure it is part of CMAKE_MODULE_PATH for consumers.
Tracking CPM¶
rapids_cpm_find(nlohmann_json 3.9.1
BUILD_EXPORT_SET example-targets
)
The rapids_cpm_find()
combines the CPMFindPackage()
command and dependency tracking, in a very similar way
to rapids_find_package()
. In this example what we are saying is that nlohmann_json is only needed by the build directory example-config
and not needed by the installed example-config. While this pattern is rare, it occurs when projects have some dependencies that aren’t needed by consumers but are
propagated through the usage requirements inside a project via $<BUILD_INTERFACE>. Why use a build directory config file at all? The most common
reason is that developers need to work on multiple dependent projects in a fast feedback loop. In that case this workflow avoids having to re-install a project each time
a change needs to be tested in a dependent project.
When used with BUILD_EXPORT_SET, rapids_cpm_find()
will generate a CPMFindPackage(<PackageName> ...)
call, and when used
with INSTALL_EXPORT_SET it will generate a find_dependency(<PackageName> ...)
call. The theory behind this is that most packages currently don’t have
great build config.cmake support so it is best to have a fallback to cpm, while it is expected that all CMake packages have install rules.
If this isn’t the case for a CPM package you can instead use rapids_export_cpm()
, and rapids_export_package()
to specify the correct generated commands
and forgo using [BUILD|INSTALL]_EXPORT_SET.
Generating example-config.cmake¶
set(doc_string [=[Provide targets for the example library.]=])
rapids_export(INSTALL example
EXPORT_SET example-targets
NAMESPACE example::
DOCUMENTATION doc_string
)
rapids_export(BUILD example
EXPORT_SET example-targets
NAMESPACE example::
DOCUMENTATION doc_string
)
Before rapids-cmake, if a project wanted to generate a config module they would follow the example in
the cmake-packages docs and use install(EXPORT)
, export(EXPORT)
, write_basic_package_version_file
, and a custom config.cmake.in file.
The goal of rapids_export()
is to replace all the boilerplate with an easy to use function that also embeds the necessary
dependency calls collected by BUILD_EXPORT_SET and INSTALL_EXPORT_SET.
rapids_export()
uses CMake best practises to generate all the necessary components of a project config file. It handles generating
a correct version file, finding dependencies and all the other boilerplate necessary to make well-behaved CMake config files. Moreover,
the files generated by rapids_export()
are completely standalone with no dependency on rapids-cmake.