19、安装规则:install()命令,安装目标、文件、目录、脚本,DESTDIR与CMAKE_INSTALL_PREFIX

说到CMake的安装规则,我刚开始接触时也觉得挺绕的。说白了,install()就是告诉构建系统:这些东西编译完了,该放到哪个目录去。你想想看,一个项目编译出来那么多东西——可执行文件、动态库、头文件、配置文件,总得有个规矩把它们归置好。

我个人习惯把安装规则写在每个子目录的CMakeLists.txt末尾,这样模块清晰,维护起来也方便。嗯,咱们一步步来看。

install()命令的基本用法

install()是CMake里最核心的安装命令。它有好几种变体,分别对应不同的安装对象。我把它分成四类:

  • 安装目标:可执行文件、库文件
  • 安装文件:单个文件,比如配置文件
  • 安装目录:整个目录,比如资源文件夹
  • 安装脚本:在安装前后执行的自定义脚本

先看一个最简单的例子:

# 安装可执行文件到 bin 目录
install(TARGETS myapp RUNTIME DESTINATION bin)

# 安装动态库到 lib 目录
install(TARGETS mylib LIBRARY DESTINATION lib)

# 安装静态库到 lib 目录
install(TARGETS mylib_static ARCHIVE DESTINATION lib)

这里有个细节要注意:TARGETS后面跟的是CMake里定义的target名字,不是文件名。我曾经见过新手直接把源文件路径写进去,结果CMake报错半天找不到原因。

安装目标:TARGETS 的三种类型

CMake把目标文件分成三类,每一类对应不同的安装位置:

参数 适用对象 典型安装目录
RUNTIME 可执行文件、DLL(Windows) bin/
LIBRARY 共享库(.so, .dylib) lib/
ARCHIVE 静态库(.a, .lib)、DLL导入库 lib/

为什么会这样区分?因为不同平台对文件类型有不同约定。比如Linux下共享库放在lib/,而Windows下DLL放在bin/。CMake帮你处理了这些平台差异,你只需要按逻辑分类就行。

我的习惯: 如果一个target同时生成共享库和静态库,我会分别写两条install规则,各归各的目录。这样别人用你的库时,一眼就能看出该链接哪个。

安装文件和目录

除了目标文件,我们经常还需要安装头文件、配置文件、资源文件等。这时候用FILESDIRECTORY

# 安装单个文件
install(FILES config.ini DESTINATION etc)

# 安装整个目录(递归)
install(DIRECTORY include/ DESTINATION include
        FILES_MATCHING PATTERN "*.h")

# 安装目录时排除某些文件
install(DIRECTORY resources/ DESTINATION share/myapp
        PATTERN ".git" EXCLUDE)

这里有个坑:DIRECTORY后面的路径如果带斜杠,行为会不一样。比如include/表示把目录内容安装过去,而include表示把目录本身安装过去。我曾经因为多写了一个斜杠,导致头文件路径多了一层嵌套,排查了半天。

注意: 安装目录时,CMake默认会保留目录结构。如果你只想安装目录下的文件,记得在路径末尾加斜杠。

安装脚本:在安装前后做点事

有时候我们需要在安装过程中执行一些额外操作,比如生成配置文件、设置权限、创建符号链接。这时候可以用SCRIPTCODE

# 安装前执行脚本
install(SCRIPT pre_install.cmake)

# 安装后执行内联代码
install(CODE [[
  message(STATUS "安装完成!")
  file(WRITE ${CMAKE_INSTALL_PREFIX}/README.txt "感谢使用")
]])

我个人更推荐用CODE写内联脚本,因为这样所有逻辑都在CMakeLists.txt里,不用额外维护一个文件。但如果脚本逻辑很复杂,还是单独放一个.cmake文件更清晰。

DESTDIR 与 CMAKE_INSTALL_PREFIX

这两个概念经常让人混淆。我简单解释一下:

  • CMAKE_INSTALL_PREFIX:编译时指定的安装根目录,默认是/usr/local。你在install()里写的DESTINATION都是相对于这个路径的。
  • DESTDIR:安装时临时加的前缀,通常用于打包(比如deb、rpm)。它是一个环境变量,不是CMake变量。

举个例子:

# 设置安装前缀
cmake -DCMAKE_INSTALL_PREFIX=/opt/myapp ..

# 安装时使用 DESTDIR
make install DESTDIR=/tmp/staging

最终文件会被安装到/tmp/staging/opt/myapp/bin/。说白了,DESTDIR就是在CMAKE_INSTALL_PREFIX前面再加一层。

核心区别:
  • CMAKE_INSTALL_PREFIX 是CMake变量,在配置阶段确定
  • DESTDIR 是环境变量,在安装阶段临时指定
  • 最终路径 = $DESTDIR + $CMAKE_INSTALL_PREFIX + DESTINATION

避坑指南:我踩过的几个坑

讲几个我实际项目中遇到的教训:

  • 路径硬编码:不要在DESTINATION里写绝对路径,除非你明确知道自己在做什么。否则换台机器就炸了。
  • 忘记安装头文件:库编译成功了,但别人引用时找不到头文件。我建议把所有公共头文件单独列一个install(DIRECTORY ...)规则。
  • 权限问题:默认安装的文件权限是644(可读不可执行)。如果你的脚本需要执行权限,记得加PERMISSIONS参数。
  • DESTDIR 不生效:如果你在CMake里用set(CMAKE_INSTALL_PREFIX ...)硬编码了路径,DESTDIR可能不会按预期工作。最好让用户通过命令行传入。

知识体系总览

下面这张图概括了本章的核心逻辑:

install() 命令知识体系 install() 安装目标 (TARGETS) 安装文件 (FILES) 安装目录 (DIRECTORY) 安装脚本 (SCRIPT) RUNTIME LIBRARY ARCHIVE 单个文件 递归安装 + 模式匹配 安装前/后执行 最终安装路径 $DESTDIR + $CMAKE_INSTALL_PREFIX + DESTINATION

总结

安装规则看似简单,但用好了能让你的项目专业度提升一个档次。我个人建议:

  • 每个target都配上明确的install规则,不要偷懒
  • 头文件和资源文件单独用DIRECTORY安装,方便用户引用
  • 打包时记得用DESTDIR,别直接装到系统目录
  • 多写几个install(CODE ...)做安装后的校验,比如检查文件是否存在

嗯,安装这块就讲到这里。记住一点:安装规则写得好,别人用你的库时就少骂两句。

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