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 的名字是大小写敏感的。我个人习惯用首字母大写的驼峰命名,比如 Runtime、SDK、Docs。这样在后续配置中一眼就能认出来。
小技巧:如果你不指定 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.exeMyApp-SDK-1.0.0-win64.exeMyApp-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.exe、MyApp-SDK-1.0.0-win64.exe、MyApp-Docs-1.0.0-win64.exe。
21.7 组件安装的SVG流程图
下面这张图展示了组件安装的完整流程,从CMake配置到最终生成独立安装包:
21.8 避坑指南
我在项目中踩过几个坑,分享给你:
- 组件名不要用空格:比如
Runtime Files这种,CPack解析时会出问题。用下划线或驼峰。 - CPACK_COMPONENTS_ALL 必须和 install() 中的组件名完全一致:大小写、拼写都不能错。我曾经因为把
Runtime写成了runtime,结果打包出来少了一个组件。 - NSIS 安装器对组件支持最好:如果你用
NSIS生成器,用户安装时可以看到组件选择界面。如果用ZIP或TGZ,组件打包就是生成多个压缩包。 - 组件依赖需要自己处理:CPack 不会检查组件间的依赖。比如 SDK 依赖 Runtime,你需要在文档中说明,或者在安装脚本中做检查。
我的习惯:每次添加新组件后,我都会运行 cpack -D CPACK_COMPONENTS_ALL=... --config CPackConfig.cmake 来验证一下,确保所有组件都能正确生成。
21.9 总结
组件安装是CPack中非常实用的功能。它让你的安装包从「一刀切」变成了「按需取用」。用户开心,你也省心。
核心就三步:
- 在
install()中给每个目标加上COMPONENT参数 - 在CPack配置中声明
CPACK_COMPONENTS_ALL - 设置
CPACK_COMPONENTS_GROUPING为ONE_PER_GROUP
嗯,就这么简单。下次你的项目需要分发不同用途的安装包时,试试组件安装吧。