4、第一个CPack项目:为Hello World项目添加CPack支持,生成第一个安装包
好了,前面我们聊了那么多CPack的基础概念。说实话,光说不练假把式。这一章,我们就真刀真枪地干一场。
我会带着你,从一个最简单的Hello World项目开始,一步步给它加上CPack支持。目标很明确:跑完cmake、make、cpack这三板斧,最后拿到一个实实在在的安装包。
嗯,这个过程我当年第一次做的时候,踩了不少坑。比如忘了设置版本号,结果打出来的包叫「helloworld-0.0.0-Linux.tar.gz」,看着就很不专业。还有一次,INSTALL命令写错了路径,安装完发现可执行文件跑到了系统根目录下……还好是在虚拟机里。
咱们今天就把这些坑都绕过去。
4.1 准备一个「裸」的Hello World项目
先看看我们手头有什么。一个最简单的C++项目,就两个文件:
HelloWorld/
├── CMakeLists.txt
└── main.cpp
main.cpp 的内容:
#include <iostream>
int main() {
std::cout << "Hello, CPack World!" << std::endl;
return 0;
}
CMakeLists.txt 的内容:
cmake_minimum_required(VERSION 3.15)
project(HelloWorld VERSION 1.0.0 LANGUAGES CXX)
add_executable(helloworld main.cpp)
这个项目现在能编译,能运行。但你要把它发给别人?抱歉,人家还得自己装CMake、自己编译。我们要做的,就是把它变成一个「双击安装,开箱即用」的安装包。
4.2 添加安装规则:INSTALL命令
CPack本身不会魔法。它不知道你要把文件装到哪里。你得告诉它:「嘿,这个可执行文件,给我放到安装目录的bin文件夹下」。
这个指令,就是 install() 命令。
修改 CMakeLists.txt,在 add_executable 后面加上:
install(TARGETS helloworld
RUNTIME DESTINATION bin)
解释一下这行代码的意思:
TARGETS helloworld:指定我们要安装的目标,就是刚才编译出来的 helloworld 可执行文件。RUNTIME DESTINATION bin:说明这是一个运行时目标(可执行文件),安装到目标路径下的 bin 目录。
你可能会问:「为什么是 RUNTIME 而不是其他?」
在CMake的世界里,目标分三类:
| 目标类型 | 适用场景 | 典型安装目录 |
|---|---|---|
| RUNTIME | 可执行文件、DLL动态库(Windows) | bin/ |
| LIBRARY | 共享库(.so, .dylib) | lib/ 或 lib64/ |
| ARCHIVE | 静态库(.a, .lib)、DLL导入库 | lib/ 或 lib64/ |
helloworld 是个可执行文件,所以用 RUNTIME。这个分类我一开始也觉得有点绕,后来做项目多了,发现它其实很合理——不同平台、不同类型的文件,安装位置天然就不一样。
4.3 引入CPack:几行配置搞定
安装规则写好了,接下来就是CPack的配置。CMake提供了一个专门的模块叫 CPack,我们只需要 include 它,再设置几个变量就行。
在 CMakeLists.txt 末尾加上:
# CPack 配置
include(CPack)
set(CPACK_PACKAGE_NAME "HelloWorld")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A simple Hello World program")
set(CPACK_PACKAGE_VENDOR "Your Company Name")
# 指定打包格式,这里我们先用TGZ(tar.gz)
set(CPACK_GENERATOR "TGZ")
完整的 CMakeLists.txt 现在长这样:
cmake_minimum_required(VERSION 3.15)
project(HelloWorld VERSION 1.0.0 LANGUAGES CXX)
add_executable(helloworld main.cpp)
install(TARGETS helloworld
RUNTIME DESTINATION bin)
# CPack 配置
include(CPack)
set(CPACK_PACKAGE_NAME "HelloWorld")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A simple Hello World program")
set(CPACK_PACKAGE_VENDOR "Your Company Name")
set(CPACK_GENERATOR "TGZ")
include(CPack) 这一行必须放在所有 set(CPACK_xxx) 变量的前面。为什么?因为 include 的时候,CPack模块会读取当前已定义的变量。如果你把变量写在 include 后面,那CPack就看不到了。我曾经因为这个顺序问题,折腾了半小时……
4.4 构建并打包:见证奇迹的时刻
配置写完了,接下来就是见证奇迹的时刻。打开终端,进入项目目录,执行:
# 第一步:配置
mkdir build && cd build
cmake ..
# 第二步:编译
make
# 第三步:打包
cpack
如果一切顺利,你会在 build 目录下看到一个文件:
HelloWorld-1.0.0-Linux.tar.gz
解压看看里面有什么:
tar -xzf HelloWorld-1.0.0-Linux.tar.gz
cd HelloWorld-1.0.0-Linux
./bin/helloworld
输出:
Hello, CPack World!
成了!
这个安装包的结构是:
HelloWorld-1.0.0-Linux/
└── bin/
└── helloworld
你看,CPack自动帮我们创建了 bin 目录,把 helloworld 放了进去。而且包名、版本号、平台信息都自动拼接好了。
4.5 试试其他打包格式
TGZ 只是开胃菜。CPack 支持很多格式,改一行配置就能切换:
# 改成生成 .deb 包(Debian/Ubuntu)
set(CPACK_GENERATOR "DEB")
# 或者生成 .rpm 包(Fedora/CentOS)
set(CPACK_GENERATOR "RPM")
# 或者生成 .sh 自解压脚本
set(CPACK_GENERATOR "STGZ")
# 甚至同时生成多种格式
set(CPACK_GENERATOR "TGZ;DEB;RPM")
我个人最常用的是 TGZ;DEB 组合。开发环境用 tar.gz 快速验证,发布时再打 deb 包给用户。
sudo apt install dpkg-dev 或 sudo yum install rpm-build。
4.6 避坑指南:我踩过的几个坑
最后,分享几个我当年踩过的坑,你遇到了可以少走弯路:
- INSTALL 命令没写:CPack 打包时发现没有任何文件要安装,会生成一个空包。我第一次就犯了这错,解压后里面啥也没有。
- 版本号不一致:project() 里写了 VERSION 1.0.0,但 CPACK_PACKAGE_VERSION 却写死了 "2.0.0"。结果包名和内部版本号对不上,用户反馈问题我都不知道是哪个版本。
- 忘记重新 cmake:修改了 CMakeLists.txt 后,一定要重新执行
cmake ..。只执行make是不会生效的。这个坑我至少踩过五次。 - Windows 上的路径分隔符:如果你在 Windows 上测试,DESTINATION 要用正斜杠
bin,而不是反斜杠bin\。CMake 会帮你处理好平台差异。
4.7 小结
这一章我们做了什么?
- 准备了一个 Hello World 项目
- 用
install()命令指定了安装规则 - 用
include(CPack)和几个变量配置了打包参数 - 成功生成了第一个 tar.gz 安装包
- 尝试了 DEB、RPM 等其他格式
说实话,第一次看到自己打出来的安装包,还是有点小成就感的。虽然只是个 Hello World,但「从源码到安装包」这条链路,你已经跑通了。
下一章,我们会深入聊聊 CPack 的变量系统。你会发现,原来那些包名、版本号、描述信息,都可以用变量灵活控制。到时候,你的安装包就能做到「千人千面」了。