RPM打包基础指南

平时会使用RPM包安装软件,那么如何打RPM包呢?本文分别展示本地编译型、原生解释型、字节码编译解释型程序的RPM打包操作和基础原理。

2023年04月28日

1. 简介

RPM打包指南:

怎样准备打RPM包的源代码

这是为没有软件开发经验的人准备的,请看准备要打包的程序

怎样将源代码打成RPM包

这是为要将软件打成RPM包的开发人员准备的,请看打包程序

高级使用场景

这是打包RPM高级场景的一些指南,请看高级主题

1.1 PDF 版本

你可以从本文档的PDF版本下载PDF版本。

1.2 文章书写约定

文章书写预定如下:

  • 命令行输出和包括源代码在内的文本文件的内容放在代码块中:
[root@xx.rpmbuild]# ls -l
total 0
drwx------ 3 root root 58 Oct 18 16:47 BUILD
drwx------ 2 root root  6 Oct 18 16:47 BUILDROOT
drwx------ 4 root root 35 Oct 18 16:47 RPMS
drwx------ 2 root root  6 Oct 18 16:47 SOURCES
drwx------ 2 root root  6 Oct 18 16:47 SPECS
drwx------ 2 root root 92 Oct 18 16:21 SRPMS
  • 文中出现的其他内容引用或者专业术语会用粗体或者斜体来标识,第一次出现的术语引用会用超链接形式链接到不同的文档。
  • 工具名称、命令或者代码常用的内容会用代码的方式引用起来。

1.3 协作文章指南

你可以通过提issue或者PR的形式协作贡献GitHub repository上的该文档,不同形式的参与贡献都是受欢迎的。


2. 准备

在接下来的文章中,你需要安装下面提到的Linux依赖包:

一些包在FedoraCentOSRHEL系统中是默认安装的。下面命令中的包列出来了本文章完整的依赖。

On Fedora,CentOS 8,and RHEL 8:

dnf install gcc \
    rpm-build \
    rpm-devel \
    rpmlint \
    make python bash \
    coreutils diffutils patch \
    rpmdevtools

On CentOS 7 and RHEL 7:

yum install gcc \
    rpm-build \
    rpm-devel \
    rpmlint \
    make python bash \
    coreutils diffutils patch \
    rpmdevtools

3. 为什么要打RPM包?

RPM Package Manager(RPM)是运行在红帽企业版Linux、CentOS和Fedora上的一个包管理系统。RPM使得在这里Linux发行版上发布、管理和更新软件变得更加方便。许多软件的提供商用传统的压缩包的方式发布软件,但是使用RPM进行包管理有如下优点:

安装、重装、卸载、升级和校验包

用户可以使用标准的包管理工具(例如yum或者PackageKit)来安装、重装、卸载、升级和检验你的RPM包。

使用一个数据库来索引和校验包

因为RPM维护着一个所安装的RPM包和文件的数据库,用户可以在系统上索引和校验包。

使用元信息来描述包安装命令等

每一个RPM包都包含描述该包的组成、版本、发布、大小、项目主页和安装命令等元信息。

可以将原始软件源代码打包成RPM源文件和二进制包

RPM支持你将原始的软件源代码打包成RPM源文件和二进制包。RPM源文件包中包含了原始软件源代码和各种补丁文件。这种设计可以让你在软件有新的版本发布的时候更方便的维护包。

将包加入到Yum仓库中

你可以将RPM包加入到一个Yum仓库中以便其他人可以找到并部署你的软件。

将你的包进行数字签名

使用GPG签名密钥,你可以将你的RPM包进行数字签名,这样你的包的用户就能够确认包的可靠性。


4. 你打的第一个RPM包

创建一个RPM包是复杂的,这里完整说明了如何处理RPM Spec文件,其中的一些细节会简化或者跳过。

Name:       hello-world
Version:    1
Release:    1
Summary:    Most simple RPM package
License:    FIXME

%description
This is my first RPM package, which does nothing.

%prep
# we have no source, so nothing here

%build
cat > hello-world.sh <<EOF
#!/usr/bin/bash
echo Hello world
EOF

