I'm trying to enhance my CMake convention-over-configuration
framework at the moment. Each of my C++ components (i.e. CMake project) is built via that framework and the framework is already capable to create a CMake Package Configuration File using the configure_package_config_file()
command.
The following (minimal) template file PackageConfig.cmake.in
(v1) is used by the framework.
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
check_required_components("@PROJECT_NAME@")
Everything works fine if a component Foo built and installed with that approach
is used by another component Bar with the find_package(<package> CONFIG)
command (as long as the correct directory paths pointing to the
installed CMake Package Configuration File of Foo is set via the CLI).
But (of course) problems occur if A itself has one or more dependencies. Using the current approach, B has to find_package()
each of the dependencies of
A itself. That means that transitive dependencies are currently not reported to the component requiring a dependency. Obviously this is not what I want to
achieve.
After some googleing I've learned about the find_dependency()
command, which has been created to solve the problem mentioned:
It is designed to be used in a Package Configuration File (
<package>Config.cmake
). find_dependency forwards the correct parameters forQUIET
andREQUIRED
which were passed to the originalfind_package()
call. Any additional arguments specified are forwarded tofind_package()
.
So far so good, but wait... I have to explicitly set each dependency name and its version again? I've already done that when declaring the dependencies in the CMakeLists.txt
! How can I create a reusable and generic Package
Configuration File using that approach? I can't identify any solution to that problem at the moment, other than explicitly listing all dependencies (together with their version) in the CMakeLists.txt
and passing that list to the PackageConfig.cmake.in
.
Example: Untested PackageConfig.cmake.in
(v2):
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
# TODO(wolters): How-to implement this with a generic approach? Would the
# following work? Does a better solution do the problem exist?
#
# 1. Add the following to `CMakeLists.txt`:
# list(APPEND target_dependencies "Baz 1.2.3")
# list(APPEND target_dependencies "Example 0.9.0")
# 2. "Pass" the variable `target_dependencies` to the
# `configure_package_config_file` command.
# 3. Add the following code to the CMake package config file.
foreach(dependency "@target_dependencies@")
find_dependency(${dependency})
endforeach()
include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
check_required_components("@PROJECT_NAME@")
Though this feels odd and I do not (yet) know if it works at all (but it should, theoretically).
So my question is: How can I generically implement transitive behavior for a CMake package configuration file.
Last but not least: I'm using the latest stable CMake version 3.9.4.
This is too long for a comment, so I'm posting as an answer...
As a first cut, you could compute the list of loaded packages before calling configure_package_config_file
in a variable called PACKAGE_DEPENDENCIES
. First adjust your template like so:
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
@PACKAGE_DEPENDENCIES@
include("${CMAKE_CURRENT_LIST_DIR}/@[email protected]")
check_required_components("@PROJECT_NAME@")
Then, to compute PACKAGE_DEPENDENCIES
, you could use a snippet like the following:
set(PACKAGE_DEPENDENCIES "")
get_property(packages GLOBAL PROPERTY PACKAGES_FOUND)
foreach (pkg IN LISTS packages)
get_property(is_transitive GLOBAL PROPERTY _CMAKE_${pkg}_TRANSITIVE_DEPENDENCY)
if (is_transitive)
continue()
elseif (${pkg}_VERSION)
string(APPEND PACKAGE_DEPENDENCIES "find_dependency(${pkg} ${${pkg}_VERSION})\n")
else ()
string(APPEND PACKAGE_DEPENDENCIES "find_dependency(${pkg})\n")
endif()
endforeach ()
You would then be ready to call configure_package_config_file
.
There are a number of ways this approach is brittle, however:
PACKAGES_FOUND
property includes all packages, even those found recursively via find_dependency
, which is most likely not what you want. Filtering it with the undocumented, internal property _CMAKE_<PKG>_TRANSITIVE_DEPENDENCY
(as I do here) might break without warning or policy in a newer CMake version. It also relies on packages calling find_dependency
as opposed to find_package
.PackageConfigVersion.cmake
file) might also lack version information.One resolution to (1) might be to only apply this behavior to packages using your convention-over-configuration framework (e.g. by setting a special global property). Similar story with (2).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With