Chapter 11

Advanced RPM Packaging

Copyright (c) 2005 by Eric Foster-Johnson. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/).

In This Chapter

*	Defining package dependency information

*	Setting triggers

*	Writing verification scripts

*	Creating subpackages

*	Creating relocatable packages

*	Defining conditional builds

The previous chapter introduced the RPM spec file, which controls how RPM packages are built and installed. This chapter delves into advanced spec file topics such as using conditional commands and making relocatable packages, starting with how to specify package dependencies.

Defining Package Dependencies

Dependencies are one of the most important parts of the RPM system. The RPM database tracks dependencies between packages to better allow you to manage your system. A dependency occurs when one package depends on another. The RPM system ensures that dependencies are honored when upgrading, installing, or removing packages. From that simple concept, RPM supports four types of dependencies:

*	Requirements, where one package requires a capability provided by another

*	Provides, a listing of the capabilities your package provides

*	Conflicts, where one package conflicts with a capability provided by another

*	Obsoletes, where one package obsoletes capabilities provided by another, usually used when a package changes name and the new package obsoletes the old name

Cross Reference

Chapter 6 covers more on dependencies. The Obsoletes dependencies are usually only used when a package is renamed, such as the apache package becoming the httpd package, starting in Red Hat Linux 8.0. The httpd package obsoletes the apache package.

You can list all of these dependencies in your spec file. The most commonly used dependency information, though, is what a package requires.

Naming dependencies

In your spec files, you can name the dependencies for your package. The basic syntax is:

Requires: capability

In most cases, the capability should be the name of another package. This example sets up a requires dependency. This means that the package requires the given capability. Use a similar syntax for the other kinds of dependencies:

Provides: capability

Obsoletes: capability

Conflicts: capability

You can put more than one capability on the dependency line. For example:

Requires: bash perl

You can use spaces or commas to separate the capabilities. For example:

Requires: bash, perl

Specifying the Version of the Dependencies

You can also add version information, for example:

Requires: bash >= 2.0

This states that the package requires the capability bash (a package) at version 2.0 or higher. The same logic applies to the other types of dependencies. For example:

Conflicts: bash >= 2.0

This example states that the package conflicts with all versions of bash 2.0 or higher.

Table 11-1 lists the version comparisons you can use.

Table 11-1 Dependency version comparisons

Comparison	Meaning
package < version	A package with a version number less than version
package > version	A package with a version number greater than version
package >= version	A package with a version number greater than or equal to version
package <= version	A package with a version number less than or equal to version
package = version	A package with a version number equal to version
package	A package at any version number

RPM supports an extended version number syntax for comparisons. The full format follows:

Epoch:Version-Release

For example:

1:5.6.0-17

In this case, the epoch is 1, the version 5.6.0, and the release is 17. In most cases, you will need just the version number. The epoch allows for handling hard-to-compare version numbers. The release number is almost never used. This makes sense, in that it ties a dependency to a particular build of the RPM package, rather than a version of the software itself. This type of dependency would only be useful if you drastically changed the way you build a package.

Creating Virtual CAPABILITIES

Dependencies are based on capabilities, most of which are packages. You can create virtual capabilities, which are just names you define. For example, the sendmail package provides a virtual capability named smtpdaemon. For example:

Provides: smtpdaemon

This capability refers to the general SMTP Internet service for sending e-mail messages. There is no file of this name. Instead, it is just a capability, arbitrary text. Other packages require this capability, such as the fetchmail mail-retrieval and forwarding application, and mutt, an e-mail client program.

By using a virtual capability, other packages can provide the capability, and most importantly, client applications can require the capability without worrying which package provides the ability to send e-mail messages. For example, the exim and postfix packages, mail transport agents like sendmail, can provide the same capability.

Note

Of course, you want to ensure that these packages specify that they conflict with each other.

Naming Dependencies on Script Engines and Modules

Scripting languages such as Perl and Tcl allow for add-on modules. Your package may require some of these add-on modules. RPM uses a special syntax with parenthesis to indicate script module dependencies. For example:

Requires: perl(Carp) >= 3.2

This indicates a requirement for the Carp add-on module for Perl, greater than or equal to version 3.2.

Setting prerequisites

A prerequisite is similar to a require dependency, except that a prerequisite must be installed prior to a given package. Specify a prerequisite as follows:

