1

I'm using CMake to build and to install a certain library, foo.

My library depends on some other library, bar, which has a config CMake script, so I have:

find_package(bar REQUIRED)
target_link_libraries(foo PUBLIC bar::bar)

That's as far as building goes. For installation, I have appropriate install() commands, with exports, and version configuration and all that stuff. This generates the package's -config.cmake file (as well as a version config file), so I don't need to keep one in the repository, nor generate one line-by-line within my CMakeLists.txt

Now, CMake has a module named find_dependency(), whose documentation suggests is to be used in package configuration files. But - I don't explicitly add it there. Should I? And more generally: Under which circumstances should I manually ensure a package configuration file has a find_dependency() for various find_package()'s?

einpoklum
  • 118,144
  • 57
  • 340
  • 684

1 Answers1

0

First, CMake does not support "Transitive" behavior for find_package() (check this question).

The documentation recommends that "All REQUIRED dependencies of a package should be found in the Config.cmake file":

# <package>Config.cmake file
include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)

include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake") # They depend on Stats
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsMacros.cmake")

So, answering your questions:

  • "I don't explicitly add it there. Should I?" You actually have to, at least for REQUIRED packages.
  • "Under which circumstances should I manually ensure a package configuration file has a find_dependency() for various find_package()'s?" For required packages, you must. For optional package, you might want to add it in the configuration file so that optional features will be available.

I am working on a project which depends on an external package (Catch2). In my top level CMakelists.txt I have:

# Top level CMakelists.txt
set(Catch2_DIR "${PATH_TO_CATCH2}/lib/cmake/Catch2/")
find_package(Catch2 ${CATCH2_VERSION} REQUIRED)

Then I added the following to my package configuration file:

# <package>Config.cmake file
include(CMakeFindDependencyMacro)
set(Catch2_DIR "@PATH_TO_CATCH2@/lib/cmake/Catch2/") #be careful, path hard coded
find_dependency(Catch2 REQUIRED)

Just be careful because the find_dependency is a macro and it will change the value of PACKAGE_PREFIX_DIR variable in your package configuration file.

  • 1
    1. But my package configuration file is auto-generated, using an `install(EXPORT)` command... I don't want to have to write a `-config.cmake` file. 2. If CMake can carry forward my targets's link_libraries and include_directories, why doesn't that, transitively, carry over whatever creates those dependency targets (e.g. `bar::bar` in my example)? – einpoklum Mar 25 '20 at 22:59
  • In a CMake project, you add several targets. The CMake `install(EXPORT)` acts on one target configured to be exported. CMake, them, generates a file exporting your target, i.e., its name and properties. For that, it creates a file `foo.cmake`. Notice that CMake knows the target dependencies, but it does not know where they came from, if they were built or imported. Therefore, you have to explictly tell cmake to export that information too. – Lucas Guesser Mar 25 '20 at 23:29
  • 1
    Sure it knows. Or rather - it can know. Every targets is created by a call to some cmake function/macro, possibly within larger macros/functions like `find_package()`. And it is quite possible to mark targets, e.g. using properties, with some kind of link/information about how they were created, for a less ephemeral kind of knowledge. Such properties can be examined by CMake to add `find_dependency()` calls in the generated `.cmake`. – einpoklum Mar 25 '20 at 23:45
  • That is the point, they can't. When you define a dependency to a target by a `target_link_libraries`, the target only knows its dependency and not how to build or import it. If such information was to be exported, it has to be done by the `find_package` command. As you know, the command doesn't do that. Well, there could be a command for the top level CMake lists on an option in the `find_package` command to export that information into a dependencies file, but if there is, I don't know about such a command. – Lucas Guesser Mar 26 '20 at 00:07
  • 1
    The dependent target doesn't know, but the _dependency_ target does know (or, again, can know). – einpoklum Mar 26 '20 at 00:15
  • 3
    @einpoklum is right. This is, IMHO, one of the most damning shortfalls of CMake. It's hard for me to conceive how it has gotten so much adoption. I have a library built with CMake that has a complicated configure-time configuration with many optional dependent libraries. If I want to link a new application against my library, I have to duplicate all of that logic that conditionally calls `find_package` with different required components in two places: one in the `CMakeLists.txt` file for the library, and again in the library's installed config file. This is not good design. – Jason R Jul 15 '20 at 19:33
  • @JasonR: It's not an _inherent_ shortfall of CMake, it's just a missing feature which can be implemented. In fact I'm sure there are some open bugs about it. To answer your question: CMake has been adopted because it is less bad than the alternatives; because it's improving; and because the developers are at least somewhat responsive. – einpoklum Jul 15 '20 at 20:26