I have projectA, into which I'm importing a library with:
add_library(foo STATIC IMPORTED)
set_property(TARGET foo PROPERTY IMPORTED_LOCATION /path/to/foo.a)
I then use foo
in several places within the project, and it all works fine.
A couple of directory levels down I want to export a library built within this project for use in yet another project with a completely disconnected CMake config. I've got:
...
target_link_libraries(thislib foo)
export(TARGETS thislib FILE /path/to/thislib.cmake)
The importing projectB also needs foo though (because the imported library needs it), and complains that it cannot find -lfoo
. I tried adding it to the export
command, but then I get:
CMake Error at thislib/CMakeLists.txt:37 (export):
export given target "foo" which is not built by this project.
I just want to export the same configuration I use locally to the other (importing) project. I don't want to have to tell projectB about foo
explicitly. Is there some way to accomplish this?
I did not find an actual solution for the problem as stated, but am posting my own workaround for future reference.
I realized that the foo
dependency was being emitted in the export; it just didn't have a path with it. And since I still haven't figured out how to get cmake to export the path along with it, I reverted my export
command to that shown in the question above (without foo
).
Then I went back to the original place where foo
was being imported and removed the add_library
and set_property
, replacing them with this:
set(foo /path/to/foo.a)
Then changed the target_link_libraries
to:
target_link_libraries(thislib ${foo})
In other words, rather than making it a real "imported library", it's just a raw library path. This does get correctly written into the export file and allows projectB to link.
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()
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With