第13章 安装与打包:install()命令,CPack配置,生成安装包

说实话,很多C++开发者写CMakeLists.txt时,只关心怎么编译。安装和打包?那是最后才想的事。我以前也这样,直到有次交付项目,对方问我「怎么没有安装脚本?」——场面一度很尴尬。

这一章,我们就来搞定CMake的安装与打包。说白了,就是让你的项目能被make install,还能一键生成deb、rpm、NSIS安装包。

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

install()是CMake里最核心的安装命令。它的作用很简单:告诉构建系统,编译完成后,哪些文件要复制到安装目录。

基本语法长这样:

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

这里要注意:

  • RUNTIME:可执行文件,放bin
  • LIBRARY:共享库(.so/.dylib),放lib
  • ARCHIVE:静态库(.a/.lib),放lib/static

我习惯把这三个分开写,清晰一点:

install(TARGETS myapp
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib/static)
小提示:如果你用GNUInstallDirs模块,可以写成DESTINATION ${CMAKE_INSTALL_BINDIR},这样更符合Linux标准。

13.2 安装头文件和配置文件

除了二进制文件,头文件和配置文件也得装。用install(DIRECTORY ...)

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

嗯,这里有个坑:include/后面的斜杠很重要。有斜杠表示复制目录内容,没有斜杠会复制目录本身。我曾经因为这个,把整个include目录装到了/usr/local/include/include下面……

13.3 安装脚本和CMake模块

有时候需要安装一些辅助脚本或CMake模块文件:

install(FILES cmake/MyConfig.cmake
        DESTINATION lib/cmake/MyProject)

这样别人用find_package(MyProject)时就能找到你的配置了。

13.4 CPack:一键生成安装包

CPack是CMake自带的打包工具。你只需要在CMakeLists.txt里设置几个变量,然后运行cpack命令,就能生成deb、rpm、NSIS等格式的安装包。

基本配置:

set(CPACK_PACKAGE_NAME "MyProject")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A useful tool")
set(CPACK_PACKAGE_VENDOR "My Company")

include(CPack)

就这么简单?对,就这么简单。但实际项目中,你还需要指定更多信息:

变量名 说明 示例值
CPACK_PACKAGE_CONTACT 维护者邮箱 support@mycompany.com
CPACK_PACKAGE_LICENSE 许可证 MIT
CPACK_PACKAGE_FILE_NAME 安装包文件名 MyProject-1.0.0-Linux
CPACK_PACKAGE_INSTALL_DIRECTORY 安装路径(Windows) MyProject

13.5 生成deb包

Debian/Ubuntu用户最熟悉的就是.deb了。CPack生成deb包需要额外设置:

set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "me@example.com")
set(CPACK_DEBIAN_PACKAGE_SECTION "devel")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl-dev, libcurl4")

然后运行:

cpack -G DEB

就会生成一个.deb文件。你可以用dpkg -i安装它。

注意:依赖关系要写对。我曾经漏写了libssl-dev,结果用户安装后运行时崩溃,排查了半天才发现是缺了OpenSSL。

13.6 生成rpm包

Red Hat/CentOS/Fedora用户需要.rpm。配置类似:

set(CPACK_GENERATOR "RPM")
set(CPACK_RPM_PACKAGE_RELEASE "1")
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
set(CPACK_RPM_PACKAGE_GROUP "Development/Tools")

运行:

cpack -G RPM

生成.rpm文件。注意,rpm打包需要rpmbuild工具,确保系统已安装。

13.7 生成NSIS安装包(Windows)

Windows用户习惯双击.exe安装。NSIS(Nullsoft Scriptable Install System)就是干这个的。

set(CPACK_GENERATOR "NSIS")
set(CPACK_NSIS_DISPLAY_NAME "MyProject")
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\MyProject.lnk' '$INSTDIR\\\\bin\\\\myapp.exe'")

运行:

cpack -G NSIS

生成一个.exe安装包。Windows用户双击就能安装,还能自动创建开始菜单快捷方式。

13.8 多平台打包策略

实际项目中,我通常这样组织:

# 通用设置
set(CPACK_PACKAGE_NAME "MyProject")
set(CPACK_PACKAGE_VERSION "1.0.0")

# 平台相关
if(WIN32)
    set(CPACK_GENERATOR "NSIS")
    set(CPACK_NSIS_DISPLAY_NAME "MyProject")
elseif(APPLE)
    set(CPACK_GENERATOR "DragNDrop")  # 生成.dmg
else()
    set(CPACK_GENERATOR "DEB;RPM")
    set(CPACK_DEBIAN_PACKAGE_MAINTAINER "me@example.com")
    set(CPACK_RPM_PACKAGE_RELEASE "1")
endif()

include(CPack)

这样,在Linux上运行cpack会生成deb和rpm,在Windows上生成NSIS,在macOS上生成dmg。一套代码,到处打包。

13.9 知识体系图

下面这张图总结了安装与打包的核心流程:

CMake 安装与打包知识体系 install() 命令 安装内容 二进制文件 头文件 配置文件 CMake模块 CPack 打包 .deb (Debian) .rpm (Red Hat) .exe (NSIS)

13.10 避坑指南

最后,分享几个我踩过的坑:

  • 路径问题install(DIRECTORY ...)的斜杠一定要小心。多一个少一个,结果天差地别。
  • 依赖缺失:打包时一定要声明所有运行时依赖。尤其是动态链接的库,漏一个就崩。
  • 权限问题:Linux下安装到/usr/local需要root权限。建议在CMakeLists.txt里加个提示。
  • 版本号CPACK_PACKAGE_VERSION和项目版本号保持一致。我习惯用project()里的版本号自动赋值。
核心要点:
  • install()负责把文件放到正确位置
  • CPack负责把安装好的文件打包成安装包
  • 不同平台用不同的Generator:DEB、RPM、NSIS
  • 依赖关系一定要写清楚,否则用户安装后无法运行

好了,安装与打包就讲到这里。下一章我们会聊聊测试——enable_testing()add_test(),让你的项目更健壮。


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