ChangeBlog  •  Archiwum  •  Kategorie  •  Artykuły  •  Galeria  •  Czytelnicy  •  Rupieciarnia
RSS wpisów  |  RSS komentarzy
Multisource

Przy składaniu bardziej skomplikowanych pakietów korzysta się często z dwóch funkcji rpm-a - pierwsza to wykorzystywanie przy budowaniu więcej niż jednej paczki ze źródłami, druga, dużo bardziej widoczna, to możliwość rozdzielania wynikowych plików do kilku rozdzielnych pakietów. Ale zacznę od pakietów "multisource", czyli plików .spec posiadających więcej niż jeden wpis "Source:" w preambule.

Powodów dla których jeden plik .spec odwołuje się do kilku paczek źródłowych może być kilka - najprostszy powód to ten, że źródła programu są rozdzielone na kilka części. Tak jest np. ze źródłami XFree86, tak samo jest też z glibc (obligatoryjny praktycznie moduł "linuxthreads" jest rozpowszechniany w osobnej paczce), tak jest też w przypadku mniejszych aplikacji - np. POV-Ray również składa się z dwóch części (kod źródłowy raytracera oraz pliki pomocnicze). Tak więc często po prostu jest to przymusem. Czasem też to konstruktor pakietu decyduje się połączyć w momencie budowania kilka źródeł w jeden pakiet, gdy np. źródła są tak czy siak potem ze sobą ściślej powiązane. A czasem po prostu konstruktor chce dodać coś od siebie do pakietu - np. dokumentację pochodzącą z zewnętrznego źródła, polskie manuale gdy w oryginalnej paczce ich nie ma, albo swoje własne pliki konfiguracyjne.

Wiadomo, że teorię najlepiej pokazać w praktycznym zastosowaniu, dlatego może pora na parę "real-life" przykładów. Na początek coś nieskomplikowanego - syslog-ng. Fajny program, ale używa dosyć egzotycznej biblioteki libol. Jest to jedyny program w moim systemie który wymaga tej biblioteki, dlatego najlepiej jest linkować go statycznie z tą biblioteką, a samego libol wcale nie mieć w systemie. Najlepszym i najbardziej uniwersalnym rozwiązaniem wydaje się być budowanie "wiązane" - jeden plik .spec zbuduje najpierw statyczną wersję libol, a potem syslog-ng (używając tej biblioteki). A potem spaczkuje tylko syslog-ng. W ten sposób całość będzie całkowicie zautomatyzowana, a konstruktor pakietu nigdy nie będzie miał libol zainstalowanej w systemie. Pakiet źródłowy RPM będzie oczywiście wymagał źródeł syslog-ng oraz libol. W dwóch oddzielnych paczkach. OK, preambuła wygląda tak:

%define libol_version 0.3.10
Summary: Nowoczesny daemon zastępujący syslogd
Name: syslog-ng
Version: 1.6.0rc3
Release: 1
License: GPL
Group: System
Url: http://www.balabit.hu/products/syslog-ng/
Source0: %{name}-%{version}.tar.bz2
Source1: libol-%{libol_version}.tar.bz2
BuildRoot: /var/tmp/%{name}-%{version}

Jako "podstawowe" źródła mam zdefiniowanego syslog-ng, jako źródła "uzupełniające" mam libol. Aby sobie ułatwić upgrade-owanie części tego pakietu wersję libol wypchnąłem do osobnej zmiennej, zdefiniowanej na szczycie preambuły. Jeśli trzeba czegoś użyć w więcej niż jednym miejscu speca, to dobrze mieć to w jakiejś zmiennej - w razie modyfikacji wystarczy zmienić deklarację zmiennej, a nie każde wystąpienie tego "czegoś" w pliku .spec.

Potem trzeba te źródła rozpakować. Sekcja %prep wygląda tak:

%prep
%setup -q
%setup -q -T -D -a 1

Po kolei: pierwsze wywołanie %setup rozpakowuje źródła domyślne, czyli Source0: - a więc syslog-ng. Domyślne opcje nie ingerują w przebieg operacji - a więc rpmbuild spróbuje rozpakować Source0. Na razie to standard. Ale drugie wywołanie %setup nie jest już takie proste - po pierwsze, opcja '-T' wyłącza rozpakowywanie domyślnych źródeł. Opcja '-D' wyłącza usuwanie katalogu %{name}-%{version} przed próbą rozpakowania. A opcja '-a 1' każe rozpakować drugi zestaw źródeł, Source1:, ale dopiero po wejściu do katalogu %{name}-%{version}. W skrócie powinno to zadziałać tak, że zostaje rozpakowany syslog-ng, następnie rpmbuild wchodzi do jego katalogu i tam rozpakowuje źródła libol. I faktycznie tak też działa. Wszystkie sprawy związane z libol postaram się załatwić w obrębie katalogu ze źródłami syslog-ng, z jednego prozaicznego powodu - nie muszę wtedy specjalnie "dozbrajać" sekcji %clean - libol zostanie usunięty razem ze źródłami syslog-ng.

