Scripts: RPM's Workhorse

The scripts that RPM uses to control the build process are among the most varied and interesting parts of the spec file. Many spec files also contain scripts that perform a variety of tasks whenever the package is installed or erased.

The start of each script is denoted by a keyword. For example, the %build keyword marks the start of the script RPM will execute when building the software to be packaged. It should be noted that, in the strictest sense of the word, these parts of the spec file are not scripts. For example, they do not start with the traditional invocation of a shell. However, the contents of each script section are copied into a file and executed by RPM as a full-fledged script. This is part of the power of RPM: Anything that can be done in a script can be done by RPM.

Let's start by looking at the scripts used during the build process.

Build-time Scripts

The scripts that RPM uses during the building of a package follow the steps known to every software developer:

Although each of the scripts perform a specific function in the build process, they share a common environment. Using RPM's --test option [1] , we can see the common portion of each script. In the following example, we've taken the cdplayer package, issued an rpm -ba --test cdplayer-1.0-1.spec, and viewed the script files left in RPM's temporary directory. This section (with the appropriate package-specific values) is present in every script RPM executes during a build:
#!/bin/sh -e
# Script generated by rpm

RPM_SOURCE_DIR="/usr/src/redhat/SOURCES"
RPM_BUILD_DIR="/usr/src/redhat/BUILD"
RPM_DOC_DIR="/usr/doc"
RPM_OPT_FLAGS="-O2 -m486 -fno-strength-reduce"
RPM_ARCH="i386"
RPM_OS="Linux"
RPM_ROOT_DIR="/tmp/cdplayer"
RPM_BUILD_ROOT="/tmp/cdplayer"
RPM_PACKAGE_NAME="cdplayer"
RPM_PACKAGE_VERSION="1.0"
RPM_PACKAGE_RELEASE="1"
set -x

umask 022
          

As we can see, the script starts with the usual invocation of a shell (in this case, the Bourne shell). There are no arguments passed to the script. Next, a number of environment variables are set. Here's a brief description of each one:

All of these environment variables are set for your use, to make it easier to write scripts that will do the right thing even if the build environment changes.

The script also sets an option that causes the shell to print out each command, complete with expanded arguments. Finally, the default permissions are set. Past this point, the scripts differ. Let's look at the scripts in the order they are executed.

The %prep Script

The %prep script is the first script RPM executes during a build. Prior to the %prep script, RPM has performed preliminary consistency checks, such as whether the spec file's source tag points to files that actually exist. Just prior to passing control over to the %prep script's contents, RPM changes directory into RPM's build area, which, by default, is /usr/src/redhat/BUILD.

At that point, it is the responsibility of the %prep script to:

  • Create the top-level build directory.

  • Unpack the original sources into the build directory.

  • Apply patches to the sources, if necessary.

  • Perform any other actions required to get the sources in a ready-to-build state.

The first three items on this list are common to the vast majority of all software being packaged. Because of this, RPM has two macros that greatly simplify these routine functions. More information on RPM's %setup and %patch macros can be found in the section called Macros: Helpful Shorthand for Package Builders.

The last item on the list can include creating directories or anything else required to get the sources in a ready-to-build state. As a result, a %prep script can range from one line invoking a single %setup macro, to many lines of tricky shell programming.

The %build Script

The %build script picks up where the %prep script left off. Once the %prep script has gotten everything ready for the build, the %build script is usually somewhat anti-climactic — normally invoking make, maybe a configuration script, and little else.

Like %prep before it, the %build script has the same assortment of environment variables to draw on. Also, like %prep, %build changes directory into the software's top-level build directory (located in RPM_BUILD_DIR, or usually called <name>-<version>).

Unlike %prep, there are no macros available for use in the %build script. The reason is simple: Either the commands required to build the software are simple (such as a single make command), or they are so unique that a macro wouldn't make it easier to write the script.

The %install Script

The environment in which the %install script executes is identical to the other scripts. Like the other scripts, the %install script's working directory is set to the software's top-level directory.

As the name implies, it is this script's responsibility to do whatever is necessary to actually install the newly built software. In most cases, this means a single make install command, or a few commands to copy files and create directories.

The %clean Script

The %clean script, as the name implies, is used to clean up the software's build directory tree. RPM normally does this for you, but in certain cases (most notably in those packages that use a build root) you'll need to include a %clean script.

As usual, the %clean script has the same set of environment variables as the other scripts we've covered here. Since a %clean script is normally used when the package is built in a build root, the RPM_BUILD_ROOT environment variable is particularly useful. In many cases, a simple
rm -rf $RPM_BUILD_ROOT
            
will suffice. [2]

Install/Erase-time Scripts

The other type of scripts that are present in the spec file are those that are only used when the package is either installed or erased. There are four scripts, each one meant to be executed at different times during the life of a package:

