CMake实战指南一:add_custom_command
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.txtCOMMAND ${CMAKE_COMMAND} -E echo %PATH% >> ${CMAKE_CURRENT_BINARY_DIR}/env.txtCOMMAND ${CMAKE_COMMAND} -E echo "Build Time: %DATE% %TIME%" >> ${CMAKE_CURRENT_BINARY_DIR}/build_info.txtCOMMAND ${CMAKE_COMMAND} -E echo "Version: ${PROJECT_VERSION}" >> ${CMAKE_CURRENT_BINARY_DIR}/build_info.txtCOMMENT "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_Hvoid 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 进阶的必备工具,但过度使用可能导致构建逻辑复杂化。建议:
- 优先使用 CMake 内置命令(如configure_file、add_library),仅在必要时引入自定义命令
- 为自定义命令添加清晰的 COMMENT,如上面案例中详细说明每个命令的意图,方便调试
- 通过add_custom_target 集中管理独立的构建步骤
通过合理运用 add_custom_command,开发者可以将 CMake 构建系统与项目的特殊需求深度整合,实现从代码生成到部署的全流程自动化。
如果有具体项目场景或疑难问题,欢迎在评论区交流!
相关文章:
CMake实战指南一:add_custom_command
CMake 进阶:add_custom_command 用法详解与实战指南 在 CMake 构建系统中,add_custom_command 是一个灵活且强大的工具,允许开发者在构建流程中插入自定义操作。无论是生成中间文件、执行预处理脚本,还是在目标构建前后触发额外逻…...
指纹浏览器技术架构解析:高并发批量注册业务的工程化实践——基于分布式指纹引擎与防关联策略的深度实现
一、技术背景与行业痛点 在跨境电商、广告投放、问卷调查等场景中,批量注册与多账号矩阵运营已成为刚需。然而,主流平台(如亚马逊、Facebook、Google)的风控系统通过浏览器指纹追踪(Canvas/WebGL/WebRTC等)…...
懂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集合进阶
集合进阶 泛型 //没有泛型的时候,集合如何存储数据 //结论: //如果我们没有给集合指定类型,默认认为所有的数据类型都是object类型 //此时可以往集合添加任意的数据类型。 //带来一个坏处:我们在获取数据的时候,无法使用他的特有行为。 //此…...
Python高阶函数-eval深入解析
1. eval() 函数概述 eval() 是 Python 内置的一个强大但需要谨慎使用的高阶函数,它能够将字符串作为 Python 表达式进行解析并执行。 基本语法 eval(expression, globalsNone, localsNone)expression:字符串形式的 Python 表达式globals:可…...
LLM面试题八
推荐算法工程师面试题 二分类的分类损失函数? 二分类的分类损失函数一般采用交叉熵(Cross Entropy)损失函数,即CE损失函数。二分类问题的CE损失函数可以写成:其中,y是真实标签,p是预测标签,取值为0或1。 …...
【团体程序涉及天梯赛】L1~L2实战反思合集(C++)
实战反思汇总记录 仔细审题,想好再写 L1-104 九宫格 - 团体程序设计天梯赛-练习集 易忽略的错误:开始习惯性地看到n就以为是n*n数组了,实际上应该是9*9的固定大小数组,查了半天没查出来 L1-101 别再来这么多猫娘了!…...
Linux Terminal Mode | canonical / nocanonical / cbreak / raw
注:本文为 “Linux 终端模式” 相关文章合辑。 略作重排,如有内容异常,请看原文。 终端输入输出的三种模式 guidao 1 前言 在进行项目开发时,需要实时读取终端输入(无需按下 Enter 键即可读取)。然而&a…...
预测分析(二):基于机器学习的数值预测
文章目录 基于机器学习的数值预测机器学习简介监督学习的任务创建第一个机器学习模型机器学习的目标——泛化过拟合现象评价函数与最优化 建模前的数据处理进一步特征变换 多元线性回归模型LASSO回归kNN算法原理算法步骤k值的选择 基于机器学习的数值预测 机器学习是人工智能的…...
JavaScript双问号操作符(??)详解,解决使用 || 时因类型转换带来的问题
目录 JavaScript双问号操作符(??)详解,解决使用||时因类型转换带来的问题 一、双问号操作符??的基础用法 1、传统方式的痛点 2、双问号操作符??的精确判断 3、双问号操作符??与逻辑或操作符||的对比 二、复杂场景下的空值处理 …...
蓝桥杯 web 展开你的扇子(css3)
普通答案: #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); }…...
聚焦楼宇自控:优化建筑性能,引领智能化管控与舒适环境
在当今建筑行业蓬勃发展的浪潮中,人们对建筑的要求早已超越了传统的遮风避雨功能,而是更加注重建筑性能的优化、智能化的管控以及舒适环境的营造。楼宇自控系统作为现代建筑技术的核心力量,正凭借其卓越的功能和先进的技术,在这几…...
前端视频流技术深度解析
一、视频流技术体系架构 1.1 现代视频流技术栈 1.1.1 核心协议对比 协议传输方式延迟适用场景浏览器支持HLSHTTP分片6-30s点播、直播回看全平台DASHHTTP动态适配3-15s多码率自适应Chrome/FirefoxWebRTCP2P/UDP<500ms实时通信、直播现代浏览器RTMPTCP长连接1-3s传统直播推…...
k8s核心资源对象一(入门到精通)
本文将深入探讨Kubernetes中的核心资源对象,包括Pod、Deployment、Service、Ingress、ConfigMap和Secret,详细解析其概念、功能以及实际应用场景,帮助读者全面掌握这些关键组件的使用方法。 一、pod 1 pod概念 k8s最小调度单元,…...
Ubuntu16.04配置远程连接
配置静态IP Ubuntu16.04 修改超管账户默认密码 # 修改root账户默认密码 sudo passwd Ubuntu16.04安装SSH # 安装ssh服务: sudo apt-get install ssh# 启动SSH服务: sudo /etc/init.d/ssh start # 开机自启 sudo systemctl enable ssh# 如无法连接&…...
基于springboot微信小程序课堂签到及提问系统(源码+lw+部署文档+讲解),源码可白嫖!
摘要 随着信息时代的来临,过去的课堂签到及提问管理方式的缺点逐渐暴露,本次对过去的课堂签到及提问管理方式的缺点进行分析,采取计算机方式构建基于微信小程序的课堂签到及提问系统。本文通过阅读相关文献,研究国内外相关技术&a…...
互联网三高-高性能之JVM调优
1 运行时数据区 JVM运行时数据区是Java虚拟机管理的内存核心模块,主要分为线程共享和线程私有两部分。 (1)线程私有 ① 程序计数器:存储当前线程执行字节码指令的地址,用于分支、循环、异常处理等流程控制 ② 虚拟机…...
数据操作语言
一、DML的核心操作类型 1.添加数据(INSERT) (1)手动插入:逐行插入数据,适用于少量数据。 INSERT INTO 表名 (字段1, 字段2) VALUES (值1, 值2);(2)批量导入:通过外部文件导入数据,适用于大数据场景...
智谛达科技:以创新为翼,翱翔AI人形机器人蓝海
在科技创新的浩瀚星空中,智谛达科技集团犹如一颗璀璨的明星,以其独特的创新光芒,照亮了AI人形机器人的广阔蓝海。这家在AI领域深耕多年的企业,始终秉持着创新为翼的发展理念,不断突破技术瓶颈,拓展应用场景,以卓越的实力和前瞻性的思维,引领着人形机器人行业的未来发展。 智谛达…...
封装可拖动弹窗(vue jquery引入到html的版本)
vue cli上简单的功能,在js上太难弄了,这个弹窗功能时常用到,保存起来备用吧 备注:deepseek这个人工智障写一堆有问题的我,还老服务器繁忙 效果图: html代码: <div class"modal-mask&qu…...
【LeetCode77】组合
题目描述 给定区间 [1, n] 和一个整数 k,需要返回所有可能的 k 个数的组合。 思路 算法选择:回溯算法 回溯算法是一种试探性搜索方法,非常适合用来解决组合问题。基本思想是: 从数字 1 开始,逐步构建组合。当当前组…...
【技术报告】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~,欢迎大家来到我的博客进行学习! 目录 1.线性表2.顺序表2.1 概念与结构2.2 分类2.2.1 静态顺序表2.2.2 动态顺序表 2.3 动态顺序表的实现初始化尾插头插尾删头删查找指定位置之前插入数据删除指定位置的数据销毁 1.线性表 首先我们需要知道的是,…...
Visual Studio 中使用 Clang 作为 C/C++ 编译器时,设置优化选项方法
在 Visual Studio 中使用 Clang 作为 C/C 编译器时,可以通过以下方法设置优化选项: 方法 1:通过项目属性设置(推荐) 右键项目 → 属性 配置属性 → C/C → 优化 优化:选择优化级别 /O0 - 禁用优化&#x…...
设计模式简述(七)原型模式
原型模式 描述基本使用 使用场景 描述 基于已有对象,利用JDK的Cloneable接口,生成一个新的对象。 常用于需要同时创建多个对象的场景 默认的clone是浅拷贝,如果要实现深拷贝需自行处理 可以在clone方法中手动拷贝数组成员或者其他引用类型成…...
Linux中查看占用端口号的进程信息的方法
在 Linux 中查看占用 ** 端口(eg:1717)**的进程号(PID),可以通过以下命令实现: 方法 1:使用 netstat 命令 sudo netstat -tulnp | grep :1717参数解释: -t:查看 TCP 端口…...
谷歌发布网络安全AI新模型Sec-Gemini v1
谷歌近日宣布推出实验性AI模型Sec-Gemini v1,旨在通过人工智能技术革新网络安全防御体系。该模型由Sec-Gemini团队成员Elie Burzstein和Marianna Tishchenko共同研发,旨在帮助网络安全人员应对日益复杂的网络威胁。 攻防不对称的破局之道 Sec-Gemini团队…...
【学Rust写CAD】35 alpha_mul_256(alpha256.rs补充方法)
源码 // Calculates (value * alpha256) / 255 in range [0,256], // for [0,255] value and [0,256] alpha256. pub fn alpha_mul_256(self,value: u32) -> Alpha256 {let prod value * self.0;Alpha256((prod (prod >> 8)) >> 8) }代码分析 这个函数 alph…...
嵌入式工程师多线程编程(三)裸机编程、RTOS、Linux及多线程编程的全面对比
以下是裸机编程、RTOS、Linux及多线程编程的全面对比解析,结合技术特性和应用场景进行深度分析: 一、架构与调度机制对比 维度裸机编程RTOSLinux任务调度无调度器(轮询/前后台系统)抢占式优先级调度(硬实时࿰…...
Meta LLaMA 4:对抗 GPT-4o 与 Claude 的开源王牌
2025 年 4 月,Meta 正式发布了 LLaMA 4 系列的首批两款模型。 这两款模型模型分别是:LLaMA 4 Scout 与 LLaMA 4 Maverick,均采用了 专家混合架构(Mixture-of-Experts, MoE)。 据 Meta 表示,这是首次有 …...
