I also can't find an ideal way to do this. But here is the workaround I'm using for now. It's extra work, and not DRY, but I think it achieves the right thing.
Imagine lib B
depends on third party lib A
. A
either has a find module defined, or we can implement a custom find module for it. Both are doable. Assume we've already written FindA.cmake
and stored in in ${CMAKE_SOURCE_DIR}/cmake
. Also, let's assume that when you run cmake to generate B
's build system, you provide A_ROOT
to help cmake locate A
.
Then in B's top-level CMakeLists.txt
we need:
# Use FindA.cmake, defined in the cmake/ directory.
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
find_package(A REQUIRED)
# ... (define the B target here)...
# Copy the config file to the build dir
configure_file(cmake/BConfig.cmake cmake/BConfig.cmake @ONLY)
# Copy A's find module (skip this step if FindA is not custom).
configure_file(cmake/FindA.cmake cmake/FindA.cmake COPYONLY)
# Create the target export.
export(EXPORT BTargets
FILE ${CMAKE_BINARY_DIR}/cmake/BTargets.cmake
NAMESPACE B::
)
# Register B so cmake can find B's config file.
export(PACKAGE B)
Now in cmake/BConfig.cmake
:
# Get the exported find module onto the module path.
# This step is unnecessary if the find module is part of cmake.
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
# Store the root so that find_package will work.
# You may have to store other variables here, too, but
# the principle is just more of the same.
set(A_ROOT @A_ROOT@) # **Could optionally cache this.
find_package(A MODULE REQUIRED)
# The usual one-liner for a config file.
include("${CMAKE_CURRENT_LIST_DIR}/BTargets.cmake")
Just to drive the solution home, let's look at a second example, Rocket
this time using Boost
, which already has a find module defined.
CMakeLists.txt
:
option(Boost_USE_MULTITHREADED ON)
option(Boost_USE_STATIC_LIBS OFF)
find_package(Boost REQUIRED COMPONENTS filesystem program_options)
add_library(Rocket rocket.cpp)
target_link_libraries(Rocket
PUBLIC Boost::filesystem
PRIVATE Boost::program_options
)
configure_file(cmake/BConfig.cmake cmake/BConfig.cmake @ONLY)
export(EXPORT RocketTargets
FILE ${CMAKE_BINARY_DIR}/RocketTargets.cmake
NAMESPACE Rocket::
)
export(PACKAGE Rocket)
Then cmake/RocketConfig.cmake
would have:
set(BOOST_ROOT @BOOST_ROOT@ CACHE PATH "In case boost was relocated.")
set(Boost_USE_MULTITHREADED @Boost_USE_MULTITHREADED@)
set(Boost_USE_STATIC LIBS @Boost_USE_STATIC_LIBS@)
find_package(Boost REQUIRED COMPONENTS filesystem program_options)
include("${CMAKE_CURRENT_LIST_DIR}/RocketTargets.cmake")
So yeah. It seems like too much typing. It seems like cmake should be able to generate all of that code from an export
statement in CMakeLists.txt
. But this strategy seems to achieve the goal.
Also, I haven't tested yet, but I suspect that, if your transitive dependency uses a CONFIG
instead of a MODULE
for find_package
, it should be very easy just to add to CMAKE_PREFIX_PATH
in your config file as well.
Edit (2021): Tsyvarev has pointed out the find_dependency()
macro, which can simplify the above approach somewhat. Consider using that instead of find_package()
.