RPM包管理

Posted by feizaipp on November 24, 2018

我的博客

1. 定义

RPM 是 Red-Hat Package Manager (RPM 软件包管理器)的缩写,这一文件格式名称虽然打上了 RedHat 的标志,但是其原始设计理念是开放式的,现在包括 OpenLinux 、 S.u.S.E. 以及 Turbo Linux 等 Linux 的分发版本都有采用,可以算是公认的行业标准了。

2. 常用选项

假如我们有一个名为 pam_usb-1.0-1.el7.x86_64.rpm 的RPM包,对该 RPM 包的常用操作如下:

  • 安装:rpm -ivh pam_usb-1.0-1.el7.x86_64.rpm
  • 卸载:rpm -e pam_usb
  • 查询:rpm -qa pam_usb,可以使用通配符,例如:rpm -qa pam*

查询某个文件来自哪个 rpm 包,假如 pam_usb 包里有一个 pam_usb.so 的动态库,要想知道 pam_usb.so 来自于哪个 RPM 包可以使用如下命令:

  • rpm -qf pam_usb.so

2. RPM 包制作

RPM 包使用 rpmbuild 工具制作,开发人员需要编写 spec 文件供 rpmbuild 使用。

2.1 安装rpmbuild工具

  • yum install rpmbuild
  • yum install rpmdevtools

安装完成后,查看:

  • rpmbuild –showrc | grep topdir

执行结果如下:

_builddir             %{_topdir}/BUILD  
_buildrootdir    %{_topdir}/BUILDROOT  
_rpmdir             %{_topdir}/RPMS  
_sourcedir         %{_topdir}/SOURCES  
_specdir             %{_topdir}/SPECS  
_srcrpmdir          %{_topdir}/SRPMS  
_topdir                /home/feizaipp/work/rpmbuild  

上面的结果表示 rpmbuild 默认工作路径。%_topdir 的宏变量定义在 /usr/lib/rpm/macros 文件里的 ,上面结果的宏定义代表生成 RPM 所需的原材料和输出的 RPM 所在的目录,下面会介绍各个目录的作用。如果用户想更改这个目录,就在用户的 home 目录下建立一个名为 .rpmmacros 的隐藏文件。然后在这里重新定义 %_topdir ,指向一个新的目录。例如:

%_topdir /home/feizaipp/work/rpmbuild

通过上面命令执行结果可以知道, rpmbuild 工具正常工作需要 6 个目录,下面是这 6 个目录所代表的含义:

  • _builddir : 编译 rpm 包的临时目录
  • _buildrootdir : 编译后生成的软件临时安装目录
  • _rpmdir : 最终生成的可安装rpm包的所在目录
  • _sourcedir : 所有源代码和补丁文件的存放目录
  • _specdir : 存放SPEC文件的目录
  • _srcrpmdir : 软件最终的rpm源码格式存放路径

上面我们将 _topdir 设置为 /home/feizaipp/work/rpmbuild 后,我们需要在这个目录手动创建上面那 6 个目录:

mkdir -pv {BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}

也可以执行下面命令,该命令会在当前用户的家目录下的 rpmbuild 目录自动创建这些目录:

rpmdev-setuptree

2.2 制作 RPM 包

2.2.1 整理源码

通常情况下源码的压缩格式为 .tar.gz ,并放到 _sourcedir 目录。

2.2.2 编写 SPEC 文件

在 _specdir 文件夹下创建 ***.spec 打包脚本,其实就是把我们的源码打包成 rpm 的过程。 spec 文件的命名格式一般是"软件名-版本.spec"。我们可以用 rpmdev-newspec -o Name-version.spec 命令生成 SPEC 文件的模版。

2.2.3 SPEC 文件的格式

其实 SPEC 文件的核心是它定义了一些阶段(如: %prep 、 %build 、 %install 和 %clean),当 rpmbuild 执行时它首先解析 SPEC 文件,然后依次执行每个阶段里的命令。

2.2.3.1 SPEC 文件头

