RPM-Pakete im Eigenbau
von K. Merker (merker@guug.de)

RPM hat sich neben dem Debian-Paketformat "deb" zu dem Standard- Paketformat für Software unter Linux entwickelt. Der Vortrag beschreibt nach einer kurzen Erläuterung zum Nutzen und zu den Grenzen eines Paketmanagers den Umgang mit RPM und die Entwicklung eigener RPM-Pakete anhand eines praktischen Beispiels.

Inhalt

Warum RPM?
Befehlsübersicht
Paket-Typen
Source-RPMs
Das Specfile
Hilfsskripte
Unterstützung verschiedener Plattformen
Buildroot
Konstanten
Die eigentliche Paketerstellung
Literatur

Warum RPM?

RPM bietet viele Vorteile gegenüber Programmpaketen, die (wie z.B. bei der Slackware-Distribution) als einfaches tar-Archiv vorliegen:

Wer kennt das Problem nicht: Man hat irgendwann ein Programm installiert und möchte es wieder deinstallieren. Wenn das Programm überhaupt über eine Deinstallationsroutine verfügt, bleiben meistens "Leichen" in Form von dynamischen Bibliotheken in den Systemverzeichnissen übrig, oder nach der Deinstallation funktionieren andere Programme nicht mehr, weil die Deinstallationsroutine Dateien gelöscht hat, die auch von anderen Programmen benötigt werden. Ähnliche Probleme können sich auch unter Linux ergeben, wenn man ohne funktionierende Programmverwaltung einfach Dateien installiert und löscht und dabei den Überblick über deren Abhängigkeiten untereinander verliert. Die Lösung dieser Probleme liegt in der Verwendung eines Paketmanagers wie RPM, der mittlerweile bei den meisten Distributionen (u.a. Red Hat, SuSE und Caldera) verwendet wird. Eine der wichtigsten Fähigkeiten von RPM ist die Möglichkeit einer absolut rückstandsfreien Deinstallation eines Programmpaketes. Erreicht wird dies, indem RPM eine Liste aller installierten Dateien mit etlichen zusätzlichen Informationen in einer Datenbank ablegt. U.a. dabei kommt auch die zweite wichtige Funktion von RPM zum Tragen: die Abhängigkeitsprüfung. Sie erfüllt mehrere Aufgaben:

Bei der Installation informiert sie den Anwender, falls das zu installierende Paket zum Betrieb weitere Software voraussetzt, diese aber nicht installiert ist. Dies ist insbesondere bei vom Programm verwendeten Bibliotheken interessant, da unter Linux nicht jedes Programm alle von ihm benötigten Bibliotheken mitbringt. Dies macht Sinn, denn Bibliotheken sind ja gerade dazu da, gemeinsam genutzte Funktionen für verschiedene Programme zur Verfügung zu stellen und nur einmal Speicherplatz zu belegen. Weiterhin verhindert das den unter anderen Betriebssystemen allgemein bekannten Effekt, dass nach der Installation eines Programmes plötzlich ein anderes nicht mehr funktioniert, weil eine benötigte Bibliothek bei der Installation eines anderen Programmes durch eine inkompatible Version überschrieben wurde. Die Bibliotheken liegen unter Linux in der Regel als eigene Pakete vor, und ohne eine solche automatische Abhängigkeitsprüfung wäre es doch recht mühsam, bei jedem Programmpaket herauszusuchen, welche Bibliotheken noch benötigt werden.

Bei der Deinstallation wird der Anwender gewarnt, wenn er ein Paket entfernen möchte, auf das (mindestens) ein anderes Paket zum weiteren Betrieb angewiesen ist, und die betroffenen Pakete werden aufgelistet.

RPM handhabt auch Updates auf vernünftige Weise: Wer kennt nicht den Ärger, wenn nach der Installation einer neuen Programmversion plötzlich die in langer Kleinarbeit erstellten Konfigurationsdateien überschrieben worden sind? RPM stellt anhand von Prüfsummen fest, ob der Anwender die ursprünglich installierte Konfiguration verändert hat, und erstellt in diesem Fall automatisch Sicherungskopien der Konfigurationsdateien.

Aus der RPM-Datenbank lässt sich jederzeit abfragen, welche Dateien zu einem Paket gehören oder auch umgekehrt, zu welchem Paket eine Datei gehört. Es passiert häufig, dass man auf eine Datei stößt und gar nicht weiß, welches Programm sie überhaupt verwendet - RPM verrät es.

