4、install命令详解:TARGETS、FILES、DIRECTORY、SCRIPT、CODE等安装类型

好,咱们进入正题。install 命令,说白了就是 CPack 打包时的「搬运工」。你告诉它什么东西要装到哪,它就老老实实照做。我见过不少新手,CMakeLists.txt 写得飞起,结果 install 命令一塌糊涂,打包出来的东西缺胳膊少腿。嗯,这节咱们就把 install 的几种安装类型彻底捋清楚。

4.1 TARGETS 安装:最常用的安装方式

我个人习惯,90% 的场景都用 TARGETS 安装。它直接针对你 build 出来的目标——可执行文件、动态库、静态库。

install(TARGETS myapp mylib
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib/static
)

这里有个关键点:RUNTIME 对应可执行文件和 DLL(Windows 下),LIBRARY 对应 .so/.dylib,ARCHIVE 对应 .a/.lib。为什么要分开?因为 Linux 和 Windows 的安装习惯不一样。我在项目中遇到过,有人把 .dll 扔到 lib 目录,结果程序启动就报找不到 dll——其实就是路径没搞对。

重要提醒: 如果你用 PUBLIC_HEADER 或 PRIVATE_HEADER,记得额外指定头文件安装路径:

install(TARGETS mylib
    LIBRARY DESTINATION lib
    PUBLIC_HEADER DESTINATION include
)

4.2 FILES 安装:零散文件的归宿

有些文件不是 build 出来的,比如配置文件、脚本、文档。这时候用 FILES 安装。

install(FILES config.ini README.md
    DESTINATION share/myapp
)

你想想看,如果每个文件都要单独写一条 install,那 CMakeLists.txt 得多长?所以 FILES 支持一次列多个文件。我曾经为了省事,把一堆 .xml 配置文件用 GLOB 收集起来再 install,结果新加文件忘了重新 cmake,打包漏了——血的教训。建议还是显式列出文件。

小技巧: 可以用 ${CMAKE_CURRENT_SOURCE_DIR} 拼接绝对路径,避免路径歧义:

install(FILES
    ${CMAKE_CURRENT_SOURCE_DIR}/config.ini
    ${CMAKE_CURRENT_SOURCE_DIR}/README.md
    DESTINATION share/myapp
)

4.3 DIRECTORY 安装:整个目录搬过去

当你要安装一整个目录的内容,比如资源文件、数据文件、子目录结构,DIRECTORY 就派上用场了。

install(DIRECTORY resources/
    DESTINATION share/myapp
    FILES_MATCHING
    PATTERN "*.png"
    PATTERN "*.json"
    PATTERN ".git" EXCLUDE
)

注意那个 斜杠!resources/ 表示安装目录下的内容,resources 不带斜杠表示安装目录本身。这个区别我踩过坑——有一次我想把整个 assets 目录装进去,结果多了一层嵌套,路径变成了 share/myapp/assets/assets/...,用户找文件找到崩溃。

DIRECTORY 还支持 PATTERN 和 REGEX 过滤,EXCLUDE 排除不需要的文件。我个人习惯用 FILES_MATCHING 配合 PATTERN,只挑需要的文件类型,避免把 .git、.svn 这些版本控制目录也打包进去。

4.4 SCRIPT 安装:在安装时执行脚本

有时候光复制文件不够,你需要在安装过程中执行一些额外操作——比如创建符号链接、设置权限、运行后处理脚本。这时候用 SCRIPT。

install(SCRIPT cmake/post_install.cmake)

这个脚本是在 CMake 上下文中执行的,所以你可以用 CMake 的变量和命令。我曾在项目中用它来生成 ldconfig 缓存:

# post_install.cmake
execute_process(COMMAND ldconfig
    WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/lib
)

注意: SCRIPT 是在 安装时 执行,不是在 build 时。如果你需要在 build 后立即执行某些操作,应该用 add_custom_command 或 add_custom_target。

4.5 CODE 安装:内联 CMake 代码

如果脚本内容很少,不值得单独写一个文件,可以用 CODE 直接内联。

install(CODE [[
    message(STATUS "Installing to: ${CMAKE_INSTALL_PREFIX}")
    file(WRITE ${CMAKE_INSTALL_PREFIX}/INSTALL_INFO.txt
        "Installed on: ${CMAKE_INSTALL_PREFIX}")
]])

双中括号 [[]] 是 CMake 的 bracket 语法,可以避免转义问题。我个人觉得 CODE 适合做简单的日志记录或状态标记,复杂的逻辑还是放 SCRIPT 里更清晰。

4.6 安装类型对比一览

安装类型 适用场景 典型 DESTINATION 注意事项
TARGETS 可执行文件、库文件 bin, lib, lib/static 区分 RUNTIME/LIBRARY/ARCHIVE
FILES 单个配置文件、文档 share, etc, doc 显式列出文件,避免 GLOB
DIRECTORY 整个资源目录 share, data 注意斜杠的有无,善用 PATTERN
SCRIPT 安装时执行复杂操作 无固定路径 在 CMake 上下文中执行
CODE 内联简单操作 无固定路径 适合日志、状态标记

4.7 组合使用:一个完整的安装示例

实际项目中,这几种安装类型经常混着用。我给你看一个我实际用过的例子:

# 安装主程序
install(TARGETS myapp
    RUNTIME DESTINATION bin
)

# 安装动态库
install(TARGETS mylib
    LIBRARY DESTINATION lib
    PUBLIC_HEADER DESTINATION include
)

# 安装配置文件
install(FILES config.ini
    DESTINATION etc/myapp
)

# 安装资源目录
install(DIRECTORY assets/
    DESTINATION share/myapp
    FILES_MATCHING
    PATTERN "*.png"
    PATTERN "*.wav"
)

# 安装后创建符号链接
install(CODE [[
    execute_process(COMMAND ln -sf
        ${CMAKE_INSTALL_PREFIX}/bin/myapp
        ${CMAKE_INSTALL_PREFIX}/bin/myapp_v1
    )
]])

你看,这样一套下来,程序、库、配置、资源、后处理全齐了。CPack 打包时,这些 install 规则会自动被 CPack 解析,生成对应的安装脚本。

我的建议: 写 install 命令时,先想清楚「用户安装后,文件结构应该长什么样」。然后从根目录开始规划 DESTINATION。别偷懒,每个文件都明确指定路径——我曾经见过有人把所有东西都扔到 bin 目录,结果配置文件、库文件、文档全混在一起,那叫一个乱。

嗯,install 命令就这些。记住一个原则:安装不是复制,是组织。你组织的越清晰,用户用起来越舒服,CPack 打包也越省心。下一节咱们聊聊 CPack 的组件安装——怎么把一个大项目拆成多个安装包。