CMake 047:解锁安装阶段自定义操作,告别配置阶段提前执行坑
- 前言✨
- [Bilibili 同步视频](#Bilibili 同步视频)
- 一、先搞懂!为什么普通代码不能在安装时运行💡
- 二、上手实战:打印安装进度日志📝
-
- [执行构建 & 安装命令](#执行构建 & 安装命令)
- 三、进阶玩法:记录每次安装的时间日志⏱️
- [四、拓展思考:C++ 场景下还能做什么?💻](#四、拓展思考:C++ 场景下还能做什么?💻)
- 五、干货总结📋
前言✨
不知道大家在使用 CMake 构建项目的时候,有没有踩过这样一个大坑🤯:
满心欢喜写下一条message()打印安装进度日志,结果执行cmake -S . -B build刚走完配置步骤,日志就一股脑全部打印出来了。等到真正执行安装命令cmake --install的时候,反而看不到任何进度提示。
究其根本,CMake 的生命周期可不是简简单单一步到位。一条完整的编译流水线被清晰地切分成了四大阶段:配置→生成→编译→安装 。
普通的message语句只会乖乖在【配置阶段】运行。如果你想要在最后的安装环节执行自定义逻辑,打印日志、写入文件、校验环境、拷贝额外资源,单纯依靠普通 CMake 语法已经束手无策。
今天就给大家揭秘 CMake 里一个冷门却超级好用的神器 ------install(CODE)🎉,它可以让一段 CMake 脚本延迟到安装时刻才执行,解锁超多高级玩法!
Bilibili 同步视频
一、先搞懂!为什么普通代码不能在安装时运行💡
先来回顾一下 CMake 标准工作流程:
-
配置阶段 :解析
CMakeLists.txt,检查环境变量、依赖库,执行顶层直接书写的message、if判断等代码 -
生成阶段:生成 Makefile、VS 工程等构建脚本
-
编译阶段:调用编译器,把源码编译成可执行程序、静态库、动态库
-
安装阶段:把编译产出的头文件、库文件、二进制程序拷贝到指定目录
⚠️踩坑预警:直接写在顶层的
message()会在配置阶段立刻执行,并不是等到安装的时候才触发!
举一个反面教材(错误写法❌):
cmake
message(STATUS "开始执行项目安装!")
install(FILES a.h DESTINATION include)
运行cmake -S . -B build,这条日志直接就打印出来了。等你后续敲下cmake --install执行安装,再也不会出现这条提示,完全达不到监控安装流程的目的。
这时候install(CODE)就该闪亮登场了!
被包裹在install(CODE "")内部的所有 CMake 代码,都会延迟执行,只有当你真正运行安装命令的时候,代码才会被唤醒运行。内部可以使用几乎全部 CMake 语法,调用函数、读写文件、打印日志,自由度拉满!
二、上手实战:打印安装进度日志📝
我们先来搭建一个极简的演示工程。
新建两个空的头文件a.h、b.h,模拟项目中需要被安装导出的头文件资源。
接下来编写CMakeLists.txt:
cmake
# 将两个头文件安装到输出目录下的include文件夹
install(FILES a.h DESTINATION include)
install(FILES b.h DESTINATION include)
# install(CODE)内部脚本,延迟至安装阶段执行
install(CODE "
message(STATUS "================Begin install================")
message(STATUS "✅ a.h 文件开始安装")
message(STATUS "✅ b.h 文件开始安装")
message(STATUS "================Install Success================")
")
执行构建 & 安装命令
bash
# 1.配置&生成构建目录
cmake -S . -B build
# 2.执行安装,所有文件输出到out文件夹下
cmake --install build --prefix out
运行结果:
配置生成的时候控制台安安静静,没有任何安装日志输出;当执行安装命令的一瞬间,进度提示文字齐刷刷打印到终端!🎉
💡小知识点:字符串转义问题
在双引号包裹的
CODE脚本块中,如果要读取 CMake 变量,必须添加反斜杠进行转义 ****${变量名}。如果忘记转义,变量会在【配置阶段】就被提前解析替换掉,等到安装阶段变量早已失效,这是新手高频踩坑点!
三、进阶玩法:记录每次安装的时间日志⏱️
仅仅打印控制台提示远远不够。我们更进一步,实现一个小功能:每一次执行安装操作,自动把当前安装时间追加写入日志文件 **install_log.txt**。
使用 CMake 内置指令string(TIMESTAMP)获取系统时间戳,搭配file(APPEND)追加写入文件。
file(APPEND)和file(WRITE)的核心区别:
WRITE:新建文件写入内容,如果文件存在就覆盖原有内容
APPEND:在文件末尾追加新内容,旧日志永久保留,完美适配日志场景
完整 CMake 示例代码:
cmake
install(FILES a.h DESTINATION include)
install(FILES b.h DESTINATION include)
install(CODE "
# 获取当前系统时间戳,格式化:年‑月‑日 时:分:秒
string(TIMESTAMP now "%Y‑%m‑%d %H:%M:%S")
message(STATUS "🕒本次项目安装时间:${now}")
# 将时间追加写入日志文件,换行分隔每一条记录
file(APPEND install_log.txt "${now}n")
")
多次执行安装命令:
bash
cmake --install build --prefix out
打开生成出来的install_log.txt,就可以看到一条条按顺序记录下来的安装时间:
Plain
2026‑08‑14 14:20:10
2026‑08‑14 14:21:33
2026‑08‑14 14:22:05
完美实现安装行为留痕!
四、拓展思考:C++ 场景下还能做什么?💻
install(CODE)不仅仅只能打印日志、记录时间。在大型 C++ 工程中,它还有超多实用的落地场景,这里给大家列举几个高频方案:
-
安装后资源校验:安装完成之后,自动校验目标路径下头文件、动态库是否拷贝成功,缺失文件立刻抛出警告
-
动态库部署 :Windows 平台安装阶段拷贝 dll 依赖文件,Linux 下执行
rpth路径修改脚本 -
生成版本配置文件 :安装的时候生成
version.h版本头文件,写入编译时间、版本号 -
执行外部程序:安装阶段调用脚本、第三方工具,完成额外的初始化工作
这里给大家演示一个 C++ 读取刚才生成的install_log.txt日志文件的示例代码,读取并打印全部安装记录:
cpp
#include <iostream>
#include <fstream>
#include <string>
int main()
{
std::ifstream logFile("install_log.txt");
if (!logFile.is_open())
{
std::cout << "日志文件打开失败!" << std::endl;
return -1;
}
std::string line;
std::cout << "=======项目所有安装记录=======n";
while (std::getline(logFile, line))
{
std::cout << line << std::endl;
}
logFile.close();
return 0;
}
五、干货总结📋
| 关键点 | 说明 |
|---|---|
install(CODE "") |
脚本延迟至安装阶段运行,不会在配置阶段提前执行 |
| 变量转义 | 代码块内部读取变量必须书写${var},防止提前解析 |
string(TIMESTAMP) |
CMake 内置函数,快速获取格式化系统时间戳 |
file(APPEND) |
追加写入文件,适合生成日志、记录流水 |

善用install(CODE)就相当于你拿到了 CMake 安装流程的 "后门控制权",当你以后遇到需要在安装那一刻执行自定义逻辑的需求,不妨掏出这个宝藏 API 试一试~
本文演示项目完整源码可自行复制 CMake 脚本动手实操,欢迎大家在评论区交流你解锁过哪些 CMake 奇技淫巧😜