构建RPM包

构建一个 RPM 包,基本思路:

  1. 明确你要构建什么。一个 rpm 包可能只是一个应用程序,也可能是不需要编译的包含一堆网页文件的 rpm 包,也可能是一个包含库的单独的 rpm 包,也可能只是文档性质的 rpm 包,也可能是补丁性质的 rpm 包,也可能只是包含配置文件的 rpm 包。

  2. 准备好「原材料」,也就是项目源代码的压缩归档文件,常以 .tar.gz 或 .tar.xz 或 .tar.bz2 等后缀结尾
  3. 收集补丁,给 rpm 打补丁
  4. 升级,包含兼容性、功能改进和冲突解决等
  5. 依赖关系。每个 rpm 都有一个能力,这个能力大多数情况下都与其名字有关。众所周知,rpm 安装后生成一堆文件,释放出的对应文件也是一种能力体现,它们有可能被其他 rpm 包依赖。换言之,这里的能力包括包的名称、包安装后的文件。在制作 rpm 的时候,有两类依赖关系:一个叫编译依赖(BuildRequires);一个叫安装依赖(Requires)
  6. 编写一个 spec 文件。spec 是配置规范文件,可理解为打包成 rpm 格式的配方。
  7. 开始构建 rpm 包,生成 rpm 包
  8. 对 rpm 包进行测试

构建要求

  1. 目录结构要求 – rpm 包的构建需要对目录结构有一定的要求,可理解为构建时所必须的「工作目录」
  2. 构建时使用的用户 – 推荐使用 root (uid=0),当然普通用户(uid>=1000)也可以进行构建
  3. 将「原材料」放入到对应的目录中
  4. 编写 spec 文件,编译打包成 rpm 包

spec 文件

什么是 spec 文件?
一个包含元数据、构建指令和安装规则的脚本,它驱动整个 RPM 包的生成流程,定义了软件从源码编译到最终打包的所有步骤。spec 的文件内容包含三部分:

  • 序言项(Preamble items)
  • 主体项(Body items)
  • 高级项(Advanced items)

除了序言项,其他部分都需要定义一些指令为构建系统提供必要的信息。

序言项标签

  • Name – 软件包的基本名称,应与 spec 文件名匹配
  • Version – 软件包的上游版本号,注意!这里的版本号不要带 – ,- 在 spec 中有特殊的意义
  • Release – 此软件包的发布次数。通常将初始值设置为 1%{?dist}
  • Summary – 简短的、单行的说明软件包的摘要信息
  • License – 被打包的软件包的开源许可证
  • URL – 大多数情况下,这是所打软件包的上游项目网站。
  • Source0 – 上游源代码压缩包存档的路径或URL
  • Patch – 补丁,可以有两种方式应用该指令,在 Patch 后面加或不加数字。也可以使用 %Patch0 这种方式。
  • BuildArch – 构建的体系架构,如果软件包不依赖特定的架构,可写为 BuildArch: noarch。如果不写,则自动继承当前机器的体系架构
  • BuildRequires – 编译时的依赖包,多个依赖包可用空格或逗号分隔。可以有多个 BuildRequires 条目
  • Requires – 软件安装后运行所需的依赖包,多个依赖包可用空格或逗号分隔。可以有多个 Requires 条目
  • ExcludeArch – 如果某个软件不能在特定的处理器架构上运行,你可以在此处排除该架构
  • Conflicts – 冲突的包,与 Requires 相反,如果匹配到对应的包,则无法安装该包。
  • Obsoletes – 废弃的包,也就是其他包提供的功能已经不推荐使用了 rpm 软件包的命名规范:

rpm 软件包的命名规范:

[Package_Name]-[Version]-[Release].[OS].[Arch].rpm [Package_Name]-[Version]-[Release].[OS].[Arch].src.rpm

Note:通常一个 rpm 软件包的完整名称由 Name、Version、Release 组成,我们称其为 NVR,即 Name-Version-Release.rpm。在上面的命名规范中,有些资料也将 [Release].[OS].[Arch] 这三部分单独划分到 Release 中。

主体项指令

一个软件包能否打包成功,全看主体项的各种步骤。

  • %description – rpm 软件包的完整描述,可跨越多行,可分为多列。
  • %prep – 预处理(编译前的准备操作),为下一步编译安装做准备。
  • %build – 编译位于 BUILD 目录里的文件,类似源代码安装 ./configure && make 的操作。
  • %install – 将 BUILD 目录的文件安装至 BUILDROOT 目录下,需要注意的是!这个 BUILDROOT 目录是最终用户安装 rpm 后得到的文件 。类似源代码安装中的 make install
  • %check – 用于测试软件的一系列命令。类似源代码安装中的 make test
  • %files – 制作 rpm 包时应该包含哪些文件。注意!这里的文件一定是和 BUILDROOT 目录下是一一对应的关系(除了 debug 目录)。BUILDROOT 是一个模拟操作系统根的临时目录。
  • %changelog – 每个 release 所做的变更日志,例如漏洞修复、补丁、额外的功能增强等。

