SPEC 基础知识
SPEC 文件基础知识
制作 rpm 软件包并不是一件复杂的工作,其中的关键在于编写软件包的 spec 描述文件。要想制作一个 rpm 软件包就必须写一个软件包描述文件 spec。这个文件中包含了软件包的诸多信息,如:软件包的名字、版本、类别、说明摘要、创建时要执行什么指令、安装时要执行什么操作、以及软件包所要包含的文件列表等等。在实际过程中,最关键的地方是要清楚虚拟路径的位置,以及宏的定义。
Mandatory parameters to be present in SPEC file to build and generate RPM
Below options are mandatory to be present in SPEC file
Name, Version, Release, Summary, License, description, install and files
1 | Name: test-dev |
1. 文件头
语法:TagName: value,比如Version: 2.1.0,tag名大小写不敏感。
spec支持定义宏,要定义宏,使用:%define testMacro 2,这里定义了一个宏,名称为testMacro,值为2,要使用这个宏,使用%{testMacro}或者%testMacro。
这个区域定义的 Name、Version 这些字段对应的值可以在后面通过 %{name},%{version} 这样的方式来引用,类似于 C 语言中的宏:
Summary:用一句话概括该软件s包尽量多的信息。Name:软件包的名字,最终 rpm 软件包是用该名字与版本号(Version)、释出号(Release)及体系号来命名软件包的,后面可使用%{name}的方式引用Version:软件版本号。仅当软件包比以前有较大改变时才增加版本号,后面可使用%{version}引用Release:软件包释出号/发行号。一般我们对该软件包做了一些小的补丁的时候就应该把释出号加 1,后面可使用%{release}引用Packager:打包的人(一般喜欢写个人邮箱)Vendor:软件开发者的名字,发行商或打包组织的信息,例如RedFlagCo,LtdLicense:软件授权方式,通常是GPL(自由软件)或GPLv2,BSDCopyright:软件包所采用的版权规则。具体有:GPL(自由软件),BSD,MIT,Public Domain(公共域),Distributable(贡献),commercial(商业),Share(共享)一般的开发都写
GPL。
Group:软件包所属类别- Development/System (开发/系统)
System Environment/Daemons (系统环境/守护)
Source0:源程序软件包的名字/源代码包的名字,如stardict-2.0.tar.gz。可以带多个用Source1、Source2等源,后面也可以用%{source1}、%{source2}引用
1 | Copy |
2. 依赖关系
依赖关系定义了一个包正常工作需要依赖的其他包,RPM在升级、安装和删除的时候会确保依赖关系得到满足。
BuildRequires: 制作过程中用到的软件包,构建依赖Requires: 安装时所需软件包Requires(pre): 指定不同阶段的依赖
BuildRoot: 这个是安装或编译时使用的「虚拟目录」,打包时会用到该目录下文件,可查看安装后文件路径,例如:BuildRoot: %_topdir/BUILDROOT。Prefix: %{_prefix}这个主要是为了解决今后安装 rpm 包时,并不一定把软件安装到 rpm 中打包的目录的情况。这样,必须在这里定义该标识,并在编写%install脚本的时候引用,才能实现 rpm 安装时重新指定位置的功能BuildArch: 指编译的目标处理器架构,noarch标识不指定,但通常都是以/usr/lib/rpm/marcros中的内容为默认值%description:软件包详细说明,可写在多个行上。这样任何人使用rpm -qi查询您的软件包时都可以看到它。您可以解释这个软件包做什么,描述任何警告或附加的配置指令,等等。URL:软件的主页
2.1. RPM 包信息查看
我通过命令查看了 nginx 包的信息,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51Copy
# 查看头部信息
$ rpm -qpi ./nginx-1.12.2-2.el7.x86_64.rpm
Name : nginx
Epoch : 1
Version : 1.12.2
Release : 2.el7
Architecture: x86_64
Install Date: (not installed)
Group : System Environment/Daemons
Size : 1574949
License : BSD
Signature : RSA/SHA256, Tue 06 Mar 2018 05:44:06 PM CST, Key ID 6a2faea2352c64e5
Source RPM : nginx-1.12.2-2.el7.src.rpm
Build Date : Tue 06 Mar 2018 05:27:44 PM CST
Build Host : buildhw-02.phx2.fedoraproject.org
Relocations : (not relocatable)
Packager : Fedora Project
Vendor : Fedora Project
URL : <http://nginx.org/>
Bug URL : <https://bugz.fedoraproject.org/nginx>
Summary : A high performance web server and reverse proxy server
Description :
Nginx is a web server and a reverse proxy server for HTTP, SMTP, POP3 and
IMAP protocols, with a strong focus on high concurrency, performance and low
memory usage.
# 查看脚本内容
$ rpm --scripts -qp ./nginx-1.12.2-2.el7.x86_64.rpm
postinstall scriptlet (using /bin/sh):
if [ $1 -eq 1 ] ; then
# Initial installation
systemctl preset nginx.service >/dev/null 2>&1 || :
fi
preuninstall scriptlet (using /bin/sh):
if [ $1 -eq 0 ] ; then
# Package removal, not upgrade
systemctl --no-reload disable nginx.service > /dev/null 2>&1 || :
systemctl stop nginx.service > /dev/null 2>&1 || :
fi
postuninstall scriptlet (using /bin/sh):
systemctl daemon-reload >/dev/null 2>&1 || :
if [ $1 -ge 1 ]; then
/usr/bin/nginx-upgrade >/dev/null 2>&1 || :
fi
2.2. BuildRequires
定义构建时依赖的软件包,在构建机器编译 rpm 包时需要的辅助工具,以逗号分隔。
假如,要求编译 myapp 时,gcc 的版本至少为 4.4.2,则可以写成 gcc >=4.2.2
2.3. Requires
定义安装时的依赖包,该 rpm 包所依赖的软件包名称,就是指编译好的 rpm 软件在其他机器上安装时,需要依赖的其他软件包。
可以用 >= 或 <= 表示大于或小于某一特定版本。 >= 号两边需用空格隔开,而不同软件名称也用空格分开。
格式:
1 | Copy |
其它写法例如:
1 | Copy |
还有例如PreReq、Requires(pre)、Requires(post)、Requires(preun)、Requires(postun)、BuildRequires等都是针对不同阶段的依赖指定。
关于pre、post、preun、postun含义理解,感觉post有一种“完成”的意思:
1 | Copy |
例如:
1 | Copy |

