CMake实战指南一:add_custom_command

news2025/4/8 8:03:32

CMake 进阶:add_custom_command 用法详解与实战指南

在 CMake 构建系统中,add_custom_command 是一个灵活且强大的工具,允许开发者在构建流程中插入自定义操作。无论是生成中间文件、执行预处理脚本,还是在目标构建前后触发额外逻辑,它都能轻松胜任。本文将从基础语法、核心参数、实战案例到高级技巧,全面解析 add_custom_command 的用法。

一、为什么需要 add_custom_command

CMake 的核心优势在于跨平台构建,但默认流程难以覆盖所有个性化需求。例如:

  • 生成动态配置文件(如根据编译选项生成 config.h
  • 集成外部工具链(如代码生成器、静态分析工具)
  • 构建后处理(如复制可执行文件到部署目录、生成版本号)
  • 复杂依赖管理(非传统文件依赖的场景)

add_custom_command 正是为解决这类问题而生,它通过在构建流程中注入自定义命令,让 CMake 具备更强的扩展性。

二、基础语法与核心参数

2.1 两大使用场景

场景 1:生成文件(File Generation)

用于定义 “输入文件→输出文件” 的映射关系,CMake 会根据依赖自动触发命令:

add_custom_command(  

    OUTPUT <output1> [<output2>...]  # 必选:命令生成的目标文件  

    COMMAND <command1> [<args1>...]   # 必选:执行的命令(可多条)  

    [MAIN_DEPENDENCY <file>]           # 主依赖文件(变化时强制重新执行)  

    [DEPENDS <dep1> <dep2>...]        # 附加依赖文件/目标(变化时触发重新执行)  

    [IMPLICIT_DEPENDS <lang> <file>]   # 隐式依赖(如语法分析生成的依赖)  

    [WORKING_DIRECTORY <dir>]          # 命令执行的工作目录(默认当前源目录)  

    [COMMENT "<message>"]              # 执行时显示的提示信息  

    [VERBATIM]                         # 保留命令原始格式(避免CMake转义)  

    [USES_TERMINAL]                    # 在终端中执行命令(Windows适用)  

)  

场景 2:关联构建目标(Target Hook)

用于在目标(可执行文件 / 库)的构建阶段插入钩子:

add_custom_command(  

    TARGET <target_name>               # 必选:关联的目标(如add_executable生成的目标)  

    PRE_BUILD | PRE_LINK | POST_BUILD  # 必选:命令执行时机(编译前/链接前/构建后)  

    COMMAND <command> [<args>...]     # 执行的命令(可多条)  

    [WORKING_DIRECTORY <dir>]          # 工作目录  

    [COMMENT "<message>"]              # 提示信息  

    [VERBATIM]                         # 禁用参数转义  

)  

2.2 核心参数解析

参数

说明

OUTPUT

必选(场景 1),指定命令生成的文件,CMake 通过检查这些文件是否存在决定是否执行命令

COMMAND

必选,支持多条命令(按顺序执行),可使用 CMake 变量(如 ${CMAKE_CURRENT_BINARY_DIR})

DEPENDS

显式依赖,支持文件路径或目标名称(如add_executable生成的目标),依赖变化时触发重跑

MAIN_DEPENDENCY

主依赖,优先级高于DEPENDS,仅当该文件变化时才强制重新生成输出文件

IMPLICIT_DEPENDS

隐式依赖(如通过语法分析推导的依赖),需指定语言类型(如CXX、C)

VERBATIM

关键参数!确保命令中的特殊符号(如$、#)不被 CMake 解析,避免语法错误

三、实战案例:从基础到进阶

3.1 案例 1:生成编译期配置文件

需求:根据 CMake 选项生成 config.h,包含版本号和编译参数。

# CMakeLists.txt  
cmake_minimum_required(VERSION 3.16)  
project(add_custom_command02 VERSION 1.0.0)  


# 定义配置模板  
configure_file(  
    ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in  
    ${CMAKE_CURRENT_BINARY_DIR}/config.h  
)  

# 使用 add_custom_command 生成动态内容  
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/build_info.txt
    COMMAND ${CMAKE_COMMAND} -E echo %PATH% >> ${CMAKE_CURRENT_BINARY_DIR}/env.txt
    COMMAND ${CMAKE_COMMAND} -E echo "Build Time: %DATE% %TIME%" >> ${CMAKE_CURRENT_BINARY_DIR}/build_info.txt
    COMMAND ${CMAKE_COMMAND} -E echo "Version: ${PROJECT_VERSION}" >> ${CMAKE_CURRENT_BINARY_DIR}/build_info.txt
    COMMENT "Generating build information"
    VERBATIM
)

# 添加自定义目标,确保配置文件在编译前生成  
add_custom_target(  
    generate_config ALL  
    DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/config.h ${CMAKE_CURRENT_BINARY_DIR}/build_info.txt  
)  

# 关联到可执行文件,确保依赖正确  
add_executable(add_custom_command01 src/main.cpp)  
target_include_directories(add_custom_command01 PRIVATE ${CMAKE_CURRENT_BINARY_DIR})  
// 以下是 main.cpp 代码示例
#include <iostream>
#include "config.h"

int main()
{
    std::cout << "Project Version: " << PROJECT_VERSION << std::endl;
    std::cout << "Build Time: " << __DATE__ << " " << __TIME__ << std::endl;
    return 0;
}
// 以下是 config.h.in 示例内容,可根据实际需求定义更多宏或变量
#ifndef CONFIG_H_IN
#define CONFIG_H_IN

#define PROJECT_VERSION "@PROJECT_VERSION@"

#endif

编译工程完毕后执行命令生成的文件

关键点

  • 通过 configure_file 处理模板文件,结合 add_custom_command 生成动态内容
  • add_custom_target 定义独立构建目标,ALL 关键字使其在默认构建时触发

3.2 案例 2:构建后自动部署

  • 需求:将可执行文件和依赖库复制到指定部署目录,并生成版本清单。

cmake_minimum_required(VERSION 3.16)  
project(DeploymentDemo)  

# 添加头文件路径(当前源目录)  
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)  

