QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 56720|回复: 96

rpm 建包原理(2010.11.22 更新)

[复制链接]
发表于 2004-10-3 22:46:15 | 显示全部楼层 |阅读模式
为什么要打包?
    制作 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 包,而不会注明这些文件属于哪些软件包,这会对用户造成困惑,因为他们很难知道这些文件属于哪些软件包。注意:如果使用 >= 这样的符号,务必在其两边各保留一个空格 [注6]
#Obsoletes: #这里列出的软件包都是“陈旧”的,当升级安装本包的时候,这里列出的包都会被自动卸载!
NoAutoReq: %{_noautoreq} #这里的意思是禁止自动查找对 spec 文件头部预定义的 _noautoreq 宏里定义的两个软件包[perl(Plot)  和 perl(WidgetDemo)]的依赖关系,通常对于 prel 模块需要这样处理。因为即便你在 Requires 字段指明了本包所依赖的包含这两个模块的那些软件包,安装 本包的时候系统仍然会直接查找是否安装有这些 perl 模块,而不会查找那些包含这两个模块的软件包是否已经安装。
#PreReq: loop #如果在 %pre 字段执行的脚本需要使用一些特殊命令,例如 loop,你需要在此标明
#Requires(pre): loop #这是上面一行的另一种写法,依此类推,还有其他几个类似的关键字:
#Requires(post)
#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 <[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  %{?_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
注意:并非所有情况都支持 DESTDIR,有时必须使用 prefix 参数。比如遇到某些不规范的 Makefile 只含有 prefix 参数。另一些时候,某些拙劣的作者甚至采用了硬编码形式直接将文件安装的绝对路径手工写入了 Makefile,这种情况下 DESTDIR 和 prefix 参数都无效,需要我们手工修改 Makefile!

%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}

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

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

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


[spec 文件尾部]

%changelog #下面是标准变更日志,日期一定不能写错,只能是英文格式。建议将邮件地址中的 @ 符号替换成 {at} 或者其他符号,而 . 替换成 {dot}。这是为了避免你的邮件地址被网络上各种垃圾邮件发送程序识别出来,从而使你的邮箱爆满。如下所示:

* Sun Oct 31 2004  Tony Black <[email protected]>
- modify the spec file and rebuild

* Sun Oct 03 2004  Lover <root {AT} Lover {dot} org>
- 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

注意:出于安全和灵活操作的考虑,晚近的 rpm 设置都已不再采用 /usr/src 作为 rpm 构建目录,转而采用在用户的家目录下创建一个以 . 开头的隐含目录作为 rpm 构建目录,比如 MagicLinux 从 2.5 版开始就是这样。

★★★
如何避免生成 debuginfo 包?
打包过程默认会创建 debuginfo 包。大部分情况下,打包的软件已经是稳定版,我们并不需要调试程序,而普通用户也没有能力调试,所以每次都生成 debuginfo 包就显得没有太大必要了。我们可以创建一个自己家目录下的 rpm 配置文件 .rpmmacros,写入一行定义来禁止打包过程生成 debuginfo 包:
echo '%debug_package %{nil}' >> ~/.rpmmacros




注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   #这里 -O2 代表采用第二优化级,通常不要使用 -O3,因为那样编译得到的二进制文件体积会增大,但是性能能否改善却是不确定的;-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 代表修改过的新源代码目录。

注6[NEW]
默认情况下,rpm 打包后会自动生成所依赖文件的列表,其实是调用了 ldd 命令。然后反查这些 .so 文件的 rpm 包,将它们加入依赖列表就可以了。
ldd prints the shared libraries required by each program or shared library specified on the command line.
例如在 coreutils-7.0-7mgc25.i686 这个包里包含一个文件 /bin/mkdir,这样查询它的依赖关系(需要给出文件的绝对路径):
  1. [root@MagicLinux athena]# ldd /bin/mkdir
  2.         linux-gate.so.1 =>  (0xb770d000)
  3.         libc.so.6 => /lib/libc.so.6 (0x49f6b000)
  4.         /lib/ld-linux.so.2 (0x49200000)