Po rozpakowaniu czas na konfigurację i kompilację. Sekcja %build:

%build
cd libol-%{libol_version}
./configure --prefix=%{_builddir}/%{buildsubdir}/libol --disable-shared --enable-static
make CFLAGS="$CFLAGS"
make install CFLAGS="$CFLAGS"
cd ..

%configure --with-libol=%{_builddir}/%{buildsubdir}/libol/bin
make

Tutaj najpierw trzeba wejść do podkatalogu ze źródłami libol, a w nim uruchomić ./configure z opcjami które włączą generowanie tylko statycznych bibliotek oraz ustawią prefix instalacyjny na %{_builddir}/%{buildsubdir}/libol. Czyli po kompilacji libol zostanie zainstalowane w obrębie katalogu ze źródłami syslog-ng, równolegle do katalogu ze źródłami libol. Katalog ze źródłami libol różni się od katalogu instalacyjnego libol tym, że ma w nazwie zawartą wersję. Potem wykonywane jest "make" i "make install". Nie pamiętam już dlaczego tak redefiniuję tutaj zmienne CFLAGS, ale skoro tak robię, to pewnie mam powód - pewnie libol inaczej ignoruje środowiskową zmienną $CFLAGS. Po "make install" nadchodzi "cd ..", czyli wyjście z katalogu ze źródłami libol o jeden poziom wyżej - do głównego katalogu ze źródłami syslog-ng. Tutaj wywołuję teraz jeszcze raz configure, ale tym razem jest to configure źródeł syslog-ng. Tutaj używam już makra %configure, aby mieć poprawnie poustawiane katalogi instalacyjne - przy instalowaniu libol nie zależało mi specjalnie na tym, bo to i tak tylko etap przejściowy. Tutaj wskazuję procesowi configure że libol mam zainstalowane w innym miejscu niż jest to robione standardowo. Potem uruchamiam "make" samego syslog-ng i po wszystkim. Proces kompilacji nie ma wyboru i musi wlinkować libol statycznie, głównie dlatego że dynamiczna wersja libol nie istnieje :)

Pozostałe sekcje, %install oraz %clean są już standardowe. W rezultacie pod koniec wszystkie źródła są usuwane, a ja dostaję działający syslog-ng i nie muszę się przejmować libol. Jest to rozwiązanie bardzo wygodne, bo w razie uaktualniania wystarczy że wrzucę gdzie trzeba paczki ze źródłami syslog-ng i libol, uruchomię budowanie, a cała reszta "zrobi się sama".

OK, pora na coś bardziej złożonego: XFree86. Tutaj chodzi o to, że źródła XFree86 są rozpowszechniane w numerowanych "kawałkach". W zależności od wymagań kompilującego potrzebna jest różna liczba kawałków. A procesem kompilacji, opcjami steruje się za pomocą specjalnego pliku opisującego co i jak ma być robione, co pominąć itp. I ten plik różnych definicji zawiera całą "konfigurację" X-ów, będzie załączony w pakiecie źródłowym jako dodatkowe "źródła". Bez zbędnych ceregieli - oto preambuła:

Summary: X-Serwer, biblioteki, zestaw podstawowych aplikacji
Name: XFree86
Version: 4.3.0.1
Release: 1
License: GPL
Group: Biblioteki
BuildRoot: /var/tmp/%{name}-%{version}

Source0: X430src-1.tgz
Source1: X430src-2.tgz
Source2: X430src-3.tgz
Source3: XFree86-host.def

Patch0: XFree86-xkb_pl.patch
Patch1: XFree86-xinitrc.patch

Mam 4 "źródła". Pierwsze trzy to paczki z projektu XFree86, a czwarta to właśnie globalny plik konfiguracyjny X-ów, dostrojony do moich potrzeb. Do tego dwa patche zmieniające domyślną polską mapę klawiatury oraz domyślny xinitrc, to też taki mój "lokalny koloryt". Miewam czasem specjalne wymagania :P

