21、安装规则:install()命令详解、TARGETS/FILES/DIRECTORY/SCRIPT安装、安装权限与配置、导出目标
说实话,安装规则这块内容,很多C++开发者写CMake时都是「能用就行」。我以前也这么干——写个简单的install(TARGETS ...)就完事了。直到有一次,我需要把一个库部署到客户的嵌入式环境里,才发现安装规则里藏着不少门道。
今天我就把install()命令的几种常见用法,以及导出目标这套机制,给你掰开揉碎了讲清楚。
21.1 install() 命令的四种安装模式
install()命令说白了就四种模式:安装目标、安装文件、安装目录、安装脚本。我习惯把它们叫做「四大金刚」。咱们一个一个来看。
21.1.1 TARGETS 安装:最常用的模式
这是你平时用得最多的。把编译好的库、可执行文件安装到指定位置。
install(TARGETS mylib myapp
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static
)
这里有个细节我提醒你注意:RUNTIME、LIBRARY、ARCHIVE 这三个关键字,分别对应可执行文件、共享库、静态库。你想想看,如果只写一个DESTINATION,那所有类型的目标都塞到同一个目录里,是不是有点乱?
21.1.2 FILES 安装:安装配置文件
有时候你不需要安装整个目标,只想把几个配置文件、头文件或者资源文件放过去。这时候用FILES模式。
install(FILES config.ini logo.png
DESTINATION share/myapp
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
)
嗯,这里要注意权限设置。我在项目中遇到过一个问题:安装后的配置文件只有root能读,普通用户一运行就报错。后来我加上了WORLD_READ权限才解决。
21.1.3 DIRECTORY 安装:整个目录搬过去
如果你有一整个目录的资源文件要安装,比如assets/、data/,用DIRECTORY模式最方便。
install(DIRECTORY assets/ data/
DESTINATION share/myapp
FILES_MATCHING PATTERN "*.png" PATTERN "*.json"
PATTERN ".git" EXCLUDE
)
这里有个坑:目录名后面加不加斜杠,行为完全不同。加斜杠表示「安装目录里的内容」,不加斜杠表示「安装目录本身」。我曾经因为这个搞错过,结果安装出来的目录结构多了一层。
21.1.4 SCRIPT 安装:执行自定义脚本
有些安装步骤没法用CMake的标准模式搞定,比如生成一个符号链接、修改配置文件里的路径。这时候就用SCRIPT模式,执行一个CMake脚本。
install(SCRIPT cmake/post_install.cmake)
我个人觉得,SCRIPT模式是最后的杀手锏。能用前三种模式解决的问题,尽量别用脚本。因为脚本里的逻辑没法被CMake的安装系统「理解」,后续做卸载、打包时容易出问题。
21.2 安装权限与配置
安装权限这块,很多人直接忽略。但如果你要把软件部署到生产环境,或者做成deb/rpm包,权限就很重要了。
CMake支持标准的Unix权限设置:
install(TARGETS mydaemon
RUNTIME DESTINATION sbin
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_EXECUTE
)
上面这个例子,我给守护进程设置了755权限——所有者可读写执行,组和其他用户只能读和执行。为什么没有WORLD_WRITE?你想想看,一个系统守护进程如果谁都能写,那还得了?
644权限(没有执行位),结果链接器报「无法加载共享库」。折腾了半天才发现是权限问题。共享库必须有WORLD_EXECUTE权限才能被加载。
21.3 导出目标:让其他项目找到你
导出目标(Export)是CMake里一个非常强大的机制。说白了,就是让你的库安装后,其他项目用find_package()就能找到。
21.3.1 基本导出流程
导出目标需要三步走:
- 给目标设置 EXPORT 名称
- 安装导出文件
- 生成并安装配置文件
# 第一步:安装目标时指定导出集
install(TARGETS mylib
EXPORT MyLibTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
# 第二步:安装导出文件
install(EXPORT MyLibTargets
DESTINATION lib/cmake/MyLib
NAMESPACE MyLib::
)
# 第三步:生成配置文件
include(CMakePackageConfigHelpers)
configure_package_config_file(
cmake/MyLibConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
INSTALL_DESTINATION lib/cmake/MyLib
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
DESTINATION lib/cmake/MyLib
)
你看,这三步走完,其他项目就可以这样用了:
find_package(MyLib REQUIRED)
target_link_libraries(myapp PRIVATE MyLib::mylib)
21.3.2 命名空间的重要性
我注意到很多开源项目都用MyLib::这种命名空间。这不是花架子,而是有实际意义的。命名空间可以避免目标名冲突——万一你的库和另一个库都叫core,没有命名空间就乱套了。
21.4 知识体系总览
下面这张图,我把安装规则的核心逻辑梳理了一下。你可以对照着看,心里有个全局观。
21.5 实战中的一些建议
最后,我分享几个实战中总结出来的经验:
- 安装路径要灵活:别写死路径,用
CMAKE_INSTALL_PREFIX变量。用户安装时可以指定-DCMAKE_INSTALL_PREFIX=/usr/local。 - 组件化安装:如果你的项目有多个组件(比如运行时库、开发头文件、文档),用
COMPONENT关键字分组。这样用户可以用cmake --install . --component dev只安装开发组件。 - 测试安装:在CI里加一个步骤,把项目安装到临时目录,然后编译一个测试程序链接安装后的库。这样可以提前发现安装问题。
嗯,关于安装规则,今天就聊到这里。这些内容看起来琐碎,但每个细节都可能成为你部署时的绊脚石。多写几个install()命令,多跑几次安装测试,慢慢就熟练了。