%install
mkdir -p %{buildroot}/usr/bin/
install -m 755 hello-world.sh %{buildroot}/usr/bin/hello-world.sh

%files
/usr/bin/hello-world.sh

%changelog
# let's skip this for now

保存上面的内容到hello-world.spec文件中,执行如下命令:

$ rpmdev-setuptree
$ rpmbuild -ba hello-world.spec

命令rpm-dev setuptree会在$HOME中创建一些必要的目录,这些目录不会被删除,所以该命令执行一次即可。

命令rpmbuild创建了rpm包,该命令输出类似:

Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.2PwGew
+ umask 022
+ cd /root/rpmbuild/BUILD
+ RPM_EC=0
++ jobs -p
+ exit 0
Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.GSkFSU
...
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.C2EpwV
+ umask 022
+ cd /root/rpmbuild/BUILD
+ /usr/bin/rm -rf /root/rpmbuild/BUILDROOT/hello-world-1-1.ky10.aarch64
+ RPM_EC=0
++ jobs -p
+ exit 0

~/rpmbuild/RPMS/X86_64/hello-world-1-1.x86_64.rpm就是你打的第一个RPM包,该包可以在系统中安装测试。


5. 准备要打包的程序

本章主要介绍源代码和软件的生成,是后续打RPM包要了解的基础知识。

5.1 源代码是什么?

源代码是计算机能够读懂的指令,描述如何进行一次计算。源代码通过编程语言来表达。本教程给出了使用不同编程语言编写的3个版本的Hello World程序,这3中不同语言的程序会用不同的方式打包,覆盖了RPM打包3个主要的操作方法。

世界上有数以千计的编程语言,本文档只举例了其中的3中,但是对于整体概念性的了解就足够了。

bash语言版的Hello World:

bello

#!/binb/bash

printf "Hello World\n"

Python语言版的Hello World:

pello.py

#!/usr/bin/env python

print("Hello World")

C语言版本的Hello World:

cello.c

#include <stdio.h>

int main(void) {
    printf "Hello World\n";
    return 0;
}

这3个版本的程序都是用命令行输出一个Hello World。

对于打包程序来说,了解如何编程不是必要的,但是如果知道的话会很有帮助。

5.2 程序是怎么制作的?

将人类能够读懂的源代码转化为计算机实际能够一行行执行的机器指令有很多方法,总结起来共有3种:

  1. 程序是本地编译的(natively compiled)。
  2. 程序是通过原生解释器进行解释的(interpreted by raw interpreting)。
  3. 程序是通过预编译好的byte文件进行解释的(interpreted by byte compiling)

5.2.1 本地编译型代码

本地编译型程序是将源代码编译为机器代码,也就是一个可执行的二进制文件,这样的程序是独立的。

这样的程序所打的RPM包是架构相关的,也就是说你使用64-bit(x86_64)的ADM或者Intel处理器编译的这类程序不能够在32-bit的处理器上运行,所生成的RPM包名上有处理器架构信息。

5.2.2 解释型代码

一些编程语言,例如bash或者Python,不将程序编译为机器代码,而是通过一个解释器或者虚拟机在没有转化源代码的情况下一行行执行代码。

完全用解释型语言写的程序是架构无关的,因此这样程序的RPM包名称中会有noarch字样。

解释型语言是字节码编译解释的或者是原生解释的,这两种类型在程序构建和打包流程上是不同的。

Raw-interpreted programs

原生解释型语言程序不需要进行编译,它们可以直接在解释器上运行。

Byte-compiled programs

字节码编译解释型语言程序需要预编译成字节码,然后在对应语言虚拟机上运行。

一些编程语言既可以选择原生解释也可以是字节码编译解释。

5.3 从源代码构建程序

这一节内容介绍如何从源代码构建程序。

  • 对于编译型编程语言写的程序,源代码需要经过构建产生机器码。不同的编程语言中这个过程可能叫做compiling或者translating,构建的结果是可以被程序员指定运行任务的run或者executed的程序。
  • 对于解释型编程语言写的程序,源代码不需要构建,直接可以有运行。
  • 对于字节码编译解释型编程语言写的程序,源代码首先需要编译成字节码,然后再在叫做对应语言的虚拟机上运行。