Diese eierlegende Wollmilchsau hat leider einen kleinen aber nichtsdestotrotz sehr lästigen Nachteil: nicht jedes Programm gibt es als RPM-Paket. Die Vorteile der Paketverwaltung lassen sich dauerhaft jedoch nur nutzen, wenn man sie konsequent verwendet, d.h. man installiert nur RPM-Pakete oder man reserviert einen eigenen Bereich im System für Programme, die ohne Paketmanagement installiert werden. Es hat sich bewährt, dafür den Verzeichnisbaum unter /usr/local/ zu verwenden, der von den Paketen aus den Distributionen nicht belegt wird (bzw. nicht belegt werden sollte - bei einigen älteren Distributionen wurde diese Regel leider nicht immer konsequent eingehalten). Innerhalb dieses Baumes hat man dann aber natürlich genau die Nachteile, die oben beschrieben wurden. Daher empfiehlt sich der andere Weg: für Programme, die man nicht als RPM bekommt, einfach selbst eines zu erstellen. Das ist bei weitem nicht so schwer wie es sich anhört. Wer in der Lage ist, ein Programm aus dem Sourcecode zu compilieren und zu installieren, kann auch RPM-Pakete erzeugen.

Ein grundsätzliches Problem kann auch RPM leider nicht aus der Welt räumen: die prinzipiellen Unterschiede zwischen verschiedenen Architekturen und Distributionen. Das bedeutet, dass ein RPM-Paket, das z.B. für die SuSE-Distribution erstellt wurde, nicht unbedingt mit der Red-Hat-Distribution funktioniert und umgekehrt. Dies ist keine Schwäche des Paketformates an sich, sondern liegt darin begründet, dass das im Paket enthaltene Programm prinzipbedingt auf eine bestimmte Umgebung angepasst ist. So verwendete SuSE bis zur Version 5.3 als C-Bibliothek, die zur Nutzung fast aller Programme benötigt wird, die libc5, Red Hat seit Version 5.0 ebenso wie SuSE seit Version 6.0 dagegen die libc6. Libc5 und libc6 sind untereinander nicht binärkompatibel, d.h. Programme, die unter Verwendung der einen erzeugt wurden, funktionieren nicht mit der anderen. Dieses Problem lässt sich zwar mit Kompatibilitätsbibliotheken lösen, aber ein Mischbetrieb von Programmen für libc5 und libc6 verursacht einen erhöhten Speicherbedarf und kann in Einzelfällen zu unschönen Seiteneffekten führen. Ähnliche Schwierigkeiten ergeben sich durch die Verwendung unterschiedlicher Pfade für Programme, Konfigurationsdateien und Init-Skripte bei verschiedenen Distributionen. Man sollte also auch bei der Verwendung von RPM darauf achten, Pakete zu installieren, die zum eigenen System passen. Es bleibt zu hoffen, dass derartige Probleme in Zukunft zumindest zum Teil wegfallen, wenn die Bemühungen des Linux Standard Base Projects (LSB) Früchte tragen, einheitliche Vorgaben für die Basiselemente einer Distribution und gemeinsame Schnittstellen zu Konfigurationsfunktionen zu definieren.

Befehlsübersicht

An dieser Stelle möchte ich eine kurze Übersicht über die Benutzung der wichtigsten Funktionen von RPM geben. Der Aufruf von RPM erfolgt immer nach dem gleichen Muster: "rpm -Befehl [Optionen] Name", wobei die wichtigsten Möglichkeiten für Befehl die folgenden sind:

i (install)
Installiert das durch Name angegebene Paket.
U (update)
Ersetzt ein bereits installiertes Paket durch die neuere Version in Name.
e (erase)
Deinstalliert das Paket mit dem Namen Name.
q (query)
ermöglicht das Abfragen verschiedener Informationen, z.B. ob ein Paket installiert ist, aus welchen Dateien es besteht, zu welchem Paket eine Datei gehört und vieles mehr.

Bei den Befehlen i und U ist Name der Name der Paketdatei, die installiert werden soll. Um z.B. das Paket foo-1.0-1.i386.rpm zu installieren, genügt die Zeile "rpm -i foo-1.0-1.i386.rpm".

RPM unterscheidet zwischen dem Dateinamen und dem Paketnamen. Der Paketname ist unabhängig vom Dateinamen und im Paket selbst abgelegt. Das ist auch sinnvoll, denn Dateinamen können verändert werden, z.B. weil ein Paket auf einer Diskette mit FAT-Dateisystem transportiert werden muss, die nur acht Zeichen lange Dateinamen erlaubt, obwohl der Paketname länger als acht Zeichen ist. In die von RPM geführte Liste der installierten Pakete wird immer der Paketname eingetragen, nie der Dateiname. Im Normalfall entspricht der Paketname dem ersten Teil des Dateinamens; im vorstehenden Beispiel wäre der Paketname also wahrscheinlich "foo". Um den Paketnamen herauszufinden, kann man den Befehl "rpm -qp Dateiname" (-qp für "query packagefile") verwenden, welcher als Ausgabe Paketname-Version-Release liefert, im obenstehenden Beispiel also "foo-1.0-1". Interessant ist hier zunächst nur der Paketname; Version und Release werden später noch erklärt.

