分类: LINUX
2009-06-30 20:34:08
------------------------------------------------------------------
[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} 引用。例如
%{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 文件
# 下面是可选的字段
URL: #这是软件主页地址
#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 档中有打包前未发现的错误,打包过程中断是否有办法补救呢?
打包过程中
断是很常见的现象。并非没有办法补救,我们曾经用这种偷懒的方法对付需要反复编译巨大软件包的困境。但是也发现有时会造成一些难以发现,或者难以解释的错
误。如果你在 make 阶段就出了错,我们建议重头来过,不建议使用这种方法,因为这是非常不可靠的。所以还是不要偷懒为好。
如果编译已经通过,但是在分包过程中中断,单纯为了验证 rpm 的分包是否有错,可以这样做:
1、不删除编译产生的所有文件(即 mBUILD 目录里的东西),并删除先前生成的虚拟根目录(即 /var/tmp/软件名称-版本号 目录)
2、建立一个当前 spec 的副本,删除其中 prepare 小节以后和 install 小节以前的部分,对这个 spec 运行建包命令,即可跳过所有编译过程,直接安装、打包。
[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
如果只想生成二进制包,使用下面命令:rpmbuild -bb --target=i686 xxx.spec
如果只想生成源代码包,使用下面命令:rpmbuild -bs --target=i686 xxx.spec
第九、rpm软件包系统标准分组(即是上面spec提到的Group)在这里
[root@localhost everest]# cat /usr/share/doc/rpm-4.4.2.1/GROUPS
第十、宏
各种宏定义在系统这里:
/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
第十一、打补丁
补丁通常是这样创建的:
diff -Nur directory.old directory.new > xxx.patch
directory.old 代表旧源代码目录,directory.new 代表修改过的新源代码目录。