5.3.1 本地编译型代码

这个例子中,你会创建一个cello.c的C语言程序,并将之转化为可执行程序。

cello.c

#include <stdio.h>

int main(void){
    printf("Hello World\n")
    return 0
}

手动构建

调用C语言的GUN编译工具GCC将源代码转化为二进制:

gcc -g -o cello cello.c

执行二进制程序cello:

./cello

Hello World

就是这么简单,你已经成功从源代码构建并运行了本地编译型程序。

自动构建

除了手动构建之外,你还可以选择自动构建,这也是大型项目的最佳实践。自动构建需要创建一个Makefile文件并通过运行GNU make工具完成。

在cello.c源代码的同级目录创建一个文件并命名为Makefile

cello:
    gcc -g -o cello cello.c
clean:
    rm cello

执行make命令就可以构建程序:

$ make
make: 'cello' is up to date.

因为已经有一个构建结构存在,先删除然后再次make

$ make clean
rm cello

$ make
gcc -g -o cello cello.c

紧接着一次构建之后再次构建,不会执行任何内容

$ make
make: 'cello' is up to date.

最后,执行程序:

$ ./cello
Hello World

现在你学会手动或者使用工具构建一个程序了。

5.3.2 解释型代码

接下来的两个例子展示用Python语言写的字节码编译解释型程序和用bash编写的原生解释型程序。

下面两个例子中文件开头的的#!是所谓的shebang,并不是编程语言源代码的一部分。shebang能够让文本文件可执行,系统程序会解析含有shebang的文本并找到二进制可执行程序的path,这个可执行程序就作为对应编程语言的解释器。

字节码编译型代码

这个例子中,你将编译用python语言写的pello.py程序,然后用Python语言的虚拟机执行编译后生成第字节码。Python语言源代码可以原生解释运行,但是字节码编译解释的解释的方式执行更快,因此RPM打包一般更倾向于字节码编译版本推送给大家使用。

pello.py

#!/usr/bin/env python

print("Hello World")

不同的字节码编译解释型语言的编译运行流程是不同的,它取决于这个编程语言、语言虚拟机、相关工具以及语言定义的流程。

实践中Python往往是字节码编译解释运行的,但是不是这里展示的这种方式。接下来的流程为了简单没有遵照社区的标准。对于实际的Python项目,参考Software Packing and Distribution

字节码编译pello.py

$ python -m compileall pello.py

$ file pello.pyc
pello.pyc: python 2.7 byte-compiled

运行字节码pello.pyc

$ python pello.pyc
Hello World

原生解释型程序

在这个例子中,你会原生解释运行bash语言写的bello程序。

bello

#!/bin/bash

printf "Hello World\n"

使用shell脚本语言(例如bash)写的程序是原生解释运行的,因此你只需要将源代码赋予可执行权限并且运行:

$ chmod +x bello
$ ./bello
Hello World

5.4 给程序打补丁

一个补丁程序是可以用来更新其他源代码的源代码。补丁使用diff文件格式,因为它代表了程序两个不同版本之间的差异。diff文件需要用diff工具来创建,它可以被patch工具用来给源代码打补丁。

软件开发人员大都使用版本控制软件,例如git,来管理代码仓库,这类软件针对软件打补丁提供了自己的方法。

在接下来的示例中,我们使用diff创建源文件的补丁,再使用patch对源文件打补丁。对程序打补丁会在后续RPM打包处理SPEC files的时候用到。

打补丁和RPM打包有什么关系?在打包的时候,不是直接更改源代码,而是在源文件上使用补丁包。

1). 保留原始源代码内容:

diff -Naur cello.c.orig cello.c > cello-output-first-patch.patch

2). 修改cello.c内容:

#include <stdio.h>

int main(void) {
    printf("Hello World from my very first patch!\n");
    return 0;
}

3). 使用diff工具生成patch文件:

