11、安装与打包:install命令详解、CPack配置、生成安装包、安装路径规范
说实话,很多开发者写CMakeLists.txt时,只关心编译能不能过。安装和打包?那是最后才想的事。我以前也这样,直到有一次要给客户交付一个SDK,手动复制头文件和库文件,折腾了大半天,还漏了一个依赖库……嗯,从那以后,我再也不敢轻视install命令和CPack了。
这一章,我们就来聊聊怎么把你的构建产物,优雅地安装到系统里,再打包成漂亮的安装包。
11.1 install() 命令——安装的核心
install() 是CMake里最常用的安装指令。它告诉构建系统:哪些文件要装到哪里去。我个人习惯把install命令写在每个子目录的CMakeLists.txt末尾,这样模块清晰,维护起来也方便。
11.1.1 安装目标(TARGETS)
最常用的场景,就是把编译出来的库或可执行文件安装到指定目录。
install(TARGETS mylib myapp
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static
)
这里要注意:RUNTIME 对应可执行文件和Windows上的DLL,LIBRARY 对应Linux上的.so或macOS上的.dylib,ARCHIVE 对应静态库.a或.lib。我曾经见过有人把静态库装到 lib 目录,结果链接器找不到,折腾了半天才发现是路径写错了。
11.1.2 安装文件(FILES / DIRECTORY)
除了目标文件,头文件、配置文件、文档等也需要安装。
install(FILES include/mylib.h DESTINATION include/mylib)
install(DIRECTORY config/ DESTINATION etc/mylib
FILES_MATCHING PATTERN "*.conf"
)
我习惯把头文件单独放在一个 include/ 子目录下,这样用户引用时路径清晰:#include <mylib/mylib.h>。你想想看,如果所有头文件都平铺在 include/ 下,大型项目很容易重名冲突。
11.1.3 安装脚本与权限
有时候需要安装一些脚本,或者设置文件权限。
install(PROGRAMS scripts/start.sh DESTINATION bin)
install(FILES README.md DESTINATION share/doc/mylib)
PROGRAMS 会自动给文件添加可执行权限,省得你手动 chmod +x。
11.2 安装路径规范——别乱放
安装路径是有行业惯例的。GNU标准定义了 prefix、bindir、libdir、includedir 等变量。CMake内置了 GNUInstallDirs 模块,帮你自动适配。
include(GNUInstallDirs)
install(TARGETS mylib
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mylib
)
这样做的好处是:在Linux上默认装到 /usr/local/lib,在macOS上装到 /usr/local/lib,在Windows上装到 C:/Program Files/.../lib。跨平台不用改代码。
DESTINATION /usr/local/lib,这样用户想安装到其他前缀时就会失败。用 ${CMAKE_INSTALL_PREFIX} 或 GNUInstallDirs 才是正道。
11.3 CPack——一键生成安装包
CMake自带的打包工具叫CPack。它读取CMakeLists.txt里的安装规则,然后生成各种格式的安装包:Linux上的deb/rpm、macOS上的dmg、Windows上的NSIS/InnoSetup,甚至压缩包tar.gz/zip。
11.3.1 基础配置
在顶层CMakeLists.txt里加上这几行:
include(CPack)
set(CPACK_PACKAGE_NAME "MyLib")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A modern C++ library")
set(CPACK_GENERATOR "TGZ;DEB;RPM")
然后运行:
cmake --build . --target package
# 或者
cpack
你就会在当前目录下看到 MyLib-1.0.0-Linux.tar.gz、MyLib-1.0.0-Linux.deb 等文件。是不是很简单?
11.3.2 针对不同平台的定制
不同平台的安装包有各自的元数据要求。比如deb包需要维护者信息,rpm需要分组。
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "me@example.com")
set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries")
set(CPACK_NSIS_DISPLAY_NAME "MyLib Installer")
我曾经给一个客户打包Windows安装程序,忘了设置 CPACK_NSIS_CREATE_ICONS,结果安装完桌面上没有快捷方式,客户以为安装失败了……嗯,从那以后我每次都会检查NSIS的配置项。
11.3.3 组件安装
大型项目通常有多个组件:运行时库、开发头文件、文档、示例等。CPack支持按组件打包。
install(TARGETS mylib
COMPONENT runtime
LIBRARY DESTINATION lib)
install(FILES include/mylib.h
COMPONENT dev
DESTINATION include)
install(DIRECTORY examples/
COMPONENT examples
DESTINATION share/mylib/examples)
set(CPACK_COMPONENTS_ALL runtime dev examples)
set(CPACK_COMPONENT_RUNTIME_DESCRIPTION "Runtime libraries")
set(CPACK_COMPONENT_DEV_DESCRIPTION "Development headers")
这样用户可以选择只安装运行时,或者全部安装。我一般会把 dev 组件单独打包,因为很多用户只需要运行程序,不需要头文件。
11.4 知识体系结构图
下面这张图,帮你理清安装与打包的整体脉络。
11.5 实战:一个完整的安装打包示例
我们来看一个实际项目的CMakeLists.txt片段。假设我有一个库叫 fastjson,包含头文件、动态库和示例程序。
cmake_minimum_required(VERSION 3.16)
project(fastjson VERSION 2.1.0 LANGUAGES CXX)
include(GNUInstallDirs)
# 添加库
add_library(fastjson SHARED src/json.cpp)
target_include_directories(fastjson PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
# 添加可执行文件
add_executable(json_parser examples/parser.cpp)
target_link_libraries(json_parser PRIVATE fastjson)
# 安装规则
install(TARGETS fastjson
EXPORT fastjsonTargets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/fastjson
)
install(TARGETS json_parser
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(FILES include/fastjson/json.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/fastjson
)
install(EXPORT fastjsonTargets
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/fastjson
)
# CPack配置
set(CPACK_PACKAGE_NAME "fastjson")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_VENDOR "MyCompany")
set(CPACK_GENERATOR "TGZ;DEB")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "dev@mycompany.com")
include(CPack)
运行 cmake --build . --target package 后,你会得到 fastjson-2.1.0-Linux.tar.gz 和 fastjson-2.1.0-Linux.deb。安装到系统里试试:
sudo dpkg -i fastjson-2.1.0-Linux.deb
# 或者
tar -xzf fastjson-2.1.0-Linux.tar.gz -C /usr/local
GNUInstallDirs 保证跨平台兼容,用 CPack 一键生成安装包。这样交付给用户时,他们只需要一条命令就能安装使用。
好了,这一章的内容就到这里。安装和打包看似简单,但细节很多。我建议你动手试一下,哪怕是一个只有两个文件的小项目,走一遍完整的安装打包流程,你会对CMake的理解更深一层。