[整理] RPM包制作

来源:互联网 发布:潮流计算的软件 编辑:程序博客网 时间:2024/04/29 06:35
为什么要打包? 
制作 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 文件 <Share Object>,相当于 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 <tony@magiclinux.org> #这是打包者的信息 

%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 档中有打包前未发现的错误,打包过程中断是否有办法补救呢? 
打包过程中断是很常见的现象。并非没有办法补救,我们曾经用这种偷懒的方法对付需要反复编译巨大软件包的困境。但是也发现有时会造成一些难以发现,或者难以解释的错误。如果你在 make 阶段就出了错,我们建议重头来过,不建议使用这种方法,因为这是非常不可靠的。所以还是不要偷懒为好。 

如果编译已经通过,但是在分包过程中中断,单纯为了验证 rpm 的分包是否有错,可以这样做: 
1、不删除编译产生的所有文件(即 mBUILD 目录里的东西),并删除先前生成的虚拟根目录(即 /var/tmp/软件名称-版本号 目录) 
2、建立一个当前 spec 的副本,删除其中 prepare 小节以后和 install 小节以前的部分,对这个 spec 运行建包命令,即可跳过所有编译过程,直接安装、打包。 


[spec 文件尾部] 

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

* Sun Oct 31 2004 Tony Black <tony@magiclinux.org
- modify the spec file and rebuild 

* Sun Oct 03 2004 Lover <root@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=<dir> 重新指定安装位置。例如: 
软件包默认安装到 /usr 下,你希望安装到 /opt/usr 下,则使用命令: 
rpm -ivh --prefix=/opt/usr xxx.rpm 
如果你还有一些文件默认安装到 /etc 下,你需要安装到 /usr/etc 下,则要改用参数 --relocate=<old>=<new>,例如: 
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。 : 

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


RPM包rpmbuild SPEC文件深度说明


一、编写spec脚本 
    rpm建包的原理其实并不复杂,可以理解为按照标准的格式整理一些信息,包括:软件基础信息,以及安装、卸载前后执行的脚本,对源码包解压、打补丁、编译,安装路径和文件等。
实际过程中,最关键的地方,是要清楚虚拟路径的位置,以及宏的定义。 
二、关键字 

spec脚本包括很多关键字,主要有:
引用
Name: 软件包的名称,后面可使用%{name}的方式引用

Summary: 软件包的内容概要

Version: 软件的实际版本号,例如:1.0.1等,后面可使用%{version}引用

Release: 发布序列号,例如:1linuxing等,标明第几次打包,后面可使用%{release}引用

Group: 软件分组,建议使用标准分组

License: 软件授权方式,通常就是GPL

Source: 源代码包,可以带多个用Source1、Source2等源,后面也可以用%{source1}、%{source2}引用

BuildRoot: 这个是安装或编译时使用的“虚拟目录”,考虑到多用户的环境,一般定义为:
%{_tmppath}/%{name}-%{version}-%{release}-root

%{_tmppath}/%{name}-%{version}-%{release}-buildroot-%(%{__id_u} -n}
该参数非常重要,因为在生成rpm的过程中,执行make install时就会把软件安装到上述的路径中,在打包的时候,同样依赖“虚拟目录”为“根目录”进行操作。
后面可使用$RPM_BUILD_ROOT 方式引用。

URL: 软件的主页

Vendor: 发行商或打包组织的信息,例如RedFlag Co,Ltd

Disstribution: 发行版标识

Patch: 补丁源码,可使用Patch1、Patch2等标识多个补丁,使用%patch0或%{patch0}引用

Prefix: %{_prefix} 这个主要是为了解决今后安装rpm包时,并不一定把软件安装到rpm中打包的目录的情况。这样,必须在这里定义该标识,并在编写%install脚本的时候引用,才能实现rpm安装时重新指定位置的功能

Prefix: %{_sysconfdir} 这个原因和上面的一样,但由于%{_prefix}指/usr,而对于其他的文件,例如/etc下的配置文件,则需要用%{_sysconfdir}标识

Build Arch: 指编译的目标处理器架构,noarch标识不指定,但通常都是以/usr/lib/rpm/marcros中的内容为默认值

Requires: 该rpm包所依赖的软件包名称,可以用>=或<=表示大于或小于某一特定版本,例如:
libpng-devel >= 1.0.20 zlib 
※“>=”号两边需用空格隔开,而不同软件名称也用空格分开
还有例如PreReq、Requires(pre)、Requires(post)、Requires(preun)、Requires(postun)、BuildRequires等都是针对不同阶段的依赖指定
 

Provides: 指明本软件一些特定的功能,以便其他rpm识别

Packager: 打包者的信息

%description 软件的详细说明


