历史由来

RPM 全称是 RedHat Package Manager,由 RedHat 公司发展而来的包管理系统。类似于 MacOS / Window 系统的软件安装,RPM 使得 Linux 上的软件安装也成为可能。RPM 不同于普通的 shell 脚本安装,其主要作用包括,RPM 包内程序的安装记录,依赖检查,升级及卸载等功能。除了 RPM 还有 SRPM,SRPM 是包含源码的 RPM 包,全称 Source RPM,可以根据不同的平台编译出不同的 RPM 包。

命名规则

RPM 的命名规则如下所示。

  1. <Name>-<Version>-<Release>.<Architecture>.rpm
  2. hello-world - 1.0 - 1.el7 .x86_64 .rpm
  3. 包名称 包的版本号 包发布次数 适合的硬件平台 后缀名称

SRPM 的命名规则如下所示,相比 RPM 缺少了硬件平台的相关信息,但源码包就应该与平台无关,由不同的平台各自编译出相对应的 RPM 包。

  1. <Name>-<Version>-<Release>.src.rpm
  2. cello - 1.0 - 1.el7 .src.rpm
  3. 包名称 包的版本号 包发布次数 后缀名称

常见的硬件平台可参考如下表格所示。

硬件平台 说明
i386/i586/i686 因特尔 CPU 的 32 位硬件平台
x86_64 64 位 CPU 的硬件平台
noarch 与平台无关,一般作用于跨平台开发语言,如 Java / Python / Bash

依赖管理

RPM 包内部能够定义依赖的 RPM 模块,但使用 rpm -i 命令进行安装的时候并不会自动下载其依赖度的 RPM 模块。为此,就诞生了 yum 管理 RPM 模块,这使得管理 RPM 包在安装的时候变得特别轻松,因为 yum 在安装的过程中能够下载 RPM 包中所需的 RPM 模块。检查 RPM 模块的依赖,可以使用 rpm -qR <rpm 包名> 命令。

常用命令

1. 安装

  1. rpm -ivh <包名>.rpm
  2. -i 安装
  3. -v 查看更详细的安装信息
  4. -h 显示安装的进度条

可以同时安装多个,参考如下所示

  1. rpm -ivh <包名1>.rpm <包名2>.rpm ...

安装远程 RPM 包,参考如下所示

  1. rpm -ivh http://ip/path/package.rpm

检查是否存在包依赖,参考如下所示

  1. rpm -ivh packagename.rpm --test

重新安装,参考如下所示

  1. rpm -ivh packagename.rpm --replacepkgs

2. 卸载

  1. rpm -e packagename

若发现卸载时执行脚本出错,可以增加 —noscripts 参数忽略

  1. rpm -e packagename --noscripts

3. 升级与更新

  1. rpm -Uvh packagename.rpm
  1. rpm -Fvh packagename.rpm
  1. -U 表示如果系统内部未安装,则系统将直接安装;若已安装,则更新处理
  2. -F 表示如果系统内部未安装,则系统将忽略处理;若已安装,则更新处理

4. 查询

查询所有已安装的 rpm 程序,参考如下

  1. $ rpm -qa
  1. gpm-libs-1.20.7-6.el7.x86_64
  2. libidn-1.28-4.el7.x86_64
  3. libnl3-3.2.28-4.el7.x86_64
  4. aic94xx-firmware-30-6.el7.noarch
  5. gawk-4.0.2-4.el7_3.1.x86_64
  6. grep-2.20-3.el7.x86_64
  7. nss-softokn-3.36.0-5.el7_5.x86_64
  8. ... ...

检查是否安装 rpm 程序,参考如下

  1. $ rpm -q httpd
  2. package httpd is not installed
  3. $ rpm -q logrotate
  4. logrotate-3.8.6-17.el7.x86_64

列出 rpm 程序的详细信息,参考如下

  1. $ rpm -qi logrotate
  1. Name : logrotate
  2. Version : 3.8.6
  3. Release : 17.el7
  4. Architecture: x86_64
  5. Install Date: Fri May 10 17:48:28 2019
  6. Group : System Environment/Base
  7. Size : 107156
  8. License : GPL+
  9. Signature : RSA/SHA256, Mon Nov 12 22:39:25 2018, Key ID 24c6a8a7f4a80eb5
  10. Source RPM : logrotate-3.8.6-17.el7.src.rpm
  11. Build Date : Wed Oct 31 03:13:00 2018
  12. Build Host : x86-01.bsys.centos.org
  13. Relocations : (not relocatable)
  14. Packager : CentOS BuildSystem <http://bugs.centos.org>
  15. Vendor : CentOS
  16. URL : https://github.com/logrotate/logrotate
  17. Summary : Rotates, compresses, removes and mails system log files
  18. ... ...

