openSUSE:Packaging scriptlet snippets

Jump to: navigation, search

RPM scriptlet recipes

rpm spec files have several sections which allow packages to run code on installation and removal. These scriptlets are mostly used to update the running system with information from the package. This page offers a quick overview of the RPM scriptlets and a number of common recipes for scriptlets in packages. For a more complete treatment of scriptlets, please see the Maximum RPM book.

Syntax

The basic syntax is similar to the %build, %install and other sections of the rpm spec file. The scripts support a special flag, -p, which allows the scriptlet to invoke a particular interpreter instead of the default -p /bin/sh. If you use bashisms in a scriptlet, it would be prudent to specify -p /bin/bash.

The -p option may also be used to invoke one program directly without the shell indirection, e.g. by using %post -p /sbin/ldconfig.

The scriptlets also take an argument, passed into them by the controlling rpmbuild process. This argument, accessed via $1, is the number of packages of this name which will be left on the system when the action completes. So, for the common case of install, upgrade, and uninstall, we have:

install upgrade uninstall
%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)

Note that these values will vary if there are multiple versions of the same package installed (This mostly occurs with parallel installable packages such as the kernel. However, it can also occur when errors prevent a package upgrade form completing.) It is therefore a good idea to use this construct:

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

for %pre and %post scripts, rather than checking that it equals 2.

Except in some really exceptional cases (if any), we want all scriptlets to exit with the zero exit status. Because rpm in its default configuration does not at the moment execute shell scriptlets with the -e argument to the shell, excluding explicit exit calls (frowned upon with a non-zero argument!), the exit status of the last command in a scriptlet determines its exit status. Most commands in the snippets in this document have a "|| :" appended to them, which is one way to ignore the exit status (by way of executing :, a shell syntactic shortcut doing the same as /bin/true) for those commands whether they succeeded or not. Usually, the most important bit is to apply this to the last command executed in a scriptlet, or to add a separate command such as plain ":" or "exit 0" as the last one in a scriptlet. Note that depending on the case, other error checking/prevention measures may be more appropriate, as well as running some commands only if we saw a previous command in the scriptlet which is a must prerequisite to succeed.

Non-zero exit codes from scriptlets break installs/upgrades/erases so that no further actions will be taken for that package in a transaction (see scriptlet ordering below), which may for example prevent an old version of a package from being erased on upgrades, leaving behind duplicate rpmdb entries and possibly stale, unowned files on the filesystem. There are some cases where letting the transaction to proceed when some things in scriptlets failed may result in partially broken setup. It is however often limited to that package only whereas letting a transaction to proceed with some packages dropped out on the fly is more likely to result in broader system-wide problems.

Scriptlet Ordering

The scriptlets in %pre and %post are run before and after a package is installed, respectively. The scriptlets %preun and %postun are run before and after a package is uninstalled, respectively. The scriptlets %pretrans and %posttrans are run at start and end of a transaction, respectively. On upgrade, the scripts are run in the following order:

  1. %pretrans of new package
  2. %pre of new package
  3. (package install)
  4. %post of new package
  5. %preun of old package
  6. (removal of old package)
  7. %postun of old package
  8. %posttrans of new package

Snippets

Shared libraries

Installing shared libraries requires running /sbin/ldconfig afterwards to update the dynamic linker's cache files. These can be invoked like:

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

It is also common to declare these sections with the -p option (see above) as they are often the only program invoked in a scriptlet, and thus the implicit starting of a shell would be redundant. Prefer writing it like this:

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

Or, if you are working with a sub-package,

%post -n <sub-package> -p /sbin/ldconfig
%postun -n <sub-package> -p /sbin/ldconfig

If applicable, the -p method is recommended, because doing so will automatically add appropriate dependencies on /sbin/ldconfig to the package.

Sections in a spec file end where the next section begins. If a section is truly empty (consists of at most whitespace), rpm will invoke the specified interpreter with zero arguments; if however, the section is not empty, rpm invokes the interpreter with two arguments (the script file, and a counter). If your rpm section is not empty, you end up invoking ldconfig with two arguments which it does not know how to handle, causing spurious warnings such as

   ldconfig: relative path `0' used to build cache
   ldconfig: relative path `1' used to build cache

To avoid this, make sure the section is completely empty. The '#' character is only introducing a comment in the specfile preamble, %package section, and %files. Elsewhere, it is taken verbatim, and that can cause your %postlet to be non-empty.

Cf. https://bugzilla.redhat.com/show_bug.cgi?id=1003962#c0 that reads in particular:

The scripts support a special flag, -p which specifies the interPreter that should be used to run the script (the default is /bin/sh). Sometimes the -p option is used with no body in order to run a single command directly rather than having to spawn a shell to invoke the programs (i.e. %post -p /sbin/ldconfig). Note that this form requires that there be nothing but white space (not even comments) until the next section begins.

Evaluate whether you really need a comment in the place where you put it. Consider removing it or moving it elsewhere where it has no effect. It is also possible to go back to implicit default of -p /bin/sh and have it waste time parsing comments as (no-op) shell commands.

Users and groups

These are discussed on a separate page

Services

Initscripts Conventions

Full guidelines for SysV-style initscripts can be found here: openSUSE:Packaging_init_scripts

GConf

GConf is a configuration scheme currently used by the GNOME desktop. Programs which use it setup default values in a name.schemas file which is installed under %_sysconfdir/gconf/schemas/name.schemas. These defaults are then registered with the gconf daemon which monitors the configuration values and alerts applications when values the applications are interested in change. The schema files also provide documentation about what each value in the configuration system means, which gets displayed when you browse the database in the gconf-editor program.

