7、打包预设(packagePresets):定义 CPack 打包配置。

说实话,很多团队在 CMake 构建上花了不少功夫,但到了「打包」这一步,往往就靠手写脚本或者靠某位同事的本地记忆。我见过太多次了——换台机器打包,出来的安装包结构都不一样。

CMake Presets 里的 packagePresets,就是来解决这个问题的。它让你把 CPack 的打包配置标准化,跟构建配置一样,写进 JSON 文件里,人人共享。

为什么需要打包预设?

你想想看,一个项目最终要交付给用户,无非是几种形式:源码、二进制压缩包、安装程序(比如 NSIS 或 DMG)、或者 deb/rpm 这类系统包。CPack 本身很强大,但它依赖一大堆变量:CPACK_GENERATORCPACK_PACKAGE_NAMECPACK_PACKAGE_VERSION……

这些变量散落在 CMakeLists.txt 里,或者靠命令行传参。一旦项目大了,维护起来就头疼。我个人习惯是把这些打包参数统一放到 packagePresets 里,清晰、可复用、可继承。

核心价值:打包预设让「构建」和「打包」解耦。你可以用同一个构建产物,打出不同格式的包,而不用重新编译。

基本结构

一个 packagePresets 条目,长这样:

{
  "packagePresets": [
    {
      "name": "default",
      "displayName": "默认打包配置",
      "description": "生成 ZIP 压缩包",
      "generator": "ZIP",
      "configurations": ["Release"],
      "variables": {
        "CPACK_PACKAGE_NAME": "MyApp",
        "CPACK_PACKAGE_VERSION": "1.0.0",
        "CPACK_PACKAGE_VENDOR": "MyCompany"
      }
    }
  ]
}

这里 generator 指定了打包格式,variables 里填的就是 CPack 的变量。嗯,这里要注意:configurations 字段是可选的,它指定了用哪个构建配置的产物来打包。如果你不写,默认就用当前构建目录里的东西。

继承与覆盖

打包预设也支持 inherits,这一点跟构建预设和测试预设完全一致。我经常这样用:

{
  "packagePresets": [
    {
      "name": "base",
      "hidden": true,
      "generator": "ZIP",
      "variables": {
        "CPACK_PACKAGE_NAME": "MyApp",
        "CPACK_PACKAGE_VENDOR": "MyCompany"
      }
    },
    {
      "name": "linux-deb",
      "inherits": "base",
      "displayName": "Debian 包",
      "generator": "DEB",
      "variables": {
        "CPACK_PACKAGE_VERSION": "1.0.0",
        "CPACK_DEBIAN_PACKAGE_MAINTAINER": "dev@mycompany.com"
      }
    },
    {
      "name": "macos-dmg",
      "inherits": "base",
      "displayName": "macOS DMG",
      "generator": "DragNDrop",
      "variables": {
        "CPACK_PACKAGE_VERSION": "1.0.0"
      }
    }
  ]
}

你看,base 预设隐藏了,只作为模板。然后 linux-debmacos-dmg 各自继承它,再补充平台特有的变量。这样改一次基础配置,所有打包方式都跟着变。

小技巧:如果你有多个平台共享的变量(比如版本号、项目名),放在 base 预设里。平台特有的变量(比如维护者邮箱、图标路径)放在子预设里。这样维护起来特别清爽。

与构建预设的联动

打包预设里有一个 configurePreset 字段,用来指定它依赖哪个配置预设。为什么要这个?因为 CPack 需要知道构建目录在哪。你指定了 configurePreset,CMake 就能自动找到对应的构建目录。

{
  "packagePresets": [
    {
      "name": "release-package",
      "configurePreset": "release",
      "generator": "ZIP",
      "configurations": ["Release"],
      "variables": {
        "CPACK_PACKAGE_NAME": "MyApp",
        "CPACK_PACKAGE_VERSION": "1.0.0"
      }
    }
  ]
}

