rpm建包原理
2009-11-22 18:08:48 阿炯

为什么要打包?
制作rpm 不仅仅是打包,更可以解决菜单创建、打补丁、完成大量预配置、与其他软件包互动等操作。使用源代码安装要求用户了解基本的编译过程、能够应付各种不能编译的意外、必须自己完成抽象的配置、甚至懂得软件开发,能够自己打补钉,……对非计算机专业的用户而言简直就是天方夜谭。这是把软件开发的最后一步抛给了用户,作为发行版,这是极不负责任的!也是不现实的,除非用 lfs,但那是一本菜谱,不是真正的发行版。

软件作者发布源代码是正确的,负责的作者一般都同时提供 rpm 和 deb 包以及它们的源代码包。除非他们不会制作。愿意使用什么,那是个人的自由,但对外就不能只想到自己。GNU 精神是一种公益精神,没有奉献精神,在自由软件领域是要遭唾弃的!

直接使用其他发行版的rpm 常常是不行的。不知道大家看没看“恼人的依赖关系”这个帖子。可以在技术支持区搜索一下。任何两个发行版本在二进制上都是不能兼容的!他们实际是不同的操作系统。不仅使用的库文件不同,配置也迥异。特别是同一个发行版的不同版本更不兼容。任何包都必须在本地重新编译,而且不一定通得过,因为还有 spec 宏的兼容问题!如果要在别的发行版上使用,必须用源码编译,这是常识。考虑文件布局和配置问题,有时直接编译也是不够的,必须修改 spec,甚至自己打补丁。

如何创建rpm包?
rpm 建包原理其实不复杂。写 spec 相当于一种脚本编程,主要是在 spec 里提供一些软件相关信息,以及安装、卸载前后要执行的脚本,然后展开压缩的源代码包,打上补丁,执行编译,然后利用 make install 可以重新指定安装目的地的特性,把编译好的文件安装到指定的虚拟根目录下的指定位置里,一般是虚拟的 /usr 里,然后把这些目录、信息和脚本打成一个压缩包,即 rpm 包。同时可选地生成源码包 src.rpm。当然有很多具体细节问题。您应该首先阅读软件的 readme 和 INSTALL 文件。

打包原则
1. 任何人都应该在系统现有 src.rpm 的 spec 基础上修改更新,除非没有这样的包。这可以省去很多麻烦,少走弯路。
2. 任何人都无权删除别人的 changelog 和原始打包者的信息,这是对别人的不尊重。但你可以追加自己的信息。
3. 尽可能在 spec 里使用系统的标准宏定义,而不要用非标准写法。
4. 任何人都不应该直接提供修改后的源代码,而应该以补丁形式发布你的修改,在 spec 里完成打补丁操作。务必做到一个补丁只解决一个问题,这样才能确保补丁的可重用性,否则版本升级后补丁很容易失效。如果你确信自己的补丁是清洁补丁,尽可能发给上游开发者,这样才能一劳永逸。你所打的任何补丁,其授权方式必须和被修改源代码保持一致。
5. rpm 不是跨平台打包技术。确保软件的二进制代码能够跨平台运行,不是系统软件打包者要考虑的事,而是应用软件作者和独立二进制代码发布者应该考虑的事。我们没有责任和义务确保从我们系统里拆解下来的部件能够运行于其他系统上,不支持,更不提倡这种移花接木的作法。

试图解决跨平台问题的打包技术,我印象中有 autopackage 和 klik 技术,参见:
http://autopackage.org
http://klik.atekon.de

预备知识:
首先我们观察一下 rpm 文件名的特点。一个 rpm 包文件名通常由 5 段组成:
%{name}-%{version}-%{release}.ix86.rpm
cutedict-1.1-1mgc.i686.rpm
这里 %{name}=cutedict,%{version}=1.1,%{release}=1mgc,ix86=i686,如果是为 athlon 芯片家族编译的包,这里就是 athlon,依此类推。

