24、CPack配置文件:CPackConfig.cmake、CPackSourceConfig.cmake、常用变量设置

说到CPack,很多人第一反应就是「哦,打包工具嘛」。但真到自己动手配置时,往往会被那两个配置文件搞得一头雾水。我刚开始接触CPack时也踩过不少坑,今天就把这块掰开揉碎了讲清楚。

核心要点:CPack的打包行为完全由两个配置文件驱动——CPackConfig.cmakeCPackSourceConfig.cmake。前者控制二进制包,后者控制源码包。

24.1 两个配置文件的分工

说白了,这两个文件就像是一对双胞胎,长得像但干的事不一样。

配置文件 用途 典型输出
CPackConfig.cmake 控制二进制打包(可执行文件、库、资源) .deb, .rpm, .dmg, .exe 安装包
CPackSourceConfig.cmake 控制源码打包(源代码、CMakeLists.txt) .tar.gz, .zip 源码压缩包

我个人的习惯是:先配好CPackConfig.cmake,再根据需要调整CPackSourceConfig.cmake。因为大多数场景下,我们更关心的是给用户一个能直接安装的二进制包。

24.2 CPackConfig.cmake 常用变量

这个文件里能设置的变量很多,但真正常用的就那么几个。我列出来给你看:

# 基础信息
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.2.3")
set(CPACK_PACKAGE_VENDOR "MyCompany")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "一个很棒的应用")

# 安装路径
set(CPACK_PACKAGE_INSTALL_DIRECTORY "MyApp")
set(CPACK_PACKAGE_INSTALL_REGISTRY_KEY "MyApp")

# 许可证
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.txt")

# 生成器
set(CPACK_GENERATOR "DEB;RPM;NSIS")

小技巧:CPACK_PACKAGE_VERSION 最好和项目版本号保持一致。我见过有人手写版本号,结果项目升级了包名还是旧的,用户安装时直接覆盖了旧版本——那叫一个乱。

24.3 生成器相关的变量

不同的打包格式,需要的变量也不一样。举个例子:

# DEB 包专用
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "developer@mycompany.com")
set(CPACK_DEBIAN_PACKAGE_SECTION "devel")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1, libc6 (>= 2.31)")

# RPM 包专用
set(CPACK_RPM_PACKAGE_RELEASE "1")
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
set(CPACK_RPM_PACKAGE_GROUP "Development/Tools")

# NSIS (Windows) 包专用
set(CPACK_NSIS_DISPLAY_NAME "MyApp 1.2.3")
set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\MyApp\\Uninstall.lnk' '$INSTDIR\\uninstall.exe'")

嗯,这里要注意:不同生成器的变量名有固定前缀。DEB 用 CPACK_DEBIAN_,RPM 用 CPACK_RPM_,NSIS 用 CPACK_NSIS_。记不住没关系,查文档就行,我刚开始也经常翻车。

24.4 CPackSourceConfig.cmake 的配置

源码打包相对简单,但有个坑很多人会踩——默认会把整个构建目录都打包进去。你想想看,那里面有多少临时文件?

我曾经有一次打包源码,结果把 build 目录下的 .o 文件全带进去了,压缩包直接膨胀到 500MB。后来才发现是忘了排除构建目录。

# 源码包配置
set(CPACK_SOURCE_GENERATOR "TGZ;ZIP")

# 排除构建目录和版本控制目录
set(CPACK_SOURCE_IGNORE_FILES
  "/build/"
  "/.git/"
  "/.svn/"
  "~$"
  ".swp$"
)

# 也可以手动指定要打包的文件
set(CPACK_SOURCE_INSTALLED_DIRECTORIES
  "${CMAKE_SOURCE_DIR}/src"
  "${CMAKE_SOURCE_DIR}/include"
  "${CMAKE_SOURCE_DIR}/CMakeLists.txt"
)

注意:CPACK_SOURCE_IGNORE_FILES 使用的是正则表达式匹配。如果你写 /build/,它会匹配所有路径中包含 /build/ 的目录。但如果你写 build,那连文件名里带 build 的文件也会被排除——我吃过这个亏。

24.5 组件打包

如果你的项目比较大,比如有客户端、服务端、文档等多个部分,可以用组件打包:

# 启用组件打包
set(CPACK_COMPONENTS_ALL client server docs)