这样你只需要运行:

cmake --build --preset release
ctest --preset release
cpack --preset release-package

三步走,干净利落。我曾经在一个跨平台项目里,用这种方式把打包流程从 20 分钟的手动操作压缩到了 30 秒的自动化命令。

常用变量一览

CPack 的变量很多,但常用的其实就这些:

变量名 说明 示例值
CPACK_PACKAGE_NAME 包名 MyApp
CPACK_PACKAGE_VERSION 版本号 1.0.0
CPACK_PACKAGE_VENDOR 供应商 MyCompany
CPACK_PACKAGE_DESCRIPTION 描述 A useful tool
CPACK_GENERATOR 打包格式 ZIP, DEB, NSIS
CPACK_PACKAGE_FILE_NAME 输出文件名模板 MyApp-1.0.0-win64
CPACK_INSTALL_CMAKE_PROJECTS 指定安装的项目 (通常不用改)
注意:如果你在 variables 里定义了 CPACK_GENERATOR,它会覆盖预设顶层 generator 字段。我个人建议统一用顶层的 generator 字段,更直观。

避坑指南

我曾经在一个项目里,打包预设里写了 generator: "DEB",但构建机器上没有安装 dpkg 相关工具。结果 CPack 报错,说找不到生成器。这个坑其实很好避免——在打包预设里加一个 vendor 字段?不,CPack 没有这个字段。但你可以用 condition 来做平台判断。

{
  "packagePresets": [
    {
      "name": "linux-deb",
      "condition": {
        "type": "equals",
        "lhs": "${hostSystemName}",
        "rhs": "Linux"
      },
      "generator": "DEB",
      "variables": {
        "CPACK_PACKAGE_NAME": "MyApp",
        "CPACK_PACKAGE_VERSION": "1.0.0"
      }
    }
  ]
}

这样,只有在 Linux 上才会启用这个预设。Windows 和 macOS 上它会被自动忽略。嗯,这个 condition 的用法跟构建预设里是一样的,非常实用。

知识结构图

下面这张图,帮你理清打包预设在整个 CMake Presets 体系中的位置:

打包预设(packagePresets)知识结构 CMake Presets 体系 configurePresets buildPresets testPresets packagePresets packagePresets 核心要素 generator + variables 指定打包格式和参数 inherits 继承 复用基础配置 condition 条件 平台/环境判断

实际项目中的用法

最后,分享一个我实际项目里的打包预设配置。这个项目需要同时打出 Windows 安装包和 Linux 压缩包:

{
  "packagePresets": [
    {
      "name": "win-installer",
      "configurePreset": "msvc-release",
      "generator": "NSIS",
      "configurations": ["Release"],
      "variables": {
        "CPACK_PACKAGE_NAME": "DataProcessor",
        "CPACK_PACKAGE_VERSION": "2.3.1",
        "CPACK_PACKAGE_VENDOR": "DataTech",
        "CPACK_NSIS_DISPLAY_NAME": "数据处理工具",
        "CPACK_NSIS_INSTALL_ROOT": "$PROGRAMFILES64"
      }
    },
    {
      "name": "linux-tarball",
      "configurePreset": "gcc-release",
      "generator": "TGZ",
      "configurations": ["Release"],
      "variables": {
        "CPACK_PACKAGE_NAME": "dataprocessor",
        "CPACK_PACKAGE_VERSION": "2.3.1",
        "CPACK_PACKAGE_FILE_NAME": "dataprocessor-2.3.1-linux-x64"
      }
    }
  ]
}

你看,两个预设共用同一个版本号,但打包格式、文件名、平台特定参数都各自独立。维护起来非常方便——改版本号只需要改一个地方。

好了,关于打包预设就聊这么多。说白了,它就是给 CPack 戴上了一顶「标准化」的帽子。让团队里每个人都能用同样的方式打出同样的包,不再出现「我机器上能打包,你机器上就不行」这种尴尬事。


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