如何在CMake源码分发tarball中解析符号链接?
背景信息
在Ubuntu 22.04上使用CMake V3.29.0构建公司内部服务器用的项目,希望通过CPack生成可分发的.tar.gz压缩包。
问题描述
通过符号链接共享代码目录在多个项目中复用通用代码,日常开发正常,但生成分发包时,tarball里保留的是符号链接,而非实际的.cpp和.hpp源码文件。
项目结构如下(注意符号链接目录):
src/ CMakeLists.txt main.cpp foo.hpp foo.cpp database/ CMakeLists.txt create_db.hpp create_db.cpp infrastructure/ CMakeLists.txt return_codes.hpp runtime_configuration.hpp runtime_configuration.cpp toolbox/ CMakeLists.txt exceptions/ <----- <-- 符号链接到共享目录 CMakeLists.txt ext_std_exception.hpp program_terminate_error.hpp program_terminate_error.cpp work-queue/ <----- <-- 符号链接到共享目录 CMakeLists.txt work_queue.hpp basic_work_queue_processor.hpp basic_work_queue_entry.hpp
执行cpack --config CPackSourceConfig.cmake时,exceptions/和work-queue/目录在.tar.gz中以符号链接形式存在。
相关代码示例
src/toolbox/exceptions目录下的CMakeLists.txt内容:
add_library(exceptions_ext_filesystem_error OBJECT ${CMAKE_CURRENT_LIST_DIR}/ext_filesystem_error.hpp ${CMAKE_CURRENT_LIST_DIR}/ext_filesystem_error.cpp) add_library(exceptions_os_error OBJECT ${CMAKE_CURRENT_LIST_DIR}/os_error.hpp ${CMAKE_CURRENT_LIST_DIR}/os_error.cpp) add_library(exceptions_program_terminate_error OBJECT ${CMAKE_CURRENT_LIST_DIR}/program_terminate_error.hpp ${CMAKE_CURRENT_LIST_DIR}/program_terminate_error.cpp) add_library(ql_toolbox_exceptions $<TARGET_OBJECTS:exceptions_ext_filesystem_error> $<TARGET_OBJECTS:exceptions_os_error> $<TARGET_OBJECTS:exceptions_program_terminate_error> )
父目录的CMakeLists.txt包含语句:
include(${CMAKE_CURRENT_LIST_DIR}/exceptions/CMakeLists.txt)
求助需求
想知道能否配置CMake解析目录符号链接,将其中的源码文件包含到分发包中,而非保留符号链接。网上大多是创建符号链接的资料,反向解析的内容很少。
已尝试方案
以下方法均未成功:
- 使用
install命令的相关配置:install( TARGETS my-project DESTINATION bin ARCHIVE COMPONENT Development NAMELINK_SKIP ) - 修改
CMAKE_INSTALL_MODE为COPY(已是默认设置,无效果) - 设置
CMAKE_FIND_PACKAGE_RESOLVE_SYMLINKS(对该场景无用) include()语句无强制解析符号链接的选项
可选方案
- 寻找
include(...)的DEREFERENCE_DIR_SYMLINK选项(怀疑不存在) - 将共享代码打包为独立共享库(但希望所有代码打包到单个tarball,不想处理库)
- 构建前复制所有源码到新目录并处理符号链接(增加额外步骤)
- 排除tarball中的符号链接目录,在目标机器重新创建(不够优雅)
解决方案
方法1:CPack打包前复制符号链接目录到构建目录
在顶层CMakeLists.txt中添加自定义逻辑,先将符号链接指向的真实目录复制到构建目录,再让CPack打包构建目录下的文件:
# 定义复制符号链接目录的函数 function(copy_symlink_dir src_dir dest_dir) if(IS_SYMLINK ${src_dir}) get_filename_component(real_dir ${src_dir} REALPATH) file(COPY ${real_dir}/ DESTINATION ${dest_dir}) message(STATUS "Copied symlinked directory ${src_dir} -> ${real_dir} to ${dest_dir}") endif() endfunction() # 复制目标符号链接目录到构建目录对应位置 copy_symlink_dir(${CMAKE_SOURCE_DIR}/src/toolbox/exceptions ${CMAKE_BINARY_DIR}/src/toolbox/exceptions) copy_symlink_dir(${CMAKE_SOURCE_DIR}/src/toolbox/work-queue ${CMAKE_BINARY_DIR}/src/toolbox/work-queue) # 配置CPack打包构建目录下的源码 set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}") set(CPACK_SOURCE_GENERATOR "TGZ") set(CPACK_SOURCE_INSTALLED_DIRECTORIES "${CMAKE_BINARY_DIR}/src;/src") # 忽略原源码中的符号链接目录,避免重复打包 set(CPACK_SOURCE_IGNORE_FILES "${CMAKE_SOURCE_DIR}/src/toolbox/exceptions;${CMAKE_SOURCE_DIR}/src/toolbox/work-queue")
方法2:安装阶段直接复制真实文件
如果是打包二进制分发版,可在install阶段直接将符号链接指向的文件复制到安装目录,再由CPack打包:
# 处理exceptions目录 get_filename_component(real_exceptions_dir ${CMAKE_SOURCE_DIR}/src/toolbox/exceptions REALPATH) install(DIRECTORY ${real_exceptions_dir}/ DESTINATION include/toolbox/exceptions FILES_MATCHING PATTERN "*.hpp") install(FILES ${real_exceptions_dir}/*.cpp DESTINATION src/toolbox/exceptions) # 处理work-queue目录 get_filename_component(real_workqueue_dir ${CMAKE_SOURCE_DIR}/src/toolbox/work-queue REALPATH) install(DIRECTORY ${real_workqueue_dir}/ DESTINATION include/toolbox/work-queue FILES_MATCHING PATTERN "*.hpp") install(FILES ${real_workqueue_dir}/*.cpp DESTINATION src/toolbox/work-queue)
方法3:修改子目录解析逻辑
在符号链接目录的父CMakeLists.txt(即src/toolbox/CMakeLists.txt)中,临时切换到真实目录再包含子CMakeLists.txt,让CMake直接处理真实文件:
# 处理exceptions符号链接 if(IS_SYMLINK ${CMAKE_CURRENT_LIST_DIR}/exceptions) get_filename_component(real_exceptions_dir ${CMAKE_CURRENT_LIST_DIR}/exceptions REALPATH) set(ORIGINAL_CURRENT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(CMAKE_CURRENT_SOURCE_DIR ${real_exceptions_dir}) include(${real_exceptions_dir}/CMakeLists.txt) set(CMAKE_CURRENT_SOURCE_DIR ${ORIGINAL_CURRENT_SOURCE_DIR}) else() include(${CMAKE_CURRENT_LIST_DIR}/exceptions/CMakeLists.txt) endif() # 处理work-queue符号链接 if(IS_SYMLINK ${CMAKE_CURRENT_LIST_DIR}/work-queue) get_filename_component(real_workqueue_dir ${CMAKE_CURRENT_LIST_DIR}/work-queue REALPATH) set(ORIGINAL_CURRENT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(CMAKE_CURRENT_SOURCE_DIR ${real_workqueue_dir}) include(${real_workqueue_dir}/CMakeLists.txt) set(CMAKE_CURRENT_SOURCE_DIR ${ORIGINAL_CURRENT_SOURCE_DIR}) else() include(${CMAKE_CURRENT_LIST_DIR}/work-queue/CMakeLists.txt) endif()
此方法让CMake在构建时直接操作真实文件,CPack打包源码时会自动包含这些文件(需确保CPack配置包含对应源码路径)。
内容的提问来源于stack exchange,提问作者1xBadAdvice

