How do you avoid target name conflicts in a multi-config setup?

I’m forking this discussion off from https://gitlab.kitware.com/cmake/cmake/-/issues/23482. I’ve got some large, pre-built libraries that are released separately for the Debug and Release versions. And I would just like to hook them up in a Visual Studio multi-configuration setup. The included Find[Package].cmake do not seem to support multi-configuration setups well, but I know this should still be possible in theory. CMake is making it pretty tricky though.

Both versions of the pre-built libraries include a Fiind[Package].cmake file. They have the same name, which is the first issue. If I run find_package([package]) twice and try to specify the paths for each version, CMake will ignore the second path because it’s silently cached the location from the first call. You can get around this with the NAMES option to alias the packages, but each config doesn’t just define itself – it defines a recursive web of targets, and none of these targets are distinguished by configuration. So the second find_package() call will silently use the targets defined from the first call for all of the internal libraries.

Basically all I need is the ability to call find_package() twice and distinguish targets with the same name. A namespacing system, or something. Some way to distinguish [package1].target_foo from [package2].target_foo. If you have any other ideas for approaching this I’d appreciate it. Changing the original library to support multi-config would be quite complicated and would not support backwards compatability.

There is an issue about namespacing that is kind of related. However, the way CMake prefers to do this is to have a single target with multiple locations attached in configuration-specific properties (e.g., IMPORTED_LOCATION_RELEASE rather than just IMPORTED_LOCATION).

CMake will make this work if the project’s artifacts don’t collide in a single build tree as the per-config properties are added in configuration-specific files when exported. So I would try making sure the artifact names are changed based on the configuration and just use a single install prefix for both.

I’ve seen that namespacing discussion, and I think it would be the “right” solution here, probably the only right solution (unfortunately it doesn’t seem like it’s going to happen in the near term). Note that I’m trying to work within the confines of pre-built libraries that are externally provided, and make it backwards compatible, so given that constraint I wouldn’t be able to change how things are installed/exported. And right now the targets have the same name across debug/release and don’t annotate their build configuration in the way you describe.

The only solution I’ve found (possibly the only solution that exists without namespacing or building the library from source) is the following ridiculousness: run two separate CMake instances in a subprocess via execute_process(). Call find_package() in each, and recursively parse the targets via the manual examination of all target properties, heuristically determining which properties refer to targets. Prepend all targets with a namespace. Do the same for any variables that need to be surfaced. Then (because of a second restriction in CMake, that you can’t re-export imported targets with modified properties), manually write the targets to a custom CMake file, filtering out the read only properties. Do the same with the variables. Import these .cmake files in the main process.

This works, but it’s really gross and fragile, and I’m basically just creating a meta-namespacing system through string parsing. I am kind of surprised by how well it works though.

An alternate solution might be to use the scoping of find_package (though you’ll may have to fight the cache) and do something like:

add_library(ExtDep::FullTarget IMPORTED UNKNOWN)
add_subdirectory(for-release) # find the release build, put its data onto `ExtDep::FullTarget` with `_RELEASE` suffixes
add_subdirectory(for-debug) # find the debug build and as done in `for-release`

This might be able to skirt the recursive target problem at least.