列出系统内所有 rpm 程序相关的文件(从 rpm 数据库中读取),参考如下

  1. $ rpm -ql logrotate
  1. /etc/cron.daily/logrotate
  2. /etc/logrotate.conf
  3. /etc/logrotate.d
  4. /etc/rwtab.d/logrotate
  5. /usr/sbin/logrotate
  6. /usr/share/doc/logrotate-3.8.6
  7. /usr/share/doc/logrotate-3.8.6/CHANGES
  8. /usr/share/doc/logrotate-3.8.6/COPYING
  9. /usr/share/man/man5/logrotate.conf.5.gz
  10. /usr/share/man/man8/logrotate.8.gz
  11. /var/lib/logrotate
  12. /var/lib/logrotate/logrotate.status

列出 rpm 程序的依赖,参考如下

  1. $ rpm -qR logrotate
  1. /bin/sh
  2. /bin/sh
  3. config(logrotate) = 3.8.6-17.el7
  4. coreutils >= 5.92
  5. libacl.so.1()(64bit)
  6. libacl.so.1(ACL_1.0)(64bit)
  7. libc.so.6()(64bit)
  8. libc.so.6(GLIBC_2.14)(64bit)
  9. libc.so.6(GLIBC_2.2.5)(64bit)
  10. libc.so.6(GLIBC_2.3)(64bit)
  11. libc.so.6(GLIBC_2.3.4)(64bit)
  12. libc.so.6(GLIBC_2.4)(64bit)
  13. libc.so.6(GLIBC_2.7)(64bit)
  14. libc.so.6(GLIBC_2.8)(64bit)
  15. libpopt.so.0()(64bit)
  16. libpopt.so.0(LIBPOPT_0)(64bit)
  17. libselinux.so.1()(64bit)
  18. popt
  19. rpmlib(CompressedFileNames) <= 3.0.4-1
  20. rpmlib(FileDigests) <= 4.6.0-1
  21. rpmlib(PayloadFilesHavePrefix) <= 4.0-1
  22. rtld(GNU_HASH)
  23. rpmlib(PayloadIsXz) <= 5.2-1

通过文件查询属于哪个 rpm 包程序,参考如下

  1. $ rpm -ql logrotate
  2. /etc/cron.daily/logrotate
  3. /etc/logrotate.conf
  4. /etc/logrotate.d
  5. ... ...
  6. $ rpm -qf /etc/logrotate.conf
  7. logrotate-3.8.6-17.el7.x86_64

即使 /etc/logrotate.conf 文件被删除也可以通过此方式查询,因为这查询的是数据库,并发本地文件。

查询 rpm 包的依赖,-p (package), -R (Requires) 参考如下

  1. $ rpm -qpR packagename.rpm
  2. ...

制作 RPM

1. 打包 RPM

