3、CPack生成器详解(上):Archive生成器(TGZ/TBZ2/TXZ/ZIP)、STGZ生成器(自解压)、External生成器

好,咱们接着往下聊。上一章我们把 CPack 的配置骨架搭好了,CMakeLists.txt 里该写的也都写了。但说实话,配置只是第一步,真正决定你的安装包长什么样的,是 生成器(Generator)。

CPack 的生成器,说白了就是「打包引擎」。你告诉它「我要一个压缩包」,它就去调 tar 或 zip;你告诉它「我要一个自解压安装包」,它就生成一个带解压逻辑的 Shell 脚本。我刚开始接触 CPack 时,以为生成器就是换个后缀名那么简单,结果在 Linux 上打包给 Windows 用户用,闹了笑话。嗯,这里面的门道,咱们今天一次性说清楚。

3.1 Archive 生成器:最通用的打包方式

Archive 生成器,顾名思义,就是生成各种压缩格式的归档文件。它支持 TGZ、TBZ2、TXZ、ZIP 这四种主流格式。我个人习惯把它叫做「万能生成器」,因为几乎在所有平台上都能用。

3.1.1 支持的格式与底层工具

先看一张表,把四种格式的底层依赖和适用场景说清楚:

生成器名称 文件后缀 底层工具 压缩率 跨平台性
TGZ .tar.gz tar + gzip 中等 Linux/macOS 原生,Windows 需额外工具
TBZ2 .tar.bz2 tar + bzip2 较高 同上,但 bzip2 在 Windows 上更少见
TXZ .tar.xz tar + xz 最高 Linux 主流,macOS 需安装 xz-utils
ZIP .zip zip/unzip 中等 Windows 原生支持,所有平台通用

你可能会问:「那我该选哪个?」我的建议是:面向 Linux 用户,优先用 TGZ;面向 Windows 用户,必须用 ZIP。为什么?因为 Windows 用户拿到一个 .tar.gz 文件,大概率会愣住——「这玩意儿怎么打开?」我在项目中就遇到过,给客户发了一个 TGZ 包,对方回邮件问「是不是发错了,我要的是安装包」。从那以后,只要目标平台有 Windows,我一定加一个 ZIP 生成。

3.1.2 配置示例与避坑

配置 Archive 生成器非常简单。在 CMakeLists.txt 里加上一行:

set(CPACK_GENERATOR "TGZ" "ZIP")

然后运行 cpack,你就会在构建目录下看到两个文件:MyProject-1.0.0-Linux.tar.gz 和 MyProject-1.0.0-Linux.zip。

但这里有个坑:文件名中的架构信息。默认情况下,CPack 会把当前机器的架构(比如 x86_64)加到文件名里。如果你在 CI 上打包,想生成一个「无架构」的通用包,怎么办?

我曾经被这个坑过——在 Jenkins 上打出来的包叫 MyProject-1.0.0-Linux-x86_64.tar.gz,结果测试人员拿到 ARM 机器上,一看文件名就以为包不对。其实内容是一样的,但名字误导人。

解决办法是手动覆盖文件名模板:

set(CPACK_PACKAGE_FILE_NAME "MyProject-${CPACK_PACKAGE_VERSION}")
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)

这样生成的文件名就变成了 MyProject-1.0.0.tar.gz,干净利落。

小技巧: 如果你需要同时生成多个格式,可以用 CPACK_GENERATOR 设置多个值。但要注意,ZIP 格式在 Linux 上需要安装 zip 工具,否则会报错。我一般会在 CI 脚本里先检查一下:which zip || apt-get install -y zip。

3.2 STGZ 生成器:自解压的 Shell 脚本

STGZ 全称是 Self-Extracting Tar GZip,说白了就是一个 Shell 脚本 + 压缩包 的合体。用户拿到一个 .sh 文件,直接 sh MyProject.sh 就能解压安装,不需要任何额外工具。

你想想看,这玩意儿在什么场景下最有用?给 Linux 新手用户分发软件。你不需要教他们怎么解压 tar.gz,也不需要让他们装 zip,一个脚本搞定一切。我记得有一次给一个嵌入式团队做工具链分发,对方工程师对 Linux 不熟,我直接扔了一个 STGZ 包过去,他们双击运行就装好了,省了我不少售后时间。

3.2.1 配置与定制

启用 STGZ 生成器同样简单:

set(CPACK_GENERATOR "STGZ")

生成的文件名默认是 MyProject-1.0.0-Linux.sh。运行 cpack 后,你会得到一个可执行的 Shell 脚本。

但这里有个关键点:STGZ 生成器允许你定制安装过程中的提示信息。比如你想在解压前显示一段欢迎语,或者让用户选择安装路径,都可以通过 CPack 变量实现:

set(CPACK_STGZ_INSTALL_SCRIPT "${CMAKE_SOURCE_DIR}/cmake/stgz_install.sh")
set(CPACK_STGZ_PROMPT "欢迎安装 MyProject!请输入安装路径 [默认: /usr/local]: ")

其中 stgz_install.sh 是你自己写的脚本,可以包含任何 Shell 命令。我一般会在里面加一个检查依赖的逻辑:

