3. CPack配置文件:从CMakeLists.txt到CPackConfig.cmake
好,咱们进入CPack配置的核心环节。说实话,很多人在CMake里折腾半天,最后打包这一步反而草草了事。我个人习惯是把CPack配置当作独立模块来对待,而不是随便塞几行代码完事。这一章,咱们就把CMakeLists.txt里的CPack配置、CPackConfig.cmake和CPackSourceConfig.cmake这三个文件彻底讲透。
3.1 CMakeLists.txt中的CPack配置
先说说最常用的方式——直接在CMakeLists.txt里配置CPack。嗯,这里有个关键点:CPack的配置本质上就是设置一堆以CPACK_开头的变量。你想想看,CMake本身不负责打包,它只是把这些变量传递给CPack工具。
核心原则:所有CPACK_变量必须在include(CPack)之前定义,否则不会生效。
来看一个我实际项目里用过的配置模板:
# 项目基本信息
set(CPACK_PACKAGE_NAME "MyEnterpriseApp")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_VENDOR "MyCompany Inc.")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "企业级应用打包示例")
# 维护者信息
set(CPACK_PACKAGE_CONTACT "devops@mycompany.com")
set(CPACK_PACKAGE_MAINTAINERS "Zhang San <zhangsan@mycompany.com>")
# 安装目录前缀
set(CPACK_PACKAGE_INSTALL_DIRECTORY "MyEnterpriseApp")
set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "MyEnterpriseApp")
# 必须最后引入CPack模块
include(CPack)
我在项目中遇到过一个问题:有人把include(CPack)写在了前面,结果后面定义的变量全都没生效。排查了半天才发现是顺序搞反了。所以记住:先定义变量,再include。
3.2 CPackConfig.cmake文件详解
当CMakeLists.txt里写了include(CPack)之后,CMake会生成一个CPackConfig.cmake文件。这个文件在构建目录里,说白了就是所有CPACK_变量的最终快照。
为什么要单独讲这个文件?因为有时候你不想把打包配置和构建配置混在一起。我曾经接手过一个项目,CMakeLists.txt长达2000行,找CPack配置找得我眼睛都快瞎了。后来我建议团队把打包配置抽离到单独的CPackConfig.cmake里。
我的建议:对于复杂项目,直接在项目根目录创建CPackConfig.cmake,然后用include()引入。这样CMakeLists.txt干净,打包配置也独立。
来看看一个典型的CPackConfig.cmake长什么样:
# 文件: CPackConfig.cmake
# 这个文件会被CMakeLists.txt通过include()引入
# 包类型配置
set(CPACK_GENERATOR "TGZ;DEB;RPM")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_SYSTEM_PROCESSOR}")
# 依赖检查
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl-dev (>= 1.1), libcurl4 (>= 7.58)")
set(CPACK_RPM_PACKAGE_REQUIRES "openssl >= 1.1, libcurl >= 7.58")
# 许可证信息
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md")
# 安装后脚本
set(CPACK_INSTALL_SCRIPT "${CMAKE_SOURCE_DIR}/cmake/post_install.cmake")
这里有个坑,我提醒一下:CPACK_GENERATOR的取值顺序会影响打包行为。比如你先写了DEB再写RPM,CPack会按这个顺序依次生成。如果你只想生成其中一种,别偷懒,只写一个就好。
3.3 CPackSourceConfig.cmake文件详解
这个文件很多人不太熟悉,但它其实挺重要的。CPackSourceConfig.cmake专门用于源码打包。你执行cpack --config CPackSourceConfig.cmake时,它就会生成源码包。
为什么要区分源码包和二进制包?我举个例子:有一次我们需要把代码发给客户做二次开发,但客户环境和我们不一样,二进制包跑不起来。这时候源码包就派上用场了。
CPackSourceConfig.cmake的配置方式和CPackConfig.cmake类似,但有一些专属变量:
# 源码包配置
set(CPACK_SOURCE_GENERATOR "TGZ;ZIP")
set(CPACK_SOURCE_IGNORE_FILES
"/build/"
"/.git/"
"/.svn/"
".*~"
"*.swp"
"${CPACK_SOURCE_IGNORE_FILES}"
)
# 源码包名称
set(CPACK_SOURCE_PACKAGE_FILE_NAME
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-source"
)
# 源码包安装前缀
set(CPACK_SOURCE_INSTALL_DIRECTORY
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}"
)
注意:CPACK_SOURCE_IGNORE_FILES这个变量特别容易踩坑。如果你不把构建目录排除掉,源码包会变得巨大无比。我曾经见过一个项目,源码包打了2GB,结果里面90%都是构建产物。
这里有个小技巧:你可以用CPACK_SOURCE_STRIP_FILES来指定哪些文件需要剥离。比如有些注释文件、测试数据,打包时可以直接去掉。
3.4 三个文件的协作关系
这三个文件的关系,我用一个简单的图来说明:
从图上可以看得很清楚:
- CMakeLists.txt是配置的起点,你在这里定义CPACK_变量
- CPackConfig.cmake和CPackSourceConfig.cmake是CMake自动生成的,分别对应二进制包和源码包
- 你也可以手动创建自定义的CPackConfig.cmake来覆盖自动生成的内容
3.5 实际项目中的配置策略
说了这么多理论,来点实际的。我在团队里推行了一套配置策略,分享给你参考:
| 场景 | 推荐做法 | 原因 |
|---|---|---|
| 小型项目(< 10个模块) | 直接在CMakeLists.txt中配置 | 简单直接,维护成本低 |
| 中型项目(10-50个模块) | 创建独立的CPackConfig.cmake | 配置与构建分离,便于团队协作 |
| 大型项目(> 50个模块) | 使用CPackConfig.cmake + 模块化配置 | 按功能拆分配置文件,比如packaging/deb.cmake、packaging/rpm.cmake |
| 需要源码分发 | 额外配置CPackSourceConfig.cmake | 单独控制源码包的内容和排除规则 |
避坑指南:我曾经在CI流水线里直接用cpack命令,结果发现每次打的包文件名都不一样。后来排查发现是CPACK_PACKAGE_FILE_NAME里用了CMAKE_SYSTEM_PROCESSOR,而CI节点的架构不统一。解决方案是:在CI脚本里显式指定架构,或者用set(CPACK_PACKAGE_FILE_NAME ...)固定下来。
最后说一句:CPack的配置其实不复杂,但细节很多。你只要记住三个核心文件各自的职责,再根据项目规模选择合适的配置策略,基本就不会出大问题。下一章咱们会深入具体的生成器配置,到时候再细聊DEB和RPM的那些坑。