历史由来
RPM 全称是 RedHat Package Manager,由 RedHat 公司发展而来的包管理系统。类似于 MacOS / Window 系统的软件安装,RPM 使得 Linux 上的软件安装也成为可能。RPM 不同于普通的 shell 脚本安装,其主要作用包括,RPM 包内程序的安装记录,依赖检查,升级及卸载等功能。除了 RPM 还有 SRPM,SRPM 是包含源码的 RPM 包,全称 Source RPM,可以根据不同的平台编译出不同的 RPM 包。
命名规则
RPM 的命名规则如下所示。
<Name>-<Version>-<Release>.<Architecture>.rpmhello-world - 1.0 - 1.el7 .x86_64 .rpm包名称 包的版本号 包发布次数 适合的硬件平台 后缀名称
SRPM 的命名规则如下所示,相比 RPM 缺少了硬件平台的相关信息,但源码包就应该与平台无关,由不同的平台各自编译出相对应的 RPM 包。
<Name>-<Version>-<Release>.src.rpmcello - 1.0 - 1.el7 .src.rpm包名称 包的版本号 包发布次数 后缀名称
常见的硬件平台可参考如下表格所示。
| 硬件平台 | 说明 |
|---|---|
| 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. 安装
rpm -ivh <包名>.rpm-i 安装-v 查看更详细的安装信息-h 显示安装的进度条
可以同时安装多个,参考如下所示
rpm -ivh <包名1>.rpm <包名2>.rpm ...
安装远程 RPM 包,参考如下所示
rpm -ivh http://ip/path/package.rpm
检查是否存在包依赖,参考如下所示
rpm -ivh packagename.rpm --test
重新安装,参考如下所示
rpm -ivh packagename.rpm --replacepkgs
2. 卸载
rpm -e packagename
若发现卸载时执行脚本出错,可以增加 —noscripts 参数忽略
rpm -e packagename --noscripts
3. 升级与更新
rpm -Uvh packagename.rpm
rpm -Fvh packagename.rpm
-U 表示如果系统内部未安装,则系统将直接安装;若已安装,则更新处理-F 表示如果系统内部未安装,则系统将忽略处理;若已安装,则更新处理
4. 查询
查询所有已安装的 rpm 程序,参考如下
$ rpm -qa
gpm-libs-1.20.7-6.el7.x86_64libidn-1.28-4.el7.x86_64libnl3-3.2.28-4.el7.x86_64aic94xx-firmware-30-6.el7.noarchgawk-4.0.2-4.el7_3.1.x86_64grep-2.20-3.el7.x86_64nss-softokn-3.36.0-5.el7_5.x86_64... ...
检查是否安装 rpm 程序,参考如下
$ rpm -q httpdpackage httpd is not installed$ rpm -q logrotatelogrotate-3.8.6-17.el7.x86_64
列出 rpm 程序的详细信息,参考如下
$ rpm -qi logrotate
Name : logrotateVersion : 3.8.6Release : 17.el7Architecture: x86_64Install Date: Fri May 10 17:48:28 2019Group : System Environment/BaseSize : 107156License : GPL+Signature : RSA/SHA256, Mon Nov 12 22:39:25 2018, Key ID 24c6a8a7f4a80eb5Source RPM : logrotate-3.8.6-17.el7.src.rpmBuild Date : Wed Oct 31 03:13:00 2018Build Host : x86-01.bsys.centos.orgRelocations : (not relocatable)Packager : CentOS BuildSystem <http://bugs.centos.org>Vendor : CentOSURL : https://github.com/logrotate/logrotateSummary : Rotates, compresses, removes and mails system log files... ...
列出系统内所有 rpm 程序相关的文件(从 rpm 数据库中读取),参考如下
$ rpm -ql logrotate
/etc/cron.daily/logrotate/etc/logrotate.conf/etc/logrotate.d/etc/rwtab.d/logrotate/usr/sbin/logrotate/usr/share/doc/logrotate-3.8.6/usr/share/doc/logrotate-3.8.6/CHANGES/usr/share/doc/logrotate-3.8.6/COPYING/usr/share/man/man5/logrotate.conf.5.gz/usr/share/man/man8/logrotate.8.gz/var/lib/logrotate/var/lib/logrotate/logrotate.status
列出 rpm 程序的依赖,参考如下
$ rpm -qR logrotate
/bin/sh/bin/shconfig(logrotate) = 3.8.6-17.el7coreutils >= 5.92libacl.so.1()(64bit)libacl.so.1(ACL_1.0)(64bit)libc.so.6()(64bit)libc.so.6(GLIBC_2.14)(64bit)libc.so.6(GLIBC_2.2.5)(64bit)libc.so.6(GLIBC_2.3)(64bit)libc.so.6(GLIBC_2.3.4)(64bit)libc.so.6(GLIBC_2.4)(64bit)libc.so.6(GLIBC_2.7)(64bit)libc.so.6(GLIBC_2.8)(64bit)libpopt.so.0()(64bit)libpopt.so.0(LIBPOPT_0)(64bit)libselinux.so.1()(64bit)poptrpmlib(CompressedFileNames) <= 3.0.4-1rpmlib(FileDigests) <= 4.6.0-1rpmlib(PayloadFilesHavePrefix) <= 4.0-1rtld(GNU_HASH)rpmlib(PayloadIsXz) <= 5.2-1
通过文件查询属于哪个 rpm 包程序,参考如下
$ rpm -ql logrotate/etc/cron.daily/logrotate/etc/logrotate.conf/etc/logrotate.d... ...$ rpm -qf /etc/logrotate.conflogrotate-3.8.6-17.el7.x86_64
即使 /etc/logrotate.conf 文件被删除也可以通过此方式查询,因为这查询的是数据库,并发本地文件。
查询 rpm 包的依赖,-p (package), -R (Requires) 参考如下
$ rpm -qpR packagename.rpm...
制作 RPM
1. 打包 RPM
让我们来制作一个 RPM 包,制作 RPM 包过程非常有趣,当然,如果想要更加深入的了解打包的过程和内部的 Marcos 及变量,可以参考 https://rpm-packaging-guide.github.io/ 文档说明。这里以 CentOS 7 为例,来制作 RPM 包,但适用于其他使用 RPM 标准构建的操作系统,编辑 hello.spec 内容如下。
Name: hello-worldVersion: 1.0Release: 1%{?dist}Summary: My first RPM PackageGroup: iCuterLicense: MITURL: https://www.icuter.cn%descriptionThis is my first RPM package SPEC%prep# Nothing to do%buildcat > hello-world.sh <<EOF#!/usr/bin/bashecho Hello worldEOF%installmkdir -p %{buildroot}/usr/bin/install -m 755 hello-world.sh %{buildroot}/usr/bin/hello-world.sh%files/usr/bin/hello-world.sh%doc# No documents%changelog
$ rpmbuild -ba hello.specExecuting(%prep): /bin/sh -e /var/tmp/rpm-tmp.cFj1Sq+ umask 022+ cd /root/rpmbuild/BUILD+ exit 0Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.ytayuz+ umask 022+ cd /root/rpmbuild/BUILD+ cat+ exit 0Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.xdiO6H+ umask 022+ cd /root/rpmbuild/BUILD+ '[' /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64 '!=' / ']'+ rm -rf /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64++ dirname /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64+ mkdir -p /root/rpmbuild/BUILDROOT+ mkdir /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64+ mkdir -p /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64/usr/bin/+ install -m 755 hello-world.sh /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64/usr/bin/hello-world.sh+ /usr/lib/rpm/check-buildroot+ /usr/lib/rpm/redhat/brp-compress+ /usr/lib/rpm/redhat/brp-strip /usr/bin/strip+ /usr/lib/rpm/redhat/brp-strip-comment-note /usr/bin/strip /usr/bin/objdump+ /usr/lib/rpm/redhat/brp-strip-static-archive /usr/bin/strip+ /usr/lib/rpm/brp-python-bytecompile /usr/bin/python 1+ /usr/lib/rpm/redhat/brp-python-hardlink+ /usr/lib/rpm/redhat/brp-java-repack-jarsProcessing files: hello-world-1.0-1.el7.centos.x86_64Provides: hello-world = 1.0-1.el7.centos hello-world(x86-64) = 1.0-1.el7.centosRequires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1Requires: /usr/bin/bashChecking for unpackaged file(s): /usr/lib/rpm/check-files /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64Wrote: /root/rpmbuild/SRPMS/hello-world-1.0-1.el7.centos.src.rpmWrote: /root/rpmbuild/RPMS/x86_64/hello-world-1.0-1.el7.centos.x86_64.rpmExecuting(%clean): /bin/sh -e /var/tmp/rpm-tmp.Gb9eW8+ umask 022+ cd /root/rpmbuild/BUILD+ /usr/bin/rm -rf /root/rpmbuild/BUILDROOT/hello-world-1.0-1.el7.centos.x86_64+ 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 包的路径
Wrote: /root/rpmbuild/SRPMS/hello-world-1.0-1.el7.centos.src.rpmWrote: /root/rpmbuild/RPMS/x86_64/hello-world-1.0-1.el7.centos.x86_64.rpm
简单分析一下 install 命令
install -m 755 hello-world.sh %{buildroot}/usr/bin/hello-world.sh
相当于以下命令的组合
chmod 755 hello-world.shcp hello-world.sh %{buildroot}/usr/bin/hello-world.sh
再来看看我们生成的 hello-world-1.0-1.el7.centos.x86_64.rpm 的详细信息
$ rpm -qpi /root/rpmbuild/RPMS/x86_64/hello-world-1.0-1.el7.centos.x86_64.rpm
Name : hello-worldVersion : 1.0Release : 1.el7.centosArchitecture: x86_64Install Date: (not installed)Group : iCuterSize : 33License : MITSignature : (none)Source RPM : hello-world-1.0-1.el7.centos.src.rpmBuild Date : Sat 13 Mar 2021 05:18:43 PM CSTBuild Host : VM_0_6_centosRelocations : (not relocatable)URL : https://www.icuter.cnSummary : My first RPM PackageDescription :This is my first RPM package SPEC
对于临时生成的系统文件夹,可根据日志输出,通过 tree 命令查看 /root/rpmbuild 的目录结构
$ tree /root/rpmbuild/root/rpmbuild|-- BUILD| `-- hello-world.sh|-- BUILDROOT|-- RPMS| `-- x86_64| `-- hello-world-1.0-1.el7.centos.x86_64.rpm|-- SOURCES|-- SPECS`-- SRPMS`-- hello-world-1.0-1.el7.centos.src.rpm
2. 安装 RPM
$ rpm -ivh /root/rpmbuild/RPMS/x86_64/hello-world-1.0-1.el7.centos.x86_64.rpmPreparing... ################################# [100%]Updating / installing...1:hello-world-1.0-1.el7.centos ################################# [100%]
检查一下是否已经安装成功了
$ rpm -q hello-worldhello-world-1.0-1.el7.centos.x86_64
运行一下,我们制作的 hello-world.sh 脚本
$ sh /usr/bin/hello-world.shHello world
3. 卸载 RPM
$ rpm -e hello-world
检查一下是否已经卸载成功了
$ rpm -q hello-worldpackage hello-world is not installed
制作 SRPM
为了实现跨平台安装,我们可以构建 SRPM,然后在不同平台构建 RPM 包,主要方便 C 语言在不同平台编译,对于 Java / Python / Bash 等这类本身就跨平台的语言而言可以直接构建 RPM 包。
1. 准备 C 程序
$ mkdir cello-1.0/$ vi cello.c
#include<stdio.h>int main(void){printf("Hello World!\n");return 0;}
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 - Message … - Message … 完整的样例参考如下 %changelog - 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
