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
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 )
rapids_find_package(Threads REQUIRED BUILD_EXPORT_SET example-targets INSTALL_EXPORT_SET example-targets )
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.
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() 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.
rapids_cpm_find(nlohmann_json 3.9.1 BUILD_EXPORT_SET example-targets )
rapids_cpm_find() combines the
CPMFindPackage() command and dependency tracking, in a very similar way
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_package() to specify the correct generated commands
and forgo using [BUILD|INSTALL]_EXPORT_SET.
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
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.