PreReq: capability

You can include version-number dependencies, such as:

PreReq: capability >= version

In most usage, a PreReq: acts just like Requires:, in fact, the PreReq: directive exists just to allow for a manual order to dependencies. RPM guarantees that the PreReq: package will be installed prior to the package that names the PreReq: dependency.

Cross Reference

Chapter 14 covers a common problem of handling circular dependencies using prerequisites.

Naming build dependencies

Your package, once built, has a set of dependencies. These dependencies are important for anyone installing the package. But there are also dependency issues when trying to build packages. Build dependencies allow you to specify what is necessary to build the package. While you may think this would be the same as what is needed to install a package, this is normally not true. Linux distributions tend to divide up software into runtime and development packages. For example, the python package contains the runtime for executing scripts written in Python. The python-devel package provides the ability to write extensions to the Python language.

RPM allows you to define build-time dependencies in your spec files using the following directives:

BuildRequires:

BuildConflicts:

BuildPreReq:

These directives act like Requires:, Conflicts:, and PreReq:, respectively, except that the dependencies are needed to build the package, not install it. For example, your package may require a C compiler to build, or may need a special build tool or developer library.

Generating dependencies automatically

Because so many dependencies are related to shared libraries, the RPM system will automatically generate provide dependencies for any file in your packages that is a shared object, or .so, file. RPM will also automatically generate require dependencies for all files in the %files list that require shared libraries. To do this, RPM uses the ldd command, which determines the shared libraries used by an application.

In addition, the find-requires and find-provides scripts in /usr/lib/rpm can determine Perl, Python and Tcl script dependencies and other dependencies, such as Java package dependencies, automatically. The find-requires script determines requires dependencies automatically, and the find-provides script determines provides dependencies.

Cross Reference

Chapter 14 covers how to turn off the automatic generation of dependencies.

Setting Triggers

Triggers provide a way for one package to take action when the installed status of another package changes. A trigger is a script you define in your package’s spec file that gets run by the RPM system when the status of another named package changes. If your package depends in some way on another package, a trigger can allow your package to deal with changes to the other package.

Triggers are not a replacement for package dependencies. Instead, triggers are useful when you need to change a package’s installation based on other packages installed on the system. For example, if your package is a mail client program, your package will need to have a mail transfer agent, or MTA. Linux supports a number of different mail transfer agents, such as sendmail, vmail, exim, qmail, and postfix.

Typically a system will have one mail transfer agent installed. In most cases, a mail client won’t care which MTA is installed, as long as one is installed. (In fact, most of these packages should be marked that they conflict with one another, ensuring that a given system can only have one.)

The %triggerin script is run when a given target package is installed or upgraded. The %triggerin script is also run when your package is installed or upgraded, should the target package be already installed. Similarly, the %triggerun script is run if the target package is removed. It is also run if your package is removed and the target package is installed. The %triggerpostun script is run after the target package has been removed. It is not run if your package is removed.

To define one of these scripts, you need to list the name of the target package; for example:

%triggerin -- tcsh

script commands...

This example sets up a trigger for the tcsh package. If the tcsh package is installed or upgraded, RPM will run the script. If your package is installed or upgraded and the tcsh package is presently installed, RPM will also run the script.

Define the %triggerun script similarly:

%triggerun -- tcsh

script commands...

You can also use version numbers in the trigger script definition to only run the script in the case of a particular version. For example:

%triggerpostun -- vixie-cron < 3.0.1-56

/sbin/chkconfig --del crond

/sbin/chkconfig --add crond

This example, from the vixie-cron scheduling package, runs a post-uninstall trigger for the same package, but for older versions. To define trigger scripts for particular versions, use the same syntax as for requires dependencies for naming the version number and comparisons.

Triggers are run through /bin/sh, the most commonly used shell script engine. With the -p option, though, you can specify a different script interpreter. For example, to write a Perl script, define your trigger like the following:

%triggerpostun -p /usr/bin/perl -- vixie-cron < 3.0.1-56

system("/sbin/chkconfig --del crond");

system("/sbin/chkconfig --add crond");

With subpackages, defined following, you can use a -n option to tie the trigger script to a subpackage. For example:

%triggerpostun -n subpackage_name -- vixie-cron < 3.0.1-56

/sbin/chkconfig --del crond

/sbin/chkconfig --add crond