复制代码
接着反查其中一个文件的来源(同样需要给出文件的绝对路径):
  1. [root@MagicLinux athena]# rpm -qf /lib/libc.so.6
  2. glibc-2.10.1-2mgc25.i686
复制代码
如果想做绿色软件,就要在 rpm 中包含这些依赖的 .so 文件,同时这些 .so 文件同样依赖着另一些 .so 文件,必要时也要打进 rpm 包。理论上在  postinstall 和 postuninstall 脚本里执行 /sbin/ldconfig ".so 文件所在目录" 就可以“注册”这些库文件,然后就能直接使用这些依赖的文件,而不用安装那些依赖的 rpm 包了。

[ 本帖最后由 KDE 于 2010-11-22 23:38 编辑 ]
 楼主| 发表于 2004-10-3 23:00:36 | 显示全部楼层
这里有一个由压缩包直接创建 rpm/srpm 包的工具,但是它无法知道那些文件、目录应该被打进包里,你需要自己指定:
http://sourceforge.net/project/s ... amp;release_id=7939


下面是一个实际例子:
-------------------------------------------------------

  1. Summary:                        Challenging 2D Moto cross Platform Game
  2. Summary(zh_CN):            富有挑战性的 2D 摩托跨平台游戏
  3. Name:                              xmoto
  4. Version:                           0.3.1
  5. Release:                          1mgc
  6. Source0:                          http://download.tuxfamily.org/xmoto/xmoto/%{version}/xmoto-%{version}-src.tar.gz
  7. Source1:                          xmoto.desktop
  8. Source2:                          xmoto.png
  9. Patch0:                            xmoto-man.patch
  10. URL:                                http://xmoto.sourceforge.net
  11. Group:                             Amusements/Games
  12. Group(zh_CN):                娱乐/游戏
  13. Packager:                        Pascal Bleser <[email protected]>, kde <athena_star {at} 163 {dot} com>
  14. Distribution:                     Magic Linux 2.1
  15. License:                           GPL
  16. BuildRoot:                        %{_tmppath}/%{name}-%{version}-%{release}-buildroot-%(%{__id_u} -n)

  17. Requires:                         SDL, libgcc, libstdc++, libogg, libvorbis, lua, ode
  18. BuildRequires:                 gcc-c++, libstdc++, libstdc++-devel
  19. BuildRequires:                 SDL_mixer-devel, SDL_ttf-devel
  20. BuildRequires:                 curl-devel
  21. BuildRequires:                 ode-devel
  22. BuildRequires:                 lua-devel >= 5.0
  23. BuildRequires:                 libGL-devel
  24. BuildRequires:                 libGLU-devel
  25. BuildRequires:                 libjpeg-devel
  26. BuildRequires:                 libpng-devel
  27. BuildRequires:                 bzip2-devel
  28. BuildRequires:                 sqlite-devel

  29. %description
  30. X-Moto is a challenging 2D moto cross platform game, where physics play an all
  31. important role in the gameplay. You need to control your bike to its limit, if
  32. you want to have a chance finishing the more difficult of the challenges.

  33. %description -l zh_CN
  34. X-Moto 是一个极富挑战性的 2D 摩托车跨平台游戏。在游戏中,您可以亲历
  35. 所有重要角色。如果您想有机会完成更高难度的挑战,您必须竭尽全力控制
  36. 您的摩托车。

  37. %prep
  38. %setup -q
  39. %patch0
  40. #fix encoding
  41. sed -i 's/\r//' src/xmscene/Camera.cpp
  42. sed -i 's/\r//' src/xmscene/Camera.h

  43. #fix permissions
  44. chmod 644 src/xmscene/Camera.*

  45. %build
  46. export LDFLAGS=-L%{_prefix}/X11R6/%{_lib}
  47. %configure --with-enable-zoom=1
  48. %{__make} %{?_smp_mflags}

  49. %install
  50. %{__rm} -rf "%{buildroot}"
  51. %{__make} DESTDIR="%{buildroot}" install

  52. # Install desktop file and icon
  53. %{__mkdir_p} "%{buildroot}/usr/share/applications"
  54. %{__install} -m 0644 "%{SOURCE1}" "%{buildroot}/usr/share/applications/"
  55. %{__mkdir_p} "%{buildroot}/usr/share/pixmaps"
  56. %{__install} -m 0644 "%{SOURCE2}" "%{buildroot}/usr/share/pixmaps/"

  57. # Locale files
  58. %find_lang %{name} %{name}.lang

  59. %clean
  60. %{__rm} -rf %{buildroot} %{_builddir}/%{buildsubdir}

  61. %files -f %{name}.lang
  62. %defattr(-,root,root)
  63. %attr(0644,root,root) %doc AUTHORS ChangeLog COPYING NEWS README
  64. %{_bindir}
  65. %{_datadir}/xmoto
  66. %{_datadir}/applications/xmoto.desktop
  67. %{_datadir}/pixmaps/xmoto.png
  68. %{_mandir}

  69. %changelog
  70. * Thu Jul 12 2007 kde <athena_star {at} 163 {dot} com> - 0.3.1-1mgc
  71. - update to release 0.3.1

  72. * Thu Dec 29 2005 kde <[email protected]> 0.1.10-1mgc
  73. - update to release 0.1.10


  74. * Sat Oct 29 2005 kde <[email protected]> 0.1.6-1mgc
  75. - port to Magic Linux 2.0

  76. * Thu Oct 13 2005 Pascal Bleser <[email protected]> 0.1.6-1
  77. - new upstream version

  78. * Mon Oct  3 2005 Pascal Bleser <[email protected]> 0.1.4-1
  79. - new package


