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
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:
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"
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".
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
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()
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