RPM package building guide

Sam Isaacson (sbi@nbcs.rutgers.edu)


Introduction to rpm package building

Rpm packages are usually built with a "spec file," which is a collection of text and shell scripts that build, install and describe a software program. Here is a typical spec file:

Summary: Rc shell from Plan 9
Name: rc
Version: 1.6
Release: 1
Group: System Environment/Shells
Copyright: BSD-type
Source: rc-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-root
Requires: readline
BuildRequires: readline-devel

%description
rc is a command interpreter and programming language similar to sh(1).
It is based on the AT&T Plan 9 shell of the same name.  The shell
offers a C-like syntax (much more so than the C shell), and a powerful
mechanism for manipulating variables.  It is reasonably small and
reasonably fast, especially when compared to contemporary shells.  Its
use is intended to be interactive, but the language lends itself well
to scripts.

  [from the man page]

%prep
%setup -q

%build
LD="/usr/ccs/bin/ld -L/usr/local/lib -R/usr/local/lib" \
  LDFLAGS="-L/usr/local/lib -R/usr/local/lib" ./configure --with-history \
  --with-readline
make

%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local
make install prefix=$RPM_BUILD_ROOT/usr/local sysconfdir=$RPM_BUILD_ROOT/etc

%clean
rm -rf $RPM_BUILD_ROOT

%files
%defattr(-,bin,bin)
%doc COPYING AUTHORS EXAMPLES README RELDATE ChangeLog
/usr/local/bin/rc
/usr/local/bin/-
/usr/local/bin/--
/usr/local/bin/-p
/usr/local/bin/--p
/usr/local/man/man1/rc.1
/usr/local/man/man1/history.1

The spec file is split into several sections, which will be examined individually.

In order to write spec files, it is important to understand rpm's dependency checking, macros and directory structure. When you build a package, rpm creates a list of the shared libraries that it includes and a list of the shared libraries to which it is linked. RPM records the shared libraries that the package provides, along with the package name itself and anything manually specified in the spec file, along with version information. Similarly, RPM records the required shared libraries and manually specified requires. In rc, readline, libc.so.1, libcurses.so.1, libdl.so.1 and libreadline.so.4 are required, and rc (version 1.6) is provided. Readline and libreadline.so.4 are both provided by the readline package; the rest are provided by the operating system.

"Build requires" -- packages required at build time -- can be specified in a spec file. When it is built, rc needs readline-devel, as it has the header files for the readline library.

Rpm has a simple macro system. Macros can be defined like so:

%define foo bar
%{foo}

is preprocessed to become "bar". Rpm has logical constructs: %if/%else/%endif, %ifos, and %ifarch (cf. openssl.spec).

Finally, rpm has a system of directories for package building:

prefix/src/redhat/RPMS/sparc
prefix/src/redhat/RPMS/sparc64
prefix/src/redhat/RPMS/sparcv9
   .
   .
   .
prefix/src/redhat/SRPMS
prefix/src/redhat/SOURCES   ($RPM_SOURCE_DIR)
prefix/src/redhat/BUILD     ($RPM_BUILD_DIR)
prefix/src/redhat/SPECS

RPM expects to find your source in SOURCES; it will unpack and compile the source code in BUILD. RPM expects to find the files that the package will install in $RPM_BUILD_ROOT, which rc has set to %{_tmppath}/rc-root ("%{_tmppath}" is a macro set by rpm which expands to the name of a directory for temporary files).

The Preamble

The preamble from rc.spec is:

Summary: Rc shell from Plan 9
Name: rc
Version: 1.6
Release: 1
Group: System Environment/Shells
Copyright: BSD-type
Source: rc-%{version}.tar.gz
BuildRoot: %{_tmppath}/%{name}-root
Requires: readline

It describes the package name, version, etc. Name, Version, Release, Group, Copyright (or License), and Summary are required. Other fields, such as URL and Packager, are optional. Name, Version and Release define macros called %{name}, %{version} and %{release} respectively.

