rpm制作

为什么要打包? 
制作 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 里完成打补丁操作。务必做到一个补丁只解决一个问题,这样才能确保补丁的可重用性,否则版本升级后补丁很容易失效。如果你确信自己的补丁是清洁补丁,尽可能发给上游开发者,这样才能一劳永逸。你所打的任何补丁,其授权方式必须和被修改源代码保持一致。 



预备知识: 
首先我们观察一下 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 的文件,因为它使用的大量宏定义和我们不兼容,甚至 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,make install 时一般会将软件安装在 /var/tmp/软件名称-版本号-发布序列号-buildroot/usr/ 下的 bin/ 下(可执行文件)、share/ 下(数据、资源文件)、lib/ 下(动态共享库文件,即 .so 文件 <Share Object>,相当于 windows 下的 DLL 文件)等等,例如 /var/tmp/cce-0.51-1mgc-buildroot/usr/bin/cce。不过实际不一定都是这样,具体情况具体对待 
# 下面是可选的字段 
URLhttp://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: XFree86>=4.3 zlib libpng #这里罗列所有安装本包时需要先行安装的包(依赖包),通常软件会依赖这些包里的一部分文件。可以分成多行来写。如果这里不写明依赖包,打包时系统会自动把具体依赖的 .so 文件写进 rpm 包,而不会注明这些文件属于哪些软件包,这会对用户造成困惑,因为他们很难知道这些文件属于哪些软件包 
#Obsoletes: #这里列出的软件包都是“陈旧”的,当安装本包的时候,这里列出的包都会被自动卸载! 
NoAutoReq: %{_noautoreq} #这里的意思是禁止自动查找对 spec 文件头部预定义的 _noautoreq 宏里定义的两个软件包[perl(Plot) 和 perl(WidgetDemo)]的依赖关系,通常对于 prel 模块需要这样处理。因为即便你在 Requires 字段指明了本包所依赖的包含这两个模块的那些软件包,安装 本包的时候系统仍然会直接查找是否安装有这些 perl 模块,而不会查找那些包含这两个模块的软件包是否已经安装。 
#PreReq: loop #如果在 %post 字段执行的脚本需要使用一些特殊命令,例如 loop,你需要在此标明 
#Requires(post): loop #这是上面一行的另一种写法 
#Autoreq: 0 #这里使用 0 关闭了自动标注本软件包需要的依赖关系的功能,使用 1 或者不写此行(默认)就是开启自动标注依赖关系的功能。自动依赖校验只会通过 pkgconfig 找出依赖的 .so 文件,而绝对不是软件包!可以通过命令反查生成的 rpm 包所依赖的这些 .so 文件属于哪个包,再把这些依赖的包的名称写进 spec,最后重新编译就行了。 
#Autoprov: 0 #这里使用 0 关闭了自动标注本软件包提供的依赖关系的功能,使用 1 或者不写此行(默认)就是开启自动标注依赖关系的功能 
#Autoreqprov: 0 此关键字兼具上述两条的功能 
#BuildRequires: XFree86-devel>=4.3 #这是编译时依赖的包 
#Provides: lda #这里标注本软件包提供的某些特定功能。例如 sendmail 在没有本地递送代理 [local delivery agent (lda)] 时不能工作,而你的软件包恰好提供了这一功能,你需要在此标明。而在 sendmail 的 spec 里你需要写明:Requires: lda 
Packager: Tony Black <[email protected]> #这是打包者的信息 

%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 #这是 make 命令 

%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/软件名称-版本号 目录删掉。这里是编译过程生成的中间文件。 

按照我们在 /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} 

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

%postun #rpm 包卸载后执行的脚本。 
/sbin/ldconfig #这里举例,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} 

[spec 文件尾部] 

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

* Sun Oct 31 2004 Tony Black <[email protected]
- 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。 :lol: 

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

你可能感兴趣的:(rpm制作)