一、安装与打包:从代码到可交付物

说实话,很多开发者写 CMake 就止步于「能编译通过」。但项目做大了,你总得把东西交给别人用吧?要么是安装到系统里,要么是打个安装包发出去。这就是我们今天要聊的——install 命令CPack 打包

我个人习惯是,在项目第一天就把 install 规则写好。别等到最后再补,那时候依赖关系乱成一团,你会想哭的。

1.1 install() 命令:把文件放到该放的地方

install() 是 CMake 里最核心的安装指令。它的基本语法长这样:

install(TARGETS target_name
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib/static)

这里要注意区分三种目标类型:

目标类型 对应文件 典型 DESTINATION
RUNTIME 可执行文件 (.exe, 无后缀) bin
LIBRARY 共享库 (.so, .dylib, .dll) lib
ARCHIVE 静态库 (.a, .lib) 或 DLL 导入库 lib/static

嗯,这里有个坑。我在项目中遇到过有人把静态库也扔到 LIBRARY 里,结果安装路径全乱了。静态库请用 ARCHIVE,别问我为什么,这是 CMake 的约定。

除了安装目标,你还可以安装头文件、配置文件、脚本等:

install(FILES config.json DESTINATION etc/myapp)
install(DIRECTORY include/ DESTINATION include
        FILES_MATCHING PATTERN "*.h")
小技巧: 安装目录时,如果源目录末尾带斜杠(include/),表示只拷贝目录内容;不带斜杠(include),则连目录本身一起拷贝。我经常用这个特性来精确控制目录结构。

1.2 安装路径的变量与 GNUInstallDirs

你可能会问:bin、lib 这些路径写死合适吗?当然不合适。不同操作系统、不同发行版,路径习惯完全不同。

CMake 提供了 GNUInstallDirs 模块,帮你自动适配:

include(GNUInstallDirs)

install(TARGETS myapp
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

这些变量在 Linux 上会展开为 bin、lib,在 macOS 上可能变成 Applications、Frameworks。说白了,就是让 CMake 替你操心平台差异。

我个人习惯是:永远不要硬编码路径。哪怕你只在 Windows 上开发,也请用变量。因为有一天你可能会把项目移植到 Linux,到时候改路径改到崩溃——我曾经就干过这种蠢事。

1.3 安装的权限与配置

有时候你需要安装一些系统服务或脚本,需要特定权限:

install(PROGRAMS start.sh DESTINATION bin
        PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
                    GROUP_READ GROUP_EXECUTE
                    WORLD_READ WORLD_EXECUTE)

PROGRAMS 会自动添加可执行权限,但如果你要精细控制,就用 PERMISSIONS 手动指定。

注意: 安装时不要依赖 umask。不同用户的 umask 设置不同,可能导致安装后的文件权限不一致。显式指定 PERMISSIONS 是最稳妥的做法。

1.4 CPack:一键生成安装包

install 命令只负责把文件放到构建目录的安装前缀下。要真正生成安装包(.deb、.rpm、.dmg、.exe 安装程序),你需要 CPack。

CPack 是 CMake 自带的打包工具。配置起来很简单:

# 在 CMakeLists.txt 末尾添加
include(CPack)

# 设置包信息
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A wonderful application")
set(CPACK_PACKAGE_VENDOR "My Company")

然后运行:

cmake --build . --target install
cpack -G DEB   # 生成 .deb 包
cpack -G RPM   # 生成 .rpm 包
cpack -G NSIS  # 生成 Windows 安装程序
cpack -G DragNDrop  # 生成 macOS .dmg

你想想看,一行命令就能生成各种平台的安装包,是不是很爽?

1.5 CPack 的生成器与配置细节

不同的生成器有不同的配置项。这里我列几个常用的:

生成器 适用平台 关键变量
DEB Debian/Ubuntu CPACK_DEBIAN_PACKAGE_MAINTAINER
RPM Red Hat/Fedora CPACK_RPM_PACKAGE_LICENSE
NSIS Windows CPACK_NSIS_DISPLAY_NAME
DragNDrop macOS CPACK_DMG_VOLUME_NAME

举个例子,如果你要生成 .deb 包,必须设置维护者信息:

set(CPACK_DEBIAN_PACKAGE_MAINTAINER "yourname@example.com")

不设置的话,CPack 会报错。嗯,这个坑我踩过——第一次打包时忘了设,折腾了半小时才找到原因。

1.6 安装与打包的完整流程

把上面所有知识点串起来,一个典型的流程是这样的:

  1. 编写 CMakeLists.txt,用 install() 定义安装规则
  2. 引入 GNUInstallDirs 确保路径可移植
  3. 在项目末尾 include(CPack) 并设置包元信息
  4. 构建项目:cmake --build .
  5. 安装到临时目录:cmake --install . --prefix /tmp/staging
  6. 打包:cpack -G DEB

这里我建议你始终使用 --prefix 指定一个临时安装目录,不要直接安装到系统里。否则测试打包时会把系统搞乱——我曾经把测试用的 .so 装到了 /usr/lib,结果系统库被覆盖,重装了系统才修好。

核心要点:
  • install() 定义「安装什么」和「装到哪里」
  • GNUInstallDirs 解决路径可移植性
  • CPack 把安装结果打包成可分发的格式
  • 始终使用临时安装目录进行测试

1.7 知识体系总览

下面这张图概括了安装与打包的完整知识结构:

安装与打包知识体系 install() 命令 定义安装规则 GNUInstallDirs 可移植路径变量 CPack 打包 生成安装包 TARGETS / FILES / DIRECTORY BINDIR / LIBDIR / INCLUDEDIR DEB / RPM / NSIS / DragNDrop 完整流程:编写规则 → 配置路径 → 构建 → 安装 → 打包 cmake --build . → cmake --install . → cpack -G <generator> ⚠ 避坑指南 始终使用临时安装目录测试,不要直接安装到系统路径

这张图把今天的内容串起来了。从左到右,从 install 规则到路径配置,再到 CPack 打包,最后形成完整的交付流程。你写项目时,按这个顺序来,基本不会出错。

好了,安装与打包的核心内容就这些。记住:install 是基础,CPack 是升华。先把 install 写对,打包就是水到渠成的事。


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