# 生成可执行文件  
add_executable(DeploymentDemo  
    src/main.cpp  
    src/utils.cpp  
)  

# 构建后部署命令(兼容Windows/Linux)  
add_custom_command(  
    TARGET DeploymentDemo  
    POST_BUILD  
    # 创建部署目录(跨平台路径)  
    COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/deploy"  
    # 复制可执行文件(使用生成器表达式获取路径)  
    COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:DeploymentDemo>" "${CMAKE_CURRENT_BINARY_DIR}/deploy"  
    # 复制动态库(Windows示例,Linux可忽略或使用cp命令)  
    COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:DeploymentDemo>" "${CMAKE_CURRENT_BINARY_DIR}/deploy"  
    # 生成版本清单  
    COMMAND ${CMAKE_COMMAND} -E echo "Version: ${PROJECT_VERSION}" > "${CMAKE_CURRENT_BINARY_DIR}/deploy/VERSION.txt"
    VERBATIM  
)  

 main.cpp代码

#include <iostream>  
#include "utils.h"  

int main() {  
    std::cout << "Main program running." << std::endl;  
    printDeploymentInfo();  // 调用部署信息函数  
    return 0;  
}  
// 假设这里有一个简单的辅助函数声明在 utils.h 中,实现于 utils.cpp,用于打印一些部署信息
// utils.h
#ifndef UTILS_H
#define UTILS_H

void printDeploymentInfo();

#endif

// utils.cpp
#include <iostream>
#include "utils.h"

void printDeploymentInfo()
{
    std::cout << "Deployment completed successfully." << std::endl;
}

编译工程完毕后执行命令生成的文件

关键点

  • POST_BUILD 时机确保在目标构建完成后执行
  • $<TARGET_FILE:target> 生成器表达式动态获取目标文件路径
  • copy_if_different 避免重复复制,提升构建效率