Inside your trigger scripts, $1, the first command-line argument, holds the number of instances of your package that will remain after the operation has completed. The second argument, $2, holds the number of instances of the target package that will remain after the operation. Thus, if $2 is 0, the target package will be removed.

The anonftp package, mentioned in Chapter 6, has a lot of triggers. Many of these set up a number of commands to be locally available to the anonftp package. This networking package is also closely tied to the version of the C library, glibc, as shown in Listing 11-1

Listing 11-1: Anonftp package trigger scripts.

%triggerin -- glibc

copy() { file="`ls --sort=time $1 |head -n 1`"; ln -f "$file" "$2" 2>/dev/null |

| cp -df "$file" "$2"; }

# Kill off old versions

rm -f /var/ftp/lib/ld-* /var/ftp/lib/libc* /var/ftp/lib/libnsl* /var/ftp/lib/lib

nss_files* &>/dev/null || :

# Copy parts of glibc, needed by various programs in bin.

LIBCVER=`basename $(ls --sort=time /lib/libc-*.so |head -n 1) .so |cut -f2- -d-`

copy /lib/ld-${LIBCVER}.so /var/ftp/lib

copy /lib/libc-${LIBCVER}.so /var/ftp/lib

copy /lib/libnsl-${LIBCVER}.so /var/ftp/lib

copy /lib/libnss_files-${LIBCVER}.so /var/ftp/lib

md5sum /var/ftp/lib/lib*-*.so /var/ftp/lib/libtermcap.so.*.*.* 2>/dev/null >/var

/ftp/lib/libs.md5

chmod 0400 /var/ftp/lib/libs.md5

# Use ldconfig to build symlinks and whatnot.

[ ! -e /var/ftp/etc/ld.so.conf ] && touch /var/ftp/etc/ld.so.conf

/sbin/ldconfig -r /var/ftp

%triggerin -- fileutils

copy() { file="`ls --sort=time $1 |head -n 1`"; ln -f "$file" "$2" 2>/dev/null |

| cp -df "$file" "$2"; }

copy /bin/ls /var/ftp/bin

md5sum `ls /var/ftp/bin/* |grep -v bin.md5` >/var/ftp/bin/bin.md5

chmod 0400 /var/ftp/bin/bin.md5

%triggerin -- cpio

copy() { file="`ls --sort=time $1 |head -n 1`"; ln -f "$file" "$2" 2>/dev/null |

| cp -df "$file" "$2"; }

copy /bin/cpio /var/ftp/bin

md5sum `ls /var/ftp/bin/* |grep -v bin.md5` >/var/ftp/bin/bin.md5

chmod 0400 /var/ftp/bin/bin.md5

%triggerin -- tar

copy() { file="`ls --sort=time $1 |head -n 1`"; ln -f "$file" "$2" 2>/dev/null |

| cp -df "$file" "$2"; }

copy /bin/tar /var/ftp/bin

md5sum `ls /var/ftp/bin/* |grep -v bin.md5` >/var/ftp/bin/bin.md5

chmod 0400 /var/ftp/bin/bin.md5

%triggerin -- gzip

copy() { file="`ls --sort=time $1 |head -n 1`"; ln -f "$file" "$2" 2>/dev/null |

| cp -df "$file" "$2"; }

copy /bin/gzip /var/ftp/bin

ln -sf gzip /var/ftp/bin/zcat

md5sum `ls /var/ftp/bin/* |grep -v bin.md5` >/var/ftp/bin/bin.md5

chmod 0400 /var/ftp/bin/bin.md5

%triggerin -- libtermcap

copy() { file="`ls --sort=time $1 |head -n 1`"; ln -f "$file" "$2" 2>/dev/null |

| cp -df "$file" "$2"; }

rm -f /var/ftp/lib/libtermcap.so.*.*.* &>/dev/null || :

copy '/lib/libtermcap.so.*.*.*' /var/ftp/lib

md5sum /var/ftp/lib/lib*-*.so /var/ftp/lib/libtermcap.so.*.*.* 2>/dev/null >/var

/ftp/lib/libs.md5

chmod 0400 /var/ftp/lib/libs.md5

# Use ldconfig to build symlinks and whatnot.

[ ! -e /var/ftp/etc/ld.so.conf ] && touch /var/ftp/etc/ld.so.conf

