CMake如何实现自定义目标构建后编译动态生成源文件生成目标文件
问题根因
两次构建才能完成编译的本质是CMake执行阶段的特性导致的:
- 配置阶段会执行
file(GLOB_RECURSE)、add_library、target_sources这类命令,此时构建流程还未启动,动态生成的源文件不存在,自然无法被枚举加入编译列表 - 构建阶段才会执行
add_custom_command中定义的生成逻辑,产出源文件,但此时编译依赖图已经在配置阶段固定,第一次构建只会跑完源文件生成步骤,不会编译刚产出的文件;第二次构建时配置阶段才能枚举到已经存在的源文件,将其纳入编译流程。
可行实现方案(单次构建完成全流程)
核心逻辑:把「枚举生成的源文件」「编译生成的源文件」动作全部从配置阶段移到构建阶段执行,不在配置阶段做任何依赖未生成文件的操作,全程不需要触发二次配置或二次构建。
具体实现步骤如下:
1. 编写两个辅助CMake脚本
第一个脚本用于源文件生成完成后,枚举输出目录下的所有.c文件,写入源文件清单:
# gen_source_list.cmake # 入参1:生成源文件的目录 入参2:源文件清单输出路径 file(GLOB c_files "${ARGV0}/*.c") string(REPLACE ";" "\n" file_content "${c_files}") file(WRITE ${ARGV1} "${file_content}")
第二个脚本用于读取源文件清单,逐个编译生成对象文件,最后输出对象文件清单供链接使用:
# compile_generated_src.cmake # 入参:GEN_SRC_DIR 生成源目录, OBJ_OUT_DIR 对象文件输出目录, TARGET_CC C编译器路径, TARGET_CFLAGS 编译选项, OBJ_LIST_FILE 对象清单输出路径 file(GLOB GENERATED_C_SRCS "${GEN_SRC_DIR}/*.c") set(OBJ_FILES "") foreach(src_path ${GENERATED_C_SRCS}) get_filename_component(src_base ${src_path} NAME_WE) set(obj_path "${OBJ_OUT_DIR}/${src_base}.c.o") execute_process( COMMAND ${TARGET_CC} ${TARGET_CFLAGS} -c ${src_path} -o ${obj_path} RESULT_VARIABLE compile_ret ) if(NOT compile_ret EQUAL 0) message(FATAL_ERROR "Compile generated source failed: ${src_path}") endif() list(APPEND OBJ_FILES ${obj_path}) endforeach() string(REPLACE ";" "\n" obj_content "${OBJ_FILES}") file(WRITE ${OBJ_LIST_FILE} "${obj_content}")
2. 修改主CMakeLists.txt逻辑
替换原有生成、枚举、编译逻辑,代码如下:
# 统一配置路径变量 set(GEN_SRC_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/out_dir) set(GEN_SRC_LIST_FILE ${GEN_SRC_OUT_DIR}/source_list.txt) set(GEN_OBJ_LIST_FILE ${CMAKE_CURRENT_BINARY_DIR}/generated_obj_list.txt) # 定义源文件生成命令 add_custom_command( OUTPUT ${GEN_SRC_LIST_FILE} COMMAND ${CMAKE_COMMAND} -E make_directory ${GEN_SRC_OUT_DIR} # 执行原有的源文件生成逻辑 COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_CURRENT_SOURCE_DIR} start.bat -i test.ini -o ${GEN_SRC_OUT_DIR} # 生成完成后枚举所有.c文件写入源清单 COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/gen_source_list.cmake ${GEN_SRC_OUT_DIR} ${GEN_SRC_LIST_FILE} # 绑定生成逻辑的依赖输入,输入变更时才会重新生成 DEPENDS test.ini start.bat VERBATIM ) add_custom_target(gen_src DEPENDS ${GEN_SRC_LIST_FILE}) # 定义编译生成源文件的命令 add_custom_command( OUTPUT ${GEN_OBJ_LIST_FILE} COMMAND ${CMAKE_COMMAND} -DGEN_SRC_DIR=${GEN_SRC_OUT_DIR} -DOBJ_OUT_DIR=${CMAKE_CURRENT_BINARY_DIR} -DTARGET_CC=${CMAKE_C_COMPILER} # 继承主目标的编译选项,可根据需求追加自定义宏、头文件路径等参数 -DTARGET_CFLAGS=$<JOIN:$<TARGET_PROPERTY:mainlib,COMPILE_OPTIONS>;$<TARGET_PROPERTY:mainlib,INCLUDE_DIRECTORIES>,$<SEMICOLON>> -DOBJ_LIST_FILE=${GEN_OBJ_LIST_FILE} -P ${CMAKE_CURRENT_SOURCE_DIR}/compile_generated_src.cmake DEPENDS gen_src ${GEN_SRC_LIST_FILE} VERBATIM ) # 将生成的对象文件链接到主目标 add_dependencies(mainlib ${GEN_OBJ_LIST_FILE}) # 利用链接器响应文件语法,直接读取对象清单完成链接 target_link_libraries(mainlib PRIVATE @${GEN_OBJ_LIST_FILE})
方案说明
- 全流程动作均在构建阶段执行:首次构建时会按顺序执行「生成源文件→枚举源文件→编译源文件→链接对象文件」,不需要二次构建
- 不会在配置阶段生成源文件,仅当生成逻辑的依赖(test.ini、start.bat)变更时,才会重新触发源文件生成和对应编译,无重复生成问题
- 适配单配置、多配置生成器(Makefile、Ninja、Visual Studio、Xcode均可),多配置场景只需在编译参数中追加配置相关的生成器表达式(如
$<IF:$<CONFIG:Debug>,-g,-O2>)即可 - 不要使用
file(GLOB_RECURSE CONFIGURE_DEPENDS)方案,该方案本质是检测到文件变化后触发重配置,仍然需要两次构建流程才能完成编译,不符合需求。
内容的提问来源于stack exchange,提问作者deb
相关产品推荐
相关产品推荐

