说明
点击这里,查看制作 Openresty RPM 包的 SPEC 文件
本文运行环境:
- 操作系统:CentOS Linux release 7.8.2003 (Core)
- rpm/rpmbuild:RPM version 4.11.3
常见的 Linux 发行版主要分为两类:类 RedHat 系列和类 Debian 系列。在类 RedHat 系统中,软件包的格式是 rpm;在类 Debian 系统中,软件包的格式是 deb。类 RedHat 系统提供 rpm(RedHat Package Manager)命令,用于安装、卸载和升级 RPM 软件包;类 Debian 系统提供 dpkg 命令,用于安装、卸载和升级 DEB 软件包。
CentOS 提供 yum 工具,自动解决软件包的安装依赖,yum 本质上还是调用 rpm。不同发行版上的包管理工具,如下所示:
| 分类 | 发行版 | 手动安装命令 | 自动安装命令 | 软件包后缀 |
|
类 RedHat |
Fedora/CentOS |
rpm |
yum |
*.rpm |
| openSUSE/SUSE | zypper | |||
| Mandriva Linux/Mageia | urpmi | |||
| 类 Debian | Debian/Ubuntu | dpkg | apt | *.deb |
从运行结构来看,软件主要分为三部分:可执行程序、配置文件和动态链接库。还可能包含可选的头文件、手册、示例程序等。
本文主要讲述如何使用 rpmbuild 制作 RPM 包,rpmbuild 的输入对象是 SPEC 文件,下面开始介绍 SPEC 文件。
对于版本小于等于 4.4.x 的 rpm,rpmbuild 的默认工作目录是 /usr/src/redhat,这使得普通用户无法制作 RPM 包。所以从版本 4.5.x 开始,rpmbuid 的默认工作路径被移动到 $HOME/rpmbuild,并且建议用户在制作 RPM 包时,尽量不要使用 root 身份进行操作。rpmbuild 的默认工作路径通常由 /usr/lib/rpm/macros 文件里的 %{_topdir} 宏定义。如果用户想要更改 rpmbuild 的工作路径,那么可以在用户的家目录下创建一个名为 .rpmmacros 的隐藏文件,然后在其中重新定义 %{_topdir}。比如:
xxxxxxxxxx%_topdir %{getenv:HOME}/myrpmbuildenv
在 %{_topdir} 目录下,一般需要建立 6 个目录:
BUILD:
BUILDROOT:
make install 时,软件被安装到“虚拟根目录”。所有需要打包进 RPM 包的文件或目录,也要拷贝到“虚拟根目录”。简而言之,打包时,以“虚拟目录”为“根目录”进行操作。最终的 RPM 包中的文件都来自于“虚拟根目录”。(在 %files 阶段,书写将要打包的文件列表时,必须以宏或 “/” 开头,其中 “/” 代表“虚拟根目录”)RPMS:
SOURCES:
SPECS:
SRPMS:
在建立上述目录之后,将用于生成 RPM 包的所有源代码、Shell 脚本、配置文件拷贝到 SOURCES目录。通常情况下,源代码的格式是 *.tar.gz,SPEC 文件的命名格式是“软件名-版本.spec”。将 SPEC 文件拷贝到 SPECS 目录,cd 到 SPECS 目录,然后执行:
xxxxxxxxxxrpmbuild -bb 软件名-版本.spec最终生成的 RPM 包在 RPMS 目录。
SPEC 文件分为头部和阶段。阶段有多个,比如 %prep、%build、%install、%clean、%files、%pre、%post、%preun、%postun 等。执行 rpmbuild 时,它首先解析 SPEC 文件,然后依次执行每个阶段里的指令。
接下来,简单地介绍 SPEC 文件的头部:
/usr/share/doc/rpm-4.x.x/GROUPS 文件,建议使用标准分组。创建者在创建包时,需要对包进行分类,以便将功能相似的包放在一起。rpm 命令可以通过分组查询包。比如有一个名为 System Environment/Base 的分组,该分组中的包提供比较底层的功能,可以通过 rpm 命令查看该分组由哪些包组成:rpm -qg "System Environment/Base" rpmbuild 命令将到 %{_sourcedir} 目录中寻找),如前所述,源代码的压缩格式是 *.tar.gz,比如 %{name}-%{version}.tar.gz,其中 name 和 version 是前面两行定义的值。如果还有其它配置或脚本,那么依次使用 Source1、Source2 等向后追加即可,后面可以使用 %{SOURCE0}、%{SOURCE1} 等引用gcc >= 4.2.2, make/usr/lib/rpm/marcros 文件中的 %{_build_arch},比如 BuildArch: noarch%prep 阶段
动作:将 %_sourcedir 目录中的源代码包解压到 %_builddir 目录,如果需要给源代码打补丁,那么也在该阶段进行
解压缩时,最常用的指令是 %setup,并且只有 *.tar.gz 格式的源代码包才被解压缩。%setup 还将完成后续阶段的目录切换和设置,如果在该阶段不用这条指令,那么后续阶段需要手动更改相应的目录。%setup 常用的选项如下:
使用 %patch 打补丁。比如 %patch0 -p1 表示使用前面定义的 Patch0 打补丁,并且忽略第 1 层目录
%build 阶段
./configure 和 make 指令。如果软件需要先执行 bootstrap 之类的操作,那么可以放在 configure 之前--prefix 设置为 /usr。此外该宏还可以接受额外参数。如果不用 %configure,那么需要完全手动地指定 configure 时的配置参数。同样也可以给 make 传递额外的参数,比如 make %{?_smp_mflags} OPTIMIZE="%{optflags}",其中 %{optflags} 是 /usr/lib/rpm/marcros 中定义的优化参数%install 阶段
动作:
make install,将软件安装到“虚拟根目录”该阶段最常用的指令是 rm -rf $RPM_BUILD_ROOT、make install DESTDIR=$RPM_BUILD_ROOT
如果软件拥有额外的配置文件或启动脚本,那么需要手动用 cp 或 install 命令将它们拷贝到“虚拟根目录”。比如 install -d $RPM_BUILD_ROOT/、cp -a * $RPM_BUILD_ROOT/。(这里的相对路径是相对于“编译软件的临时目录”的;同时也可以使用 %{SOURCE1} 等从源代码目录下拷贝文件)
%clean 阶段
make clean 清除“虚拟根目录”等%files 阶段
/usr/share/doc/<name>-<version>/ 目录%changelog
动作:记录每次打包时的变更日志。标准格式是
xxxxxxxxxx* date +"%a %b %d %Y" 修改人 邮箱 本次版本- 内容
下面是可选的阶段:
ldconfig 重构动态链接库缓存,启动服务等如果准备将正在制作的 RPM 包放到系统安装光盘中,那么需要考虑 RPM 包中定义的脚本是否有问题。由于系统安装时只依赖于一个“小”环境,该环境与实际安装完成的环境有很大区别,所以大部分脚本在该安装环境中无法生效,甚至带来麻烦。
因此对于需要放到安装光盘中的套件,较好的方法是不加入执行脚本。
此外,RPM 还提供一种信号机制 - 不同操作返回不同信息,并且保存到 $1 中(0 代表卸载;1 代表安装;2 代表升级):
xxxxxxxxxx%postunif [ "$1" = "0" ]; then/sbin/ldconfigfi
下面是 RPM 包用到的一些文件和目录:
标准分组:/usr/share/doc/rpm-4.x.x/GROUPS
宏定义:/usr/lib/rpm/macros
已安装的 RPM 包数据库:/var/lib/rpm
避免生成 debuginfo 包:
xxxxxxxxxxecho '%debug_package %{nil}' >> ~/.rpmmacros 在安装的时候,修改默认路径:
xxxxxxxxxxrpm -ivh --prefix=/opt/usr XXX.rpm或者同时修改多个路径:
xxxxxxxxxxrpm XXX.rpm --relocate=/usr=/opt/usr --relocate=/etc=/usr/etcdiff 命令,生成补丁文件diff 命令用于比较文件的内容,找到改动的地方。diff 的输出称为补丁(patch),Linux 系统中的 patch 命令可以根据 diff 程序的输出,将源文件的内容更新为目标文件。diff 是 SVN、Git 等版本控制工具不可或缺的一部分。
diff 可以比较单个文件或目录。如果比较单个文件,那么只有当输入文件是文本文件时才有效,diff 以逐行的方式,比较文本文件的异同。如果比较目录,那么 diff 将比较两个目录下文件名相同的文本文件,并且列出不同的二进制程序、公共子目录和只在一个目录中出现的文件。
命令格式为:
xxxxxxxxxxdiff [参数] [文件 1 或目录 1 ] [文件 2 或目录 2]常用参数包括:
-r 或 --recursive:递归地比较子目录-N 或 --new-file:比较目录时,如果文件 A 只出现在一个目录中,那么默认显示:Only in 目录: 文件 A。如果使用 -N 参数,那么 diff 将文件 A 与空白文件进行比较-u 或 --unified:以合并的方式,显示文件的不同-c 显示文件的全部内容,并且标出不同之处下面通过几个示例进行说明,示例文件如下:
xxxxxxxxxx├── a│ └── sub│ └── 1.txt└── b└── sub└── 1.txt
a/sub/1.txt:
xxxxxxxxxx12345
b/sub/1.txt:
xxxxxxxxxx14.15678
示例 1:
xxxxxxxxxx$ diff a/sub/1.txt b/sub/1.txt2,4c2< 2< 3< 4---> 4.15a4,6> 6> 7> 82,4c2 的含义如下:
前面的数字 2,4 表示第一个文件中的行,中间的字母 c 表示需要在第一个文件上执行的操作(a=add、c=change、d=delete),后面的数字 2 表示第二个文件中的行。也就是说,第一个文件中的第 [2, 4] 行需要做出修改才能与第二个文件中的第 2 行匹配。
接下来的内容是需要修改的地方,前面带 < 的部分表示左边文件的第 [2, 4] 行的内容,带 > 的部分表示右边文件第 2 行的内容,中间的 - 则是两个文件内容的分隔符。
示例 2:
xxxxxxxxxx$ diff -c a/sub/1.txt b/sub/1.txt*** a/sub/1.txt ...--- b/sub/1.txt ...****************** 1,5 **** 1! 2! 3! 4 5--- 1,6 ---- 1! 4.1 5+ 6+ 7+ 8说明:
示例 3:
xxxxxxxxxx$ diff -u a/sub/1.txt b/sub/1.txt--- a/sub/1.txt ...+++ b/sub/1.txt ...@@ -1,5 +1,6 @@ 1-2-3-4+4.1 5+6+7+8说明:
示例 4:
xxxxxxxxxx# diff -uNr a b | tee patch.logdiff -uNr a/sub/1.txt b/sub/1.txt--- a/sub/1.txt ...+++ b/sub/1.txt ...@@ -1,5 +1,6 @@ 1-2-3-4+4.1 5+6+7+8说明:
patch 命令,打补丁patch 用 diff 制作的补丁,实现源文件(目录)和目标文件(目录)的相互转换。常用的选项有:
-p <NUM>:表示忽略 NUM 层目录
比如:
xxxxxxxxxx--- a/a.txt ...+++ b/a.txt ...
参数 -p0 表示在当前目录下寻找目录 a,然后在目录 a 下面寻找文件 a.txt,执行 patch 操作。
参数 -p1 表示忽略第 1 层目录,即忽略 a,在当前目录下寻找文件 a.txt
-E:在打完补丁后,如果发现空文件,那么就删除它
-R:给新版本打上补丁,使其变为老版本
-b:备份每个文件的原始内容
示例 1:
使用上一小节的示例 4 生成的 patch.log。
x
# 进入 a/ 目录$ patch -p1 < ../patch.logpatching file sub/1.txt
# 进入 b/ 目录$ patch -R -p1 < ../patch.logpatching file sub/1.txt说明:
a 变成 b;b 变成 a示例文件:
xrpmbuild/├── BUILD├── BUILDROOT├── RPMS├── SOURCES│ └── hello_world.sh├── SPECS│ └── rpmtester.spec└── SRPMS
rpmbuild/SOURCES/hello_world.sh:
xxxxxxxxxx
export PATH=$PATH:/binecho -e "\033[32mHello World.\033[0m"rpmbuild/SPECS/rpmtester.spec:
xxxxxxxxxxName: rpmtesterVersion: 1.0Release: 1%{?dist}Summary: rpm testerGroup: Development/ToolsLicense: GPLURL: http://timd.cn/rpm/Source0: hello_world.sh%descriptionrpm tester%install%{__install} -d %{buildroot}/opt/mypackage%{__install} -m 0755 %SOURCE0 %{buildroot}/opt/mypackage/hello_world.sh%files%defattr(-, root, root, -)/opt/mypackage/hello_world.sh%changelog* Wed Aug 24 2016 Tim 744475502@qq.com 1.0-1- initialization
打包:
xxxxxxxxxx$ rpmbuild -bb rpmbuild/SPECS/rpmtester.spec生成的 RPM 包在:
xxxxxxxxxxrpmbuild/RPMS/x86_64/rpmtester-1.0-1.el7.x86_64.rpm
安装 RPM 包:
xxxxxxxxxx$ sudo rpm -ivh rpmbuild/RPMS/x86_64/rpmtester-1.0-1.el7.x86_64.rpmPreparing... ################################# [100%] package rpmtester-1.0-1.el7.x86_64 is already installed运行安装的命令:
xxxxxxxxxx$ /opt/mypackage/hello_world.shHello World.