复制代码


几点注意:
1、要求必须翻译 spec,翻译必须符合 i18n 规范,统一使用 gb18030 编码和 locale;
2、rpm 标准分组信息参见上文;
3、注意例子里的标准构建根和 clean 小节的写法;
4、所有非命令行程序,都必须给出符合 magic 规范的菜单文件,即 desktop 文件:
(a) 提供简、繁体中文菜单,这样就能良好支持繁体中文区的用户,具体就是翻译 desktop 文件
(b) desktop 文件必须使用 utf-8 编码
(c) desktop 文件的分类(Categories)参考系统里其他相似类别的文件,例如:游戏统一写成 Categories=Application;Game;

例如 bzflag.desktop:

  1. [Desktop Entry]
  2. Encoding=UTF-8
  3. Name=BZFlag
  4. GenericName[zh_CN]=铁甲威龙
  5. GenericName[zh_TW]=鐵甲威龍
  6. Comment=A multiplayer 3D tank battle game
  7. Comment[zh_CN]=一个多人 3D 坦克战斗游戏
  8. Comment[zh_TW]=一個多人 3D 坦克戰鬥游戲
  9. Exec=bzflag
  10. Icon=bzflag.xpm
  11. Terminal=false
  12. Categories=Application;Game;
  13. Type=Application
复制代码


繁体中文有自己的特定用语,有时和简体中文不一样,比如大陆叫“宏”,台湾叫“巨集”,载入“配置”,台湾叫载入“组态”,我们叫“文件”和“文档”,台湾刚好相反,管文件叫文档,管文档叫文件,这个比较麻烦,我也只有一个不完善的繁体--->简体转换脚本,没有反向的。(参见置顶帖)。

[ 本帖最后由 KDE 于 2009-8-29 14:22 编辑 ]
回复

使用道具 举报

 楼主| 发表于 2004-10-3 23:13:46 | 显示全部楼层
参考文献:

1. http://www-900.ibm.com/developerWorks/cn/linux/management/package/rpm/part1/index.shtml
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
回复

使用道具 举报

发表于 2004-10-4 09:31:42 | 显示全部楼层
%exclude宏好像ML中竟然没有。
回复

使用道具 举报

发表于 2004-10-4 11:15:13 | 显示全部楼层
%exclude

ML有的。
回复

使用道具 举报