高级项

高级项包含 Scriptlet(程序段)与 Triggers(触发器)。

  • Scriptlet 是在安装或删除软件包之前或之后执行的一系列 RPM 指令;Triggers 提供了一种在安装和卸载软件包期间进行交互的方法。Triggers 难以调试,因此要尽量减少使用它。
  • %pre – 在安装包之前执行的程序段
  • %pretrans – 在安装或删除任何包之前执行的程序段
  • %post – 在安装包之后执行的程序段
  • %preun – 在卸载包之前执行的程序段,通常在升级的时候会执行
  • %postun – 在卸载包之后执行的程序段
  • %posttrans – 在事务结束时执行的程序段
    关于这部分内容的更多内容,请参阅 这里

rpm 软件包构建实验

实验要求:将 Nginx 最新版本构建成 rpm 包
环境: 操作系统:Rocky Linux 8.10
rpm 版本:4.14.3
用户:root (uid=0)

准备好需要的命令行工具:

Shell > dnf -y install rpmdevtools rpm-build

其中 rpmdevtools 软件包包含这些基本的命令行工具:

  • rpmdev-setuptree 在当前用户家目录中生成 rpm 的构建树(build tree)
  • rpmdev-diff 比对两个归档的不同内容
  • rpmdev-newspec 从模板中创建新的 .spec 文件
  • rpmdev-checksig 使用备用的rpm密钥环检查软件包的签名
  • rpminfo 打印有关可执行文件和库的信息
  • rpmdev-md5 显示 RPM 中所有文件的 md5 校验

步骤一:准备「工作目录」

Shell > cd ; rpmdev-setuptree

Shell > tree rpmbuild/
rpmbuild/
├── BUILD
├── RPMS
├── SOURCES
├── SPECS
└── SRPMS

目录 说明

  • BUILD 构建过程中的工作目录
  • RPMS 存放生成的二进制 rpm 包
  • SOURCES 存放构建所需的资源,包含源码压缩文件、补丁文件、配置文件等
  • SPECS 核心目录,存放 spec 文件
  • SRPMS 存放生成的源码 RPM 包(.src.rpm),其包含源代码和 .spec 文件,便于分发和重新构建

步骤二:使用 spec 文件

有两种方式创建 spec 文件:
使用者创建一个全新的 spec 文件,文件内容需要自行填写
使用 rpmdev-newspec 命令工具创建一个 spec 模块文件,使用者只需填写必要的指令与字段即可

Shell > cd /root/rpmbuild/SOURCES ; wget -c https://nginx.org/download/nginx-1.29.0.tar.gz

Shell > cd  /root/rpmbuild/SPECS ; rpmdev-newspec nginx
nginx.spec created; type minimal, rpm version >= 4.14.

# 该文件的内容如下:
Shell > cat nginx.spec
Name:           nginx
Version:
Release:        1%{?dist}
Summary:

License:
URL:
Source0:

BuildRequires:
Requires:

%description

%prep
%autosetup

%build
%configure
%make_build

%install
rm -rf $RPM_BUILD_ROOT
%make_install

%files
%license add-license-file-here
%doc add-docs-here

%changelog
* Fri Jul  4 2025 root
-

修改之后的内容如下:

Name:           nginx
Version:       1.29.0
Release:        1%{?dist}
Summary:       A high performance web server and reverse proxy server

License:       BSD
URL:           http://nginx.org/
Source0:     %{name}-%{version}.tar.gz

BuildRequires: gcc gcc-c++ openssl openssl-devel make pcre pcre-devel gzip tar bzip2 bzip2-devel
# Requires:
%description
Nginx is a web server and a reverse proxy server for HTTP, SMTP, POP3 and \
IMAP protocols, with a strong focus on high concurrency, performance and low \
memory usage.

%prep
%setup -q

%build
./configure \
  --sbin-path=/usr/sbin/nginx \
  --conf-path=/etc/nginx/nginx.conf \
  --error-log-path=/var/log/nginx/error.log \
  --pid-path=/var/run/nginx/nginx.pid \
  --lock-path=/var/lock/nginx.lock \
  --http-log-path=/var/log/nginx/access.log \
  --user=nginx \
  --group=nginx \
  --with-http_ssl_module \
  --with-http_v2_module \
  --with-http_stub_status_module \
  --with-http_gzip_static_module 
make %{_smp_mflags}

%install
rm -rf $RPM_BUILD_ROOT
make install DESTDIR=%{buildroot}
%{__install} -p -d -m 0755 %{buildroot}/var/run/nginx
%{__install} -p -d -m 0755 %{buildroot}/var/log/nginx

