20

I am trying to acquire a list of the absolute paths to all libraries linked to a specific target in CMake for use in a call to add_custom_command. However, get_target_property(_LINK_LIBRARIES ${TARGET} LINK_LIBRARIES only includes the direct dependencies (i.e. anything that is used in a target_link_libraries(${TARGET} ...) call).

Therefore, if I link another CMake target, e.g. mylibrary, the list would include mylibrary, but as a name only and without transitively linked libraries. As this list can also include arbitrarily complex generator expressions, checking each item if it is a target and retrieving its LINK_LIBRARIES recursively is not viable. Furthermore the target could be specified at a later point in the CMakeLists.txt and if(TARGET mylibrary) would be skipped.

For INCLUDE_DIRECTORIES and COMPILE_DEFINITIONS this is easily solved, as although both behave similarly when get_target_property is used (except that linked targets are obviously not in the list), a generator expression of the form $<TARGET_PROPERTY:${TARGET},INCLUDE_DIRECTORIES> produces the desired list of recursively required includes and definitions. However, $<TARGET_PROPERTY:${TARGET},LINK_LIBRARIES> produces the same list as the get_target_property variant.

How can I retrieve the desired list of absolute paths?

Demonstration:

cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)

file(WRITE a.cpp "void foo() {};\n")
file(WRITE b.cpp "int main(int, char**) { return 0; }\n")

find_package(Boost REQUIRED COMPONENTS filesystem system)

add_library(A STATIC a.cpp)
target_include_directories(A PUBLIC ${Boost_INCLUDE_DIRS})
target_link_libraries(A PUBLIC ${Boost_LIBRARIES})

# demonstrates (at configure time) that the LINK_LIBRARIES property can contain
# arbitrary generator expressions, making a recursive solution infeasible
get_target_property(A_LINK_LIBRARIES A LINK_LIBRARIES)
message(STATUS "A LINK_LIBARIES: ${A_LINK_LIBRARIES}")

add_executable(B b.cpp b_lists)
target_link_libraries(B PRIVATE A)
target_include_directories(B PRIVATE .)

get_target_property(B_INCLUDE_DIRECTORIES B INCLUDE_DIRECTORIES)
get_target_property(B_LINK_LIBRARIES B LINK_LIBRARIES)

# demonstrates (at compile time) that method 1 is not recursive while method 2 is (for INCLUDE_DIRECTORIES)
# demonstrates (at compile time) that the library list is never recursive
add_custom_command(
    OUTPUT b_lists
    COMMAND ${CMAKE_COMMAND} -E echo "B INCLUDE_DIRECTORIES 1: ${B_INCLUDE_DIRECTORIES}"
    COMMAND ${CMAKE_COMMAND} -E echo "B INCLUDE_DIRECTORIES 2: $<TARGET_PROPERTY:B,INCLUDE_DIRECTORIES>"
    COMMAND ${CMAKE_COMMAND} -E echo "B LINK_LIBRARIES 1: ${B_LINK_LIBRARIES}"
    COMMAND ${CMAKE_COMMAND} -E echo "B LINK_LIBRARIES 2: $<TARGET_PROPERTY:B,LINK_LIBRARIES>"
    DEPENDS A
)
set_source_files_properties(b_lists PROPERTIES SYMBOLIC TRUE)

Output:

