CMake execute_process用法详解

发布于:2025-04-22 ⋅ 阅读:(16) ⋅ 点赞:(0)

execute_process 是 CMake 中的一个命令,用于在 CMake 配置阶段(即运行 cmake 命令时)执行外部进程。它与 add_custom_commandadd_custom_target 不同,后者是在构建阶段(如 makeninja)执行命令。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_VARIABLEERROR_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()

注意事项

  1. 执行时机execute_process 在 CMake 配置阶段立即执行,若命令耗时较长,可能拖慢配置过程。
  2. 跨平台兼容性:命令在不同平台下可能需要调整(如 cmd.exe vs bash)。
  3. 输出处理:若输出内容较大,建议重定向到文件而非变量。
  4. 错误处理:始终检查 RESULT_VARIABLE 以确保命令成功。

add_custom_command 的区别

  • execute_process: 在配置阶段运行,直接影响 CMake 变量和后续流程。
  • add_custom_command: 在构建阶段运行,生成文件或触发其他构建步骤。

根据需求选择合适的命令!


你提到的执行时机问题非常重要,也是很多 CMake 初学者容易混淆的地方。我们通过一个具体的例子和对比来彻底理解 execute_process 的执行时机。


CMake 的两个阶段

CMake 的工作流程分为两个阶段:

  1. 配置阶段 (Configure Time)

    • 运行 cmake 命令时发生(如 cmake -B build
    • 此时会执行所有 execute_process 命令
    • 生成构建系统文件(如 Makefilebuild.ninja
  2. 构建阶段 (Build Time)

    • 运行构建命令时发生(如 makeninja
    • 此时会执行 add_custom_commandadd_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 运行时) 构建阶段 (makeninja 运行时)
执行次数 每次运行 cmake 时执行一次 每次构建时执行(如果输出需要更新)
典型用途 获取系统信息、预生成配置文件 编译时生成代码、处理构建依赖
能否影响构建规则 不能直接定义构建系统的依赖关系 可以定义构建依赖关系

什么时候用 execute_process

  1. 需要立即获取信息
    例如:检测编译器特性、查询 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 写入头文件
    
  2. 生成构建所需的初始文件
    例如:生成默认配置文件(如果文件不存在)。

    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

  1. 需要在构建时生成文件
    例如:用 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)
    
  2. 构建时需要动态更新内容
    例如:每次构建时更新版本时间戳。

    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
    

网站公告

今日签到

点亮在社区的每一天
去签到