#!/bin/sh
# 检查是否安装了 libfoo
if ! ldconfig -p | grep libfoo > /dev/null 2>&1; then
  echo "错误:未检测到 libfoo,请先安装。"
  exit 1
fi
注意: STGZ 生成器生成的脚本是纯文本的,用户可以用 cat 或 less 查看内容。所以不要在脚本里放任何敏感信息(比如密码、密钥)。我曾经见过有人把数据库密码硬编码在 STGZ 脚本里,结果被用户直接看到了……嗯,那场面挺尴尬的。

3.2.2 STGZ 的局限性

STGZ 虽然方便,但也不是万能的。它有几个硬伤:

  • 只支持 Linux/macOS:Windows 上没法直接运行 .sh 脚本。
  • 解压速度慢:因为脚本里内嵌了 base64 编码的压缩包,解压时需要先解码再解压,比直接解压 tar.gz 慢不少。
  • 文件体积大:base64 编码会让体积增加约 33%。如果你的安装包本身就有几百 MB,那 STGZ 就不太合适了。

所以我的建议是:STGZ 适合小工具、脚本库、或者给非技术用户的分发。如果是大型软件,还是老老实实用 Archive 生成器吧。

3.3 External 生成器:接入第三方打包工具

External 生成器,说白了就是一个「桥接器」。它允许你调用外部的打包工具,比如 NSIS(Windows 安装包)、PackageMaker(macOS .pkg)、或者你自己写的打包脚本。

你可能会问:「CPack 自己不是能生成安装包吗?为什么还要用 External?」原因很简单:有些场景 CPack 搞不定。比如你想生成一个带图形界面的 Windows 安装向导,或者你想生成一个 macOS 的 .dmg 文件,CPack 原生不支持这些格式。这时候,External 生成器就派上用场了。

3.3.1 配置方式

External 生成器的配置稍微复杂一点,因为它需要你提供一个外部的打包脚本。基本配置如下:

set(CPACK_GENERATOR "External")
set(CPACK_EXTERNAL_PACKAGE_SCRIPT "${CMAKE_SOURCE_DIR}/cmake/external_package.cmake")

然后在 external_package.cmake 里,你可以调用任何外部命令。比如调用 NSIS 生成 Windows 安装包:

# external_package.cmake
set(NSIS_SCRIPT "${CMAKE_SOURCE_DIR}/installer.nsi")
execute_process(
  COMMAND makensis ${NSIS_SCRIPT}
  WORKING_DIRECTORY ${CPACK_TEMPORARY_DIRECTORY}
  RESULT_VARIABLE NSIS_RESULT
)
if(NOT NSIS_RESULT EQUAL 0)
  message(FATAL_ERROR "NSIS 打包失败!")
endif()

我在项目中用过 External 生成器来对接 Inno Setup(Windows 安装工具)。当时客户要求安装包必须带一个「选择组件」的界面,CPack 原生做不到,我就写了一个 Inno Setup 脚本,通过 External 生成器调用它。效果还不错,就是调试起来有点麻烦——你得先确保外部工具在 PATH 里。

核心要点: External 生成器不会帮你做任何打包工作,它只是提供了一个「钩子」,让你在 CPack 的流程中插入自己的逻辑。所以你需要自己搞定外部工具的安装、脚本编写、以及错误处理。

3.3.2 适用场景总结

External 生成器适合以下场景:

  • 需要生成特定平台的安装包(如 .exe、.msi、.dmg)
  • 需要复杂的安装逻辑(如多语言、自定义组件、注册表操作)
  • 需要集成已有的打包工具链(比如公司内部自研的打包工具)

但要注意,External 生成器有一个明显的缺点:可移植性差。你的 CMakeLists.txt 里如果用了 External 生成器,那其他开发者构建时,必须安装对应的外部工具。否则 CPack 会报错。所以我的建议是:只在 CI 或特定平台上启用 External 生成器,不要把它作为默认配置。

3.4 三种生成器的对比与选择

最后,咱们用一张表总结一下这三种生成器的特点:

生成器 输出格式 跨平台性 定制能力 推荐场景
Archive .tar.gz / .zip 等 高(ZIP 全平台通用) 低(仅控制文件名和压缩级别) 通用分发、CI 产物、库文件
STGZ .sh(自解压脚本) 中(仅 Linux/macOS) 中(可定制安装提示和前置脚本) 小工具分发、非技术用户
External 任意(由外部工具决定) 低(依赖外部工具) 高(完全自定义) 平台专属安装包、复杂安装逻辑

我个人习惯是:默认用 Archive 生成 TGZ 和 ZIP,覆盖 90% 的场景。如果目标用户是 Linux 新手,再加一个 STGZ。只有遇到特殊需求(比如 Windows 安装向导),才动用 External 生成器。你想想看,这样既保证了通用性,又不会把配置搞得太复杂。

嗯,这一章的内容就到这里。下一章咱们继续聊剩下的生成器——包括 NSIS、PackageMaker、以及 RPM/DEB 这些包管理器的打包方式。到时候你会发现,CPack 的生态比你想的要丰富得多。