注意:
下面是一个spec 模板。
1. 凡是行首加上 # 的都被注释掉了,实际运行时不起作用,如要使其生效,请去掉注释符 #。
2. 凡是以 %{***} 和 %*** 形式出现的符号都是“宏”,很多宏都是系统预定义的〔注2〕,也可以是自己定义的。
3. 下面的黑体字是 spec 文件的关键字,不能写错。
4. 有不明白的地方可以参见跟帖里的参考文献。
5. 如果软件没有使用 GNU autotool 创建,而是自己写的 Makefile,这就导致不能按照常规方法打包,非常麻烦。
6. 服务器软件通常都需要大量预配置工作,spec 打包绝非一两天能解决,需要深入研究很多东西和反复实践,建议初学者不要尝试。
7. 其他发行版的 spec 与 src.rpm 是很好的教材,建议打包前先找找 Fedora 或 SuSE 的文件学习,能借鉴最好,但不应该不修改照搬过来或使用 Mandrake/Mandriva 的文件,因为它使用的大量专有宏定义和我们不兼容,甚至 src.rpm 直接编译都通不过。

------------------------------------------------------------------
[spec 文件头部]
# Initial spec file created by autospec #这里是一些注释
%define _noautoreq perl(Plot) perl(WidgetDemo) #这里用 %define 自定义一个系统里没有的叫做 _noautoreq 的宏,后面可以用 %{_noautoreq} 引用。
Name: software #这是软件包名称,后面可以用 %{name} 引用
Summary: a software #这是软件包的内容提要
#Summary(zh_CN): #这里写入中文内容提要(如果有必要。不建议使用,因为如果系统里的默认编码与此处不符,会导致显示乱码。例如:我们使用 GBK,如果这里的中文是 UTF-8 编码,在 kpackage 里就会显示乱码,但是 synaptic 可能能够正确显示,但需要把 zh_CN 改为 zh_CN.UTF-8 )
Version: number #这是软件的实际版本号,例如 2.1.6、2.2final 等,后面可以用 %{version} 引用
Release: number #发布序列号,我们用 1mgc、2mgc 等等,标明这是第几次打包。如果软件本身是预览版,比如 pre6 版,则写成 pre6_1mgc、pre6_2mgc,后面可以用 %{release} 引用
Group: Applications/Multimedia #这是软件分组,建议使用标准分组,参见下面:〔注1〕
#Group(zh_CN): #中文软件分组(如果有必要)
License: GPL #这是软件授权方式,通常是 GPL
Source: %{name}-%{version}.tar.gz #这是源代码(通常是压缩包),可以带有完整的网址,可以用正整数标识多个源 Source0 Source4 Source5 Source100,数字不必连续排列,后面可以用 %{SOURCE0}、%{SOURCE4}、%{SOURCE5}、%{SOURCE100} 引用。例如 http://www.example.net/src/%{name}-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot #这是 make install 时使用的“虚拟根目录”也称为“构建根目录”,通常是 /var/tmp/软件名称-版本号-发布序列号-buildroot。对于服务器环境,可能同时有多人操作,为了确保编译软件时临时目录不会相互覆盖,还需要加上当前用户的标识:BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot-%(%{__id_u} -n)
make install 时一般会将软件安装在 /var/tmp/软件名称-版本号-发布序列号-buildroot/usr/ 下的 bin/ 下(可执行文件)、share/ 下(数据、资源文件)、lib/ 下(动态共享库文件,即 .so 文件 ,相当于 windows 下的 DLL 文件)等等,例如 /var/tmp/cce-0.51-1mgc-buildroot/usr/bin/cce。不过实际不一定都是这样,具体情况具体对待。

