Home Wiki > openSUSE:Packaging scriptlet snippets
Sign up | Login

openSUSE:Packaging scriptlet snippets

tagline: 来自openSUSE


RPM 小脚本菜谱

RPM spec 文件有数个章节,允许软件包在安装和卸载时运行小脚本。这些小脚本经常被用来使用软件包中的信息无缝更新系统。本文提供了关于 RPM 小脚本的一个快速概览和一系列软件包中常用的小脚本菜谱。想要找到更多更完整的小脚本用法,请参考 Maximum RPM 这本书。

内容:

语法

小脚本片段的基本语法和 RPM spec 文件的 %build, %install 以及其他章节都很像。小脚本支持一个特殊的参数,-p,它可以允许小脚本直接调用程序而不用去通过命令行来调用程序。(例如:%post -p /sbin/ldconfig)

小脚本也接受参数,rpmbuild 进程会传递参数给它们。这个参数,通过 $1 来调用,实际意思是指:某动作结束时,遗留在系统上的该软件包的个数。除了 RPM 4.4 以后出现的 %pretrans 和 %posttrans,它们的 $1 的值始终是 0。具体的安装,升级和卸载动作对应的参数的值如下:

安装 升级 卸载
 %pretrans $1 == 0 $1 == 0
 %pre $1 == 1 $1 == 2
 %post $1 == 1 $1 == 2
 %preun $1 == 1 $1 == 0
 %postun $1 == 1 $1 == 0
 %posttrans $1 == 0 $1 == 0

注意,如果系统上有同名软件包的多个版本,这些值也是会变化的(这通常发生在那些可以同时安装的软件包比如内核上。然而,如果软件升级因错误而未完成,也会出现这种情况)。所以在 %pre 和 %post 脚本中使用下面的检测手段而不是去检测 $1 等于 2 比较好( -eq 等于,-gt 大于,具体见 help test):

%pre
if [ "$1" -gt 1 ] ; then
  ...
fi

除了一些确实特殊的情况(如果有的话),我们想让所有的小脚本都以 exit 0 状态退出。因为默认配置的 RPM 目前在执行命令行小脚本时不会传递 -e 参数给命令行,除非是脚本中明确调用了 exit(避免非 0 的退出状态),不然小脚本中最后一条命令的 exit 状态就是脚本的 exit 状态。大部分小脚本片段都会使用 "||:('||'或者,空命令+:,那返回的一定是 exit 0)" 结尾,这是一种给小脚本的命令强制指定 exit 0 退出状态的通用技巧,而不理会该命令是否执行成功。通常把它们添加到小脚本执行的最后一个命令的结尾,或者使用一个单独的命令如 ":" 或者 "exit 0 来作为小脚本的最后一条命令。注意,因实例而异,有时有些错误检测/规避措施可能更合适,比如将前一条命令的成功状态作为必须条件来执行一些指令。

在一个批处理进程中,小脚本的非 0 的退出状态会破坏安装/升级/卸载,让后续操作不能进行(见下面的小脚本运行顺序),这会导致,例如,升级时不能移除老版本的软件包,在 rpm 数据库中遗留冗余条目,也可能在文件系统中留下不明文件。这些都是小脚本中的一些命令失败,却让批处理进程继续会导致的部分破碎的设定的实例。这通常只会影响一个包,但是在对多个包进行操作时也会导致系统层面的问题。

小脚本运行顺序

在 %pre 和 %post 里的小脚本分别在软件包安装前和安装后执行。在 %preun 和 %postun 里的小脚本分别在软件包卸载前和卸载后执行。在 %pretrans 和 %posttrans 里的小脚本分别在进程运行前和运行后执行。升级时,小脚本按照如下顺序执行:

  1. 新包的 %pretrans
  2. 新包的 %pre
  3. 软件包安装
  4. 新包的 %post
  5. 旧包的 %preun
  6. 移除旧包
  7. 旧包的 %postun
  8. 新包的 %posttrans

菜谱

共享库

安装共享库文件需要运行 /sbin/ldconfig 来更新动态链接缓存。像这样调用:

%post
/sbin/ldconfig
%postun
/sbin/ldconfig

通常也会使用 -p 选项来声明这些脚本片段,因为它们经常是脚本调用的唯一程序,因此盲目的启动命令行多余的:

%post -p /sbin/ldconfig
%postun -p /sbin/ldconfig

如果可以的话,推荐后面的方法,因为那么做会自动添加 /sbin/ldconfig 所需的依赖到最终的软件包(更值得的这么做的原因是它避免了在脚本中启动不必要的命令行进程)。

注意 RPM spec 文件的章节在下一个章节开始时才结束。有时这会造成出乎预料的错误。例如你使用了注释:

# post 章节:
%post -p /sbin/ldconfig

# postun 章节:
%postun -p /sbin/ldconfig

