install(EXPORT ... EXPORT_PACKAGE_DEPENDENCIES) should not force REQUIRED in find_dependency() calls
In !8957 (merged), support was added for a new EXPORT_PACKAGE_DEPENDENCIES
keyword with install(EXPORT)
. Targets record which package they were a part of, and if another target depends on them, the dependency relationship is captured in the consumer's export file as a find_dependency()
call. As currently implemented, that find_dependency()
call includes a REQUIRED
keyword, but I believe that isn't the correct behavior. Consider the following three related projects:
inner/CMakeLists.txt
cmake_minimum_required(VERSION 3.29)
project(inner LANGUAGES CXX)
add_library(inner STATIC inner.cpp)
target_sources(inner PUBLIC FILE_SET HEADERS FILES inner.h)
install(TARGETS inner
EXPORT inner
FILE_SET HEADERS
DESTINATION include
INCLUDES
DESTINATION include
)
install(EXPORT inner
DESTINATION lib/cmake/inner
FILE inner-config.cmake
)
outer/CMakeLists.txt
cmake_minimum_required(VERSION 3.29)
project(outer LANGUAGES CXX)
find_package(inner REQUIRED)
add_library(outer STATIC outer.cpp)
target_sources(outer PUBLIC FILE_SET HEADERS FILES outer.h)
target_link_libraries(outer PRIVATE inner)
install(TARGETS outer
EXPORT outer
FILE_SET HEADERS
DESTINATION include
INCLUDES
DESTINATION include
)
install(EXPORT outer
DESTINATION lib/cmake/outer
FILE outer-config.cmake
EXPORT_PACKAGE_DEPENDENCIES
)
consumer/CMakeLists.txt
cmake_minimum_required(VERSION 3.29)
project(consumer LANGUAGES CXX)
find_package(outer QUIET)
if(outer_FOUND)
message(STATUS "Have outer")
else()
message(STATUS "Do not have outer")
endif()
Summarising the relationships:
-
inner
depends on nothing. -
outer
always requiresinner
. -
consumer
looks forouter
, butouter
is an optional dependency.
Let's say you then follow these steps:
- Build and install
inner
to some location. - Configure
outer
, settingCMAKE_PREFIX_PATH
to whereverinner
was installed to. - Build and install
outer
to some other location. - Configure
consumer
, settingCMAKE_PREFIX_PATH
to only whereouter
as installed to.
In the last step, because CMAKE_PREFIX_PATH
points to a location where only outer
and not inner
can be found, what should happen is that the call to find_package(outer QUIET)
should silently return with outer_FOUND
set to false. This is because while it can find an outer-config.cmake
, the dependencies that outer-config.cmake
looks for are missing, so the outer
package can't load. Rather than this being a fatal error, the call to find_package(outer)
should simply ignore that outer-config.cmake
file as being loaded unsuccessfully, then move on to trying the next location. But instead, it causes a fatal error because the outer-config.cmake
file generated by the install(EXPORT)
call in outer/CMakeLists.txt
contains find_dependency(inner REQUIRED)
. The REQUIRED
keyword here causes a fatal error if inner
cannot be found instead of returning immediately to indicate the dependency was not satisfied.
This area was sort of touched on in !8957 (comment 1456457), but I think the above scenario was not considered. I believe the current behavior of forcing a REQUIRED
keyword in the generated find_dependency()
calls is incorrect. It should instead rely on the existing find_dependency()
behavior where it will look at whether the parent project had a REQUIRED
keyword and propagate that down into the find_dependency()
call.