三、spec脚本主体 
spec脚本的主体中也包括了很多关键字和描述,下面会一一列举。我会把一些特别需要留意的地方标注出来。
%prep 预处理脚本

%setup -n %{name}-%{version} 把源码包解压并放好
通常是从/usr/src/asianux/SOURCES里的包解压到/usr/src/asianux/BUILD/%{name}-%{version}中。
一般用%setup -c就可以了,但有两种情况:一就是同时编译多个源码包,二就是源码的tar包的名称与解压出来的目录不一致,此时,就需要使用-n参数指定一下了。

%patch 打补丁
通常补丁都会一起在源码tar.gz包中,或放到SOURCES目录下。一般参数为:
%patch -p1 使用前面定义的Patch补丁进行,-p1是忽略patch的第一层目录
%Patch2 -p1 -b xxx.patch 打上指定的补丁,-b是指生成备份文件

◎补充一下 
引用
%setup 不加任何选项,仅将软件包打开。 
%setup -n newdir 将软件包解压在newdir目录。 
%setup -c 解压缩之前先产生目录。 
%setup -b num 将第num个source文件解压缩。 
%setup -T 不使用default的解压缩操作。 
%setup -T -b 0 将第0个源代码文件解压缩。 
%setup -c -n newdir 指定目录名称newdir,并在此目录产生rpm套件。 
%patch 最简单的补丁方式,自动指定patch level。 
%patch 0 使用第0个补丁文件,相当于%patch ?p 0。 
%patch -s 不显示打补丁时的信息。 
%patch -T 将所有打补丁时产生的输出文件删除。


%configure 这个不是关键字,而是rpm定义的标准宏命令。意思是执行源代码的configure配置
在/usr/src/asianux/BUILD/%{name}-%{version}目录中进行 ,使用标准写法,会引用/usr/lib/rpm/marcros中定义的参数。
另一种不标准的写法是,可参考源码中的参数自定义,例如:
引用
CFLAGS="$RPM_OPT_FLAGS" CXXFLAGS="$RPM_OPT_FLAGS" ./configure --prefix=%{_prefix}

%build 开始构建包
在/usr/src/asianux/BUILD/%{name}-%{version}目录中进行make的工作 ,常见写法:
引用
make %{?_smp_mflags} OPTIMIZE="%{optflags}"

都是一些优化参数,定义在/usr/lib/rpm/marcros中

%install 开始把软件安装到虚拟的根目录中
在/usr/src/asianux/BUILD/%{name}-%{version}目录中进行make install的操作。这个很重要,因为如果这里的路径不对的话,则下面%file中寻找文件的时候就会失败。 常见内容有:
%makeinstall 这不是关键字,而是rpm定义的标准宏命令。也可以使用非标准写法:
引用
make DESTDIR=$RPM_BUILD_ROOT install


引用
make prefix=$RPM_BUILD_ROOT install

需要说明的是,这里的%install主要就是为了后面的%file服务的。所以,还可以使用常规的系统命令:
引用
install -d $RPM_BUILD_ROOT/
cp -a * $RPM_BUILD_ROOT/

%clean 清理临时文件
通常内容为:
引用
[ "$RPM_BUILD_ROOT" != "/" ] && rm -rf "$RPM_BUILD_ROOT"
rm -rf $RPM_BUILD_DIR/%{name}-%{version}

※注意区分$RPM_BUILD_ROOT和$RPM_BUILD_DIR:
$RPM_BUILD_ROOT是指开头定义的BuildRoot,而$RPM_BUILD_DIR通常就是指/usr/src/asianux/BUILD,其中,前面的才是%file需要的。
 

%pre
 rpm安装前执行的脚本

%post rpm安装后执行的脚本

%preun rpm卸载前执行的脚本

%postun rpm卸载后执行的脚本

%files 定义那些文件或目录会放入rpm中
这里会在虚拟根目录下进行,千万不要写绝对路径,而应用宏或变量表示相对路径。 如果描述为目录,表示目录中除%exclude外的所有文件。
%defattr (-,root,root) 指定包装文件的属性,分别是(mode,owner,group),-表示默认值,对文本文件是0644,可执行文件是0755

%exclude 列出不想打包到rpm中的文件
※小心,如果%exclude指定的文件不存在,也会出错的。 
%changelog 变更日志
四、范例
下面的.spec脚本是一个比较简单的范例,其作用是把一个目录中的所有文件都打包为一个rpm包。
1、前期工作 
我们假设需要打包的目录就是我们的源码文件。这样,可以暂时忽略比较麻烦的打补丁、编译等问题,而且也是一种常见的方式。 在编写.spec脚本前,需要准备好“源码”,也就是目录,内容比较简单:
引用
[root@mail html]# ll
total 4
drwxr-xr-x 3 root root 4096 Jun 4 14:45 demo
[root@mail html]# ll demo/
total 4
drwxr-xr-x 3 root root 4096 Jun 4 14:45 images
-rw-r--r-- 1 root root 0 Jun 4 14:45 index.html

