15、安装与打包:install命令与CPack打包

说到CMake的安装与打包,我其实挺有感触的。早期做C++项目时,编译完了一堆二进制文件,怎么发给别人用?手动拷贝?写个shell脚本?都不是长久之计。CMake的install命令和CPack工具,就是专门解决这个问题的。

说白了,install告诉CMake「哪些文件要装到哪个目录」,而CPack则负责「把这些文件打包成安装包」。两者配合,就能实现一键生成安装程序。

15.1 install命令:核心三件套

install命令有几种常用形式,我工作中最常用的就是TARGETS、FILES和DIRECTORIES。咱们一个一个看。

15.1.1 安装目标:install(TARGETS ...)

这是最常用的。你想把编译出来的库或可执行文件装到系统里,就用它。

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

这里要注意:RUNTIME对应可执行文件(Windows下还有DLL),LIBRARY对应共享库(.so/.dylib),ARCHIVE对应静态库(.a/.lib)。

我的习惯:我会把静态库单独放到 lib/static 目录,避免和共享库混在一起。曾经有个项目,同事把静态库和共享库都丢到同一个lib目录,结果链接时老是找错文件,排查了半天。

15.1.2 安装文件:install(FILES ...)

头文件、配置文件、脚本等,用这个。

install(FILES
    include/mylib.h
    include/mylib_config.h
    DESTINATION include/mylib
)

嗯,这里有个坑:FILES不会保留目录结构。如果你有多个子目录的头文件,得用DIRECTORIES

15.1.3 安装目录:install(DIRECTORY ...)

install(DIRECTORY include/
    DESTINATION include
    FILES_MATCHING PATTERN "*.h"
)

注意include/后面的斜杠!有斜杠表示拷贝目录内容,没有斜杠表示拷贝目录本身。我刚开始用的时候经常搞混,后来养成习惯:一律加斜杠,只拷贝内容。

15.1.4 导出目标:install(EXPORT ...)

这个稍微高级一点。如果你写了一个库,想让其他CMake项目通过find_package找到它,就需要导出。

install(TARGETS mylib
    EXPORT mylibTargets
    LIBRARY DESTINATION lib
    PUBLIC_HEADER DESTINATION include
)

install(EXPORT mylibTargets
    FILE mylibTargets.cmake
    DESTINATION lib/cmake/mylib
)

这样别人就能用find_package(mylib)来引用你的库了。我在做公司内部公共库时,就是用这套方案,省去了每个人手动配置路径的麻烦。

15.2 安装路径的灵活配置

硬编码路径是不好的。CMake提供了GNUInstallDirs模块,帮你自动适配不同平台的路径。

include(GNUInstallDirs)

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

在Linux上,CMAKE_INSTALL_BINDIR默认是bin;在Windows上,可能就变成.了。你想想看,这样跨平台就省心多了。

我曾经踩过的坑:在Windows上安装DLL时,如果DLL依赖其他DLL,记得把依赖也一起安装。否则用户运行时会弹出「找不到xxx.dll」的错误。我一般会在 install 里显式列出所有依赖的DLL。

15.3 CPack打包:从安装到安装包

install命令定义好了安装规则,CPack就负责把这些规则打包成安装程序。你只需要在CMakeLists.txt末尾加上几行配置。

set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My Awesome Application")

include(CPack)

然后运行:

cmake --build . --target package

或者:

cpack

默认会生成一个压缩包。如果你想生成其他格式,比如NSIS安装包(Windows)、DEB包(Debian)、RPM包(Red Hat),可以指定生成器。

cpack -G NSIS   # Windows安装包
cpack -G DEB    # Debian包
cpack -G RPM    # Red Hat包
cpack -G TGZ    # tar.gz压缩包

15.4 常用CPack生成器对比

生成器 适用平台 输出格式 特点
NSIS Windows .exe 交互式安装向导,支持开始菜单快捷方式
DEB Debian/Ubuntu .deb 系统包管理器直接安装
RPM Red Hat/Fedora .rpm 系统包管理器直接安装
TGZ 跨平台 .tar.gz 简单解压即用,无依赖
ZIP 跨平台 .zip Windows友好,解压即用

15.5 自定义CPack配置

CPack有很多可配置项。我常用的几个:

# 安装包图标
set(CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/icons/myapp.ico")

# 安装路径前缀(Linux下默认为/usr/local)
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")

# 依赖检测(DEB/RPM)
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl-dev (>= 1.1)")

# 许可证文件
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.txt")
我的建议:如果你要生成DEB或RPM包,一定要设置好 CPACK_PACKAGING_INSTALL_PREFIX。默认是 /usr/local,但系统包管理器通常期望安装到 /usr。我曾经没改这个,结果打出来的DEB包安装到了 /usr/local,用户用 dpkg -L 都找不到文件。

15.6 知识体系总览

下面这张图总结了本章的核心逻辑:从源码到安装包,CMake帮你串联了整个过程。

CMake安装与打包流程 CMakeLists.txt + 源码 cmake --build . install() 命令定义安装规则 TARGETS | FILES | DIRECTORIES | EXPORT cmake --install . cpack -G NSIS/DEB/RPM 安装到系统目录 生成安装包(.exe/.deb/.rpm) install定义规则 → cmake --install 安装到本地 | cpack 打包成安装包

15.7 实战:一个完整的例子

假设你有一个库和一个可执行文件,想打包成DEB包。完整的CMakeLists.txt大概长这样:

cmake_minimum_required(VERSION 3.15)
project(MyApp VERSION 1.0.0 LANGUAGES CXX)

add_library(mylib SHARED src/mylib.cpp)
target_include_directories(mylib PUBLIC include)

add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE mylib)

# 安装规则
include(GNUInstallDirs)

install(TARGETS mylib
    EXPORT mylibTargets
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mylib
)

install(TARGETS myapp
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

install(EXPORT mylibTargets
    FILE mylibTargets.cmake
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mylib
)

# CPack配置
set(CPACK_PACKAGE_NAME "myapp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My Application")
set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.31)")

include(CPack)

然后执行:

mkdir build && cd build
cmake ..
make
cpack -G DEB

你就会得到一个myapp-1.0.0-Linux.deb文件。用户拿到后直接sudo dpkg -i就能安装。

核心要点:
  • install(TARGETS) 安装编译产物,区分RUNTIME/LIBRARY/ARCHIVE
  • install(FILES) 安装单个文件,install(DIRECTORY) 安装整个目录
  • install(EXPORT) 导出CMake配置,方便其他项目引用
  • CPack基于install规则生成安装包,支持NSIS/DEB/RPM/TGZ等多种格式
  • 使用GNUInstallDirs模块实现跨平台路径适配

好了,这一章的内容就到这里。安装与打包是CMake工程化的最后一步,也是把代码交付给用户的关键环节。掌握了这些,你的项目就能做到「编译完、打包好、直接发」。


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