13、安装规则:install命令详解、安装目标、安装文件、安装目录、配置安装路径

说到 CMake 的安装规则,我刚开始接触时也觉得有点绕。说白了,install 命令就是告诉 CMake:「编译好的东西该放到哪里去」。但如果你只是随便写个 install(TARGETS ...) 就跑,那大概率会踩坑。

我个人习惯把安装规则分成三类来理解:安装目标(可执行文件、库)、安装文件(头文件、配置文件)、安装目录(整个目录结构)。咱们一个一个来拆解。

13.1 install 命令的基本语法

先看最核心的 install() 命令。它有好几种形式,但最常用的就三种:

# 安装目标(可执行文件、库)
install(TARGETS target1 target2
        RUNTIME DESTINATION bin
        LIBRARY DESTINATION lib
        ARCHIVE DESTINATION lib/static)

# 安装文件
install(FILES file1.h file2.h
        DESTINATION include/mylib)

# 安装目录
install(DIRECTORY config/
        DESTINATION etc/mylib)

嗯,这里要注意:DESTINATION 是相对路径,它相对于 CMAKE_INSTALL_PREFIX。默认情况下,这个前缀在 Linux 上是 /usr/local,在 Windows 上是 C:/Program Files/${PROJECT_NAME}

核心要点: CMAKE_INSTALL_PREFIX 是所有安装路径的根目录。你可以通过 cmake -DCMAKE_INSTALL_PREFIX=/my/custom/path .. 来修改它。

13.2 安装目标:RUNTIME、LIBRARY、ARCHIVE 的区别

我在项目中遇到过不少同事,写 install(TARGETS mylib DESTINATION lib) 就完事了。结果在 Windows 上编译动态库时,发现 .dll 被装到了 lib 目录,而不是 bin 目录。为什么会这样?

因为 CMake 把目标分成了三类:

类型 含义 典型文件 建议安装位置
RUNTIME 运行时需要的文件 .exe, .dll, .so(某些情况) bin/
LIBRARY 共享库(非运行时) .so, .dylib lib/
ARCHIVE 静态库或导入库 .a, .lib lib/ 或 lib/static

你想想看,一个动态库在 Linux 上是 .so(属于 LIBRARY),在 Windows 上是 .dll(属于 RUNTIME)和 .lib(属于 ARCHIVE)。所以正确的写法是:

add_library(mylib SHARED src/mylib.cpp)

install(TARGETS mylib
        RUNTIME DESTINATION bin    # Windows 上的 .dll
        LIBRARY DESTINATION lib    # Linux 上的 .so
        ARCHIVE DESTINATION lib/static)  # Windows 上的 .lib

我的习惯: 即使你只做 Linux 开发,也建议把 RUNTIME、LIBRARY、ARCHIVE 都写上。这样跨平台时不会出问题。我曾经因为只写了 LIBRARY,导致 Windows 上 .dll 没被安装,调试了半天才发现。

13.3 安装文件:头文件和配置文件

安装文件相对简单,但有个坑要注意——路径结构

# 错误示范:直接把头文件拍平到 include 目录
install(FILES src/foo.h src/bar.h
        DESTINATION include)

# 正确做法:保持目录结构
install(FILES include/mylib/foo.h include/mylib/bar.h
        DESTINATION include/mylib)

我个人建议,头文件安装时一定要保留子目录结构。否则用户 #include <mylib/foo.h> 时会找不到文件。嗯,这个坑我踩过不止一次。

另外,如果你有多个文件要安装,可以用 FILE_SET(CMake 3.23+):

target_sources(mylib
    PUBLIC FILE_SET HEADERS
    BASE_DIRS include
    FILES include/mylib/foo.h include/mylib/bar.h)

install(TARGETS mylib
    FILE_SET HEADERS DESTINATION include)

这样更简洁,而且 CMake 会自动处理头文件的依赖关系。

13.4 安装目录:整个目录结构搬过去

install(DIRECTORY ...) 适合安装配置文件、资源文件、脚本等。它的语法是:

install(DIRECTORY config/ scripts/
        DESTINATION share/mylib
        FILES_MATCHING
        PATTERN "*.json"
        PATTERN "*.sh"
        PATTERN ".git" EXCLUDE)

注意 config/ 后面的斜杠!有斜杠表示安装目录下的内容,没有斜杠表示安装目录本身。举个例子:

  • install(DIRECTORY config/ DESTINATION etc) → 安装后是 etc/file1.conf
  • install(DIRECTORY config DESTINATION etc) → 安装后是 etc/config/file1.conf

这个细节我刚开始也记不住,后来干脆统一加斜杠,省心。

13.5 配置安装路径:CMAKE_INSTALL_PREFIX

前面说了,CMAKE_INSTALL_PREFIX 是所有安装路径的根。但实际项目中,我们经常需要更灵活的路径控制。比如:

# 在 CMakeLists.txt 中设置默认值
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
    set(CMAKE_INSTALL_PREFIX "/opt/${PROJECT_NAME}" CACHE PATH "..." FORCE)
endif()

# 或者用 GNUInstallDirs 模块
include(GNUInstallDirs)
install(TARGETS mylib
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

GNUInstallDirs 模块会根据平台自动设置 CMAKE_INSTALL_BINDIRCMAKE_INSTALL_LIBDIR 等变量。我个人强烈推荐使用它,尤其是做跨平台项目时。

避坑指南: 我曾经在 macOS 上使用 Homebrew 安装的 CMake,默认 CMAKE_INSTALL_PREFIX/usr/local。但 Homebrew 本身也管理 /usr/local,结果我的库和 Homebrew 的库混在一起,卸载时出了大问题。后来我养成了习惯:开发阶段一律用 -DCMAKE_INSTALL_PREFIX=$PWD/install 指定到本地目录。

13.6 知识体系总览

下面这张图总结了安装规则的核心逻辑:

CMake 安装规则核心结构 install() 命令 TARGETS 安装目标 FILES 安装文件 DIRECTORY 安装目录 RUNTIME → bin/ (.exe/.dll) LIBRARY → lib/ (.so/.dylib) ARCHIVE → lib/static/ (.a/.lib) 头文件 → include/ 配置文件 → etc/ 脚本文件 → bin/ 带斜杠:安装内容 无斜杠:安装目录本身 PATTERN 过滤/排除 CMAKE_INSTALL_PREFIX(安装根目录)

13.7 实战建议

最后,分享几个我多年积累下来的经验:

  1. 开发阶段用本地目录cmake -DCMAKE_INSTALL_PREFIX=$PWD/install ..,避免污染系统目录。
  2. 用 GNUInstallDirs 模块:它帮你处理了不同平台的路径差异,省心。
  3. 安装头文件时保留子目录:用户期望 #include <mylib/foo.h> 而不是 #include <foo.h>
  4. 动态库的 RUNTIME 和 LIBRARY 都要写:跨平台时不会漏掉 .dll 或 .so。
  5. cmake --install . --prefix /path:这是 CMake 3.15+ 的安装方式,比 make install 更通用。

一个小技巧: 如果你不确定安装后的目录结构,可以用 cmake --install . --prefix /tmp/test-install --verbose 先试装到临时目录,看看文件都放到了哪里。确认无误后再正式安装。

嗯,关于安装规则,核心就是理解 DESTINATION 是相对于 CMAKE_INSTALL_PREFIX 的,以及不同目标类型对应不同的安装位置。把这些搞清楚了,安装这块就不会再出幺蛾子了。


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