# 下面是可选的字段
URL: http://www.example.net/ #这是软件主页地址
#Vendor: Red Hat Co.,ltd. #这是发行商或者打包组织的信息,我们统一写成 MGC Group,不过这一行可以省略,把它写入 /usr/lib/rpm/macros 标准宏定义文件里,该文件的 Vendor 这行定义是空的,而且通常前面用 # 注释掉了
#Distribution: Red Hat Linux #这是软件针对的发行版标识,我们是 Magic Linux
Patch: src-%{version}.patch #这是补丁源代码(可以是压缩包),可以用正整数标识多个源 Patch1 Patch4 Patch6,数字不必连续排列。
Prefix: %{_prefix} #指定 make install 时在虚拟根目录里的安装位置,通常用标准的 %{_prefix} 宏,它代表 /usr。这里指定后,用户可以在 rpm 安装阶段重新指定安装到其他位置,如 /opt,否则就不能改变安装位置。
Prefix: %{_sysconfdir} #如果软件有些文件并非都安装到 %{_prefix},那么你需要指明其他位置。例如你需要把一个配置文件放到 /etc 下面,这显然不在 /usr 下面,此时你需要前方这种写法。%{_sysconfdir} 宏代表 /etc。这里指定后,用户可以在 rpm 安装阶段重新指定这些文件安装到其他位置,如 /opt/etc,否则就不能改变安装位置。〔注3〕
#BuildArch: noarch #编译目标处理器架构,就是今后软件运行时的 CPU 类型。noarch 是不指定架构,通常标准默认是 i386,定义在了系统的标准宏定义文件 /usr/lib/rpm/macros 里 〔注2〕。实际编译时可以在 rpmbuild 命令行用 --target=i686 参数,spec 里通常不写这行。
#Requires: libpng-devel >= 1.0.20 zlib libpng #这里罗列所有安装本包时需要先行安装的包(依赖包),通常软件会依赖这些包里的一部分文件。可以分成多行来写。如果这里不写明依赖包,打包时系统仅仅会自动把具体依赖的 .so 文件写进 rpm 包,而不会注明这些文件属于哪些软件包,这会对用户造成困惑,因为他们很难知道这些文件属于哪些软件包。注意:如果使用 >= 这样的符号,务必在其两边各保留一个空格
#Obsoletes: #这里列出的软件包都是“陈旧”的,当升级安装本包的时候,这里列出的包都会被自动卸载!
NoAutoReq: %{_noautoreq} #这里的意思是禁止自动查找对 spec 文件头部预定义的 _noautoreq 宏里定义的两个软件包[perl(Plot) 和 perl(WidgetDemo)]的依赖关系,通常对于 prel 模块需要这样处理。因为即便你在 Requires 字段指明了本包所依赖的包含这两个模块的那些软件包,安装本包的时候系统仍然会直接查找是否安装有这些 perl 模块,而不会查找那些包含这两个模块的软件包是否已经安装。
#PreReq: loop #如果在 %pre 字段执行的脚本需要使用一些特殊命令,例如 loop,你需要在此标明
#Requires(post): loop #这是上面一行的另一种写法,依此类推,还有其他几个类似的关键字:
#Requires(pre)
#Requires(preun)
#Requires(postun)
#Autoreq: 0 #这里使用 0 关闭了自动标注本软件包需要的依赖关系的功能,使用 1 或者不写此行(默认)就是开启自动标注依赖关系的功能。自动依赖校验只会通过 pkgconfig 找出依赖的 .so 文件,而绝对不是软件包!可以通过命令反查生成的 rpm 包所依赖的这些 .so 文件属于哪个包,再把这些依赖的包的名称写进 spec,最后重新编译就行了。
#Autoprov: 0 #这里使用 0 关闭了自动标注本软件包提供的依赖关系的功能,使用 1 或者不写此行(默认)就是开启自动标注依赖关系的功能
#Autoreqprov: 0 此关键字兼具上述两条的功能
#BuildRequires: libpng-devel >= 1.0.20 #这是编译时依赖的包
#Provides: lda #这里标注本软件包提供的某些特定功能。例如 sendmail 在没有本地递送代理 [local delivery agent (lda)] 时不能工作,而你的软件包恰好提供了这一功能,你需要在此标明。而在 sendmail 的 spec 里你需要写明:Requires: lda
Packager: Tony Black #这是打包者的信息

%description 软件的详细说明
This is the description...

#%description -l zh_CN 中文软件说明(如果有必要。不建议使用,因为如果系统里的默认编码与此处不符,会导致显示乱码。例如:我们使用 GBK,如果这里的中文是 UTF-8 编码,在 kpackage 里就会显示乱码,但是 synaptic 可能能够正确显示,但需要把 zh_CN 改为 zh_CN.UTF-8 )

[spec 文件体部]
%prep #下面开始预处理

