CMake自定义命令构建macOS通用静态库失败,求原因及修正方案
问题描述
我维护一个基于CMake的C++项目,依赖第三方Rust库。使用该库前需先编译为静态C库;由于要生成macOS通用二进制文件,需要分别编译arm64和x86_64架构版本,再通过lipo合并为通用静态库。
我曾用三个独立CMake自定义目标完成各步骤(构建两个架构的库、合并)并声明依赖,方案可行,但希望移除可能让用户困惑的中间目标。当前采用自定义命令的方案如下:
set (RESVG_LIB_OUTPUT_DIR ${CMAKE_BINARY_DIR}/resvg) set (RESVG_LIB_OUTPUT ${RESVG_LIB_OUTPUT_DIR}/libresvg.a) add_custom_command ( WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Ext/resvg/c-api COMMAND ${CMAKE_COMMAND} -E env MACOSX_DEPLOYMENT_TARGET=10.7 cargo build --release --target aarch64-apple-darwin --target-dir ${RESVG_LIB_OUTPUT_DIR} OUTPUT ${RESVG_LIB_OUTPUT_DIR}/aarch64-apple-darwin/release/libresvg.a) add_custom_command ( WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/Ext/resvg/c-api COMMAND ${CMAKE_COMMAND} -E env MACOSX_DEPLOYMENT_TARGET=10.7 cargo build --release --target x86_64-apple-darwin --target-dir ${RESVG_LIB_OUTPUT_DIR} OUTPUT ${RESVG_LIB_OUTPUT_DIR}/x86_64-apple-darwin/release/libresvg.a) add_custom_command ( DEPENDS ${RESVG_LIB_OUTPUT_DIR}/aarch64-apple-darwin/release/libresvg.a ${RESVG_LIB_OUTPUT_DIR}/x86_64-apple-darwin/release/libresvg.a WORKING_DIRECTORY ${RESVG_LIB_OUTPUT_DIR} COMMAND lipo -create -output libresvg.a aarch64-apple-darwin/release/libresvg.a x86_64-apple-darwin/release/libresvg.a OUTPUT ${RESVG_LIB_OUTPUT}) add_library (resvg STATIC IMPORTED) set_target_properties (resvg PROPERTIES IMPORTED_LOCATION ${RESVG_LIB_OUTPUT}) target_include_directories (resvg INTERFACE ${CMAKE_CURRENT_LIST_DIR}/Ext/resvg/c-api)
当应用目标链接resvg目标时,出现错误:
ninja: error: 'resvg/libresvg.a', needed by 'my_application_target', missing and no known rule to make it
我认为这些自定义命令已定义构建规则,且此前用类似技术自动生成过构建过程中的源文件。另外,我也曾尝试将resvg目标声明为INTERFACE库并链接${RESVG_LIB_OUTPUT},问题依旧。
我的问题:
- 该方案为何无效?
- 是否可修改该方案,仅通过自定义命令实现?(已有基于自定义目标的可行方案,不接受自定义目标相关的解决方案)
原因分析
- IMPORTED库目标不触发自定义命令:
STATIC IMPORTED库的本质是告知CMake“该库文件已存在”,CMake不会主动为其查找或触发生成该文件的自定义命令。即便你通过add_custom_command定义了生成libresvg.a的规则,CMake也不会将规则与IMPORTED库关联,构建系统(如Ninja)自然不知道需要先执行这些命令生成文件。 - INTERFACE库的局限性:用
INTERFACE库链接${RESVG_LIB_OUTPUT}时,CMake仅会把该路径作为链接项,但同样不会自动关联对应的自定义命令——因为没有目标明确依赖这个生成文件的构建规则,构建系统无法感知到需要先执行命令生成它。
修改方案(仅用自定义命令实现)
核心思路是让应用目标直接依赖最终的通用库文件,确保CMake能追踪到生成该文件的完整依赖链,具体有两种实现方式:
方式一:让应用目标直接依赖生成的库文件
在应用目标(如my_application_target)的定义中,添加对${RESVG_LIB_OUTPUT}的依赖:
add_executable(my_application_target ...) # 让应用目标明确依赖生成的通用库,触发自定义命令链 add_dependencies(my_application_target ${RESVG_LIB_OUTPUT}) target_link_libraries(my_application_target PRIVATE resvg)
CMake识别到${RESVG_LIB_OUTPUT}是自定义命令的输出后,会自动触发所有前置自定义命令(编译两个架构的库、合并)。
方式二:将自定义命令输出关联到IMPORTED库
通过add_dependencies把生成文件的依赖链绑定到IMPORTED库,这样所有链接resvg的目标都会间接触发自定义命令:
add_library(resvg STATIC IMPORTED) set_target_properties(resvg PROPERTIES IMPORTED_LOCATION ${RESVG_LIB_OUTPUT} INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/Ext/resvg/c-api ) # 让IMPORTED库目标依赖生成的通用库,间接触发全部自定义命令 add_dependencies(resvg ${RESVG_LIB_OUTPUT})
两种方式的核心都是让CMake的依赖图包含自定义命令的输出节点,确保构建系统能找到生成libresvg.a的规则,按顺序执行编译双架构库、合并的步骤。
内容的提问来源于stack exchange,提问作者PluginPenguin
相关产品推荐
相关产品推荐