在这个例子中,注释 "# postun 章节:" 实际上被 RPM 程序归到了 post 章节里(并且注释 "# post 章节:" 实际上是归属于 post 章节之前的那个章节如 %install,因为现在没有 %clean 了)。在一些实例中,例如 SLE11 和 SLE11_SP1 的编译系统里,这样的注释会返回一个你并不想要的参数。会在 /sbin/ldconfig 命令后面加上 '0' 或者 '1',这时就会出现诸如 "/sbin/ldconfig: 使用了相对路径 '1'来编译缓存(relative path '1' used to build cache)" 这样的编译错误。因此在 pot/postun 章节的行末使用传统 bash 脚本中明确的 "exit 0" 会更保险 - 假如你想在 RPM 包安装/卸载时自动忽略上面那样的错误 - 反之脚本中使用一个明确的 exit 1 会执行软件包安装/升级/卸载时的错误,就像上面那样。例如:

%post
/sbin/ldconfig || exit 1 
如果 ldconfig 失败就安全退出,"||" 表示如果前面不对就执行后面

在 ldconfig 之后应该执行的脚本片段

exit 0 
如果 ldconfig 不失败,在它之后执行的那些脚本无论返回了什么结果,都认为它是成功的,即忽略所有非 ldconfig 的错误。

%postun
/sbin/ldconfig
[ "$1" -eq 0 ] && undo_what_was_only_nice_to_have
如果 %postun 的第一个参数是 0, 就执行 un_do_what...
exit 0
不管 %postun 章节执行代码返回了什么结果,都认为是 %postun 是成功的。也即软件卸载永远不会失败。
即使可能留下配置文件。(软件的内容是保证会被卸载掉的,但是你的家目录下可能残留配置文件。)

这里需要解释下:
[ ""$1"" -eq 0 ]:
bash 脚本中,参数是从 $0 开始的,$0 多数表示命令本身。
比如本例中 $0 就是 %postun 这个宏。specfile 文件的所有"章节"如 %build、%install 都是独立的宏。
而 $1 表示紧跟着 %postun 的第一个参数。
%postun 宏不是没有参数,只是这个参数不是由用户指定,而是程序自动返回的参数。
因此,本代码的意思是,
检测 %postun 返回的第一个参数是不是 0.
在 RPM 程序中,
如果第一个版本的软件包安装成功,也就是说如果你安装了该软件,会自动返回给 %pre 和 %post 宏参数 1; 
如果最后一个版本的软件包卸载成功,也就是说你再没有该软件,会自动返回给 %preun 和 %postun 宏参数 0;
本代码的最终意思是,
检测软件有没有被卸载。
整个代码片段的意思是:
运行 ldconfig,如果软件成功卸载,继续(&&)运行后面的脚本,忽略所有错误,包括 ldconfig 的。
脚本完成。

用户和组

这些在 单独的页面 里讨论。

系统服务

初始化脚本公约

在这里 openSUSE:Packaging_init_scripts 有 SysV 风格的初始化脚本说明。

GConf

GConf 是目前 GNOME 桌面使用的配置方案。软件会在 %{_sysconfdir}/gconf/schemas/ 目录下的 [NAME].schemas 文件中设定默认配置。这些配置随后会注册到监视配置文件内容的 gconf 守护进程中,当这些配置改变时会通知程序。配置文件也提供了文档说明配置方案中每一个变量的意思(使用 gconf-editor 程序浏览数据库时会看到)。

因为是打包而不是在用来打包的那台机器上用嘛,我们需要在编译时禁止配置文件的安装,然后在安装时把 [NAME].schemas 文件中各变量的值注册到 gconf 守护进程,在卸载时反注册掉。由于小脚本的运行顺序,我们把它分成四步来做。

在软件包创建时禁用 GConf 安装可以这么做:

%install
export GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL="1"
make install DESTDIR="%buildroot"
...

GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL 环境变量会在软件编译时覆盖配置方案的安装。对于有些软件包也可以通过传递一个 configure 参数来实现:

%build
%configure --disable-schemas
...

不幸的是,这种 configure 切换法只适用于那些上游开发者已经调整了他们的 Makefile.am 去处理这部分的情况。如果 Makefile.am 没有被调整过,这种切换不会产生任何效果,那时你就必须使用环境变量来做替代了。

第二步处理软件升级时删除老的配置方案:

Requires(pre):       gconf2
...
%pre
if [ "$1" -gt 1 ] ; then
# ["$1" -gt 1] 如果 %pre 的第一个参数大于 1,即如果同 %Name 的软件已经安装。
export GCONF_CONFIG_SOURCE="$(gconftool-2 --get-default-source)"
gconftool-2 --makefile-uninstall-rule \
        %_sysconfdir/gconf/schemas/[NAME].schemas >/dev/null || :
fi

在本环节中,我们将要卸载旧的配置方案,因为我们要升级它嘛。首先我们使用 gconftool-2 --get-default-source 命令来探测 gconf 把这些配置都放在哪里。然后我们从那里卸载它们。如果软件包是要升级一个使用了另一个配置方案名的包,那么就把 [NAME] 替换成另一个配置方案名来卸载掉它。

下面一步是安装新的配置方案:

%post
export GCONF_CONFIG_SOURCE="$(gconftool-2 --get-default-source)"
gconftool-2 --makefile-install-rule \
        %_sysconfdir/gconf/schemas/[NAME].schemas > /dev/null || :

