主要参考内容(均为官方文档):
https://rpm-packaging-guide.github.io/#building-rpms
https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/8/html/packaging_and_distributing_software/introduction-to-rpm_packaging-and-distributing-software#introduction-to-rpm_packaging-and-distributing-software
本文的环境:WSL2 + Qemu + OpenEuler24.03
提到的所有示例,均在该环境下测试
一、快速了解
RPM Package Manager (RPM)是一个运行在 Red Hat Enterprise Linux (RHEL)、CentOS 和 Fedora 上的软件包管理系统。您可以使用 RPM 为任何这些操作系统所创建的软件进行分发、管理和更新。
在 Red Hat Enterprise Linux 中,RPM 完全集成到更高级别的软件包管理软件中,如 YUM 或 PackageKit。虽然 RPM 提供了自己的命令行界面,但大多数用户只需要通过这个软件与 RPM 进行交互。但是,在构建 RPM 软件包时,您必须使用 RPM 工具,如 rpmbuild (8) 。
具体来说,RPM 软件包包含以下部分:
- GPG 签名:GPG 签名用于验证软件包的完整性。
- 标头(软件包元数据):RPM 软件包管理器使用此元数据来确定软件包依赖项、安装文件的位置及其他信息。
- payload:有效负载是一个 cpio 归档,其中包含要安装到系统的文件。
RPM 软件包有两种类型。这两种类型都共享文件格式和工具,但内容不同,并实现不同的目的: - 源 RPM(SRPM):SRPM 包含源代码和 spec 文件,它描述了如何将源代码构建为二进制 RPM。另外,SRPM 可以包含源代码的补丁。
- 二进制 RPM:一个二进制 RPM 包含了根据源代码和补丁构建的二进制文件。
安装 rpmdevtools 软件包:
openEuler 中使用 dnf,其他系统中可能使用 yum 安装:
dnf install rpmdevtools*
二、前置知识
这里仅简要罗列,更详细的内容可从文章开头的参考链接中查看
可以选择跳过,直接查看第三章的示例
1、源代码
源代码是人类可读的计算机指令,其描述了如何执行计算。源代码是使用编程语言表达的。
2、机器码
源代码转换为机器码方法:
- 原生编译软件
原生编译的软件是独立软件、原生编译的 RPM 软件包是特定于架构的,例如 C 语言 - 使用语言解释器或语言虚拟机解释软件,可以使用原始解释或字节编译软件
完全使用解释编程语言编写的软件不是特定于架构的。因此,生成的 RPM 软件包在其名称中有 noarch 字符串- 原始解释的软件:不需要编译这类软件,原始解释的软件由解释器直接执行
- 字节编译的软件:必须首先将这类软件编译成字节码,然后由语言虚拟机执行
3、构建方式
在软件构建过程中,源代码被转换为可以使用 RPM 打包的软件工件
- 原生编译的代码构建软件,使用以下方法之一将使用编译语言编写的软件构建成可执行文件:
- 手动构建(使用 gcc 命令)
- 自动化构建(使用 Makefile 文件与 make 命令)
- 将解释编程语言编写的源代码转换为机器码:
- 字节编译
- 原始解释(例如 Shell 脚本)
Python 支持原始解释、字节编译两种方式,字节编译的版本速度更快。因此,RPM 软件包程序更喜欢将字节编译的版本打包,来分发给最终用户。
4、修复软件(打补丁)
在打包软件时,可能需要对原始源代码进行某些更改,如修复 bug 或更改配置文件。在 RPM 打包中,可以将原始源代码保持不变,并在其上应用补丁。
补丁是更新源代码文件的一段文本。补丁具有 diff 格式,因为它代表文本的两个版本之间的区别。可以使用 diff 实用程序创建补丁,然后使用 patch 实用程序将补丁应用到源代码。
5、LICENSE
软件许可证文件(LICENSE)告知用户他们可以使用源代码做什么和不能做什么。
没有源代码许可证意味着您保留此代码的所有权限,任何人都不能从源代码复制、分发或创建衍生工作。
GPLv3 LICENSE 示例:
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
6、源代码存档(归档文件)
归档文件是带有 .tar.gz 或 .tgz 后缀的文件。将源代码放入存档中是发布软件以稍后打包为分发的常用方法。
注意:补丁 文件不随程序一起在存档中分发。构建 RPM 时,RPM 软件包管理器应用补丁。补丁将与 tar.gz 存档一起放在 ~/rpmbuild/SOURCES/ 目录中。
LICENSE 和源代码是一起归档的,而 patch 不和源代码一起归档,即 .tar.gz 归档文件中包含 LICENSE 和源代码,patch 和 .tar.gz 并列存放在 ~/rpmbuild/SOURCES 目录下
# 源代码文件夹
mkdir cello-1.0
mv cello.c cello-1.0/
mv Makefile cello-1.0/
mv LICENSE cello-1.0/
# 进行压缩打包,创建存档
tar -cvzf cello-1.0.tar.gz cello-1.0
# 移动到 ~/rpmbuild/SOURCES/ 目录中, 存储用于构建软件包的源文件的默认目录
# 对于需要 patch 的软件包,将所需的 patch 也移动到 ~/rpmbuild/SOURCES/ 目录中
mv cello-1.0.tar.gz ~/rpmbuild/SOURCES/
三、打包 Hello World 示例
0、设置工作区
# 1. 安装 rpmdevtools 软件包
dnf install rpmdevtools
# 2. 执行 rpmdev-setuptree 程序
rpmdev-setuptree
# 默认在 ~ 目录下创建 rpmbuild 文件夹
tree ~/rpmbuild/
RPM 打包工作区目录:
1、spec 文件
spec 文件是一个文件,其中包含 rpmbuild 实用程序用来构建 RPM 软件包的指令。此文件通过在一系列部分中定义指令,为构建系统提供必要信息。这些部分在 spec 文件的 Preamble 和 Body 部分中定义:
- Preamble 部分包含一系列在 Body 部分中使用的元数据项。
- Body 部分代表指令的主要部分。
列举了 Preamble 部分关键的指令:
列举 Body 中部分重要的项目:
2、BuildRoots
在 RPM 打包上下文中,buildroot 是 chroot 环境。构建工件通过使用与最终用户系统中将来层次结构相同的文件系统层次结构放在此,buildroot 充当根目录。构建工件的放置必须遵循最终用户系统的文件系统层次结构标准。
buildroot 中的文件稍后放入 cpio 存档,后者成为 RPM 的主要部分。当在最终用户的系统中安装 RPM 时,这些文件将提取到 root 目录中,保留正确的层次结构。
# 对于不熟悉的宏,可以使用 rpm --eval 查看内容
rpm --eval %{_bindir}
# 输出:/usr/bin
3、使用 spec 文件
要打包新软件,必须创建一个 spec 文件。通过以下任一方法创建 spec 文件:
- 从头开始手动编写新的 spec 文件。
- 使用 rpmdev-newspec 工具。这个工具会创建一个未填充的 spec 文件,其中会填写必要的指令和字段。
cd ~/rpmbuild/SPECS
# 创建空的 spec 文件
rpmdev-newspec bello # hello world Bash版本
rpmdev-newspec cello # hello world C版本
rpmdev-newspec pello # hello world python版本
填充 spec 文件流程:
- 打开 rpmdev-newspec 创建的 ~/rpmbuild/SPECS/.spec 文件
- 填充 spec 文件的 Preamble 部分的指令:
- Name:已指定为 rpmdev-newspec 的参数
- Version:设置为与源代码的上游版本匹配
- Release:自动设置为 1%{?dist},它最初是 1
- Summary:软件包的单行说明
- License:与源代码关联的软件许可证
- URL:上游软件网站的 URL,为实现一致性,请使用 %{name} RPM 宏变量,并使用 https://example.com/%{name} 格式
- Source:上游软件源代码的 URL,直接链接到被打包的软件版本
- BuildRequires:指定软件包的构建时依赖项
- Requires:指定软件包的运行时依赖项
- BuildArch:指定软件架构
- 填充 spec 文件 Body 部分中的以下指令:可以将这些指令视为部分标题,因为这些指令可以定义多行、多结构或脚本化任务
- %description:软件的完整描述
- %prep:一系列命令来准备软件以进行构建
- %build:用于构建软件的一系列命令
- %install:一系列命令:用于指示 rpmbuild 命令如何将软件安装到 BUILDROOT 目录中
- %files:指定在系统上安装的 RPM 软件包提供的文件列表
- %changelog:软件包的每个 Version-Release 的 datetamped 条目列表
从 %changelog 部分的第一行开始,带有星号(*)字符,后跟 Day-of-Week Month Day Year Name Surname - Version-Release
对于实际更改条目,请遵循这些规则:- 每个更改条目都可以包含多个项目,每个代表一个改变。
- 每个项目在新行中开始。
- 每个项目以连字符(-)字符开头。
4、分析 bello 的 spec 文件
Bello 是使用 bash 写的 hello world,spec 文件如下
Name: bello
Version: 0.1 --> 软件版本
Release: 1%{?dist} --> 发布版本
Summary: Hello World example implemented in bash script
License: GPLv3+
URL: https://www.example.com/%{name} --> 软件上游网站 url
Source0: https://www.example.com/%{name}/releases/%{name}-%{version}.tar.gz # 软件的 url
Requires: bash --> 运行时依赖 bash
BuildArch: noarch --> 与机器的体系结构无关
%description
The long-tail description for our Hello World Example implemented in
bash script.
%prep --> 准备阶段:例如解压缩源代码存档
%setup -q
%build --> 构建阶段:bash 脚本不需要构建
%install --> 安装阶段
mkdir -p %{buildroot}/%{_bindir}
install -m 0755 %{name} %{buildroot}/%{_bindir}/%{name} --> 安装到指定路径
%files
%license LICENSE --> LICENSE
%{_bindir}/%{name} --> /usr/bin/bello
%changelog --> 改变日志
* Tue May 31 2016 Adam Miller <maxamillion@fedoraproject.org> - 0.1-1
- First bello package
- Example second item in the changelog for version-release 0.1-1
- BuildRequires 指令指定软件包的 build-time 依赖项已被删除,因为没有可用于 bello 的构建步骤。Bash 是原始解释编程语言,文件仅安装到其系统上的位置。
- Requires 指令指定软件包的运行时依赖项,它只包括 bash,因为 bello 脚本只需要 bash shell 环境才能执行。
- %build 部分指定如何构建软件为空,因为不需要构建 bash 脚本。
- %setup 对源代码存档进行解压
install:是Linux系统中用来复制文件和设置权限的命令,-m 0755:这个选项指定了安装文件的权限。0755是一个八进制数,表示文件所有者有读写执行权限(7),组用户和其他用户有读取和执行权限(5),没有写权限。
所以,整个 install 命令的作用是将名为%{name}的可执行文件复制到%{buildroot}/%{_bindir}目录下,并设置其权限为0755。在RPM打包过程中,%{buildroot}目录下的内容最终会被打包到 RPM 文件中,当用户安装RPM包时,这些文件会被安装到实际的文件系统路径上。
rpmbuild 常用的参数:
# 进入 SPEC 文件夹中
cd ~/rpmbuild/SPECS
# 以构建 bello 的 RPM 包为例(其他的类似)
rpmbuild -bb bello.spec
可以使用 --noclean 留下构建的过程文件,在 BUILD/BUILDROOT 中可以看到 build 时做了什么
默认在构建好 rpm 包时,BUILD/BUILDROOT 下的内容会被清理
5、分析 pello 的 spec 文件
Pello 是使用 python 写的 hello world 程序
注:以下 spec 文件无法再 python3 上使用,python2 下应该是可以正确使用的【未验证】
在 python3 中,python -m compile 会将生成的pyc字节码文件放到 pycache 文件夹下,并且会修改命名,与该 spec 文件不匹配。而在 python2 中,生成的 pyc 字节码文件,可以按照以下 spec 描述进行。
Name: pello
Version: 0.1.1
Release: 1%{?dist}
Summary: Hello World example implemented in Python
License: GPLv3+
URL: https://www.example.com/%{name}
Source0: https://www.example.com/%{name}/releases/%{name}-%{version}.tar.gz
BuildRequires: python --> 构建生成 pyc 字节码时,需要使用 python
Requires: python --> 运行时需要使用 python
Requires: bash --> 运行时需要 bash
BuildArch: noarch
%description
The long-tail description for our Hello World Example implemented in Python.
%prep --> 准备阶段:
%setup -q --> 解压源代码存档文件
%build --> 构建阶段:
python -m compileall %{name}.py --> build 阶段, 对 py 进行编译, 得到字节码
%install --> 安装阶段:
mkdir -p %{buildroot}/%{_bindir} --> 递归创建目录
mkdir -p %{buildroot}/usr/lib/%{name}
cat > %{buildroot}/%{_bindir}/%{name} <<EOF --> 创建切入bash脚本,命名为 pello
#!/bin/bash
/usr/bin/python /usr/lib/%{name}/%{name}.pyc --> 使用 python 执行 pyc
EOF
chmod 0755 %{buildroot}/%{_bindir}/%{name} --> 修改切入bash脚本的权限
install -m 0644 %{name}.py* %{buildroot}/usr/lib/%{name}/ --> 将源 py 文件安装到 /usr/lib 中
%files
%license LICENSE --> LICENSE
%dir /usr/lib/%{name}/ --> /usr/lib/pello 目录
%{_bindir}/%{name} --> /usr/bin/pello 下的新建的切入脚本
/usr/lib/%{name}/%{name}.py* --> /usr/lib/pello 目录下的 pyc 字节码文件
%changelog
* Tue May 31 2016 Adam Miller <maxamillion@fedoraproject.org> - 0.1.1-1
- First pello package
- Requires 指令指定软件包的运行时依赖项,其中包括两个软件包:
- 在运行时执行字节代码所需的 python 软件包。
- 执行小入口点脚本所需的 bash 软件包。
- BuildRequires 指令指定软件包的 build-time 依赖项,它只包括 python 软件包。pello 程序需要 python 执行字节型构建流程。
- %build 部分指定如何构建软件,创建脚本的字节版本。请注意,在实际打包中,通常会根据所使用的发行版自动执行。
- %install 部分与这个事实对应,您必须将字节文件安装到系统上的库目录中,以便可以访问它。
创建打包程序脚本的示例显示了 spec文件本身可以脚本化。
7、分析 cello 的 spec 文件
cello 是使用 C语言编写的 hello world 示例程序,并且使用补丁 patch 进行修复
注:个人实验过程中,%prep 部分使用官方提供的示例,应用补丁会有错误,未进行解决
应用补丁的 cello 版本实验可能应用不成功
Name: cello
Version: 1.0
Release: 1%{?dist}
Summary: Hello World example implemented in C
License: GPLv3+
URL: https://www.example.com/%{name}
Source0: https://www.example.com/%{name}/releases/%{name}-%{version}.tar.gz
Patch0: cello-output-first-patch.patch --> 需要使用的补丁
BuildRequires: gcc --> 构建时需要 gcc
BuildRequires: make --> 自动化构建需要 make
%description
The long-tail description for our Hello World Example implemented in
C.
%prep --> 准备阶段:
%setup -q --> 解压源码等
%patch0 --> 应用补丁到源码(属于准备阶段)
%build --> 构建阶段:
make %{?_smp_mflags} --> %{?_smp_mflags} 表示 -j4 等,加速编译
%install
%make_install --> 调用 make install
%files
%license LICENSE --> LICENSE
%{_bindir}/%{name} --> 可执行文件
%changelog
* Tue May 31 2016 Adam Miller <maxamillion@fedoraproject.org> - 1.0-1
- First cello package
Makefile 文件中指定如何编译、clean,以及 install
# cello 的 Makefile 文件
cello:
gcc -g -o cello cello.c
clean:
rm cello
install:
mkdir -p $(DESTDIR)/usr/bin --> 递归创建目录
install -m 0755 cello $(DESTDIR)/usr/bin/cello --> 安装到指定路径