1、CPack入门:CPack是什么、CPack在CMake生态中的位置、CPack与CMake的关系、CPack的工作流程

1.1 CPack到底是什么?

CPack,说白了就是CMake自带的打包工具。

你写完了代码,用CMake构建好了项目,生成了可执行文件、库文件、配置文件……然后呢?怎么把这些东西发给用户?

手动压缩?写脚本?维护多个平台的安装包格式?

嗯,CPack就是来解决这个问题的。

我个人习惯把CPack理解为「CMake的打包插件」。它不负责编译,不负责链接,只负责一件事:把你构建好的产物,打包成用户能直接安装的格式。

比如:

  • Windows上的.exe安装包(NSIS、WiX)
  • macOS上的.dmg
  • Linux上的.deb、.rpm、.tar.gz

我在项目中遇到过最尴尬的情况:代码写得挺好,CMakeLists.txt也写得挺规范,结果交付的时候,运维说「你这包怎么装不上?」。后来才发现,是CPack的依赖声明漏了。从那以后,我对CPack的每个字段都不敢马虎。

1.2 CPack在CMake生态中的位置

CMake生态圈里,有几个核心工具,我画个图帮你理清关系:

CMake 构建系统生成器 CTest 测试工具 CPack 打包工具 CDash 持续集成面板 .deb / .rpm / .dmg / .exe

你看,CMake是核心,它负责生成构建系统。CTest负责测试,CDash负责展示测试结果,而CPack负责打包。这四个工具组成了CMake的完整工具链。

你想想看,如果没有CPack,你要怎么交付?写Shell脚本?用Python的打包库?维护一套自己的打包逻辑?

CPack的好处就在于:它和CMake共享同一套变量和配置。你在CMakeLists.txt里定义的项目信息、安装规则、依赖关系,CPack可以直接拿来用。

核心观点:CPack不是独立工具,它是CMake生态的「最后一公里」。没有它,你的项目就停留在「能编译」的阶段,到不了「能安装」的阶段。

1.3 CPack与CMake的关系

很多人刚接触CPack时会问:CPack是CMake的一部分吗?还是独立的?

答案是:CPack是CMake的模块,不是独立工具。

具体来说:

  • CPack的配置写在CMakeLists.txt里,或者单独的CPackConfig.cmake文件里
  • CPack使用CMake的install()命令定义的安装规则
  • CPack读取CMake的变量,比如CMAKE_INSTALL_PREFIX、PROJECT_NAME、PROJECT_VERSION

我举个例子你就明白了:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyApp VERSION 1.2.3)

# 安装可执行文件
install(TARGETS myapp DESTINATION bin)

# 安装配置文件
install(FILES config.ini DESTINATION etc)

# 启用CPack
include(CPack)

# 设置打包参数
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_GENERATOR "DEB;RPM;TGZ")

看到了吗?include(CPack) 这一行,就把CPack模块加载进来了。之后你运行 cpack 命令,它就会读取这些配置,生成对应的安装包。

小技巧:我个人习惯把CPack的配置单独放到 CPackConfig.cmake 文件里,然后在CMakeLists.txt中通过 include(CPackConfig.cmake) 引入。这样主构建文件不会太臃肿,打包相关的配置也容易维护。

1.4 CPack的工作流程

CPack的工作流程,其实就三步。我画个流程图帮你理解:

步骤1:构建项目 cmake --build . 步骤2:安装到临时目录 cmake --install . --prefix /tmp/staging 步骤3:打包 cpack -G DEB CPack 内部流程 CPack 内部干了什么? 1. 读取 CPackConfig.cmake 配置 2. 收集 install() 定义的安装规则 3. 调用对应生成器(DEB/RPM/NSIS...) 4. 输出安装包文件

流程其实很简单:

  1. 先构建:把代码编译成可执行文件和库文件
  2. 再安装:把构建产物安装到一个临时目录(staging directory)
  3. 最后打包:CPack读取安装目录里的文件结构,按照你指定的格式打包

这里有个关键点,我曾经踩过坑:CPack打包时,依赖的查找路径和运行时不一样。

举个例子,你的程序依赖libfoo.so,编译时能找到,但CPack打包时,它不会自动把libfoo.so打进包里。你需要显式地用install()命令把依赖的库也安装到临时目录。

避坑指南:我曾经有一个项目,在开发机上跑得好好的,打包发给客户后,客户说「缺少libssl.so.1.1」。查了半天,发现是CPack没有把OpenSSL的运行时库打包进去。从那以后,我养成了一个习惯:每次打包前,先用 ldd 检查一下可执行文件的依赖,确保所有运行时库都被 install() 覆盖到了。

1.5 一个完整的CPack入门示例

光说不练假把式。我们来看一个完整的例子:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(HelloCPack VERSION 1.0.0 LANGUAGES CXX)

# 添加可执行文件
add_executable(hello main.cpp)

# 安装规则
install(TARGETS hello DESTINATION bin)
install(FILES README.md DESTINATION share/doc/HelloCPack)

# ---------- CPack 配置 ----------
set(CPACK_PACKAGE_NAME "HelloCPack")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A simple CPack demo")
set(CPACK_PACKAGE_VENDOR "My Company")
set(CPACK_PACKAGE_CONTACT "support@mycompany.com")

# 指定打包格式
set(CPACK_GENERATOR "TGZ;DEB")

# 如果是DEB包,需要额外配置
if(CPACK_GENERATOR MATCHES "DEB")
  set(CPACK_DEBIAN_PACKAGE_MAINTAINER "developer@mycompany.com")
  set(CPACK_DEBIAN_PACKAGE_SECTION "devel")
  set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
endif()

# 加载CPack模块(必须放在所有配置之后)
include(CPack)

构建和打包命令:

# 构建
mkdir build && cd build
cmake ..
cmake --build .

# 打包
cpack -G TGZ    # 生成 .tar.gz
cpack -G DEB    # 生成 .deb

运行后,你会在build目录下看到:

  • HelloCPack-1.0.0-Linux.tar.gz
  • HelloCPack-1.0.0-Linux.deb

提示:如果你在Windows上,可以试试 cpack -G NSIS 生成exe安装包。NSIS是CPack默认支持的Windows安装包生成器,配置起来也很简单。

1.6 小结

这一章我们聊了:

  • CPack是CMake的打包模块,不是独立工具
  • 它在CMake生态中负责「最后一公里」的交付
  • 它和CMake共享配置,通过include(CPack)启用
  • 工作流程:构建 → 安装到临时目录 → 打包

下一章,我们会深入CPack的配置体系,聊聊那些让你头疼的CPACK_XXX变量到底该怎么配。嗯,到时候我会分享一些我踩过的坑,保证让你少走弯路。