# 每个组件的描述
set(CPACK_COMPONENT_CLIENT_DISPLAY_NAME "客户端")
set(CPACK_COMPONENT_CLIENT_DESCRIPTION "用户使用的桌面客户端")

set(CPACK_COMPONENT_SERVER_DISPLAY_NAME "服务端")
set(CPACK_COMPONENT_SERVER_DESCRIPTION "后端服务程序")

set(CPACK_COMPONENT_DOCS_DISPLAY_NAME "文档")
set(CPACK_COMPONENT_DOCS_DESCRIPTION "API 文档和用户手册")

# 分组(可选)
set(CPACK_COMPONENT_GROUP_RUNTIME "运行时组件")
set(CPACK_COMPONENT_GROUP_DEVELOPMENT "开发组件")

这样做的好处是:用户安装时可以选择只装客户端,或者只装文档。我记得有个项目,服务端和客户端是分开卖的,用组件打包后,一个安装包就能搞定两种授权场景。

24.6 知识体系结构图

下面这张图帮你理清 CPack 配置文件的整体脉络:

CPack 配置文件知识体系 CPack 打包系统 CPackConfig.cmake CPackSourceConfig.cmake 基础信息变量 生成器变量 组件打包 源码生成器 排除规则 目录指定 二进制包:.deb, .rpm, .dmg, .exe 源码包:.tar.gz, .zip 分工

24.7 实战:一个完整的配置示例

说了这么多,不如直接看一个完整的例子。这是我之前一个项目的配置,你可以参考:

# CPackConfig.cmake

# ---- 项目基本信息 ----
set(CPACK_PACKAGE_NAME "DataPipeline")
set(CPACK_PACKAGE_VERSION "2.1.0")
set(CPACK_PACKAGE_VERSION_MAJOR "2")
set(CPACK_PACKAGE_VERSION_MINOR "1")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_PACKAGE_VENDOR "DataFlow Inc.")
set(CPACK_PACKAGE_CONTACT "support@dataflow.com")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "高性能数据管道处理工具")

# ---- 安装与卸载 ----
set(CPACK_PACKAGE_INSTALL_DIRECTORY "DataPipeline")
set(CPACK_UNINSTALL_NAME "uninstall")

# ---- 许可证与资源 ----
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md")

# ---- 生成器选择 ----
if(WIN32)
  set(CPACK_GENERATOR "NSIS")
  set(CPACK_NSIS_DISPLAY_NAME "DataPipeline ${CPACK_PACKAGE_VERSION}")
  set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
elseif(APPLE)
  set(CPACK_GENERATOR "DragNDrop")
  set(CPACK_DMG_VOLUME_NAME "DataPipeline ${CPACK_PACKAGE_VERSION}")
else()
  set(CPACK_GENERATOR "DEB;RPM")
  set(CPACK_DEBIAN_PACKAGE_MAINTAINER "dev@dataflow.com")
  set(CPACK_RPM_PACKAGE_RELEASE "1")
endif()

# ---- 组件定义 ----
set(CPACK_COMPONENTS_ALL core plugins docs)
set(CPACK_COMPONENT_CORE_DISPLAY_NAME "核心引擎")
set(CPACK_COMPONENT_PLUGINS_DISPLAY_NAME "插件集")
set(CPACK_COMPONENT_DOCS_DISPLAY_NAME "文档")

我的建议:把 CPack 配置单独放到一个 cmake/Packaging.cmake 文件里,然后在主 CMakeLists.txt 中用 include(cmake/Packaging.cmake) 引入。这样主文件不会太臃肿,也方便维护。

24.8 调试技巧

配置写好了,怎么验证对不对?我一般用这几个命令:

# 查看所有 CPack 变量
cmake --build . --target help_vars | grep CPACK

# 生成但不打包(只生成配置文件)
cpack --config CPackConfig.cmake -G DEB --no-package

# 调试组件打包
cpack --config CPackConfig.cmake -G DEB --components core

说白了,CPack 的配置就是「变量驱动」的。你设对了变量,打包就成功了一半。另一半?嗯,就是多试几次,看看生成的包结构对不对。我每次改配置后都会先打一个测试包,确认无误再正式发布。


公众号:蓝海资料掘金营,微信deep3321