/sbin/ldconfig -r /var/ftp

%triggerin -- ncompress

copy() { file="`ls --sort=time $1 |head -n 1`"; ln -f "$file" "$2" 2>/dev/null |

| cp -df "$file" "$2"; }

copy /usr/bin/compress /var/ftp/bin

md5sum `ls /var/ftp/bin/* |grep -v bin.md5` >/var/ftp/bin/bin.md5

chmod 0400 /var/ftp/bin/bin.md5

%triggerpostun -- anonftp 4.0

if [ "$2" != 1 ] ; then

# The user has multiple glibc packages installed. We can't read the

# user's mind, so don't do anything.

exit 0

fi

copy() { file="`ls --sort=time $1 |head -n 1`"; ln -f "$file" "$2" 2>/dev/null |

| cp -df "$file" "$2"; }

# Kill off old versions

rm -f /var/ftp/lib/ld-* /var/ftp/lib/libc* /var/ftp/lib/libnsl* /var/ftp/lib/lib

nss_files* &>/dev/null || :

# Copy parts of glibc, needed by various programs in bin.

LIBCVER=`basename /lib/libc-*.so .so | cut -f2- -d-`

copy /lib/ld-${LIBCVER}.so /var/ftp/lib

copy /lib/libc-${LIBCVER}.so /var/ftp/lib

copy /lib/libnsl-${LIBCVER}.so /var/ftp/lib

copy /lib/libnss_files-${LIBCVER}.so /var/ftp/lib

copy /bin/ls /var/ftp/bin

copy /bin/cpio /var/ftp/bin

copy /bin/tar /var/ftp/bin

copy /bin/gzip /var/ftp/bin

ln -sf gzip /var/ftp/bin/zcat

copy /usr/bin/compress /var/ftp/bin

rm -f /var/ftp/lib/libtermcap.so.*.*.* &>/dev/null || :

copy '/lib/libtermcap.so.*.*.*' /var/ftp/lib

# Use ldconfig to build symlinks and whatnot.

[ ! -e /var/ftp/etc/ld.so.conf ] && touch /var/ftp/etc/ld.so.conf

/sbin/ldconfig -r /var/ftp

# Generate md5sums for verifyscript

md5sum /var/ftp/lib/lib*-*.so /var/ftp/lib/libtermcap.so.*.*.* 2>/dev/null >/var

/ftp/lib/libs.md5

chmod 0400 /var/ftp/lib/libs.md5

md5sum `ls /var/ftp/bin/* |grep -v bin.md5` >/var/ftp/bin/bin.md5

chmod 0400 /var/ftp/bin/bin.md5

Writing Verification Scripts

RPM automatically handles package verification, checking to see that the proper files are installed, and testing the files themselves for the proper size and other attributes. You may need to do more in your package, though, to ensure everything is properly set up. With RPM, you can:

*	Control the tests used to verify each file, as described in Chapter 10

*	Create a verify script that performs other tests

If you need to perform some other test to verify your package, such as check that a configuration file has a particular setting (and that the setting is valid), you can fill in the %verifyscript in the spec file. The %verifyscript acts much like the %pre or %post scripts, except that the %verifyscript gets executed during package verification. Fill in a %verifyscript as follows:

%verifyscript

your script commands ....

Common %verifyscript actions are to check for an entry in a system configuration file, such as an init-time startup script or /etc/shells (which lists the available shells). These are files owned by other packages that may need to be properly modified for a package to be properly installed. If your package has a similar circumstance, write a %verifyscript. In your script, send all error output to stderr.

Cross Reference

See Chapter 5 for more on package verification.

Creating Subpackages

A spec file may define more than one package. This type of additional package is called a subpackage. Subpackages exist to handle cases where you don’t want to associate one spec file with one package. Instead, you can define multiple packages within the spec file, as needed. For example, you may want to build the runtime and developer packages together, or the client and server portions of an application using subpackages. Splitting large documentation sets into separate subpackages is also common.

With subpackages, you get:

*	One spec file

*	One source RPM

*	One set of build commands

*	Multiple binary RPMs, one per package or subpackage

In most cases, subpackages are created just as a means to partition the files produced by a package into separate packages. For example, you will often see development libraries and header files split into a separate package from the main application package. Sometimes documentation is split out into a separate package, or client and server applications are divided into separate packages. In the end, though, this usually results in shifting files into subpackages and nothing more.