我们在diff工具使用的时候指定了一些列的参数,获取更多的内容可以参考diff man page

$ diff -Naur cello.c.orig cello.c
--- cello.c.orig        2016-05-26 17:21:30.478523360 -0500
+++ cello.c     2016-05-27 14:53:20.668588245 -0500
@@ -1,6 +1,6 @@
 #include<stdio.h>

 int main(void){
-    printf("Hello World!\n");
+    printf("Hello World from my very first patch!\n");
     return 0;
 }

-开头的行将会被删除,替换为+开头的行。

4). 将patch内容保存在文件中:

$ diff -Naur cello.c.orig cello.c > cello-output-first-patch.patch

5). 恢复原始的cello.c

$ cp cello.c.orig cello.c

我们保留了原始的cello.c,因为当一个rpm包构建的时候,使用的是原始文件,而不是修改过的。更多信息访问处理SPEC files

使用cello-output-first-patch.patch文件给cello.c程序打补丁,需要将补丁文件重定向到patch程序就可以:

$ patch < cello-output-first-patch.patch
patching file cello.c

现在cello.c的内容为:

$ cat cello.c
#include<stdio.h>

int main(void){
    printf("Hello World from my very first patch!\n");
    return 0;
}

可以构建并且执行打补丁后的cello.c

$ make clean
rm cello

$ make
gcc -g -o cello cello.c

$ ./cello
Hello World from my very first patch!

现在你学会了创建一个patch,给一个程序打patch,构建打补丁后的程序并运行了。

5.5 安装Arbitrary Artifacts

Linux和其他Unix-like操作系统的好处是Filesystem Hierarchy Standard(FHS),它规定了什么样的文件应该放置在什么样的目录中。通过RPM包安装的软件也应该遵照FHS的约定,比如,一个可执行文件应该放置在系统的PATH变量中。

本文章中,Arbitrary Artifacts指的是任何通过RPM包安装在系统中的内容。对于RPM包和系统来说,它可以是一个脚本、一个从源代码编译的二进制文件或者是提前编译好的二进制及任何其他文件。

我们将介绍把Arbitrary Artifacts安装到系统的两种方法:分别是installmake install命令。

5.5.1 使用install命令

有时候使用GNU make命令自动化工具不是最优的选择,例如,如果你的包很简单,没有什么别的内容,这种情况下,经常使用install(由系统coreutils提供)命令将Artifact放置在文件系统的指定目录中并赋予一系列的权限。

接下来的例子将使用我们之前创建的bello文件作为arbitrary artifact进行安装。注意你需要在命令中指定sudo权限或者使用root用户(删除命令中sudo部分)来执行这个命令。

这个例子将bello文件安装在/usr/bin/目录下并赋予可执行的权限。

脚本是:

sudo install -m 0755 bello /usr/bin/bello

现在bello在你的系统$PATH变量路径中来,因此你可以不指定全命令路径的情况下在系统的任何目录执行bello

$ cd ~

$ bello
Hello World

5.5.2 使用make install命令

一个自动安装程序到系统中的方法是make install命令,它需要你在Makefile中指定如何安装arbitrary artifact到系统中。

Makefile经常是由开发人员提供,而不是打包的人。

在Makefile中添加install部分:

cello:
        gcc -g -o cello cello.c

clean:
        rm cello

install:
        mkdir -p $(DESTDIR)/usr/bin
        install -m 0755 cello $(DESTDIR)/usr/bin/cello

5.6 准备打包的源代码

这一部分中的代码可以在这里找到。

开发人员常常会用压缩包的方式发布源代码并用来创建RPM包,这一部分你讲学习制作这样的压缩包。

创建源代码压缩包往往不是RPM打包人员做的事情,而是由开发人员负责。RPM打包人员打包时会拿到准备好的压缩包。

软件应该用某一个license发布,例如我们将要使用的GPLv3。每一个软件包当中应该有一个LICENSE文件指定license的内容。RPM打包人员在打包时要处理license文件。

接下来的示例使用的license内容如下:

$COPY$

5.7 将源代码放入压缩文件中