Die wichtigsten Optionen für "rpm -q" sind i (info), womit eine Reihe von Informationen incl. einer Beschreibung des Paketes angezeigt wird, sowie l, was die im Paket enthaltenen Dateien auflistet. Dabei wird mittels der Option p unterschieden, ob sich die Anfrage auf ein bereits installiertes Paket bezieht (ohne p), d.h. die Daten stammen aus der RPM-Datenbank, oder ob die Daten eines noch nicht installierten Paketes in einer Datei abgefragt werden sollen. Letzteres haben wir oben getan, um aus dem Dateinamen den Paketnamen zu erhalten. Bei Abfragen aus der RPM-Datenbank ist Name natürlich der Paketname, bei Abfragen mit p ist es der Dateiname. Beim Befehl e (erase) ist Name der Paketname, da ja ein bereits installiertes Paket deinstalliert werden soll.

Paket-Typen

RPM-Pakete existieren in zwei Grundformen, die von außen an den Endungen der jeweiligen Dateinamen erkennbar sind:

Source-RPMs

Ein Source-RPM besteht aus folgenden Komponenten:

Das Specfile enthält alle notwendigen Informationen, um aus den Programmquelltexten und den Patches ein fertiges RPM-Paket zu erzeugen. Wenn das Specfile fertiggestellt ist, wird daraus mit einem einzigen Befehl, nämlich "rpm -ba Specfile" (-ba für "build all") sowohl ein Source-RPM (prinzipiell plattformunabhängig) als auch ein Binary-RPM (für die konkrete Plattform, auf der das Paket compiliert wurde) erzeugt. Die Hauptarbeit beim Erzeugen eines RPMs steckt in der Erstellung des Specfiles.

Das Specfile

Das Specfile ist eine Textdatei mit mehreren Sektionen:

Kopfdaten
Name, Versionsnummer, Copyright, Source-URL, einzeilige Kurzbeschreibung, etc.
description
ausführlichere Beschreibung des Pakets
prep, build, install, clean
Shellscripts, die die Sourcen entpacken, compilieren und die resultierenden Binaries installieren
pre, post, preun, postun
Shellscripts, die vor/nach der Installation bzw. Deinstallation des Paketes ausgeführt werden
files
Liste aller Dateien des Paketes

Am einfachsten sieht man das anhand eines Beispiel-Specfiles:


Summary: The tin newsreader (Version pre1.4 19990413)
Summary(de): Tin - ein Newsreader (Version pre1.4 19990413)
Name: tin-unoff
Version: 19990413
Release: 1
Copyright: (?)
Group: Applications/News
Source: ftp://ftp.tin.org/pub/news/clients/tin/current/tinpre-1.4-19990413.tar.gz 
Distribution: none
Vendor: none
Packager: K. Merker <merker@guug.de>
Provides: tin

%description
Tin unoff is an ASCII-based newsreader with color- and pgp-support.
It is based on the original tin by Iain Lea and is maintained by
Urs Janssen <urs@tin.org>.

This release is compiled with support for
- reading by NNTP
- reading directly from the newsspool
- MIME
- color
- PGP

%description -l de
Tin unoff ist ein ASCII-basierter Newsreader mit Farb- und PGP-Unterstuetzung.

Er basiert auf dem Original-TIN von Iain Lea und wird von Urs Janssen
<urs@tin.org> gepflegt.

Dieses Paket wurde compiliert mit Unterstuetzung fuer
- lesen per NNTP
- lesen aus dem Newsspool
- MIME
- Farbe
- PGP

%prep
%setup -n tin-19990413

%build
./configure --enable-color --enable-etiquette --enable-forgery \
--enable-locale --enable-nntp --with-inews-dir=/usr/bin \
--with-ispell=/usr/bin/ispell --with-libdir=/var/lib/news \
--with-metamail=/usr/bin/metamail --with-ncurses \
--with-nntp-default-server=localhost \
--with-pgp=/usr/bin/pgp --with-spooldir=/var/spool/news \
--prefix=/usr --disable-locale 

cd src
make

%install
make install

%clean
rm -rf $RPM_BUILD_DIR/tin-19990413

