原文:http://zhumeng8337797.blog.163.com/blog/static/100768914201181782310771/  

最近学习rpm打包,参考ibm文档库里rpm打包的文章,结合自己的实践,总结如下,一来备忘,二来和大家交流。

和deb打包不同,rpm打包需要特定的目录及结构。查看rpm打包目录,以下为在CentOS5.5下的输出结果:

$ rpm --showrc|grep _topdir  
-14: _builddir  %{_topdir}/BUILD  
-14: _rpmdir    %{_topdir}/RPMS  
-14: _sourcedir %{_topdir}/SOURCES  
-14: _specdir   %{_topdir}/SPECS  
-14: _srcrpmdir %{_topdir}/SRPMS  
-14: _topdir    %{_usrsrc}/redhat  
$ rpm --showrc|grep _usrsrc  
-14: _topdir    %{_usrsrc}/redhat  
-14: _usrsrc    %{_usr}/src  
$ rpm --showrc|grep _usr  
-14: _defaultdocdir     %{_usr}/share/doc  
-14: _topdir    %{_usrsrc}/redhat  -14: _usr       /usr  
-14: _usrsrc    %{_usr}/src

经过层层寻找,最终发现打包目录在/usr/src/redhat目录下,看看目录结构:

$ tree /usr/src/redhat  /usr/src/redhat  
|-- BUILD  
|-- RPMS  
|   |-- athlon  
|   |-- geode  
|   |-- i386  
|   |-- i486  
|   |-- i586  
|   |-- i686  |   
`-- noarch  
|-- SOURCES  
|-- SPECS  
 `-- SRPMS

其中BUILD存放编译生成的临时文件,RPMS存放根据各种构架生成的rpm包,SOURCES存放源码包,SPECS存放spec文件,SRPMS存放生成的SRPM包。

最简单例子

下面以hello world为例,构建一个最小化打包过程。

首先需要写一个SPEC文件hello.spce:

Summary:    hello world rpm package  
Name:       hello  
Version:    0.1  Release:    1  
Source:     hello-0.1.tar.gz  License:    
GPL  Packager:   amoblin  
Group:      Application  
URL:        http://www.ossxp.com    
%description  This is a software for making your life more beautiful!    
%prep  %setup -q    
%build  gcc -o hello hello.c    
%install  install -m 755 hello /usr/local/bin/hello    
%files  /usr/local/bin/hello

放到上述SPECS目录下。

然后一个源程序hello.c:

#include <stdio.h>  int main()  {      printf("Hello, World!\n");      return 0;  }

存放在hello-0.1目录,然后打包放到SOURCES目录:

$ tar zcvf hello-0.1.tar.gz hello-0.1  hello-0.1/  hello-0.1/hello.c  $ sudo mv hello-0.1.tar.gz /usr/src/redhat/SOURCES

在SPECS目录下使用rpmbuild进行打包:

$ cd /usr/src/redhat  $ sudo rpmbuild -ba hello.spec  ...  Wrote: /usr/src/redhat/SRPMS/hello-0.1-1.src.rpm  Wrote: /usr/src/redhat/RPMS/i386/hello-0.1-1.i386.rpm

这时会逐个运行hello.spec文件的内容,最终生成两个文件,一个包含源码的rpm包和一个二进制rpm包。

使用 -bs 选项只生成源码rpm包;使用 -bb 选项只生成rpm包。

查看rpm包信息和包内容:

$ rpm -qpi ../RPMS/i386/hello-0.1-1.i386.rpm  $ rpm -qpl ../RPMS/i386/hello-0.1-1.i386.rpm

第一个命令的输出是spec文件的序言部分的内容,第二个命令的输出是%files部分的文件列表。

现在有个问题,打包目录在/usr/src/redhat下,需要root权限才能操作,太不方便了,能不能在用户自定义目录下打包呢?

自定义打包目录

我们可以通过修改topdir宏的值来自定义打包路径:

$ echo %_topdir $HOME/rpmbuild > ~/.rpmmacros

这样再查看topdir的值会发现已变为用户主目录下rpm子目录了。这时修改文件就方便多了。

但在执行rpmbuild时仍会出问题:

$ rpmbuild -ba hello.spec  ...  install: 无法删除 “/usr/local/bin/hello”: 权限不够  error: Bad exit status from /var/tmp/rpm-tmp.65773 (%install)  ...

这是因为rpmbuild在构建rpm包时会将程序安装一遍,然后再提取安装文件。由于需要复制二进制文件hello到系统目录/usr/local/bin/下,所以普通用户执行就报错了。

那么怎么办呢?这里需要使用虚拟根了。

修改spec文件,在Description段落前,URL字段之后增加一行:

BuildRoot:  %{_builddir}/%{name}-root

修改install段落,将绝对安装路径改为使用构建根的方式::

