Рекурсивный список LINK_LIBRARIES в CMake

Я пытаюсь получить список абсолютных путей ко всем библиотекам, связанным с определенной целью в CMake для использования при вызове add_custom_command. Однако get_target_property(_LINK_LIBRARIES ${TARGET} LINK_LIBRARIES включает только прямые зависимости (т.е. Все, что используется при вызове target_link_libraries(${TARGET} ...)).

Поэтому, если я связываю другую цель CMake, например. mylibrary, список будет включать mylibrary, но как имя и без транзитно связанных библиотек. Поскольку этот список может также включать произвольно сложные выражения генератора, проверка каждого элемента, если он является целью и получение рекурсивно его LINK_LIBRARIES, не является жизнеспособным. Кроме того, цель может быть указана в более поздней точке CMakeLists.txt и if(TARGET mylibrary) будет пропущена.

Для INCLUDE_DIRECTORIES и COMPILE_DEFINITIONS это легко решается, так как оба они ведут себя одинаково, когда используется get_target_property (за исключением того, что связанные цели, очевидно, не входят в список), выражение генератора формы $<TARGET_PROPERTY:${TARGET},INCLUDE_DIRECTORIES> производит требуемый список рекурсивно необходимых включает и определений. Однако $<TARGET_PROPERTY:${TARGET},LINK_LIBRARIES> создает тот же список, что и вариант get_target_property.

Как получить желаемый список абсолютных путей?

Демонстрация:

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)

Вывод:

(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

Ответ 1

Ваше желание уже какое-то время было и, насколько мне известно, еще не было (как для CMake 3.3.2), встроенного в CMake (см. 0012435: возможность получить все библиотеки ссылок для цели?).

У меня появилась надежда, потому что в этом билете перечислены несколько возможных альтернативных подходов. Но после того, как я протестировал их против вашего проекта CMake, я бы сказал, что они на самом деле не являются решением:

  • export_library_dependencies() - Устаревший

    Примечание. Поскольку это работает только для зависимостей Lib-To-Lib, у меня есть - для этого теста - изменил ваш add_executable() на вызов add_library()

    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}")
    

    даст, например,

    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;
    

    См. также политика CMP0033 "Команда export_library_dependencies() не должна называться"

  • export(TARGETS ...)

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

    Но это сохраняет выражения генератора в выходе, и вам нужно добавить в список все зависимые цели, поэтому ничего хорошего.

    См. также политика CMP0024 "Disallow include export result" .

  • GET_PREREQUISITES()

    Я использовал код как использовать функции cmake get_prerequisites и get_filename_component для установки на установку по умолчанию?, но он показывает - как описано в документации модуля - что в нем перечислены только разделяемые библиотеки.

    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()
    

    будет выводиться на моей машине Windows:

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

Ссылки

Ответ 2

Возможно рекурсивное перемещение LINK_LIBRARY.

Вот get_link_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()