%setup -n %{name}-%{version} #到这里,系统把源码包从 /usr/src/mBuild/SOURCES 解压缩到 /usr/src/mBuild/BUILD/%{name}-%{version} ,并转到那里展开的压缩包目录(%{name}-%{version} )里,以便完成打补丁等准备工作,最后还要退回到 /usr/src/mBuild/BUILD 目录下。-n 后面指定的参数代表 tar 包展开后生成的目录名,一般 -n %{name}-%{version} 是不需要的,除非 tar 包名称和展开后生成的目录名不符,或者你要处理多个 tar 包。如果打包时报告找不到 ./configure 说明 -n 参数指定的目录不对,或者软件源代码目录里没有 configure 脚本 (比如你的代码是从 cvs 里 commit out 出来的,你需要进行一些准备工作,比如通过运行 autogen.sh 脚本来自动创建 configure 脚本。这具体看是哪个软件,不同软件有不同的方法)。如果有多个源代码包要编译,用“-n 名称”指定多个 setup 字段。

%patch #这里开始打补丁。例如 %patch0 -p1,%Patch2 -p1 -b .xxx.patch 这里 %patch0 是对第一个补丁的引用,%Patch2 -p1 -b .xxx.patch 表明第二个补丁是压缩包,要先解压缩,再打补丁。 -p1 是 patch 命令的常用参数,代表忽略 patch 时的第一(顶)层目录。(为什么?因为创建补丁时两个比照的目录或者文件名肯定是不同的。参见〔注5〕)

%configure #系统重新进入 /usr/src/mBuild/BUILD/%{name}-%{version} 执行 configure 脚本进行配置,然后返回 /usr/src/mBuild/BUILD 目录下。%configure 是 rpm 定义的标准配置宏,含义很复杂,但绝对标准。具体含义参见 〔注2〕。非标准写法:
CFLAGS="$RPM_OPT_FLAGS" \
CXXFLAGS="$RPM_OPT_FLAGS" \
./configure --prefix=%{_prefix}
或者:
CFLAGS="$RPM_OPT_FLAGS" CXXFLAGS="$RPM_OPT_FLAGS" ./configure --prefix=%{_prefix}

%build #开始构建包。系统重新进入 /usr/src/mBuild/BUILD/%{name}-%{version} 执行 make,然后返回 /usr/src/mBuild/BUILD 目录下。
make %{?_smp_mflags} OPTIMIZE="%{optflags}" #这是 make 命令,其两个参数的含意是:
%{?_smp_mflags} 如果系统里定义了make 的并行编译参数,则使用这个参数。例如: -j2 表示 make 同时执行两个文件的编译操作。如果你使用多个 CPU 或者非单核 CPU,这个参数可以明显提高编译速度,但是这里指定的数字不宜超过你的 CPU 内核数量+1。
OPTIMIZE="%{optflags}" 如果系统里定义了 gcc 的优化参数,则在软件默认优化参数的基础上追加使用这里指定的优化参数。例如: -O2 -g -pipe 表示使用 gcc 第二优化级、为调试工具 GDB 提供额外的支持信息、使用管道而不是临时文件以便加快编译速度。
这两个参数具体定义在:/usr/lib/rpm/mBuild/macros 里。

%install #下面开始将编译好的软件安装到虚拟的根目录。系统重新进入 /usr/src/mBuild/BUILD/%{name}-%{version} 执行 make install,然后返回 /usr/src/mBuild/BUILD 目录下。
rm -rf $RPM_BUILD_ROOT #先清理安装目的地——虚拟根目录

%makeinstall #这是 rpm 定义的标准的安装宏,含义很复杂,但绝对标准。具体含义参见 〔注2〕。
非标准写法: make DESTDIR=$RPM_BUILD_ROOT install 或者 make prefix=$RPM_BUILD_ROOT install (这行原先写错了,非常抱歉)

