红联Linux门户
Linux帮助

rpm打包技术详解

发布时间:2005-12-28 09:16:11来源:红联作者:yy123
RPM 是广泛使用的用于交付 Linux 软件的工具,用户可以轻松的安装用 RPM 打包的产品。在本文中(系列文章的第 1 篇),IBM 软件工程师 Dan Poirier 向您演示如何在 Red Hat Linux 7.1 系统上用 RPM 打包简单软件。
RPM(Red Hat Package Manager)是用于 Linux 分发版(distribution)的最常见的软件包管理器。因为它允许分发已编译的软件,所以用户只用一个命令就可以安装软件。

RPM 是 Linux“标准基本库”版本 1.0.0 指定的安装工具。在 Linux 分发版前 10 名中,有 8 个是基于 RPM(请参阅“Comparison of Linux Distributions”,它位于本文后面的 参考资料中列出的 distrowatch.com 上)。即使某些通常不使用 RPM 的分发版,如 Debian,也有可用工具将 RPM 转换成它们自己的格式。在 Linux 上,对于除开发人员以外的任何人,RPM 也是用来打包软件的最佳选择。

不论您是自己开发软件,还是通过提供代码以从中创建 RPM 软件包向开放源码项目提供帮助,本文都会帮助您入门。顺便要说的是,本系列的后续文章会涉及下列主题:构建 RPM 软件包而未必是 root 用户,在构建软件之前为其打补丁,安装和卸载时运行脚本以及在安装或卸载 其它软件包时运行脚本。

简单实例
我将从简单的实例开始,主要使用 RPM 缺省值。然后,我会添加上几个可选特性。

RPM 软件包从源文件形式的程序开始,作好了编译准备。我不想创建一个没有实际意义的示例,而是选择使用 GNU Indent 程序(请参阅 参考资料)。

在 Linux 下创建 Indent 非常容易。 indent-2.2.6.tar.gz 文件位于当前目录之中,所有您要做的就是:

手工构建 indent
$ tar xzf indent.2.2.6.tar.gz
$ cd indent-2.2.6
$ ./configure
$ make
$ make install



如果您已经构建过很多开放源码项目,那么这可能看起来很熟悉。 unpack;./configure;make;make install 序列是典型的使用 GNU 自动配置(autoconf)工具的软件。因为这太普通了,所以我在这里描述的关于 indent 的大部分东西,几乎不加改变就可以用于其它开放源码项目。

现在假设您正在使用 Red Hat 7.1。在本文的后面,我将给出一些 在其它 Linux 分发版上使用 RPM 的建议。

在 Red Hat 7.1 上, 在继续以前,请确保您已安装了 rpm-build 软件包。为检查是否安装,请运行 rpm -q rpm-build 。您应该看到一些类似于 rpm-build-4.0.2-8 的信息(版本可能会不同)。如果您看到的是 package rpm-build is not installed ,则将需要从 Red Hat 安装 CD 上安装它。

制作基本 RPM 软件包
为了构建 RPM 软件包,您需要写一个名为 spec 文件的 RPM 输入文件,该文件告诉 RPM 如何构建和打包您的软件。编写 spec 文件您需要:

创建文件 indent-1.spec,如下所示。您可以任意地给它命名并把它放到任何地方;RPM 对这些没有要求。
以 root 用户登录。
将 indent-2.2.6.tar.gz 文件复制到 /usr/src/redhat/SOURCES。
运行 rpm -ba indent-1.spec ,将 indent-1.spec 改为您使用的名字。
第一个 spec 文件:indent-1.spec
Summary: GNU indent
Name: indent
Version: 2.2.6
Release: 1
Source0: %{name}-%{version}.tar.gz
License: GPL
Group: Development/Tools
%description
The GNU indent program reformats C code to any of a variety of
formatting standards, or you can define your own.
%prep
%setup -q
%build
./configure
make
%install
make install
%files
%defattr(-,root,root)
/usr/local/bin/indent
%doc /usr/local/info/indent.info
%doc %attr(0444,root,root) /usr/local/man/man1/indent.1
%doc COPYING AUTHORS README NEWS



您应该看到 RPM 解包这个 tar 文件,编译并安装它。 在 Red Hat 7.1 上,工作目录将是 /usr/src/redhat/BUILD。

最后,RPM 将创建两个 RPM 文件。将在 /usr/src/redhat/SRPMS/indent-2.2.6-1.src.rpm 中创建一个源 RPM 文件,而在 /usr/src/redhat/RPMS/i386/indent-2.2.6-1.i386.rpm 中创建一个二进制 RPM 文件。

源 RPM 文件简单地捆绑了 spec 文件和构建软件包用到的所有源文件和补丁文件。如果您选择分发它,则其他人可以很容易地用它重建您的软件。二进制 RPM 文件仅包含已编译的软件和如何安装的信息。

RPM 做什么
下面总结了在您运行 rpm -ba filename.spec 时,RPM 都做些什么:

