21、CPack组件安装:COMPONENT参数、CPACK_COMPONENTS_ALL、按组件打包

说到CPack,很多人的第一反应就是「把项目打包成一个压缩包完事」。嗯,这确实是最常见的用法。但我在实际项目中遇到过好几次这样的场景:用户只想装个运行时库,结果把整个SDK和文档都塞进去了;或者客户要的是开发包,你却只给了个可执行文件。

这时候,组件安装就派上用场了。说白了,就是把你的安装内容拆成几个逻辑组,让用户按需取用。我个人的习惯是,只要项目超过三个安装目标,就会考虑用组件来组织。

21.1 什么是组件安装?

组件安装,就是给每个安装目标打上一个标签。这个标签叫 COMPONENT。CPack在打包时,可以根据这些标签来筛选内容。

举个例子,一个典型的C++库项目,通常包含三类内容:

  • Runtime:运行时库、动态链接库、可执行文件
  • SDK:头文件、静态库、CMake配置文件、pkg-config文件
  • Docs:API文档、示例代码、许可证文件

你想想看,用户如果只是跑个程序,他需要头文件和文档吗?不需要。反过来,开发者要编译自己的代码,他需要运行时库吗?也需要,但更核心的是头文件和库文件。

核心思想:组件 = 安装内容的逻辑分组。每个组件可以独立安装、独立打包。

21.2 在install()命令中使用COMPONENT

在CMake中,给安装目标指定组件非常简单——加一个 COMPONENT 参数就行。

# 运行时组件
install(TARGETS myapp
  RUNTIME DESTINATION bin
  COMPONENT Runtime
)

# SDK组件
install(TARGETS mylib
  ARCHIVE DESTINATION lib
  COMPONENT SDK
)
install(FILES include/mylib.h
  DESTINATION include
  COMPONENT SDK
)

# 文档组件
install(FILES README.md LICENSE
  DESTINATION share/doc/myapp
  COMPONENT Docs
)

这里要注意,COMPONENT 的名字是大小写敏感的。我个人习惯用首字母大写的驼峰命名,比如 RuntimeSDKDocs。这样在后续配置中一眼就能认出来。

小技巧:如果你不指定 COMPONENT,CMake会使用默认组件,名字叫 Unspecified。我建议还是显式指定,避免混淆。

21.3 CPACK_COMPONENTS_ALL:告诉CPack有哪些组件

光在 install() 里写了组件还不够,你还得告诉CPack:「嘿,我这里有这些组件,你看着办。」

这个变量就是 CPACK_COMPONENTS_ALL。它通常放在项目的根 CMakeLists.txt 中,或者单独一个 CPackConfig.cmake 文件里。

set(CPACK_COMPONENTS_ALL Runtime SDK Docs)

嗯,就这么简单。但有个坑——顺序很重要。CPack在生成安装包时,会按照这个列表的顺序来处理组件。我建议把最常用的组件放在前面,比如 Runtime 放第一个。

如果你不设置 CPACK_COMPONENTS_ALL,CPack会尝试自动检测所有用到的组件。但我在项目中遇到过,自动检测有时会漏掉一些组件,尤其是当组件名在变量中动态生成时。所以,我建议总是显式声明

21.4 按组件打包:生成独立的安装包

组件定义好了,接下来就是打包。CPack支持两种模式:

  • 全量打包:所有组件打成一个包
  • 按组件打包:每个组件生成独立的安装包

要启用按组件打包,需要设置:

set(CPACK_COMPONENTS_GROUPING ONE_PER_GROUP)
# 或者
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)

我个人更常用 ONE_PER_GROUP,这样每个组件都会生成一个独立的安装包。比如:

  • MyApp-Runtime-1.0.0-win64.exe
  • MyApp-SDK-1.0.0-win64.exe
  • MyApp-Docs-1.0.0-win64.exe

用户只需要下载自己需要的部分。这在企业级部署中非常实用——运维人员只需要 Runtime,开发人员才需要 SDK。

注意:按组件打包时,每个组件都是独立的安装包。如果组件之间有依赖关系(比如 SDK 依赖 Runtime),你需要自己处理依赖提示。CPack 不会自动帮你解决依赖。

21.5 组件描述与分组

为了让用户知道每个组件是干什么的,你可以给组件添加描述信息:

set(CPACK_COMPONENT_RUNTIME_DISPLAY_NAME "Runtime")
set(CPACK_COMPONENT_RUNTIME_DESCRIPTION "运行时库和可执行文件")

