简介:一份面向Linux系统管理员与软件打包初学者的教程,系统讲解RPM与DEB两种主流软件包格式的完整制作流程。文档从包目录结构切入:DEB部分详述DEBIAN目录、control文件及preinst、postinst、prerm、postrm脚本职责;RPM部分介绍BUILDROOT、SOURCES、SPECS等目录作用,以及spec文件中%pre、%post等脚本段配置。随后演示dpkg -b和rpmbuild的实际打包命令,并列出安装、升级、卸载的常用操作。通过阅读可掌握依赖声明、目录组织、脚本编写等关键细节,快速搭建自己的打包环境,减少手动安装的兼容性问题。资源仅含1个doc文档,大小约267KB,已有416人学习下载,适合需要补充打包实操经验的运维和研发人员。
1. 为什么你的工具需要打成RPM或DEB包:一次分发解决的三个麻烦
你有没有经历过这种场景:写了个内部运维工具,编译完把二进制扔给同事,结果他机器上跑起来直接报错,不是缺库就是路径不对。要么把一堆 .so 文件手动拷过去,要么干脆把自己机器借给他。这就是 Linux 软件包的用武之地。RPM 和 DEB 是 Linux 软件包管理的两大原生格式,前者用于 RHEL/CentOS/Fedora 系,后者用于 Debian/Ubuntu 系,它们把文件清单、依赖关系、安装脚本全部标准化,一条命令装上、一条命令卸掉。同样叫“打包”,electron 打包出来的是跨平台应用目录,webpack 打包优化处理的是前端产物,而 RPM 和 DEB 是系统级的原生包格式。这篇适合要分发命令行工具、维护内部仓库,或者被 apt/yum 依赖问题折腾过的人读。
下面直接落到 spec 文件和 debian/rules,把两条打包链路完整走一遍。我先讲清楚两套体系为什么长成这样,再分别给出能跑通的最小示例和验证命令,最后集中写分发时最容易踩的坑。
2. RPM与DEB的本质差异:从元数据结构到依赖声明方式
打包之前先搞明白一件事:你面对的是两套完全独立、但设计思想同构的系统。RPM 系用 rpm + dnf,DEB 系用 dpkg + apt。包格式不通用,但元数据描述包、文件清单描述内容、脚本钩子描述生命周期,这几件事两边思路几乎一样。先把这些底层差异看清楚,后面写 spec 和 control 时就不会犯“把 RPM 的习惯搬进 DEB”的错。
2.1 rpm包管理的完整链路:从rpm到dnf的依赖解析
rpm 本身只负责单包的安装、卸载、查询和校验,dnf(以及被它替代的 yum)在这些之上做仓库管理和依赖求解。这个分层影响很大:你用 rpm -ivh 装一个包,缺少依赖就直接报错退出;用 dnf install 装同一个包,它会把仓库里满足 Requires 的依赖包一起拉进来再执行事务。apt 对 deb 做的是同一件事,只不过它的依赖声明字段叫 Depends。
这个差异直接决定了打包时的依赖声明策略。你在 spec 里写的 Requires 包名,必须在目标发行版的软件源里真实存在。常有人问为什么同一个 rpm 在 Fedora 上能装、CentOS 上装不了,多半就是 Requires 里写了个 Fedora 有而 CentOS 没有的包名。另一个经典场景是 rpm 安装 mysql:老版本的 mysql rpm 依赖一堆 per-DBD 和 libncurses 相关包,手动 rpm -ivh 必须按顺序挨个装,错一步就卡住。后来 dnf 能自动解析,不代表打包时可以乱写 Requires,依赖声明得保守,尽量依赖底层稳定库,不要依赖某个带小版本号的工具包。
还有一点容易忽略:dnf 和 apt 在解析依赖时看的不是单个 .rpm/.deb 文件的头部,而是仓库的元数据。所以你在本地 rpm -ivh 能装上,不代表把它扔进 dnf 仓库后依赖解析也成立。本地能装是因为 rpm 只校验了当前系统里已有的 rpmdb;仓库解析则要保证你声明的每个依赖都能在仓库里找到来源。
2.2 包内结构对比:ar归档、control.tar.gz与cpio payload
两个包格式的物理结构差异很大,但搞清楚之后排错会省很多时间。RPM 包内部由 lead、header 和 payload 三段组成,header 存的是键值对形式的元数据,payload 是 cpio 归档再压缩。DEB 包其实是一个 ar 归档,里面固定有三个成员:debian-binary(版本标记)、control.tar.gz(元数据和维护脚本)、data.tar.xz(实际安装的文件)。
| 对比项 | RPM | DEB |
|---|---|---|
| 封装格式 | lead + header + payload(cpio) | ar 归档,三个固定成员 |
| 元数据位置 | header 段,rpm -qip 可读 | control.tar.gz 里的 control 文件 |
| 文件清单位置 | payload 段,rpm -qlp 可读 | data.tar.xz,dpkg-deb -c 可看 |
| 维护脚本位置 | header 段里的脚本段 | control.tar.gz 里的 postinst 等文件 |
| 解包排错方式 | rpm2cpio 包 | cpio -idv | dpkg-deb -R 包 目标目录 |
实际排错时这些结构差异非常有用。比如你拿到一个 rpm 但系统里没有 rpm 命令,可以用 rpm2cpio 把 payload 抽出来看内容;拿到一个 deb 觉得包内文件不对,dpkg-deb -R 直接解到目录,不用安装就能检查。我一般会在构建完成后各解包一次,确认文件路径和权限没问题再分发,这一步能拦下很多低级错误。
2.3 依赖声明、安装脚本与配置文件策略:Requires、Depends与%config
RPM 和 DEB 的依赖声明语法不同,但表达逻辑一致。RPM 在 spec 里用 Requires 声明运行期依赖,用 BuildRequires 声明构建期依赖;DEB 在 control 文件里用 Depends 和 Build-Depends 对应。两边都会在构建时跑自动依赖分析器:rpmbuild 扫描二进制的动态链接段,把 libfoo.so.2()(64bit) 这样的 soname 写进 Requires;dpkg 系的 dpkg-shlibdeps 做类似工作,但更倾向于把库文件映射到提供它的包名,比如 libssl3。
依赖声明最大的坑是 BuildRequires 和 Requires 混用。编译时需要、运行时不需的库,比如生成 man page 的 asciidoc,必须放 BuildRequires;运行时需要的动态库则让自动依赖生成器去抓,不要手动乱加。手动加依赖时尽量写包名而不是文件路径,因为依赖求解器只能按包名去仓库里找。
生命周期脚本两边也是一一对应的:RPM 的 %pre、%post、%preun、%postun 分别对应 DEB 的 preinst、postinst、prerm、postrm。升级场景下,RPM 的 %postun 会收到一个参数,$1 为 1 表示卸载,为 2 表示升级;DEB 的 postinst 则通过“configure”阶段区分首次安装和升级。写脚本时务必保证幂等,不要想着“只在安装时执行一次”,因为升级也会触发。
配置文件策略也要提前定。RPM 在 %files 里用 %config(noreplace) 标记用户可能修改的配置,升级时如果用户改过就保留旧文件,把新文件存成 .rpmnew;DEB 则把需要保留的文件写进 debian/conffiles,升级时 dpkg 会询问用户是保留还是替换。如果你发的工具带配置文件,这一步没做好,升级一次用户配置就丢了,售后会非常难看。
3. 用rpmbuild打出第一个RPM包:spec文件逐行拆解与验证
原理讲完了,开始动手。RPM 打包的核心是一个 spec 文件,它既是构建脚本,也是元数据声明。下面从一个纯脚本工具的 spec 开始,不涉及编译,把每条指令的用途讲清楚,再把最常用的验证命令列出来。
3.1 准备构建环境:rpmdev-setuptree与目录结构
在 RHEL/CentOS/Fedora 上先装构建工具,然后生成构建目录树。Ubuntu 上也可以装 rpm 尝试打 rpm 包,但宏路径和工具链差异很大,构建结果经常在真机上装不上,我不建议在 Debian 系上打 rpm。
# 安装构建工具(Fedora / RHEL 系) sudo dnf install -y rpm-build rpmdevtools # 生成 ~/rpmbuild 目录树 rpmdev-setuptree # 确认目录结构 find ~/rpmbuild -maxdepth 1 -type d这条命令会创建 BUILD、RPMS、SOURCES、SPECS、SRPMS 五个目录。Source 压缩包放 SOURCES,spec 文件放 SPECS,构建产物出现在 RPMS 下的架构子目录里。BUILD 和 BUILDROOT 是中间产物目录,每次构建前 rpmbuild 会清空重建,不要往里放任何手工文件。
打包前要把源码准备好:把 tar 包放进 SOURCES 目录,名字必须和 spec 里的 Source0 一致,否则 %setup 找不到文件。目录树本身没玄学,但 CI 环境里如果不想往 $HOME 写东西,后面我会讲用 --define 改 _topdir 的做法。
3.2 写一个最小spec并完成rpmbuild -bb
假设你要分发一个内部备份脚本 mytool,附带一个配置文件。二进制只有两个文件,没有编译步骤,所以 spec 比较短,但每一段都有讲究。
Name: mytool Version: 1.0 Release: 1%{?dist} Summary: Internal backup helper script License: GPLv3 Source0: mytool-1.0.tar.gz BuildArch: noarch Requires: bash, tar, gzip %description Internal backup helper that archives specified directories. No compilation steps; installs a script and a config file. %prep %setup -q %build # 没有需要编译的源码,此段留空 %install mkdir -p %{buildroot}%{_bindir} %{buildroot}%{_sysconfdir} install -m 0755 mytool %{buildroot}%{_bindir}/mytool install -m 0644 mytool.conf %{buildroot}%{_sysconfdir}/mytool.conf %files %{_bindir}/mytool %config(noreplace) %{_sysconfdir}/mytool.conf %changelog * Thu Jan 01 2025 Your Name <you@example.com> - 1.0-1 - Initial package这段 spec 里每一条都对应一个实际行为。Source0 指向 SOURCES 目录里的压缩包,rpmbuild 会在 %prep 阶段自动解压到 BUILD/mytool-1.0/,%setup -q 中的 -q 表示安静模式,如果压缩包解压出来的目录名和 Name-Version 不一致,需要加 -n 参数指定目录名。
BuildArch: noarch 表示这个包不依赖 CPU 架构,纯脚本包必须写这一行,否则 rpmbuild 默认按当前架构打。如果包里有编译产物,删掉这行,让构建器自动识别架构。Requires 声明运行期依赖,这里写了 bash、tar、gzip,安装时 dnf 会检查这些包是否存在。
%install 段操作的是 %{buildroot},不是系统根目录。这条非常关键:rpmbuild 会把 %{buildroot} 当作临时根目录,打包时把文件装进去,最后再收集里面的内容做成 payload。绝不能在 %install 里直接 cp 到 /usr/bin,否则文件会写进构建机的真实系统,而且不会进包。install 命令的 -m 参数控制权限,脚本 0755、配置 0644 是最常用的组合。
%files 段列的是安装后系统里的绝对路径,不带 %{buildroot} 前缀。%config(noreplace) 标记配置文件,用户改过就保留旧文件,新文件生成为 .rpmnew。%changelog 必须写,rpmlint 会检查它的格式和日期合法性。
构建命令很简单:
# 把 spec 放进 SPECS 目录后执行 rpmbuild -bb ~/rpmbuild/SPECS/mytool.spec # 查看产物 ls ~/rpmbuild/RPMS/noarch/-bb 表示只构建二进制包,-ba 会额外产出 src.rpm 源码包。构建过程中如果 %install 里写了文件但 %files 没声明,rpmbuild 会直接报错终止,这个行为是保护机制,后面避坑章节会专门讲。
3.3 用4条命令验证包:rpmlint、rpm -qip、rpm -qlp与rpm -qp --requires
构建成功不代表能分发,我用四个命令做基本体检:
# 静态检查 spec 和包 rpmlint ~/rpmbuild/SPECS/mytool.spec ~/rpmbuild/RPMS/noarch/mytool-1.0-1.el9.noarch.rpm # 查看包的元数据 rpm -qip ~/rpmbuild/RPMS/noarch/mytool-1.0-1.el9.noarch.rpm # 列出包内文件清单 rpm -qlp ~/rpmbuild/RPMS/noarch/mytool-1.0-1.el9.noarch.rpm # 查看包的依赖声明 rpm -qp --requires ~/rpmbuild/RPMS/noarch/mytool-1.0-1.el9.noarch.rpmrpmlint 会输出错误和警告,其中 E 开头的错误建议全部处理掉,W 开头很多是风格建议,脚本类包的 W 可以接受。rpm -qip 展示 Name、Version、Install Time 等元数据,确认打包者信息没有填错。rpm -qlp 列出文件,重点核对路径和权限,%buildroot 写错路径时这里一眼就能发现。rpm -qp --requires 查看实际的依赖声明,确认自动生成的 soname 依赖有没有被错误地删掉,有没有多出不存在的包名。
3.4 必调的3个参数与常见误用差异:_topdir、BuildRequires与%install
实际使用中我经常调三个参数。第一个是 --define "_topdir /path/to/rpmbuild",它把整个构建目录树挪到别的位置,CI 里不污染 $HOME 时很常用,但它要配合 --define "_tmppath /path/tmp" 一起用,否则临时目录还在原位置。第二个是 BuildRequires,它只在构建机上生效,不会写进包的依赖表,编译工具链和文档生成工具都放这里;运行时需要的库放 Requires,让自动依赖生成器扫描。第三个是 %install 段里的路径写法,%{buildroot} 必须用宏引用,不要写成 /root/rpmbuild/BUILDROOT/... 这种硬编码,换个用户打包直接翻车。
还有一个常见误用:有人为了让构建通过,在 spec 顶部写 %define _unpackaged_files_terminate_build 0,把“装了文件但没声明”的报错关掉。这等于把错误从构建期挪到运行期,安装缺文件时用户只会看到程序跑不起来。正确的做法是看报错里列出的未打包文件路径,回填 %files 段。
4. 用debuild打出第一个DEB包:debian目录、control与rules手写
DEB 打包的入口和 RPM 完全不同。RPM 的 spec 是一个独立的构建脚本;DEB 则要求源码目录下带一个 debian 子目录,里面放 control、rules、changelog 等文件。很多项目只发布官方 deb(比如从官网下载 chrome 的 deb 包),自己打的包想要达到同样质量,需要把 debian 目录的每个文件都弄明白。
4.1 准备构建环境:debhelper、devscripts与dh_make
先装工具链:
sudo apt install -y build-essential debhelper devscriptsdebhelper 提供 dh 命令族,devscripts 提供 debuild 包装脚本。然后建项目目录,目录名我习惯用“包名-上游版本号”的格式,比如 mytool-1.0,这样 dpkg-buildpackage 的自动检测逻辑最省心:
mkdir -p mytool-1.0/debian cd mytool-1.0 # 把源码文件放进来 cp ../mytool ../mytool.conf .如果你只想快速生成模板,可以装 dh-make 后在目录里跑 dh_make --createorig,它会产出全套模板。我下面的示例直接手写关键文件,因为模板自动生成的很多行对纯脚本包是多余的,手写反而更好理解。
4.2 手写control与rules:最小可构建的DEB
control 文件是 DEB 包的元数据核心,字段少一个都可能让包管理器拒收。这里写一个最小可用的版本:
Source: mytool Section: utils Priority: optional Maintainer: Your Name <you@example.com> Build-Depends: debhelper (>= 13) Standards-Version: 4.6.0 Package: mytool Architecture: all Depends: ${misc:Depends}, tar, gzip Description: Internal backup helper script Internal backup helper that archives specified directories. No compilation steps; installs a script and a config file.Source 段描述源码包,Package 段描述二进制包。一个源码包可以对应多个二进制包,所以这两个字段单独成段。Architecture: all 对应 RPM 里的 noarch,纯脚本包必须写 all;有编译产物的包写 amd64 或当前架构。Depends 里的 ${misc:Depends} 是 debhelper 自动注入的依赖占位符,不要删掉,dh 工具链会把需要的包名替换进去。Description 第一行是摘要,后面缩进的行是详细描述,两段之间不能有空行,否则 dpkg 解析会出错。
rules 文件是构建脚本,用 makefile 语法:
#!/usr/bin/make -f %: dh $@ override_dh_auto_build: override_dh_auto_install: dh_install --sourcedir=.第一行是 shebang,告诉系统用 make 解释。%: 是 make 的通配目标,把 dh 命令逐个执行。这个项目没有 Makefile,所以覆盖掉 dh_auto_build 让它什么都不做,再覆盖 dh_auto_install 改用 dh_install 从当前目录收集文件。注意 dh_install 前面那一行必须是 Tab 缩进,不能用空格,这是 makefile 语法的硬性要求,我用这段写过太多次,踩过这个空格坑太多次了。
dh_install 读取的是 debian/mytool.install 文件。这个文件名必须和二进制包名一致,内容是“源文件 目标目录”的映射:
mytool usr/bin/ mytool.conf etc/dh_install 会把 mytool 安装到包的 usr/bin 目录,mytool.conf 安装到 etc 目录。这个 .install 文件里不写 debian/tmp 前缀,dh 工具链会自动处理临时的打包根目录,这也是它比手写 install 命令安全的地方。
还有一个文件不能缺:debian/changelog。debuild 构建时依赖它判断版本号,格式如下:
mytool (1.0-1) unstable; urgency=medium * Initial release -- Your Name <you@example.com> Thu, 01 Jan 2025 00:00:00 +0000版本号写在括号里,1.0 是上游版本,-1 是 Debian 修订号。changelog 缺失或者格式不对,dpkg-buildpackage 会在最开始的阶段直接退出。
4.3 debuild -us -uc构建与lintian验证
所有文件准备齐了之后,先给 rules 加执行权限,然后构建:
chmod +x debian/rules debuild -us -uc -b-us 跳过源码包签名,-uc 跳过变更日志签名,-b 表示只构建二进制包。本地测试时这三个参数是必须的,因为你的机器上大概率没有 GPG 签名密钥,不跳过会卡在签名步骤报错。构建完成后回到上级目录,产物是 mytool_1.0-1_all.deb。
验证命令和 RPM 侧对应:
# 查看 control 信息 dpkg-deb -I mytool_1.0-1_all.deb # 查看包内文件清单 dpkg-deb -c mytool_1.0-1_all.deb # 静态检查 lintian mytool_1.0-1_all.debdpkg-deb -I 输出包信息,-c 列出文件。lintian 是 Debian 官方的静态检查工具,E 级错误一定要修,W 级警告视情况处理。纯脚本包最常见的 lintian 警告是 maintainer-script-without-set -e、suggests-而不是-depends 之类的风格问题,不影响安装,但如果你要往 Debian 官方仓库提包,这些都会被要求修掉。
4.4 DEB打包特有的两个参数:Architecture: all与${misc:Depends}
RPM 和 DEB 参数一一对应,但有两个是 DEB 特有的。第一个是 Architecture: all,它表示纯脚本或纯数据包,安装时不受架构限制。打错这个字段的后果是:在 amd64 机器上构建出来的包,在 arm64 机器上 apt 会提示架构不匹配拒绝安装。第二个是 ${misc:Depends} 和 ${shlibs:Depends},它们是 dh_make 时代就存在的宏占位符,构建时被替换成实际依赖。删掉它们不会立刻报错,但生成的包会缺少 debconf 或共享库依赖,装到干净机器上就露馅。
还有版本号的区别。Debian 的版本号格式是 [epoch:]upstream_version[-debian_revision],连字符被解释成 revision 的分隔符。如果你把上游版本写成 1.0-beta,dpkg 会认为 1.0 是上游版本,beta 是修订号,导致输出版本比较错乱正确。预发布版本我一般用波浪号,写成 1.0~beta,Debian 的版本比较规则里波浪号排在任何字符之前,这种写法能保证预发布版本永远低于正式版。
5. RPM和DEB打包常见问题排查:从“未找到rpm命令”到“软件包似乎无效”
打包翻车通常不在构建报错的那一刻,而在装到别人机器上的那一刻。我把分发过程中高频踩的坑按“现象 → 原因 → 解决”整理成下面几条,每一条都是我实际遇到过、或者帮同事排查过的场景。
5.1 最小系统上的“未找到rpm命令”:包类型和工具链不匹配
现象:把打好的 rpm 包拷贝到一台 Ubuntu 机器上,执行 rpm -ivh mytool.rpm,bash 提示 rpm: command not found。
原因:rpm 包只能安装在使用 rpm 包管理的发行版上。Ubuntu 属于 Debian 系,默认没有 rpm 命令,也更不应该用 rpm 安装。很多人忽略了这个前提,把 rpm 和 deb 当成像 exe 那样的跨平台安装包。
解决:先确认目标机器的发行版。如果是 Debian/Ubuntu,用 dpkg 系工具装 deb 包;如果手头只有 rpm,需要解包看内容时用 rpm2cpio:
rpm2cpio mytool.rpm | cpio -idv这会直接把 rpm 里的文件解压到当前目录,适合应急查看,但不适合替代正式安装。最干净的方案是在目标发行版上打对应的包格式。
5.2 安装成功但运行报缺库:ldd反查依赖包的排查法
现象:rpm -ivh mytool.rpm 安装成功,运行二进制时提示 error while loading shared libraries: libfoo.so.2: cannot open shared object file。
原因:构建机上恰好装了 libfoo.so.2,打包时自动依赖扫描发现了它并写进了 Requires,但分发前有人手动精简了 Requires,或者构建时用了 --nodeps 绕过检查。结果包装上了,运行环境里没有对应库。
解决:在构建机上先确认二进制到底依赖哪些库:
ldd mytool # 假设输出里有 libfoo.so.2 => /usr/lib64/libfoo.so.2 # 反查这个库属于哪个包 rpm -qf /usr/lib64/libfoo.so.2把 rpm -qf 输出的包名写进 Requires。注意不要直接写库文件路径到 Requires,依赖求解器只认包名。如果你把它做成了 noarch 脚本包,脚本里调用了某个系统命令,同样要检查这个命令属于哪个包,补进 Requires。漏依赖这种事,构建时看不出来,装到干净环境才暴露,所以我会习惯性地在容器里做一次安装测试。
5.3 “软件包似乎无效”与“无法定位软件包”:control格式和apt install的路径陷阱
现象:dpkg -i mytool.deb 报“软件包似乎无效”;或者改用 apt install mytool.deb,提示“无法定位软件包”。
原因:这两个提示是完全不同的故障。“软件包似乎无效”说明 deb 包内部结构损坏,最常见的是 control 文件格式错误,比如 Description 段有空行、字段名拼错、或者包版本号非法。“无法定位软件包”则是因为 apt install 后面直接写了文件名,apt 会把它当成软件源里的包名去搜索,根本不会去看当前目录。
解决:先看包信息能不能正常读取:
dpkg-deb -I mytool_1.0-1_all.deb如果 dpkg-deb 都报错,说明 control 字段确实有问题,用 lintian 检查具体错误。如果包没问题,用 apt 安装时必须加 ./ 前缀:
sudo apt install ./mytool_1.0-1_all.deb加上 ./ 是告诉 apt“这是当前目录的本地文件”,apt 才会走本地安装路径。这个细节我见过好几个同事栽过,搜索结果里“无法定位软件包”的提问有一大半是这个原因。
5.4 版本号里的连字符:dpkg版本解析的隐蔽坑
现象:构建好的 deb 包在 dpkg -i 时报版本号格式错误,或者安装后 apt 显示的版本和预期不一致,比如你写的是 1.0-beta,安装后显示的是 1.0,revision 是 beta。
原因:Debian 版本号三段结构里,连字符专门用于分隔上游版本和 Debian 修订号。上游版本本身不允许出现连字符,出现就会被 dpkg 解析成修订号。这个规则和 RPM 的 Version 字段完全不同,RPM 里版本号带连字符虽然不规范但通常能过,dpkg 是直接拒绝。
解决:预发布版本用波浪号或加号替代连字符:
mytool (1.0~beta-1) unstable; urgency=medium波浪号在 dpkg 的版本对比中排在所有字符前面,1.0~beta 永远小于 1.0,适合表达“即将发布但还没到正式版”的状态。加号则相反,1.0+beta 会排在 1.0 之后,适合表达补丁版本。定版本号之前先想清楚这个语义,改版本号的成本远低于和一个不明不白的版本号较劲。
5.5 %install里的文件没进包:别急着关掉构建终止开关
现象:rpmbuild -bb 报错 Installed (but unpackaged) file(s) found,构建终止;或者构建成功,但 rpm -qlp 列出的文件不全。
原因:%install 段把文件装进了 %{buildroot},但 %files 段没有对应声明。这是 RPM 打包新手最高频的错误。rpmbuild 默认会对“装进去了但没声明”的文件报错,这是保护机制,不是 bug。
解决:先看构建终端输出的未打包文件路径,再进 %{buildroot} 里确认实际目录结构:
find %{buildroot} -type f | sed 's|%{buildroot}||'输出结果就是 %files 段应该写的路径。有人图省事在 spec 顶部写 %define _unpackaged_files_terminate_build 0 把报错改成警告,这样构建能过,但包里的文件确实缺失,安装后程序直接跑不起来。我的习惯是把这个开关当成“报错遮羞布”,永远不会在生产构建里用它。
6. 把打包放进容器里:构建环境隔离与冒烟验证技巧
构建环境本身是最容易产生“我这能打,CI 上打不了”这种玄学问题的来源。机器 A 上装了新版 debhelper,机器 B 上是旧版,构建出的 deb 依赖信息就有差异。让构建环境可复现的最好方式,是把构建过程整个搬进容器。
我常用的 DEB 构建命令是这样:
docker run --rm -v "$PWD":/work -w /work debian:stable \ bash -c "apt-get update && apt-get install -y debhelper devscripts && debuild -us -uc -b"RPM 侧同理,用 fedora 镜像加 rpm-build 替换 debhelper 即可。--rm 让容器用完即删,不残留构建垃圾;-v 把当前目录挂载进容器,构建产物直接落在宿主机上;debian:stable 会跟随 Debian 稳定版走,避免本机装的工具链版本过新导致构建出的包在旧系统上装不了。容器里的依赖工具链是全新的,这能兜住那种“构建机装过旧版本所以侥幸成功”的隐性问题。
构建完成后的验证,我坚持一个动作:起一个干净容器,把包装进去跑冒烟测试,而不是在构建机上升级安装。比如 deb 包用 debian:stable-slim 起容器,dpkg -i 装进去,执行一遍工具的 help 和基本功能。RPM 包同理用 fedora 的最小镜像。这一步能同时验证依赖声明是否完整、可执行文件路径是否正确、配置文件权限是否正常。我经历过最惨的一次事故,就是把一个依赖本地环境的包直接分发,同事机器上装完跑了半年才报错,排查一周才定位到是版本过期和依赖冲突叠加的问题。从那以后我定了规矩:构建必须进容器,装包必须冒烟验证,发布必须记录版本号。这套习惯帮我挡掉了绝大部分分发事故,希望帮到你。
本文还有配套的精品资源,点击获取