Generally, source filenames match the expansion of "%{name}-%{version}.tar.gz". The %{version} macro makes maintaining the package much easier; its use is highly recommended. If the source field has an URL, rpm automatically downloads the source and places it in $RPM_SOURCE_DIR. You can specify multiple sources with Source0, Source1, etc.

The %description

This section is parsed separately from the preamble, but can be thought of as another field. Generally, one can steal the introduction from a README or man page to get a good description.

The %prep section

%prep
%setup -q

The %prep section is where the source is prepared, usually in $RPM_BUILD_DIR. Rpm provides the %setup and %patch primitives which automatically untar and patch your source. %setup expects it to untar into a directory called %{name}-%{version}; otherwise you have to pass it the -n switch, which renames the directory. The important %setup switches are:

-n <name>    (name of build directory)
-c           (creates top-level build directory)
-D           (don't delete top-level build directory)
-T           (don't unpack Source0)
-a <n>       (unpack Source number n, after cd'ing to build directory)
-a <n>       (unpack Source number n, before cd'ing to build directory)
-q           (unpack silently)

To unpack several sources into the same directory, you need to have something like the following in %prep:

%setup -q
%setup -D -T -a 1
%setup -D -T -a 2
That unpacks source 0, then cds into %{name}-%{version} and unpacks source 1 and 2. As for patches, you have the following switches:

-P < n >      (use Patch number n)
-p, -b, -E        (see patch(1))
While %prep appears to be all macros, don't be fooled -- %prep, %clean, %build, %install, %pre, %post, etc. are all shell scripts.

You might need to install GNU tar and put it on your PATH before Sun tar when building packages with extremely long filenames (the GNOME software in particular requires gnutar).

The %build section

%build
LD="/usr/ccs/bin/ld -L/usr/local/lib -R/usr/local/lib" \
  LDFLAGS="-L/usr/local/lib -R/usr/local/lib" ./configure --with-history \
  --with-readline
make

The %build section is where the actual compiling takes place. Rpm has a %configure macro, which is broken by design (it takes the directories from prefix/lib/rpm/macros, so it might misplace your files; and it only works with GNU configure). With GNU configure, you probably want to configure and build the sources like so:

automake    # if you patched Makefile.am
autoconf    # if you patched configure.in
LD="/usr/ccs/bin/ld -L/usr/local/lib -R/usr/local/lib" \
LDFLAGS="-L/usr/local/lib -R/usr/local/lib" CPPFLAGS="-I/usr/local/include" \
./configure --prefix=/usr/local --sysconfdir=/etc
make

Unfortunately, GNU configure may not use $LD and $LDFLAGS together -- sometimes it does, and sometimes it doesn't. It is more reliable to pass everything into configure (especially because it increases the chance that your specfile will work on someone else's machine). If you're compiling C++ for X, add CXX="g++ -fpermissive" (Sun's include files aren't ANSI C++).

As for imake (with Sun's cc, not gcc), try:

xmkmf -a
make CCOPTIONS="-I/usr/local/include" LINTOPTS="" \
     EXTRA_LDOPTIONS="-L/usr/local/lib -R/usr/local/lib"

Using imake and gcc is left as an exercise to the reader.

Don't specify the prefix as $RPM_BUILD_ROOT/usr/local; many programs hardcode file locations at the configure or make stage.

The %install section

%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local
make install prefix=$RPM_BUILD_ROOT/usr/local sysconfdir=$RPM_BUILD_ROOT/etc

The %install section is where the files get "installed" into your build root. You can build rpms without a build root, but this practice is highly deprecated and insecure (more on this later). Always begin the %install section with something along the lines of

rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local
Sometimes, you can get away with just adding

make install prefix=$RPM_BUILD_ROOT/usr/local

Usually, it's a little hairier. If your program puts files in /etc, you have to tell make install (if you use make install). If a program hardcodes file locations at the make install stage, the best solution is to massage the output of make -n install. Truly devious programs such as qmail, which compile their own installer, make require patches to install correctly.

Other scripts

%clean
rm -rf $RPM_BUILD_ROOT

Generally, the only other script you need is %clean, which gets executed after the build (just clean out $RPM_BUILD_ROOT). You also get %pre (preinstall), %post (postinstall), %preun (preuninstall), %postun (postuninstall), %verifyscript (executed with rpm -V), and triggers (read the documentation included with rpm).

The %files section

%files
%defattr(-,bin,bin)
%doc COPYING AUTHORS EXAMPLES README RELDATE ChangeLog
/usr/local/bin/rc
/usr/local/bin/-
/usr/local/bin/--
/usr/local/bin/-p
/usr/local/bin/--p
/usr/local/man/man1/rc.1
/usr/local/man/man1/history.1

The %files section is where you list all the files in the package. You have a few commands at your disposal: %doc (marks documentation), %attr (marks attibutes of a file - mode [- means don't change mode], user, group), %defattr (default attributes), %verify (see Maximum RPM), %config (marks configuration files), %dir and %docdir.

If a filename in the %files list corresponds to a directory, the package owns the directory as well as all the files in it; so don't put /usr/bin in your %files list. Be careful with globbing and directories; if you list a file twice, rpm will not build your package. Also, some symlinks (absolute ones) cause rpm to complain bitterly; avoid unintentionally grabbing them.

Methods for generating file lists

Unfortunately, generating file lists isn't always easy. Assuming that you didn't have to parse the output of make -n install yourself to write the %install section, try doing something sneaky like:

$ ./configure --prefix=/usr/local --sysconfdir=/etc
$ make
$ mkdir -p sandbox/usr/local/
$ make install prefix=`pwd`/sandbox/usr/local/ sysconfdir=`pwd`/etc
$ for i in `find sandbox -type f` ; do    # check to ensure that no files
> strings $i | grep sandbox && echo $i    # "know" that they were installed
> done                                    # in the build root

Check out the Makefile. Some packages use prefix; others use PREFIX, DESTDIR, or something different. Sometimes, you don't need to add the "usr/local" part. This is, incidentally, a good reason not to build packages as root&emdash;if you accidentally install the software on your system (instead of in an empty directory), you cannot test your package as easily.

Using the rudimentary genspec.pl script (or find(1)), you can use this directory to generate a file list. After you get a list, you may wish to replace long lists of files with globs. For instance:

/usr/local/lib/locale/cs/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/de/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/fi/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/fr/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/ja/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/pl/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/pt_BR/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/ru/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/sk/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/sk/LC_MESSAGES/popt.mo
/usr/local/lib/locale/sl/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/sr/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/sv/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/tr/LC_MESSAGES/rpm.mo
/usr/local/lib/locale/ro/LC_MESSAGES/popt.mo

becomes

/usr/local/lib/locale/*/LC_MESSAGES/*.mo

This makes packages more maintainable. If Spanish translations were added, the glob would catch them; otherwise, you would have to add /usr/local/lib/local/es/LC_MESSAGES/rpm.mo to the file list. You have to be careful, however, that the globs catch only the files or directories you want.

Sometimes, it may be appropriate to generate a file list on the fly. The perl package does this:

%build
sh Configure -de -Dprefix=/usr/local -Dcpp='/opt/SUNWspro/bin/cc -E' \
             -Dcc='/opt/SUNWspro/bin/cc' \
             -Dinstallprefix="$RPM_BUILD_ROOT/usr/local" \
             -Dldflags='-L/usr/local/lib -R/usr/local/lib' -Dusethreads
make
make test

%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local
make install

# clean up files which know about the build root
for fn in .packlist Config.pm ; do
    afn="$RPM_BUILD_ROOT/usr/local/lib/perl5/%{version}/%{perl_arch}/$fn"
    chmod 0644 $afn
    mv $afn $afn.TEMP
    sed "s#$RPM_BUILD_ROOT##g" < $afn.TEMP > $afn
    rm -f $afn.TEMP
done
chmod 0444 \
  $RPM_BUILD_ROOT/usr/local/lib/perl5/%{version}/%{perl_arch}/Config.pm


find $RPM_BUILD_ROOT -type f \( -name \*.h -o -name \*.a \) -print \
    | sed "s#^$RPM_BUILD_ROOT/*#/#" > DEVEL-LIST
find $RPM_BUILD_ROOT -type f ! \( -name \*.h -o -name \*.a \) -print \
    | sed "s#^$RPM_BUILD_ROOT/*#/#" > REGULAR-LIST

%files -f REGULAR-LIST
%doc Copying Artistic README

%files devel -f DEVEL-LIST

Subpackages

If you want to make more than one package out of a single source tree, you have to use subpackages. Here is an example of spec file with subpackages:

Name: readline
Version: 4.1
Copyright: GPL
Group: System Environment/Libraries
Summary: GNU readline
Release: 1
Source: readline-4.1.tar.gz
Provides: libhistory.so
Provides: libreadline.so
BuildRoot: %{_tmppath}/%{name}-root

%description
GNU readline is a library that enables history, completion, and
emacs/vi-like motion functionality in a program linked with it.

%package devel
Summary: Readline header files, static libraries
Group: Development/Libraries
Requires: readline = 4.1

%description devel
This package contains the header files and static libraries for
readline.  Install this package if you want to write or compile a
program that needs readline.

%prep
%setup -q

%build
autoconf
LDFLAGS="-L/usr/local/lib -R/usr/local/lib" ./configure \
    --prefix=/usr/local --enable-shared
make
make shared

%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local
make install prefix=$RPM_BUILD_ROOT/usr/local
make install-shared prefix=$RPM_BUILD_ROOT/usr/local

%clean
rm -rf $RPM_BUILD_ROOT

%post 
ln -s  /usr/local//lib/libhistory.so.4 /usr/local/lib/libhistory.so
ln -s  /usr/local//lib/libreadline.so.4 /usr/local/lib/libreadline.so
if [ -x /usr/local/bin/install-info ] ; then
	/usr/local/bin/install-info --info-dir=/usr/local/info \
		 /usr/local/info/rluserman.info
	/usr/local/bin/install-info --info-dir=/usr/local/info \
		 /usr/local/info/history.info
fi

%preun
rm /usr/local/lib/libhistory.so
rm /usr/local/lib/libreadline.so
if [ -x /usr/local/bin/install-info ] ; then
	/usr/local/bin/install-info --delete --info-dir=/usr/local/info \
		 /usr/local/info/rluserman.info
	/usr/local/bin/install-info --delete --info-dir=/usr/local/info \
		 /usr/local/info/history.info
fi

%files
%defattr(-,bin,bin)
%doc COPYING
/usr/local/lib/libhistory.so.4
/usr/local/lib/libreadline.so.4
/usr/local/info/readline.info
/usr/local/info/rluserman.info
/usr/local/info/history.info
/usr/local/man/man3/readline.3

%files devel
%defattr(-,bin,bin)
/usr/local/include/readline
/usr/local/lib/libreadline.a
/usr/local/lib/libhistory.a

This creates two packages: readline and readline-devel. (If you just want devel, replace %package devel with %package -n devel and %files with %files -n devel).

Style and Security

More information

Go to rpm.org for more information. Unfortunately, rpm is extremely poorly documented. Maximum RPM is out of date; the most authoritative source on rpm is rpm's source, which is kind of messy. Any one of the redhat mirrors has source rpms; run them through rpm2cpio and take a look at the specfiles (which are unfortunately Redhat-specific).


$Id: guide.html,v 1.1.1.1 2001/12/14 20:38:47 sbi Exp $