Config modules and recursive import target dependencies

This is a follow-up to something posted on the mailing list years ago that was ignored: CMake - Shortcomings with exporting and importing targets

I’ve now been bit by this not only in my own projects, but also in the wild.

NOTE: PLEASE IGNORE FOR PURPOSES OF DISCUSSION that CMake’s built-in find_package module for Boost defines a Boost::boost target, as that’s beside the larger point that I’m trying to make!

Take a look at this GitHub project: GitHub - a-n-t-h-o-n-y/Twitter-API-C-Library: Twitter REST and Stream APIs in C++

It’s a CMake project that builds a couple of libraries that have a dependency on a custom boost import target. Config modules are generated and deployed via the CMake install process in order to facilitate use of the libraries in other projects.

Unfortunately, because import target definitions are not propagated by the config module generator, downstream users will get bit by the unsatisfied custom import target dependency of the imported target defined by the config module. This forces downstream users to dig up the source for the library project and copy its CMake goop for defining the custom import target in order to satisfy the upstream dependency. This hurts. This is a pain point. I’ve now been bit by this multiple times.

In my own project, my workaround was to ignore CMake’s config module generator and write my own that propagated upstream import target definitions into the generated config module. It would obviously be much better if CMake supported this out of the box, though, which is why I’m still trying to get attention on this - especially now that I’ve been bit by it in multiple, totally different situations.

This is what the CMakeFindDependencyMacro is for. In your foo-config.cmake, add a find_dependency(Boost) to get its imported targets.

That looks like it’s only useful if the dependency was set up via find_package which wouldn’t apply if the dependency is synthesized directly in the project as an import target or via ExternalProject_Add() etc.

Imported targets are imported targets; CMake has no idea the difference between one made by a find module or regular code. Sorry, got the threads I’m replying to mixed up.

If you synthesized an imported target manually, you’ll need to recreate it in some way in your config.cmake file as well.

Right, I tried to explain that already. My point is that CMake’s package config generation functionality provides absolutely nothing to help there, yet it could (and should) since CMake knows all about the synthesized/imported target (albeit only in the build environment and not necessarily the deployment one). What’s worse is that you have to bypass the package config generation completely and use your own, because I don’t think there’s any straightforward way specified to dovetail custom stuff with the generated stuff.

So the problem with exporting IMPORTED targets as-is is that it embeds build-time information into the target. So if you built against /usr/lib/libA.so, but someone uses it packaged up in /opt/homebrew/a/lib/libA.so, your package just exported a target which says "must use /usr/lib" which isn’t actually true. This is why you need to find your dependencies again in your -config.cmake: they may have relocated. Now, if you don’t care about that, fine. But I don’t think CMake should give such a mechanism that is only useful to unmovable install trees (as most projects should be relocatable).

Now, if someone wants to write a separate module that takes an imported target and dumps it into a file for use by config.cmake scripts, fine. But I don’t think CMake should be getting into that business and providing such a module itself.