读取并解析 filename.spec 文件
运行 %prep 部分来将源代码解包到一个临时目录,并应用所有的补丁程序。
运行 %build 部分来编译代码。
运行 %install 部分将代码安装到构建机器的目录中。
读取 %files 部分的文件列表,收集文件并创建二进制和源 RPM 文件。
运行 %clean 部分来除去临时构建目录。
spec 文件的内容
spec 文件有几个部分。第一部分是未标记的;其它部分以 %prep 和 %build 这样的行开始。


第一部分(未标记)定义了多种信息,其格式类似电子邮件消息头。

Summary 是一行关于该软件包的描述。

Name 是该软件包的基名, Version 是该软件的版本号。 Release 是 RPM 本身的版本号 — 如果修复了 spec 文件中的一个错误并发布了该软件同一版本的新 RPM,就应该增加发行版号。

License 应该给出一些许可术语(如:“GPL”、“Commercial”、“Shareware”)。

Group 标识软件类型;那些试图帮助人们管理 RPM 的程序通常按照组列出 RPM。您可以在 /usr/share/doc/rpm-4.0.2/GROUPS 文件看到一个 Red Hat 使用的组列表(假设您安装的 RPM 版本是 4.0.2)。但是您还可以使用那些组名以外的名称。

Source0 、 Source1 等等给这些源文件命名(通常为 tar.gz 文件)。 %{name} 和 %{version} 是 RPM 宏,它们扩展成为头中定义的 rpm 名称和版本。因此,在这个实例中, Source0 被设置为 indent-2.2.6.tar.gz 。

不要在 Source 语句中包含任何路径。缺省情况下,RPM 会在 /usr/src/redhat/SOURCES 中寻找文件。请将您的源文件复制或链接到那里。(要使 spec 文件尽量可移植的话,应当尽量避免嵌入自己开发机器上的假想路径。其他开发人员就可以指示 RPM 在别的目录下查找源文件,而不用修改您的 spec 文件。)

描述
接下来的部分从 %description 行开始。您应该在这里提供该软件更多的描述,这样任何人使用 rpm -qi 查询您的软件包时都可以看到它。您可以解释这个软件包做什么,描述任何警告或附加的配置指令,等等。

Shell 脚本
下面几部分是嵌入 spec 文件中的 shell 脚本。

%prep 负责对软件包解包。在最常见情况下,您只要用 %setup 宏即可,它会做适当的事情,在构建目录下解包源 tar 文件。加上 -q 项只是为了减少输出。

%build 应该编译软件包。该 shell 脚本从软件包的子目录下运行,在我们这个例子里是 indent-2.2.6 目录,因而这常常与运行 make 一样简单。

%install 在构建系统上安装软件包。这似乎和 make install 一样简单,但通常要复杂些。我将在下面解释这点。

文件列表
%files 列出应该捆绑到 RPM 中的文件,并能够可选地设置许可权和其它信息。

在 %files 中,您可以使用 一次 %defattr 来定义缺省的许可权、所有者和组;在这个示例中, %defattr(-,root,root) 会安装 root 用户拥有的所有文件,使用当 RPM 从构建系统捆绑它们时它们所具有的任何许可权。

可以用 %attr(permissions,user,group) 覆盖个别文件的所有者和许可权。

可以在 %files 中用一行包括多个文件。

可以通过在行中添加 %doc 或 %config 来标记文件。 %doc 告诉 RPM 这是一个文档文件,因此如果用户安装软件包时使用 --excludedocs ,将不安装该文件。您也可以在 %doc 下不带路径列出文件名,RPM 会在构建目录下查找这些文件并在 RPM 文件中包括它们,并把它们安装到 /usr/share/doc/%{name}-%{version} 。以 %doc 的形式包括 README 和 ChangeLog 这样的文件是个好主意。

%config 告诉 RPM 这是一个配置文件。在升级时,RPM 将会试图避免用 RPM 打包的缺省配置文件覆盖用户仔细修改过的配置。

警告:如果在 %files 下列出一个目录名,RPM 会包括该目录下的所有文件。通常这不是您想要的,特别对于 /bin 这样的目录。

避免简单实例中的问题
这个最基本的 spec 文件有几个问题。最大的问题之一就是您最后在构建系统上实际安装了该产品。而这可能只是一个软件测试版本,您也许并不想在构建系统中安装它。

RPM 用一个名为 构建根(build root)的特性来处理这个问题。它的想法是设置您的 spec 文件,以将所有安装的文件复制到一个虚拟目录树(从构建根开始);然后 RPM 从那里得到文件。

但是,这需要一些软件包的支持。在包括 indent 在内的很多 GNU 软件包中,在 make install 的时候定义 DESTDIR 将会在所有安装路径之前添加 DESTDIR 值。

请注意 不要使用 ./configure --prefix=$RPM_BUILD_ROOT 。这会在假设整个软件包文件的最终位置是构建根的情况下安装整个软件包。这对于 indent 可能没有关系,但任何需要在运行时找到其安装文件的程序都将失败,因为当 RPM 最终安装到用户系统后,这些文件就不再位于构建根之下 — 那只是您构建系统上的一个临时目录。

请参阅更新的文件 indent-2.spec,如下所示。