To define a subpackage within a spec file, you start with the %package directive. For example:

%package sub_package_name

By default, the name of the subpackage will be the name of the package, a dash, and the subpackage name provided with the %package directive. For example:

%package server

This example names a subpackage server, which is a real subpackage inside the telnet package. In this case, the name for the server subpackage will be telnet-server, that is, the naming format is package-subpackage.

If you don’t want this naming format, you can use the –n option to the %package directive to define an entirely new name, using the following syntax:

%package -n new_sub_package_name

For example:

%package –n my-telnet-server

With the –n option, you specify the full name for the subpackage. The RPM system will not prefix the name with the enclosing package name.

Providing information for subpackages

When you define a subpackage, you need to provide as many of the package information directives as you need, including at the least Summary:, Group:, and %description directives. Anything not specified will use the parent package’s value, such as the version. Place these directives after the %package directive. For example:

%package server

Requires: xinetd

Group: System Environment/Daemons

Summary: The server program for the telnet remote login protocol.

The %description directive for subpackages requires the name of the subpackage using the following syntax:

%description subpackage

For example:

%description server

Telnet is a popular protocol for logging into remote systems

over the Internet. The telnet-server package includes a telnet daemon that supports remote logins into the host machine. The

telnet daemon is enabled by default. You may disable the telnet

daemon by editing /etc/xinetd.d/telnet.

If you used the –n option with the %package directive, you need to repeat the –n option with the %description directive. For example:

%description –n my-telnet-server

Telnet is a popular protocol for logging into remote systems

over the Internet. The telnet-server package includes a telnet daemon that supports remote logins into the host machine. The

telnet daemon is enabled by default. You may disable the telnet

daemon by editing /etc/xinetd.d/telnet.

The same concept works for the %files section. You need a separate %files section for each subpackage. For example:

%files server

%defattr(-,root,root)

%{_sbindir}/in.telnetd

%{_mandir}/man5/issue.net.5*

%{_mandir}/man8/in.telnetd.8*

%{_mandir}/man8/telnetd.8*

Again, if you used the –n option with the %package directive, you need to repeat the –n option with the %files section. For example:

%files –n my-telnet-server

%defattr(-,root,root)

%{_sbindir}/in.telnetd

%{_mandir}/man5/issue.net.5*

%{_mandir}/man8/in.telnetd.8*

%{_mandir}/man8/telnetd.8*

Defining scripts for subpackages

Much as you define separate %files and %description sections for subpackages, you can also define install and uninstall scripts for subpackages. The syntax is similar to that for the %files and %description sections:

%pre subpackage

For example, Listing 11-2 shows the scripts from the VNC package.

Listing 11-2: VNC package install and uninstall scripts.

%post server

if [ "$1" = 1 ]; then

/sbin/chkconfig --add vncserver

fi

%preun server

if [ "$1" = 0 ]; then

/sbin/service vncserver stop >/dev/null 2>&1

/sbin/chkconfig --del vncserver

fi

%postun server

if [ "$1" -ge "1" ]; then

/sbin/service vncserver condrestart >/dev/null 2>&1

fi

Building subpackages

The build sections in the spec file serve double duty. These sections are used for building the main package as well as subpackages. This is one reason why there are so many options on the %setup macro.

The %setup macro allows for selectively unpacking the sources, rather than the default option of unpacking all the sources. For example, the following %setup macro definition gives rpmbuild specific instructions for unpacking one source file:

%setup –D- T –a 1

In this example, the –D option disables the automatic deletion of the directory where the sources will be unpacked. This means any previous contents of this directory, perhaps for other subpackages, will be left alone. The –T option disables the automatic unpacking of the source files, and the –a 1 option specifies to only unpack the first source file. You may need to use options like these when working with subpackages. Though, in most cases, subpackages are just means to partition the package files into separate packages. In cases like this, you will likely not need any of these special %setup options.

Cross Reference

Chapter 10 covers the %setup macro and lists the available options.

Creating Relocatable Packages

A relocatable package allows a user to specify where to install the package. For example, if you build a package for Red Hat Linux, the normal directory for binary executable programs is /usr/bin. Other versions of Linux, though, may place executable programs into /opt/bin, for example. If your package forces the use of /usr/bin, then your package won’t work on these other systems.

Cross Reference