%clean #清扫战场,打包完成后删掉编译过程产生的中间文件、目录
[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf "$RPM_BUILD_ROOT" #如果虚拟根目录不是真正的 / 目录,就删除掉。这里将软件打包时安装到的虚拟根目录删掉。通常直接用 rm -rf $RPM_BUILD_ROOT 就很安全。
rm -rf $RPM_BUILD_DIR/%{name}-%{version} #将 /usr/src/mBuild/BUILD/软件名称-版本号 目录删掉。这里是编译过程生成的中间文件。注意:这里的 %{name}-%{version} 必须和 %setup -n %{name}-%{version} 指定的相应内容保持一致。

按照我们在 /usr/lib/rpm/macros 里的定义,通常
%clean
rm -rf $RPM_BUILD_ROOT
rm -rf $RPM_BUILD_DIR/%{name}-%{version}
也可以写成
%clean
%{__rm} -rf %{buildroot}
%{__rm} -rf %{_builddir}/%{name}-%{version}

%pre #rpm 包安装前执行的脚本。

%post #rpm 包安装后执行的脚本。
/sbin/ldconfig #这里举例,ldconfig 用于安装库文件后进行“注册”,这么说不准确,但好理解。这里可以是任何 sh 脚本。

%preun #rpm 包卸载前执行的脚本。
%postun #rpm 包卸载后执行的脚本。
if [ "$1" = "0" ]; then
/sbin/ldconfig
fi
或者写作:
if [ $1 -eq 0 ]; then
/sbin/ldconfig
fi

★★★
如果您的包作为系统包的一部分,需要通过发行版本的安装程序安装的话,不要指望能使用内嵌脚本执行任何重要操作,包括创建任何文件。因为安装程序的运行环境不可能完全等同于安装后实际运行时的系统环境,某些操作是不能正确执行的。一般,安装程序在安装后期会执行一些补偿操作,来完成诸如库文件和内核模块注册、初始环境设置等操作,所以你不必担心库文件安装不正确。
具体举例来说:最常见的 %post 小节的脚本在系统初始化安装阶段就很可能得不到正确执行。因为安装程序所处的小 linux 系统环境不同于安装后的真实系统,而此时的真实系统还不完整,即便立即 chroot 切换入真实系统也未必能正确执行,况且安装一个 rpm 时其内嵌脚本不可能等你切换入真实系统才执行。所以除了 ldconfig 之类的命令外,尽可能不要使用内嵌脚本,特别是不要创建任何文件,比如配置文件或者 desktop 文件,所有这些都必须静态创建好。您可以在 install 字段完成这些创建工作,也可以事先创建好,并把它们作为 source 的一部分。
既然内嵌脚本在系统初始化安装阶段都是不可靠的,那么上文提到的 ldconfig 又有什么作用呢?原来,通常安装程序都会在安装包结束后,自动创建 /etc/ld.so.conf,并且执行一次 ldconfig,从而完成对所有库文件的注册。如果这个包是在系统安装后额外安装的,那么所有的内嵌脚本都应该被这个真实的系统正确执行,此时的 ldconfig 就会被正确执行。

★★★
我们可以看到,在 postun 小节定义的脚本里多出来一个 if 判断语句,这事干什么用的呢?这里的 $1 是什么意思呢?原来 rpm 相当强大,以其包升级操作为例,它会这样执行:
新包的 pre 脚本
安装文件
新包的 post 脚本
旧包的 preun 脚本
删除安装过程没有覆盖的全部文件,但不包括重要配置文件
旧包的 postun 脚本
如果有“触发”脚本,实际操作会更复杂。
通常在 postun 脚本里我们执行的都是一些清扫垃圾的操作,比如删除程序额外创建的临时文件、配置文件(我们建议通过交互式方式执行此操作,询问用户是否删除程序运行时创建的配置文件,因为有些配置文件用户未必想要删除)。卸载软件包没问题,但是升级操作就可能造成灾难性后果:刚刚安装好的软件包被删掉了一部分文件。为了避免这样的局面,rpm 提供了一种信号机制,不同操作会返回不同信号,并且把它存放到默认变量 $1 当中:0 代表卸载、1 代表安装、2 代表升级。这样我们就可以通过判断 $1 的值来决定怎样执行脚本。上面的脚本就表示:仅当执行卸载操作的时候才执行 /sbin/ldconfig 命令。

★★★
在 rpm 内嵌脚本里的重要命令需要使用完整的或称绝对路径,例如 /sbin/ldconfig 等,这是为了确保正确的命令被执行。由于用户自己可能重新定义了 PATH 环境变量,导致其他位置上的 ldconfig 可执行程序的搜索路径先于 /sbin/ldconfig,可能产生难以预料的后果。

%files #本节定义哪些文件、目录将被打进 rpm 包里。如果你认为哪些文件不必打进 rpm 包里(一般是 debug、src 和 devel 文件、目录),你就不要列在这里,或者使用 %exclude 关键字指明哪些文件不打进 rpm 包里。你甚至可以在 spec 文件的其他字段安装或者删除一些特定的文件,这就是比较复杂的技术了。但是如何才能知道到底软件向系统内安装了哪些目录和文件呢?这个问题有点复杂。参见〔注4〕。注意:此处系统的当前路径指的就是虚拟的根目录。所以虚拟的根目录路径(例如 /var/tmp/cce-0.51-1mgc-root/)不要写在这里。应该直接用类似 %{_bindir} 的宏(表示 /usr/bin ) 来指定包含的目录,也可以单独指定一个或一组文件。
%defattr(-,root,root) 指定包装文件的属性,代表(mode, owner, group) 即文件属性、文件属主、用户群组,- 代表属性为默认值,对文本文件是八进制数0644,可执行文件是 0755。下面指定具体哪些目录或文件将被打进包里,很多宏的定义要看你的具体系统〔注2〕
#下面具体指定打进 rpm 包的文件、目录,例如:
%dir %{_datadir}/tst/
%dir %{_datadir}/tst/plugin/
%{_bindir}/tst
%{_datadir}/tst/plugin/libtest.so
"/usr/share/tst/plugin/*.png"
%{_datadir}/tst/plugin/test.plugin
%config %{_datadir}/tst/tst.conf

%exclude /usr/src #如果上面列出的目录里包含一些你不想要的东西,比如源代码(src),你可以在此将他们“抠”出去。这里指定具体哪些目录或文件将被排除在包外,即不打进包,一般是 debug、src 和 devel 文件、目录。

%files devel #这里分出 devel 包,主要包含与软件开发相关的头文件与库文件包。
%defattr(-,root,root)
%{_includedir}

代码:
这是 %files 小节的最简单写法:
%files
%defattr(-,root,root)
%{_sysconfdir}    #如果您提供了位于 /etc 的设置文件,需要这行
%{_prefix}    #将安装目标目录里的所有东西都打进 rpm 包,除了 %exclude 列出的内容
%exclude %{_prefix}/*/debug*    #除掉所有的 debug 调试文件*
%exclude %{_prefix}/src    #除掉所有的源代码文件*
*注意:如果没有这样的文件、目录,则打包过程会出错,只要在 %exclude 前方加上 # 注释掉这行就行了。

[spec 文件尾部]
%changelog #下面是标准变更日志,日期一定不能写错,只能是英文格式。

* Sun Oct 31 2004 Tony Black
- modify the spec file and rebuild

* Sun Oct 03 2004 Lover
- initial spec file created by autospec ver. 0.8 with rpm 3 compatibility

-----------------------------------------------------------------------
把源代码压缩包、补丁等等放到 /usr/src/mBuild/SOURCES 目录里,把 spec 文件放到 /usr/src/mBuild/SPECS 目录里,在 SPECS 目录里以 root 身份执行:

rpmbuild -ba --target=i686 xxx.spec

即可在 /usr/src/mBuild/RPMS/i686 里生成 rpm 包,一般还会有 debug info 包,对普通用户基本没什么用。在 /usr/src/mBuild/SRPMS 里则生成 src.rpm 包。

如果只想生成二进制包,使用下面命令:

rpmbuild -bb --target=i686 xxx.spec

如果只想生成源代码包,使用下面命令:
rpmbuild -bs --target=i686 xxx.spec

注1:
rpm 软件包系统标准分组在这里:
/usr/share/doc/rpm-4.3.2/GROUPS

大致内容如下:
Amusements/Games
Applications/Archiving
Applications/Communications
Applications/Databases
Applications/Editors
Applications/Emulators
Applications/Engineering
Applications/File
Applications/Graphics
Applications/Internet
Applications/Multimedia
Applications/Productivity
Applications/Publishing
Applications/System
Applications/Text
Development/Debuggers
Development/Languages
Development/Libraries
Development/System
Development/Tools
Documentation
System Environment/Base
System Environment/Daemons
System Environment/Kernel
System Environment/Libraries
System Environment/Shells
User Interface/Desktops
User Interface/X
User Interface/X Hardware Support

注2:
各种宏定义在系统这里:
/usr/lib/rpm/macros

通常我们要对其适当优化一下,修改如下:
%vendor MGC Group
%optflags -O2 -g -pipe
%_arch i686 这里相当于 rpmbuild 的参数 --target=i686 指将来运行软件包时的环境
%_build_arch i686 这里相当于 rpmbuild 的参数 --build=i686 指建包时的环境(你的机器),这可以比默认的 i386 快一些。

常见宏定义(左侧是宏名,右侧是相应的定义):
%_prefix /usr
%_exec_prefix %{_prefix} #展开后是 /usr
%_bindir %{_exec_prefix}/bin #展开后是 /usr/bin
%_sbindir %{_exec_prefix}/sbin #展开后是 /usr/sbin
%_libexecdir %{_exec_prefix}/libexec #展开后是 /usr/libexec
%_datadir %{_prefix}/share #展开后是 /usr/share
%_sysconfdir %{_prefix}/etc #展开后是 /usr/etc 但是在 magic linux 里 %_sysconfdir 代表的是 /etc,这是由另一个被发行版特殊定制的文件决定的!
%_sharedstatedir %{_prefix}/com #展开后是 /usr/com
%_localstatedir %{_prefix}/var #展开后是 /usr/var
%_libdir %{_exec_prefix}/lib #展开后是 /usr/lib
%_includedir %{_prefix}/include #展开后是 /usr/include
%_infodir %{_prefix}/info #展开后是 /usr/info
%_mandir %{_prefix}/man #展开后是 /usr/man 在 magic linux 里 %_mandir 代表的是 /usr/share/man

***注意***
仅当你使用标准配置宏 %configure 的时候,文件才会被指定到上述标准位置上。否则,如果你在 %file 字段使用这些标准宏就可能出错,系统可能报告找不到这些文件,因为它们可能默认安装到了别处。
*********

已安装的 RPM 包数据库在这里:
/var/lib/rpm/

注3:
软件包安装时用参数 --prefix=
重新指定安装位置。例如:
软件包默认安装到 /usr 下,你希望安装到 /opt/usr 下,则使用命令:
rpm -ivh --prefix=/opt/usr xxx.rpm
如果你还有一些文件默认安装到 /etc 下,你需要安装到 /usr/etc 下,则要改用参数 --relocate==,例如:
rpm xxx.rpm --relocate=/usr=/opt/usr --relocate=/etc=/usr/etc

如何知道 rpm 软件包到底向系统什么位置安装了什么文件呢?,你可以使用下面的命令查询:
rpm -qpl xxx.rpm

注 4:任何没有被列在 %files 字段的目录或文件都不会被自动打进 rpm 包里。反之如果你在任何 %files 字段指定了虚拟根目录里并不存在的东西,系统就会报错,包括用 %exclude 排除的东西也是这样。通常我们只需要在 %files 字段指定所有顶层目录就可以了。若要了解软件到底向系统内安装了哪些目录和文件,你可以采取下列办法:

1. 在 %files 字段内只写进 %{_prefix}:
%files
%defattr(-,root,root)
%{_prefix}
这样所有东西都将被打进 rpm 包。打好包之后,用如下命令查询生成的 rpm 包的目录结构:
rpm -qpl xxx.rpm

2. 打包前手工执行配置、安装,当 ./configure 执行后,重定向安装到一个虚拟根目录里。例如(注意大小写):
./configure
make (这步可以省略,不信就试试)
make DESTDIR=/var/tmp/xxx install 或者 make prefix=/var/tmp/xxx install
然后进入 /var/tmp/xxx 目录查看里面的目录结构:
cd /var/tmp/xxx
tree

3. 打包前手工执行配置、安装,当 ./configure 执行后,查看生成的 Makefile 的 install 字段。注意:如果软件不符合 GNU 规范,可能并没有提供 configure 脚本,而是直接提供了 Makefile。这些通常都是游戏软件。这比较复杂,如果你不懂编程,可能看不懂 Makefile。 :lol:

注5:
补丁通常是这样创建的:
diff -Nur directory.old directory.new > xxx.patch
directory.old 代表旧源代码目录,directory.new 代表修改过的新源代码目录。

这里有一个由压缩包直接创建 rpm/srpm 包的工具,但是它无法知道那些文件、目录应该被打进包里,你需要指定:
http://sourceforge.net/project/show...release_id=7939

下面是一个实际例子,更多范例可以浏览这里:
http://www.magiclinux.org/people/kde/magic/specs/
--------------------------------------------------------------------
Summary: KDE graphics package
Name: kdegraphics
Version: 3.3.0
Release: 2mgc
Copyright: GPL
URL: http://www.kde.org
Group: desktop/kde
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
Source: %{name}-%{version}.tar.bz2
Prefix: %{_prefix}
Requires: qt, arts, kdelibs, kdebase, liblcms, fribidi
Packager: kanker

%description
Graphic applications for the K Desktop Environment. provide some useful tools like ksnapshot,kpaint and so on.

%package devel
Summary: Development files for kdegraphics
Requires: %{name}
Group: Development/Libraries
Provides: kviewshell-devel
Provides: libkscan-devel

%description devel
Graphic applications for the K Desktop Environment. This package contains header files for developing applications using kdegraphics.

%prep

%setup -q -n %{name}-%{version}
%Build
rm -rf kcoloredit
rm -rf kpdf
rm -rf kruler
rm -rf kgamma
rm -rf kuickshow
rm -rf kview
make -f admin/Makefile.common cvs
./configure --prefix=%{_prefix} --enable-final --disable-debug \
--with-extra-includes=%{_includedir}/freetype2 --includedir=%{_includedir}

make
%install
rm -fr $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install
cp -r $RPM_BUILD_ROOT%{_datadir}/apps/kolourpaint/icons/hicolor/* $RPM_BUILD_ROOT%{_datadir}/icons/crystalsvg/

%clean
rm -rf $RPM_BUILD_ROOT
rm -rf $RPM_BUILD_DIR/%{name}-%{version}

%files
%defattr(-,root,root)
%{_bindir}
%{_libdir}
%{_datadir}
%exclude %{_libdir}/debug

%files devel
%defattr(-,root,root)
%{_includedir}

%changelog
* Sun Oct 3 2004 kde
- fix the spec file and rebuild

* Sat Oct 2 2004 kanker
- initialize the first spec file

参考文献:
1. http://www.rpm.org/RPM-HOWTO/build.html#SCRIPTS

-----------------------------------------------------------------------------
Building RPM packages from tar.gz archives
With working rpmbuild -ta

If you are runnig a system with RPM support, the preferred way to install rsync on your system will be the creation of a nice RPM package. First you should get the current source of rsync, either as a tar.gz archive from the rsync download page or check out an up-to-date copy via SVN (like described in the chapter about SVN) and read the next chapter.
If you have downloaded a tar.gz archives of rsync, create and install the RPMs just by doing the following steps (where [arch] stands for the platform you have built the package and might be something like i386, i586, sparc or whatever, XXX stands for the version number you have downloaded).

To build the rsync package and install it do:
% rpmbuild -ta rsync-XXX.tar.gz
% rpm -i /usr/src/redhat/RPMS/[arch]/rsync-XXX.[arch].rpm

Note
Note for SuSE users: you have to specify the directory /usr/src/packages instead of /usr/src/redhat !

If you haven't seen any errors, then that's it and you can skip the rest of this chapter. If rpm was unable to build the packages and says something like "spec file not found", then go on and read the rest of this section.
With broken rpmbuild -ta support

If you can't get rpmbuild -ta working, here are the steps for making that manually (the hard way):
1.Go to your RPM "topdir". This normally is /usr/src/redhat for the redhat distribution or /usr/src/packages if you have the SuSE distribution.
% cd /usr/src/redhat

2.Put the tar.gz archive into the SOURCES subdirectory (you have to replace "somewhere" with the real directory where the files are, of course).
% cp /somewhere/rsync-XXX.tar.gz SOURCES

3.Extract the spec file from the archives and put it into the SPEC subdirectory.
% tar -xOzf SOURCES/rsync-XXX.tar.gz \*.spec > SPECS/rsync.spec

4.Let rpm do the compile job and generate the rpm of rsync. If you only want to make a binary package, you can specify -bb instead of -ba, or just -bs to build only a source package.
% rpmbuild -ba SPECS/rsync.spec
%rpmbuild -ba --target=i686 SPECS/rsync.spec    (根据架构编译包)

5.If everything was ok, you can install the binary rpm of rsync, it will be in the BUILD directory. If you already have a version of rsync installed, please remove it first or use the parameter -U instead of -i for upgrading instead of installing.
% rpm -ivh BUILD/[arch]/rsync-XXX.[arch].rpm

---------------------------------------------------------------------

该文章最后由 阿炯 于 2012-05-07 10:42:47 更新,目前是第 3 版。