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 forQUIETandREQUIREDwhich 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