W tym pakiecie kluczem jest sekcja %prep. Całe źródła XFree86 rozpakowują się do katalogu "xc", każda paczka zawiera osobną część drzewka, ale wszystkie mają ścieżki bezwzględne, tzn. każda paczka zawiera ten odgórny katalog "xc" - wystarczy tylko rozpakowywać wszystkie potrzebne paczki w jednym katalogu. A oto i %prep:

%prep
%setup -q -n xc
%setup -q -T -D -b 1 -n xc
%setup -q -T -D -b 2 -n xc
cp %{SOURCE3} config/cf/host.def
%patch0 -p1
%patch1 -p1

Najpierw rozpakowywane są źródła domyślne (numer 0). Przy okazji opcja '-n' powiadamia rpmbuild, że powstały katalog nie ma nazwy w schemacie %{name}-%{version}. Potem rozpakowuję pozostałe dwie sztuki źródeł, używając opcji '-b' - bo każda paczka zawiera identyczny układ katalogów i należy je rozpakowywać w jednym katalogu - tak, żeby drzewka się nakryły. A potem następuje coś, dla czego wybrałem ten przykład z XFree86 :) Chodzi o tę linijkę z "cp". Source3: to ten specjalny plik konfiguracyjny, należy go umieścić w odpowiednim miejscu drzewka (config/cf/host.def). Robię to za pomocą zwykłego "cp" - jedyne co tutaj ważne, to sposób odwołania się do źródeł. rpmbuild udostępnia nazwy plików (razem ze ścieżkami) opisane w preambule jako Source poprzez właśnie takie makra. Tutaj więc kopiuję %{SOURCE3} na jego miejsce przeznaczenia (od razu zmieniając mu nazwę na właściwą). Przy okazji ścieżka docelowa jest względna, bo wiem że znajduję się już w katalogu "xc" (makro %setup przenosi tam po rozpakowaniu źródeł). A potem nakładam te dwa patche, ale cała reszta już nie jest ważna. Chodzi tylko o pokazanie, że jeśli coś opiszesz w Source?: to możesz się potem dobrać używając makra %{SOURCE?}

A na koniec jeszcze jedna "wielopaczkowa" konstrukcja - glibc. Tutaj sprawy mają się następująco: nowe glibc są rozpowszechniane jako pojedyncza paczka, ale przy kompilacji trzeba dograć do nich dodatek "linuxthreads". We wcześniejszych czasach identycznie trzeba było robić też np. z dodatkiem "crypto", ale on już wszedł na stałe do głównej dystrybucji glibc. Tak że teraz na zewnątrz pozostał tylko ten "linuxthreads". Ale to jeszcze nie koniec - developerzy glibc zalecają, by konfigurowanie i budowanie glibc odbywało się w katalogu innym niż ten, w którym znajdują się źródła glibc. Katalog w którym będzie się odbywała kompilacja i katalog ze źródłami nie powinny też być w sobie w żaden sposób zagnieżdżone. Te wymogi sprawiają, że glibc jest dobrym przykładem nieco pokrętniejszej budowy pakietów. A, gdyby to jeszcze było zbyt mało - ja używam nieco starszych źródeł i dopiero przy budowaniu nakładam patcha podnoszącego wersję. A oto preambuła:

Summary: Standardowa biblioteka C z projektu GNU
Name: glibc
Version: 2.3.2
Release: 4
License: GPL
Group: Biblioteki

Source0: glibc-2.3.1.tar.gz
Source1: glibc-linuxthreads-2.3.2.tar.bz2

Patch0: glibc-2.3.1-2.3.2.diff.bz2
Patch1: glibc-install.patch
Patch2: glibc-ldd.patch
Patch3: glibc-lthrds_noomit.patch

BuildRoot: /var/tmp/%{name}-%{version}

Dwie paczki źródeł. Jedna zawiera glibc-2.3.1, druga dodatek linuxthreads (ale do wersji 2.3.2). Cztery patche - jeden robiący "update" glibc do 2.3.2, jeden poprawiający nieco proces instalacji na potrzeby rpmbuild, pozostałe dwa to znowu "lokalny koloryt". Potem następuje sekcja %prep - znowu, to ona jest tutaj najważniejsza

%prep
#glibc
%setup -q -n glibc-2.3.1

#patchowanie do wersji 2.3.2
%patch0 -p1

#linuxthreads
%setup -q -D -T -a 1 -n glibc-2.3.1

#patch na linuxthreads
%patch3 -p1

#patch no-ldconfig
%patch1 -p1

#patch na ldd
%patch2 -p1

#zakłada katalog na potrzeby budowania
%setup -q -T -c -n glibc

Pozostawiłem oryginalne, autorskie komentarze :) Pierwsze makro %setup rozpakowuje źródła glibc. Od razu podaję nazwę wynikowego katalogu, bo te źródła są "o oczko" starsze niż wersja samego pakietu rpm. Następnie źródła są patchowane. A potem rozpakowują się źródła linuxthreads. Tutaj jednak sytuacja ma się inaczej niż z XFree86 i dodatek musi zostać rozpakowany wewnątrz katalogu z głównymi źródłami. Stąd w %setup zamiast -b używam opcji -a. Od razu podaję też nazwę katalogu do którego ma wejść makro %setup, wiadomo, źrodła są w innej wersji niż mówi tag Version: pakietu. Potem kolej na nałożenie kolejnych patchy, a na końcu gwóźdź programu - założenie katalogu w którym będzie się odbywać sama kompilacja. Mógłbym użyć zwykłego "mkdir", ale ja to robię inaczej - używam makra %setup podając mu nazwę katalogu (-n) i każąc go założyć (-c), ale nie rozpakowywać domyślnie żadnych źródeł (-T). Dzięki temu makro %setup założy mi pusty katalog, czyniąc go jednocześnie domyślnym katalogiem do którego będę automatycznie przenoszony na początku każdej kolejnej sekcji. Tylko o to mi chodzi - żebym nie musiał potem do niego samodzielnie wchodzić. Dlatego też zakładam ten katalog na samym końcu - bo to, który katalog będzie "domyślnym" jest decydowane kolejnością wywołań %setup - obowiązuje to, co ustawi ostatnio wywołane makro. W tym przykładzie chodziło mi o pokazanie, jak za pomocą %setup założyć sobie taki pusty, domyślny katalog :)

Ale to nie koniec, bo teraz trzeba jeszcze skonfigurować źródła i skompilować je - czyli sekcja %build:


%build
../glibc-2.3.1/configure \
--prefix=%{_prefix} \
--mandir=%{_mandir} \
--infodir=%{_infodir} \
--libexecdir=%{_libexecdir} \ 
--localstatedir=%{_localstatedir} \
--sysconfdir=%{_sysconfdir} \
--disable-profile \
--disable-omitfp \
--enable-add-ons \
--enable-shared \
--enable-kernel=2.4.20
make

Dlaczego tak dziwnie? Z jednego powodu - muszę będąc w katalogu przeznaczonym na kompilację wywołać configure znajdujące się w katalogu ze źródłami. To utrudnia użycie makra %configure i zmusza mnie do wklepania stosownych opcjiręcznie. Być może jest jakieś wyjście z tej sytuacji, ale mi nie chciało się nad tym zastanawiać zbyt długo :) Potem już nie występują takie kłopoty, jedyna zmiana jaką trzeba wprowadzić to przeróbka sekcji %clean z

%clean
rm -rf %{buildroot} %{_builddir}/%{buildsubdir}

na

%clean
rm -rf %{buildroot} %{_builddir}/%{buildsubdir} %{_builddir}/glibc

co ma na celu usuwanie oprócz głównego katalogu ze źródłami także tego dodatkowego katalogu, w którym odbywała się konfiguracja i kompilacja. A, jeszcze jedno - oczywiście gdyby nie patchować źródeł tylko pracować od razu na nowej wersji źródeł, to specfile dałoby się nieco uprościć, przede wszystkim zniknęłaby większość opcji '-n' w sekcji %prep.

Tak że, jak widzisz, wykorzystanie kilku paczek ze źródłami lub plikami pomocniczymi nie jest specjalnie skomplikowana. Można używać gotowych makr %setup, można też dobierać się bezpośrednio do plików opisanych w preambule (przez makra %{SOURCE0} itp.). To daje możliwości dodawania do paczek swoich własnych dodatków, które potem zostaną "wbudowane" w końcowy binarny pakiet, pozwala to też na "zespawanie" dwóch osobnych zestawów źródeł w jeden program (jak to robię w przypadku syslog-ng+libol). Jeśli coś jest nadal niejasne, to popróbuj pisania małych, testowych plików .spec. Operowanie na wielu paczkach ze źródłami, rozpakowywanie ich na różne sposoby czy wkopiowywanie w określone lokacje naprawdę nie jest trudne.