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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 10:48:11