%files
/usr/bin/tin
/usr/bin/rtin
/usr/man/man1/tin.1
%doc README
%doc README.VMS
%doc doc/CHANGES
%doc doc/DEBUG_REFS
%doc doc/INSTALL
%doc doc/TODO
%doc doc/WHATSNEW
%doc doc/auth.txt
%doc doc/filtering
%doc doc/internals.txt
%doc doc/iso2asc.txt
%doc doc/good-netkeeping-seal
%doc doc/umlaute.txt

Die Kopfdaten im Einzelnen:

Summary:
Enthält eine kurze, möglichst einzeilige Zusammenfassung des Paketinhalts. Zusätzlich kann in Klammern ein ISO-Länderkürzel für die Sprache des Eintrages angegeben werden, z.B. Summary(de) für deutschsprachig.
Name:
Der Name des Paketes
Version:
Die Versionsnummer des Programmes
Release:
Dies ist die Versionsnummer des Paketes im Gegensatz zur Versionsnummer des Programmes. Sie wird immer dann erhöht, wenn zwar die gleiche Programmversion wie vorher verwendet wird, jedoch Änderungen am Paket vorgenommen wurden, z.B. durch Hinzufügen zusätzlicher Dokumentation o.ä.
Copyright:
Eine kurze Zusammenfassung der Copyright-Bestimmungen des Paketes, bei GPL-Software i.d.R. einfach "GPL".
Group:
Gibt die Kategorie an, in die das Paket gehört. Einige grafische Paketinstallationstools (z.B. "Glint" bei Red Hat) zeigen die verfügbaren Pakete nach diesen Kategorien sortiert an.
Source:
Gibt den URL an, unter dem die Originalsourcen zum Paket zu finden sind.
Distribution:
Die Distribution, zu der das Paket gehört, oder für die es erstellt wurde.
Vendor:
Gibt, so vorhanden, den Hersteller des im Paket gelieferten Programmes an.
Packager:
Ist der Ersteller des Paketes (Name und Emailadresse).
Provides:
Bietet die Möglichkeit, zusätzliche Ziele für Abhängigkeiten anzugeben. Im Einzelnen wird das später noch im Abschnitt "Abhängigkeiten und Konflikte" erklärt.

RPM kennt noch einige weitere Kopfdatenfelder, die für ein einfaches Paket aber nicht notwendig sind, so dass ich sie in diesem Beispiel nicht verwende, um es nicht unnötig kompliziert zu machen.

Der Abschnitt %description enthält eine ausführlichere Beschreibung des Pakets als die Summary-Zeile. Seit der RPM-Version 2.5 werden auch Beschreibungen in verschiedenen Sprachen unterstützt, wobei die verschiedenen Sprachen ähnlich wie bei der Summary-Zeile durch ein ISO-Länderkürzel gekennzeichnet werden, das hier jedoch mit der Option -l angefügt wird.

Bevor wir uns mit dem Teil des Specfiles befassen, in dem die eigentliche Arbeit verrichtet wird, ist es sinnvoll, zunächst einen Blick auf die von RPM verwendete Verzeichnisstruktur zu werfen. RPM arbeitet bei der Paketerstellung vollständig innerhalb eines Verzeichnisbaumes, dessen Wurzelverzeichnis über eine Konfigurationsdatei (global: /usr/lib/rpm/rpmrc, /etc/rpmrc, benutzerabhängig: ~/.rpmrc) bestimmt werden kann. Bei Red Hat ist dieses Wurzelverzeichnis /usr/src/redhat, bei SuSE ist es /usr/src/packages. Unterhalb dieses Verzeichnisses, das im Folgenden mit $topdir bezeichnet wird, befinden sich die Verzeichnisse

SOURCES
Hier stehen die Original-Programmquelltexte, i.d.R. als tar-Archiv, sowie eventuelle Patches.
SPECS
Die Specfiles.
BUILD
Das Arbeitsverzeichnis, in dem die Sourcen ausgepackt werden und in dem der Compilerlauf erfolgt.
RPMS
Hier werden die fertigen Binary-RPMs abgelegt, und zwar in einem Unterverzeichnis pro Architektur, also z.B. in RPMS/i386/ für Intel-PCs.
SRPMS
Hier werden die erzeugten Source-RPMS abgelegt.

Bei der Installation eines Source-RPMs mittels "rpm -i bar-1.0-1.src.rpm" werden die jeweils dorthin gehörenden Komponenten in SOURCES bzw. SPECS abgelegt.

