execute_process
是 CMake 中的一个命令,用于在 CMake 配置阶段(即运行 cmake
命令时)执行外部进程。它与 add_custom_command
或 add_custom_target
不同,后者是在构建阶段(如 make
或 ninja
)执行命令。execute_process
通常用于获取系统信息、生成代码或处理依赖项。
基本语法
execute_process(
COMMAND <cmd1> [<args1>]
[COMMAND <cmd2> [<args2>] ...]
[WORKING_DIRECTORY <dir>]
[TIMEOUT <seconds>]
[RESULT_VARIABLE <var>]
[OUTPUT_VARIABLE <var>]
[ERROR_VARIABLE <var>]
[INPUT_FILE <file>]
[OUTPUT_FILE <file>]
[ERROR_FILE <file>]
[OUTPUT_QUIET]
[ERROR_QUIET]
[COMMAND_ECHO <where>]
[ENCODING <encoding>]
[ECHO_OUTPUT_VARIABLE]
[ECHO_ERROR_VARIABLE]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE]
)
参数详解
1. COMMAND
(必需)
指定要执行的命令及其参数。可以链式调用多个命令,按顺序执行(类似 Shell 的管道 |
)。
execute_process(
COMMAND echo "Hello"
COMMAND sed "s/Hello/Hi/"
)
# 输出 "Hi"
2. WORKING_DIRECTORY
设置命令执行的工作目录。
execute_process(
COMMAND pwd
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/subdir
)
3. TIMEOUT
设置超时时间(秒),超时后终止进程。
execute_process(
COMMAND sleep 10
TIMEOUT 5 # 5 秒后终止
)
4. RESULT_VARIABLE
保存命令执行的返回值(退出码)。通常 0
表示成功,非 0
表示错误。
execute_process(
COMMAND git rev-parse --verify HEAD
RESULT_VARIABLE git_result
)
if(NOT git_result EQUAL 0)
message(FATAL_ERROR "Git failed!")
endif()
5. OUTPUT_VARIABLE
和 ERROR_VARIABLE
捕获命令的标准输出(stdout
)和标准错误(stderr
)。
execute_process(
COMMAND ls non_existent_file
OUTPUT_VARIABLE out
ERROR_VARIABLE err
)
message("Output: ${out}") # 空
message("Error: ${err}") # 显示错误信息
6. OUTPUT_STRIP_TRAILING_WHITESPACE
删除输出末尾的空白字符(如换行符)。
execute_process(
COMMAND echo "Hello World "
OUTPUT_VARIABLE out
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message("Output: [${out}]") # 输出 "Hello World"
7. INPUT_FILE
, OUTPUT_FILE
, ERROR_FILE
将输入、输出、错误重定向到文件。
execute_process(
COMMAND my_tool
INPUT_FILE input.txt
OUTPUT_FILE output.txt
ERROR_FILE error.log
)
常见用法示例
1. 执行命令并获取输出
execute_process(
COMMAND uname -s
OUTPUT_VARIABLE OS_NAME
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message("OS: ${OS_NAME}") # 如 "Linux", "Darwin"
2. 检查命令是否成功
execute_process(
COMMAND which python3
RESULT_VARIABLE result
OUTPUT_QUIET
)
if(result EQUAL 0)
message("Python3 found")
else()
message(FATAL_ERROR "Python3 not found")
endif()
3. 链式命令(管道)
execute_process(
COMMAND cat ${CMAKE_SOURCE_DIR}/data.txt
COMMAND grep "important"
COMMAND wc -l
OUTPUT_VARIABLE line_count
)
4. 处理错误输出
execute_process(
COMMAND gcc --version
ERROR_VARIABLE gcc_error
OUTPUT_VARIABLE gcc_output
)
if(gcc_error)
message("Error: ${gcc_error}")
else()
message("Output: ${gcc_output}")
endif()
注意事项
- 执行时机:
execute_process
在 CMake 配置阶段立即执行,若命令耗时较长,可能拖慢配置过程。 - 跨平台兼容性:命令在不同平台下可能需要调整(如
cmd.exe
vsbash
)。 - 输出处理:若输出内容较大,建议重定向到文件而非变量。
- 错误处理:始终检查
RESULT_VARIABLE
以确保命令成功。
与 add_custom_command
的区别
execute_process
: 在配置阶段运行,直接影响 CMake 变量和后续流程。add_custom_command
: 在构建阶段运行,生成文件或触发其他构建步骤。
根据需求选择合适的命令!
你提到的执行时机问题非常重要,也是很多 CMake 初学者容易混淆的地方。我们通过一个具体的例子和对比来彻底理解 execute_process
的执行时机。
CMake 的两个阶段
CMake 的工作流程分为两个阶段:
配置阶段 (Configure Time)
- 运行
cmake
命令时发生(如cmake -B build
) - 此时会执行所有
execute_process
命令 - 生成构建系统文件(如
Makefile
或build.ninja
)
- 运行
构建阶段 (Build Time)
- 运行构建命令时发生(如
make
或ninja
) - 此时会执行
add_custom_command
或add_custom_target
中定义的命令
- 运行构建命令时发生(如
例子对比
假设我们有两个 CMake 脚本:
案例 1:使用 execute_process
(配置阶段执行)
# CMakeLists.txt
message("=== 配置阶段开始 ===")
execute_process(
COMMAND echo "正在生成 version.h (配置阶段)"
COMMAND bash -c "echo '#define VERSION \"1.0.0\"' > version.h"
)
add_executable(my_app main.cpp version.h)
message("=== 配置阶段结束 ===")
运行结果:
$ cmake -B build
=== 配置阶段开始 ===
正在生成 version.h (配置阶段) # 立即执行!
=== 配置阶段结束 ===
Generating done.
$ ls build/version.h # 文件已生成
version.h
$ make # 构建阶段不会重新生成 version.h
[100%] Built target my_app
案例 2:使用 add_custom_command
(构建阶段执行)
# CMakeLists.txt
message("=== 配置阶段开始 ===")
add_custom_command(
OUTPUT version.h
COMMAND echo "正在生成 version.h (构建阶段)"
COMMAND bash -c "echo '#define VERSION \"1.0.0\"' > version.h"
)
add_executable(my_app main.cpp version.h)
message("=== 配置阶段结束 ===")
运行结果:
$ cmake -B build
=== 配置阶段开始 ===
=== 配置阶段结束 === # 此时没有生成 version.h
Generating done.
$ ls build/version.h # 文件不存在
ls: cannot access 'build/version.h': No such file or directory
$ make # 构建阶段生成 version.h
[ 50%] Generating version.h
正在生成 version.h (构建阶段)
[100%] Built target my_app
关键差异总结
特性 | execute_process |
add_custom_command |
---|---|---|
执行时机 | 配置阶段 (cmake 运行时) |
构建阶段 (make 或 ninja 运行时) |
执行次数 | 每次运行 cmake 时执行一次 |
每次构建时执行(如果输出需要更新) |
典型用途 | 获取系统信息、预生成配置文件 | 编译时生成代码、处理构建依赖 |
能否影响构建规则 | 不能直接定义构建系统的依赖关系 | 可以定义构建依赖关系 |
什么时候用 execute_process
?
需要立即获取信息
例如:检测编译器特性、查询 Git 提交哈希、检查系统库是否存在。# 在配置阶段获取 Git 提交 ID execute_process( COMMAND git log -1 --format=%h OUTPUT_VARIABLE GIT_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ) configure_file(config.h.in config.h) # 将 GIT_COMMIT_HASH 写入头文件
生成构建所需的初始文件
例如:生成默认配置文件(如果文件不存在)。if(NOT EXISTS "${CMAKE_SOURCE_DIR}/config.json") execute_process( COMMAND cp "${CMAKE_SOURCE_DIR}/config.default.json" "${CMAKE_SOURCE_DIR}/config.json" ) endif()
什么时候用 add_custom_command
?
需要在构建时生成文件
例如:用 Protobuf 生成代码、在编译前预处理资源文件。add_custom_command( OUTPUT generated_code.cpp COMMAND protoc --cpp_out=. my.proto DEPENDS my.proto ) add_executable(app main.cpp generated_code.cpp)
构建时需要动态更新内容
例如:每次构建时更新版本时间戳。add_custom_command( OUTPUT timestamp.h COMMAND bash -c "date +'#define TIMESTAMP \"%Y-%m-%d %H:%M:%S\"' > timestamp.h" ) add_executable(app main.cpp timestamp.h)
易错场景分析
错误:期望 execute_process
在每次构建时运行
# ❌ 错误:这个文件只会在运行 cmake 时生成一次
execute_process(
COMMAND echo "#define BUILD_COUNT 1" > build_count.h
)
# ✅ 正确:使用 add_custom_command 在每次构建时更新
add_custom_command(
OUTPUT build_count.h
COMMAND bash -c "echo '#define BUILD_COUNT $(($(cat build_count.h 2>/dev/null | cut -d' ' -f3) + 1))' > build_count.h"
)
错误:在 execute_process
中生成未跟踪的文件
execute_process(
COMMAND touch some_file.txt # 生成的文件不会被 CMake 自动跟踪
)
# ✅ 正确:显式声明生成的文件
add_custom_command(
OUTPUT some_file.txt
COMMAND touch some_file.txt
)
add_executable(app main.cpp some_file.txt) # 建立依赖关系
总结
execute_process
是 配置阶段的“一次性操作”,适合做初始化工作。- 如果你需要 在构建时动态生成内容 或 定义构建依赖关系,应该使用
add_custom_command
。 - 可以通过以下命令直观观察执行时机:
# 查看配置阶段的输出 cmake -B build # 查看构建阶段的输出 cmake --build build --verbose