Unlike the build-time scripts, there is little in the way of environment variables for these scripts. The only environment variable available is RPM_INSTALL_PREFIX, and that is only set if the package uses an installation prefix.

Unlike the build-time scripts, there is an argument defined. The sole argument to these scripts, is a number representing the number of instances of the package currently installed on the system, after the current package has been installed or erased. Sound tricky? It really isn't. Here's an example:

Assume that a package, called blather-1.0, is being installed. No previous versions of blather have been installed. Since the software is being installed, only the %pre and %post scripts are executed. The argument passed to these scripts will be 1, since the the number of blather packages installed is 1. [3]

Continuing our example, a new version of the blather package, version 1.3, is now available. Clearly it's time to upgrade. What will the scripts' values be during the upgrade? As blather-1.3 is installing, its %pre and %post scripts will have an argument equal to 2 (1 for version 1.0 already installed, plus 1 for version 1.3 being installed). As the final part of the upgrade, it's then time to erase blather version 1.0. As the package is being removed, its %preun and %postun scripts are executed. Since there will be only one blather package (version 1.3) installed after version 1.0 is erased, the argument passed to version 1.0's scripts is 1.

To finally bring an end to this example, we've decided to erase blather 1.3. We just don't need it anymore. As the package is being erased, its %preun and %postun scripts will be executed. Since there will be no blather packages installed once the erase completes, the argument passed to the scripts is 0.

With all that said, of what possible use would this argument be? Well, it has two very interesting properties:

  1. When the first version of a package is installed, its %pre and %post scripts will be passed an argument equal to 1.

  2. When the last version of a package is erased, its %preun and %postun scripts will be passed an argument equal to 0.

Based on these properties, it's trivial to write an install-time script that can take certain actions in specific circumstances. Usually, the argument is used in the %preun or %postun scripts to perform a special task when the last instance of a package is being erased.

What is normally done during these scripts? The exact tasks may vary, but in general, the tasks are any that need to be performed at these points in the package's existence. One very common task is to run ldconfig when shared libraries are installed or removed. But that's not the only use for these scripts. It's even possible to use the scripts to perform tests to ensure the package install/erasure should proceed.

Since each of these scripts will be executing on whatever system installs the package, it's necessary to choose the script's choice of tools carefully. Unless you're sure a given program is going to be available on all the systems that could possibly install your package, you should not use it in these scripts.

The %pre Script

The %pre script executes just before the package is to be installed. It is the rare package that requires anything to be done prior to installation; none of the 350 packages that comprise Red Hat Linux Linux 4.0 make use of it.

The %post Script

The %post script executes after the package has been installed. One of the most popular reasons a %post script is needed is to run ldconfig to update the list of available shared libraries after a new one has been installed. Of course, other functions can be performed in a %post script. For example, packages that install shells use the %post script to add the shell name to /etc/shells.

If a package uses a %post script to perform some function, quite often it will include a %postun script that performs the inverse of the %post script, after the package has been removed.

The %preun Script

If there's a time when your package needs to have one last look around before the user erases it, the place to do it is in the %preun script. Anything that a package needs to do immediately prior to RPM taking any action to erase the package, can be done here.

The %postun Script

The %postun script executes after the package has been removed. It is the last chance for a package to clean up after itself. Quite often, %postun scripts are used to run ldconfig to remove newly erased shared libraries from ld.so.cache.

Verification-Time Script — The %verifyscript Script

The %verifyscript executes whenever the installed package is verified by RPM's verification command. The contents of this script is entirely up to the package builder, but in general the script should do whatever is necessary to verify the package's proper installation. Since RPM automatically verifies the existence of a package's files, along with other file attributes, the %verifyscript should concentrate on different aspects of the package's installation. For example, the script may ensure that certain configuration files contain the proper information for the package being verified:
for n in ash bsh; do
    echo -n "Looking for $n in /etc/shells... "
    if ! grep "^/bin/${n}\$" /etc/shells > /dev/null; then
        echo "missing"
        echo "${n} missing from /etc/shells" >&2
    else
        echo "found"
    fi
done
          

In this script, the config file /etc/shells, is checked to ensure that it has entries for the shells provided by this package.

It is worth noting that the script sends informational and error messages to stdout, and error messages only to stderr. Normally RPM will only display error output from a verification script; the output sent to stdout is only displayed when the verification is run in verbose mode.

Notes

[1]

Described in the section called --test — Create, Save Build Scripts For Review in Chapter 12.

[2]

Keep in mind that this command in a %clean script can wreak havoc if used with a build root of, say, /. the section called Using --buildroot Can Bite You! in Chapter 12 discusses this in more detail.

[3]

Or it will be 1, once the package is completely installed. Remember, the number is based on the number of packages installed after the current package's install or erase has completed.