关键看生成文件用途:编译前需的(如头文件)用output+add_dependencies;构建后操作(如打包)用post_build(仅限add_custom_target)。byproducts适用于无确定输出的场景,如linter或动态路径生成。

add_custom_command 的两种执行时机怎么选
关键看你要生成的文件是编译前需要的(比如头文件、协议缓冲区代码),还是构建后要做的(比如打包、压缩、校验)。add_custom_command 本质只有两类触发方式:OUTPUT 和 PRE_BUILD/POST_BUILD,但后者仅对 add_custom_target 有效,不能直接挂到库或可执行目标上。
常见错误是把生成 proto.h 的命令写成 COMMAND + POST_BUILD —— 这会导致编译器在第一次编译时找不到头文件而失败。正确做法是用 OUTPUT 声明产出,并让 CMake 知道这个输出是某个源文件的依赖:
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.pb.h
COMMAND protoc --cpp_out=${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/msg.proto
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/msg.proto
COMMENT "Generating protobuf headers"
)
然后必须显式告诉目标它依赖这个输出:
add_executable(myapp main.cpp)
add_dependencies(myapp ${CMAKE_CURRENT_BINARY_DIR}/generated.pb.h)
target_sources(myapp PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated.pb.h)
为什么 add_custom_target 配合 BYPRODUCTS 更适合“伪生成”场景
当命令不产生真实文件(比如运行 linter、触发 CI 通知),或产出文件路径动态不可知(如 git describe 写入版本字符串到临时变量),就不能靠 OUTPUT 触发重执行。BYPRODUCTS 是更安全的替代:它声明“这个命令可能生成这些文件”,但不强制要求它们存在;CMake 会把它当作构建图中的一个节点,支持 add_dependencies,且不会因缺失 byproduct 报错。
典型用法:
-
BYPRODUCTS必须配合OUTPUT或COMMAND使用,单独写无效 - 若命令实际没生成对应文件,CMake 不报错,但下次构建仍会执行(除非加
VERBATIM+ 手动缓存) - 不要把
BYPRODUCTS当作“忽略输出检查”的捷径——它解决的是“产出不确定”问题,不是“不想管输出”
add_custom_target(update_version
COMMAND ${CMAKE_COMMAND} -E echo "$$(${GIT_EXECUTABLE} describe --tags)" > ${CMAKE_CURRENT_BINARY_DIR}/VERSION
BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/VERSION
COMMENT "Updating VERSION file"
)
COMMAND 中的路径和变量为什么总出错
CMake 在 add_custom_command 里展开变量是“惰性的”:所有 ${...} 在配置阶段(cmake ..)就被替换成绝对路径,而 $<...></...> 是生成器表达式,延迟到构建阶段(make/ninja)才求值。混用就会崩溃。
常见陷阱:
- 在
COMMAND里写${CMAKE_SOURCE_DIR}/script.py→ 没问题,但若脚本本身要读相对路径参数,就失效 - 想传当前构建配置(Debug/Release)给命令?必须用
$<config></config>,写成${CMAKE_BUILD_TYPE}会变成空或错误值 - Windows 上用
cmd.exe /c调命令时,%CD%不可靠,应统一用$<xxx></xxx>或$<current_source_dir></current_source_dir>
正确示例(跨平台获取当前源目录并传给 Python 脚本):
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/config.json
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/gen_config.py
$<current_source_dir>
$<config>
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/gen_config.py
)</config></current_source_dir>
自定义目标生成器(generator expression)的边界在哪
生成器表达式($<...></...>)不是万能的。它只能用于 CMake 已知上下文:比如 target_compile_definitions、source_group、add_custom_command(COMMAND)。但它不能出现在 if()、set() 或 shell 命令内部(除非该命令本身由 CMake 解析,如 COMMAND ${CMAKE_COMMAND} -E ...)。
容易被忽略的限制:
-
$<mylib></mylib>只在 target-aware 上下文有效,不能塞进add_custom_command(OUTPUT ...)的文件名里 -
$<...></...><semicolon></semicolon> -
<...></...>
<mylib></mylib>
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