Die %prep-Sektion dient dazu, den Programmquelltext und eventuelle Patches auszupacken. Das kann man von Hand tun, aber warum sollte man sich Arbeit machen, wenn RPM das auch kann. Dazu gibt es das Makro %setup, welches aus dem Kopfdatenfeld Source den Dateinamen extrahiert und diesen im Verzeichnis $topdir/SOURCES/ öffnet, in $topdir/BUILD/ auspackt und anschließend in das Verzeichnis mit den entpackten Sourcen wechselt. Da RPM nicht erraten kann, wie das Verzeichnis heißt, welches beim Entpackvorgang angelegt wurde, wird unterstellt, dass es Name-Version heißt. In unserem Beispiel würde also versucht, in das Verzeichnis $topdir/BUILD/tin-unoff-19990413 zu wechseln, was leider nicht funktioniert, da beim Auspacken des Source-Archivs statt dessen das Verzeichnis $topdir/BUILD/tin-19990413 angelegt wird. Daher kennt das %setup-Makro die Option "-n Verzeichnis". In unserem Beispiel lautet der %setup-Aufruf also "%setup -n tin-19990413".

Weiterhin können in der %prep-Sektion auch Patches eingespielt werden, wovon in unserem Beispiel allerdings kein Gebrauch gemacht wird. Im Falle des Falles würde der Patch in den Kopfdaten mit "Patch: Patchfile" angegeben und in der %prep-Sektion mittels "%patch -p0" eingebracht. RPM kann natürlich auch mit mehreren Source-Archiven und mehreren Patches umgehen. In diesem Fall werden die einzelnen Elemente durchnummeriert, also z.B.

Patch0:  Patchfile0
Patch1:  Patchfile1

im Kopf und

%patch0 -p0
%patch1 -p1

im %prep-Abschnitt. Für weitere Details sei auf die Literaturangaben verwiesen, wo dieses Thema erschöpfend behandelt wird.

Im Abschnitt %build werden einfach die Befehle angegeben, die man auch beim manuellen compilieren des Programmes verwenden würde, im einfachsten Falle bei Verwendung von GNU autoconf also "./configure; make". Im Grunde ist %build nicht mehr und nicht weniger als ein Shellscript, das von RPM im Build-Verzeichnis ausgeführt wird. Es lassen sich alle Befehle der Standard-Build-Shell (normalerweise /bin/sh, unter Linux in aller Regel die bash im sh-Kompatibilitätsmodus) verwenden.

Entsprechend enthält der Abschnitt %install die Befehle zur Installation des im %build-Abschnittes erzeugten Programms, im einfachsten Fall also ein "make install".

Im Abschnitt %clean können "Aufräumarbeiten" ausgeführt werden, z.B. das Löschen des nun nicht mehr benötigten Unterverzeichnisses in $topdir/BUILD/. In unserem Beispiel sieht man hier eine der nützlichen Hilfsfunktionen von RPM: Variablen. RPM definiert einige Veriablen, die in den Abschnitten %prep, %build, %install und %clean verwendet werden können. Zur Verfügung stehen unter anderem die Variablen

$RPM_BUILD_DIR
das BUILD-Verzeichnis
$RPM_SOURCE_DIR
das SOURCE-Verzeichnis
$RPM_PACKAGE_NAME
der Paketname (nur der Name, keine Versionsnummer, in unserem Beispiel also "tin-unoff")
$RPM_PACKAGE_VERSION
die Programm-Versionsnummer
$RPM_PACKAGE_RELEASE
die Paket-Versionsnummer
$RPM_ARCH
Das Kürzel der verwendeten Hardwareplattform, z.B. "i386" bei PCs
$RPM_OS
das verwendete Betriebssystem, z.B. "Linux"

Mit Hilfe dieser Variablen lassen sich z.B. sehr einfach Specfiles schreiben, die sich ohne großen Aufwand an neue Programmversionen anpassen lassen. In unserem Beispiel habe ich weitgehend auf die Verwendung von Variablen verzichtet, um das Beispiel nicht zu kompliziert zu machen; im praktischen Einsatz erweist sich ihre Verwendung (zusammen mit einigen anderen Funktionen von RPM) allerdings häufig als vorteilhaft.

Der letzte Abschnitt im Specfile ist %files. Es gibt leider keine sichere Methode, mit der RPM feststellen könnte, welche Dateien im Abschnitt %install installiert wurden, so dass der Paketersteller eine Liste der zum Paket gehörenden Dateien liefern muss.

