引言:大楼竣工后的”交付仪式”
在前面的章节里,我们的”施工队长”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 要求你明确告诉它:不同类型的”建筑构件”应该搬到哪个房间。这通过 RUNTIME、LIBRARY、ARCHIVE 和 OBJECTS 来指定。
| 类型 | 含义 | 典型文件 | 安装位置惯例 |
|---|---|---|---|
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_MATCHING 和 PATTERN 进行过滤。
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 位系统上,它会自动处理 lib 与 lib64 的差异,让项目看起来更专业。
六、组件化安装:给用户提供”可选套餐”
假设你的项目是一个大型 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.deb 和 myapp-dev.deb。
七、安装权限与属性控制
在 Unix-like 系统上,文件的读、写、执行权限至关重要。install() 命令提供了 PERMISSIONS 选项来精确控制。
7.1 文件权限
可用的权限关键字包括:
OWNER_READ,OWNER_WRITE,OWNER_EXECUTEGROUP_READ,GROUP_WRITE,GROUP_EXECUTEWORLD_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 项目从”个人玩具”走向”工业产品”的必经之路。本节我们学习了:
install()的四种核心模式:TARGETS、FILES、PROGRAMS、DIRECTORY;- 目标安装时的类型区分,特别是
RUNTIME和LIBRARY在不同平台上的差异; - 如何使用目录过滤和文件安装来部署资源;
- 利用
CODE和SCRIPT在安装时执行初始化任务; - 通过
CMAKE_INSTALL_PREFIX和GNUInstallDirs管理安装路径; - 使用
COMPONENT实现组件化安装; - 通过
PERMISSIONS控制文件权限。
掌握了这些,你的”施工队长”就不仅能盖好楼,还能在完成的那一刻,把所有钥匙、家具和说明书整整齐齐地摆放在业主指定的房间里。接下来,我们将继续学习如何把安装好的内容导出为配置包,让其他 CMake 项目也能轻松找到你的库(6.2 节:导出目标与配置包)。


没有回复内容