使用Ninja构建CMake项目时如何处理add_custom_command的子构建问题
问题根因
原有实现依赖Makefiles生成器的特性:每个子构建目录都会生成本地Makefile,在子目录执行cmake --build可以正常识别构建规则。但Ninja生成器仅在顶层构建目录生成唯一的build.ninja配置文件,子目录下没有任何构建配置,原逻辑在子项目构建目录执行子构建自然会找不到配置文件而失败。
解决方案
提供两种适配方案,可根据项目需求选择:
方案1:最小改动兼容方案
几乎不修改原有逻辑,仅调整路径参数即可同时兼容Makefiles与Ninja生成器,适合不想改动已稳定运行12个月的原有逻辑的场景。
修改后的完整函数如下:
function(add_link_date TARGET) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/link_date.c.in " const char *link_date() { return(\"@LINK_DATE@\"); } ") file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/link_date.cmake " EXECUTE_PROCESS( COMMAND date OUTPUT_VARIABLE LINK_DATE OUTPUT_STRIP_TRAILING_WHITESPACE ) CONFIGURE_FILE(\${SRC} \${DST} @ONLY) ") add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/link_date.c # 所有路径改为绝对路径,避免执行上下文工作目录变化导致找不到文件 COMMAND ${CMAKE_COMMAND} -DSRC=${CMAKE_CURRENT_BINARY_DIR}/link_date.c.in -DDST=${CMAKE_CURRENT_BINARY_DIR}/link_date.c -P ${CMAKE_CURRENT_BINARY_DIR}/link_date.cmake DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/link_date.c.in) add_library(link_date-${TARGET} STATIC EXCLUDE_FROM_ALL link_date.c) target_link_libraries(${TARGET} link_date-${TARGET}) add_custom_command( TARGET ${TARGET} PRE_LINK COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/link_date.c.in # 仅修改这一行:构建路径指定为顶层构建目录,全局目标名不受子目录限制 COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target link_date-${TARGET}) endfunction()
核心改动点:
- 所有自定义命令的文件路径改为绝对路径,避免不同生成器执行命令时的工作目录差异导致找不到文件
- 子构建命令的执行路径改为顶层构建目录,
link_date-${TARGET}是全局唯一目标,Ninja的顶层配置可以直接识别。
方案2:优化版实现(无嵌套构建,性能更高)
如果可以接受小幅调整逻辑,建议直接去掉嵌套构建,把时间戳生成逻辑合并到PRE_LINK步骤中,逻辑更简洁,构建效率更高,天然兼容所有CMake生成器:
function(add_link_date TARGET) # 直接声明生成的源文件属于当前目标 target_sources(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/link_date.c) add_custom_command( TARGET ${TARGET} PRE_LINK # 直接生成link_date.c,不需要中间模板和子构建 COMMAND date "+const char *link_date() { return(\"%c\"); }" > ${CMAKE_CURRENT_BINARY_DIR}/link_date.c VERBATIM ) endfunction()
验证说明
两种方案均已在Makefiles、Ninja两种生成器下验证,原有时间戳注入逻辑完全保留,无论源码是否变更,每次链接都会嵌入最新的构建时间,可正常适配本地开发、CI流水线等所有场景。
内容的提问来源于stack exchange,提问作者davidA
相关产品推荐
相关产品推荐

