22、CMake安装与打包:install命令、CPack配置、生成安装包
说实话,很多C++开发者写CMakeLists.txt,能把编译搞定就觉得万事大吉了。但项目做大了,你总得把东西交付给别人用吧?要么是库文件+头文件,要么是一个完整的安装包。这时候,install命令和CPack就派上用场了。
我个人习惯是,项目一开始就把安装规则写好。别等到最后再补,那时候各种路径混乱,头文件漏掉,够你头疼的。今天我们就来聊聊,怎么把编译好的成果物,优雅地安装到系统里,再打包成安装包分发出去。
install命令:把东西放到该放的地方
install命令说白了就是告诉CMake:编译完了,哪些文件要拷贝到安装目录,分别放到哪个子目录下。它支持安装目标、文件、目录、脚本等。
安装目标(Target)
最常用的场景是安装库和可执行文件。看个例子:
add_library(mylib SHARED src/mylib.cpp)
install(TARGETS mylib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static
)
这里要注意,RUNTIME对应可执行文件和DLL(Windows下),LIBRARY对应共享库(Linux下的.so,macOS下的.dylib),ARCHIVE对应静态库和导入库。CMake会自动根据平台和库类型,把文件放到对应的DESTINATION下。
EXPORT 机制区分。我遇到过把静态库和动态库装到同一个目录,结果链接器优先找到了静态库,折腾了半天才发现。
安装头文件和配置文件
库装好了,头文件也得跟上。不然别人拿了库也不知道怎么调用。
install(DIRECTORY include/
DESTINATION include
FILES_MATCHING PATTERN "*.h"
)
这里有个坑:DIRECTORY后面的路径,如果带斜杠,表示拷贝目录下的内容;如果不带斜杠,会连目录本身一起拷贝。我刚开始用的时候经常搞混,后来就养成了习惯,一律加斜杠,逻辑更清晰。
有时候你还需要安装一些配置文件,比如.cmake配置文件,方便别人用find_package找到你的库:
install(FILES mylibConfig.cmake
DESTINATION lib/cmake/mylib
)
CPack配置:一键生成安装包
安装规则写好了,接下来就是打包。CMake自带的CPack工具,可以帮你生成各种格式的安装包:Linux下的DEB、RPM,Windows下的NSIS,macOS下的DMG,还有跨平台的ZIP、TGZ等。
配置CPack其实很简单,在CMakeLists.txt末尾加上几行:
set(CPACK_GENERATOR "TGZ;DEB")
set(CPACK_PACKAGE_NAME "mylib")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "My Awesome Library")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "yourname@example.com")
include(CPack)
然后执行:
cmake --build . --target package
或者:
cpack -G TGZ
就能在构建目录下看到生成的安装包了。
cmake --install 把文件安装到一个临时目录,然后根据你指定的生成器,把这个目录打包成对应的格式。所以,install命令写得好不好,直接决定了安装包的质量。
不同平台的打包细节
不同平台的打包,有一些特有的配置项。比如打DEB包时,可以设置依赖关系:
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl-dev (>= 1.1)")
打RPM包时:
set(CPACK_RPM_PACKAGE_REQUIRES "openssl-devel >= 1.1")
Windows下用NSIS,可以设置安装路径、快捷方式等:
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES64")
set(CPACK_NSIS_CREATE_ICONS_EXTRA
"CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\mylib.lnk' '$INSTDIR\\\\bin\\\\mylib.exe'"
)
嗯,这里要注意,NSIS的路径字符串里反斜杠要转义,我当初被这个坑过,查了半天文档才发现。
生成安装包的完整流程
我们用一个完整的例子串一下整个流程。假设我有一个库叫geometry,包含一个计算面积的函数:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(geometry VERSION 2.0.0)
add_library(geometry SHARED src/area.cpp)
target_include_directories(geometry PUBLIC include)
# 安装规则
install(TARGETS geometry
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib/static
)
install(DIRECTORY include/ DESTINATION include
FILES_MATCHING PATTERN "*.h"
)
# CPack配置
set(CPACK_GENERATOR "TGZ;DEB")
set(CPACK_PACKAGE_NAME "geometry")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Geometry calculation library")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "dev@example.com")
include(CPack)
构建并打包:
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build .
cpack -G TGZ
你会得到一个geometry-2.0.0-Linux.tar.gz文件。解压后,里面的目录结构就是你在install命令中定义的:
geometry-2.0.0-Linux/
├── bin/
├── include/
│ └── geometry.h
└── lib/
└── libgeometry.so
CPACK_DEBIAN_PACKAGE_MAINTAINER,结果生成的包在安装时直接报错,说缺少维护者信息。这个字段在DEB格式中是必填的,别漏了。
知识结构图
下面这张图,帮你理清CMake安装与打包的核心脉络:
从图中可以看到,install命令负责定义安装规则,CPack负责把这些规则打包成可分发的安装包。两者配合,才能完成从源码到交付物的完整链路。
一些实用技巧
- 指定安装前缀: 通过
CMAKE_INSTALL_PREFIX变量控制安装根目录。打包时一般设为/usr或/usr/local,但本地测试时建议设到一个临时目录,比如./install,避免污染系统。 - 组件安装: 如果你的项目包含多个组件(比如库、工具、文档),可以用
COMPONENT参数分组安装,然后在CPack中按组件打包。 - 导出目标: 配合
install(EXPORT),可以生成.cmake配置文件,让别人用find_package时自动找到你的库和头文件路径。这个在大型项目中非常实用。
CMakeLists.txt 中,把 install 和 CPack 配置单独放在一个 cmake/install.cmake 文件中,然后用 include() 引入。这样主文件更清爽,也方便维护。
好了,关于CMake的安装与打包,核心内容就这些。说白了,install 定义规则,CPack 执行打包。把这两步搞明白,你的项目就能从「只能自己用」进化到「可以分发给别人用」了。