Simplify exporting of CMake Packages
Exporting CMake packages currently requires some boilerplate that is error prone and hard to get right. As an effect, many projects are not properly exported.
The following code exports a project more or less properly:
# When installing the targets, add them to an EXPORT set:
install(TARGETS ${targets} EXPORT "${PROJECT_NAME}"
... # Much boilerplate. See !2558, #18628.
)
# Install the EXPORT set as a <project>Targets.cmake file:
install(EXPORT "${PROJECT_NAME}"
COMPONENT "Develop"
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}"
FILE "${PROJECT_NAME}Targets.cmake"
NAMESPACE "${PROJECT_NAME}::"
)
# Create a <project>Config.cmake file, that finds all dependencies
# and then includes the <project>Targets.cmake from above.
# Also, optionally include a <project>Extra.cmake if the project provides
# CMake commands and/or variables.
set(cfg_file "${CMAKE_BINARY_DIR}/${PROJECT_NAME}Config.cmake")
file(WRITE "${cfg_file}"
"include(CMakeFindDependencyMacro)\n"
"find_dependency(...)\n"
... # All dependencies.
"include(\"\${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}Targets.cmake\")\n"
"include(\"\${CMAKE_CURRENT_LIST_DIR}/${PROJECT_NAME}Extra.cmake\" OPTIONAL)\n"
)
# Create a <project>ConfigVersion.cmake file:
set(cfg_version_file "${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake")
write_basic_package_version_file("${cfg_version_file}"
COMPATIBILITY SameMajorVersion
VERSION "${PROJECT_VERSION}"
)
# Install both <project>Config.cmake and <project>ConfigVersion.cmake to the
# same destination and component as the <project>Targets.cmake file:
install(FILES "${cfg_file}" "${cfg_version_file}"
COMPONENT "Develop"
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}"
)
We can wrap this code in a higher level function to reduce some of the boilerplate and provide more sensible defaults:
my_export_package(TARGETS ${targets} DEPENDENCIES ${deps})
But two questions remain:
- Why do we have to pass
DEPENDENCIES
to that function? - Don't we need a different set of
DEPENDENCIES
depending on the fact whether the${targets}
are built asSTATIC
vsSHARED
?
I think the correct answer is: CMake should be able to figure out what the dependencies are, it is simply not implemented:
- CMake knows the list of targets that were added to a specific export set.
- For each of those targets, CMake knows the list of interface link libraries (which is a different set, depending on the target type, but CMake knows that).
- For each of those link libraries, cmake knows whether the library is
IMPORTED
or not. - If a link library is
IMPORTED
, then CMake should better remember from what package it was imported from! It could set the arguments of thefind_package()
command as a target property of the imported target. It currently does not store this information. - If a link library is not
IMPORTED
, then CMake knows whether the library is installed and added to anEXPORT
set. It already prints a warning otherwise. - CMake further knows how that
EXPORT
set is installed and should be able to deduce arguments to afind_dependency()
call from this information.
Given any list of targets, cmake should be able to deduce a list of
find_dependency()
calls. CMake should make those calls in the file that is
generated in the install(EXPORT)
command. This removes the necessity to
generate a separate <project>Targets.cmake
file.
Some packages provide CMake commands and/or variables, that are often written to
a <package>Extra.cmake
file. install(EXPORT)
should take a EXTRA_CMAKE
parameter and include()
the specified file at the end of the generated file.
install(EXPORT)
should also take the arguments VERSION
and COMPATIBILITY
to succeed the write_basic_package_version_file()
function. Having version
information available when the EXPORT
set is installed, allows using this
information for deducing arguments to the find_dependency()
call.
The call to install(EXPORT)
might look like this:
install(TARGETS ${targets} EXPORT "${PROJECT_NAME}"
...
)
install(EXPORT "${PROJECT_NAME}"
[COMPONENT "<component>"] # Should have a reasonable default: #18628
[DESTINATION "<destination>"] # Should have a reasonable default: #18453
FILE "${PROJECT_NAME}Config.cmake" # Default?
NAMESPACE "${PROJECT_NAME}::" # Default?
COMPATIBILITY SameMajorVersion # Default?
VERSION "${PROJECT_VERSION}" # Default?
EXTRA_CMAKE "${PROJECT_NAME}Extra.cmake"
)
CC: @brad.king, @craig.scott, @kyle.edwards Ref: !2558 (merged), #18628, #18628, #18453