CMake指令:add_custom_command和add_custom_target详解

目录

1.add_custom_command

1.1.简介

1.2.常见使用场景

1.3.注意事项

2.add_custom_target

2.1.简介

2.2.常见使用场景

2.3.注意事项

3.add_custom_command和add_custom_target区别

[3.1. 区别](#3.1. 区别)

3.2.示例说明

4.总结

相关链接


1.add_custom_command

1.1.简介

add_custom_command() 用于定义构建阶段 (如 make/ninja 运行时)执行的自定义命令,通常用于生成文件、预处理资源、或在编译前后执行额外操作(如复制文件、运行脚本等)。它的核心是与构建系统(如 Makefile、Ninja)集成,根据依赖关系自动触发命令执行。

add_custom_command() 有两种主要用法:生成文件 (通过 OUTPUT 定义输出文件)和为目标添加命令 (通过 TARGET 关联目标)。

1.生成文件(最常用)

cpp 复制代码
add_custom_command(
  OUTPUT <output_file1> [output_file2...]  # 命令生成的文件(必填)
  COMMAND <cmd1> [args1...]               # 要执行的命令(必填)
  [MAIN_DEPENDENCY <file>]                # 主要依赖文件(如输入源文件)
  [DEPENDS <dep1> <dep2>...]              # 其他依赖(文件或目标,依赖变化则重新执行)
  [WORKING_DIRECTORY <dir>]               # 命令执行的工作目录
  [COMMENT <message>]                     # 执行时显示的信息(方便调试)
  [VERBATIM]                              # 确保命令参数被正确转义(跨平台安全)
)

2.为目标添加前置 / 后置命令

cpp 复制代码
add_custom_command(
  TARGET <target_name>                    # 关联的目标(如可执行文件、库)
  [PRE_BUILD]                             # 目标构建前执行
  [PRE_LINK]                              # 目标链接前执行(仅部分构建系统支持)
  [POST_BUILD]                            # 目标构建后执行(最常用)
  COMMAND <cmd1> [args1...]               # 要执行的命令(必填)
  [WORKING_DIRECTORY <dir>]
  [COMMENT <message>]
  [VERBATIM]
)

1.2.常见使用场景

1.生成源文件(构建时动态生成代码)

当需要在构建阶段通过脚本(如 Python、Perl)生成源代码或头文件时,add_custom_command() 是核心工具。生成的文件需被其他目标依赖,以触发命令执行。

示例:用 Python 脚本生成头文件

cpp 复制代码
# 定义命令:用 generate_header.py 生成 version.h
add_custom_command(
  OUTPUT ${CMAKE_BINARY_DIR}/version.h  # 输出文件(构建目录下)
  COMMAND python3 ${CMAKE_SOURCE_DIR}/scripts/generate_header.py 
    --version 1.0.0 
    --output ${CMAKE_BINARY_DIR}/version.h
  MAIN_DEPENDENCY ${CMAKE_SOURCE_DIR}/scripts/generate_header.py  # 脚本本身是主要依赖
  DEPENDS ${CMAKE_SOURCE_DIR}/version.txt  # 版本信息文件变化时重新生成
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}    # 在构建目录执行
  COMMENT "生成 version.h..."             # 构建时显示的提示
  VERBATIM                                # 跨平台参数转义(如路径含空格时)
)

# 创建一个目标(如可执行文件),依赖生成的 version.h
add_executable(myapp src/main.cpp ${CMAKE_BINARY_DIR}/version.h)
# 确保 myapp 知道 version.h 的生成目录
target_include_directories(myapp PRIVATE ${CMAKE_BINARY_DIR})
  • generate_header.pyversion.txt 变化时,构建系统会自动重新执行命令生成 version.h
  • 若没有目标依赖 version.h,该命令可能不会执行(需结合 add_custom_target 强制执行,见下文)。

2.为目标添加后置命令(如复制文件、运行测试)

通过 TARGET + POST_BUILD 可在目标(如可执行文件)编译完成后执行命令,例如复制二进制到指定目录、运行验证脚本等。

示例:编译后复制二进制到 bin 目录

cpp 复制代码
add_executable(myapp src/main.cpp)

# 构建 myapp 后,复制到 ${CMAKE_BINARY_DIR}/bin 目录
add_custom_command(
  TARGET myapp
  POST_BUILD  # 编译链接完成后执行
  COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin  # 创建目录
  COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:myapp> ${CMAKE_BINARY_DIR}/bin/  # 复制文件
  COMMENT "复制 myapp 到 bin 目录..."
  VERBATIM
)
  • $<TARGET_FILE:myapp> 是生成器表达式,自动获取 myapp 的二进制文件路径(跨平台兼容)。
  • ${CMAKE_COMMAND} -E 是 CMake 自带的命令行工具,支持 copy/make_directory 等跨平台操作(避免直接用 cp/mkdir,增强兼容性)。

