Introduction:
When installing an export-set using install(EXPORT)
a package-config script is generated which, when loaded via a find_package
call, creates imported targets and also sets their INTERFACE_LINK_LIBRARIES
property.
case 1:
If the INTERFACE_LINK_LIBRARIES
property contains dependencies to targets that have been imported (e.g. by a call to find_package
) during build-time, then that generated package-config script does not check if these dependency targets exist when it is loaded by find_package
.
At the end of the generated package-config you can find the following comment:
# This file does not depend on other imported targets which have
# been exported from the same project but in a separate export set.
case 2:
However, if the INTERFACE_LINK_LIBRARIES
contains dependencies to targets that themselves are also built and exported in another export-set, then the generated package-config script checks that these dependency targets already exist and fails the current find_package
call if they are not.
At the end of the generated package-config you can find the following code instead of the comment from case 1:
# Make sure the targets which have been exported in some other
# export set exist.
unset(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)
foreach(_target "dependency1" "dependency2" )
if(NOT TARGET "${_target}" )
set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets "${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets} ${_target}")
endif()
endforeach()
if(DEFINED ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)
if(CMAKE_FIND_PACKAGE_NAME)
set( ${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE)
set( ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE "The following imported targets are referenced, but are missing: ${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets}")
else()
message(FATAL_ERROR "The following imported targets are referenced, but are missing: ${${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets}")
endif()
endif()
unset(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE_targets)
Observation:
Note, the exact wording of the comment in case 1: it talks about targets which have been exported from the same project (but in some other export set).
I can understand this reasoning. Targets from the same project probably have something in common and therefore might be expected to be installed together, especially if they depend on each other.
However, the check in case 2 is always generated for all dependencies that were built together, regardless of the project they belong to.
Hypothesis:
Either the generated comment in case 1 is misleading or the check in case 2 should not be generated in all cases.
Suggestion:
Whether the check from case 2 should be generated for all dependencies (as currently) or only for the ones from the same project, I would prefer to have some mechanism to disable this check.
Maybe some variable that is checked and only if it evaluates to TRUE
should the specific check be done.
That would allow to introduce the required dependencies after find_package
(successfully) returns which is a valid use-case, especially if find_package
is called recursively to find indirect dependencies / components.
Otherwise, the (indirect) dependencies and their order has to be known at the time find_package
is called for one (direct) dependency.
Of course, that problem currently only manifests if the check is in the generated package-config, which is the case if all dependencies are built monolithically.
Example for reproduction:
Possibly, I want to use find_package
to find some dependency and use the pre-built and already installed one and only fall back to building it myself via add_subdirectory
if I am unable to find it.
That would generate different package-config scripts, whether I found a pre-built dependency or had to build it myself:
source1/source1.cpp:
int source1() { return 1; }
source1/source2/source2.cpp:
int source2() { return 2; }
source1/CMakeLists.txt:
cmake_minimum_required( VERSION 3.23 )
project( lib1 VERSION 1.0.0 )
# Prefer pre-built dependency and fall back to building it ourselves.
if (NOT TARGET common::lib2)
find_package( lib2 )
if (NOT lib2_FOUND)
add_subdirectory( source2 )
endif()
endif()
# Build lib1.
add_library( ${PROJECT_NAME} SHARED )
add_library( common::${PROJECT_NAME} ALIAS ${PROJECT_NAME} )
target_sources( ${PROJECT_NAME} PRIVATE source1.cpp )
target_link_libraries( ${PROJECT_NAME} PUBLIC common::lib2 )
# Install lib1 and its package-config script.
install( TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}_exportset
COMPONENT ${PROJECT_NAME}
)
install( EXPORT ${PROJECT_NAME}_exportset
DESTINATION lib/cmake/${PROJECT_NAME}-${PROJECT_VERSION}
NAMESPACE common::
COMPONENT ${PROJECT_NAME}
FILE ${PROJECT_NAME}-config.cmake
)
source1/source2/CMakeLists.txt:
cmake_minimum_required( VERSION 3.23 )
project( lib2 VERSION 2.0.0 )
# Build lib2.
add_library( ${PROJECT_NAME} SHARED )
add_library( common::${PROJECT_NAME} ALIAS ${PROJECT_NAME} )
target_sources( ${PROJECT_NAME} PRIVATE source2.cpp )
# Install lib2 and its package-config script.
install( TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}_exportset
COMPONENT ${PROJECT_NAME}
)
install( EXPORT ${PROJECT_NAME}_exportset
DESTINATION lib/cmake/${PROJECT_NAME}-${PROJECT_VERSION}
NAMESPACE common::
COMPONENT ${PROJECT_NAME}
FILE ${PROJECT_NAME}-config.cmake
)
Build like so:
# Build and install only lib2:
cmake -S source1/source2 -B build2-only && cmake --build build2-only && cmake --install build2-only --component lib2 --prefix installed2-only
# Build and install only lib1, using pre-built lib2:
lib2_DIR=installed2-only cmake -S source1 -B build1-only && cmake --build build1-only && cmake --install build1-only --component lib1 --prefix installed1-only
# Build and install lib1 and lib2:
cmake -S source1 -B build-both && cmake --build build-both && cmake --install build-both --component lib1 --prefix installed-both && cmake --install build-both --component lib1 --prefix installed-both
You will notice that the two package-config scripts for lib1
,
-
installed1-only/lib/cmake/lib1-1.0.0/lib1-config.cmake
and installed-both/lib/cmake/lib1-1.0.0/lib1-config.cmake
are not identically but only differ in the (absence of) the dependency check.