第二个 spec 文件:indent-2.spec
Summary: GNU indent
Name: indent
Version: 2.2.6
Release: 2
Source0: %{name}-%{version}.tar.gz
License: GPL
Group: Development/Tools
BuildRoot: %{_builddir}/%{name}-root
%description
The GNU indent program reformats C code to any of a variety of
formatting standards, or you can define your own.
%prep
%setup -q
%build
./configure
make
%install
rm -rf $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root)
/usr/local/bin/indent
%doc /usr/local/info/indent.info
%doc %attr(0444,root,root) /usr/local/man/man1/indent.1
%doc COPYING AUTHORS README NEWS



更改说明
首先,我们增加了版本的发行号。无论何时,当您编辑 spec 文件时,都不要忘了这么做。

我们在头中添加了 BuildRoot,以便告诉 RPM 这是在构建期间临时安装文件的地方。对于临时文件,我们这里使用了两个 RPM 宏,而不是假设某个特定位置。在 Red Hat 7.1 上, %{_builddir} 以类似于 /usr/src/redhat/BUILD 结束。

我们还需要告诉系统将 indent 安装在那里。RPM 帮助我们用构建根的值定义一个 shell 变量 RPM_BUILD_ROOT ,因此在 make install 时,我们只需将它作为 DESTDIR 值传入即可。

我们还在 %install 和 %clean 中添加了几行,以便在开始安装以前(为保险起见)和完成以后清除构建根。%clean 是一切都正常的情况下在 RPM 构建结束时运行的脚本,这样临时文件就不会一直保留。

最后,在 %files 中,请注意我们没有在此处的路径前包括 BuildRoot。我们使用了“真正”的路径;RPM 将在构建根下寻找这些文件,因为您已经包括了 BuildRoot 定义。

这一次发生了什么
如果仔细观察您会发现,在 RPM 进行安装部分以前,一切工作照旧。然后,文件将不直接安装到 /usr/local/bin,而是安装在(比如说)/usr/src/redhat/BUILD/indent-root/usr/local/bin 中。

如果您检查最终的二进制 RPM 文件(用 rpm -qlp indent-2.2.6-2.i386.rpm ),您会看到构建根已被 RPM 除去。如果您安装 RPM,这些文件最终将安装在正确的目录,如 /usr/local/bin/indent 中。

在其它 Linux 分发版上使用 RPM
如果您在使用不同的 Linux 分发版,RPM 可能会有不同的内置路径。例如,它几乎肯定不会在 /usr/src/redhat 查找源文件!要确定希望的 RPM 安装路径,请运行 rpm --showrc 并查看下列部分如何被定义:

_sourcedir
RPM 在哪里查找源文件(tar 文件,等)
_srcrpmdir
RPM 在哪里放入新的源 RPM 文件
_rpmdir
RPM 将把新的二进制 RPM 文件放在哪里(在特定于体系结构的子目录中)
其中一些根据其它变量定义;例如,当您看到 %{_topdir} ,查找 _topdir 的定义,等等。

下一步是什么
我希望这篇用 RPM 打包软件的介绍会对您有所帮助。有关相关的阅读材料,请参阅下面的 参考资料。在本系列的后续文章中,我们将讨论这些主题:

构建 RPM 软件包而不必是 root 用户
在创建软件之前为软件打补丁
在安装和卸载时运行脚本
在安装或卸载 其它软件包时运行脚本
文章评论