让我们来制作一个 RPM 包,制作 RPM 包过程非常有趣,当然,如果想要更加深入的了解打包的过程和内部的 Marcos 及变量,可以参考 https://rpm-packaging-guide.github.io/ 文档说明。这里以 CentOS 7 为例,来制作 RPM 包,但适用于其他使用 RPM 标准构建的操作系统,编辑 hello.spec 内容如下。

  1. Name: hello-world
  2. Version: 1.0
  3. Release: 1%{?dist}
  4. Summary: My first RPM Package
  5. Group: iCuter
  6. License: MIT
  7. URL: https://www.icuter.cn
  8. %description
  9. This is my first RPM package SPEC
  10. %prep
  11. # Nothing to do
  12. %build
  13. cat > hello-world.sh <<EOF
  14. #!/usr/bin/bash
  15. echo Hello world
  16. EOF
  17. %install
  18. mkdir -p %{buildroot}/usr/bin/
  19. install -m 755 hello-world.sh %{buildroot}/usr/bin/hello-world.sh
  20. %files
  21. /usr/bin/hello-world.sh
  22. %doc
  23. # No documents
  24. %changelog
  1. $ rpmbuild -ba hello.spec
  2. Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.cFj1Sq
  3. + umask 022
  4. + cd /root/rpmbuild/BUILD
  5. + exit 0
  6. Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.ytayuz
  7. + umask 022
  8. + cd /root/rpmbuild/BUILD
  9. + cat
  10. + exit 0
  11. Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.xdiO6H
  12. + umask 022
  13. + cd /root/rpmbuild/BUILD
  14. + '[' /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64 '!=' / ']'
  15. + rm -rf /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64
  16. ++ dirname /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64
  17. + mkdir -p /root/rpmbuild/BUILDROOT
  18. + mkdir /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64
  19. + mkdir -p /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64/usr/bin/
  20. + install -m 755 hello-world.sh /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64/usr/bin/hello-world.sh
  21. + /usr/lib/rpm/check-buildroot
  22. + /usr/lib/rpm/redhat/brp-compress
  23. + /usr/lib/rpm/redhat/brp-strip /usr/bin/strip
  24. + /usr/lib/rpm/redhat/brp-strip-comment-note /usr/bin/strip /usr/bin/objdump
  25. + /usr/lib/rpm/redhat/brp-strip-static-archive /usr/bin/strip
  26. + /usr/lib/rpm/brp-python-bytecompile /usr/bin/python 1
  27. + /usr/lib/rpm/redhat/brp-python-hardlink
  28. + /usr/lib/rpm/redhat/brp-java-repack-jars
  29. Processing files: hello-world-1.0-1.el7.centos.x86_64
  30. Provides: hello-world = 1.0-1.el7.centos hello-world(x86-64) = 1.0-1.el7.centos
  31. Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
  32. Requires: /usr/bin/bash
  33. Checking for unpackaged file(s): /usr/lib/rpm/check-files /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64
  34. Wrote: /root/rpmbuild/SRPMS/hello-world-1.0-1.el7.centos.src.rpm
  35. Wrote: /root/rpmbuild/RPMS/x86_64/hello-world-1.0-1.el7.centos.x86_64.rpm
  36. Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.Gb9eW8
  37. + umask 022
  38. + cd /root/rpmbuild/BUILD
  39. + /usr/bin/rm -rf /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64
  40. + exit 0

Executing(%prep)、Executing(%build)、Executing(%install) 分别执行了我们定义的 %prep、%build、%install 脚本,其他则是由 rpmbuild 命令自动生成的脚本语句,可以说,rpmbuild 帮助我们做了很多重复的工作。

通过输出日志可以看到,%{buildroot} 的内容是 /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64/

下面的日志输出的就是构建的 SRPM / RPM 包的路径

  1. Wrote: /root/rpmbuild/SRPMS/hello-world-1.0-1.el7.centos.src.rpm
  2. Wrote: /root/rpmbuild/RPMS/x86_64/hello-world-1.0-1.el7.centos.x86_64.rpm

简单分析一下 install 命令

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

相当于以下命令的组合

  1. chmod 755 hello-world.sh
  2. cp hello-world.sh %{buildroot}/usr/bin/hello-world.sh

再来看看我们生成的 hello-world-1.0-1.el7.centos.x86_64.rpm 的详细信息

  1. $ rpm -qpi /root/rpmbuild/RPMS/x86_64/hello-world-1.0-1.el7.centos.x86_64.rpm
  1. Name : hello-world
  2. Version : 1.0
  3. Release : 1.el7.centos
  4. Architecture: x86_64
  5. Install Date: (not installed)
  6. Group : iCuter
  7. Size : 33
  8. License : MIT
  9. Signature : (none)
  10. Source RPM : hello-world-1.0-1.el7.centos.src.rpm
  11. Build Date : Sat 13 Mar 2021 05:18:43 PM CST
  12. Build Host : VM_0_6_centos
  13. Relocations : (not relocatable)
  14. URL : https://www.icuter.cn
  15. Summary : My first RPM Package
  16. Description :
  17. This is my first RPM package SPEC

