Exported build directories, package registry, & single-config generators
When using export(PACKAGE)
to export a build directory to the user package registry, CMake will find the exported build directory in find_package()
calls of other packages. This is very convenient when simultaneously working on a library and on a project consuming the library.
CMake defines a mechanism for mapping build configurations for imported targets. This means that a project can be installed with more than one build configuration, and CMake will automatically select the configuration that matches your current build configuration. However, exported build directories for single-config generators (e.g. Ninja or Makefile) only contain a single build configuration, hence the mapping logic doesn't apply.
While it is possible to export multiple build directories with different build configurations to the package registry, this isn't particularly helpful; find_package()
only matches target architecture and version semantics, and it simply picks the first match among the exported build directories. This usually goes unnoticed on non-Windows systems (GCC kind of tolerates mixing debug and release builds) but leads to linker errors with VC++ on Windows (VC++ doesn't support mixing debug and release builds). And these are hard to diagnose – why does CMake find the "wrong" build directory? – and hard to resolve: the only way to reliably enforce using a particular build configuration from an exported build directory is to erase the package registry entries for all other build directories.
(I'd argue that even on non-Windows platforms the behavior is broken because one shouldn't be mixing debug and non-debug builds, and that the linker errors in VC++ are actually a feature. libstdc++ doesn't mind if you mix object files with different settings for _GLIBCXX_DEBUG
, but of course this switch changes object layouts, which is fun to debug.)
In my own projects I currently work around this problem by using a custom "...ConfigVersion.cmake" file which also matches the build configuration
- if the exported build directory uses a single-config generator,
- if the package is not "arch-independent",
- if the user didn't disable this behavior by defining
IGNORE_EXPORTED_BUILD_DIR_CONFIG
.
I do this only for exported build directories; the version check in the installed package remains unaltered.
Code snippet:
# ExtendedConfigVersion.cmake.in
...
# Build configurations are considered to match if both are "Debug" or neither
# is "Debug". All other configurations can be matched freely.
if(NOT "@_IBPF_ARCH_INDEPENDENT@"
AND NOT "@GENERATOR_IS_MULTI_CONFIG@"
AND NOT IGNORE_EXPORTED_BUILD_DIR_CONFIG)
if(GENERATOR_IS_MULTI_CONFIG)
set(PACKAGE_VERSION "${PACKAGE_VERSION} single-config")
set(PACKAGE_VERSION_UNSUITABLE TRUE)
return()
elseif(("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" AND NOT "@CMAKE_BUILD_TYPE@" STREQUAL "Debug")
OR (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" AND "@CMAKE_BUILD_TYPE@" STREQUAL "Debug"))
set(PACKAGE_VERSION "${PACKAGE_VERSION} single-config CMAKE_BUILD_TYPE=@CMAKE_BUILD_TYPE@")
set(PACKAGE_VERSION_UNSUITABLE TRUE)
return()
endif()
endif()
What I dislike about this approach is that it is not the library that suffers from this problem, it is always the dependent project. To fix the issue with a custom version template when it occurs in a dependent project, it is the library project that needs to be changed.
This might be a problem that is best addressed in CMake itself. But I'm not sure what the "right" solution would look like. Perhaps it would be preferable to extend the package registry mechanism such that it can distinguish "single-config" and "multi-config" build directories, and that it selects the build directory with the best match for the build config?
One challenge is that we probably don't want to enforce that build configurations match exactly. It should be possible to mix Release
and RelWithDebInfo
, for instance. My snippet above handles this by special-casing Debug
: either both build types are Debug
or neither is, otherwise the package is considered unsuitable. This works for me, but hard-coding Debug
doesn't feel quite right.
The question is further complicated by the issue of mixing "single-config" and "multi-config" build configurations. My snippet above simply cries "unsuitable" if a "multi-config" project is considering a "single-config" build directory. Perhaps a more fine-grained response would be desirable, e.g. a "multi-config" project can find a package build directory if exported build directories are available for both Debug
and non-Debug
configurations.
Any opinions on this?
(Previously discussed here: https://github.com/robotology/ycm/issues/264)