共有 8 条评论

  1. a737714673 于 2013-05-16 17:12:10发表:

    好东西学习

  2. dhtustmqj 于 2013-05-14 19:55:25发表:

    ok[img][/img]

  3. 证券 于 2013-05-07 23:22:43发表:

    学习了!!!

  4. hgybjn 于 2013-05-07 00:11:13发表:

    做个标记

  5. 99390939 于 2013-05-04 10:46:27发表:

    爱上

  6. 于 2013-05-03 17:09:10发表:

    你好,请问,我rpmbuild打包rsync,安装打包好的rpm包时提示需要perl(packaging::git-status.pl)这个依赖,但是实际上是不需要的。在rhel6下解压一个包时,我rpmbuild打包正常,但是安装时还是需要依赖,但是依赖都是不需要的,而且rpm包里自身都自带,可以强制安装,都没问题,但是yum里面不支持--nodeps选项,请问打包时怎么去掉这个不需要的依赖呢?

  7. yy123 于 2005-12-28 09:17:16发表:

    在安装和卸载时运行脚本

    RPM 是一种广泛用于交付 Linux 软件的工具; 用户可以轻松地安装用 RPM 打包的产品。在本文(该系列文章的第 2 篇)中,Dan 说明了在不具备 root 权限的情况下如何对软件进行打包,在不做更改的情况下如何处理不在 Linux 上构建的软件,以及如何分发您的工作结果。

    当在用户机器上安装或卸载程序时,能够执行命令将是很有用的。 例如,您可能需要编辑一个系统配置文件以启用新的服务,或者需要定义一个新用户以拥有正在安装的程序的所有权。

    为了最有效地使用本文中的建议和示例,请先阅读本系列文章的 第 1 部分和 第 2 部分, 它们分别向您演示了如何使用 RPM 以及如何分发您的产品。

    安装和卸载脚本的工作原理
    安装和卸载脚本看起来很简单,但它们工作原理中的一些意外可能会引起大问题。

    这里是一些基本信息。可以将下列四节中的任意一个添加到 .spec 文件, 它列出了在您的包安装期间各个点上运行的 shell 脚本:

    %pre
    在安装包之前运行
    %post
    在安装包之后运行
    %preun
    在卸载包之前运行
    %postun
    在卸载包之后运行
    尤其要注意 %install 与这些节之间的差异。构建 RPM 时, %install 在开发机器上运行;它应该将产品安装在开发机器上或安装到一个构建根目录中。 另一方面,这些节指定当用户正在安装或卸载您的 RPM 包时将在 用户的机器上运行什么。

    这里有一个示例,是在前文基础上建立的。我们要使用 install-info 将 GNU indent 的 info 文件添加到目录中。

    清单 1. indent-4.spec
    # Simplistic example of install scripts - do not use
    Summary: GNU indent
    Name: indent
    Version: 2.2.6

    Release: 4

    Source0: %{name}-%{version}.tar.gz
    License: GPL
    Group: Development/Tools
    BuildRoot: %{_builddir}/%{name}-root
    %description
    The GNU indent program reformats C code to any of a variety of
    formatting standards, or you can define your own.
    %prep
    %setup -q
    %build
    ./configure
    make
    %install
    rm -rf $RPM_BUILD_ROOT
    make DESTDIR=$RPM_BUILD_ROOT install

    %post

    if [ -x /sbin/install-info ]; then
    /sbin/install-info /usr/local/info/indent.info /usr/local/info/dir
    fi
    %preun
    if [ -x /sbin/install-info ]; then
    /sbin/install-info --delete /usr/local/info/indent.info /usr/local/info/dir
    fi
    %clean
    rm -rf $RPM_BUILD_ROOT
    %files
    %defattr(-,root,root)
    /usr/local/bin/indent
    %doc /usr/local/info/indent.info
    %doc %attr(0444,root,root) /usr/local/man/man1/indent.1
    %doc COPYING AUTHORS README NEWS



    请注意,在尝试使用 install-info 工具之前,首先对它进行检查。我们不希望只是因为我们不能提供至产品文档的链接而使安装失败。

    但也有可能在某种情况下您希望安装过程失败。一种好的技术是使用 %pre 脚本来检查安装前提条件,它们比 RPM 可以直接支持的更复杂。 如果不符合前提条件,那么脚本以非零状态退出,而且 RPM 不会继续安装。

    另外请注意,我们必须小心地使用卸载脚本来撤销安装脚本。

    没有那么简单:升级使每件事情都变得复杂
    现在,让我们着手升级。如果用户只安装和删除您自己的包,那么前面节中的指令将正常工作;但在升级期间,它们会完全失效。

    以下是 RPM 如何执行升级:

    运行新包的 %pre
    安装新文件
    运行新包的 %post
    运行旧包的 %preun
    删除新文件未覆盖的所有旧文件
    运行旧包的 %postun
    如果我们使用前面的示例来升级,那么 RPM 最后将运行 %postun 脚本, 它将除去我们在安装脚本中所做的所有工作!使用 RPM 的一般开发人员可能不会想到这一点。 我不会尝试解释其原因,只是解释您必须为此做点什么。

    相当幸运的是,在一定程度上,脚本有一种方法可以告之是否正在安装、删除或升级包。每个脚本都被传递单一命令行参数 — 一个数字。 这应该告诉脚本 在当前包完成安装或卸载之后将安装多少个包的副本。

    只查看在各种情况下传递的值或许更容易,而不是尝试计算它。

    这里是在安装期间传递的实际值:

    运行新包的 %pre (1)
    安装新文件
    运行新包的 %post (1)
    这里是在升级期间传递的值:

    运行新包的 %pre (2)
    安装新文件
    运行新包的 %post (2)
    运行旧包的 %preun (1)
    删除新文件未覆盖的任何旧文件
    运行旧包的 %postun (1)
    这里是在删除期间传递的值:

    运行旧包的 %preun (0)
    删除文件
    运行旧包的 %postun (0)
    可以通过将类似下例的一些东西添加到您的包中来自己测试它。然后创建一个带稍高发行版号的新包,安装第一个,然后升级到第二个,最后卸载它,以查看所有可能性。当然,在信任的公共社区上发布任何 RPM 之前,您总是要对它进行几次这样的尝试。

    清单 2. 脚本执行的测试顺序和参数
    %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



    这里是另一个示例,这次正确地处理升级过程:

    清单 3. indent-5.spec
    Summary: GNU indent
    Name: indent
    Version: 2.2.6

    Release: 5

    Source0: %{name}-%{version}.tar.gz
    License: GPL
    Group: Development/Tools
    BuildRoot: %{_builddir}/%{name}-root
    %description
    The GNU indent program reformats C code to any of a variety of
    formatting standards, or you can define your own.
    %prep
    %setup -q
    %build
    ./configure
    make
    %install
    rm -rf $RPM_BUILD_ROOT
    make DESTDIR=$RPM_BUILD_ROOT install
    %post

    if [ "$1" = "1" ] ; then # first install

    if [ -x /sbin/install-info ]; then
    /sbin/install-info /usr/local/info/indent.info /usr/local/info/dir
    fi

    fi

    %preun

    if [ "$1" = "0" ] ; then # last uninstall

    if [ -x /sbin/install-info ]; then
    /sbin/install-info --delete /usr/local/info/indent.info /usr/local/info/dir
    fi

    fi

    %clean
    rm -rf $RPM_BUILD_ROOT
    %files
    %defattr(-,root,root)
    /usr/local/bin/indent
    %doc /usr/local/info/indent.info
    %doc %attr(0444,root,root) /usr/local/man/man1/indent.1
    %doc COPYING AUTHORS README NEWS



    现在,仅当完全删除这个包时才会除去 info 链接。

    触发器 -- 在安装或卸载其它包时运行脚本
    假设在安装或卸载 其它包时要运行您包中的一些代码。 可以用 触发器(trigger)脚本来完成这一任务。

    您为什么要这样做?通常是因为您的包使用一个或多个其它包的服务,或者提供服务给一个或多个其它包。

    这里有一个示例。假设您正在为 Emacs 和 Xemacs 编辑器打包一个极好的附加工具。它可以与其中任何一个或两个编辑器一起工作,但根据所安装的编辑器,需要做一些少量的配置。

    安装时,可以对 Emacs 和 Xemacs 进行测试,并配置您的工具以使可用编辑器可以访问它。 但是,如果用户稍后安装 Xemacs,那么会发生什么情况呢?您的工具在 Xemacs 中不可用,除非用户卸载并重新安装您的工具。 如果您的包可以告诉 RPM,“让我知道是否安装了 Xemacs”,这是否会更好呢?

    这是触发器脚本的思想。可以将它添加到 .spec 文件中:

    清单 4. 触发器示例
    %triggerin -- emacs
    # Insert code here to run if your package is already installed,
    # then emacs is installed,
    # OR if emacs is already installed, then your package is installed
    %triggerin -- xemacs
    # Insert code here to run if your package is already installed,
    # then xemacs is installed,
    # OR if xemacs is already installed, then your package is installed
    %triggerun -- emacs
    # insert code here to run if your package is already installed,
    # then emacs is uninstalled
    %triggerun -- xemacs
    # insert code here to run if your package is already installed,
    # then xemacs is uninstalled
    %postun
    # Insert code here to run if your package is uninstalled



    触发器脚本被传递了 两个参数。第一个参数是当触发器脚本完成运行时将安装的 您的包的实例数。第二个参数是当触发器脚本完成运行时将安装的 要触发的包的实例数。

    这里是 RPM 升级期间脚本执行和文件安装及卸载的完整顺序,它来自 RPM 分发版中的 triggers 文件:

    清单 5. 脚本顺序
    new-%pre for new version of package being installed
    ... (all new files are installed)
    new-%post for new version of package being installed
    any-%triggerin (%triggerin from other packages set off by new install)
    new-%triggerin
    old-%triggerun
    any-%triggerun (%triggerun from other packages set off by old uninstall)
    old-%preun for old version of package being removed
    ... (all old files are removed)
    old-%postun for old version of package being removed
    old-%triggerpostun
    any-%triggerpostun (%triggerpostun from other packages set off by old un
    install)



    高级脚本编制


    备用解释器
    通常,所有安装时脚本和触发器脚本都是使用 /bin/sh shell 程序运行的。如果您更喜欢另一个脚本语言,比方说 Perl,那么可以通过将 -p interpreter 添加到脚本行来告诉 RPM 应该使用另一种解释器运行您的脚本。例如:

    清单 6. 备用解释器示例
    %post -p /usr/bin/perl
    # Perl script here
    %triggerun -p /usr/bin/perl -- xemacs
    # Another Perl script here



    请注意,这 不适用于 RPM 的构建时脚本,如 %install 。

    RPM 变量
    RPM 在将 RPM 变量存储到 RPM 包文件之前先在您的脚本中扩充它们,有时候这是有用的。 例如,可以在 .spec 文件顶部附近定义您自己的参数,然后在整个 .spec 文件 — 甚至在您的脚本中使用 %{variable_name} 引用它们:

    清单 7. RPM 变量示例
    ...
    %define foo_dir /usr/lib/foo
    ...
    %install
    cp install.time.message $RPM_BUILD_ROOT/%{foo_dir}
    %files
    %{foo_dir}/install.time.message
    %post
    /bin/cat %{foo_dir}/install.time.message



    要避免的事情
    您可能会在安装时试图做一些事情,但结果会证明这是一个坏主意。 例如,与用户交互的任何尝试或许不能很好地工作。RPM 被设计成在无需用户出现的情况下允许进行批处理安装。 如果在安装期间 RPM 包停下来并提出问题,而没有人看到这个问题,那么安装将一直挂起。

    您可能要避免的另一件事情是启动任何服务。在完整安装期间,您不能确定程序所需的每样东西是否已经在那里(例如,可能还没有任何网络);另外,如果在完整操作系统安装期间每个 RPM 服务都尝试启动,那么整个安装过程大概会花很长时间。这种情况下您可以做的就是打印消息,告诉用户有关任何所需配置或需要启动的服务的信息。 如果用户正在手工安装您的 RPM 包,那么他或她将看到这些消息;如果它是较大批处理安装的一部分,那么它不会损害任何东西,机器几乎肯定在结束时重新引导,启动您的服务。

    要记住的事情
    如果您的包安装了 init 脚本,则可以使用 chkconfig 来安排将在适当运行级别上启动和停止的服务。虽然可以通过将必需的符号链接直接安装为包的一部分来实现同一件事情, 但要使它们恢复正常会有很多麻烦,您可能宁愿使用 chkconfig 。

    为了安全起见,许多服务在一个特定的用户标识下运行;如果您的服务是这样的话,当系统上不存在该用户时,您需要在系统上创建它。

    如果您的包安装了任何 GNU info 文件,那么在 Info 目录中将看不到它们,除非在安装时使用 install-info 工具添加它们。

    当然,在卸载之前,必须试图停止您的包可能正在运行的任何服务(但如果服务不在运行,请确保卸载不会失败)。

    当然,在卸载时,应该将您在安装时可能对系统做的大多数更改恢复成原来状态。 但稍微考虑一下您的操作;例如,卸载 RPM 包时不应该意外地删除任何用户创建的文件。 所以,请不要尝试除去用户标识或删除整个目录树,这样可能会好一些。

  8. yy123 于 2005-12-28 09:16:45发表:

    在不具备 root 权限的情况下构建、给软件打补丁和分发 RPM
    RPM 是一种广泛用于发布 Linux 软件的工具; 用户可以轻松地安装用 RPM 打包的产品。在本文(该系列文章的第 2 篇)中,Dan 说明了在不具备 root 权限的情况下如何对软件进行打包,在不做更改的情况下如何处理不在 Linux 上构建的软件,以及如何分发您的工作结果。

    如果您没有阅读过本系列文章的 第 1 部分, 那么您可能希望在继续阅读本文之前先阅读那篇文章。

    不作为 root 用户来构建 RPM 包
    正如您在第 1 部分中看到的那样,构建 RPM 软件包通常要求您以 root 用户登录。 其原因如下:

    RPM 在打包过程中安装软件,并且通常只有 root 用户可以写到安装目录中。
    RPM 需要读写 /usr/src/redhat(一般用户不能修改它)下的目录。


    我们在 第 1 部分中探讨了通过用 RPM 构建根(build root)来解决第一个问题。

    要解决第二个问题,可以通过更改 %_topdir 设置来告诉 RPM 查找和创建不同目录集中的文件。按照下面的方法在您的主目录下创建一个名为 .rpmmacros的文件:

    %_topdir /home/your_userid/rpm

    这个文件会告诉 RPM:它先前在 /usr/src/redhat 下查找的所有目录应该改为在 /home/your_userid/rpm 下查找。 现在,您应该创建这样一个完整的目录树:~/rpm ~/rpm/SOURCES ~/rpm/SPECS ~/rpm/BUILD ~/rpm/RPMS ~/rpm/RPMS/i386 ~/rpm/SRPMS

    ~/rpm
    ~/rpm/SOURCES
    ~/rpm/SPECS
    ~/rpm/BUILD
    ~/rpm/RPMS
    ~/rpm/RPMS/i386
    ~/rpm/SRPMS

    (如果愿意,可以通过在 RPM 中重新定义其它宏,来将其中任何目录放在您想放的任何地方。您可能需要考虑更改的一些宏包括 %_sourcedir 、 %_specdir 、 %_srcrpmdir 、 %_builddir 和 %_rpmdir 。 有关这些宏的缺省值,请查看 /usr/lib/rpm/macros。 对于这个示例,我们仅仅将它们都放在 ~/rpm 下。)

    现在,将 indent-2.2.6.tar.gz 文件(请参阅本文后面的 参考资料)复制到 ~/rpm/SOURCES,这里 没有以 root 用户登录,运行 rpm -ba indent-2.spec (这些文件就是 第 1 部分中的那些文件)。RPM 将 把 indent 构建在 ~/rpm/BUILD 目录下,并将二进制的 RPM 包放在 ~/rpm/RPMS/i386 中,将源代码包放在 ~/rpm/SRPMS 中。

    与之相对照,在没有构建根的情况下,尝试使用 spec 文件 indent-1.spec 。RPM 在尝试将 indent 安装到 /usr/local/bin 中时会失败。

    告诫
    使用构建根和设置 RPM 的 i%_topdir 使您能在不作为 root 运行的情况下构建许多软件包,但这并不总是很容易。

    首先,一些包并不象 indent 那样可以容易地安装到构建根目录中。对于那些任何未用 GNU autoconf 来开发的包,您必须要仔细查看一下,看是否有一种方法,可以将包安装到另一个目录中, 这也许可以修改 Makefile 来强制这样做。 在下一部分中,我将向您演示如何使用 RPM 来构建已修改的程序。

    其次,只有相当少部分包将在其正常安装期间试图做一些只有 root 用户才可以做的事情,如:

    创建特殊文件(管道、设备文件等等)
    修改系统配置文件`


    您必须逐个处理这些问题。通常,您可以在 post-install 脚本(在安装 RPM 之后运行的脚本)中做一些必要工作。 我将在以后的文章中讨论它们,但简而言之,可以将“%post”节添加到 spec 文件中, 并在该节中放置一些 Linux 命令,以便在安装 RPM 之后运行这些命令。

    给软件打补丁
    假设您有一些要打成 RPM 包的软件, 但如果不对它做一些更改,就不能在 Linux 上构建它。 然而,您又没有拥有该软件,所以不能正式地更改它。

    您所需要做的就是对该软件的正式版本进行 打补丁或做一些修改。 但是,对其他人的软件进行修改,然后再分发此修改过的版本通常被认为是不礼貌的,所以您希望您自己所做的更改对别人来说也是可用的。 这样,使用您的包的任何人都可以看到您做了哪些事情以及确定您所做的更改是否是可接受的。

    这是常有的事,而且 RPM 也提供一些帮助。 您可以建立一个 RPM 包,以便二进制 RPM 文件包含您对程序所做的修改,并且源 RPM 唤霭???嫉脑创?耄?而且还包含您所做的更改以及有关如何应用和构建这些更改的所有详细信息。

    这些步骤如下:

    断定对源代码做哪些更改就可使软件工作。
    创建一个 补丁文件来捕获您所做的更改。
    将该补丁添加到 RPM spec 文件。


    第 1 步. 断定要对源代码做哪些更改
    通常,要做的第一件事是,在没有 RPM 的情况下编译并运行软件。明了必须要更改的那些文件。 如果有必要,可创建一些新文件或除去与原始源代码一起交付的一些文件。

    对于这个示例,我将代码抽取到目录 indent-2.2.6-working 中。我修改了 indent.c, 以便在程序启动时打印一条表示友好的消息,然后验证该程序是否仍可以构建以及该程序是否仍能工作。

    第 2 步. 创建补丁文件来捕获您所做的更改
    现在,您希望创建一个仅捕获您所做更改的补丁文件。 这里有一种可以实现这的方法。虽然这有点儿乏味,但能够确保您捕获所有更改。

    将软件完全抽取到一个新目录中,然后复制您已对其做过更改的文件,使其覆盖刚抽取出的那些文件。 这样,在本来应该在您先前构建和测试软件时创建的目录中就不会有任何多余的文件。 类似地,复制您创建的任何新文件,并删除任何您先前删除过的文件。
    对于这个示例,我已完全抽取到目录 indent-2.2.6-my,并覆盖文件 indent.c。

    再一次将软件抽取到另一个目录。这样就提供了一个原始软件的副本来与您的软件进行比较。对于这个示例,我是将软件抽取到 indent-2.2.6 目录。
    现在,已有三个目录:

    indent-2.2.6-working
    工作目录
    indent-2.2.6-my
    经过更改的软件,其中含有我所做的更改
    indent-2.2.6
    未更改的软件


    从这三个目录的父目录中用类似于以下的命令生成补丁文件: diff -uNr indent-2.2.6 indent-2.2.6-my >indent-2.2.6.patch
    注意,使用 diff 时运用了选项 -uNr 。 -u 以 统一格式创建补丁文件,这种格式比缺省格式更紧凑些。 -N 确保补丁文件将正确地处理已经创建或删除文件的情况。 -r 比较命令行上所给出的两个目录的所有子目录中的所有文件。

    另外还要注意:只要您完全按上述来做,这些目录名是无关紧要的。 补丁文件中将有这些目录名,但我们将通知补丁程序忽略它们。

    现在,检查一下补丁文件 indent-2.2.6.patch。下面是我的示例:

    清单 1. indent-2.2.6.patch

    diff -uNr indent-2.2.6/indent.c indent-2.2.6-my/indent.c
    --- indent-2.2.6/indent.c Thu Nov 16 22:01:04 2000
    +++ indent-2.2.6-my/indent.c Wed Sep 26 14:33:11 2001
    @@ -1864,6 +1864,8 @@
    int using_stdin = false;
    enum exit_values exit_status;
    + printf("Hello from Dan
    ");
    +
    #ifdef _WIN32
    /* wildcard expansion of commandline arguments, see wildexp.c */
    extern void wildexp (int *argc, char ***argv);


    有时候,您会注意到 diff 检查出了您无意要做的更改。 这时,您可能需要回过去,清除您的代码并再次生成补丁,直到获得一个干净的、令您满意的补丁文件为止。

    一旦按您所希望的那种方式完成补丁之后,最好添加注释以说明您所做的更改。 在不损害任何内容的情况下,在补丁文件的开始处或结束处添加文本。

    清单 2. 带注释的 indent-2.2.6.patch

    Dan Poirier - 2001-09-26 - added a friendly greeting as indent starts.
    This is just an example.
    diff -uNr indent-2.2.6/indent.c indent-2.2.6-my/indent.c
    --- indent-2.2.6/indent.c Thu Nov 16 22:01:04 2000
    +++ indent-2.2.6-my/indent.c Wed Sep 26 14:33:11 2001
    @@ -1864,6 +1864,8 @@
    int using_stdin = false;
    enum exit_values exit_status;
    + printf("Hello from Dan
    ");
    +
    #ifdef _WIN32
    /* wildcard expansion of commandline arguments, see wildexp.c */
    extern void wildexp (int *argc, char ***argv);


    第 3 步. 将该补丁添加到 RPM spec 文件中
    现在,该让 RPM 使用您的补丁了。将该补丁文件复制到您的 SOURCES 目录(如果您遵循了先前的建议,则或许是 ~/rpm/SOURCES),然后对 spec 文件做下列更改:

    清单 3. indent-3.spec:使用 indent-2.2.6.patch

    Summary: GNU indent
    Name: indent
    Version: 2.2.6
    Release: 3
    Source0: %{name}-%{version}.tar.gz
    Patch0: %{name}-2.2.6.patch
    License: GPL
    Group: Development/Tools
    BuildRoot: %{_builddir}/%{name}-root
    %description
    The GNU indent program reformats C code to any of a variety of
    formatting standards, or you can define your own.
    %prep
    %setup -q
    %patch -p1
    %build
    ./configure
    make
    %install
    rm -rf $RPM_BUILD_ROOT
    make DESTDIR=$RPM_BUILD_ROOT install
    %clean
    rm -rf $RPM_BUILD_ROOT
    %files
    %defattr(-,root,root)
    /usr/local/bin/indent
    %doc /usr/local/info/indent.info
    %doc %attr(0444,root,root) /usr/local/man/man1/indent.1
    %doc COPYING AUTHORS README NEWS



    现在,用 rpm -ba indent-3.spec 命令构建您的包。 如果您密切关注构建过程的话,会看到在构建期间 RPM 应用了您的补丁。

    %Patch0: %{name}-2.2.6.patch 这一行告诉 RPM 第一个补丁文件名。 如果有必要,可以添加 %Patch1 、 %Patch2 等。

    在 %prep 部分中的 %patch -p1 行是一个 RPM 宏, 它将在您系统的构建目录中运行补丁程序,其中把第一个补丁文件作为输入。 需要将 -p1 传递给补丁程序,告诉它从补丁文件中的路径中剥去一层目录,因为该补丁文件包含 indent-2.2.6 目录名,而 RPM 将在该目录内运行该补丁文件。

    通过示例学习
    既然,您理解了有关如何构建 RPM 包的基础知识,则可以通过研究一些示例来学习更多的知识。 最好的源代码示例之一是您自己的 Linux 分发版。例如,RedHat 带有包含源 RPM 包的整张 CD。 以下是如何使用它们。

    源 RPM 包包含:

    一个 .spec 文件
    一个或多个源文件
    所有使用过的补丁文件


    与安装二进制 RPM 包类似,可以使用 rpm -i filename.rpm 安装源 RPM 包。 安装完之后,.spec 文件将在您的 %_specdir 目录中,源文件和补丁文件将在您的 %_sourcedir 目录中。 如果创建了上面描述的 .rpmmacros 文件,那么这些目录为 ~/rpm/SPECS 和 ~/rpm/SOURCES。

    现在,可以读取 Red Hat 自己分发的这些包的 .spec 文件。 可以尝试用 rpm -ba foo.spec 构建这些 .spec 文件,并观察所发生的事情,以及摆弄 spec 文件以尝试一些新的事物。

    对于 GNU indent 程序,一个好的方法是从 Red Hat 的源 RPM 包开始。看一下您是否可以想出为什么它们的 .spec 文件不同于本文中的 .spec 文件。

    二进制 RPM 包的可移植性
    不幸的是,二进制 RPM 包在可移植性方面不是很好。多数情况下,构建在某个 Linux 分发版上的 RPM 不能应用到另一个 Linux 分发版。 更不要说应用到同一个分发版的另一个版本上!

    原因有很多,包括基本内核版本、库版本和目录结构方面的差异。

    这很不幸,但象 Linux Standard Base(请参阅 参考资料)这样的组织正在尝试达到分发版之间的一致, 以解决难以移植的问题。也许有一天,构建在一个主流 Linux 分发版上的任何 RPM 都可以安装和运行在相同的处理器之上的任何 其它主流 Linux 分发版上。

    至于现在,您应该做好计划,有多少个 RPM 将要在其上运行的分发版,就可能构建有多少个 RPM,或者寻找志愿者来为您完成这件事。

    分发您的工作结果:tar 文件和源 RPM 包
    为了使其他人在尽可能多的分发版上构建您的软件,就要使 .spec 文件和补丁文件成为可用的文件。

    如果有必要,最好的方法是直接更改软件,所以该方法将在 Linux 上进行构建,并将 .spec 文件包含在分发版中。 如果 .spec 文件在带有源码的 tar 压缩包(.tar.gz 文件)中,那么用户只需运行:

    rpm -tb foo.tar.gz


    并构建该包的二进制 RPM — 甚至无需解压该 tar 文件!

    如果无法使 .spec 文件包含在软件中,则可以分发一个源 RPM 包。 有了这,用户就可以运行:

    rpm --rebuild foo.src.rpm


    并在他们的系统上构建二进制 RPM。