set(CPACK_COMPONENT_SDK_DISPLAY_NAME "Development Kit")
set(CPACK_COMPONENT_SDK_DESCRIPTION "头文件、静态库和CMake配置")

set(CPACK_COMPONENT_DOCS_DISPLAY_NAME "Documentation")
set(CPACK_COMPONENT_DOCS_DESCRIPTION "API文档和示例代码")

这些描述在图形化安装界面(比如NSIS安装器)中会显示出来,用户勾选时一目了然。

另外,你还可以把组件分组。比如把 Runtime 和 SDK 归为「核心组件」,Docs 归为「可选组件」:

set(CPACK_COMPONENT_RUNTIME_GROUP "核心组件")
set(CPACK_COMPONENT_SDK_GROUP "核心组件")
set(CPACK_COMPONENT_DOCS_GROUP "文档")

分组后,安装界面会按组展示,用户体验更好。

21.6 实战:一个完整的组件打包配置

下面是一个完整的例子,把前面讲的内容串起来:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyApp VERSION 1.0.0)

# 安装运行时
install(TARGETS myapp
  RUNTIME DESTINATION bin
  COMPONENT Runtime
)

# 安装SDK
install(TARGETS mylib
  ARCHIVE DESTINATION lib
  COMPONENT SDK
)
install(FILES include/mylib.h
  DESTINATION include
  COMPONENT SDK
)

# 安装文档
install(FILES README.md LICENSE
  DESTINATION share/doc/myapp
  COMPONENT Docs
)

# CPack配置
set(CPACK_GENERATOR "NSIS")
set(CPACK_COMPONENTS_ALL Runtime SDK Docs)

set(CPACK_COMPONENT_RUNTIME_DISPLAY_NAME "Runtime")
set(CPACK_COMPONENT_RUNTIME_DESCRIPTION "运行时库和可执行文件")
set(CPACK_COMPONENT_SDK_DISPLAY_NAME "SDK")
set(CPACK_COMPONENT_SDK_DESCRIPTION "开发工具包")
set(CPACK_COMPONENT_DOCS_DISPLAY_NAME "Documentation")
set(CPACK_COMPONENT_DOCS_DESCRIPTION "API文档")

set(CPACK_COMPONENTS_GROUPING ONE_PER_GROUP)

include(CPack)

构建并打包:

mkdir build && cd build
cmake ..
cpack

你会看到生成了三个独立的安装包:MyApp-Runtime-1.0.0-win64.exeMyApp-SDK-1.0.0-win64.exeMyApp-Docs-1.0.0-win64.exe

21.7 组件安装的SVG流程图

下面这张图展示了组件安装的完整流程,从CMake配置到最终生成独立安装包:

CPack组件安装流程 CMakeLists.txt 中定义组件 install(TARGETS ... COMPONENT Runtime) install(FILES ... COMPONENT SDK) set(CPACK_COMPONENTS_ALL Runtime SDK Docs) set(CPACK_COMPONENTS_GROUPING ONE_PER_GROUP) cpack 打包 MyApp-Runtime.exe MyApp-SDK.exe MyApp-Docs.exe

21.8 避坑指南

我在项目中踩过几个坑,分享给你:

  • 组件名不要用空格:比如 Runtime Files 这种,CPack解析时会出问题。用下划线或驼峰。
  • CPACK_COMPONENTS_ALL 必须和 install() 中的组件名完全一致:大小写、拼写都不能错。我曾经因为把 Runtime 写成了 runtime,结果打包出来少了一个组件。
  • NSIS 安装器对组件支持最好:如果你用 NSIS 生成器,用户安装时可以看到组件选择界面。如果用 ZIPTGZ,组件打包就是生成多个压缩包。
  • 组件依赖需要自己处理:CPack 不会检查组件间的依赖。比如 SDK 依赖 Runtime,你需要在文档中说明,或者在安装脚本中做检查。

我的习惯:每次添加新组件后,我都会运行 cpack -D CPACK_COMPONENTS_ALL=... --config CPackConfig.cmake 来验证一下,确保所有组件都能正确生成。

21.9 总结

组件安装是CPack中非常实用的功能。它让你的安装包从「一刀切」变成了「按需取用」。用户开心,你也省心。

核心就三步:

  1. install() 中给每个目标加上 COMPONENT 参数
  2. 在CPack配置中声明 CPACK_COMPONENTS_ALL
  3. 设置 CPACK_COMPONENTS_GROUPINGONE_PER_GROUP

嗯,就这么简单。下次你的项目需要分发不同用途的安装包时,试试组件安装吧。


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