说明
点击这里,查看制作 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
目录,然后执行:
xxxxxxxxxx
rpmbuild -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
%postun
if [ "$1" = "0" ]; then
/sbin/ldconfig
fi
下面是 RPM 包用到的一些文件和目录:
标准分组:/usr/share/doc/rpm-4.x.x/GROUPS
宏定义:/usr/lib/rpm/macros
已安装的 RPM 包数据库:/var/lib/rpm
避免生成 debuginfo 包:
xxxxxxxxxx
echo '%debug_package %{nil}' >> ~/.rpmmacros
在安装的时候,修改默认路径:
xxxxxxxxxx
rpm -ivh --prefix=/opt/usr XXX.rpm
或者同时修改多个路径:
xxxxxxxxxx
rpm XXX.rpm --relocate=/usr=/opt/usr --relocate=/etc=/usr/etc
diff
命令,生成补丁文件diff
命令用于比较文件的内容,找到改动的地方。diff
的输出称为补丁(patch),Linux 系统中的 patch
命令可以根据 diff
程序的输出,将源文件的内容更新为目标文件。diff
是 SVN、Git 等版本控制工具不可或缺的一部分。
diff
可以比较单个文件或目录。如果比较单个文件,那么只有当输入文件是文本文件时才有效,diff
以逐行的方式,比较文本文件的异同。如果比较目录,那么 diff
将比较两个目录下文件名相同的文本文件,并且列出不同的二进制程序、公共子目录和只在一个目录中出现的文件。
命令格式为:
xxxxxxxxxx
diff [参数] [文件 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:
xxxxxxxxxx
1
2
3
4
5
b/sub/1.txt:
xxxxxxxxxx
1
4.1
5
6
7
8
示例 1:
xxxxxxxxxx
$ diff a/sub/1.txt b/sub/1.txt
2,4c2
< 2
< 3
< 4
---
> 4.1
5a4,6
> 6
> 7
> 8
2,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.log
diff -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.log
patching file sub/1.txt
# 进入 b/ 目录
$ patch -R -p1 < ../patch.log
patching 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:/bin
echo -e "\033[32mHello World.\033[0m"
rpmbuild/SPECS/rpmtester.spec:
xxxxxxxxxx
Name: rpmtester
Version: 1.0
Release: 1%{?dist}
Summary: rpm tester
Group: Development/Tools
License: GPL
URL: http://timd.cn/rpm/
Source0: hello_world.sh
%description
rpm 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 包在:
xxxxxxxxxx
rpmbuild/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.rpm
Preparing... ################################# [100%]
package rpmtester-1.0-1.el7.x86_64 is already installed
运行安装的命令:
xxxxxxxxxx
$ /opt/mypackage/hello_world.sh
Hello World.