高级技巧与最佳实践

4.1 动态生成命令参数

利用 CMake 的变量和生成器表达式,使命令参数动态化:

# 根据系统架构选择不同的处理脚本  
add_custom_command(  
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/arch_info.txt  
    COMMAND ${CMAKE_COMMAND} -E echo "Architecture: $<IF:$<BOOL:$<CMAKE_SIZEOF_VOID_P:8>>,x86_64,i386>" >> ${ARCH_INFO_FILE}  
    VERBATIM  
)  

4.2 处理复杂依赖链

通过 IMPLICIT_DEPENDS 声明隐式依赖(如语法分析生成的依赖):

add_custom_command(  
    OUTPUT parser.cpp parser.h  
    COMMAND bison -d ${CMAKE_CURRENT_SOURCE_DIR}/parser.yy -o ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp  
    IMPLICIT_DEPENDS CXX ${CMAKE_CURRENT_BINARY_DIR}/parser.h  # 声明C++头文件依赖  
    VERBATIM  
)  

4.3 避免循环依赖

确保 add_custom_command 的输出文件不被其依赖的目标直接或间接依赖,例如:

# 错误示例:输出文件作为目标源文件,同时目标依赖自身  
add_custom_command(OUTPUT a.txt COMMAND echo "a" > a.txt)  
add_executable(bad_target a.txt)  # 循环依赖风险!  

正确做法:通过 add_custom_target 显式管理依赖关系。

五、常见问题与解决方案

5.1 输出文件路径错误

