30、安装规则:install()命令安装目标、文件、目录到指定路径
说实话,很多初学者写CMakeLists.txt时,最头疼的就是最后一步——怎么把编译好的东西装到系统里?
我刚开始用CMake那会儿,也踩过这个坑。编译完一个库,发现头文件没装过去,结果别的项目引用时各种找不到路径。后来才明白,install()命令才是CMake构建流程的"最后一公里"。
核心认知:install()不是编译的一部分,而是"部署"的一部分。它告诉CMake:编译完成后,把哪些东西复制到哪个目录。
30.1 安装目标:TARGETS模式
这是最常用的模式。说白了,就是把编译出来的库、可执行文件装到指定位置。
# 安装可执行文件到 bin 目录
install(TARGETS myapp
RUNTIME DESTINATION bin
)
# 安装动态库到 lib 目录
install(TARGETS mylib
LIBRARY DESTINATION lib
)
# 安装静态库到 lib 目录
install(TARGETS mylib_static
ARCHIVE DESTINATION lib
)
这里有个细节我特别想强调:RUNTIME、LIBRARY、ARCHIVE这三个关键字不能乱用。
| 关键字 | 适用对象 | 典型安装路径 |
|---|---|---|
| RUNTIME | 可执行文件、DLL(Windows) | bin/ |
| LIBRARY | 动态库(.so/.dylib) | lib/ |
| ARCHIVE | 静态库(.a/.lib)、DLL导入库 | lib/ |
我的习惯:如果一个目标同时生成动态库和静态库,我会分别写两个install(TARGETS ...)语句,各指定对应的DESTINATION。这样更清晰,不容易出错。
30.2 安装文件:FILES模式
有时候我们需要安装一些非编译产物,比如配置文件、脚本、文档等。这时候用FILES模式。
# 安装单个文件
install(FILES config.ini
DESTINATION etc/myapp
)
# 安装多个文件
install(FILES
README.md
LICENSE
CHANGELOG.md
DESTINATION share/doc/myapp
)
嗯,这里要注意:FILES模式不会自动创建子目录。如果你想把文件装到某个子目录下,得在DESTINATION里写完整路径。
30.3 安装目录:DIRECTORY模式
这个模式我用的最多。尤其是安装头文件时,整个include目录直接扔过去,省事。
# 安装整个目录
install(DIRECTORY include/
DESTINATION include
)
# 安装目录,但排除某些文件
install(DIRECTORY include/
DESTINATION include
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.hpp"
PATTERN ".git" EXCLUDE
)
我曾经踩过的坑:DIRECTORY后面跟的路径,如果带斜杠(include/),表示复制目录内容;如果不带斜杠(include),表示复制目录本身。这个区别很容易搞混,我至少在这上面翻过两次车。
30.4 安装脚本与权限控制
有时候安装前后需要执行一些脚本,比如创建用户、设置环境变量等。CMake提供了SCRIPT和CODE参数。
# 安装前执行脚本
install(SCRIPT pre_install.cmake)
# 安装后执行CMake代码
install(CODE [[
message(STATUS "安装完成!")
file(WRITE ${CMAKE_INSTALL_PREFIX}/etc/installed.txt "ok")
]])
# 设置文件权限
install(FILES myscript.sh
DESTINATION bin
PERMISSIONS OWNER_EXECUTE OWNER_READ
GROUP_EXECUTE GROUP_READ
WORLD_EXECUTE WORLD_READ
)
30.5 安装配置与目标导出
如果你在写一个库,想让别人通过find_package找到它,那就得用EXPORT和CONFIG模式。
# 安装目标并导出
install(TARGETS mylib
EXPORT MyLibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
# 安装导出的目标文件
install(EXPORT MyLibTargets
FILE MyLibTargets.cmake
DESTINATION lib/cmake/MyLib
)
# 安装配置文件
install(FILES MyLibConfig.cmake
DESTINATION lib/cmake/MyLib
)
说实话,这个配置导出功能我一开始觉得挺复杂的。但后来发现,一旦配好了,其他项目用find_package(MyLib)就能直接引用,省去了手动设置头文件路径和库路径的麻烦。
30.6 安装路径的变量与默认值
CMake提供了一些内置变量来控制安装路径:
| 变量名 | 默认值 | 说明 |
|---|---|---|
| CMAKE_INSTALL_PREFIX | /usr/local(Unix) C:/Program Files(Windows) |
安装根目录 |
| CMAKE_INSTALL_BINDIR | bin | 可执行文件目录 |
| CMAKE_INSTALL_LIBDIR | lib 或 lib64 | 库文件目录 |
| CMAKE_INSTALL_INCLUDEDIR | include | 头文件目录 |
# 使用GNUInstallDirs模块获取标准路径
include(GNUInstallDirs)
install(TARGETS mylib
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(DIRECTORY include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
我建议:尽量用GNUInstallDirs模块。它自动处理了不同平台的路径差异,比如64位系统下lib和lib64的区别。省心不少。
30.7 知识体系总览
下面这张图把install()命令的几种模式串起来了,方便你快速回顾:
30.8 实战建议
最后,分享几个我实际项目中的经验:
- 先本地测试:用
cmake --install . --prefix /tmp/test_install先装到临时目录,确认路径和文件都对,再装到系统目录。 - 区分Debug和Release:可以用
CONFIGURATIONS Debug;Release参数,让不同配置装到不同目录。 - 组件化安装:用
COMPONENT参数把安装分成多个组件(如runtime、dev、doc),用户可以选择只装需要的部分。
一句话总结:install()命令就是CMake的"部署说明书"。写清楚它,你的项目才能被其他人(或者未来的你)顺利使用。
公众号:蓝海资料掘金营,微信deep3321