Red-Hat Package Manager 简称 RPM,这一文件格式在历史名称上虽然打着 RedHat 标志,但其原始设计理念是开放式的,现在包括 OpenLinux、SuSE、Turbo Linux 等多个 Linux 分发版本都有采用,可以算是公认的行业标准了。
在此介绍下如何制作 RPM 包,尤其是如何写 .spec 配置文件,以及常见的技巧。
简介
若要构建一个标准的 RPM 包,需要创建 .spec
文件,其中包含软件打包的全部信息。然后,使用 rpmbuild 命令,按照 spec 文件的配置,系统会按照步骤生成最终的 RPM 包。
另外,需要注意的是,在使用时,需要使用普通用户,一定不要用 root 用户。
结下来,看看如何编译制作 RPM 包。
环境配置
接下来,看看如何配置一步步制作 RPM 包。
安装工具
首先,在 CentOS 中配置环境。
----- 安装环境,需要rpm-build工具来打包,该包只依赖于gdb以及rpm版本
# yum install rpm-build -y
----- 新建所需目录
$ mkdir -pv ~/rpm-maker/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}
配置环境
打包相关的配置保存在宏文件 (macrofiles) 中,默认使用 $HOME/rpmbuild
目录,用户配置文件保存在 $HOME/.rpmmacros
;而制作包时的操作实际都在 topdir 指定的目录下,可以通过如下方式查看。
--- 查看所有的配置文件顺序
$ rpmbuild --showrc | grep macros
--- 确认上述的根工作目录,两种方式相同
$ rpmbuild --showrc | grep ': _topdir'
$ rpm --eval '%_topdir'
--- 设置topdir变量,然后再次验证下,确认已经修改为目标目录
$ echo "%_topdir %{getenv:HOME}/rpm-maker" > ~/.rpmmacros
$ rpm --eval '%_topdir'
--- 也可以在制作rpm包时通过--define指定
$ rpmbuild --define '_topdir rpm-maker' -ba package.spec
下载 MySQL 的源码,并保存在 SPECS 目录下,整体目录结构如下。
$ tree ~/rpm-maker
.
|-- BUILD ← 解压后的tar.gz包
|-- BUILDROOT ← 临时安装目录,会打包该目录下文件
|-- RPMS ← 编译后的RPM包
|-- SOURCES ← 所需的文件
| `-- mysql-xxx.tar.gz ← 包括了源码包
|-- SPECS ← *.spec编译脚本
| |-- mysql-xxx.spec
| `-- mysql-yyy.spec
`-- SRPMS ← src格式的rpm包存放的位置
执行命令
接下来,看看如何生成 rpm 包,也就是使用 rpmbuild 命令,过程可以分为多个阶段。
$ rpmbuild --bb mysql.spec
使用参数:
-bb 制作成二进制RPM包
-bs 制作源码的RPM包
-ba 源码和二进制两种形式的RPM包
-bl 检测BUILDROOT没有包含到rpm包中的文件
-bp 执行到pre
-bc 执行到build段
-bi 执行install段
--quiet 默认会输出每次执行的shell命令,此时忽略
----- 通过源码包重新编译
$ rpmbuild --rebuild package-0.0-0.src.rpm
----- 编译时指定参数
$ rpmbuild -bb SPECS/librdkafka.spec --define "__version 0.9.4" --define "__release 1"
一般来说,执行的顺序为 rpmbuild -bp
,再 -bc
再 -bi
,如果没问题,rpmbuild -ba
生成 src 包与二进制包,使用时通过 rpm -ivh xxx.rpm;
以及 rpm -e xxx.rpm
进行安装卸载。
执行步骤
1. 在 %prep 段中,通过 %setup -q 宏解压、打补丁,一般是从 SOURCES 目录下解压到 BUILD 目录下,一般目录是 "NAME-VERSION" 。
2. 通过 %build 段定义了如何进行编译,编译目录就是上述的 BUILD/NAME-VERSION 。
2.1 首先通过 %configure %cmake 进行配置。
2.2 然后利用 %{__make} %{?_smp_mflags} 进行并行编译。
3. 接着就是安装,也就是 %install 字段,一般会在 BUILDROOT/NAME-VERSION-RELEASE-ARCH 目录下。
3.1 通常为了清理环境会先使用 rm -rf %{buildroot} 清理。
3.2 接着通过 %{__make} install DESTDIR=%{buildroot} 命令进行安装,其中 DESTDIR 相当于是根目录了。
3.3 通过上步安装的文件,需要都打包到 RPM 包中,如果不需要那么就先清理掉。
3.4 编译没有生成的也可以通过 %{__install} %{__mv} %{__rm} 命令直接复制。
4. 执行检查规范,对应了 %test 段,一般执行一些单元测试。
5. 开始打包
6. 在 %clean 段处理清理操作,通常会通过 rm -rf %{buildroot} 删除编译的中间内容。
另外,还有 %check
字段,用于做些检查。
SPEC 文件
接下来准备 spec 文件,也是核心的内容,该文件包括三部分:介绍部分,编译部分,files 部分。接下来,是一个简单的示例,可以看看到底是如何制作 RPM 包的。
### 0.define section ← ###自定义宏段(可选),方便后续引用
%define mysql_user mysql ← 定义宏,方便后续引用
%define _topdir /home/jinyang/databases/rpm-maker
### 1.The introduction section ← ###介绍区域段
Name: mysql ← 包名称,通常会在指定源码压缩包时引用
Version: 5.7.17 ← 版本号,同上
Release: 1%{?dist} ← 释出号,每次制作rpm包时递增该数字
Summary: MySQL from FooBar. ← 软件包简介,最好不要超过50字符
License: GPLv2+ and BSD ← 许可,GPL、BSD、MIT等,也可以使用or
Group: Applications/Databases ← 程序组名,从/usr/share/doc/rpm-VER/GROUPS选择
URL: http://kidding.com ← 一般为官网
Source0: %{name}-boost-%{version}.tar.gz ← 源码包名称(可以使用URL),可以用SourceN指定多个,如配置文件
#Patch0: some-bugs.patch ← 如果需要打补丁,则依次填写
BuildRequires: gcc,make ← 制作过程中用到的软件包
Requires: pcre,pcre-devel,openssl,chkconfig ← 安装时所需软件包,可使用bash >= 1.1.1
Requires(pre): test ← 指定不同阶段的依赖
BuildRoot: %_topdir/BUILDROOT ← 会打包该目录下文件,可查看安装后文件路径
Packager: FooBar <foobar@kidding.com>
Vendor: kidding.com
%description ← ###软件包的详细描述,可以撒丫子写了
It is a MySQL from FooBar.
#--- 2.The Prep section ← ###准备阶段,主要是解压源码,并切换到源码目录下
%prep
%setup -q ← 宏的作用是解压并切换到目录
#%patch0 -p1 ← 如果需要打补丁,则依次写
#--- 3.The Build Section ← ###编译制作阶段,主要目的就是编译
%build
make %{?_smp_mflags} ← 多核则并行编译
#--- 4.Install section ← ###安装阶段
%install
if [-d %{buildroot}]; then
rm -rf %{buildroot} ← 清空下安装目录,实际会自动清除
fi
make install DESTDIR=%{buildroot} ← 安装到buildroot目录下
%{__install} -Dp -m0755 contrib/init.d %{buildroot}%{_initrddir}/foobar
%{__install} -d %{buildroot}%{_sysconfdir}/foobar.d/
#--- 4.1 scripts section ← ###没必要可以不填
%pre ← 安装前执行的脚本
%post ← 安装后执行的脚本
%preun ← 卸载前执行的脚本
%postun ← 卸载后执行的脚本
%pretrans ← 在事务开始时执行脚本
%posttrans ← 在事务结束时执行脚本
#--- 5. Clean section ← ###清理段,可以通过--clean删除BUILD
%clean
rm -rf %{buildroot}
#--- 6. File section ← ###打包时要包含的文件,注意同时需要在%install中安装
%files
%defattr (-,root,root,0755) ← 设定默认权限
%config(noreplace) /etc/my.cnf ← 表明是配置文件,noplace表示替换文件
%doc %{src_dir}/Docs/ChangeLog ← 表明这个是文档
%attr(644, root, root) %{_mandir}/man8/mysqld.8* ← 分别是权限,属主,属组
%attr(755, root, root) %{_sbindir}/mysqld
#--- 7. Chagelog section ← ###记录SPEC的修改日志段
%changelog
* Fri Dec 29 2012 foobar <foobar@kidding.com> - 1.0.0-1
- Initial version
对于 %config(noreplace)
表示为配置文件,且不能覆盖,新安装时如果配置文件存在则会将原文件保存为 *.rpmsave
,升级时则不会覆盖原文件,直接将包中的文件命名为 *.rpmnew
,更详细内容可以参考 RPM, %config, and (noreplace) 。
关于脚本部分,通常所有安装脚本和触发器脚本都是使用 /bin/sh
来执行的,如果想使用其他脚本,如 Lua,可使用 -p /usr/bin/lua
来告诉 rpm 使用 lua 解释器。如
%post -p /usr/bin/lua
# lua script here
%postun -p /usr/bin/perl
$ perl sciprt here
另外,如果只执行一条命令,也使用 -p
选项可直接执行,如 %post -p /sbin/ldconfig
。
一般最后一条命令的 exit 状态就是脚本的 exit 状态,除一些特殊情况,一般脚本都是以 exit 0
状态退出,所以大部分小脚本片段都会使用 "||:"
退出。
%prep
%setup -q -T -a 0 -a 7 -a 10 -c -n %{src_dir}
参数列表:
-T 禁止自动解压源码文件
-D 解压前不删除目录
-a 切换目录前,解压指定Source文件,例如-a 0表示解压Source0
-b 切换目录后,解压指定Source文件,例如-a 0表示解压Source0
-n 解压后目录名称与RPM名称不同,则通过该参数指定切换目录
-c 解压缩之前先生成目录
systemd
spec 脚本中提供了与 systemd 相关的脚本,关于脚本的详细内容可以查看 macros.systemd.in,实用方式很简单,如下:
----- 安装结束后,实际执行systemctl preset,执行服务预设的内容
%systemd_post foobar.service
----- 执行卸载前,systemctl disable
%systemd_preun foobar.service
----- 卸载后执行重启操作,daemon-reload+try-restart
%systemd_postun_with_restart foobar.service
----- 如果不需要重启,例如有状态链接的(D-Bus),执行systemctl daemon-reload
%systemd_postun foobar.service
关于安装升级的各个操作步骤详见 RPM SPEC pre/post/preun/postun argument values 以及 Fedora Packaging Guidelines for RPM Scriptlets 。
校验
在安装完 RPM 包之后,可以通过 --verify
或者 -V
进行校验,正常不会显示任何信息,可以通过 --verbose
或者 -v
显示每个文件的信息;文件丢失显示 missing
,属性方面的修改内容如下:
SM5DLUGT c filename
属性:
S: 文件大小;
M: 权限;
5: MD5检查和;
D: 主从设备号;
L: 符号连接;
U: 属主;
G: 属组;
T: 最后修改时间。
类型:
c: 配置文件;
d: 文档文件。
增加签名
当制作 RPM 包之后,为了防止被篡改,可以使用私钥进行签名,然后将公钥开放出去,然后用户下载完软件包可以通过公钥进行验证签名,从而确保文件的原始性。
可以通过如下步骤进行操作。
1. 生成密钥对
首先,需要使用 gpp 来生成公私钥对,一般可以使用 RSA 非对称加密。
$ gpg --gen-key
此时会在 ~/.gnupg
目录下创建相关文件,创建完后可以通过如下命令查看当前的密钥对。
$ gpg --list-keys
/home/foobar/.gnupg/pubring.gpg
------------------------------
pub 2048R/860FB269 2017-01-13
uid foobar (just for test) <foobar@example.com>
其中 UID 分别对应了用户名、注释、邮箱。
2. 软件包签名
修改 RPM 宏,使用上述生成的密钥对。
$ echo %_signature gpg >> ~/.rpmmacros
$ echo "%_gpg_name foobar (just for test)" >> ~/.rpmmacros
对已有 rpm 软件包进行签名。
$ rpm --addsign package_name.rpm
Enter pass phrase:
Pass phrase is good.
package_name.rpm
如果失败,则会出现如下的错误。
$ rpm --addsign package_name.rpm
Enter pass phrase:
Pass phrase check failed.
此时需要仔细检查密钥名称和写入 rpm 宏里面的是否一致,最好是将 --list-keys
显示的内容整体复制出来。
当然也可以在通过 rpmbuild --sign
打包时自动包含签名。
3. 增加签名
生成签名使用的是私钥,验证签名需要使用公钥。简单来说需要执行:1)将 gpg 产生的公钥导出到一个文件;2) 将这个公钥文件导入到 RPM 数据库里;3) 使用 rpm 命令进行检验。
----- 导出公钥到一个文本文档
$ gpg --export -a "foobar (just for test)" > RPM-GPG-KEY-FOOBAR
----- 查看rpm数据库中已有的公钥
$ rpm -q gpg-pubkey-*
gpg-pubkey-f4a80eb5-53a7ff4b
gpg-pubkey-352c64e5-52ae6884
gpg-pubkey-442df0f8-4783f24a
----- 将公钥导入到RPM数据库,注意需要root用户执行
# rpm --import RPM-GPG-KEY-FOOBAR
通过上述命令重新查看公钥时,会发现新增了一个 gpg-pubkey-860fb269-5a65a0ad
公钥,其中 860FB269
与上述 gpg 生成的密钥信息相同。
如果不需要,可以通过 rpm -e gpg-pubkey-860fb269-5a65a0ad
删除即可。
4. 签名验证
通过 rpm -K package_name.rpm
命令对签名验证即可,如下是验证成功以及失败时输出的内容。
package_name.rpm: rsa sha1 (md5) pgp md5 OK
package_name.rpm: RSA sha1 (MD5) (PGP) (MD5) (PGP) md5 NOT OK (MISSING KEYS: PGP#c0eb63c7 PGP#c0eb63c7)
其它
如果导入公钥失败,那么在安装时会生成一个类似 Header V3 RSA/SHA1 signature: NOKEY, key ID c0eb63c7
的告警信息。
如果要在 YUM 中验证签名,可以将公钥复制到系统 RPM 公钥目录,一般如下操作即可。
# cp RPM-GPG-KEY-FOOBAR /etc/pki/rpm-gpg/
在源配置文件中通过如下两行来指定 gpg key 检验:
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-FOOBAR
这样在通过 YUM 安装软件包时,当下载完毕以后会首先使用公钥进行签名验证。
常用设置
接下来,看看一些常用的实用技巧。
在一个 SPEC 文件中可以同时打包多个 RPM 包,当然也可以通过 %package -n foobar
指定子模块 (subpackage) 的名称 。
宏定义
在定义文件的安装路径时,通常会使用类似 %_sharedstatedir
的宏,这些宏一般会在 /usr/lib/rpm/macros
中定义,当然部分会同时在不同平台上覆盖配置,可以直接 grep
查看。
RPM 内建宏定义在 /usr/lib/rpm/redhat/macros
文件中,这些宏基本上定义了目录路径或体系结构等等;同时也包含了一组用于调试 spec 文件的宏,关于 Macro 详细可以查看 Macro syntax,其中常用如下:
%dump 打印宏的值,包括一些内建的宏定义,也可以通过rpm --showrc查看
%{echo:message} 打印信息到标准输出
%{warn:message} 打印信息到标准错误
%{error:message} 打印信息到标准错误,然后返回BADSPEC
%{expand:expression} 类似Bash中的eval内置命令
另外常用的是根据宏来设置变量。
%{?foobar:expr} 如果宏 foobar 存在则使用 expand expr,否则为空;也可以取反 %{!?foobar:expr}
%{?macro} 只测试该宏是否存在,存在就用该宏的值,反之则不用,如 %configure %{?_with_foobar}
另外,在判断宏的 Bool 值时,可以通过如下方式测试,如果 variable
定义,则为 01
也就是 true
否则为 0
。
%if 0%{?variable:1}
... ...
%endif
配置脚本
很多制作 RPM 包的操作都是通过宏定义设置的,如下简单列举一下常见的宏定义操作。
__os_install_post
安装完之后的操作,例如去除二进制文件中的注释等;
__spec_install_pre
在安装前的操作,设置一些环境变量,然后删除BUILDROOT中的文件(如果文件多时耗时增加);
如果想取消某些操作,可以将这些操作设置为 %{nil}
即可。
----- 查看对应的命令
$ rpm --showrc | grep -A 4 ': __os_install_post'
-14: __os_install_post
/usr/lib/rpm/redhat/brp-compress
%{!?__debug_package:
/usr/lib/rpm/redhat/brp-strip %{__strip}
/usr/lib/rpm/redhat/brp-strip-comment-note %{__strip} %{__objdump}
----- 在SPEC文件头部添加如下内容
%global __os_install_post %{nil}
----- 在用户的~/.rpmmacros文件中添加如下配置
%__os_install_post %{nil}
define vs. global
两者都可以用来进行变量定义,不过在细节上有些许差别,简单列举如下:
- define 用来定义宏,global 用来定义变量;
- 如果定义带参数的宏 (类似于函数),必须要使用 define;
- 在
%{}
内部,必须要使用 global 而非 define; - define 在使用时计算其值,而 global 则在定义时就计算其值;
可以简单参考如下的示例。
#--- %prep之前的参数是必须要有的
Name: mysql
Version: 5.7.17
Release: 1%{?dist}
Summary: MySQL from FooBar.
License: GPLv2+ and BSD
%description
It is a MySQL from FooBar.
%prep
#--- 带参数时,必须使用%define定义
%define myecho() echo %1 %2
%{!?bar: %define bar defined}
echo 1: %{bar}
%{myecho 2: %{bar}}
echo 3: %{bar}
# 如下是输出内容
#1: defined
#2: defined
#3: %{bar}
显然 3 的输出是不符合预期的,可以将 %define
修改为 global
即可。
变量使用
define 定义的变量类似于局部变量,只在 %{!?foo: ... }
区间有效,不过 SPEC 并不会自动清除该变量,只有再次遇到 %{}
时才会清除,WTF!!!
在命令行中通过 --define 'with_ssl bundled'
进行定义,在 SPEC 脚本中,使用 %{?with_ssl: }
处理上述参数,或者通过 %{!?with_ssl: }
处理未定义参数时的情况。
#--- 根据传入的变量,设置好相应的ssl_option变量
%{?with_ssl: %global ssl_option -DWITH_SSL=%{with_ssl}}
#--- 接下来,通过如下方式使用上述定义的变量
%{?ssl_option}
在 RPM 中,问号 ?
用于条件检测,默认如果对应的变量值不存在,那么 RPM 会原样保留字符串,通过问号表示,如果不存在则移除。
$ rpm --eval='foo:%{foo}'$'\n''bar:%{?bar}'
foo:%{foo}
bar:
$ rpm --define='foo foov' --eval='foo:%{foo}'$'\n''bar:%{?bar}'
foo:foov
bar:
$ rpm --define='foo foov' --define='bar barv' --eval='foo:%{foo}'$'\n''bar:%{?bar}'
foo:foov
bar:barv
除了上述的表示方法外,还可以使用如下内容:
%define _without_foobar 1
# enabled by default
%define with_foobar 0%{!?_without_foobar:1}
如果 _without_foobar
变量不存在,那么默认 with_foobar
为 01 也就是 true,否则就是 0 也就是 false;可以通过如下命令行进行测试。
$ rpm --define='with_foobar 0%{!?_without_foobar:1}' --eval='foobar:%{with_foobar}'
foobar:01
$ rpm --define='_without_foobar 1' --define='with_foobar 0%{!?_without_foobar:1}' --eval='foobar:%{with_foobar}'
foobar:0
测试脚本
如下是一个测试用的脚本,可以用来生成简单的测试 SPEC 脚本,并执行。
该脚本会将 /tmp/foobar
目录作为工作目录,然后生成一个简单的 Hello world 程序。
#!/bin/bash
WORKSPACE=/tmp/foobar
echo "0. Prepare dirs"
mkdir -pv ${WORKSPACE}/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS,Workspace/foobar-1.0.0}
echo "1. Generate tar"
cd ${WORKSPACE}/Workspace/foobar-1.0.0
cat << EOF > main.c
#include <stdio.h>
int main(int argc, char *argv)
{
printf("Hello World!!!\n");
return 0;
}
EOF
cat << "EOF" > Makefile
all: main.c
gcc -o foobar $< -Wall
install:
install -m 755 foobar ${DESTDIR}/usr/bin/foobar
EOF
cd ${WORKSPACE}/Workspace
tar -jcf foobar-1.0.0.tar.bz2 foobar-1.0.0
#[ "${WORKSPACE}/BUILD" != "/" ] && rm -rf "${WORKSPACE}/BUILD/"
rm -rf "${WORKSPACE}/SOURCES/foobar-1.0.0.tar.bz2"
mv foobar-1.0.0.tar.bz2 ${WORKSPACE}/SOURCES/
echo "2. Prepare spec-file"
cd ${WORKSPACE}
cat << EOF > SPECS/foobar-1.0.0.spec
### 0. The define section
%global _topdir ${WORKSPACE}
### 1.The introduction section
Name: foobar
Version: 1.0.0
Release: 1%{?dist}
Summary: Just a RPM example
License: GPLv2+ and BSD
BuildRequires: gcc,make
Source0: %{name}-%{version}.tar.bz2
BuildRoot: %_topdir/BUILDROOT
%description
It is a RPM example.
#--- 2.The Prep section
%prep
%setup -q
#%patch0 -p1
#--- 3.The Build Section
%build
make %{?_smp_mflags}
#echo %{_sysconfdir}
%install
#/usr/bin/
install -d -m 0751 %{buildroot}/%{_bindir}
make install DESTDIR=%{buildroot}
#--- 4.1 scripts section
%pre
echo "pre" >> /tmp/foobar
%post
echo "post" >> /tmp/foobar
%preun
echo "preun" >> /tmp/foobar
%postun
echo "postun" >> /tmp/foobar
#--- 5.clean section
%clean
rm -rf %{buildroot}
#--- 6.file section
%files
%defattr(-,root,root,-)
%attr(755, root, root) %{_bindir}/foobar
#--- 7.chagelog section
%changelog
* Fri Dec 28 2012 foobar foobar@kidding.com - 1.0.0-1
- Initial version
EOF
echo "3. Perform rpmbuild"
cd ${WORKSPACE}
rpmbuild --clean --define '_topdir /tmp/foobar' -ba SPECS/foobar-1.0.0.spec
参考示例
如上介绍了手动如何编译生成 rpm 包,这里通过 collectd 看看,如何使用一些常见的工具,更加方便的制作 rpm 包,可以参考 collectd 创建的示例 。
# yum install rpmdevtools yum-utils
----- 配置环境,生成topdir工作目录~/rpmbuild,以及配置文件~/.rpmmacros
$ rpmdev-setuptree
----- 下载collectd源码,并解压
$ mkdir ~/src && cd ~/src && curl --progress https://collectd.org/files/collectd-5.5.3.tar.gz | tar xz
其它
如下是 %if
判断的使用。
%if 0%{?rhel} == 6
%global compatver 5.1.17
BuildRequires: systemd
%else
%global compatver 5.1.17
BuildRequires: sysv
%endif
可以使用 Requires(pre)、Requires(post) 等都是针对不同阶段的依赖设置;可以通过 %package PACKAGE-NAME
设置生成不同的 RPM 分支包。
另外,可以生成 GPG 签名,在此不再赘述。
添加用户
可以在 %pre
段中添加如下内容。
/usr/sbin/groupadd -g 66 -o -r monitor >/dev/null 2>&1 || :
参数:
--gid/-g
指定group id;
--non-unique/-o
group id可以不唯一,此时相当于指定了一个 alias;
--system/-r
创建系统分组;
/usr/sbin/useradd -M %{!?el5:-N} -g monitor -o -r -d %{_libdir}/%{name} -s /bin/false \
-c "Uagent Server" -u 66 monitor >/dev/null 2>&1 || :
参数:
--no-create-home/-M
不创建HOME目录;
--no-user-group/-N
不创建相同用户名的分组;
--non-unique/-o
user id可以不唯一,此时相当于指定了一个 alias;
--system/-r
创建系统用户;
--home-dir/-d HOME_DIR
指定HOME目录,如果不存在则会创建;
--shell/-s SHELL
指定默认的shell;
Build-ID
在新版本的 Fedora 27 以及 Redhat 8 中,增加了对于 build-id 的支持,在使用 rpmbuild 时默认会自动添加,会在 /usr/lib/.build-id
目录下生成相关的文件。
可以通过 --define "_build_id_links none"
参数取消文件的生成。
增加 build-id 的目的是为了可快速找到正确的二进制文件以及 Debuginfo 。
初始宏定义
很多宏在 /etc/rpm
目录下定义,如上面的 dist
在 /etc/rpm/macros.dist
文件中定义。
RPM包查看
对于生成的 RPM 包,只能查看头部信息和脚本内容,指令分别如下。
$ rpm --info -qp XXX.rpm
$ rpm --scripts -qp XXX.rpm
变量定义
在定义 Version
时,如果使用 %{?package_version:1.0.0}
可以工作但是,使用 %{!?package_version:1.0.0}
却无效。
而且这里的参数不能通过类似 rpmbuld --define='package_version 1.9.1' foobar.spec
的方式传入。
依赖检查
如果在安装时有些环境或者平台的检查,例如只支持某些平台,那么需要添加到 %pre
段中,其退出码非 0 ;注意,如果使用 %post
不会生效。
另外,在卸载时,如果 %preun
的退出码非 0 则会导致卸载失败;与安装类似,如果使用 %postun
同样不会生效。
对于如上场景可以使用 --noscripts
忽略,该参数等价于 --nopre
--nopost
--nopreun
--nopostun
,例如。
# rpm --noscripts -ivh foobar-1.0.0-1.x86_64.rpm
# rpm --noscripts -evh foobar
如果想查看执行的是那些脚本,可以通过 rpm -q --scripts package
命令查看,当机器上有多个版本时,如果要删除所有版本可以使用 --allmatches
参数。
debuginfo
rpmbuild 默认会生成 debuginfo 包,如果要关闭,可以通过如下的几种方式设置。
----- 修改配置文件,可以是用户级别,或者是全局的/etc/rpm/macros
$ echo '%debug_package %{nil}' >> ~/.rpmmacros
$ echo '%define debug_package %{nil}' >> ~/.rpmmacros
----- 也可以在命令行中添加参数
$ rpmbuild --define "debug_package %{nil}"
注意,如果使用 %global debug_package %{nil}
定义,需要添加到 %prep
和 %setup
段之间。
注意,如果要使用 rpm 的 debuginfo 包,在编译的时候就需要添加 -g
参数,否则即使生成了 debuginfo 包,也有可能会导致没有提取到完整的 debuginfo 包,在使用 gdb 时会报如下的错误。
separate debug info file has no debug info
如果在 RedHat 中,也可能会报 Empty %files file /your/file/path/debugsourcefils.list
错误。
参考
很多关于 RPM 的介绍可以参考文档 Maximum RPM;另外,还可以参考下 MySQL 源码包中的相关示例 mysql.spec 。