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.2.示例说明
4.总结
相关链接
1.add_custom_command
1.1.简介
add_custom_command()
用于定义构建阶段(如 make
/ninja
运行时)执行的自定义命令,通常用于生成文件、预处理资源、或在编译前后执行额外操作(如复制文件、运行脚本等)。它的核心是与构建系统(如 Makefile、Ninja)集成,根据依赖关系自动触发命令执行。
add_custom_command()
有两种主要用法:生成文件(通过 OUTPUT
定义输出文件)和为目标添加命令(通过 TARGET
关联目标)。
1.生成文件(最常用)
add_custom_command(OUTPUT <output_file1> [output_file2...] # 命令生成的文件(必填)COMMAND <cmd1> [args1...] # 要执行的命令(必填)[MAIN_DEPENDENCY <file>] # 主要依赖文件(如输入源文件)[DEPENDS <dep1> <dep2>...] # 其他依赖(文件或目标,依赖变化则重新执行)[WORKING_DIRECTORY <dir>] # 命令执行的工作目录[COMMENT <message>] # 执行时显示的信息(方便调试)[VERBATIM] # 确保命令参数被正确转义(跨平台安全)
)
2.为目标添加前置 / 后置命令
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 脚本生成头文件
# 定义命令:用 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.hMAIN_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.py
或version.txt
变化时,构建系统会自动重新执行命令生成version.h
。 - 若没有目标依赖
version.h
,该命令可能不会执行(需结合add_custom_target
强制执行,见下文)。
2.为目标添加后置命令(如复制文件、运行测试)
通过 TARGET
+ POST_BUILD
可在目标(如可执行文件)编译完成后执行命令,例如复制二进制到指定目录、运行验证脚本等。
示例:编译后复制二进制到 bin
目录
add_executable(myapp src/main.cpp)# 构建 myapp 后,复制到 ${CMAKE_BINARY_DIR}/bin 目录
add_custom_command(TARGET myappPOST_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
,增强兼容性)。
示例:解压文件
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.gzCOMMAND${CMAKE_COMMAND} -E touch ${wrap_BLAS_LAPACK_sources}WORKING_DIRECTORY${CMAKE_CURRENT_BINARY_DIR}DEPENDS${CMAKE_CURRENT_SOURCE_DIR}/wrap_BLAS_LAPACK.tar.gzCOMMENT"Unpacking C++ wrappers for BLAS/LAPACK"VERBATIM
)
这段代码做了以下几件事:
- 定义输出文件列表(解压后预期的文件)
- 使用CMake自带的tar命令解压文件
- 使用touch命令更新文件时间戳(防止使用旧文件)
- 指定工作目录为构建目录
- 声明依赖关系(依赖于压缩包文件)
- 添加构建时的提示信息
- 使用VERBATIM确保命令跨平台兼容
3.结合 add_custom_target
强制执行命令
如果自定义命令生成的文件没有被其他目标依赖(如生成日志、文档),需用 add_custom_target
创建一个目标,并让该目标依赖 add_custom_command
的输出,以确保命令执行。
示例:生成文档(无其他目标依赖)
# 定义生成文档的命令
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.依赖关系是核心
构建系统通过 OUTPUT
、DEPENDS
判断是否需要执行命令。只有当输出文件不存在,或依赖文件(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>
调用,也能被其他目标依赖。
基本语法:
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 文档
add_custom_target(docs # 目标名称:make docs 可触发COMMAND doxygen Doxyfile # 执行 doxygen 生成文档WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # 在源码目录执行DEPENDS Doxyfile src/ # 依赖配置文件和源码目录COMMENT "生成 API 文档..."
)
- 用户可通过
make docs
或ninja docs
手动触发文档生成; - 若
Doxyfile
或src/
目录内容变化,下次执行时会重新生成。
2.加入默认构建(通过 ALL
选项)
若目标需在默认构建时自动执行(如生成必需的资源文件),可添加 ALL
选项。
示例:编译前生成配置文件
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.依赖其他目标(联动执行)
自定义目标可依赖其他目标(如可执行文件、库),实现 “构建后操作”。
示例:构建后运行测试
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
使用。
示例:清理生成的文档
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_allCOMMAND ${CMAKE_MAKE_PROGRAM} clean # 执行默认的 cleanCOMMAND ${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
触发:
# 定义生成文件的命令
add_custom_command(OUTPUT data.txtCOMMAND echo "data" > data.txt
)# 定义目标,依赖生成的文件
add_custom_target(generate_dataDEPENDS data.txt # 触发命令执行
)
4.跨平台命令
避免直接使用平台特定命令(如 rm
/del
),优先用 ${CMAKE_COMMAND} -E
提供的跨平台操作:
# 跨平台删除目录
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/temp
3.add_custom_command和add_custom_target区别
3.1. 区别
在 CMake 中,add_custom_command
和 add_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
定义一个生成文件的命令:
# 定义命令:生成 output.txt
add_custom_command(OUTPUT output.txtCOMMAND echo "生成文件" > output.txtCOMMENT "执行生成命令..."
)# 只有当其他目标依赖 output.txt 时,上述命令才会执行
add_executable(myapp main.cpp output.txt) # myapp 依赖 output.txt → 触发命令
- 若没有
myapp
依赖output.txt
,这个命令永远不会执行; - 即使手动运行
make
,也不会触发(因为没有目标关联它)。
示例 2:add_custom_target
的主动执行
用 add_custom_target
定义一个目标:
# 定义目标:打印一条消息
add_custom_target(print_msgCOMMAND echo "这是一个自定义目标"COMMENT "执行目标命令..."
)
- 无需依赖其他目标,用户可直接通过
make print_msg
触发该命令; - 若想让它在默认构建中执行,可添加
ALL
选项:add_custom_target(print_msg ALL ...)
,这样运行make
时会自动执行。
2.关联性:两者常结合使用
add_custom_command
生成的命令若没有被其他目标依赖,可能永远不会执行。此时可通过 add_custom_target
创建一个目标,让该目标依赖 add_custom_command
的输出,从而强制命令执行。
示例:生成文档(无其他目标依赖时)
# 1. 定义生成文档的命令(生成 docs/index.html)
add_custom_command(OUTPUT docs/index.htmlCOMMAND doxygen Doxyfile # 用 doxygen 生成文档DEPENDS Doxyfile src/ # 依赖配置文件和源码COMMENT "生成文档..."
)# 2. 定义目标 generate_docs,依赖上述命令的输出
add_custom_target(generate_docsDEPENDS docs/index.html # 依赖生成的文件 → 触发上述命令
)
- 此时,用户运行
make generate_docs
会触发add_custom_command
的命令,生成文档; - 若没有
add_custom_target
,add_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
。
相关链接
- CMake 官网 CMake - Upgrade Your Software Build System
- CMake 官方文档:CMake Tutorial — CMake 4.1.0-rc1 Documentation
- CMake 源码:https://github.com/Kitware/CMake
- CMake 源码:https://gitlab.kitware.com/cmake/cmakeku
- 中文版基础介绍: CMake 入门实战 | HaHack
- wiki: Home · Wiki · CMake / Community · GitLab
- Modern CMake 简体中文版: Introduction · Modern CMake