(configure)
A LINK_LIBARIES: $<$<NOT:$<CONFIG:DEBUG>>:D:/libs/boost-1_55_0/lib/boost_filesystem-vc110-mt-1_55.lib>;$<$<CONFIG:DEBUG>:D:/libs/boost-1_55_0/lib/boost_filesystem-vc110-mt-gd-1_55.lib>;$<$<NOT:$<CONFIG:DEBUG>>:D:/libs/boost-1_55_0/lib/boost_system-vc110-mt-1_55.lib>;$<$<CONFIG:DEBUG>:D:/libs/boost-1_55_0/lib/boost_system-vc110-mt-gd-1_55.lib>
(build)
Generating b_lists
B INCLUDE_DIRECTORIES 1: D:/projects/cmakeminimal/.
B INCLUDE_DIRECTORIES 2: D:/projects/cmakeminimal/.;D:/libs/boost-1_55_0/include/boost-1_55
B LINK_LIBRARIES 1: A
B LINK_LIBRARIES 2: A
Joe
  • 6,497
  • 4
  • 29
  • 55
  • @usr1234567 please read the question properly as it already states the two approaches I have tried and why they do not work. – Joe Sep 24 '15 at 08:12
  • You are right, sorry. I wanted to see some code but it wouldn't help. – usr1234567 Sep 24 '15 at 08:14
  • Yes, I could try making a minimal example but it didn't seem all that useful. – Joe Sep 24 '15 at 08:21
  • `checking each item if it is a target and retrieving its LINK_LIBRARIES recursively is not viable.` - Why checking each item is not vialable? As I understand, generator expressions are evaluated on build time, so them cannot be evaluated to target names, which are known only at configure time. `Furthermore the target could be specified at a later point in the CMakeLists.txt and if(TARGET mylibrary) would be skipped.` - All targets used in `target_link_libraries` should exist at the call time. – Tsyvarev Sep 24 '15 at 10:00
  • @Tsyvarev `cannot be evaluated to target names`, wrong, they absolutely can. try linking to `$<$:A>` in the minimal exampe. Furthermore, and the more serious issue, not all generator exprression are valid in `add_custom_command`. `should exist at the call time`, should, maybe, have to, definitely not. Move lines 8-15 of the example to the end of the file and observe how the result is exactly the same. – Joe Sep 24 '15 at 10:35
  • @Joe: Yes, you are right: generator expressions can express target name. Or, even *part* of the target name (e.g. `<$<$CONFIG:Debug>:A>A` is recognized as `AA` target for `Debug` configuration). So matching libraries to targets names would be very difficult thing. – Tsyvarev Sep 24 '15 at 17:13

2 Answers2

12

Recursively traversing LINK_LIBRARY property is possible.

Here is a get_link_libraries() that does that, however it does not handle all cases (e.g. libraries not being a target, not imported libraries).

function(get_link_libraries OUTPUT_LIST TARGET)
    get_target_property(IMPORTED ${TARGET} IMPORTED)
    list(APPEND VISITED_TARGETS ${TARGET})
    if (IMPORTED)
        get_target_property(LIBS ${TARGET} INTERFACE_LINK_LIBRARIES)
    else()
        get_target_property(LIBS ${TARGET} LINK_LIBRARIES)
    endif()
    set(LIB_FILES "")
    foreach(LIB ${LIBS})
        if (TARGET ${LIB})
            list(FIND VISITED_TARGETS ${LIB} VISITED)
            if (${VISITED} EQUAL -1)
                get_target_property(LIB_FILE ${LIB} LOCATION)
                get_link_libraries(LINK_LIB_FILES ${LIB})
                list(APPEND LIB_FILES ${LIB_FILE} ${LINK_LIB_FILES})
            endif()
        endif()
    endforeach()
    set(VISITED_TARGETS ${VISITED_TARGETS} PARENT_SCOPE)
    set(${OUTPUT_LIST} ${LIB_FILES} PARENT_SCOPE)
endfunction()
Paweł Bylica
  • 3,780
  • 1
  • 31
  • 44
  • 1
    Does not work if `INTERFACE_LINK_LIBRARIES` contains a generator expressions `$<$:CONAN_LIB::fmt_fmt_RELEASE;>` since its a list itself... – Gabriel Dec 23 '21 at 16:54
11

Your wish has been out there for a while and is - as far as I know - not yet (as for CMake 3.3.2) embedded into CMake itself (see 0012435: Possibility to get all link libraries for a target?).