对于临时生成的系统文件夹,可根据日志输出,通过 tree 命令查看 /root/rpmbuild 的目录结构

  1. $ tree /root/rpmbuild
  2. /root/rpmbuild
  3. |-- BUILD
  4. | `-- hello-world.sh
  5. |-- BUILDROOT
  6. |-- RPMS
  7. | `-- x86_64
  8. | `-- hello-world-1.0-1.el7.centos.x86_64.rpm
  9. |-- SOURCES
  10. |-- SPECS
  11. `-- SRPMS
  12. `-- hello-world-1.0-1.el7.centos.src.rpm

2. 安装 RPM

  1. $ rpm -ivh /root/rpmbuild/RPMS/x86_64/hello-world-1.0-1.el7.centos.x86_64.rpm
  2. Preparing... ################################# [100%]
  3. Updating / installing...
  4. 1:hello-world-1.0-1.el7.centos ################################# [100%]

检查一下是否已经安装成功了

  1. $ rpm -q hello-world
  2. hello-world-1.0-1.el7.centos.x86_64

运行一下,我们制作的 hello-world.sh 脚本

  1. $ sh /usr/bin/hello-world.sh
  2. Hello world

3. 卸载 RPM

  1. $ rpm -e hello-world

检查一下是否已经卸载成功了

  1. $ rpm -q hello-world
  2. package hello-world is not installed

制作 SRPM

为了实现跨平台安装,我们可以构建 SRPM,然后在不同平台构建 RPM 包,主要方便 C 语言在不同平台编译,对于 Java / Python / Bash 等这类本身就跨平台的语言而言可以直接构建 RPM 包。

1. 准备 C 程序

  1. $ mkdir cello-1.0/
  2. $ vi cello.c
  1. #include<stdio.h>
  2. int main(void){
  3. printf("Hello World!\n");
  4. return 0;
  5. }

2. 部署 C 程序包

$ tar -czf cello-1.0.tar.gz cello-1.0/
$ mkdir -p ~/rpmbuild/SOURCES/
$ mv cello-1.0.tar.gz ~/rpmbuild/SOURCES/

3. 编辑 SPEC 文件

Name:    cello
Version: 1.0
Release: 1%{?dist}
Summary: SRPM Hello World
URL:     http://example.com/first-srpm
License: GPLv3

BuildRequires: gcc

Requires:      bash

Source0: https://example.com/srpm/%{name}-%{version}.tar.gz
Source1: %{name}-%{version}.tar.gz

%description
This is my first RPM demo

%prep
%setup -q

%build
gcc -g -o cello cello.c

%install
mkdir -p %{buildroot}/usr/bin
install -m 0755 cello %{buildroot}/usr/bin/cello

%files
/usr/bin/%{name}

%changelog
  • Source0 中指定了 cello-1.0.tar.gz 的包来源,但 Linux 系统会由于安全原因不会主动下载,若想要让操作系统主动下载 Source0 中的源码,可以尝试 Stackoverflow 上的解决方案
  • Source1 指定的程序必须在 ~/rpmbuild/SOURCES/ 目录中存在
  • %setup -q 使用 tar -xof 命令代替 tar -xovvf 解压,更多使用方法可参考 %step 文档说明。另外,对于源码包的压缩要求是必须 tar.gz 包,而不能是 zip 包
  • %build 根据 cello.c 编译出可执行文件 cello
  • %install 安装 cello 文件到 %{buildroot} 目录
  • %files 指定要打包的文件,这些文件必须要存在于 %{buildroot} 目录

    4. 构建 SRPM

    ```bash $ rpmbuild -ba cello.spec

… … Wrote: /root/rpmbuild/SRPMS/cello-1.0-1.el7.src.rpm Wrote: /root/rpmbuild/RPMS/x86_64/cello-1.0-1.el7.x86_64.rpm … …