2.4. BuildRoot
该参数非常重要,因为在生成 rpm 的过程中,执行 make install 时就会把软件安装到上述的路径中,在打包的时候,同样依赖“虚拟目录”为“根目录”进行操作。后面可使用 $RPM_BUILD_ROOT 方式引用。
考虑到多用户的环境,一般定义为:
1 | Copy |
3. 预处理 %prep
这个阶段是「预处理」,通常用来执行一些解开源程序包的命令,为下一步的编译安装作准备。
%prep 和下面的 %build,%install 段一样,除了可以执行 rpm 所定义的宏命令(以%开头)以外,还可以执行 SHELL 命令,命令可以有很多行,如我们常写的 tar 解包命令。功能上类似于 ./configure。
%prep 阶段进行实际的打包准备工作,它是使用节前缀 %prep 表示的。主要功能有:
- 将文件 (
SOURCES/) 解压到构建目录 (BUILD/) - 应用
Patch(打补丁) (SOURCES/ => BUILD/) - 描述
rm -rf $RPM_BUILD_ROOT - 描述或编辑本部分用到的命令到
PreReq - 通过
b .XXX描述补丁备份
它一般包含 %setup 与 %patch 两个命令。%setup 用于将软件包打开,执行 %patch 可将补丁文件加入解开的源程序中。
示例:
1 | Copy |
3.1. 宏 %setup
这个宏解压源代码,将当前目录改为源代码解压之后产生的目录。这个宏还有一些选项可以用。例如,在解压后,%setup 宏假设产生的目录是 %{name}-%{version}
1 | Copy |
主要的用途就是将 %sourcedir 目录下的源代码解压到 %builddir 目录下,也就是将~/rpmbuild/SOURCES 里的包解压到 ~/rpmbuild/BUILD/%{name}-%{version} 中。一般用 %setup -c 就可以了,但有两种情况:
- 一就是同时编译多个源码包,
- 二就是源码的 tar 包的名称与解压出来的目录不一致,此时,就需要使用
n参数指定一下。
1 | Copy |
应该 -q 参数给 %setup 宏。这会显著减少编译日志文件的输出,尤其是源代码包会解压出一堆文件的时候。
在 ~/rmpbuild/BUILD/%{name}-%{version} 目录中进行 ,使用标准写法,会引用/usr/lib/rpm/marcros 中定义的参数。
3.2. 宏 %patch
这个宏将头部定义的补丁应用于源代码。如果定义了多个补丁,它可以用一个数字的参数来指示应用哪个补丁文件。它也接受 -b extension 参数,指示 RPM 在打补丁之前,将文件备份为扩展名是 extension 的文件。
通常补丁都会一起在源码 tar.gz 包中,或放到 SOURCES 目录下。一般参数为:
%patch -p1使用前面定义的Patch补丁进行,p1是忽略patch的第一层目录%Patch2 -p1 -b xxx.patch打上指定的补丁,b是指生成备份文件
3.3. %build 阶段
本段是「构建」阶段,这个阶段会在 %_builddir 目录下执行源码包的编译。一般是执行执行常见的 configure 和 make 操作。
该阶段一般由多个 make 命令组成。与 %prep 段落一样,这些命令可以是 shell 命令,也可以是宏。
记住两点:
%build和%install的过程中,都必须把编译和安装的文件定义到“虚拟根目录” 中%file中必须明白,用的是相对目录
这个阶段我们最常见只有两条指令:
1 | Copy |
%configure 这个不是关键字,而是 rpm 定义的标准宏命令。意思是执行源代码的configure配置。会自动将 prefix 设置成 /usr。
这个 %{?_smp_mflags} %{optflags} 是什么意思呢?
1 | Copy |
所以,上面那个命令就是 make -j4。问题又来了,make -j4 表示什么意思?
都是一些优化参数
3.4. %install 阶段
「安装」阶段,就是执行 make install 命令操作。开始把软件安装到虚拟的根目录中。这个阶段会在 %buildrootdir 目录里建好目录结构,然后将需要打包到 rpm 软件包里的文件从 %builddir 里拷贝到 %_buildrootdir 里对应的目录里。
在 ~/rpmbuild/BUILD/%{name}-%{version} 目录中进行 make install 的操作。%install 很重要,因为如果这里的路径不对的话,则下面 %file 中寻找文件的时候就会失败。
特别需要注意的是:%install 部分使用的是绝对路径,而 %file 部分使用则是相对路径,虽然其描述的是同一个地方。千万不要写错。
将已编译的软件安装到虚拟的目录结构中,从而可以打包成一个 RPM。当用户最终用 rpm -ivh name-version.rpm 安装软件包时,这些文件会安装到用户系统中相应的目录里。
1 | Copy |
「翻译」一下:
1 | Copy |
需要说明的是,这里的 %install 主要就是为了后面的 %file 服务的。所以,还可以使用常规的系统命令:
1 | Copy |
其中 %{buildroot} 和 $RPMBUILDROOT 是等价的, %{buildroot}必须全部用小写,不然要报错。
注意区分 $RPM_BUILD_ROOT和 $RPM_BUILD_DIR:
$RPM_BUILD_ROOT是指开头定义的BuildRoot,是%file需要的。$RPM_BUILD_DIR通常就是指~/rpmbuild/BUILD
scripts section 没必要可以不填#
%pre安装前执行的脚本%post安装后执行的脚本%preun卸载前执行的脚本%postun卸载后执行的脚本%pretrans在事务开始时执行脚本%posttrans在事务结束时执行脚本
%preun %postun 的区别是什么呢?
- 前者在升级的时候会执行,后者在升级 rpm 包的时候不会执行
3.5. %files 阶段
任何打包的文件,都需要在这个包的详细的文件列表中,如果是目录,包的所有者的全部目录都在中间, %dir 来指定空目录,可以用 %files -f /tmp/dyanmic_filelist 来指定一个文件列表。
默认 %config 会替换掉配置,给原来的配置修改名字为 .rpmorig ,如果不想修改的话,就用%config(noreplace) 就会给新的配置文件名字命名为 .rpmnew.
本段是文件段,主要用来说明会将 %{buildroot} 目录下的哪些文件和目录最终打包到rpm包里。定义软件包所包含的文件,分为三类:
- 说明文档(doc)
- 配置文件(config)
- 执行程序
具体一点,包含如下场景:
1. 一个文件没有被%config或%config(noreplace)指令配置
此时,不管该文件在安装完成后,有没有在本地被修改过,当升级该rpm包时,该文件会被这个新的rpm包的里的同名文件替换,(旧文件被删除)。
2. 一个文件被%config指令配置
此时包含如下情况:
- 该文件在新的rpm包里相对之前的rpm有变化,且在本地没有被修改过。此时执行
rpm -Uvh xxxx时,新rpm包里的该文件会替换旧的文件。(旧文件被删除) - 该文件在新的rpm包里相对之前的rpm有变化,且在本地被修改过。此时执行
rpm -Uvh xxxx时,新rpm包里的该文件会替换掉掉旧的文件,旧的文件会被保存为xx**.rpmsave**,如/etc/redis/redis.conf.rpmsave - 该文件在新的rpm包里相对之前的rpm没有变化,且在本地没有被修改过。此时执行
rpm -Uvh xxxx时,新rpm包里的该文件会替换掉旧的文件。(旧文件被删除) - 该文件在新的rpm包里相对之前的rpm没有变化,且在本地被修改过。此时执行
rpm -Uvh xxxx时,新rpm包里的该文件不会覆盖旧的文件,旧文件保持不变。
3. 一个文件被%config(noreplace)指令配置
此时包含如下情况:
- 该文件在新的rpm包里相对之前的rpm有变化,且在本地没有被修改过。此时执行
rpm -Uvh xxxx时,新rpm包里的该文件会替换旧的文件。(旧文件被删除) - 该文件在新的rpm包里相对之前的rpm有变化,且在本地被修改过。此时执行
rpm -Uvh xxxx时,旧文件保持不变,新rpm包里的该文件并重命名为xx**.rpmnew**,例如/etc/redis/redis.conf.rpmnew - 该文件在新的rpm包里相对之前的rpm没有变化,且在本地没有被修改过。此时执行
rpm -Uvh xxxx时,新rpm包里的该文件会替换旧的文件。(旧文件被删除) - 该文件在新的rpm包里相对之前的rpm没有变化,且在本地被修改过。此时执行
rpm -Uvh xxxx时,新rpm包里的该文件不会覆盖旧的文件,旧文件保持不变。
| 文件标记 | 在update包中是否更新 | 本地磁盘没有被修改 | 本地磁盘被修改过 |
|---|---|---|---|
| 无 | 没更新 | 替换(旧文件删除) | 替换(旧文件删除) |
| 无 | 更新 | 替换(旧文件删除) | 替换(旧文件删除) |
| %config | 没更新 | 替换(旧文件删除) | 保留修改过的文件,不替换 |
| %config | 更新 | 替换(旧文件删除) | 旧文件被保存为xxx.rpmsave,新文件替换旧文件 |
| %config(noreplace) | 没更新 | 替换(旧文件删除) | 保留修改过的文件,不替换 |
| %config(noreplace) | 更新 | 替换(旧文件删除) | 旧文件保持不变,新文件重命名为xxx.rpmnew |
| 文件标记 | 在update包中是否更新 | 本地磁盘被修改过 |
|---|---|---|
| 之前是%config(noreplace),现在是%config | 更新 | 文件被替换,旧文件被保存为xxx.rpmsave |
| 之前是%config,现在是%config(noreplace) | 更新 | 旧文件保持不变,update包中的新文件重命名为xxx.rpmnew |
| macro | definition | comment |
|---|---|---|
%{_sysconfdir} |
/etc |
|
%{_prefix} |
/usr |
can be defined to /app for flatpak builds |
%{_exec_prefix} |
%{_prefix} |
default: /usr |
%{_includedir} |
%{_prefix}/include |
default: /usr/include |
%{_bindir} |
%{_exec_prefix}/bin |
default: /usr/bin |
%{_libdir} |
%{_exec_prefix}/%{_lib} |
default: /usr/%{_lib} |
%{_libexecdir} |
%{_exec_prefix}/libexec |
default: /usr/libexec |
%{_sbindir} |
%{_exec_prefix}/sbin |
default: /usr/sbin |
%{_datadir} |
%{_datarootdir} |
default: /usr/share |
%{_infodir} |
%{_datarootdir}/info |
default: /usr/share/info |
%{_mandir} |
%{_datarootdir}/man |
default: /usr/share/man |
%{_docdir} |
%{_datadir}/doc |
default: /usr/share/doc |
%{_rundir} |
/run |
|
%{_localstatedir} |
/var |
|
%{_sharedstatedir} |
/var/lib |
|
%{_lib} |
lib64 |
lib on 32bit platforms |
还可定义文件存取权限,拥有者及组别。
这里会在虚拟根目录下进行,千万不要写绝对路径,而应用宏或变量表示相对路径。
在 %files 阶段的第一条命令的语法是:
1 | Copy |
注意点:同时需要在 %install 中安装。
1 | Copy |
%exclude 列出不想打包到 rpm 中的文件。注意:如果 %exclude 指定的文件不存在,也会出错的。
在安装 rpm 时,会将可执行的二进制文件放在 /usr/bin 目录下,动态库放在 /usr/lib 或者 /usr/lib64 目录下,配置文件放在 /etc 目录下,并且多次安装时新的配置文件不会覆盖以前已经存在的同名配置文件。
关于 %files 阶段有两个特性:
%{buildroot}里的所有文件都要明确被指定是否要被打包到 rpm里。什么意思呢?假如,%{buildroot}目录下有 4 个目录 a、b、c和d,在%files里仅指定 a 和 b 要打包到 rpm 里,如果不把 c 和 d 用exclude声明是要报错的;- 如果声明了
%{buildroot}里不存在的文件或者目录也会报错。
关于 %doc 宏,所有跟在这个宏后面的文件都来自 %{_builddir} 目录,当用户安装 rpm 时,由这个宏所指定的文件都会安装到 /usr/share/doc/name-version/ 目录里。
4. %clean
清理段,可以通过 --clean 删除 BUILD
编译完成后一些清理工作,主要包括对 %{buildroot} 目录的清空(这不是必须的),通常执行诸如 make clean 之类的命令。
5. %changelog
本段是修改日志段,记录 spec 的修改日志段。你可以将软件的每次修改记录到这里,保存到发布的软件包中,以便查询之用。
每一个修改日志都有这样一种格式:
- 第一行是:
星期 月 日 年 修改人 电子信箱。其中:星期、月份均用英文形式的前 3 个字母,用中文会报错。 - 接下来的行写的是修改了什么地方,可写多行。一般以减号开始,便于后续的查阅。
1 | Copy |
6. 宏
在定义文件的安装路径时,通常会使用类似 %_sharedstatedir 的宏,这些宏一般会在 /usr/lib/rpm/macros 中定义。关于宏的语法,可以查看 Macro syntax
RPM 内建宏定义在 /usr/lib/rpm/redhat/macros 文件中,这些宏基本上定义了目录路径或体系结构等等;同时也包含了一组用于调试 spec 文件的宏。
所有宏都可以在 /usr/lib/rpm/macros 找到,附录一些常见的宏:
1 | Copy |
利用 rpmbuild 构建 rpm 安装包时,通过命令 rpm --showrc|grep prefix 查看。
通过 rpm --eval "%{macro}" 来查看具体对应路径。
比如我们要查看 %{_bindir} 的路径,就可以使用命令 rpm --eval "%{ _bindir}" 来查看。
1 | Copy |
7. 变量
define 定义的变量类似于局部变量,只在 %{!?foo: ... } 区间有效,不过 SPEC 并不会自动清除该变量,只有再次遇到 %{} 时才会清除
7.1. define vs. global
两者都可以用来进行变量定义,不过在细节上有些许差别,简单列举如下:
define用来定义宏,global用来定义变量;- 如果定义带参数的宏 (类似于函数),必须要使用
define; - 在
%{}内部,必须要使用global而非define; define在使用时计算其值,而global则在定义时就计算其值;
可以简单参考如下的示例。
1 | Copy |
3 的输出是不符合预期的,可以将 %define 修改为 global 即可
7.2. %{?dist} 表示什么含义?
不加问号,如果 dist 有定义,那么就会用定义的值替换,否则就会保 %{dist};加问好,如果 dist 有定义,那么也是会用定义的值替换,否则就直接移除这个tag %{?dist}
举例:
1 | Copy |