21. 6.1 安装规则配置

引言:大楼竣工后的”交付仪式”

在前面的章节里,我们的”施工队长”CMake 带领着各支小队(Target),从地基(编译)到封顶(链接),一步步把一栋大楼建造了起来。但一栋真正合格的大楼,不仅要盖得结实,还要完成交付——把钥匙、家具、使用说明书,甚至是后续的物业维护方案,整整齐齐地交到业主手中。

在 C++ 项目里,”交付”就是安装(Install)。编译生成的可执行文件和库不能永远躺在 build/ 目录里,它们需要被复制到系统的标准位置(如 /usr/local/bin/usr/local/lib),或者打包分发给用户。而 CMake 的 install() 命令,就是那位负责组织”搬家”和”交付”的后勤主管。

这一节,我们将学习如何编写完整的安装规则,让你的项目从”能跑”升级为”能部署”。

一、install 指令概览:四种”搬运方式”

CMake 的 install() 命令非常灵活,根据你要搬运的”货物”类型,它主要提供四种模式:

1.1 TARGETS:安装构建目标

用于安装我们通过 add_executable()add_library() 定义的目标。这是最常见的安装方式。

install(TARGETS myapp mylib
  RUNTIME DESTINATION bin
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib
)

1.2 FILES:安装普通文件

用于安装源代码树中的普通文件,比如头文件、配置文件、文档等。这些文件不会被修改,原样复制到目标位置。

install(FILES myapp.conf README.md
  DESTINATION etc/myapp
)

1.3 PROGRAMS:安装可执行脚本

FILES 类似,但 PROGRAMS自动赋予可执行权限(在 Unix-like 系统上)。适合安装 Shell 脚本、Python 脚本等。

install(PROGRAMS launch.sh helper.py
  DESTINATION bin
)

1.4 DIRECTORY:安装整个目录

用于递归复制整个目录,比如资源文件夹(assets)、图标、多语言文件等。

install(DIRECTORY assets/
  DESTINATION share/myapp
)

注意目录名末尾的斜杠:assets/ 表示复制目录内部的内容,而 assets(不带斜杠)则会创建一个名为 assets 的子目录再把内容放进去。

二、目标安装的类型区分:RUNTIME vs LIBRARY vs ARCHIVE

当我们使用 install(TARGETS ...) 时,CMake 要求你明确告诉它:不同类型的”建筑构件”应该搬到哪个房间。这通过 RUNTIMELIBRARYARCHIVEOBJECTS 来指定。

类型 含义 典型文件 安装位置惯例
RUNTIME 运行时文件 可执行文件(.exe)、Windows DLL bin/
LIBRARY 动态库 .so(Linux)、.dylib(macOS) lib/
ARCHIVE 静态库 / 导入库 .a、Windows 的 .lib(配合 DLL) lib/
OBJECTS 对象库的目标文件 .obj、.o(较少直接安装) 通常不安装

特别提醒 Windows 开发者:在 Windows 上,DLL 虽然是动态库,但在运行时可执行文件需要找到它们,因此 DLL 通常被视为 RUNTIME 类型安装到 bin/ 目录,而对应的导入库(.lib)则作为 ARCHIVE 安装到 lib/ 目录。

add_library(mylib SHARED mylib.cpp)

install(TARGETS mylib
  RUNTIME DESTINATION bin   # Windows: DLL 放这里
  LIBRARY DESTINATION lib   # Linux/macOS: .so/.dylib 放这里
  ARCHIVE DESTINATION lib   # Windows: .lib 导入库放这里
)

三、文件与目录安装:配置与资源的部署

3.1 安装配置文件与数据文件

一个完整的应用程序通常不仅需要二进制文件,还需要配置文件、数据库模板、UI 资源等。使用 FILES 可以精确控制这些文件的安装位置。

install(FILES
  ${CMAKE_SOURCE_DIR}/config/app.json
  ${CMAKE_SOURCE_DIR}/config/logging.ini
  DESTINATION etc/myapp
)

3.2 目录安装的递归与过滤

当资源文件数量很多时,逐个列举不现实。DIRECTORY 模式支持递归复制,并且可以通过 FILES_MATCHINGPATTERN 进行过滤。

install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/
  DESTINATION include
  FILES_MATCHING
    PATTERN "*.h"
    PATTERN "*.hpp"
    PATTERN "internal" EXCLUDE        # 排除 internal 子目录
    PATTERN "*_test.hpp" EXCLUDE      # 排除测试头文件
)

这个例子就像搬家时告诉工人:”把卧室里的衣服和书籍都搬走,但不要把日记本和杂物间的东西带上。”

四、安装时执行脚本:交付后的”开荒保洁”

有时候,文件复制到位并不意味着安装结束。你可能需要创建日志目录、修改动态链接器缓存、或者生成一些依赖当前安装路径的文件。CMake 允许你在安装阶段执行自定义脚本或代码。

4.1 install(CODE …):内联执行 CMake 代码

适合简单的、一次性的命令,比如创建空目录。

install(CODE "file(MAKE_DIRECTORY ${CMAKE_INSTALL_PREFIX}/var/log/myapp)")
install(CODE "message(STATUS "Installing to: ${CMAKE_INSTALL_PREFIX}")")

注意这里使用了反斜杠转义 ${CMAKE_INSTALL_PREFIX},这是为了让变量在安装时(而不是配置时)才被求值。

4.2 install(SCRIPT …):调用外部脚本

如果安装后的逻辑比较复杂,可以写成一个单独的 .cmake 脚本文件。

install(SCRIPT "${CMAKE_SOURCE_DIR}/cmake/PostInstall.cmake")

PostInstall.cmake 中,你可以使用所有 CMake 命令来完成复杂的初始化工作。

五、安装路径管理:CMAKE_INSTALL_PREFIX 与 GNU 标准

5.1 CMAKE_INSTALL_PREFIX

这是安装规则的”总指挥”。它定义了所有 DESTINATION根前缀。默认值在不同平台有所不同:

  • Unix/Linux:默认是 /usr/local
  • Windows:默认是 C:Program Files<ProjectName>

用户可以通过命令行覆盖它:

cmake --install build --prefix /opt/myapp

5.2 GNUInstallDirs:遵循社区惯例

如果你希望项目符合 GNU 编码标准,CMake 提供了内置模块 GNUInstallDirs。引入后,你可以使用一组预定义变量,避免硬编码路径。

include(GNUInstallDirs)

install(TARGETS myapp
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}      # 默认 bin
)

install(TARGETS mylib
  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}      # 默认 lib 或 lib64
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(FILES mylib.h
  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}          # 默认 include
)

使用 GNUInstallDirs 的好处是:在 64 位系统上,它会自动处理 liblib64 的差异,让项目看起来更专业。

六、组件化安装:给用户提供”可选套餐”

假设你的项目是一个大型 SDK,用户可能只想安装运行时文件来运行程序,而不需要头文件和静态库来进行二次开发。这时,组件化安装(COMPONENT)就派上用场了。

你可以把安装内容划分为不同的组件:

install(TARGETS myapp
  RUNTIME DESTINATION bin
  COMPONENT runtime
)

install(TARGETS mylib
  LIBRARY DESTINATION lib
  COMPONENT runtime
)

install(FILES mylib.h
  DESTINATION include
  COMPONENT development
)

install(TARGETS mylib
  ARCHIVE DESTINATION lib
  COMPONENT development
)

安装时,用户可以只选择安装特定组件:

# 只安装运行时(普通用户)
cmake --install build --component runtime

# 安装开发组件(开发者)
cmake --install build --component development

组件化安装也是后续 CPack 打包(将在 6.3 节介绍)的基础,它能直接生成多个安装包,比如 myapp-runtime.debmyapp-dev.deb

七、安装权限与属性控制

在 Unix-like 系统上,文件的读、写、执行权限至关重要。install() 命令提供了 PERMISSIONS 选项来精确控制。

7.1 文件权限

可用的权限关键字包括:

  • OWNER_READ, OWNER_WRITE, OWNER_EXECUTE
  • GROUP_READ, GROUP_WRITE, GROUP_EXECUTE
  • WORLD_READ, WORLD_WRITE, WORLD_EXECUTE

示例:安装一个只对所有者可写的配置文件:

install(FILES secret.conf
  DESTINATION etc/myapp
  PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
)

7.2 对 PROGRAMS 和脚本的权限

install(PROGRAMS ...) 默认会带上执行权限,但如果你想额外限制(比如只允许所有者和组内成员执行),可以显式指定:

install(PROGRAMS deploy.sh
  DESTINATION bin
  PERMISSIONS OWNER_EXECUTE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_READ
)

7.3 目录权限

对于 DIRECTORY 安装,权限会应用到复制的所有文件上。如果需要对目录本身设置特定权限(如 setgid 位),可能需要配合 install(CODE ...) 执行额外的 file(CHMOD ...) 命令。

小结:从工地到交付

安装规则是 CMake 项目从”个人玩具”走向”工业产品”的必经之路。本节我们学习了:

  1. install() 的四种核心模式:TARGETSFILESPROGRAMSDIRECTORY
  2. 目标安装时的类型区分,特别是 RUNTIMELIBRARY 在不同平台上的差异;
  3. 如何使用目录过滤和文件安装来部署资源;
  4. 利用 CODESCRIPT 在安装时执行初始化任务;
  5. 通过 CMAKE_INSTALL_PREFIXGNUInstallDirs 管理安装路径;
  6. 使用 COMPONENT 实现组件化安装;
  7. 通过 PERMISSIONS 控制文件权限。

掌握了这些,你的”施工队长”就不仅能盖好楼,还能在完成的那一刻,把所有钥匙、家具和说明书整整齐齐地摆放在业主指定的房间里。接下来,我们将继续学习如何把安装好的内容导出为配置包,让其他 CMake 项目也能轻松找到你的库(6.2 节:导出目标与配置包)。

请登录后发表评论

    没有回复内容

正在唤醒异次元光景……