%install  mkdir -p $RPM_BUILD_ROOT/usr/local/bin  install -m 755 hello $RPM_BUILD_ROOT/usr/local/bin/hello

通过BuildRoot的值告诉rpmbuild,我们的构建根是builddir下的hello-root目录。其中以%{}括起来的是RPM宏,_builddir代表~/rpmbuild/BUILD目录;name代表spec文件开头的Name字段值。

以下划线开头的builddir是系统RPM宏,我们可以通过rpm –showrc看到,可以在.rpmmacros中自定义。

RPM_BUILD_ROOT和前面的宏不同,这里没有{}括起来,是为了在以后安装生成的rpm时不至于也去寻找传说中的构建根。

如果喜欢的话,可以修改Source字段如下::

Source: %{name}-%{version}.tar.gz

好,继续回到构建根。现在执行rpmbuild,会在BUILD下创建hello-root目录作为虚拟根,hello安装在其中的usr/local/bin目录下。

使用Makefile

一般源程序都使用Makefile的,因此我们再进一步,添加一个Makefile文件,在spec里使用make来编译。

简单的Makefile文件如下:

SRC = hello.c    
hello: $(SRC)      
   gcc $^ -o $@    
clean:      rm -f hello    
install:      
-mkdir -p $(RPM_INSTALL_ROOT)/usr/local/bin/      
install -m 755 hello $(RPM_INSTALL_ROOT)/usr/local/bin/hello

由于使用了Makefile,我们最好升级一下版本号,将原先的hello-0.1目录复制为hello-0.2目录,放入Makefile文件。

修改spec文件,更新版本号,同时将build和install部分用make替换::

Version:    0.2    ...    
%build  make    
%install  RPM_INSTALL_ROOT=$RPM_BUILD_ROOT make install    ...

这里将构建根参数传递给Makefile,从而将程序安装在指定的根目录下。

如果喜欢做事不留痕的话,可以在install段落后面增加clean段,清理生成的虚拟根:

%clean  rm -rf $RPM_BUILD_ROOT

使用补丁文件

有很多源码包里都有补丁文件,在打包时要先打补丁,这也要在spec文件里告诉rpmbuild一声才行。

复制hello-0.2一份,随便修改一个hello.c,和原目录做比较,生成补丁文件:

$ cp -r hello-0.2 hello-0.2.my  
$ vi hello-0.2.my/hello.c  
$ diff -uNr hello-0.1 hello-0.2 > hello-0.1.patch

然后修改spec文件,增加Patch字段,以及在prep段落增加打补丁动作::

...  Patch0:  
%{name}-%{version}.patch  ...  
%prep  
%setup -q  
%patch -p1  ...

Patch0告诉rpmbuild这是一个补丁,如果补丁不止一个的话,可以通过Patch1,Patch2增加。

再次执行rpmbuild,生成rpm包。

查看SRPM包内容:

$ rpm -qpl ../SRPMS/hello-0.2-1.src.rpm  hello-0.2.patch  hello-0.2.tar.gz  hello.spec

补丁果然被打包进来了!

在安装过程中使用脚本

可以使用%pre,%post,%preun,%postun段落来定义安装前后,卸载前后的脚本动作::

%pre  echo This is pre for 
%{version}-%{release}: arg=$1  
%post  echo This is post for 
%{version}-%{release}: arg=$1  
%preun  echo This is preun for %{version}-%{release}: arg=$1  
%postun  echo This is postun for %{version}-%{release}: arg=$1

具体的脚本内容可自行替换。

打包rpm的过程就这么简单!

但上面这些方法一般都是针对打包者的,打包者不管以什么手段从任何地方得到了源代码包,然后保持一颗虔诚的心,恭恭敬敬地将之安放在rpmbuild/SOURCES目录下,然后天马行空,随意发挥或挥发,写下一篇洋洋洒洒的spec文件,对号入座到SPECS目录下,然后rpmbuild隆重出场,进行一系列生产加工,最终产生出来了传说中的rpm包和src.rpm包。

但,有时我们不总是打包者,更多时候我们是开发者,使用某种版本控制系统,比如Git,进行开发,最后的产品希望打包发布,还用上面的方式吗?不行,太麻烦了~

那怎么办呢?欲知结果,敬请期待,清明之后见分晓~

 

 

在源代码中打包

仍拿前面的helloworld举例,把spec文件放到开发目录下,修改去掉补丁文件,然后打包:

$ ls hello-0.2  hello.c  hello.spec  Makefile  
$ tar zcvf hello-0.2.tar.gz hello-0.2  
hello-0.2/  hello-0.2/Makefile  hello-0.2/hello.spec  hello-0.2/hello.c

接下来就要用到rpmbuild另一个很有用的参数了:-t,可以看一个rpmbuild的man:

$ man rpmbuild  
NAME         rpmbuild - Build RPM Package(s)    
SYNOPSIS     BUILDING PACKAGES:  
rpmbuild {-ba|-bb|-bp|-bc|-bi|-bl|-bs} [rpmbuild-options] SPECFILE ...    
rpmbuild {-ta|-tb|-tp|-tc|-ti|-tl|-ts} [rpmbuild-options] TARBALL ...    
rpmbuild {--rebuild|--recompile} SOURCEPKG ...    
 MISCELLANEOUS:         rpmbuild --showrc    ...

rpmbuild通过-t参数来构建打包过的源程序。-ta就是构建rpm包和srpm包,-tb就是只构建二进制rpm包,-tp是只构建打过补丁的包,-ts是只构建srpm包。

好了,现在可以生成rpm包了:

$ rpmbuild -ta hello-0.2.tar.gz

如果使用git的话,就更方便了,因为Git提供了对任意分支打包的命令:git-archive。下面我们先使用git初始化我们的开发目录,然后使用git-archive来生成压缩包。

$ cd hello-0.2  
$ git init  
Initialized empty Git repository in /home/admin/work/hello-0.2/.git/  
$ git add -A  
$ git ci -m init  
 3 files changed, 73 insertions(+), 0 deletions(-)  
 create mode 100644 Makefile  
create mode 100644 hello.c  
create mode 100644 hello.spec 
 $ git archive --prefix=hello-0.2/ master|gzip > hello-0.2.tar.gz  
$ ls  hello-0.2.tar.gz  hello.c  hello.spec  Makefile

通过 –prefix参数使当前master分支下文件打包到hello-0.2目录下,注意在文件夹名后面一定要有一个斜扛,否则就变成一般的文件名前缀了。

生成压缩包以后,就可以像之前一样使用rpmbuild来生成rpm包了。

使用这种方式编译rpm包,无疑给开发者带来了很多方便,尤其是使用git做版本控制的开发者。而且也比debian打包简单,debian打包需要一个debian目录,里面一大堆文件,而rpm打包,一切尽在一个spec文件中,经典!

接下来说说python程序包的制作,这个比较特殊。

python应用程序的RPM包制作

正如前面所见的,rpmbuild内置了很多宏,使得在写rpm包时方便很多。所有这些宏都在/usr/lib/rpm/macros中定义。

查看python相关的宏:

$ cat /usr/lib/rpm/macros | grep python  
%__python               /usr/bin/python  
%__python_provides      /usr/lib/rpm/pythondeps.sh --provides  
%__python_requires      /usr/lib/rpm/pythondeps.sh --requires  
#       at the python prompt for example, after "import rpm".

如上,在spec文件里可以使用%{__python}来调用系统python。

这里没有定义sitelib,我们一般在spec文件开头自定义:

%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}

可以看出,%()里面是shell命令,通过python的get_python_lib函数得到python的site lib路径。

此外,还有安装要求:

BuildRequires:  python-devel  
%if 0%{?fedora} >= 8  
BuildRequires: python-setuptools-devel  
%else  BuildRequires: python-setuptools  
%endif

在安装部分,可以使用%{__python}宏来进行安装:

%install  rm -rf $RPM_BUILD_ROOT  %{__python} setup.py install --root $RPM_BUILD_ROOT

–root $RPM_BUILD_ROOT的作用就是告诉setup.py安装到虚拟根上,类似前面的 RPM_INSTALL_ROOT=$RPM_BUILD_ROOT。

此外,这里还有一个INSTALLED_FILES标志可以使用,用来标记安装到系统中的文件,但来自fedoraproject的建议是,不提倡使用 INSTALLED_FILES,因为它只会标记文件,而不会标记目录,所以有可能遗漏。

打包文件列表部分写法如下:

%files  %defattr(-,root,root,-)  
%attr(755,root,root) 
%{_bindir}/gistore  
%{python_sitelib}/*  
%doc /usr/share/doc/gistore/README  
%doc /usr/share/doc/gistore/COPYING  
%doc /usr/share/doc/gistore/CHANGELOG

%files部分列出要打包的文件和目录,可以使用前面的标记安装文件,但最好的方法就是把文件或目录手动列出来。

%defattr(-,root,root,-)代表使用默认的文件和目录权限

%{_bindir}是系统bin目录,具体为/usr/bin/,这个目录下的文件一般是可执行的。

%doc 代表文档,可以这样写::

%doc README COPYING CHANGELOG

这样的话,rpmbuild会从源码根目录下复制上述文件到/usr/share/doc/%{name}-%{version}/目录下,而按照前面的那种三行的写法的话,是自定义doc文件夹的位置,可以看出,自定义的doc文件夹名称没有版本号。

写完了以后就可以打包了,打包过程和前面无异。

下次有时间的话,会讲如何进行多个子包的打包~

posted on 2012-09-06 16:53  Sparkjin  阅读(1528)  评论(0编辑  收藏  举报