发表于 2004-10-4 12:56:46 | 显示全部楼层
没查到啊。
回复

使用道具 举报

发表于 2004-10-4 17:02:41 | 显示全部楼层
晕,明明就是有嘛,

如果没有那rpm怎么做的?
回复

使用道具 举报

发表于 2004-10-4 22:00:03 | 显示全部楼层
有几个问题想请教:
1、怎样增加一个文件到rpm包中?例如:把libqq.so集成到gaim的rpm中;
2、怎样在安装时自动修改某个配置文件?例如:安装aaa.rpm时修改/etc/bbb.conf第32行或者某个指定的字段?
3、怎样实现在安装aaa.rpm时自动检查是否有所依赖的libbbb.so、如果没有就自动从%URL字段所指的位置下载libbbb.so的包?
回复

使用道具 举报

发表于 2004-10-4 23:04:57 | 显示全部楼层
1、在spec中加入一个source1:libqq.so
在%install段,用install -m 755 %{SOURCE1} $RPM_BUILD_ROOT/要指定的目录。
2、在spec的%post段,用echo或grep之类的脚本实现对配置文件的修改。
3、在spec的%pre段,加入依赖检查及文件下载安装脚本。
回复

使用道具 举报

 楼主| 发表于 2004-10-5 00:17:11 | 显示全部楼层
[quote:0365843587="sejishikong"]%exclude宏好像ML中竟然没有。[/quote]

%define
%description
%prep
%setup
%install
%clean
%files
%changelog
这些在 /usr/lib/rpm/macros 都没有定义,不是照样使用?难道哪个发行版定义了?
回复

使用道具 举报

发表于 2004-10-5 09:21:18 | 显示全部楼层
噢,是我不对了,因为我在写SPEC的时候,这个exclude不像其它的宏会变颜色,我就以为没有呢。
回复

使用道具 举报

发表于 2004-10-5 21:21:17 | 显示全部楼层
谢谢啊!正是我需要的,置顶加精!
回复

使用道具 举报

发表于 2004-10-10 22:55:02 | 显示全部楼层
我就觉得rpm包不如tgz包好用.看看有多麻烦!!!
回复

使用道具 举报

发表于 2004-10-10 23:00:34 | 显示全部楼层
[quote:32039aeb22="wjping119"]我就觉得rpm包不如tgz包好用.看看有多麻烦!!![/quote]

不见得。不同的人可能有不同的习惯。
回复

使用道具 举报

发表于 2004-10-11 00:37:20 | 显示全部楼层
rpm的机理确实比tgz复杂一些,但对于用户则更简易一些。

其实目前存在的几种软件包格式都有一定的不足:
1、tarball格式对于开发者来说比较容易理解,只要按照规范,做起来也容易,但对于用户来说却并不是最简单的;
2、rpm和deb都功能强大,deb在解决倚赖性方面甚至更胜一筹,但制作起来都比较麻烦,对于用户也不是完全那么简易的。

DOS/Windows下,过去没有统一的软件包格式,一般都是分为压缩包直接拷贝和自行开发安装程序,最后发展出多种安装程序制作工具。微软受到rpm和deb的启发后开发了msi软件包格式,逐渐将以前的安装程序制作工具整合为一种统一的类似rpm的格式,从而使安装包的制作和使用更加简便。

解决软件包问题是Magic Linux推广普及的一个重要关口,解决这个问题只需要做两件事情:
1、开发使软件包制作更加简易的工具。这个工具应解决软件包维护、集成套件打包、自开发软件打包等问题,让即使是初学者、刚加入ML项目的朋友也能够完成软件包的制作工作。
2、开发适用于所有Linux软件包格式的用户工具。将现有的rpm管理工具、deb管理工具、tarball管理工具等软件管理工具集成起来,建立统一的软件包倚赖关系树,同时解决单独的软件包管理工具的缺陷和不足之处。

以上是我的一点拙见,对彻底解决感兴趣的朋友不放尝试一下。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-4-20 07:13 , Processed in 0.056539 second(s), 15 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表