示例:解压文件

cpp 复制代码
set(wrap_BLAS_LAPACK_sources
  ${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.hpp
  ${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxBLAS.cpp
  ${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.hpp
  ${CMAKE_CURRENT_BINARY_DIR}/wrap_BLAS_LAPACK/CxxLAPACK.cpp
)

add_custom_command(
  OUTPUT
    ${wrap_BLAS_LAPACK_sources}
  COMMAND
    ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
  COMMAND
    ${CMAKE_COMMAND} -E touch ${wrap_BLAS_LAPACK_sources}
  WORKING_DIRECTORY
    ${CMAKE_CURRENT_BINARY_DIR}
  DEPENDS
    ${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gz
  COMMENT
    "Unpacking C++ wrappers for BLAS/LAPACK"
  VERBATIM
)

这段代码做了以下几件事:

  1. 定义输出文件列表(解压后预期的文件)
  2. 使用CMake自带的tar命令解压文件
  3. 使用touch命令更新文件时间戳(防止使用旧文件)
  4. 指定工作目录为构建目录
  5. 声明依赖关系(依赖于压缩包文件)
  6. 添加构建时的提示信息
  7. 使用VERBATIM确保命令跨平台兼容

3.结合 add_custom_target 强制执行命令

如果自定义命令生成的文件没有被其他目标依赖(如生成日志、文档),需用 add_custom_target 创建一个目标,并让该目标依赖 add_custom_command 的输出,以确保命令执行。

示例:生成文档(无其他目标依赖)

cpp 复制代码
# 定义生成文档的命令
add_custom_command(
  OUTPUT ${CMAKE_BINARY_DIR}/docs/index.html  # 生成的文档
  COMMAND doxygen ${CMAKE_SOURCE_DIR}/Doxyfile  # 用 doxygen 生成文档
  DEPENDS ${CMAKE_SOURCE_DIR}/Doxyfile ${CMAKE_SOURCE_DIR}/src  # 依赖配置和源码
  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
  COMMENT "生成文档..."
)

# 创建自定义目标,依赖生成的文档
add_custom_target(
  generate_docs ALL  # ALL 表示默认构建时执行(可选)
  DEPENDS ${CMAKE_BINARY_DIR}/docs/index.html  # 依赖上述命令的输出
)
  • ALL 选项让 generate_docs 成为默认构建目标的一部分(运行 make 时自动执行);若不加,需手动运行 make generate_docs

1.3.注意事项

1.依赖关系是核心

构建系统通过 OUTPUTDEPENDS 判断是否需要执行命令。只有当输出文件不存在,或依赖文件(DEPENDS/MAIN_DEPENDENCY)比输出文件新时,命令才会执行。

2.跨平台兼容性

避免直接使用平台特定命令(如 Linux 的 cp、Windows 的 copy),优先用 ${CMAKE_COMMAND} -E 提供的跨平台操作:

  • cmake -E copy <src> <dest>:复制文件
  • cmake -E make_directory <dir>:创建目录
  • cmake -E remove <file>:删除文件

3.add_custom_target 的区别

  • add_custom_command 定义 "命令",需通过依赖触发执行;
  • add_custom_target 定义 "目标"(类似 make 中的伪目标),可直接通过 make <target> 执行,常用来触发 add_custom_command

4.前置命令(PRE_BUILD)的限制

PRE_BUILD 在目标开始构建前执行,但并非所有构建系统都支持(如 Makefile 不支持,Ninja 支持)。若需跨平台的前置操作,建议通过依赖文件的方式间接实现(如让目标依赖一个生成文件的命令)。

2.add_custom_target

2.1.简介

在 CMake 中,add_custom_target() 用于定义构建阶段的自定义目标 (类似 Makefile 中的 "伪目标"),这些目标不对应实际的源文件编译,而是执行用户指定的命令(如生成文档、清理文件、运行测试等)。它是构建系统的 "一等公民",可直接通过 make <target>ninja <target> 调用,也能被其他目标依赖。

基本语法:

cpp 复制代码
add_custom_target(
  <target_name>            # 目标名称(必填)
  [ALL]                    # 可选:是否加入默认构建目标(运行 make 时自动执行)
  [COMMAND <cmd1> [args1...]]  # 要执行的命令(可多个,按顺序执行)
  [DEPENDS <dep1> <dep2>...]  # 依赖项(文件或其他目标,依赖变化时重新执行)
  [WORKING_DIRECTORY <dir>]   # 命令执行的工作目录
  [COMMENT <message>]        # 构建时显示的提示信息
  [VERBATIM]                 # 确保命令参数被正确转义(跨平台安全)
  [BYPRODUCTS <file1> <file2>...]  # 命令生成的副产品文件(CMake 3.9+)
)

2.2.常见使用场景

1.创建独立任务(不参与默认构建)

最常见的用途是定义可手动触发的任务(如生成文档、清理临时文件)。

示例:生成 API 文档

cpp 复制代码
add_custom_target(
  docs                     # 目标名称:make docs 可触发
  COMMAND doxygen Doxyfile  # 执行 doxygen 生成文档
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}  # 在源码目录执行
  DEPENDS Doxyfile src/    # 依赖配置文件和源码目录
  COMMENT "生成 API 文档..."
)
  • 用户可通过 make docsninja docs 手动触发文档生成;
  • Doxyfilesrc/ 目录内容变化,下次执行时会重新生成。

2.加入默认构建(通过 ALL 选项)

若目标需在默认构建时自动执行(如生成必需的资源文件),可添加 ALL 选项。

示例:编译前生成配置文件

cpp 复制代码
add_custom_target(
  generate_config ALL      # 加入默认构建,make 时自动执行
  COMMAND python3 ${CMAKE_SOURCE_DIR}/scripts/generate_config.py  # 生成配置
  OUTPUT ${CMAKE_BINARY_DIR}/config.h  # 生成的配置文件
  DEPENDS ${CMAKE_SOURCE_DIR}/scripts/generate_config.py  # 依赖脚本
  COMMENT "生成配置文件..."
)

# 让可执行文件依赖此目标,确保生成配置文件后再编译
add_executable(myapp src/main.cpp)
add_dependencies(myapp generate_config)  # 依赖 generate_config 目标
  • 运行 make 时,generate_config 会先执行,确保 config.h 存在;
  • add_dependencies() 用于建立目标间的依赖关系。

3.依赖其他目标(联动执行)

自定义目标可依赖其他目标(如可执行文件、库),实现 "构建后操作"。

示例:构建后运行测试

cpp 复制代码
add_executable(mytest test/main.cpp)  # 测试程序

add_custom_target(
  run_tests                    # 目标名称:make run_tests 触发
  COMMAND ${CMAKE_BINARY_DIR}/mytest  # 运行测试程序
  DEPENDS mytest               # 依赖 mytest 目标(确保先编译)
  COMMENT "运行单元测试..."
)
  • 执行 make run_tests 时,CMake 会先确保 mytest 已编译,再运行测试;
  • 常用于自动化测试流程(如 CI/CD 环境)。

4.清理临时文件(结合 clean 目标)

可定义自定义清理目标,配合 make clean 使用。

示例:清理生成的文档

cpp 复制代码
add_custom_target(
  clean_docs                   # 目标名称:make clean_docs 触发
  COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/docs  # 删除 docs 目录
  COMMENT "清理文档..."
)

# 将 clean_docs 添加到默认的 clean 目标中(可选)
add_custom_target(
  clean_all
  COMMAND ${CMAKE_MAKE_PROGRAM} clean  # 执行默认的 clean
  COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/docs  # 额外清理文档
  COMMENT "完全清理..."
)
  • 执行 make clean_docs 可单独清理文档;
  • 执行 make clean_all 可同时执行默认清理和自定义清理。

2.3.注意事项

1.目标名称必须唯一

同一目录下的自定义目标名称不能重复,否则会覆盖或报错。

2.ALL 选项的慎用

添加 ALL 会使目标成为默认构建的一部分,可能延长构建时间(如生成文档、运行测试),建议仅对必需的操作使用 ALL

3.add_custom_command 的配合

若自定义目标需生成文件,通常先用 add_custom_command 定义命令,再用 add_custom_target 触发:

cpp 复制代码
# 定义生成文件的命令
add_custom_command(
  OUTPUT data.txt
  COMMAND echo "data" > data.txt
)

# 定义目标,依赖生成的文件
add_custom_target(
  generate_data
  DEPENDS data.txt  # 触发命令执行
)

4.跨平台命令

避免直接使用平台特定命令(如 rm/del),优先用 ${CMAKE_COMMAND} -E 提供的跨平台操作:

cpp 复制代码
# 跨平台删除目录
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/temp

3.add_custom_command和add_custom_target区别

3.1. 区别

在 CMake 中,add_custom_commandadd_custom_target 都是用于自定义构建流程的命令,但它们的核心定位、触发方式和使用场景有显著区别。

  • add_custom_command 定义 "具体要执行的命令"(如生成文件、复制资源),但它本身不创建可直接调用的目标,需依赖其他条件触发;
  • add_custom_target 定义 "一个可执行的目标 "(类似 Makefile 中的 "伪目标",如 make clean),可直接通过构建命令(如 make mytarget)调用,也可被其他目标依赖。
维度 add_custom_command add_custom_target
本质 定义 "命令"(描述 "做什么") 定义 "目标"(描述 "一个可执行的任务")
是否生成目标 不生成独立目标,无对应的构建条目(如 make XXX 无法直接调用) 生成独立目标,可通过 make <target> 直接执行
触发方式 需通过 "依赖" 触发: 1. 若生成文件(OUTPUT),则被其他目标依赖该文件时触发; 2. 若关联目标(TARGET),则随目标构建(如编译、链接)触发。 需通过 "显式调用" 或 "被其他目标依赖" 触发: 1. 用户手动执行 make <target>; 2. 其他目标通过 DEPENDS 依赖它时,随依赖目标触发。
典型用途 生成文件(如动态生成代码)、为已有目标添加前后置命令(如编译后复制二进制) 创建独立任务(如生成文档、清理日志)、强制触发 add_custom_command 的命令

3.2.示例说明

1.触发方式的核心差异

这是两者最关键的区别:

  • add_custom_command 的命令不会主动执行,必须满足特定依赖条件才会触发;
  • add_custom_target 的目标可以主动执行(用户显式调用),也可被依赖触发。

示例 1:add_custom_command 的触发依赖

假设用 add_custom_command 定义一个生成文件的命令:

cpp 复制代码
# 定义命令:生成 output.txt
add_custom_command(
  OUTPUT output.txt
  COMMAND echo "生成文件" > output.txt
  COMMENT "执行生成命令..."
)

# 只有当其他目标依赖 output.txt 时,上述命令才会执行
add_executable(myapp main.cpp output.txt)  # myapp 依赖 output.txt → 触发命令
  • 若没有 myapp 依赖 output.txt,这个命令永远不会执行;
  • 即使手动运行 make,也不会触发(因为没有目标关联它)。

示例 2:add_custom_target 的主动执行

add_custom_target 定义一个目标:

cpp 复制代码
# 定义目标:打印一条消息
add_custom_target(
  print_msg
  COMMAND echo "这是一个自定义目标"
  COMMENT "执行目标命令..."
)
  • 无需依赖其他目标,用户可直接通过 make print_msg 触发该命令;
  • 若想让它在默认构建中执行,可添加 ALL 选项:add_custom_target(print_msg ALL ...),这样运行 make 时会自动执行。

2.关联性:两者常结合使用

add_custom_command 生成的命令若没有被其他目标依赖,可能永远不会执行。此时可通过 add_custom_target 创建一个目标,让该目标依赖 add_custom_command 的输出,从而强制命令执行。

示例:生成文档(无其他目标依赖时)

cpp 复制代码
# 1. 定义生成文档的命令(生成 docs/index.html)
add_custom_command(
  OUTPUT docs/index.html
  COMMAND doxygen Doxyfile  # 用 doxygen 生成文档
  DEPENDS Doxyfile src/     # 依赖配置文件和源码
  COMMENT "生成文档..."
)

# 2. 定义目标 generate_docs,依赖上述命令的输出
add_custom_target(
  generate_docs
  DEPENDS docs/index.html  # 依赖生成的文件 → 触发上述命令
)
  • 此时,用户运行 make generate_docs 会触发 add_custom_command 的命令,生成文档;
  • 若没有 add_custom_targetadd_custom_command 的命令因无依赖而不会执行。

4.总结

add_custom_command() 是构建阶段自定义流程的核心工具,主要用于:

  • 动态生成源文件 / 资源(通过 OUTPUT);
  • 目标构建前后执行辅助操作(通过 TARGET)。
    其关键是正确设置依赖关系(DEPENDS/OUTPUT),并结合 add_custom_target 确保命令被触发,同时注意跨平台兼容性。

add_custom_target() 是 CMake 中扩展构建流程的核心工具,主要用于:

  • 创建独立任务(如文档生成、测试运行);
  • 定义默认构建时执行的操作(通过 ALL 选项);
  • 实现目标间的依赖关系(如构建后自动执行其他命令)。

核心要点:

  • add_custom_command :专注于 "定义命令逻辑",是 "被动触发" 的(依赖文件或目标),用于生成文件或给已有目标加前后置操作。
  • add_custom_target :专注于 "创建可执行任务 ",是 "主动可调用" 的(通过 make <target>),常用于封装命令、创建独立任务(如生成文档、清理文件),或强制触发 add_custom_command

相关链接