大多数 rpm 包的制作都是用源码包来编译构建,而我的需求是直接将可运行的二进制文件制作成 rpm 包,而且是在 Ubuntu 系统上。网上的大部分资料都是源码来制作 rpm 包,且比较零乱、不完整。rpm 制作的重要一步就是编写 spec 文件,在该文件中定义了如何编译源码,然后又如何打包的过程。通过大量的资料查阅和分析,最后发现在 spec 文件中把 源码编译的部分删掉便可以直接将二进制文件制作成 rpm 包。

安装 rpm 工具

制作 rpm 包需要用到 rpmbuild 工具。在 ubuntu 上,该工具包含在 rpm 包中,可以直接从源里安装:

sudo apt-get install rpm

配置工作路径

在制作 rpm 包之前,首先要配置工作路径,也就是制作 rpm 包所在的目录。制作 rpm 包需要有一个特定的目录结构。当前的工作路径保存在宏 % _topdir 中,可以通过 rpmbuild 命令查看:

rpmbuild --showrc | grep topdir

默认情况下工作路径为当前用户目录下的 rpmbuild 目录。如果你不想在用户目录下的rpmbuild目录制作rpm包,可以在当前用户目录下的 .rpmmacros 文件(如果没有,则创建)中修改宏 %_topdir 的配置,例如:

%_topdir /home/konghy/workdir(你的工作路径)

建立构建目录结构

在类 redhat 系统中,可以用 rpmdev-setuptree 命令直接在常见所需的目录结构,而在 Ubuntu 系统貌似没有该工具,那么手动创建即可:

mkdir -pv /home/konghy/workdir/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}

目录说明:

BUILD    编译之前,如解压包后存放的路径
BUILDROOT    编译后存放的路径
RPMS     打包完成后rpm包存放的路径
SOURCES     源包所放置的路径
SPECS    spec文档放置的路径
SPRMS    源码rpm包放置的路径

通过源码编译制作软件包时, 一般都把源码打包成 tar.gz 格式然后存放于 SOURCES 路径下,而在 SPECS 路径下编写 spec 文档,编译生成的二进制文件会临时的存放在 BUILDROOT 目录下等待打包,打包完成后会被删除,通过命令打包后,默认会把打包后的 rpm 包放在 RPMS 下,而源码包会被放置在SRPMS下。

编写 spec 文件

在我看来, spec 文件核心的部分就只有两个部分:一个是软件包的基本信息描述部分,其中 NameVersionRelease 三个字段是必须的,最终生成的软件包的名字会依赖于此;另一个是 %files 段,它定义需要打包的文件。剩下的大致也可以分为两个部分:一个是定义源码的编译过程,这里不做详述;另一个是定义安装和卸载前后要做的工作,主要在四个段中定义:

%pre
在安装包之前运行
%post
在安装包之后运行
%preun
在卸载包之前运行
%postun
在卸载包之后运行

对于 %files 阶段有两个比较重要的特性:

  • %{buildroot}里的所有文件都要明确被指定是否要被打包到rpm里。例如,%{buildroot}目录下有4个目录a、b、c和d,在%files里仅指定a和b要打包到rpm里,如果不把c和d用exclude声明是要报错的;

  • 如果声明了%{buildroot}里不存在的文件或者目录也会报错。

下面是一个 spec 文件模板:

# 这个区域定义的Name、Version这些字段对应的值可以在后面
# 通过%{name},%{version}这样的方式来引用,类似于C语言中的宏

# Name制定了软件的名称
Name:       nginx
# 软件版本
Version:    1.5.2
# 释出号,也就是第几次制作rpm
Release:    1%{?dist}
# 软件的介绍,必须设置,最好不要超过50个字符
Summary:    Nginx from WangYing

# 软件的分组,可以通过/usr/share/doc/rpm-4.8.0/GROUPS文件中选择,也可以
# 在相应的分类下,自己创建一个新的类型,例如这里的Server
Group:      Application/Server
# 许可证类型
License:    GPL
# 软件的源站
URL:        http://nginx.org
# 制作rpm包的人员信息
Packager:   WangYing 
# 源码包的名称,在%_topdir/SOURCE下,如果有多个源码包的话,可以通过
# Source1、Source2这样的字段来指定其他的源码包
Source0:    %{name}-%{version}.tar.gz
# BuildRoot指定了make install的测试安装目录,通过这个目录我们可以观察
# 生成了哪些文件,方便些files区域。如果在files区域中写的一些文件报
# 不存在的错误,可以查看%_topdir/BUILDROOT目录来检查有哪些文件。
BuildRoot:  %_topdir/BUILDROOT
# 指定安装的路径
Prefix:     /usr/local/nginx-1.5.2

# 制作过程需要的工具或软件包
BuildRequires:  gcc,make
# 安装时依赖的软件包
Requires: pcre,pcre-devel,openssl

# 软件的描述,这个可以尽情地写
%description
Nginx is a http server