Chapter 19 covers using RPM on other versions of Linux.

With a relocatable package, though, you allow the user to redefine the top-level directories for your package, such as changing from /usr/bin to /opt/bin in the previous example. Making relocatable packages is generally considered a good thing, as you make the user’s life easier.

To set up a relocatable package, you need to:

*	Set up the prefix directives for the top-level directories

*	Define the files under the prefix directories

Setting up the prefixes

The Prefix: directive names a top-level directory as a prefix you can relocate to another directory. For example:

Prefix: /usr

This states that all files under /usr can be relocated to other directories by simply mapping /usr to some other directory, such as /opt, on the rpm command line when installing or upgrading the package.

Note

You can define more than one Prefix: directive to list more than one top-level directory.

Define the files section

When you use a Prefix: directive in your spec file, all files in the %files section must be under the directory named with the Prefix: directive. For example, from the jikes compiler package:

Prefix: /usr

...

%files

%defattr(-,root,root)

/usr/bin/jikes

%doc /usr/doc/jikes-%{version}/license.htm

%doc /usr/man/man1/jikes.1*

In this example, all the files are under the /usr directory. All files in the %files section must be located under one of the Prefix: directories. If you have more than one top-level directory, such as /usr and /etc, define more than one Prefix: directive. For example:

Prefix: /usr

Prefix: /etc

Cross Reference

Chapter 4 covers how to install or upgrade packages into different directories using the --relocate and --prefix options.

Problems creating relocatable packages

Not all packages work well as relocatable packages. Some packages have files that simply must go into a certain location and are therefore not relocatable. Some packages have programs that are hard-coded to look for files in a particular location and therefore cannot be relocated elsewhere. Other packages have symbolic links that also may not be relocatable. Furthermore, your package may provide software that is referenced by other packages, in the known directories. Relocating such a package will disable other software packages, packages you may not even know about.

If your packages face any of these problems, chances are that making the package relocatable is not a good idea.

In addition, if you use the %doc directive with local file names, remember that RPM will make a package-specific documentation directory, normally under /usr/doc. For example:

%doc README NEWS

This may defeat your attempts to create a relocatable package, unless you have a Prefix: directive with /usr, because the normal location is under /usr/doc, and all files in the %files section must start with one of the directories named with Prefix: directives.

Defining Conditional Builds

With the ability to define macros inside spec files, and also to use macros defined elsewhere, you gain a lot of control over how your package gets built. You can go further, though, and use special directives to perform only certain commands based on certain conditions. This adds a powerful capability to your spec files, and also makes it much easier to do things like build for multiple versions of Linux or other operating systems, as well as handle various backwards-compatibility issues.

To define conditional build commands, you need to create conditional constructs in your package’s spec file. In addition, you need to define macros that the conditional constructs use to determine whether or not to execute a set of spec file directives.

Cross Reference

See Chapter 21 for more on macro file locations, and Chapters 19 and 20 for more on using RPM on other versions of Linux and other operating systems, respectively.

RPM supports a number of ways to make parts of your spec file enabled or disabled based on certain conditions. These include conditional macros, conditional blocks, and special directives based on the system architecture.

Defining conditional macros

You can use a special syntax to test for the existence of macros. For example:

%{?macro_to_test: expression}

This syntax tells RPM to expand the expression if the macro macro_to_test exists. If the macro macro_to_test does not exist, nothing will be output. You can also reverse this test. A leading exclamation point, !, tests for the non-existence of a macro:

%{!?macro_to_test: expression}

In this example, if the macro_to_test macro does not exist, RPM will expand the expression.

If you want, you can omit the expression and just test for the existence of the macro. If it exists, RPM will use the value of the macro. If the macro does not exist, RPM will use nothing. For example:

%build

./configure %{?_with_ldap}

make

In this case, if the _with_ldap macro exists, the value of that macro will get passed on the command line to the configure script. If the _with_ldap macro does not exist, nothing extra will be passed on the command line to the configure script. This is very important when creating commands to build or install packages.

Cross Reference

Many of the macros you will test this way are set up with the --with command-line parameter. See Chapter 19 for details.

Using conditional blocks

The %if macro enables all the directives up to the %endif directive, if the condition is true. This is much like scripting languages. For example:

%if %{old_5x}

%define b5x 1

%undefine b6x

%endif

