概述

本文在“RPM version 4.8.0”下,测试通过。点击这里查看用于生成Openresty的rpm包的spec文件
常见的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-get *.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},比如:

%_topdir    %{getenv:HOME}/myrpmbuildenv

%{_topdir}目录下,一般需要建立6个目录:

在上述目录建立好之后,将所有用于生成rpm包的源代码、shell脚本、配置文件拷贝到SOURCES目录下,通常情况下,源代码的压缩格式是*.tar.gz格式,SPEC文件的命名格式是“软件名-版本.spec”。将SPEC文件拷贝到SPECS目录下,cd到SPECS目录,执行:
rpmbuild -bb 软件名-版本.spec
最终生成的rpm包就被放在RPMS目录了。


SPEC文件的头部

可以将SPEC文件,分为:头部和“阶段”(有多个,比如:%prep、%build、%install、%clean、%files、%pre、%post、%preun、%postun等)。当rpmbuild执行时,它首先会去解析SPEC文件,然后依次执行每个阶段里的指令。
接下来,简单介绍一下SPEC文件的头部:

1,生成补丁:

diff命令:
	diff命令用于比较文件的内容,以找到改动的地方。diff程序的输出称为补丁(patch),因为Linux系统中有一个patch命令,可以根据diff程序的输出,将源文件的内容更新为目标文件。diff是svn、git等版本控制工具不可或缺的一部分。
    命令格式:diff [参数] [文件1或目录1] [文件2或目录2]
    命令概述:diff可以比较单个文件或目录。如果比较的是单个文件,那么只有当输入文件是文本文件时才有效,diff会以逐行的方式,比较文本文件的异同处。如果比较的是目录,那么diff会比较两个目录下文件名相同的文本文件,并列出不同的二进制程序、公共子目录和只在某一个目录中出现的文件。
    常用参数:
    	-r或--recursive 递归的比较子目录
        -N或--new-file 在比较目录时,如果文件A只出现在某个目录中,那么默认会显示:Only in 目录: 文件A。如果使用-N参数,那么diff会将文件A与一个空白文件比较
        -u或--unified 以合并的方式,来显示文件的不同
        -c 显示文件的全部内容,并标出不同之处
	例子:
    	1,diff a/sub/1.txt b/sub/1.txt 
		2d1
		< 2
		4a4
		> 4
        说明:2d1表示,第一个文件比第二个文件多了第2行;4a4表示,第二个文件比第一个文件多了第4行。
        diff的normal显示格式有三种提示:
        a - add
        c - change
        d - delete
        
        2,diff a/sub/1.txt b/sub/1.txt -c
        *** a/sub/1.txt	2016-08-15 13:49:49.283584763 +0800
        --- b/sub/1.txt	2016-08-15 13:54:10.934518079 +0800
        ***************
        *** 1,5 ****
          1
        - 2
          3
          4
          5
        --- 1,5 ----
          1
          3
          4
        + 4
          5
        说明:
        “+”表示后者比前者多了该行
        “-”表示前者比后者多了该行
        “!”表示两者在该行有差别
        
        3,diff a/sub/1.txt b/sub/1.txt -u
        --- a/sub/1.txt	2016-08-15 13:49:49.283584763 +0800
        +++ b/sub/1.txt	2016-08-15 13:54:10.934518079 +0800
        @@ -1,5 +1,5 @@
         1
        -2
         3
         4
        +4
         5
        说明:
        “---”表示变动前的文件
        “+++”表示变动后的文件
        变动的位置用两个“@”作为起始和结束标记。“-1,5”中的“-”表示第一个文件,“1”表示第1行,“5”表示连续的5行,合在一起表示下面是第一个文件的第1行开始的连续5行。同理“+1,5”表示第二个文件的第1行开始的连续5行。
        
        4,diff -ruN a b | tee patch.log
        diff -ruN a/a.txt b/a.txt
        --- a/a.txt	2016-08-15 13:36:27.999509807 +0800
        +++ b/a.txt	1970-01-01 08:00:00.000000000 +0800
        @@ -1,5 +0,0 @@
        -this
        -is
        -a
        -.
        -txt
        diff -ruN a/sub/1.txt b/sub/1.txt
        --- a/sub/1.txt	2016-08-15 14:13:52.956834226 +0800
        +++ b/sub/1.txt	2016-08-15 13:54:10.934518079 +0800
        @@ -1,5 +1,5 @@
         1
        -2
         3
         4
        +4
         5
        说明:比较两个目录的不同,并生成补丁。  	

2,打补丁:

patch命令:
	patch就是利用diff制作的补丁来实现源文件(目录)和目标文件(目录)的相互转换。常用的选项有:
    -p 表示忽略掉NUM层目录
    比如:
    --- a/a.txt 2016-08-15 13:36:27.999509807 +0800
    +++ b/a.txt 1970-01-01 08:00:00.000000000 +0800
    参数-p0表示,在当前目录下寻找目录a,然后在目录a下面寻找文件a.txt来执行patch操作。
    参数-p1表示,忽略第一层目录(即不管a),在当前目录下寻找文件a.txt。
    
    -E 表示在打完补丁之后,如果发现了空文件,那么就删除它。
    -R 表示给新版本打上补丁,使其变成老版本。
    -b 表示备份每个文件的原始内容。
    
    下面是一个例子:
    [root@iZ23dastruaZ test]# tree .
    .
    ├── a
    │   ├── a.txt
    │   └── sub
    │       └── 1.txt
    └── b
        └── sub
            └── 1.txt

    4 directories, 3 files
    
    [root@iZ23dastruaZ test]# diff -urN a b >patch.log
    [root@iZ23dastruaZ test]# cp -R a a_bak
	[root@iZ23dastruaZ test]# cp -R b b_bak
    [root@iZ23dastruaZ test]# cd a
    [root@iZ23dastruaZ a]# patch -p1 < ../patch.log 
    patching file a.txt
    patching file sub/1.txt
    [root@iZ23dastruaZ a]# cd ../b
    [root@iZ23dastruaZ b]# patch -p1 -R < ../patch.log 
    patching file a.txt
    patching file sub/1.txt

	最终,a变成了b,b变成了a。

SPEC文件中的“阶段”

下面是可选的阶段:


其它

1,如果正在制作的rpm包是准备放到系统安装光盘中的,则需要考虑rpm中定义的脚本是否有问题。由于系统在安装的时候只是依赖于一个小环境,而该环境与实际安装完的环境有很大的区别,所以,大部分的脚本在该安装环境中都是无法生效,甚至会带来麻烦的。
所以,对于这样的,需要放到安装光盘中的套件,不加入执行脚本是较佳的方法。
另外,rpm提供了一种信号机制:不同的操作会返回不同的信息,并保存到$1中(0代表卸载、1代表安装、2代表升级)。
可以这样使用:

%postun
if [ "$1" = "0" ]; then
    /sbin/ldconfig
fi

2, rpm软件包 系统的标准分组:/usr/share/doc/rpm-4.x.x/GROUPS
各种宏定义:/usr/lib/rpm/macros
已经安装的rpm包数据库:/var/lib/rpm
如果要避免生成debuginfo包:
echo '%debug_package %{nil}' >> ~/.rpmmacros
如果rpm包已经做好,但在安装的时候想修改默认路径,则可以:
rpm -ivh --prefix=/opt/usr xxx.rpm
又或者同时修改多个路径:
rpm xxx.rpm --relocate=/usr=/opt/usr --relocate=/etc=/usr/etc