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 的组件安装——怎么把一个大项目拆成多个安装包。