现象:命令执行后文件未生成到预期目录。解决

  • 使用绝对路径(如 ${CMAKE_CURRENT_BINARY_DIR}/output.txt
  • 通过 WORKING_DIRECTORY 指定命令执行目录

5.2 依赖检测不生效

现象:依赖文件变化后,命令未重新执行。解决

  • 确保 DEPENDS 正确列出所有相关文件
  • 对非文件依赖(如环境变量),可添加虚拟依赖文件

5.3 多命令执行顺序混乱

现象:多条 COMMAND 未按顺序执行。解决

  • CMake 保证 COMMAND 按声明顺序执行,无需额外处理

5.4 VERBATIM 的必要性

场景:命令中包含 $$#$ 等符号时,必须添加 VERBATIM,否则 CMake 会尝试解析为变量或注释,导致错误。

六、总结与推荐用法

使用场景

推荐语法

核心参数

文件生成

OUTPUT + COMMAND + DEPENDS

VERBATIM, MAIN_DEPENDENCY

构建阶段钩子

TARGET + PRE/POST_BUILD

WORKING_DIRECTORY, 生成器表达式

复杂依赖管理

IMPLICIT_DEPENDS

语言类型(如CXX

动态参数生成

生成器表达式(如$<TARGET_FILE>

VERBATIM

add_custom_command 的灵活性使其成为 CMake 进阶的必备工具,但过度使用可能导致构建逻辑复杂化。建议:

  1. 优先使用 CMake 内置命令(如configure_fileadd_library),仅在必要时引入自定义命令
  2. 为自定义命令添加清晰的 COMMENT,如上面案例中详细说明每个命令的意图,方便调试
  3. 通过add_custom_target 集中管理独立的构建步骤

通过合理运用 add_custom_command,开发者可以将 CMake 构建系统与项目的特殊需求深度整合,实现从代码生成到部署的全流程自动化。

如果有具体项目场景或疑难问题,欢迎在评论区交流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2330328.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

懂x帝二手车数据爬虫-涉及简单的字体加密,爬虫中遇到“口”问题的解决

#脚本如下 import requests import pprint import timeurl https://www.dongchedi.com/motor/pc/sh/sh_sku_list?aid1839&app_nameauto_web_pc headers {User-Agent: Mozilla/5.0 }font_map {58425: 0, 58700: 1, 58467: 2, 58525: 3,58397: 4, 58385: 5, 58676: 6, 58…

4.7学习总结 java集合进阶

集合进阶 泛型 //没有泛型的时候&#xff0c;集合如何存储数据 //结论: //如果我们没有给集合指定类型&#xff0c;默认认为所有的数据类型都是object类型 //此时可以往集合添加任意的数据类型。 //带来一个坏处:我们在获取数据的时候&#xff0c;无法使用他的特有行为。 //此…

Python高阶函数-eval深入解析

1. eval() 函数概述 eval() 是 Python 内置的一个强大但需要谨慎使用的高阶函数&#xff0c;它能够将字符串作为 Python 表达式进行解析并执行。 基本语法 eval(expression, globalsNone, localsNone)expression&#xff1a;字符串形式的 Python 表达式globals&#xff1a;可…

LLM面试题八

推荐算法工程师面试题 二分类的分类损失函数&#xff1f; 二分类的分类损失函数一般采用交叉熵(Cross Entropy)损失函数&#xff0c;即CE损失函数。二分类问题的CE损失函数可以写成&#xff1a;其中&#xff0c;y是真实标签&#xff0c;p是预测标签&#xff0c;取值为0或1。 …

JavaScript双问号操作符(??)详解,解决使用 || 时因类型转换带来的问题

目录 JavaScript双问号操作符&#xff08;??&#xff09;详解&#xff0c;解决使用||时因类型转换带来的问题 一、双问号操作符??的基础用法 1、传统方式的痛点 2、双问号操作符??的精确判断 3、双问号操作符??与逻辑或操作符||的对比 二、复杂场景下的空值处理 …

蓝桥杯 web 展开你的扇子(css3)

普通答案&#xff1a; #box:hover #item1{transform: rotate(-60deg); } #box:hover #item2{transform: rotate(-50deg); } #box:hover #item3{transform: rotate(-40deg); } #box:hover #item4{transform: rotate(-30deg); } #box:hover #item5{transform: rotate(-20deg); }…

聚焦楼宇自控:优化建筑性能,引领智能化管控与舒适环境

在当今建筑行业蓬勃发展的浪潮中&#xff0c;人们对建筑的要求早已超越了传统的遮风避雨功能&#xff0c;而是更加注重建筑性能的优化、智能化的管控以及舒适环境的营造。楼宇自控系统作为现代建筑技术的核心力量&#xff0c;正凭借其卓越的功能和先进的技术&#xff0c;在这几…

Ubuntu16.04配置远程连接

配置静态IP Ubuntu16.04 修改超管账户默认密码 # 修改root账户默认密码 sudo passwd Ubuntu16.04安装SSH # 安装ssh服务&#xff1a; sudo apt-get install ssh# 启动SSH服务&#xff1a; sudo /etc/init.d/ssh start # 开机自启 sudo systemctl enable ssh# 如无法连接&…

基于springboot微信小程序课堂签到及提问系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 随着信息时代的来临&#xff0c;过去的课堂签到及提问管理方式的缺点逐渐暴露&#xff0c;本次对过去的课堂签到及提问管理方式的缺点进行分析&#xff0c;采取计算机方式构建基于微信小程序的课堂签到及提问系统。本文通过阅读相关文献&#xff0c;研究国内外相关技术&a…

互联网三高-高性能之JVM调优

1 运行时数据区 JVM运行时数据区是Java虚拟机管理的内存核心模块&#xff0c;主要分为线程共享和线程私有两部分。 &#xff08;1&#xff09;线程私有 ① 程序计数器&#xff1a;存储当前线程执行字节码指令的地址&#xff0c;用于分支、循环、异常处理等流程控制‌ ② 虚拟机…

封装可拖动弹窗(vue jquery引入到html的版本)

vue cli上简单的功能&#xff0c;在js上太难弄了&#xff0c;这个弹窗功能时常用到&#xff0c;保存起来备用吧 备注&#xff1a;deepseek这个人工智障写一堆有问题的我&#xff0c;还老服务器繁忙 效果图&#xff1a; html代码&#xff1a; <div class"modal-mask&qu…

【技术报告】GPT-4o 原生图像生成的应用与分析

【技术报告】GPT-4o 原生图像生成的应用与分析 1. GPT-4o 原生图像生成简介1.1 文本渲染能力1.2 多轮对话迭代1.3 指令遵循能力1.4 上下文学习能力1.5 跨模态知识调用1.6 逼真画质与多元风格1.7 局限性与安全性 2. GPT-4o 技术报告2.1 引言2.2 安全挑战、评估与缓解措施2.2.1 安…

初阶数据结构(3)顺序表

Hello~,欢迎大家来到我的博客进行学习&#xff01; 目录 1.线性表2.顺序表2.1 概念与结构2.2 分类2.2.1 静态顺序表2.2.2 动态顺序表 2.3 动态顺序表的实现初始化尾插头插尾删头删查找指定位置之前插入数据删除指定位置的数据销毁 1.线性表 首先我们需要知道的是&#xff0c;…

谷歌发布网络安全AI新模型Sec-Gemini v1

谷歌近日宣布推出实验性AI模型Sec-Gemini v1&#xff0c;旨在通过人工智能技术革新网络安全防御体系。该模型由Sec-Gemini团队成员Elie Burzstein和Marianna Tishchenko共同研发&#xff0c;旨在帮助网络安全人员应对日益复杂的网络威胁。 攻防不对称的破局之道 Sec-Gemini团队…

Meta LLaMA 4:对抗 GPT-4o 与 Claude 的开源王牌

2025 年 4 月&#xff0c;Meta 正式发布了 LLaMA 4 系列的首批两款模型。 这两款模型模型分别是&#xff1a;LLaMA 4 Scout 与 LLaMA 4 Maverick&#xff0c;均采用了 专家混合架构&#xff08;Mixture-of-Experts, MoE&#xff09;。 据 Meta 表示&#xff0c;这是首次有 …

企业级 ClickHouse Docker 离线部署实践指南20250407

企业级 ClickHouse Docker 离线部署实践指南 引言 在数据分析与日志处理日益重要的今天&#xff0c;ClickHouse 凭借其高性能、列式存储架构&#xff0c;成为企业在大数据分析中的首选引擎之一。本文基于一位金融行业从业者在离线网络环境中部署 ClickHouse 的真实实践过程&a…

DeepSeek-MLA

MLA 结构 需要缓存 KV 向量共用的压缩隐特征K 向量多头共享的带位置编码的向量 为什么带有位置信息的 Q 向量来自于隐特征向量&#xff0c;而带有位置的 K 向量来自于 H 向量且共享呢&#xff1f; 最好的方法肯定是从H向量直接计算并且不共享&#xff0c;但是会大大增加显存使…

pyTorch-迁移学习-学习率衰减-四种天气图片多分类问题

目录 1.导包 2.加载数据、拼接训练、测试数据的文件夹路径 3.数据预处理 3.1 transforms.Compose数据转化 3.2分类存储的图片数据创建dataloader torchvision.datasets.ImageFolder torch.utils.data.DataLoader 4.加载预训练好的模型(迁移学习) 4.1固定、修改预训练…

vscode Colipot 编程助手

1、登录到colipot&#xff0c;以github账号&#xff0c;关联登录 点击【continue】按钮&#xff0c;继续。 点击【打开Visual Studio Code】&#xff0c;回到vscode中。 2、问一下11? 可以看出&#xff0c;很聪明&#xff0c;一下子就算出来了。 3、帮我们写一个文件&#xf…

1、window 下SDL 下载使用, 测试环境搭建

1. SDL3下载 官网&#xff1a; https://www.libsdl.org/ 点击SDL Releases 或者 SDL GItHub 进入github下载&#xff1a; 因为自己在windows下使用的mingw,所以下载mingw版的&#xff0c;也可以 下载源码自己编译。 2. 项目搭建 这里使用的时mingw vsocde cmake, 可以使…