# %prep指定了在编译软件包之前的准备工作,这里的
# setup宏的作用是静默模式解压并切换到源码目录中,
# 当然你也可以使用tar命令来解压
%prep
%setup -q

# 编译阶段,和直接编译源代码类似,具体的操作或指定的一些参数由configure文件决定。
%build
CFLAGS="-pipe -O2 -g -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror" ./configure --prefix=%{prefix}
# make后面的意思是:如果是多处理器,则并行编译
make %{?_smp_mflags}

# 安装阶段
%install
# 先删除原来的测试安装的,只有在制作失败了%{buildroot}目录才会有内容,
# 如果成功的话,目录下会被清除。
# %{buildroot}指向的目录不是BuildRoot(%_topdir/BUILDROOT)指定的目录,
# 而是该目录下名称与生成的rpm包名称相同的子目录。例如我的是
# /usr/src/redhat/BUILDROOT/nginx-1.5.2-1.el6.x86_64
rm -rf %{buildroot}
# 指定安装目录,注意不是真实的安装目录,是在制作rpm包的时候指定的
# 安装目录,如果不指定的话,默认就会安装到configure命令中指定的prefix路径,
# 所以这里一定要指定DESTDIR
make install DESTDIR=%{buildroot}

# 安装前执行的脚本,语法和shell脚本的语法相同
%pre

# 安装后执行的脚本
%post

# 卸载前执行的脚本,我这里的做的事情是在卸载前将nginx服务器停掉
%preun
    MSG=`ps aux | grep nginx | grep -v "grep"`
    if [ -z "$MSG" ];then
        killall nginx 1>/dev/null 2>/dev/null
    fi

# 卸载完成后执行的脚本
%postun
    rm -rf %{prefix}

# 清理阶段,在制作完成后删除安装的内容
%clean
rm -rf %{buildroot}

#指定要包含的文件
%files
#设置默认权限,如果没有指定,则继承默认的权限
%defattr  (-,root,root,0755)
%{prefix}

spec 文档中常用的几个宏(变量):

RPM_BUILD_DIR:    /usr/src/redhat/BUILD
RPM_BUILD_ROOT:   /usr/src/redhat/BUILDROOT
%{_sysconfdir}:   /etc
%{_sbindir}:     /usr/sbin
%{_bindir}:       /usr/bin
%{_datadir}:      /usr/share
%{_mandir}:       /usr/share/man
%{_libdir}:       /usr/lib64
%{_prefix}:       /usr
%{_localstatedir}:   /usr/var

开始打包

制作 rpm 包需要用 rpmbuild 命令,其基本格式为:

rpmbuild [options] [spec文档|tarball包|源码包]

从spec文档建立有以下选项:

-bp  #只执行spec的%pre 段(解开源码包并打补丁,即只做准备)
-bc  #执行spec的%pre和%build 段(准备并编译)
-bi  #执行spec中%pre,%build与%install(准备,编译并安装)
-bl  #检查spec中的%file段(查看文件是否齐全)
-ba  #建立源码与二进制包(常用)
-bb  #只建立二进制包(常用)
-bs  #只建立源码包

从 tarball 包建立

-tp #对应-bp
-tc #对应-bc
-ti #对应-bi
-ta #对应-ba
-tb #对应-bb
-ts #对应-bs
3.  从源码包建立
--rebuild  #建立二进制包,通-bb
--recompile  #同-bi

其他的一些选项

--buildroot=DIRECTORY   #确定以root目录建立包
--clean  #完成打包后清除BUILD下的文件目录
--nobuild  #不进行%build的阶段
--nodeps  #不检查建立包时的关联文件
--nodirtokens
--rmsource  #完成打包后清除SOURCES
--rmspec #完成打包后清除SPEC
--short-cricuit
--target=CPU-VENDOR-OS #确定包的最终使用平台

例如我的构建方式为:

rpmbuild -bb --target=i686 SPECS/codeblocks.spec

构建过程输出如下信息:

构建目标平台:i686
为目标i686构建
处理文件:codeblocks-13.12-1.i686
Provides: codeblocks = 13.12-1 codeblocks(x86-32) = 13.12-1
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
检查未打包文件:/usr/lib/rpm/check-files /home/konghy/workdir/rpmbuild/BUILDROOT/codeblocks-13.12-1.i386
写道:/home/konghy/workdir/rpmbuild/RPMS/i686/codeblocks-13.12-1.i686.rpm
执行(%clean): /bin/sh -e /var/tmp/rpm-tmp.uWPBYI
+ umask 022
+ cd /home/konghy/workdir/rpmbuild//BUILD
+ /bin/rm -rf /home/konghy/workdir/rpmbuild/BUILDROOT/codeblocks-13.12-1.i386

我的打包经历

