Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CMake static library dependencies not propagating with ExternalProject_Add

Tags:

c

linker

cmake

I'm currently in the process of trying to get an executable to link properly with all the dependencies it needs.

Here is an example structure of the dependencies:

exe -> libA -> libB

exe and libA have their own repositories. exe pulls in libA something like this:

add_executable(exe ${sources})

ExternalProject_Add(
    libA
    GIT_REPOSITORY http://some/git/url/libA.git
    ...
)

target_include_directories(exe PRIVATE ${libA_includes})
target_link_libraries(exe ${libA_libs}/libA.a)

add_dependencies(exe libA)

libA pulls in libB in a similar fashion:

add_library(libA STATIC ${sources})

ExternalProject_Add(
    libB
    URL http://some/artifact/repo/libB.tgz
    ...
)

target_include_directories(libA PRIVATE ${libB_includes})
target_link_libraries(libA ${libB_libs}/libB.a)

add_dependencies(libA libB)

I can build libA fine, along with a test executable that properly links against libB. However, when I try to build exe, it only links against libA but not libB. How can I get CMake to know that the dependency between libA and libB shouldn't be lost through ExternalProject?

Edit:

I've created some dependency graphs to hopefully clarify what I want:

Actual:

exe actuallibA actual

What I expect:

expected

Any help would be greatly appreciated. Thanks.

Edit 2:

I ended up using the following as a solution:

In libA's CMakeLists.txt (where ${MODULE_NAME} is some module's name within libA):

export(TARGETS ${MODULE_NAME}
       NAMESPACE "${CMAKE_PROJECT_NAME}-"
       APPEND FILE ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}-targets.cmake)

In exe's CMakeLists.txt:

ExternalProject_Add(
    libA ...
)

ExternalProject_Get_Property(libA source_dir)
ExternalProject_Get_Property(libA binary_dir)

target_include_directories(${PROJECT_NAME} PRIVATE ${source_dir})

set(LIBA_TARGETS_FILE ${binary_dir}/libA-targets.cmake)
if(EXISTS ${LIBA_TARGETS_FILE})
    include(${LIBA_TARGETS_FILE})
    target_link_libraries(${MODULE_NAME} libA-module_in_liba)
endif()

add_dependencies(${MODULE_NAME} libA)

Note: this does now require the project to be built using two runs of cmake as follows:

mkdir build; cd build
cmake .. # initial configuration
make # download the external projects
cmake .. # be able to include targets cmake file from external projects
make # this should now link everything properly
like image 695
Nathaniel Avatar asked Jan 30 '15 16:01

Nathaniel


1 Answers

As far as I know, there is no way to achieve what you want to do by using a single configure run and the ExternalProject functionality. You just seem to use ${libA_libs}/libA.a, which is of course without any further information. Since the projects libA and libB could come from totally different vendors, there is no way to tell at configure time from your main project that LibA even contains a link dependency libB (which is by incidence also built within libA).

I will assume the following that you want to use the (fairly natural) idea of transitive linking. Depending on whether you develop libA/libB yourself, there are different options. I'll explain the "cleanest" way of doing this. The downside is that this needs modification of the libA project cmake as well. [if you can't do this, you will need to manually add the libB.a as link library hard-coded; i.e. work around transitivity.. sorry!]

The key to transitive linking is that you need to import the actual target created by CMake in libA, which then correctly has the target_link_libraries information to libB and all will work. The target configuration of libA can be provided using the INSTALL(.. EXPORT ) functionality in cmake, which then allows you to import the target using find_package(LIBA CONFIG). then, your exe CMakeLists.txt should look like

find_package(LIBA CONFIG QUIET)
if (NOT LIBA_FOUND)
    AddExternalProject(libA .....)
    return
else()
    add_executable(exe ${sources})
    target_link_libraries(exe <libA-target-name>)

This will include libB at link time as well as set the right include path (if defined PUBLIC in target_include_directories(libA PUBLIC ${LIBA_INCDIR}..)

The key problem is that the addition of an external project at the same configure run of course cannot yield everything that will be set by find_package, as nothing is compiled/installed yet. The idea behind external project is that you tell it where to place stuff so you subsequently know where stuff should be found, but that unfortunately will never include transitive stuff like link libraries.

There are (as always) quicker and more dirty solutions, however, the "new" cmake way of modularizing packages and making them available as targets via find_package in CONFIG mode is the best and uncomplicated way to include cmake-aware projects into each other.

like image 113
daniel.wirtz Avatar answered Oct 23 '22 07:10

daniel.wirtz