制作RPM包

概述

本文在“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个目录:

  • BUILD:
    • 编译源代码的临时目录所在的目录
    • 可以通过$RPM_BUILD_DIR引用该目录
    • 在macros文件中的宏名:%{_builddir}
  • BUILDROOT:
    • “虚拟根目录”所在的目录。该参数非常重要,执行make install时,就要把软件安装到“虚拟根目录”;所有需要打包进rpm包的文件或目录,也要拷贝到“虚拟根目录”。简而言之,在打包的时候,以“虚拟目录”为“根目录”进行操作。因此,最终的rpm包中的文件就来自“虚拟根目录”。(在%files阶段,写要打包的文件列表时,必须以宏或/开头,而/代表的就是“虚拟根目录”)
    • 可以通过$RPM_BUILD_ROOT引用“虚拟根目录”
    • 也可以通过%{buildroot}引用“虚拟根目录”
    • 在macros文件中的宏名:%{_buildrootdir}
  • RPMS:
    • 最终生成的rpm包所在的目录
    • 在macros文件中的宏名:%{_rpmdir}
  • SOURCES:
    • 源代码和补丁文件所在的目录
    • 在macros文件中的宏名:%{_sourcedir}
  • SPECS:
    • SPEC文件所在的目录
    • 在macros文件中的宏名:%{_specdir}
  • SRPMS:
    • 软件最终的rpm源码格式存放路径
    • 在macros文件中的宏名:%{_srcrpmdir}

在上述目录建立好之后,将所有用于生成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文件的头部:

  • Name - 软件包的名称,后面可以使用%{name}引用,比如protobuf。
  • Version - 软件包的版本,后面可以使用%{version}引用,比如3.0.0。
  • Release - 发布序号,标明第几次打包,后面可以使用%{release}引用,比如1%{?dist}。
  • Summary - 软件包的内容摘要。
  • Group - 软件包的安装分类,参见/usr/share/doc/rpm-4.x.x/GROUPS文件,建议使用标准分组。
    创建者在创建包的时候,需要对包进行分类,以把功能相似的包放到一起。rpm命令能通过分组来查询包。比如,有一个分组叫System Environment/Base,这个分组的包都提供了比较底层的功能,可以通过rpm命令来查看,这个分组由哪些包组成:rpm -qg "System Environment/Base"。
  • License - 软件授权方式,通常是GPL。
  • URL - 软件的主页。
  • Source0 - 源代码包的名称(rpmbuild命令会到%{_sourcedir}目录中去寻找),如前面所说,源代码的压缩格式是*.tar.gz格式,比如%{name}-%{version}.tar.gz,这里的name和version就是前面两行定义的值。如果还有其他的配置或脚本,则依次使用Source1、Source2等向后追加即可,后面可以使用%{SOURCE0}、%{SOURCE1}等引用。
  • BuildRoot - “虚拟根目录”,比如%{_topdir}/BUILDROOT。
  • BuildRequires - 在本机编译rpm包时,所依赖的软件包名称。可以用>=或<=表示大于或小于某一特定版本。注意:>=号两边需要用空格隔开,而不同的软件名称也用空格(或逗号)分开。比如 gcc >=4.2.2, make
  • Requires - 该rpm包所依赖的软件名称,类似的还有PreReq、Requires(pre)、Requires(post)、Requires(preun)、Requires(postun)等,它们都是针对不同阶段的依赖指定
  • BuildArch - 指定目标处理器的架构,noarch表示不指定,默认值是/usr/lib/rpm/marcros文件中的%{_build_arch},比如BuildArch: noarch。
  • Packager - 打包者的信息。
  • Provides - 本软件的一些特定的功能。
  • %description - 软件包的详细说明,不能超过80个英文字符。
  • Distribution - 发行版本标识。
  • Vendor - 发行商的信息。
  • Patch0 - 补丁文件的名称。如果有多个补丁文件,则依次使用Patch1、Patch2等向后追加,后面可以使用%patch0或%{patch0}等引用。
生成补丁:

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
        说明:比较两个目录的不同,并生成补丁。      
打补丁:

patch命令:  
    patch就是利用diff制作的补丁来实现源文件(目录)和目标文件(目录)的相互转换。常用的选项有:
    -p<NUM> 表示忽略掉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文件中的“阶段”

  • %prep阶段

    • 动作:将%_sourcedir目录下的源代码包解压到%_builddir目录下,如果需要给源代码打补丁,也在这个阶段进行。
    • 解压时,最常见的指令是%setup,并且只有*.tar.gz格式的源代码包才会被解压缩。%setup还会完成后续阶段的目录切换和设置,如果在这个阶段不用这条指令,那么后续的每个阶段都要自己手动去改变相应的目录。%setup常用的选项,如下:
      • %setup 不加任何选项,仅将软件包打开
      • %setup -c 解压缩之前先产生目录
      • %setup -n newdir 将源代码解压到newdir目录
      • %setup -b num 将第num个SOURCE文件解压缩
      • %setup -c -n newdir 将源代码解压到newdir目录,并在解压缩之前先产生目录
    • 使用%patch打补丁。比如%patch0 -p1,表示使用前面定义的Patch0打补丁,并忽略第一层目录。
  • %build阶段

    • 动作:在临时目录(该临时目录在%_builddir下)执行源代码的编译,通常是执行./configure和make指令。如果有些软件需要最先执行bootstrap之类的,可以放在configure之前来做。
    • 在这个阶段最常见的两条指令是:%configure、make %{?_smp_mflags}。其中%configure是rpm定义的标准宏指令,它会自动将prefix设置成/usr。另外,这个宏还可以接受额外的参数。如果不用 %configure,就需要完全手动指定configure时的配置参数了。同样也可以给make传递额外的参数,例如:make %{?_smp_mflags} OPTIMIZE="%{optflags}",其中%{optflags}是定义在/usr/lib/rpm/marcros中的优化参数。
  • %install阶段

    • 动作:(1)执行make install,将软件安装到“虚拟根目录”;(2)在“虚拟根目录”里建好目录结构,并将需要打包到rpm包的文件或目录,拷贝到“虚拟根目录”下相应的目录里。当安装rpm包时,这些文件或目录会被安装到用户系统相应的位置。
    • 这个阶段最常见的两条指令是:rm -rf $RPM_BUILD_ROOT、make install DESTDIR=$RPM_BUILD_ROOT。
    • 如果软件有额外的配置文件或启动脚本,需要手动用copy命令或install命令将它们拷贝到“虚拟根目录”。比如:install -d $RPM_BUILD_ROOT/, cp -a * $RPM_BUILD_ROOT/。(这里的相对路径是相对“编译软件的临时目录”的;同时也可以使用%{SOURCE1}等,表示从%_sourcedir目录下,拷贝源代码文件。)
  • %clean阶段

    • 动作:执行编译后的清理工作,比如执行make clean、清除“虚拟根目录”等。
  • %files阶段

    • 动作:它是制作rpm包时的一个阶段,主要用来说明将“虚拟根目录”下的哪些文件和目录打包到rpm包里。所有需要打包到rpm包的文件和目录都需要在%files下列出。
    • 写要打包的文件列表时,必须以“/”开头(也可以以宏常量开头,宏常量本质上也是以“/”开头的),表示从“虚拟根目录”中拷贝文件到最终的rpm包里。
    • %exclude用于列出不想打包到rpm中的目录或文件。如果%exclude指定的文件不存在,会报错
    • “虚拟根目录”里所有文件都要明确地被指定是否要被打包到rpm包里。比如,“虚拟根目录”下有4个目录a、b、c和d,在%files里仅指定a和b要打包到rpm里,如果不把c和d用%exclude声明,就会报错。如果声明了“虚拟根目录”里不存在的文件或目录,也会报错。
    • %defattr(文件权限,用户名,组名,目录权限),用来指定文件和目录的属性,“-”表示默认的权限,对文件文件是0644,对可执行文件是0755。
    • 所有跟在%doc宏后面的文件都来自“编译软件的临时目录”,这个宏所指定的文件会被安装到/usr/share/doc/name-version/目录里。
  • %changelog

    • 动作:记录每次打包时的变更日志。
    • 标准格式是:
      * date +"%a %b %d %Y" 修改人 邮箱 本次版本
      - 内容

下面是可选的阶段:

  • %pre 安装或者升级软件前要做的事情,比如停止服务、备份相关文件等
  • %post 安装或者升级完成后要做的事情,比如执行ldconfig重构动态库缓存、启动服务等
  • %preun 卸载软件前要做的事情,比如停止相关服务、关闭进程等。
  • %postun 卸载软件之后要做的事情,比如删除备份、配置文件等。(%preun在升级的时候会执行,%postun在升级rpm包的时候不会执行。

其它

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

感谢浏览tim chow的作品!

如果您喜欢,可以分享到: 更多

如果您有任何疑问或想要与tim chow进行交流

可点此给tim chow发信

如有问题,也可在下面留言: