RPM for programmøren

RPM filer opstår selvfølgelig ikke "af sig selv". Der skal bruges et værktøj til at lave dem - nemlig det samme rpm program, som man bruger til alle de andre RPM funktioner.

rpm programmet har en build funktion, som bruges når man skal lave sine egne rpm filer.  En 'build' består af flere faser

  1. prep - udpakning af source filer
  2. setup - installation af patches og konfiguration af softwaren
  3. build - kompilering af softwaren
  4. install - installation af program-, dokumentations- og konfigurations-filer i de korrekte directories
  5. packaging - sammenpakning af source og binary rpm filer
  6. clean - oprydning
Hver af disse faser kan kontrolleres fuldt ud gennem en konfigurations-fil, som rpm-programmet bruger til hele build-processen. Denne konfigurations-fil kaldes en 'spec' fil, og er den vigtigste fil at forstå når man skal lave sine rpm-pakker.
 

Directory struktur

Normalt bruger rpm-programmet en directory-struktur under /usr/src/redhat til at generere rpm-filer. Man kan bruge et andet top-level directory, men /usr/src/redhat er default.

Herunder er der et antal under-directories:

Inden man går i gang med at lave en rpm fil, skal man sørge for at have source-filerne lagt ned i SOURCES directoryet, og en spec-fil i SPECS directoryet. Det kan man f.eks. gøre ved at installere en source rpm-fil.
 

En spec-fil til ssh (Secure Shell)

Der har været en del skriveri om ssh på sslug mailing listen, og for nylig er der skrevet en god web side om, hvordan man bruger dette program. ssh findes ikke i rpm-format, så jeg tænkte at det kunne være nyttigt at bruge det som eksempel på, hvordan man laver en spec-fil og bruger den til at lave rpm-pakker.

Hele spec-filen ligger her, men lad os kigge på den i nogle mindre bidder.

Name: ssh
Summary: Secure Shell - secure network communications
Version: 1.2.25
Release: 2
Copyright: GPL
URL: http://www.cs.hut.fi/ssh/
Group: Utilities/Networking
Source: ftp://sunsite.auc.dk/pub/security/ssh/ssh-1.2.25.tar.gz
Patch: ssh-1.2.25-Makefile.patch
BuildRoot: /tmp/ssh-build

%description
Secure Shell enables you to communicate securely across on unsafe
network such as the Internet. Communication is transparently
encrypted, and thus secured against eavesdropping. The package
includes drop-in replacements for telnet, rlogin, rsh, rcp and
other standard networking tools.
 
Starten på sådan en spec-fil er ret standardiseret. Der er en stribe tags som man skal angive - det er blandt andet al den information om pakken, som rpm -i kommandoen skal levere.

Rækkefølgen af de enkelte tags er ligegyldig, bare de er der.

Source tag'en er vigtig - her fortæller man, hvor man har fundet den oprindelige source fil til programmet, gerne med en URL. Det er en dårlig ide kun at skrive filnavnet - så ved man ikke, hvor man skal gå hen for at finde eventuelle nyere versioner af programmet. Bemærk: Filnavnet der står her, skal matche det filnavn, der ligger i SOURCES directoryet. Så med eksemplet, skal SOURCES directoryet indeholde filen ssh-1.2.25.tar.gz

Patch tag'en er også vigtig - det er ændringer til de oprindelige sources (i form af patches) som skal installeres inden programmet kan oversættes. Disse skal også ligge i SOURCES directoryet. I eksemplet er der kun een patch; hvis man har flere, skal de nummereres, og så lister man dem et ad gangen (et patch uden nummer er automatisk nummer nul):

    Patch: foo-Makefile.patch
    Patch1: foo-glibc.patch
    Patch2: foo-wtmpfix.patch

BuildRoot tag'en er ikke krævet, men anbefales. Det er ikke det directory, hvori programmerne bliver oversat, men derimod det top-level directory, hvori de oversatte programmer og øvrige filer placeres i installations-fasen, inden de pakkes sammen i rpm-filerne.  Hvis ikke man bruger en BuildRoot tag er default at filerne placeres i de "rigtige" directories - /usr/bin, /etc, /usr/lib o.s.v. Det gør måske ikke noget, men hvis der er konfigurations-filer involveret er det en rigtig god ide at bruge BuildRoot tag'en; ellers kan man nemt risikere at komme til at overskrive sine egne omhyggeligt tilpassede konfigurations-filer undervejs i build-processen, eller (måske endnu værre) komme til at inkludere sine egne konfigurations-filer i rpm-pakken, som downloades af tusindvis af brugere. (F.eks. havde jeg en overgang password-filerne til min ISP liggende på ftp.sslug.dk, i en ppp RPM pakke). BuildRoot tag'en er dog ofte lidt besværlig, da man næsten altid skal rette i programmets 'Makefile' eller installations-script, for at de kan finde ud af at installere pakkerne et andet sted end det normale. Men det er ulejligheden værd.
 

prep fasen

prep (prepare) fasen består almindeligvis blot i udpakning af source-filerne til et underdirectory under BUILD. rpm's build-funktion kan automatisk håndtere tar arkiver (også komprimerede), så normalt kan man blot nøjes med at skrive

    %prep

i spec-filen, og så går det af sig selv.
 
 

setup fasen

setup fasen er der hvor der begynder at ske noget.

    %setup
    %patch -p1
    ./configure --prefix=/usr

Denne fase skal gøre programmet klart til at blive oversat. Typisk skal der først installeres de forskellige patches som man har listet, og dernæst skal programmet måske konfigureres.

De linier, der står efter %setup er i virkeligheden almindelige shell kommandoer. Dog er der defineret nogle makroer, som rpm's build funktion håndterer, inden de overgives til kommando-fortolkeren - een af makroerne er %patch. Denne makro kører patch programmet, med patch nummer nul som input. For at apply'e patch nummer 1 skriver man %patch1, patch nummer 2 er %patch2 o.s.v. Rækkefølgen af de forskellige patches er ligegyldig, så længe patch-programmet kan finde ud af det. Man kan også sagtens springe patches over, f.eks. hvis de ikke er relevante for den platform man builder til.

Inden kommandoerne i setup-fasen afvikles, sættes default directory til /usr/src/redhat/BUILD/pakkenavn-version, d.v.s. /usr/src/redhat/BUILD/ssh-1.2.25 i eksemplet. Hvis source-arkivet ikke er blevet pakket ud til det directory navn (f.eks. bruger nogle source-arkiver en underscore i stedet for en bindestreg), så angiver man det rigtige directory navn på %setup linien, f.eks.

    %setup -n foobar_1.17

hvis source-filerne er havnet i /usr/src/redhat/BUILD/foobar_1.17

Default directory er også vigtigt at kende, når man vælger hvilke options man giver til patch-programmet. I eksemplet kaldes patch med -p1 option, fordi patch-filen indeholder ssh_1.2.25 som første element i filnavnet. Men da default directory allerede er nede i ssh-1.2.25, bruges -p1 option for at fjerne det første directory-element fra filnavnet, inden patch leder efter de filer den skal ændre.

Konfiguration af programmet kan ske på mange måder - her er det et configure-script, som kaldes med en option om, at filerne skal bruge /usr som prefix (default er ofte /usr/local, som jeg foretrækker at reservere til ikke-rpm styrede programmer). Andre program-pakker konfigureres via en headerfil eller ved at ændre i Makefile - så må man lave en patch mellem den oprindelige fil og den rettede, og installere den som en patch i patch-listen.

Kommandoen rpm -bp specfile vil afvikle prep- og setup-fasen, og kan bruges til at "teste" om alle ens patches nu også kan installeres uden problemer.
 

build fasen

build fasen er der, hvor programmerne oversættes, og ofte er det blot at køre make. Ligesom ved setup-fasen, er default directory sat til der hvor source-filerne blev pakket ud - hvis man brugte -n optionen til setup fasen, så bliver det "husket" så man ikke skal skrive den igen.

    %build
    make CFLAGS="$RPM_OPT_FLAGS" LDFLAGS="-s"
 
Her er der dog givet nogle parametre til make kommandoen. CFLAGS bliver sat til $RPM_OPT_FLAGS - det er en environment variabel som rpm's build-funktion giver med til alle kommandoerne i build-fasen, og som indeholder standard "optimizer" indstillingerne for programmer, som oversættes med rpm. Default er det "-O2 -m486 -fno-strength-reduce" på i386 platformen.

Kommandoen rpm -bc spefile vil først afvikle prep og setup faserne, og dernæst build-fasen. Hvis man har travlt og allerede har brugt rpm -bp til at afvikle prep og setup, kan man bruge rpm -bc --short-circuit specfile for bare at køre build-fasen.

Det er typisk under build-fasen, at man ramler ind i de fleste problemer. Her kan det være nyttigt at glemme rpm et øjeblik, og i stedet gå ned i build-directoryet og køre build-kommandoerne manuelt, indtil man har fået rettet det hele og programmet kan oversættes.  Så laver man en patch-fil med sine ændringer, tilføjer den til spec-filen (husk at få den med i %setup fasen!), og går så  tilbage til at builde med rpm.
 

install fasen

install fasen sørger for at installere de nu oversatte programmer på de rigtige steder i directory strukturen. Oftest sker det med kommandoen 'make install', da de fleste pakker heldigvis selv kan finde ud af at installere sig selv. Men ellers må man selv skrive de kommandoer der skal til - enten cp, install eller hvad man nu foretrækker.

Det er også vigtigt, at man sørger for at de installerede filer får korrekt ejer/gruppe attributter, og permissions.

ssh spec-filen er lidt mere kompliceret end normalt:

    %install
    rm -rf $RPM_BUILD_ROOT
    mkdir -p $RPM_BUILD_ROOT
    make install

    %post
    if [ ! -f /etc/ssh_host_key ]; then
       echo "Generating ssh host key"
       umask 022
       /usr/bin/ssh-keygen -b 1024 -f /etc/ssh_host_key -N ''
    fi

En del af kompleksiteten skyldes brugen af BuildRoot tag'en - inden filerne kan installeres, skal det sikres at det directory vi installerer i er tomt. Derfor slettes det først, og oprettes så bagefter igen, hvorefter 'make install' klarer resten.  Det er dog ikke sket uden at jeg måtte ændre i Makefile - men det blev gjort med det patch, der blev installeret helt tilbage i setup-fasen.

Normalt vil 'make install' til ssh sørge for at generere filen /etc/ssh_host_key, som er en del af ssh's krypterings-nøglesæt. Men det vil jo ikke være smart at inkludere den fil i rpm-pakken; så ville alle der brugte rpm-pakken jo have denne nøgle til fælles. Derfor er denne del af installationen fjernet fra Makefile'n, og i stedet lagt ud som et post-install script - det står under %post.
Disse kommandoer vil blive afviklet når brugeren installerer den færdige rpm-pakke, d.v.s. hver eneste gang ssh-pakken bliver installeret på en PC. Derved får hver enkelt PC sin egen nøgle-fil.

%post bruges også til andre ting - hvis man laver en rpm-pakke med et shared library i, er det en god ide at køre /sbin/ldconfig i et post-install script; derved opdaterer man den dynamiske linker's tabel over, hvilke biblioteker der er installeret, og det kan så bruges med det samme (uden at man skal reboote systemet).

Der er andre af sådanne scripts, som man kan definere: %post-un afvikles når man af-installerer en rpm-pakke, og der er også pre-install (%pre) og pre-uninstall (%pre-un) scripts.  De kan selvfølgelig kombineres.

Install-fasen køres med kommandoen rpm -bi specfile - man kan også her bruge --short-circuit for kun at afvikle denne fase.
 

files sektionen

Når filerne er blevet installeret, så er det eneste der mangler at pakke dem sammen i en binær rpm-fil. For at kunne gøre det, er det nødvendigt at opremse alle de filer, der er en del af pakken - det sker i %files sektionen.

    %files
    /usr/bin/make-ssh-known-hosts
    /usr/bin/ssh
    /usr/bin/ssh-add

    %config /etc/ssh_config
    %config /etc/sshd_config

    %doc COPYING INSTALL OVERVIEW TODO
    %doc README README.CIPHERS README.SECURERPC README.SECURID README.TIS RFC TODO

