CMake中的LINK_LIBRARIES的递归列表

Recursive list of LINK_LIBRARIES in 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_DIRECTORIESCOMPILE_DEFINITIONS,这很容易解决,因为尽管当使用get_target_property时两者的行为类似(除了链接的目标显然不在列表中),形式为$的生成器表达式仍会生成所需的列表递归所需的包括和定义。但是,$产生的列表与get_target_property变体相同。

如何检索所需的绝对路径列表?

示范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)

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

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)

输出:

1
2
3
4
5
6
7
8
(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


据我所知,您的愿望已经存在了一段时间(据我所知)(尚未(对于CMake 3.3.2))嵌入到CMake本身中(请参阅0012435:是否可以获取目标的所有链接库?)。

我有一些希望,因为这张票列出了一些可能的替代方法。但是,在针对您的示例CMake项目测试了这些内容之后,我会说它们并不是真正的解决方案:

  • export_library_dependencies()-已弃用

    注意:因为这仅适用于Lib-To-Lib依赖项,所以对于此测试,我将add_executable()更改为add_library()调用

    1
    2
    3
    4
    5
    6
    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}")

    会给例如

    1
    2
    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 ...)

    1
    2
    3
    4
    5
    6
    7
    cmake_policy(SET CMP0024 OLD)
    export(
        TARGETS A B
        FILE Test.cmake
        NAMESPACE Imp_
    )
    include("${CMAKE_CURRENT_BINARY_DIR}/Test.cmake")

    但这将生成器表达式保留在输出中,您需要将所有依赖的目标添加到列表中,所以效果不好。

    另请参见策略CMP0024"禁止包含导出结果"。

  • GET_PREREQUISITES()

    我已经从如何使用cmake函数get_prerequisites和get_filename_component进行目标依赖安装中获取了代码?但是,如模块文档中所述,它显示仅列出共享库。

    1
    2
    3
    4
    5
    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

    1
    2
    3
    4
    5
    6
    7
    8
    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机器上输出:

    1
    2
    resolved_file='C:/Windows/SysWOW64/KERNEL32.dll'
    resolved_file='C:/Windows/SysWOW64/MSVCR110D.dll'
  • 参考文献

    • 检索CMake中的所有链接标志
    • 获取目标依赖于CMake的所有源文件


    可以递归遍历LINK_LIBRARY属性。

    这是一个get_link_libraries()可以做到的,但是它不能处理所有情况(例如,库不是目标,不是导入的库)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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()