I got some hope because this ticket lists a few possible alternative approaches. But after I tested those against your example CMake project I would say they are not really a solution:

  1. export_library_dependencies() - Deprecated

    Note: Because this works only for Lib-To-Lib dependencies I have - for this test - changed your add_executable() to an add_library() call

    cmake_policy(SET CMP0033 OLD)
    export_library_dependencies(LibToLibLinkDependencies.cmake)
    include("${CMAKE_CURRENT_BINARY_DIR}/LibToLibLinkDependencies.cmake")
    
    message("A_LIB_DEPENDS: ${A_LIB_DEPENDS}")
    message("B_LIB_DEPENDS: ${B_LIB_DEPENDS}")
    

    would give e.g.

    A_LIB_DEPENDS: optimized;../libboost_filesystem-vc110-mt-1_53.lib;debug;../libboost_filesystem-vc110-mt-gd-1_53.lib;...
    B_LIB_DEPENDS: general;A;
    

    See also policy CMP0033 "The export_library_dependencies() command should not be called"

  2. export(TARGETS ...)

    cmake_policy(SET CMP0024 OLD)
    export(
        TARGETS A B
        FILE Test.cmake 
        NAMESPACE Imp_
    )
    include("${CMAKE_CURRENT_BINARY_DIR}/Test.cmake")
    

    But this keeps the generator expressions in the output and you need add to the list all depending targets, so no good.

    See also policy CMP0024 "Disallow include export result".

  3. GET_PREREQUISITES()

    I've taken the code from how to use the cmake functions get_prerequisites and get_filename_component for target dependency installation?, but it shows - as described in the module's documentation - that it lists only the shared libraries.

    add_custom_command(
        OUTPUT b_lists
        APPEND
        COMMAND ${CMAKE_COMMAND} -D MY_BINARY_LOCATION="$<TARGET_FILE:B>" -P "${CMAKE_CURRENT_LIST_DIR}/ListSharedLibDependencies.cmake"
    )
    

    ListSharedLibDependencies.cmake

    include(GetPrerequisites)
    
    get_prerequisites(${MY_BINARY_LOCATION} DEPENDENCIES 0 0 "" "")
    
    foreach(DEPENDENCY_FILE ${DEPENDENCIES})
        gp_resolve_item("${MY_BINARY_LOCATION}" "${DEPENDENCY_FILE}" "" "" resolved_file)
        message("resolved_file='${resolved_file}'")
    endforeach()
    

    would output on my Windows machine:

    resolved_file='C:/Windows/SysWOW64/KERNEL32.dll'
    resolved_file='C:/Windows/SysWOW64/MSVCR110D.dll'
    

References

Community
  • 1
  • 1
Florian
  • 39,996
  • 9
  • 133
  • 149
  • The last approach also only works for dlls that are already found in the default paths as far as I can tell, making it even less useful. In fact on of my use cases is pre-filling the list of directories passed to `gp_resolve_items` with useful defaults. – Joe Sep 25 '15 at 12:42
  • 1
    Seeing that something so important is on the backlog is depressing. – Joe Sep 25 '15 at 12:43
  • And as "it's currently not possible" is apparently the answer I will accept this for now. – Joe Sep 25 '15 at 12:47
  • @Joe Yes, it could be depressing. Regarding your problem: would it be an option to put all the libraries and CMake outputs into defined (sub-)folder under binary outputs? Then you would know where to search for them (see e.g. [here](http://stackoverflow.com/questions/3742090/custom-directory-for-cmake-library-output), [here](http://stackoverflow.com/questions/6120511/cmake-use-add-custom-command-to-copy-binary-to-specific-location-failed-when-lo) or [here](http://stackoverflow.com/questions/32414587/parent-cmakelists-txt-overwriting-child-cmakelists-txt-output-directory-options)). – Florian Sep 25 '15 at 13:49
  • No, the main problem is with external libraries, the internal ones are already installed/built in the correct location. – Joe Sep 25 '15 at 14:04
  • @Joe Just wanted to let you know that I found a solution for makefile targets (see [here](http://stackoverflow.com/questions/34165365/retreive-all-link-flags-in-cmake)). Something build-in would still be preferable, but maybe it helps. – Florian Dec 14 '15 at 21:13
  • 1
    @Joe Just wanted to let you know that [this issue](https://gitlab.kitware.com/cmake/cmake/issues/12435) just got revived. Maybe you want to add something to the [ongoing discussion](https://www.mail-archive.com/cmake-developers@cmake.org/msg17419.html). – Florian Nov 22 '16 at 20:44
  • I'm confused. Isn't something like this a basic requirement for creating packages which other projects can find via find_package? What good is it to be able to create a package if I cannot tell client projects what libraries are required in order to link against mine? Or am I missing something here? Surely I can usefully create packages that have non-trivial external library dependencies... – Ben Farmer Nov 22 '17 at 13:00
  • @BenFarmer Packages will just define their link dependencies via the [`INTERFACE_LINK_LIBRARIES`](https://cmake.org/cmake/help/latest/prop_tgt/INTERFACE_LINK_LIBRARIES.html#prop_tgt:INTERFACE_LINK_LIBRARIES) target property. – Florian Nov 22 '17 at 13:10