因为rpm只认tar.gz格式,所以,必须打包好并移动到SOURCES目录中:
引用
[root@mail html]# tar czvf demo.tar.gz demo/
demo/
demo/images/
demo/images/logo.gif/
demo/index.html
[root@mail html]# mv demo.tar.gz /usr/src/asianux/SOURCES/

2、demo.spec的内容 
准备工作完成,下面就是范例用的脚本内容:
[root@mail html]# cd /usr/src/asianux/SPECS/
[root@mail SPECS]# cat demo.spec
引用
Summary: Test package for LinuxFly webblog
Name: suite
Version: 1.0.0
Release: 1
License: GPL
Group: System
Source: demo.tar.gz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
Url: http://www.linuxfly.org
Packager: Linuxing
Prefix: %{_prefix}
Prefix: %{_sysconfdir}
%define userpath /var/www/html/demo

%description
Just a test rpm suite.

%prep
%setup -c
%install
install -d $RPM_BUILD_ROOT%{userpath}
cp -a %{name}* $RPM_BUILD_ROOT%{userpath}

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

%files
%defattr(-,root,root)
%{userpath}

下载:
 下载文件
点击这里下载文件

※特别需要注意的是:%install部分使用的是绝对路径,而%file部分使用则是相对路径,虽然其描述的是同一个地方。千万不要写错。
五、其他 
1、扩展 
虽然上面的范例很简陋,而且缺少%build部分,但实际上只要记住两点:
a)就是%build和%install的过程中,都必须把编译和安装的文件定义到“虚拟根目录” 中。
引用
%build 
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/

b)就是%file中必须明白,用的是相对目录

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

如果把

引用
%files 
%defattr(-,root,root) 
%{_bindir}

写成

引用
%files 
%defattr(-,root,root) 
/usr/bin

则打包的会是根目录下的/usr/bin中所有的文件。
2、一些rpm相关信息 
rpm软件包系统的标准分组:/usr/share/doc/rpm-4.3.3/GROUPS
各种宏定义: /usr/lib/rpm/macros
已经安装的rpm包数据库: /var/lib/rpm
如果要避免生成debuginfo包:这个是默认会生成的rpm包。则可以使用下面的命令:

echo '%debug_package %{nil}' >> ~/.rpmmacros

如果rpm包已经做好,但在安装的时候想修改默认路径,则可以:

rpm -ivh --prefix=/opt/usr xxx.rpm

又或者同时修改多个路径:

rpm xxx.rpm --relocate=/usr=/opt/usr --relocate=/etc=/usr/etc

3、制作补丁 
详细看参考:
 [原]使用diff同patch工具 
4、如何编写%file 
由于必须在%file中包括所有套件中的文件,所以,我们需要清楚编译完的套件到底包括那些文件?
常见的做法是,人工模拟一次编译的过程:

./configrue --prefix=/usr/local/xxx
make
make DESTDIR=/usr/local/xxx install

make prefix=/usr/local/xxx install

这样,整个套件的内容就会被放到/usr/local/xxx中,可根据情况编写%file和%exclude段。
※当然,这个只能对源码按GNU方式编写,并使用GNU autotool创建的包有效,若自定义Makefile则不能一概而论。 
5、关于rpm中的执行脚本 
如果正在制作的rpm包是准备作为放到系统安装光盘中的话,则需要考虑rpm中定义的脚本是否有问题。由于系统在安装的时候只是依赖于一个小环境进行,而该环境与实际安装完的环境有很大的区别,所以,大部分的脚本在该安装环境中都是无法生效,甚至会带来麻烦的。
所以,对于这样的,需要放到安装光盘中的套件,不加入执行脚本是较佳的方法。
另外,为提供操作中可参考的信息,rpm还提供了一种信号机制:不同的操作会返回不同的信息,并放到默认变量$1中。

引用
0代表卸载、1代表安装、2代表升级

可这样使用:

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


六、参考文献: 
1. http://www-900.ibm.com/developerWorks/cn/linux/management/package/rpm/part1/index.shtml[/url] 
2. http://www-900.ibm.com/developerWorks/cn/linux/management/package/rpm/part2/index.shtml 
3. http://www-900.ibm.com/developerWorks/cn/linux/management/package/rpm/part3/index.shtml 
4. /usr/share/doc/rpm-4.3.2/ 
5. http://www.rpm.org/RPM-HOWTO/build.html#SCRIPTS 
6. http://www.linuxfans.org/nuke/modules.php?name=Forums&file=printview&t=86980&start=0


原创粉丝点击