You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在CMake中复现$<TARGET_RUNTIME_DLLS>的跨平台依赖解析?

核心问题

生成器表达式$<TARGET_RUNTIME_DLLS:..>如何在生成时递归遍历所有链接目标?能否在CMake中复现该行为?

项目背景

基于CMake的中型C++闭源项目,包含核心共享库、单元/集成测试、示例程序及自研授权工具,需跨Linux多发行版和Windows的Release/Debug配置构建。依赖管理采用Conan+CMakeDeps/CMakeToolchain集成,可通过自定义conanfile.py按需关闭模块减少依赖;使用CPack生成可分发安装包。

依赖问题

多数依赖为开源库,由Conan管理或自带CMake包配置,部分需构建为共享库。Conan生成的包会创建含生成器表达式的INTERFACE IMPORTED目标,相关属性(如INTERFACE_LINK_LIBRARIES、IMPORTED_LOCATION)在配置阶段无法解析。

运行时输出目录的依赖

Windows下可通过$<TARGET_RUNTIME_DLLS>复制所有传递依赖DLL到运行时目录,但Linux无类似$<TARGET_RUNTIME_SO>表达式。

可移植测试包的依赖

Linux下需将目标的所有传递共享库依赖复制到CMAKE_LIBRARY_OUTPUT目录,但自定义递归遍历依赖树的CMake函数因生成器表达式无法解析而失效。

安装阶段的依赖

CPack打包时,Windows可通过$<TARGET_RUNTIME_DLLS>打包CMake已知的依赖,但Linux无对应方式。

已尝试的无效方案
  • file(GET_RUNTIME_DEPENDENCIES ...):返回大量不必要的系统库;
  • install(IMPORTED_RUNTIME_ARTIFACTS ...):需明确指定共享库目标,无法处理传递依赖;
  • install(TARGETS <targets..> RUNTIME_DEPENDENCIES ...):同前,包含过多系统库;
  • BundleUtilities的fixup_bundle(...):仅适用于可执行文件,适配Linux困难且包含系统库;
  • include(InstallRequiredSystemLibraries):仅包含通用系统库;
  • $<GENEX_EVAL:...>:无法为配置阶段函数解析生成器表达式。
解决方案与原理

$<TARGET_RUNTIME_DLLS>的工作原理

该表达式在生成阶段解析目标的完整传递依赖链,自动筛选出符合条件的共享库(Windows DLL):

  1. 遍历直接/间接链接的所有目标(包括IMPORTED目标);
  2. 过滤系统库(通过CMake内部标记或IMPORTED_NO_SYSTEM属性);
  3. 返回所有符合条件的库文件路径,无需手动递归。

Linux下复现该行为的实现

1. 运行时输出目录复制(生成阶段)

利用add_custom_command在生成阶段执行递归复制逻辑,此时生成器表达式已展开:

function(copy_runtime_deps target)
  add_custom_command(TARGET ${target} POST_BUILD
    COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/copy_runtime_deps.cmake"
            $<TARGET_FILE_DIR:${target}>
            $<TARGET_FILE:${target}>
    COMMENT "Copying runtime dependencies for ${target}"
  )
endfunction()

配套的copy_runtime_deps.cmake脚本(生成阶段执行,可解析所有目标属性):

set(dest_dir ${ARGV0})
set(target_path ${ARGV1})

function(install_deps target dest)
  get_target_property(linked ${target} LINK_LIBRARIES)
  if(linked)
    foreach(lib IN LISTS linked)
      if(TARGET ${lib})
        get_target_property(type ${lib} TYPE)
        get_target_property(imported ${lib} IMPORTED)
        get_target_property(no_system ${lib} IMPORTED_NO_SYSTEM)
        # 筛选非系统的共享库依赖
        if((type STREQUAL "SHARED_LIBRARY" OR type STREQUAL "MODULE_LIBRARY") AND imported AND no_system)
          set(lib_path $<TARGET_FILE:${lib}>)
          execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different ${lib_path} ${dest})
          # 递归处理依赖的依赖
          install_deps(${lib} ${dest})
        endif()
      endif()
    endforeach()
  endif()
endfunction()

# 获取目标对应的CMake目标名称(从路径反推)
get_filename_component(target_name ${target_path} NAME_WE)
install_deps(${target_name} ${dest_dir})

使用方式:

copy_runtime_deps(your_executable)

2. 安装与CPack打包(安装阶段)

使用install(CODE)在安装阶段执行递归安装逻辑,避免配置阶段解析限制:

install(CODE "
  function(install_runtime_deps target dest)
    get_target_property(linked_targets \${target} LINK_LIBRARIES)
    if(linked_targets)
      foreach(linked IN LISTS linked_targets)
        if(TARGET \${linked})
          get_target_property(type \${linked} TYPE)
          get_target_property(imported \${linked} IMPORTED)
          get_target_property(no_system \${linked} IMPORTED_NO_SYSTEM)
          if((type STREQUAL \"SHARED_LIBRARY\" OR type STREQUAL \"MODULE_LIBRARY\") AND imported AND no_system)
            install(FILES \$<TARGET_FILE:\${linked}> DESTINATION \${dest})
            install_runtime_deps(\${linked} \${dest})
          endif()
        endif()
      endforeach()
    endif()
  endfunction()
  install_runtime_deps(your_executable \${CMAKE_INSTALL_BINDIR})
")

关键注意事项

  • 必须在生成/安装阶段执行递归逻辑,此时生成器表达式已完全展开,目标属性可正常访问;
  • 通过IMPORTED_NO_SYSTEM属性过滤系统库,确保只复制项目引入的第三方依赖;
  • 若依赖目标未设置IMPORTED_NO_SYSTEM,可手动添加:set_target_properties(dep_target PROPERTIES IMPORTED_NO_SYSTEM ON)。

内容的提问来源于stack exchange,提问作者GulaschLulatsch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:57:35