RPM(Redhat Package Manager)是用于Redhat、CentOS、Fedora等Linux 分发版(distribution)的常见的软件包管理器。因为它允许分发已编译的软件,所以用户只用一个命令就可以安装软件。
1 准备
安装打包工具rpmdevtools
$ yum install rpmdevtools
2 原理
RPM打包的时候需要编译源码,还需要把编译好的配置文件、二进制命令文件之类的东西按照安装好的样子放到合适的位置,还要根据需要对RPM的包进行测试,这些都需要先有一个“工作空间”。rpmbuild命令使用一套标准化的“工作空间”:
$ rpmdev-setuptree
rpmdev-setuptree
这个命令就是安装rpmdevtools
带来的。可以看到运行了这个命令之后,在$HOME
家目录下多了一个叫做rpmbuild
的文件夹,里边内容如下:
$ tree rpmbuild
rpmbuild
├── BUILD
├── RPMS
├── SOURCES
├── SPECS
└── SRPMS
目录说明
默认位置 | 宏代码 | 名称 | 用途 |
---|---|---|---|
~/rpmbuild/SPECS | %_specdir | Spec 文件目录 | 保存 RPM 包配置(.spec)文件 RPM包能否打包成功就在于此
|
~/rpmbuild/SOURCES | %_sourcedir | 源代码目录 | 保存源码包(如 .tar.gz 包)和所有 patch 补丁 |
~/rpmbuild/BUILD | %_builddir | 构建目录 | 源码包被解压至此,并在该目录的子目录完成编译 |
~/rpmbuild/BUILDROOT | %_buildrootdir | 最终安装目录 | 保存 %install 阶段安装的文件 |
~/rpmbuild/SRPMS | %_srcrpmdir | 源代码 RPM 包目录 | 生成/保存源码 RPM 包(SRPM) |
SPECS
下是RPM包的配置文件,是RPM打包的“图纸”,这个文件会告诉rpmbuild
命令如何去打包。“宏代码”这一列就可以在SPEC
文件中用来代指所对应的目录,类似于编程语言中的宏或全局变量。当然~/rpmbuild
这个文件夹也是有宏代码的,叫做%_topdir
。
打包的过程有点像是流水线,分好几个工序:
- 首先,需要把源代码放到
%_sourcedir
中,通常能是.tar.gz
的压缩包; - 然后,进行编译,编译的过程是在
%_builddir
中完成的,所以需要先把源代码复制到这个目录下边,一般情况下是源代码包解压后的; - 第三步,进行“安装”,这里有点类似于预先组装软件包,把软件包应该包含的内容(比如二进制文件、配置文件、man文档等)复制到
%_buildrootdir
中,并按照实际安装后的目录结构组装,比如二进制命令可能会放在/usr/bin
下,那么就在%_buildrootdir
下也按照同样的目录结构放置; - 然后,需要配置一些必要的工作,比如在实际安装前的准备啦,安装后的清理啦,以及在卸载前后要做的工作等等,这样也都是通过配置在
SPEC
文件中来告诉rpmbuild
命令; - 还有一步可选操作,那就是检查软件是否正常运行;
- 最后,生成的RPM包放置到
%_rpmdir
,源码包放置到%_srpmdir
下。
以上这些步骤都是配置在SPEC文件中的,具体来说各个阶段:
以上这些步骤都是配置在SPEC文件中的,具体来说各个阶段:
阶段 | 读取的目录 | 写入的目录 | 具体动作 |
---|---|---|---|
%prep | %_sourcedir | %_builddir | 读取位于 %_sourcedir 目录的源代码和 patch 。之后,解压源代码至 %_builddir 的子目录并应用所有 patch 。 |
%build | %_builddir | %_builddir | 编译位于 %_builddir 构建目录下的文件。通过执行类似 ./configure && make 的命令实现。 |
%install | %_builddir | %_buildrootdir | 读取位于 %_builddir 构建目录下的文件并将其安装至 %_buildrootdir 目录。这些文件就是用户安装 RPM 后,最终得到的文件。注意一个奇怪的地方: 最终安装目录 不是 %_builddir 而是%_buildrootdir 。通过执行类似 make install 的命令实现。 |
%check | %_builddir | %_builddir | 检查软件是否正常运行。通过执行类似 make test 的命令实现。很多软件包都不需要此步。 |
bin | %_buildrootdir | %_rpmdir | 读取位于 %_buildrootdir 最终安装目录下的文件,以便最终在 %_rpmdir 目录下创建 RPM 包。在该目录下,不同架构的 RPM 包会分别保存至不同子目录, noarch 目录保存适用于所有架构的 RPM 包。这些 RPM 文件就是用户最终安装的 RPM 包。 |
src | %_sourcedir | %_srcrpmdir | 创建源码 RPM 包(简称 SRPM,以.src.rpm 作为后缀名),并保存至 %_srcrpmdir 目录。SRPM 包通常用于审核和升级软件包。 |
3 示例
解释再多不如一个例子来的明白,这里用官方文档中的例子来操作一遍。
下面演示 GNU“Hello World” 项目的打包过程。虽然用 C 语言程序打印 “Hello World” 到标准输出是小菜一碟,但 GNU 版本包含了与一个典型的 FOSS 软件项目相关的最常用的外围组件,包括配置/编译/安装环境、文档、国际化等等。GNU 版本包含了一个由源代码和 configure/make 脚本组成的 tar 文件,但并不包含打包信息。因此,这是一个很好的 RPM 包打包示例。
3.1 下载源码
还记得前面介绍到的几个阶段吗,先准备源码,这里我们直接下载官方例子的源码,是个压缩包:
$ cd ~/rpmbuild/SOURCES
$ wget http://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz
3.2 编辑SPEC文件
然后后续的步骤就交给SPEC文件来配置了,编辑SPEC文件(Emacs 和 vi 的最新版本有 .spec 文件编辑模式,它会在创建新文件时打开一个类似的模板。所以可使用以下命令来自动使用模板文件):
$ cd ~/rpmbuild/SPECS
$ vim hello.spec
既然有模板,那么后边的工作就是填空题了:
Name: hello
Version: 2.1
Release: 1%{?dist}
Summary: The "Hello World" program from GNU
Summary(zh_CN): GNU "Hello World" 程序
License: GPLv3+
URL: http://ftp.gnu.org/gnu/hello
Source0: http://ftp.gnu.org/gnu/hello/%{name}-%{version}.tar.gz
%description
The "Hello World" program, done with all bells and whistles of a proper FOSS
project, including configuration, build, internationalization, help files, etc.
%description -l zh_CN
"Hello World" 程序, 包含 FOSS 项目所需的所有部分, 包括配置, 构建, 国际化, 帮助文件等.
%prep
%setup -q
%build
%configure
make %{?_smp_mflags}
%install
make install DESTDIR=%{buildroot}
%files
%doc
%changelog
* Sun Dec 4 2016 Your Name <youremail@xxx.xxx> - 2.10-1
- Update to 2.10
* Sat Dec 3 2016 Your Name <youremail@xxx.xxx> - 2.9-1
- Update to 2.9
Name
标签就是软件名,Version
标签为版本号,而 Release
是发布编号。
Summary
标签是简要说明,英文的话第一个字母应大写,以避免 rpmlint
工具(打包检查工具)警告。
License
标签说明软件包的协议版本,审查软件的 License 状态是打包者的职责,这可以通过检查源码或 LICENSE 文件,或与作者沟通来完成。
Group
标签过去用于按照 /usr/share/doc/rpm-/GROUPS
分类软件包。目前该标记已丢弃,vim的模板还有这一条,删掉即可,不过添加该标记也不会有任何影响。
%changelog
标签应包含每个 Release
所做的更改日志,尤其应包含上游的安全/漏洞补丁的说明。Changelog 日志可使用 rpm --changelog -q <packagename>
查询,通过查询可得知已安装的软件是否包含指定漏洞和安全补丁。%changelog
条目应包含版本字符串,以避免 rpmlint
工具警告。
多行的部分,如 %changelog
或 %description
由指令下一行开始,空行结束。一些不需要的行 (如 BuildRequires
和 Requires
) 可使用 ‘#’ 注释。
%prep
、%build
、%install
、%file
暂时用默认的,未做任何修改。
3.3 构建RPM包
有点迫不及待了,尝试执行以下命令,以构建源码、二进制和包含调试信息的软件包:
$ rpmbuild -ba hello.spec
1)包含要安装的文件
不过上边的命令执行失败了0_0。
命令执行后,提示并列出未打包的文件:
RPM build errors:
Installed (but unpackaged) file(s) found:
/usr/bin/hello
/usr/share/info/dir
/usr/share/info/hello.info.gz
/usr/share/locale/bg/LC_MESSAGES/hello.mo
/usr/share/locale/ca/LC_MESSAGES/hello.mo
...
那些需要安装在系统中的文件,我们需要在 %files
中声明它们,这样rpmbuild
命令才知道哪些文件是要安装的。
注意不要使用形如 /usr/bin/
的硬编码, 应使用类似 %{_bindir}/hello
这样的宏来替代。手册页应在 %doc
中声明 : %doc %{_mandir}/man1/hello.1.*
。
由于示例的程序使用了翻译和国际化,因此会看到很多未声明的 i18 文件。 使用 推荐方法 来声明它们:
包含程序安装的相关文件
查找 %install
中的语言文件: %find_lang %{name}
添加编译依赖: BuildRequires: gettext
声明找到的文件: %files -f %{name}.lang
这样下来,%files
部分的内容为:
%files -f %{name}.lang
%doc AUTHORS ChangeLog NEWS README THANKS TODO
%license COPYING
%{_mandir}/man1/hello.1.*
%{_infodir}/hello.info.*
%{_bindir}/hello
2)info文件的处理
如果程序使用 GNU info 文件,你需要确保安装和卸载软件包,不影响系统中的其他软件,按以下步骤操作:
在 %install
中添加删除 ‘dir’ 文件的命令: rm -f %{buildroot}/%{_infodir}/dir
在安装后和卸载前添加依赖 Requires(post): info
和 Requires(preun): info
添加以下安装脚本(在%install
和%files
中间即可,分别对应安装后和卸载前的阶段,详见后边内容):
%post
/sbin/install-info %{_infodir}/%{name}.info %{_infodir}/dir || :
%preun
if [ $1 = 0 ] ; then
/sbin/install-info --delete %{_infodir}/%{name}.info %{_infodir}/dir || :
3)看看各个目录里边的东西
-
%_sourcedir
下边仍然是源码的压缩包; -
%_builddir
下边是源码解压出来的文件夹hello-2.10及其下边的所有文件; -
%_buildrootdir
下边是一个名为“hello-2.10-1.el7.centos.x86_64”的文件夹(那么生成的RPM包的完整名称也是{Name}-{Version}-{Release}.{Arch}.rpm),这个文件夹下边有“usr”文件夹,其下还有“bin”、“lib”、“share”、“src”这几个文件夹,可以看到这里的目录结构和安装之后各个文件和文件夹的位置已经是基本一致的了。这里要注意的是,“usr”所在的“根目录”,也就是“hello-2.10-1.el7.centos.x86_64”这个文件夹,用宏表示就是%{buildroot}
,有的地方也用$RPM_BUILD_ROOT
代替%{buildroot}
,不过跟%{_buildrootdir}
不是一个概念,请注意。
为什么是“趁着失败”呢,因为成功打包之后有些文件夹(比如%_builddir
和%_buildrootdir
)内的内容就会被清理掉了,不过也可以在%build
和%install
阶段的时候把这俩文件夹内的东西tree
一下或者干脆复制到其他地方再看也行。
那么%build
和%install
以及其他几个阶段一般怎么配置呢?
4)本示例最终的完整SPEC
Name: hello
Version: 2.10
Release: 1%{?dist}
Summary: The "Hello World" program from GNU
Summary(zh_CN): GNU "Hello World" 程序
License: GPLv3+
URL: http://ftp.gnu.org/gnu/hello
Source0: http://ftp.gnu.org/gnu/hello/%{name}-%{version}.tar.gz
BuildRequires: gettext
Requires(post): info
Requires(preun): info
%description
The "Hello World" program, done with all bells and whistles of a proper FOSS
project, including configuration, build, internationalization, help files, etc.
%description -l zh_CN
"Hello World" 程序, 包含 FOSS 项目所需的所有部分, 包括配置, 构建, 国际化, 帮助文件等.
%prep
%setup -q
%build
%configure
make %{?_smp_mflags}
%install
make install DESTDIR=%{buildroot}
%find_lang %{name}
rm -f %{buildroot}/%{_infodir}/dir
%post
/sbin/install-info %{_infodir}/%{name}.info %{_infodir}/dir || :
%preun
if [ $1 = 0 ] ; then
/sbin/install-info --delete %{_infodir}/%{name}.info %{_infodir}/dir || :
fi
%files -f %{name}.lang
%doc AUTHORS ChangeLog NEWS README THANKS TODO
%license COPYING
%{_mandir}/man1/hello.1.*
%{_infodir}/hello.info.*
%{_bindir}/hello
%changelog
* Sun Dec 4 2016 Your Name <youremail@xxx.xxx> - 2.10-1
- Update to 2.10
* Sat Dec 3 2016 Your Name <youremail@xxx.xxx> - 2.9-1
- Update to 2.9
那么就开动起来,在执行一下rpmbuild命令瞅瞅吧:
$ rpmbuild -ba hello.spec
OK,执行成功了,看看成果吧:
$ tree ~/rpmbuild/*RPMS
/root/rpmbuild/RPMS
└── x86_64
├── hello-2.10-1.el7.centos.x86_64.rpm
└── hello-debuginfo-2.10-1.el7.centos.x86_64.rpm
/root/rpmbuild/SRPMS
└── hello-2.10-1.el7.centos.src.rpm
在RPMS文件夹下生成了RPM包,在x86_64下,表示所应用的架构,由于没有指定arch为noarch,所以默认用本机架构。在SRPMS文件夹下生产了源码包,源码包当然木有架构这一说了。所以有些人喜欢在装软件的时候从源码开始安装,因为更能贴合本机的物理情况,就像用光盘安装windows和GHOST安装windows,相对来说光盘一步一步安装更好一点点,不过我比较懒,还是直接yum install。
5)运行一下下
既然已经有RPM包了,那就安装上吧:
$ rpm -ivh ~/rpmbuild/RPMS/x86_64/hello-2.10-1.el7.centos.x86_64.rpm
运行一下:
$ hello
Hello, world!
$ which hello
/usr/bin/hello
$ rpm -qf `which hello`
hello-2.10-1.el7.centos.x86_64
可以看到编译好的二进制文件hello已经装到/usr/bin
下了,其他位置的文件请自行查看吧_。因为这个示例程序五脏俱全,不妨man
一下,看看使用文档~
$ man hello