Filerne listes blot med deres fulde path-navn (man ser bort fra evt. BuildRoot her). Hvis en fil er en konfigurations-fil, skal man skrive %config førend fil-navnet; det fortæller rpm-programmet, at denne fil ikke må slettes eller overskrives når man opdaterer pakken, men skal gemmes som en .rpmsave fil.

Dokumentations-filer listes med %doc - og dem behøver man ikke at bekymre sig om at skulle installere, de kopieres automatisk fra det directory, hvor source-arkivet er blevet udpakket. Dokumentations-filer placeres i /usr/doc/pakkenavn-version directoryet. Hvilke filer, man tager med som dokumentations-filer er selvfølgelig et skøn, men hellere lidt for mange end for få. (Når man installerer en rpm pakke, kan man bede om ikke at få dokumentations-filerne med - ved at bruge --excludedocs optionen).

Bemærk, at man-pages ikke regnes under dokumentation - de betragtes som en essentiel del af pakken, på lige fod med selve programmet.

Det er selvfølgelig vigtigt, at man får alle filerne med, som hører til pakken. Det kan være nyttigt at køre 'make -n install' for bare at se, hvad pakken foretager sig under installationen, og holde øje med hvilke filer der bliver hvorhen.

Hvis en pakke indeholder så mange filer, at de har deres eget subdirectory, kan man nøjes med at specificere directory'et - så følger alle filerne deri automatisk med. Det skal dog være et directory, der kun bruges af den pågældende pakke - ellers kan man få sig nogle overraskelser.

Det er også muligt at angive, at et tomt directory er en del af pakken - det vil typisk være et directory til f.eks. log-filer, som der jo ikke er nogen af når man laver rpm-filen, men som skal findes for at programmet kan køre. Det skal opremses i %files sektionen som

    %dir /var/log/foobar
 

Den ultimative build-kommando

Når man har en spec-fil, som er helt på plads, kan man bruge een enkelt rpm-kommando til at køre gennem hele build-fasen og generere rpm-filerne:

    rpm -ba --clean specfile
 
Den kommando klarer det hele: Udpakning af sources, installering af patches, konfiguration af programmet, oversættelse, installation, sammenpakning i rpm-filer, og oprydning bagefter så BUILD directory'et er pænt og ryddeligt.
 

Sikken et besvær!

Det er selvfølgelig lidt mere omstændeligt at lave en rpm-fil, end bare at hælde et program igennem en compiler og ned i /usr/local. Nogle gange kan det være frustrerende små detaljer i spec-filen, som gør at en build fejler - og det er som regel først efter, at din PC har tygget et par timer på at oversætte programmet...

Men min erfaring er, at den tid man bruger på at nørkle en ordentlig spec-fil sammen, er godt givet ud. Det er meget nemmere at holde styr på sin software, når man med en enkelt kommando kan få en oversigt over, hvad der er installeret, og hvilke filer hver pakke indeholder.

For nylig opgraderede jeg mit system fra RedHat 4.2 til 5.1. Det er en større opgradering, blandt andet fordi der skiftes fra de gamle libc (version 5) til det nye glibc (version 6). RedHat's installations-program klarede det meste af opgraderingen uden problemer, men jeg havde en del software pakker, som jeg selv havde lavet i rpm-format. Nu var det nemt at finde dem frem - jeg kunne bruge

    rpm -qia | grep -v Manhattan

til at liste alle pakker på mit system, og filtrere RedHat's nye pakker fra. Det blev til en liste på 10-15 pakker, som jeg selv måtte stå for at opdatere til glibc - og fordi jeg havde installeret dem liggende som RPM pakker, kunne jeg nemt gentage build-processen, og rette de småting der skulle til for at de kunne oversættes med glibc. Selv programmer, jeg sidst havde haft fat i for 2 år siden kunne nemt gen-oversættes - fordi spec-filen indeholdt alle de oplysninger, der skulle til at lave programmet. Jeg behøvede ikke at granske hukommelsen for at komme i tanke om, hvordan det nu var jeg havde konfigureret programmet sidst.

Så bare klø på - det lønner sig.
 



Henrik Størner <storner@image.dk>