Im einfachsten Fall besteht der %files-Abschnitt aus einer einfachen Auflistung aller betroffenen Dateien. In der Praxis gibt es jedoch einige Besonderheiten zu beachten. Dateien mit einer absoluten Pfadangabe werden so übernommen, wie sie angegeben sind. Relative Pfadangaben beziehen sich auf das Verzeichnis, in dem das Paket compiliert wurde, in unserem Beispiel also $topdir/BUILD/tin-19990413/. RPM behandelt u.a. Dokumentationsdateien besonders, so dass in der Dateiliste Dokumentation mit einem vorangestellten %doc markiert wird. Dokumentationsdateien mit relativer Pfadangabe werden in das Standard-Dokumentationsverzeichnis installiert, normalerweise ist das /usr/doc/Name-Version (bzw. bei SuSE /usr/doc/packages/Name-Version).

Konfigurationsdateien werden auf die gleiche Art mit %config markiert. Die Dateiattribute (Owner, Group, Zugriffsrechte) werden von den installierten Dateien übernommen. Falls das unerwünscht ist, kann man sie mit %attr(Mode,Owner,Group) auf die in den Klammern angegeben Werte setzen. Diese verschiedenen Optionen lassen sich auch kombinieren, so dass in der Dateiliste z.B. "%attr(644,root,root) %config /etc/foo.conf" stehen könnte.

Jede einzelne Datei aufzulisten, ist natürlich etwas mühselig, so dass die Angabe eines Verzeichnisses automatisch alle in diesem Verzeichnis enthaltenen Dateien und Unterverzeichnisse mit einschließt. Was aber, wenn man nur das Verzeichnis selbst, aber nicht die darin enthaltenen Dateien in das Paket aufnehmen möchte? Dazu gibt es die Option %dir. Der Eintrag "%dir /opt/foo" würde also nur das Verzeichnis /opt/foo in das Paket aufnehmen, nicht jedoch seinen Inhalt.

Abhängigkeiten und Konflikte

Ein RPM-Paket kann zur Installation erfordern, dass ein anderes Paket oder eine bestimmte Eigenschaft vorhanden ist und auch selbst anderen Paketen Eigenschaften zur Verfügung stellen. Dies geschieht

Jedes Paket exportiert automatisch seinen Namen als Fähigkeit, ebenso den sog. soname der in ihm enthaltenen Bibliotheken. Ein fiktives Paket foo-1.0-1, das die Bibliotheken libfoo.so.1 und libbar.so.2 enthält, stellt automatisch die Fähigkeiten foo in der Version 1.0 sowie libfoo.so.1 und libbar.so.2 zur Verfügung. Die vom Programm benötigten dynamischen Bibliotheken muss der Paketersteller ebensowenig manuell angeben. RPM vermerkt bei der Paketerzeugung automatisch, welche Bibliotheken von den im Paket enthaltenen Programmen benutzt werden (mit Hilfe des Befehls ldd).

Bei der Abhängigkeitsprüfung kann auch nach Versionsnummern unterschieden werden, so würde z.B. die Abhängigkeit mit dem Eintrag "Requires: foo >= 2.0" von dem vorstehenden Beispiel nicht erfüllt, da foo nur in Version 1.0 bereitgestellt wird. Über die manuelle Angabe von Fähigkeiten kann man "virtuelle" Pakete erzeugen, z.B. könnten die Pakete sendmail, smail und qmail alle die Fähigkeit MTA zur Verfügung stellen. Alle Pakete, die ein vorhandenes Mailsystem benötigen, enthalten dann ein "Requires: MTA".

Neben der Tatsache, dass Pakete voneinander abhängen, kann es natürlich auch vorkommen, dass sich zwei Pakete explizit ausschließen. So können z.B. sendmail und smail prinzipiell nicht gleichzeitig installiert sein, ohne sich gegenseitig zu stören. Gleiches gilt auch für CNews und INN. Für solche Fälle gibt es das Kopfdatenfeld "Conflicts", welches das Gegenstück zu "Requires" ist. In den Kopfdaten des Paketes CNews könnte also z.B. der Eintrag "Conflicts: inn" stehen.

Das letzte wichtige Schlüsselwort in diesem Zusammenhang ist "Obsoletes". Es kommt durchaus häufiger vor, dass aus mehreren Paketen bestehende Programme im Laufe ihrer Evolution umstrukturiert werden und z.B. eine Funktionalität, die früher in einem Hilfspaket bereitgestellt wurde, nun im Hauptpaket zur Verfügung steht. Damit ist nach Installation des neuen Hauptpaketes das alte Hilfspaket überflüssig geworden. Damit der Anwender das überflüssige Paket nicht von Hand entfernen muss, kann es mit "Obsoletes: Hilfspaket" bei der Installation des neuen Hauptpaketes automatisch deinstalliert werden.

Hilfsskripte

Wie oben schon angedeutet, gibt es neben den in unserem Beispiel verwendeten Abschnitten %prep, %build, %install und %files noch vier weitere mögliche Abschnitte:

%pre, %post, %preun und %postun. Diese Abschnitte können Shellskripte enthalten, die vor bzw. nach der Installation eines RPM-Paketes (%pre bzw. %post) respektive vor bzw. nach der Deinstallation eines Paketes (%preun bzw. %postun) ausgeführt werden. In der Praxis werden diese Funktionen z.B. dazu verwendet, nach der Installation einer Bibliothek ldconfig aufzurufen, um die neu installierte Bibliothek auch tatsächlich verfügbar zu machen, oder um eine zusätzlich installierte Shell in /etc/shells einzutragen, damit sie als Login-Shell verwendet werden kann. Im Fall der Deinstallation müssen die entsprechenden Einträge natürlich über die %preun- bzw. %postun-Skripte wieder entfernt werden.

Unterstützung verschiedener Plattformen

RPM ist für eine große Zahl verschiedener Hardwareplattformen und Betriebssysteme verfügbar (eine Liste ist unter http://www.rpm.org/platforms.html zu finden). Bei der Erstellung von Programmen für verschiedene Plattformen ist es häufig nötig, je nach Plattform gewisse Veränderungen vorzunehmen. Um nun nicht für jede Plattform ein eigenes Specfile bereitstellen zu müssen, bietet RPM die Möglichkeit, Teile des Specfiles abhängig von der jeweiligen Plattform auszuführen oder gegebenenfalls festzustellen, dass das jeweilige Programm auf der gewählten Plattform gar nicht lauffähig ist und damit gar nicht erst ein Binärpaket zu erzeugen.

Dazu können beliebige Teile des Specfiles in if-then-else-Blöcke gefasst werden. Nehmen wir an, für SunOS5/Solaris sei ein besonderer Patch notwendig, dann könnte das Specfile den Block

%ifos Solaris
Patch: Patchfile_für_Solaris
%endif

enthalten. Wenn das Paket überhaupt nur auf einer Plattform lauffähig ist (z.B. Pakete, die bestimmte Eigenheiten der jeweiligen Plattform benötigen), sollte in den Kopfdaten das Schlüsselwort ExclusiveArch (für eine bestimmte Hardwareplattform) bzw. ExclusiveOS (für ein bestimmtes Betriebssystem) enthalten sein. Beispielsweise enthielte ein Paket, welches nur unter Linux auf i386- (und dazu kompatiblen) Architekturen funktioniert, die Einträge

ExclusiveArch: i386
ExclusiveOS: Linux

Zu allen diesen Bedingungen existiert auch jeweils noch eine Negation, z.B. %ifnos (if_not_OS) oder ExcludeOS (alles außer dem angegebenen Betriebssystem).

Buildroot

Das bisher beschriebene Verfahren zur Paketerstellung hat einen gravierenden Nachteil: für die meisten Pakete braucht man dabei Root-Rechte, da im %install-Skript in der Regel in Systemverzeichnisse geschrieben werden soll, in die nur root schreiben darf. Um auch ohne Root-Rechte Pakete erzeugen zu können, gibt es die Möglichkeit, ein sogenanntes "Buildroot"-Verzeichnis zu verwenden, welches an die Stelle des Verzeichnisses /, also der Wurzel des Verzeichnisbaums, tritt. Dieses Verzeichnis wird mit dem Schlüsselwort "Buildroot: Verzeichnis" in den Kopfdaten angegeben. Wenn ein solches Buildroot-Verzeichnis angegeben ist, steht es automatisch in der Variable $RPM_BUILD_ROOT zur Verfügung.

Beim Erzeugen des Paketes streicht RPM aus den tatsächlichen Pfaden der Dateien automatisch den Teil des Pfades, der dem Buildroot-Verzeichnis entspricht, so dass alle Pfade wieder auf die "normalen" Systemverzeichnisse zeigen. Beispiel: bei einem "Buildroot: /tmp/foo" wird aus der Datei /tmp/foo/bin/bar die Datei /bin/bar. In der %files-Sektion werden die Pfade ohne Voranstellen des Buildroot-Verzeichnisses angegeben, also genau so, als ob überhaupt kein Buildroot definiert wäre. Dies funktioniert natürlich nur bei Programmen, deren Installationsroutine in der Lage ist, die erzeugten Dateien in ein anderes Verzeichnis zu schreiben, als das, wofür sie eigentlich compiliert worden sind, denn die programmintern verwendeten Pfade müssen sich ja weiterhin auf die "normalen" Verzeichnisse beziehen und nicht auf das Buildroot-Verzeichnis. Viele moderne Programme bieten diese Möglichkeit; insbesondere bei Programmen, die GNU autoconf benutzen, lässt sich das in der Regel einfach durch einen Aufruf in der Art von "./configure --install-prefix=$RPM_BUILD_ROOT" erledigen.

Wenn das Paket dabei unter Benutzung eines unprivilegierten Useraccounts erstellt wird, gehören die erzeugten Dateien natürlich allesamt auch diesem User. Wie oben dargelegt, übernimmt RPM standardmäßig den Dateieigentümer und die Zugriffsrechte von den installierten Dateien. In diesem Fall würde dies aber zu einem unerwünschten Ergebnis führen, da die Dateien eines Paketes in der Regel root oder einem anderen privilegierten Bentzer (mail, news, etc.) gehören sollen oder dabei gar mit SUID- oder SGID-Bits versehen sein müssen. Daher muss bei Benutzung der Buildroot-Option zwingend der Parameter %attrib im Abschnitt %files verwendet werden, um die korrekten Zugriffsberechtigungen zu setzen.

Konstanten

Eine recht praktische Möglichkeit für ein Specfile, mit dem immer wieder eine jeweils aktuelle Version eines Programmpaketes erzeugt werden soll, ist die Definition von Konstanten im Specfile. Sie ermöglichen es z.B., die Versions- und Releasenummern am Anfang des Specfiles zu definieren, so dass man bei einer neueren Programmversion nicht alle Stellen im Specfile, an denen sie verwendet werden, von Hand anpassen muss. Das kann beispielsweise wie folgt aussehen:

%define tin_version 19990413
%define tin_release 1
Summary: The tin newsreader (Version pre1.4 %{tin_version})
Summary(de): Tin - ein Newsreader (Version pre1.4 %{tin_version})
Name: tin-unoff
Version: %{tin_version}
Release: %{tin_release}
Copyright: (?)
Group: Applications/News
Source: ftp://ftp.tin.org/pub/news/clients/tin/current/tinpre-1.4-%{tin_version}.tar.gz
Distribution: none
Vendor: none
Packager: K. Merker 
Provides: tin
Buildroot: /tmp/tin_unoff_%{tin_version}_buildroot

Die eigentliche Paketerstellung

Nachdem nun alle Teile des Paketes vorliegen - Source, eventuelle Patches und das Specfile - ist es an der Zeit, tatsächlich ein Paket zu erzeugen. Wie oben schon erwähnt, ist der alles erschlagende Befehl "rpm -ba Specfile", welcher sowohl Binary- als auch Source-RPM erzeugt. In allgemeiner Form lautet der Aufruf "rpm -bOption", wobei Option eine der folgenden Möglichkeiten ist:

a (all)
Also Source- und Binary-RPM.
b (binary)
Nur das Binary-RPM wird erzeugt.
p (prep)
Nur der %prep-Abschnitt des Specfiles wird ausgefgührt.
c (create)
%prep- und %build-Abschnitt werden ausgeführt.
i (install)
%prep-, %build- und %install-Abschnitt werden ausgeführt.
l (list)
Es wird überprüft, ob alle im %files-Abschnitt aufgelisteten Dateien vorhanden sind.

Mit dem zusätzlichen Parameter --short-circuit in Verbindung mit den Optionen c und i können die Abschnitte %build bzw. %install ausgeführt werden, ohne die vorherigen Abschnitte durchlaufen zu müssen. Um also nur den %install-Abschnitt auszuführen, lautet der Befehl "rpm -bi --short-circuit Specfile".

Wenn alles funktioniert hat, befindet sich das fertige Binary-RPM in der Datei $topdir/RPMS/Architektur/Name-Version-Release.Architektur.rpm, in unserem Beispiel also in $topdir/RPMS/i386/tin-unoff-19990413-1.i386.rpm; das Source-RPM befindet sich in der Datei $topdir/SRPMS/Name-Version-Release.src.rpm, in unserem Beispiel also $topdir/SRPMS/tin-unoff-19990413-1.src.rpm. Das Binary-RPM kann nun mit "rpm -i $topdir/RPMS/i386/tin-unoff-19990413-1.i386.rpm" installiert werden oder eine vorhandene ältere Fassung mit "rpm -UDatei" auf das neue Paket upgedatet werden. Es ist vollbracht!

Literatur

Diese Einführung kann die Möglichkeiten von RPM nur anreißen - mit den Fähigkeiten von RPM lässt sich problemlos ein ganzes Buch füllen. Für weitere Details seien dem Interessierten folgende Werke nahegelegt:

Maximum RPM ist jedem, der sich näher mit dem Erzeugen komplexer Pakete beschäftigen will, besonders zu empfehlen.