<a name="xPkhb"></a>
#### 5. 通过 SRPM 构建 RPM
```bash
$ rpmbuild --rebuild /root/rpmbuild/SRPMS/cello-1.0-1.el7.src.rpm

... ...
Wrote: /root/rpmbuild/RPMS/x86_64/cello-1.0-1.el7.x86_64.rpm
... ...

这样,我们就可以根据 RPM 包进行安装了

$ rpm -ivh /root/rpmbuild/RPMS/x86_64/cello-1.0-1.el7.x86_64.rpm

... ...

SPEC 定义

1. Headers

Directive 说明
Name 包的名称
Version 版本号
Release 发布平台,初始值一般为 1%{?dist},后续从 1 开始递增,如 2%{?dist},3%{?dist}
Summary 包的简介
License 证书,如 MIT、BSD、BSD、GPLv2
URL 包程序对应的网站链接
Source0 源程序压缩包名称(*.tar.gz),支持多值,使用 Source1、Source2 等表示,将主动到 ~/rpmbuild/SOURCES/ 目录下获取,如果是网络程序,通过rpmbuild 进行构建的时候会因系统安全原因不会主动下载
Patch0 补丁包程序名称(*.patch),支持多值,使用 Patch1、Patch2 等表示,将主动到 ~/rpmbuild/SOURCES/ 目录下获取
BuildArch 如果非平台相关,可以设置为 BuildArch: noarch,一般作用于跨平台语言,如 Java / Python / Bash 等,默认情况下会根据平台来生成名称,如 x86_64
BuildRequires 执行 %build 前需要的包依赖说明,以逗号或空格作为分隔符
Requires 执行 %install 前需要的包依赖说明,以逗号或空格作为分隔符
ExcludeArch 指定例外的平台,表示不能在该平台上构建

2. Body

Directive 说明
%description 包的详细描述
%prep 程序编译前的准备工作,如解压缩程序包,一般配合 %setup 使用
%build 程序编译,如 C 程序的 gcc 编译
%install 将编译(build)完成的 %{builddir} 文件安装到 %{buildroot} 目录,一般是从 ~/rpmbuild/BUILD
%check 对编译后的程序进行单元测试,如果是跨平台编译的话则需要用到
%files 安装到操作系统后的文件列表
%changelog 发布包的记录日志,记录的是每一个发布包之间的差异,注意并非代码差异,日志格式参考如下。
Day-of-Week Month Day Year Name Surname - Version-Release
- Message …
- Message …

完整的样例参考如下
%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

3. Scriptlets

Directive 说明
%pre 先于 %install 执行,即 RPM 安装前执行
%post 晚于 %install 执行,即 RPM 安装后执行
%preun RPM 包卸载前执行
%postun RPM 包卸载后执行

4. Marcos

展示 RPM 所有的内置 Marcos,可以使用 rpm --showrc 命令,但因为输出的内容较多,所以一般配合 grep 来获取更精确的信息。或者在 /usr/lib/rpm/macros.d/ 目录下也可以看到内置的 Marcos 函数,当然这里的 Marcos 定义也会在 rpm --showrc 中显示。

现在,我们来做一些有趣的事情,通过 rpm --eval 命令运行 Marcos 中定义的函数。

$ rpm --showrc | grep python

... ...
-14: python_sitearch    %(%{__python} -c "from distutils.sysconfig import get_python_lib; import sys; sys.stdout.write(get_python_lib(1))")
-14: python_sitelib    %(%{__python} -c "from distutils.sysconfig import get_python_lib; import sys; sys.stdout.write(get_python_lib())")
-14: python_version    %(%{__python} -c "import sys; sys.stdout.write(sys.version[:3])")
$ rpm --eval %{python_sitearch}
/usr/lib64/python2.7/site-packages

$ rpm --eval %{python_sitelib}
/usr/lib/python2.7/site-packages

$ rpm --eval %{python_version}
2.7

通过 --eval 命令参数,我们就可以调试我们自定义的 Marcos 函数了。

我们还能覆盖内置的 Marcos 定义。

$ rpm --showrc | grep _topdir
... ...
-14: _topdir    %{getenv:HOME}/rpmbuild

通过修改 ~/.rpmmacros 文件来覆盖 -14: _topdir Marcos。

%_topdir /home/rpmbuild
$ rpm --eval %{_topdir}
/home/rpmbuild

最后,我们来学习如何定义自己的 Marcos。

$ vi ~/.rpmmacros

%mymacro %(echo -n "My first macros")
$ rpm --eval '%mymacro'
My first macros

想要了解更多 Marcos,可以参考 http://rpm.org/user_doc/macros.html

YUM 管理 RPM 包

除了使用 rpm 命令进行安装、查询、卸载之外,我们还能够使用 yum 命令做相似的事情。

1. 安装

$ yum install /root/rpmbuild/RPMS/x86_64/hello-world-1.0-1.el7.centos.x86_64.rpm

2. 查询

$ yum info hello-world
Installed Packages
Name        : hello-world
Arch        : x86_64
Version     : 1.0
Release     : 1.el7.centos
... ...

3. 卸载

$ yum erase hello-world

$ yum info hello-world
Error: No matching Packages to list

参考文献

https://rpm-packaging-guide.github.io/
https://fedoranews.org/alex/tutorial/rpm/
http://linux.vbird.org/linux_basic/0520rpm_and_srpm.php
http://rpm.org/user_doc/macros.html