openSUSE:打包脚本片段
RPM scriptlet 脚本示例
rpm spec 文件有几个部分,允许包在安装和删除时运行代码。这些 scriptlet 主要用于将包的信息更新到正在运行的系统。本页提供 RPM scriptlet 的快速概述以及包中 scriptlet 的一些常见示例。有关 scriptlet 的更完整处理,请参阅 Maximum RPM book。
语法
基本语法类似于%build, %install和其他 rpm spec 文件部分。脚本支持一个特殊标志,-p,它允许 scriptlet 调用特定的解释器而不是默认的-p /bin/sh。如果您在 scriptlet 中使用 bashisms,建议指定-p /bin/bash.
该 -p 选项也可以用于直接调用一个程序,而无需 shell 间接,例如使用%post -p /sbin/ldconfig.
scriptlet 还接受一个参数,由控制 rpmbuild 进程传递给它们。该参数,通过$1访问,是在操作完成时将保留在系统上的该名称的包的数量。因此,对于安装、升级和卸载的常见情况,我们有
| install | 升级 | 卸载 | |
|---|---|---|---|
| %pretrans | $1 == 1 | $1 == 2 | (N/A) |
| %pre | $1 == 1 | $1 == 2 | (N/A) |
| %post | $1 == 1 | $1 == 2 | (N/A) |
| %preun | (N/A) | $1 == 1 | $1 == 0 |
| %postun | (N/A) | $1 == 1 | $1 == 0 |
| %posttrans | $1 == 1 | $1 == 1 | (N/A) |
请注意,如果安装了相同包的多个版本(这通常发生在内核等可以并行安装的包中。但是,如果错误阻止了包升级完成,也可能发生这种情况),这些值会有所不同。因此,建议使用此结构
%pre if [ "$1" -gt 1 ] ; then ... fi
脚本%pre和%post,而不是检查它是否等于2.
除非在极少数情况下(如果有的话),我们希望所有 scriptlet 都以零退出状态退出。由于 rpm 在默认配置下目前不使用带有 shell 的 -e 参数执行 shell scriptlet,不包括显式 exit 调用(不建议使用非零参数!),scriptlet 中最后一个命令的退出状态决定了其退出状态。文档中许多代码片段中的大多数命令都附加了一个“|| :”,这是一种忽略退出状态的方法(通过执行:,一个 shell 语法快捷方式,与/bin/true相同)。通常,最重要的部分是将此应用于 scriptlet 中执行的最后一个命令,或者添加一个单独的命令,例如纯“:”或“exit 0”作为 scriptlet 中的最后一个命令。请注意,根据具体情况,其他错误检查/预防措施可能更合适,以及仅在 scriptlet 中的先前命令成功的前提下才运行某些命令。
scriptlet 的非零退出代码会中断安装/升级/擦除,以便在该包的事务中不会采取任何进一步的操作(参见下面的 scriptlet 执行顺序),例如,这可能会阻止旧版本的包在升级时被擦除,从而在文件系统上留下重复的 rpmdb 条目和可能过时、未拥有的文件。在某些情况下,让事务继续进行,而 scriptlet 中的某些内容失败可能会导致部分损坏的设置。但是,它通常仅限于该包,而让事务继续进行,而一些包在飞行中被放弃,更有可能导致更广泛的系统范围问题。
Scriptlet 执行顺序
中的 scriptlet%pre和%post在安装包之前和之后运行,分别。scriptlet%preun和%postun在卸载包之前和之后运行,分别。scriptlet%pretrans和%posttrans分别在事务的开始和结束时运行。在升级时,脚本按以下顺序运行
- %pretrans of new package
- %pre of new package
- (package install)
- %post of new package
- %preun of old package
- (removal of old package)
- %postun of old package
- %posttrans of new package
代码片段
安装共享库需要在之后运行/sbin/ldconfig以更新动态链接器的缓存文件。这些可以通过以下方式调用
%post /sbin/ldconfig %postun /sbin/ldconfig
通常,也建议使用-p选项(见上文)声明这些部分,因为它们通常是 scriptlet 中调用的唯一程序,因此隐式启动 shell 将是多余的。最好这样写
%post -p /sbin/ldconfig %postun -p /sbin/ldconfig
或者,如果您正在使用子包,
%post -n <sub-package> -p /sbin/ldconfig %postun -n <sub-package> -p /sbin/ldconfig
如果适用,建议使用 -p 方法,因为它会自动将适当的依赖项添加到/sbin/ldconfig到包。
spec 文件中的部分在下一个部分开始的地方结束。如果一个部分真正为空(最多包含空格),rpm 将使用零个参数调用指定的解释器;但是,如果该部分不为空,rpm 将使用两个参数调用解释器(脚本文件和计数器)。如果您的 rpm 部分不为空,您最终将使用两个参数调用 ldconfig,而它不知道如何处理这些参数,从而导致虚假警告,例如
ldconfig: relative path `0' used to build cache ldconfig: relative path `1' used to build cache
为了避免这种情况,请确保该部分完全为空。 '#' 字符只是在 specfile 序言中引入注释,%package部分,和%files。在其他地方,它被逐字接受,这可能会导致您的 %postlet 不为空。
参阅 https://bugzilla.redhat.com/show_bug.cgi?id=1003962#c0,其中特别指出
脚本支持一个特殊标志,-p它指定了用于运行脚本的解释器(默认值为/bin/sh)。有时,-p 选项不带主体用于直接运行单个命令,而不是必须生成 shell 来调用程序(即%post -p /sbin/ldconfig)。请注意,这种形式要求除了空格之外什么都没有(甚至没有注释)直到下一个部分开始。
评估您是否真的需要在您放置它的地方添加注释。考虑将其删除或移动到其他地方,在那里它不会产生任何影响。也可以返回到隐式默认的 -p /bin/sh 并浪费时间解析注释作为(无操作)shell 命令。
用户和组
这些在 单独的页面上讨论
服务
Initscripts 约定
有关 SysV 样式 initscripts 的完整指南,请参见 openSUSE:Packaging_init_scripts
GConf
GConf 是当前由 GNOME 桌面使用的配置方案。使用它的程序在name.schemas文件中设置默认值,该文件安装在%_sysconfdir/gconf/schemas/name.schemas。这些默认值随后与 gconf 守护程序注册,gconf 守护程序监视配置值,并在应用程序感兴趣的值发生更改时提醒应用程序。模式文件还提供有关配置系统中每个值的含义的文档,这些文档在 gconf-editor 程序中浏览数据库时显示。
对于打包目的,我们必须在构建期间禁用模式安装,并在安装时将值注册到name.schema文件中,并在删除时取消注册它们。由于 scriptlet 的顺序,这是一个四步过程。
可以通过以下方式禁用在包创建期间的 GConf 安装
%install export GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL="1" make install DESTDIR="%buildroot" ...
GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL 环境变量会阻止在构建包期间安装模式。对于某些包,另一种方法是传递配置标志
%build %configure --disable-schemas ...
不幸的是,如果上游打包人员没有调整他们的 Makefile.am 来处理它,则此配置开关不起作用。如果 Makefile.am 未配置,则此开关将不起作用,您需要使用环境变量代替。
这是第二部分
Requires(pre): gconf2
...
%pre
if [ "$1" -gt 1 ] ; then
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 在哪里存储其值的信息。然后,我们从该源卸载模式。如果该包可以升级曾经具有不同模式名称的包,那么我们取消注释这些行的注释以卸载它们。
下一部分是安装新模式
%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-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
此代码片段几乎与用于升级的代码片段相同。我们为什么不能只将此部分与%pre部分组合?答案是,我们希望在升级期间删除旧版本的模式。但是,这必须在安装升级包的新版本之前发生(在%post脚本中),否则我们将删除升级包安装的模式。但是,如果实际上是删除将使系统上没有该包的其他实例,我们必须在删除它之前清理模式。
避免在文件列表中进行通配符并显式列出.schemas文件是一个好习惯。这有助于防止更新后出现问题。文件列表和%post脚本必须在出现新.schemas文件时更新,并且 rpm 会在未在文件列表中提及所有已安装文件时发出警告。上面的示例文件列表应如下所示
%files
[...]
%{_sysconfdir}/gconf/schemas/epiphany.schemas
%{_sysconfdir}/gconf/schemas/epiphany-lockdown.schemas
代替
%{_sysconfdir}/gconf/schemas/*.schemas
Texinfo
GNU 项目和许多其他程序使用 texinfo 文件格式进行大部分文档。这些 info 文件通常位于/usr/share/info/。在安装或删除包时,install-info来自 info 包会负责将新安装的文件添加到主 info 索引并在取消安装时删除它们。
Requires(post): %{install_info_prereq}
Requires(preun): %{install_info_prereq}
...
%post
%install_info --info-dir=%{_infodir} %{_infodir}/%{name}.info.gz
%preun
%install_info_delete --info-dir=%{_infodir} %{_infodir}/%{name}.info.gz
这两个 scriptlet 告诉 install-info 在安装时将 info 页面的条目添加到主索引文件,并在擦除时删除它们。
Scrollkeeper
一些发行版使用 scrollkeeper 编目系统来跟踪系统上安装的文档。在 openSUSE 中不需要这样做,并且不需要在 scriptlet 中调用任何 scrollkeeper 相关宏。
MIME 数据库
共享 MIME 信息是由 freedesktop.org 定义的 标准。
当包将任何文件安装到 %{_datadir}/mime 并且发行版包含 shared-mime-info 包时,使用此功能。
一些包在安装期间使用 DESTDIR 定义调用 update-mime-database。这是一个错误,会导致打包实际的 MIME 数据库而不是其组件,即使设置了 DESTDIR。最简单的解决方法是在 %install 部分的末尾删除生成的文件。通常,除了 packages/*.xml 之外的所有内容都是生成的文件,需要删除。
当包在 %{_datadir}/mime/packages 中删除 XML 文件,直到 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
可以使用 nautilus 包测试 MIME 类型。只需安装经过测试的包,启动或重新启动 Nautilus,然后查看相应文件的属性。安装的 MIME 类型应在那里正确定义。
从 11.4 开始,需要以下宏在 scriptlet 中更新包含定义 MIME 处理程序的桌面文件的其他数据库
Requires(post): desktop-file-utils Requires(postun): desktop-file-utils %post %desktop_database_post %postun %desktop_database_postun
GTK+ 图标缓存
一些发行版(例如 Fedora)在 %post/%postun scriptlet 中调用 gtk-update-icon-cache,当应用程序将图标安装到 %{_datadir}/icons/ 中的子目录之一(例如 hicolor)时。
在 openSUSE 中,以前在安装包后调用 SuSEconfig,因此无需在包 .spec 文件中添加任何内容。由于 SuSEconfig 仅从 YaST 调用,而不是 zypper 或其他安装工具,因此建议从 11.4 开始使用以下宏
Requires(post): hicolor-icon-theme Requires(postun): hicolor-icon-theme [...] %post %icon_theme_cache_post %postun %icon_theme_cache_postun
Scriptlet 调试
调试 RPM 包构建期间的 scriptlet 失败
在包构建结束时,将安装二进制 RPM,从而执行 RPM scriptlet。
当 RPM scriptlet 失败时,构建会失败,但默认情况下构建日志中没有任何内容显示 scriptlet 代码。由于 spec 文件中的 scriptlet 通常由 RPM 宏指定,因此实际的 scriptlet 代码也不在 spec 文件中。最终,当 RPM scriptlet 在构建过程中失败时,必须进行繁琐的逆向工程才能找到特定仓库和架构下实际的 scriptlet 代码。即使有了实际的 scriptlet 代码,在不知道确切的命令及其参数的情况下,也很难弄清楚究竟是什么失败了。
当使用默认 /bin/sh 解释器的 scriptlet 失败时(默认情况下 /bin/sh 链接到 /bin/bash),使用 '/bin/bash -xv' 作为解释器运行失败的 scriptlet,以在构建日志中获取实际的 scriptlet 代码以及命令及其参数的执行情况。这应该会大大有助于找出 scriptlet 失败的根本原因。
要使用 '/bin/bash -xv' 作为解释器运行 scriptlet,请将其从类似以下内容更改为:
%post whatever_command %whatever_RPM_macro
到
%post -p "/bin/bash -xv" whatever_command %whatever_RPM_macro
然后再次构建软件包,它仍然会失败。但这一次,你将获得实际的 scriptlet 代码以及命令及其参数在构建日志中的执行情况。
在修复 scriptlet 以使其成功构建后,必须撤消你的 '/bin/bash -xv' 更改。至少,你必须从 bash 解释器中删除 '-xv' 参数
%post -p /bin/bash whatever_fixed_command %whatever_fixed_RPM_macro