SPEC 文件头部格式如下所示:

  • Name: myapp <===软件包的名字(后面会用到)
  • Version: 0.1.0 <===软件包的版本(后面会用到)
  • Release: 1%{?dist} <===发布序号
  • Summary: my first rpm <===软件包的摘要信息
  • Group: <===软件包的安装分类,参见/usr/share/doc/rpm-4.x.x/GROUPS这个文件
  • License: GPL <===软件的授权方式
  • URL: <===这里本来写源码包的下载路径或者自己的博客地址或者公司网址之类
  • Source0: %{name}-%{version}.tar.gz <===源代码包的名称(默认时rpmbuid会到SOURCES目录中去找),这里的name和version就是前两行定义的值。如果有其他配置或脚本则依次用Source1、Source2等等往后增加即可
  • BuildRoot: %{_topdir}/BUILDROOT <=== 这是make install时使用的“虚拟”根目录,最终制作rpm安装包的文件就来自这里
  • BuildRequires: <=== 在本机编译rpm包时需要的辅助工具,以逗号分隔。假如,要求编译myapp时,gcc的版本至少为4.4.2,则可以写成gcc >=4.2.2。还有其他依赖的话则以逗号分别继续写道后面
  • Requires: <=== 编译好的rpm软件在其他机器上安装时,需要依赖的其他软件包,也以逗号分隔,有版本需求的可以
  • %description <=== 软件包的详细说明信息,但最多只能有80个英文字符

2.2.3.2 %prep阶段

这个阶段主要完成对源代码包的解压和打补丁(如果有的话),而解压时最常见的指令是:

%setup -q

%setup -q 的作用是将 %_sourcedir 目录下的源代码解压到 %_builddir 目录下。这句指令可以成功执行的前提是位于 SOURCES 目录下的源码包必须是 name-version.tar.gz 的格式才行。它还会完成后续阶段目录的切换和设置,如果在这个阶段你不用这条指令,那么后面每个阶段都要自己手动去改变相应的目录。解压完成之后如果有补丁文件,也在这里做,用patch命令,如下所示,用 -p 参数指定 patch 时要跳过几个目录。

%patch -p1

2.2.3.3 %build 阶段

这个阶段的作用是在 %_builddir 目录下执行源码包的编译,一般是执行 ./configure 和 make 指令。如果有些软件需要最先执行 bootstrap 之类的,可以放在 configure 之前来做。这个阶段我们最常见只有两条指令:

%configure  
make %{?_smp_mflags}  

2.2.3.4 %install 阶段

这个阶段的作用是将需要打包到 rpm 软件包里的文件从 %_builddir 下拷贝 %_buildrootdir 目录下。当用户最终用 rpm -ivh name-version.rpm 安装软件包时,这些文件会安装到用户系统中相应的目录里。实际上就是执行 make install 命令。该阶段阶段会在 %_buildrootdir 目录里建好目录结构,然后将需要打包到 rpm 软件包里的文件从 %_builddir 里拷贝到 %_buildrootdir 里对应的目录里。这个阶段最常见的两条指令是:

rm -rf $RPM_BUILD_ROOT
make install DESTDIR=$RPM_BUILD_ROOT

其中 $RPM_BUILD_ROOT 也可以换成我们前面定义的 BuildRoot 变量,不过要写成 %{buildroot} 才可以,必须全部用小写,不然要报错。如果软件有配置文件或者额外的启动脚本之类,就要手动用 copy 命令或者 install 命令,将它也拷贝到 %{buildroot} 相应的目录里。用 copy 命令时如果目录不存在要手动建立,不然也会报错,所以推荐用 install 命令。

2.2.3.5 制作 rpm 包阶段

这个阶段是自动完成的,所以在 SPEC 文件里面是看不到的,这个阶段会将 %_buildroot 目录的相关文件制作成 rpm 软件包最终放到 %_rpmdir 目录里。这个阶段必须引出下面一个叫做 %files 的阶段,它主要用来说明会将 %{buildroot} 目录下的哪些文件和目录最终打包到 rpm 包里。

2.2.3.6 files阶段

%files  
%defattr(-,root,root)  
%attr  
%dir  
%config  
%doc  

该阶段列出了 RPM 包里面的包含那些文件。我们可以给打包到 RPM 里的文件设置权限和所有者,其中 %defattr 为文件和目录设置默认权限和所有者,参数顺序依次是:文件的默认权限或“模式”、默认的用户ID、默认的组ID、目录的默认权限或“模式”;而 %attr 对某个文件设置指定权限和所有者,例如下面两条语句:

%defattr(0644,root,root,0755)
%attr(4755,root,root) %{_sbindir}/unix_chkpwd

接着, %dir 用来指定 RPM 里包含的目录,紧接着是这个目录里包含的文件。好像不写 %dir 也没关系。 %doc 用来指定 RPM 里包含的文档文件,例如模块的 man 文档。 %config 用来标识配置文件, 这样在升级时用户对配置文件做过的修改就不会丢失。 没有它,用户千辛万苦修改过的配置文件会在升级过程中被覆盖。除了 %config 之外,还有一个 %config(noreplace) ,这两者的区别是 %config 使得旧文件被改名为 .rpmsave 并安装新文件,而 %config(noreplace) 将保持旧文件 并将新文件安装为 .rpmnew 。所有需要打包到 rpm 包的文件和目录都在这个地方列出,例如将 pam_usb.so 打包到 /usr/lib64/security 目录要添加下面一行,如果你编译出来的二进制文件没有再此列出时,系统会给你提示。