%clean
rm -rf %{buildroot}

%pre
#  $1表示软件包的执行方式,0表示卸载;1表示第一次安装;2表示升级
if [ $1 == 1 ];then
      /usr/sbin/groupadd -r nginx 2> /dev/null
      /usr/sbin/useradd -r -g nginx nginx 2> /dev/null
fi

%files
%defattr(-,root,root,-)
%doc LICENSE CHANGES README
%{_sbindir}/%{name}
%dir /var/run/nginx
%dir /var/log/nginx
%dir /etc/nginx
%config(noreplace) /etc/nginx/fastcgi.conf
%config(noreplace) /etc/nginx/fastcgi.conf.default
%config(noreplace) /etc/nginx/fastcgi_params
%config(noreplace) /etc/nginx/fastcgi_params.default
%config(noreplace) /etc/nginx/koi-utf
%config(noreplace) /etc/nginx/koi-win
%config(noreplace) /etc/nginx/mime.types
%config(noreplace) /etc/nginx/mime.types.default
%config(noreplace) /etc/nginx/nginx.conf
%config(noreplace) /etc/nginx/nginx.conf.default
%config(noreplace) /etc/nginx/scgi_params
%config(noreplace) /etc/nginx/scgi_params.default
%config(noreplace) /etc/nginx/uwsgi_params
%config(noreplace) /etc/nginx/uwsgi_params.default
%config(noreplace) /etc/nginx/win-utf
/usr/local/nginx/html/50x.html
/usr/local/nginx/html/index.html

%changelog
* Fri Jul  4 2025 root
- first version
- init

稍后我们将说明 spec 文件中的内容。

步骤三:使用 rpmbuild 进行构建 常见选项如下表所示:

  • -bp 只执行到 %prep 阶段
  • -bi 只执行到 %install 阶段
  • -bc 只执行到 %build 阶段
  • -bb 制作二进制的 rpm 包
  • -bs 制作源码格式的 rpm 包
  • -bl 检测,检测哪些文件在 BUILDROOT 目录下安装生成了,但是在 spec 文件中的 %files 没有包含进来
  • -ba 既制作二进制的 rpm 包,也制作源码格式的 rpm包

使用 -bp 选项时:

若输出文本的最后一行看到 exit 0 表示无任何问题

Shell > rpmbuild -bp /root/rpmbuild/SPECS/nginx.spec
Shell > ls /root/rpmbuild/BUILD
nginx-1.29.0
Shell > ls -lh /root/rpmbuild/BUILD/nginx-1.29.0/

使用 -bc 选项时:

检查环境后会执行编译过程

同样的,若输出文本的最后一行看到 exit 0 表示无任何问题

Shell > rpmbuild -bc /root/rpmbuild/SPECS/nginx.spec

使用 -bi 选项时:

未出现 error 或 file not found 之类的错误则表示正常

Shell > rpmbuild -bi /root/rpmbuild/SPECS/nginx.spec

使用 -ba 选项时:

Shell > rpmbuild -ba /root/rpmbuild/SPECS/nginx.spec
Shell > tree /root/rpmbuild/RPMS/

步骤四:安装 我们的 nginx-1.29.0-1.el8.x86_64.rpm 已经制作完成,安装即可:

Shell > rpm -ivh /root/rpmbuild/RPMS/x86_64/nginx-1.29.0-1.el8.x86_64.rpm
Verifying...                          ################################# [100%]
Preparing...                          ################################# [100%]
Updating / installing...
   1:nginx-1.29.0-1.el8               ################################# [100%]

# 释放出的文件
Shell > rpm -ql nginx

请注意!我这里并没有将 nginx 的启动 unit 存放在 /usr/lib/systemd/system/ 目录中。 到这一步,构建 nginx 的简单 rpm 包就完成了。

最后

spec 文件的内容非常丰富,单靠这一篇文档是可能了解完整的,详情参阅官方的 手册页

对于一般的使用者或运维人员来说,构建考虑全面的 rpm 包是一件非常费时费力的事情,主要原因在于其涉及的多环节精细化管理与技术挑战:

  • 依赖管理的复杂性。库依赖、依赖冲突等
  • spec 文件编写的技术门槛。需要考虑多阶段构建流程、跨平台适配以及相关的脚本逻辑,比如一些关系型数据库的 RPM 包需在spec 中声明 400+ 文件路径及 20+ 依赖项
  • 高昂的测试与验证成本。需要考虑卸载后无残留文件、升级时配置文件保留(.rpmnew 机制),还要考虑在不同的 RHEL 衍生版去验证一致性。

转载自:

https://www.rockylinux.cn/notes/rpm-fundamentals-02-building-software-packages.html