In this case, if the %old_5x macro has a value, the test will be true and all the directives inside the block will get executed.

A %else allows you to specify what to do if the test is not successful. For example:

%if %{old_5x}

%define b5x 1

%undefine b6x

%else

%define b6x 1

%undefine b5x

%endif

In this case, if the %old_5x macro has a value, then all the directives up to the %else will get executed. Otherwise, if the %old_5x macro has no value, the directives from the %else to the %endif will get executed.

Again, use an exclamation point to negate the test. For example:

%if ! %{old_5x}

%define b5x 1

%undefine b6x

%endif

You can use a && for an AND test. For example:

%if %{old_5x} && %{old_6x}

%{error: You cannot build for .5x and .6x at the same time}

%quit

%endif

Using architecture-based conditionals

In addition to the general-purpose %if conditional directive, you can use special directives that test for processor architecture and operating system.

The %ifarch directive enables all the directives up to the %endif directive, if the processor architecture matches the values you pass to the %ifarch directive. For example:

%ifarch sparc

%define b5x 1

%undefine b6x

%endif

This block will only get executed if the processor architecture is SPARC.

Cross Reference

Chapter 21 covers RPM architecture and operating system names.

You can pass more than one architecture name, separated by commas or spaces. For example:

%ifarch sparc alpha

%define b5x 1

%undefine b6x

%endif

This example tests if the processor architecture is SPARC or Alpha.

As with the %if directive, you can also use an %else, to cover all cases where the test is not true. For example:

%ifarch sparc alpha

%define b5x 1

%undefine b6x

%else

%define b6x 1

%undefine b5x

%endif

This example tests if the processor architecture is SPARC or Alpha. If so, the directives from the %ifarch to the %else are executed. If not, the directives from the %else to the %endif are executed.

The %ifnarch directive reverses the %ifarch test. That is, %ifnarch tests if the architecture is not one of the values listed. The following example tests if the processor architecture is not an i386 or an Alpha.

%ifnarch i386 alpha

%define b5x 1

%undefine b6x

%endif

The %ifos directive tests for the operating system. For example:

%ifos linux

%define b5x 1

%undefine b6x

%endif

This example tests if the operating system is Linux. You can reverse the test with the %ifnos directive. For example:

%ifnos irix

%define b5x 1

%undefine b6x

%endif

This example tests if the operating system is not Irix.

Summary

This chapter covers advanced topics in creating packages. Dependencies are very important. You need to specify which packages or capabilities your package requires, so the RPM system can ensure that all requirements are met before allowing users to install the package. If you do not specify the dependencies properly, then you are defeating the integrity of the RPM system.

In addition to specifying what your package requires, it is also important to specify other dependency information. For example, if your package conflicts with another package, you need to very clearly state this. E-mail and Web server packages often conflict with other servers of the same type.

You can specify both package dependencies as well as build dependencies. For example, you may need certain developer libraries to build your package, but not to install it. These are build dependencies.

To help manage dependencies between packages and system configuration issues, you can set up trigger scripts. A trigger is a script in your package that gets executed when another package is installed or removed. If your package, for example, is an e-mail client program, it may need to execute a script should the e-mail server package change. This is a great usage for triggers.

If your package has a complicated installation, the normal RPM verification won’t be sufficient. To help the RPM system ensure the integrity of all the packages, you can write a verify script in your spec file to perform any extra commands necessary to verify your package has been properly installed.

Relocatable packages allow users to install your packages in different locations than originally planned. This is very useful when working with more than one version of Linux, or with other operating systems. For example, most Linux commands are stored in /usr/bin, at least for Red Hat Linux. Other Linux distributions, or other operating systems may specify that programs added to the original set should be stored in /opt/bin and not /usr/bin, for example. Making your package relocatable helps users in these situations.

Conditional directives in your spec file allow you to control the build on different processor architectures and operating systems. The %if directive tests if a value is set. If so, then all the directives up to the %endif directive are executed. If you need to execute a different set of directives, use %else. In this case, if the %if test is true, RPM executes the directives up to the %else. If the test is not true, RPM executes the directives up to the %endif.

Once you have your spec file defined, the next step is to start building packages. The next chapter covers options for the rpmbuild command and how you can use rpmbuild to make your packages.

Chapter 11

Chris Ricker

2003-02-25T10:27:00

Eric Foster-Johnson