这里我们使用跟 %pre 章节同样的动作来升级,只不过 gconftool-2 参数用的是 --makefile-install-fule 来安装新的配置方案,而不是用来移除旧方案的 uninstall-rule。

最后一步处理软件卸载时删除配置方案:

%preun
if [ "$1" -eq 0 ] ; then
export GCONF_CONFIG_SOURCE="$(gconftool-2 --get-default-source)"
gconftool-2 --makefile-uninstall-rule \
        %_sysconfdir/gconf/schemas/[NAME].schemas > /dev/null || :
fi

这个片段几乎和升级时用的一样。为什么我们不能把它们结合一下呢?因为我们想要在升级的时候卸载掉所有老版本的配置方案。但是这必须在我们在 %post 章节安装新方案之前就做掉,不然我们同时也会移除掉升级包安装的新方案啦。然而,如果是要把该包从系统上完全删除的话,我们就必须要在 %postun 之前也就是真正删除包之前来做掉它。

在 %files 文件列表里最好不要去通配而是明确的列示出 .schemas 文件。这避免了升级的时候出现问题(不然必须安装老版本一次来检查配置方案的老名字)。文件列表和 %post 章节必须在有新的 .schemas 文件出现时升级,如果有任何安装到 %{buildroot} 却未打包进最终的二进制 RPM 的文件,rpmlint 都会报错。正确文件列表见下面例子,应该用:

%files
[...]
%{_sysconfdir}/gconf/schemas/epiphany.schemas
%{_sysconfdir}/gconf/schemas/epiphany-lockdown.schemas

而不应该用:

%{_sysconfdir}/gconf/schemas/*.schemas

Texinfo

GNU 项目和其他的一些项目中的大量文档都使用了 texinfo 文件格式。这些信息文件一般在 /usr/share/info/。

当安装或移除软件包时,info 软件包中的 install-info 命令会把新安装的文件加入到或从主 info 树中抹去。

Requires(post):  info
Requires(preun): info
...
%post
%install_info --info-dir=%{_infodir} %{_infodir}/%{name}.info.gz

%postun
%install_info_delete --info-dir=%{_infodir} %{_infodir}/%{name}.info.gz

这两个小脚本告诉 install-info 在安装时添加 info 条目到主 info 树文件,在卸载时抹掉它们。

Scrollkeeper

一些发行版使用了 scrollkeeper 分类跟踪系统来追踪安装在系统上的文档。openSUSE 不需要这么做,因此小脚本中也无需调用任何 scrollkeeper 相关的宏。

MIME 数据库

共享 MIME 信息 是由 freedesktop.org 确定的一项 标准

当软件包安装了文件到 %{_datadir}/mime 并且发行版包含了 shared-mime-info 依赖软件包时,本特性才能够使用。

一些软件包在安装时会调用 update-mime-database 并指定 DESTDIR。这么做是错误的,因为会导致将真实的 MIME 数据库打包进去,而不是打包组成数据库的元件,也就是那个 .xml 文件,即使设定了 DESTDIR 也是如此。最简单的解决方法是在 %install 章节移除生成的文件。通常,%{_datadir}/mime 下除了 packages/*.xml 的所有文件都是后生成的文件,都可以也需要被移除 。 当软件包安装了 XML 文件到 %{_datadir}/mime/packages 时,在 openSUSE 11.3 及之前的发行版版本中可以这么写:

Requires(post):    shared-mime-info
Requires(postun):  shared-mime-info

%post
/usr/bin/update-mime-database %{_datadir}/mime &> /dev/null || :

%postun
/usr/bin/update-mime-database %{_datadir}/mime &> /dev/null || :

11.4 及以后的发行版版本中可以这么写:

Requires(post):    shared-mime-info
Requires(postun):  shared-mime-info

%post
%mime_database_post

%postun
%mime_database_postun

MIME 类型可以使用 nautilus 软件或者 dolphin 来检测。安装待测的软件包,启动或重启 Nautilus,查看相关文件的属性就可以了。软件包安装的 MIME 类型应该已经在里面恰当定义好了。

当软件包安装了 定义了 MIME 处理器的桌面文件 时,其他的数据库也需要更新,因此需要在小脚本中使用如下宏(从 11.4 开始):

Requires(post):    desktop-file-utils
Requires(postun):  desktop-file-utils

%post
%desktop_database_post

%postun
%desktop_database_postun

GTK+ 图标缓存

一些发行版 (如 Fedora),在一个程序往 %{_datadir}/icons/ 的子目录之一 (如 hicolor) 安装图标时,会在它们的 %post/%postun 小脚本里调用 gtk-update-icon-cache

在 openSUSE 中,软件包安装结束后会调用 SuSEconfig,因此什么也不用往 .spec 文件里加。由于 SuSEconfig 只能从 YaST 执行而不能从zypper 或者其他的安装工具里执行,推荐使用如下宏 (11.4 开始才有):

%post
%icon_theme_cache_post

%postun
%icon_theme_cache_postun