本文要讲的主要内容在 Ubuntu 下直接将编译好的二进制文件制作成 rpm 包,以上的内容只是为了更为详细的了解 rpm 包制作的过程。或许你已经发现,要直接将编译好的二进制文件打包,只需要在 spec 文件中将源码编译的部分略去,而把 %files 段详细写明即可。例如我的 spec 文件:

Name: codeblocks
Version: 13.12
Release: 1
Summary: OpenSource Cross Platform Free C++ IDE
Group: Development/Tools/IDE
License: GPLv3+
URL: http://www.codeblocks.org/
Packager: Huoty kong 

%description
Code::Blocks is the open-source, cross-platform Integrated Development Environment (IDE).

It is based on a self-developed plugin framework allowing unlimited extensibility. Most of its functionality is already provided by plugins.

 Plugins included in the base package are:
  * Compiler frontend to many free compilers
  * Debugger frontend for GDB (and CDB for windows platforms)
  * Source formatter (based on AStyle)
  * Wizard to create new C++ classes
  * Code-completion / symbols-browser (work in progress)
  * Default MIME handler
  * Wizard to create new Code::Blocks plugins
  * To-do list
  * Extensible wizard based on scripts
  * Autosave (saves your work in the unfortunate case of a crash)

%prep

%pre
echo -e '\033[0;31;5m'
echo "------------- Installation Statement -------------"
echo "Cdoeblocks depends on wxwidget 2.8.12 and above."
echo "And depends on boost, gamin, hunspell."
echo "Make sure the system has been installed them."
echo "Otherwise the program will not run properly"
echo -e '\033[0m'


%post

%preun

%postun

%files
%defattr(-,root,root,-)
/usr

在 Ubuntu 下制作 rpm 包时,会默认将 BUILDROOT 目录下的 {name}-{version}-{release}-{arch} 的目录作为 {buildroot} 目录,所以要打包的文件必须放在该目录下才能完成打包,否则 rpmbuild 找不到文件。也就是说,我必须将要打包的 /usr 目录放到 /home/konghy/workdir/rpmbuild/BUILDROOT/codeblocks-13.12-1.i386 目录下才能完成打包。执行打包命令:

rpmbuild -bb --target=i686 SPECS/codeblocks.spec

如果成功构建,会输出如下类似的信息:

构建目标平台:i686
为目标i686构建
处理文件:codeblocks-13.12-1.i686
Provides: codeblocks = 13.12-1 codeblocks(x86-32) = 13.12-1
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
检查未打包文件:/usr/lib/rpm/check-files /home/konghy/workdir/rpmbuild/BUILDROOT/codeblocks-13.12-1.i386
写道:/home/konghy/workdir/rpmbuild/RPMS/i686/codeblocks-13.12-1.i686.rpm
执行(%clean): /bin/sh -e /var/tmp/rpm-tmp.uWPBYI
+ umask 022
+ cd /home/konghy/workdir/rpmbuild//BUILD
+ /bin/rm -rf /home/konghy/workdir/rpmbuild/BUILDROOT/codeblocks-13.12-1.i386

--target 参数用于指定 rpm 包的目标平台,例如要构建龙芯平台的 rpm 包,则 '--target=mipsel',但是这样在打包的过程中将无法获得 %_arch 的值,所以 rpmbuild 无法定位 BUILDROOT 下的目标目录。这个时候,可以用 --define 参数来手动定义 %_arch 宏。具体的命令为:

rpmbuild -bb --target=mipsel --define="%_arch mipsel" SPECS/codeblocks.spec

但是,我用同样的 spec 文件到 centos 5 上打包却除了问题。在类 redhat 系统中需要额外安装 rpmbuild:

yum install rpmbuild rpmdevtools

然后用 rpmdev-setuptree 创建所需的目录结构。我在 centos 5 上打包时,发现 rpmbuild 竟然将根目录/作为{buildroot} 目录。在我执行打包命令时,rpmbuild 竟然开始对系统的 /usr 目录打包,我意识到了不对,立刻终止了进程。我想出现这样的问题应该是 rpmbuild 缺省没有定义 {buildroot} 目录,于是我尝试在 spec 文件中定义,但是没有效果。尝试查看系统定义的宏:

rpmbuild --showrc

也没有找到答案。然后我比较了两个系统中 rpmbuild 的版本,Ubuntu 为 4.11.3,而 Centos 5 则为 4.4.2.3,区别可能就在这。于是我用 rpmbuild --help 查看 rpmbuild 的帮组手册,企图找到答案。果然,发现了一个 --buildroot 参数,这个参数用于重载构建根路径。于是重新执行打包命令:

rpmbuild -bb --buildroot=/root/rpmbuild/BUILDROOT SPECS/codeblocks.spec

构建成功。

参考资料

https://fedoraproject.org/wiki/How_to_create_an_RPM_package/zh-cn

http://blog.csdn.net/justlinux2010/article/details/9905425

http://blog.chinaunix.net/uid-23069658-id-3944462.html

http://www.jinbuguo.com/redhat/rpmbuild.html