28、与 CPack 集成:通过 packagePresets 自动化打包流程。
说实话,CMake Presets 这个功能刚出来的时候,我第一反应是:嗯,又是个配置规范化的东西。但真正让我觉得「这玩意儿真香」的,是它和 CPack 的集成。你想想看,以前我们打包项目,要么写一堆 shell 脚本,要么在 CI 里硬编码各种参数。每次换平台、换编译器,打包脚本就得跟着改,烦不烦?
packagePresets 的出现,说白了就是把打包这件事也纳入到 Presets 的统一管理里。你不需要再记住 CPack 那一堆长长的命令行参数,也不用担心团队成员用的打包参数不一致。一切都在 CMakePresets.json 里定义好,清晰、可复现。
packagePresets 的基本结构
packagePresets 的写法和 configurePresets、buildPresets 很像。它也是 CMakePresets.json 里的一个顶级数组。我习惯把它放在 buildPresets 后面,因为打包通常是构建完成后的下一步。
{
"version": 6,
"configurePresets": [...],
"buildPresets": [...],
"packagePresets": [
{
"name": "default",
"hidden": false,
"description": "默认打包配置",
"generator": "TGZ",
"configurations": ["Release"],
"installDir": "$env{HOME}/packages",
"packageName": "MyApp-${presetName}",
"packageVersion": "1.0.0",
"variables": {
"CPACK_PACKAGE_CONTACT": "dev@example.com",
"CPACK_PACKAGE_DESCRIPTION_SUMMARY": "My Application"
}
}
]
}
这里有几个关键字段,我简单说一下:
- generator:指定打包格式。TGZ 是 tar.gz,还有 DEB、RPM、NSIS 等。具体用哪个,看你目标平台。
- configurations:指定构建配置。Release、Debug 都行。我一般只打包 Release,毕竟没人想用 Debug 版本上线。
- installDir:打包产物的输出目录。注意,这个目录是相对于构建目录的。
- variables:这里可以设置 CPack 的变量。说白了,就是你在 CPackConfig.cmake 里写的那堆东西,现在可以直接在 Presets 里定义。
如何触发打包
定义好 packagePresets 之后,怎么用呢?很简单,一条命令搞定:
cmake --build --preset default
cmake --package --preset default
嗯,你没看错。CMake 3.23 之后引入了 --package 选项,专门用来触发打包。它会自动读取当前构建目录下的 CMakePresets.json,找到对应的 packagePreset,然后调用 CPack。
我记得第一次用这个命令的时候,还特意去查了文档,生怕它找不到 Preset。结果一试,发现 CMake 会自动关联构建目录和 Preset 文件。说白了,只要你用 --build --preset 构建过,构建目录里就会生成一个 CMakePresets.json 的副本,--package --preset 直接读它就行。
多平台打包的实战经验
在实际项目中,我们经常需要为不同平台生成不同的包。比如 Linux 下打 DEB,Windows 下打 NSIS 安装包。这时候 packagePresets 的继承机制就派上用场了。
{
"packagePresets": [
{
"name": "base",
"hidden": true,
"generator": "TGZ",
"configurations": ["Release"],
"variables": {
"CPACK_PACKAGE_VENDOR": "MyCompany"
}
},
{
"name": "linux-deb",
"inherits": ["base"],
"generator": "DEB",
"variables": {
"CPACK_DEBIAN_PACKAGE_MAINTAINER": "dev@example.com"
}
},
{
"name": "win-installer",
"inherits": ["base"],
"generator": "NSIS",
"variables": {
"CPACK_NSIS_DISPLAY_NAME": "MyApp Installer"
}
}
]
}
你看,我定义了一个隐藏的 base Preset,里面放通用的配置。然后 linux-deb 和 win-installer 分别继承它,只覆盖平台相关的部分。这样既减少了重复代码,又保证了配置的一致性。
与 CI/CD 的集成
packagePresets 最大的价值,其实是在 CI/CD 流水线里。你想想看,以前我们在 Jenkins 或 GitLab CI 里写打包脚本,每个项目一套,维护起来头大。现在好了,只要在 CMakePresets.json 里定义好,CI 里只需要一行命令:
cmake --package --preset linux-deb
而且,因为 Preset 是版本控制的,所以每次代码变更,打包配置也跟着变。不会出现「CI 脚本改了,但代码没更新」这种尴尬情况。
我曾经在一个跨平台项目里,用 packagePresets 同时管理了 Linux 的 DEB、RPM,Windows 的 NSIS,还有 macOS 的 DMG。每个平台一个 Preset,CI 里根据操作系统选择对应的 Preset。整个打包流程从原来的 200 多行 shell 脚本,缩减到不到 10 行。嗯,那感觉,就像把一堆乱麻理成了整齐的线团。
SVG 流程图:packagePresets 的核心逻辑
一些实用技巧
最后,分享几个我在实际项目中积累的小技巧:
- 版本号统一管理:我习惯在 packagePresets 里定义 packageVersion,而不是在 CMakeLists.txt 里写死。这样换版本时,只需要改 Presets 文件,不用动 CMake 代码。
- 条件打包:如果你需要根据条件选择不同的打包配置,可以用
condition字段。比如只在 Release 模式下打 DEB 包,Debug 模式打 TGZ 包。 - 测试打包产物:每次打包后,我建议在 CI 里加一个步骤,验证包的内容是否正确。比如检查 DEB 包里的二进制文件是否是可执行的,或者 NSIS 安装包是否能静默安装。我曾经遇到过打包后少了动态库的情况,还好在 CI 里发现了,没发到客户手里。
好了,关于 packagePresets 的内容就这些。如果你在项目中遇到了打包相关的坑,欢迎随时交流。毕竟,打包这件事,看着简单,但真正做好,还是需要不少经验的。