For packaging purposes, we have to disable schema installation during build, and also register the values in the name.schema file with the gconf daemon on installation, and unregister them on removal. Due to the ordering of the scriptlets, this is a four-step process.

Disabling the GConf installation during the package creation can be done like so:

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

The GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL environment variable suppresses the installation of the schema during the building of the package. An alternative for some packages is to pass a configure flag:

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

Unfortunately, this configure switch only works if the upstream packager has adapted their Makefile.am to handle it. If the Makefile.am is not configured, this switch will not do anything and you will need to use the environment variable instead.

Here is the second part:

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

In this section, we uninstall the old schemas when we upgrade. The way we do this is to first get the information on where gconf stores its values via the `gconftool-2 --get-default-source` line. Then, we uninstall the schema from that source. If the package could be upgrading a package which had another name for the schema at one time, then we uncomment the lines to uninstall those as well.

The next section is for installing the new schema:

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

Here, we do the same things as in the %pre section for upgrading, except that the gconftool-2 switch used is --makefile-install-rule to install the new schemas instead of the uninstall-rule to remove the old schemas.

The last section deals with deleting the schemas on package removal:

%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

This snippet is nearly the same as the one for upgrading. Why can we not just combine this portion with the %pre portion? The answer is that we want to delete any old versions of the schema during an upgrade. However, this has to happen before the new version is installed (in the %post script), otherwise we end up removing the schema that the upgrading package installs. However, if it really is a removal that will leave no other instances of this package on the system, we have to clean up the schema before deleting it.

It is a good practice to avoid globbing in the file list and explicitly list the .schemas files there. This helps to prevent problems after an update. Both the file list and the %post script must be updated if a new .schemas file appears and rpm warns if not all installed files are mentioned in the file list. The file list from the example above should look like:

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

instead of:

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

Texinfo

The GNU project and many other programs use the texinfo file format for much of its documentation. These info files are usually located in /usr/share/info/. When installing or removing a package, install-info from the info package takes care of adding the newly installed files to the main info index and removing them again on deinstallation.

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

These two scriptlets tell install-info to add entries for the info pages to the main index file on installation and remove them at erase time.

Scrollkeeper

Some distributions use the scrollkeeper cataloging system to keep track of documentation installed on the system. There is no need for doing this in openSUSE and no scrollkeeper-related macros need to be called in scriptlets.

MIME databases

Shared MIME info is a standard defined by freedesktop.org.

This feature is used when the package installs any file to %{_datadir}/mime and the distribution contains the shared-mime-info package.

Some packages call update-mime-database during installation with DESTDIR defined. It is a bug that can cause packaging of the actual MIME database instead of its component even if DESTDIR is set. The simplest work-around is to remove the generated files at the end of the %install section. Generally, everything except packages/*.xml are generated files and need to be removed.

Macros described in this paragraph are relevant only for openSUSE Leap 42.3 and older. Newer releases, including Tumbleweed, do not need these macros. They use RPM's file triggers instead.

Use this when a package drops an XML file in %{_datadir}/mime/packages up to 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 || :

Or this starting with 11.4:

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

%post
%mime_database_post

%postun
%mime_database_postun

MIME types can be tested with the package nautilus. It is enough to install the tested package, start or restart Nautilus, and look at properties of a corresponding file. The installed MIME type should be defined properly there.

The other database which needs to be updated when packages install desktop files defining MIME handlers needs the following macro in the scriptlets (starting from 11.4):

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

%post
%desktop_database_post

%postun
%desktop_database_postun

GTK+ icon cache

Some distributions (e.g. Fedora) call gtk-update-icon-cache in their %post/%postun scriptlets, when an application installs icons into one of the subdirectories in %{_datadir}/icons/ (such as hicolor).

In openSUSE, SuSEconfig used to be called after the installation of the packages, so nothing needed to be added to package .spec file. As SuSEconfig is only executed from YaST and not zypper or other install tools it is recommended to use the following macros (starting from 11.4):

Macros described in this paragraph are relevant only for openSUSE Leap 42.3 and older. Newer releases, including Tumbleweed, do not need these macros. They use RPM's file triggers instead.
Requires(post):    hicolor-icon-theme
Requires(postun):  hicolor-icon-theme

[...]

%post
%icon_theme_cache_post

%postun
%icon_theme_cache_postun

Scriptlet debugging

Debugging scriptlet failure during RPM package build

At the end of a package build, the binary RPMs get installed and thereby RPM scriptlets are executed.

When a RPM scriptlet fails, the build fails but by default there is nothing in the build log that shows the scriptlet code. Because often in the spec file scriptlets are specified by RPM macros, the actual scriptlet code is also not in the spec file. In the end when a RPM scriptlet fails during build one would have to do cumbersome reverse engineering to find out the actual scriptlet code for the particular repository and architecture where it had failed. Even with the actual scriptlet code it is not obvious what exactly had failed without knowing the exact command and its arguments that had actually failed.

When a scriptlet that uses the default /bin/sh interpreter fails (by default /bin/sh links to /bin/bash), run the failing scriptlet with '/bin/bash -xv' as interpreter to get the actual scriptlet code plus the commands and their arguments as they are executed in the build log. This should help a lot to find out the root cause why the scriptlet failed.

To run a scriptlet with '/bin/bash -xv' as interpreter change it from something like

%post
whatever_command
%whatever_RPM_macro

to

%post -p "/bin/bash -xv"
whatever_command
%whatever_RPM_macro

Then let the package build again which still fails. But this time you have the actual scriptlet code plus the commands and their arguments as they are executed in the build log.

After you had fixed the scriptlet so that it builds successfully, you must undo your '/bin/bash -xv' change. At least you must remove the '-xv' argument from the bash interpreter:

%post -p /bin/bash
whatever_fixed_command
%whatever_fixed_RPM_macro