/usr/lib64/security/pam_usb.so  

在安装 rpm 包时,会将 pam_usb.so 安装到 /usr/lib64/security 目录。

这里在写要打包的文件列表时,既可以以宏常量开头,也可以为 “/” 开头,没任何本质的区别,都表示从 %{buildroot} 中拷贝文件到最终的 rpm 包里。如果是相对路径,则表示要拷贝的文件位于 %{_builddir} 目录,这主要适用于那些在 %install 阶段没有被拷贝到 %{buildroot} 目录里的文件,最常见的就是诸如 README 、 LICENSE 之类的文件。如果不想将 %{buildroot} 里的某些文件或目录打包到rpm里,则用:

%exclude dir_name 或者 file_name  

但是关于 %files 阶段有两个特性要注意,首先 %{buildroot} 里的所有文件都要明确被指定是否要被打包到 rpm 里。什么意思呢?假如, %{buildroot} 目录下有4个目录 a 、 b 、 c 和 d ,在 %files 里仅指定 a 和 b 要打包到 rpm 里,如果不把 c 和 d 用 exclude 声明是要报错的,如果声明了 %{buildroot} 里不存在的文件或者目录也会报错。其次,关于 %doc 宏,所有跟在这个宏后面的文件都来自 %{_builddir} 目录,当用户安装 rpm 时,由这个宏所指定的文件都会安装到 /usr/share/doc/name-version/ 目录里。

2.2.3.7 %clean 阶段

编译完成后一些清理工作,主要包括对 %{buildroot} 目录的清空(当然这不是必须的),通常执行诸如 make clean 之类的命令。

2.2.3.8 %changelog 阶段

主要记录的每次打包时的修改变更日志。标准格式是:

* date +"%a %b %d %Y" 修改人 邮箱 本次版本x.y.z-p  
- 本次变更修改了那些内容

2.2.3.9 rpm 安装卸载阶段

安装或者升级软件前要做的事情,比如停止服务、备份相关文件等在 %pre 阶段。

%pre

安装或者升级完成后要做的事情,比如执行ldconfig重构动态库缓存、启动服务等在 %post 阶段。

%post  

卸载软件前要做的事情,比如停止相关服务、关闭进程等在 %preun 阶段。

%preun

卸载软件之后要做的事情,比如删除备份、配置文件等在 %postun 阶段。

%postun  

2.2.3.10 宏

在 spec 文件中使用 %define 语句定义宏,如下所示:

%define  macro_name  value

然后可以用 %macro_name 或者 %{macro_name} 来调用,也可以扩展到 shell ,如下所示:

 %define today %(date)

也可以传递参数给宏,如下所示:

%define macro_name(option)  value

%foo 1 2 3 传递 1 2 3 三个参数给宏 foo ,在宏扩展时,宏参数由下面方式指定:

  • %0 :宏的名字,这里是 foo
  • %* :传递给宏的所有参数
  • %# :传递给宏的参数个数
  • %1 :第一个参数
  • %2 :第二个参数,等等

还可以判断宏是否存在,有两种用法,第一种用法如下所示,如果 macro_to_text 存在, 返回 expression ,如果不存在,则输出为空,也可以逆着用。

%{?macro_to_text:expression}
%{!?macro_to_text:expression}

另一种用法如下所示,忽略表达式只测试该 macro 是否存在,如果存在就用该宏的值,如果不存在,就不用。

%{?macro}

2.2.3.11 生成多个 rpm

有的时候经常需要一个 SPEC 文件里生成两个 rpm 包,比如,生成一个主包和一个供开发者使用的开发包。这时候我们需要为开发包编写包描述、所属组、依赖等,如下所示:

%package devel
Group: Development/Libraries
Summary: Development files for mysql
Requires: %{name} = %{version}-%{release}

%description devel
mysql database development package

%files devel
...

2.2.4 执行 rpmbuild 制作 rpm 包

准备好 spec 文件后,我们就可以执行 rpmbild 命令制作 RPM 包了,命令如下:

rpmbuild [option] [spec]

[option]:  
-ba 既生成src.rpm又生成二进制rpm  
-bs 只生成src的rpm  
-bb 只生二进制的rpm  
-bp 执行到prep  
-bc 执行到 build段  
-bi 执行install段  
-bl 检测有文件没包含  

二进制的 rpm 包就包含我们生成的可执行程序或者库。源码包包含 spec 文件,源码包和 patch 文件,安装源码包会将 spec 文件安装到 _specdir 目录,源码包和 patch 文件安装到 _sourcedir 目录。