接下来的示例,我们分别将三个不同语言版本的Hello World放在三个gzip压缩包中。软件总是用这种方式发布并进一步打包。

5.7.1 bello

bello程序使用bash语言实现Hello World,源代码中仅仅包含bello bash脚本。因此,结果的tar.gz压缩包中除了LICENSE文件只包含一个文件。我们假设程序的版本是0.1

准备bello项目的发布包:

1). 将文件放入单独的一个目录中:

$COPY$

2). 将目录打成压缩包并移动到~/rpmbuild/SOURCES/目录中:

$COPY$

5.7.2 pello

pello程序使用Python语言实现Hello World,源代码中仅仅包含python.py程序。因此,结果的tar.gz压缩包中除了LICENSE文件只包含一个文件。我们假设程序的版本是0.1.1

准备pello项目的发布包:

1). 将文件放入单独的一个目录中:

$COPY$

2). 将目录打成压缩包并移动到~/rpmbuild/SOURCES/目录中:

$COPY$

5.7.3 cello

cello程序使用c语言实现Hello World,源代码中仅仅包含cello.cMakefile文件。因此,结果的tar.gz压缩包中除了LICENSE文件只包含两个文件。我们假设程序的版本是1.0

补丁包没有和发布包一起放在压缩文件中,RPM打包人员在RPM构建后进行程序打补丁。补丁包和.tar.gz一起放在~/rpmbuild/SOURCES/目录中。

准备cello项目的发布包:

1). 将文件放入单独的一个目录中:

$COPY$

2). 将目录打成压缩包并移动到~/rpmbuild/SOURCES/目录中:

$COPY$

3). 添加patch

$COPY$

现在要打RPM包的源代码都准备好了。


6. 打包程序

本指南介绍红帽系列Linux发行版的RPM打包,主要包括:

  • Fedora
  • Centos
  • Red Hat Enterprise Linux(RHEL)

这些Linux发行版使用rpm包。

尽管我们的目标环境重要是上述Linux发行版,本指南适合所有RPM based的发行版。需要注意的是本指南中使用的命令需要适配为发行版特定的方式。

本指南假设你没有任何给操作系统(Linux或者其他)打包软件的经验。

如果你不了解什么是一个软件RPM包或者GUN/Linux发行版,可以访问LinuxPackage Manager进行了解。

6.1 RPM Packages

这一部分中会介绍RPM打包的基础,高级主题请访问Advanced Topics

6.1.1 什么是RPM包?

RPM包是一个包含其他文件和系统须知元信息的文件。具体来说,RPM包中包含cpio压缩文件和RPM头,压缩文件就是文件的压缩包,RPM头包含了该RPM包的元信息。RPM打包人员利用包元信息决定如何解决依赖关系、在哪些路径安装软件和其他信息。

有两种类型的RPM包:

  • 源代码RPM包(SRPM)
  • 二进制RPM包

源代码RPM包和二进制RPM包有同样的文件格式和操作方式,但是它们有不同的内容和用途。一个源代码RPM包含源代码和一个SPEC文件,也有可能包含补丁文件,用来描述如何将源代码打包为RPM。二进制RPM包是从源代码和补丁构建成的二进制文件。

6.1.2 RPM Packaging工具

2. 准备章节中安装的rpmdevtools包提供了打包RPM的一些工具,列出来这些工具可以执行:

$COPY$

查看这些工具的详细使用方法可以看它们对应的man page

6.1.3 RPM Packaging Workspace

创建RPM打包workspace的目录结构需要使用rpmdev-setuptree工具:

$COPY$

创建的目录解释如下:

6.1.4 什么是SPEC File?

6.1.5 BuildRoots

6.1.6 RPM Macros

6.1.7 处理SPEC files

6.2 构建RPM包

6.2.1 RPMs源代码

6.2.2 RPMs二进制包

6.3 校验RPMs完整性

6.3.1 校验bello SPEC File

6.3.2 校验bello Binary RPM

6.3.3 校验pello SPEC File

6.3.4 校验pello Binary RPM

6.3.5 校验cello SPEC File

6.3.6 校验cello Binary RPM