Używam systemu RPM jako narzędzia, które utrzymuje mój system w porządku - ułatwia mi deinstalację całych pakietów oprogramowania, upgrade, pozyskiwanie informacji o pakietach, zajmuje się też śledzeniem siatki zależności między poszczególnymi programami i bibliotekami.
Nie używam jednak żadnej z dystrybucji Linuksa, przez co nie mam raczej możliwości instalowania gotowych, przygotowanych przez kogoś pakietów z oprogramowaniem. Mógłbym co prawda posiłkować się pakietami z różnych dystrybucji, ale one by raczej nie pasowały zbyt dobrze do mojego systemu - RPM to tylko szkielet, to że kilka dystrybucji używa tego samego mechanizmu RPM nie oznacza wcale, że ich pakiety będą między sobą kompatybilne. Bo nie będą.
Więc jestem skazany na samodzielne tworzenie pakietów RPM, a na dodatek muszę to robić praktycznie z każdym instalowanym pakietem. Mimo sporego nakładu sił i chęci jaki trzeba włożyć w zrozumienie całego mechanizmu pakietów RPM, to wszystko jednak się opłaca. Nie chcę nikogo agitować do pakowania się w sytuację w jakiej ja się znalazłem - czyli utrzymywanie całego systemu linuksowego własnymi siłami (choć to w sumie nic złego, mnie to jakoś nie zabiło) - ale nawet "pasywny" użytkownik systemu zbudowanego na pakietach RPM może zyskać na zrozumieniu tego, co utrzymuje jego system w porządku.
Na system RPM składają się następujące komponenty:
- Baza informacji o zainstalowanych pakietach
- Przechowywana zwykle w /var/lib/rpm/, składa się z wielu plików w formacie Berkeley DB.
- Aplikacje operujące na bazie danych
- Różne programy które zmieniają bądź odpytują bazę danych. Instalują pakiety, usuwają pakiety, pobierają informacje o pojedynczych plikach lub pakietach etc.
- Pakiety binarne
- Najczęściej spotykane pakiety z rozszerzeniem .rpm. Zawierają gotowe do zainstalowania, spakowane pliki wraz z informacjami o ich typie i prawach dostępu, oraz nieco informacji o samym pakiecie - zależności pakietu, informacje o nim, kategorię do której należy i inne takie.
- Pakiety źródłowe
- Rzadziej spotykana forma pakietów rpm. Mają rozszerzenie
.src.rpm i nie zawierają gotowych do zainstalowania plików. Ich
przeznaczenie jest inne - są czymś w rodzaju ładnie opakowanego kodu
źródłowego, który należy gdzieś rozpakować, skonfigurować oraz zainstalować.
Wszystko to pod kontrolą automatu RPM i przy jak najmniejszym obciążania
użytkownika. Przynajmniej w założeniach :)
Takie paczki zawierają w sobie spakowany kod źródłowy programu oraz ewentualne patche, które zostaną nałożone przed kompilacją. Źródła są następnie konfigurowane według określonego wzorca, kompilowane, a z wynikowych plików tworzone są binarne pakiety RPM gotowe do instalacji. Jest to czysty i wygodny sposób kompilowania programów, bowiem taki pakiet źródłowy zawiera oprócz "zwykłych" zależności także listę pakietów potrzebnych przy kompilacji, a sam użytkownik zwykle potrzebny jest do dwóch tylko czynności - uruchomienia procesu przebudowy takiego pakietu i późniejszej instalacji pakietów wynikowych. Brzmi to bardzo miło w dosyć złożonym światku linuksowych kompilacji, tyle że jest w tym jeden hak - pakiet źródłowy musi być hiper-poprawnie skonstruowany i dopasowany dość ściśle do systemu, na którym ma być kompilowany. Co zwykle oznacza, że trzeba korzystać z pakietów źródłowych przygotowanych przez naszą dystrybucję. "Zwykłe" źródła da się skompilować praktycznie na każdym średnio przystosowanym systemie, ale pakiety źródłowe RPM dodają tutaj warstewkę pośredniczącą - czyli mechanizm RPM właśnie - co zwiększa szanse na niepowodzenie. Jest to bezpośrednio powiązane ze wspomnianą już niezgodnością pakietów RPM pochodzących z różnych źródeł między sobą.
Oczywiście po zrozumieniu budowy i zasad działania pakietów RPM możliwe jest poprawienie dowolnego pakietu .src.rpm tak, aby dał się przebudować na dowolnym systemie...
I tutaj zaczynają się schody. Od czego należy zacząć? Hmm. Pierwszym krokiem będzie przygotowanie środowiska pracy. Potrzebny będzie katalog dedykowany budowaniu pakietów RPM, przyda się też porządny edytor do tworzenia specyfikacji pakietów (jak zwykle, Vim będzie dobrym wyborem :)
Najpierw należy wybrać sobie miejsce na katalog przeznaczony budowaniu pakietów RPM. Domyślnie tym katalogiem jest /usr/src/rpm, ale prawo zapisu do tego katalogu ma tylko root. I nie należy korzystać z tego katalogu (/usr/src/rpm) - pakiety RPM można bezproblemowo budować korzystając z uprawnień zwykłego użytkownika (tylko instalacja i pokrewne operacje wymagają praw superużytkownika). A więc skoro nie ma potrzeby korzystania z konta roota, to nie należy tego robić, prawda? Załóżmy, że na potrzeby rpm stworzyliśmy katalog ~/rpm. Teraz trzeba tylko jeszcze powiedzieć mechanizmom RPM, żeby z niego korzystały (zamiast próbować dobierać się do /usr/src/rpm).
A można zrobić to poprzez plik ~/.rpmmacros. Tutaj mała dygresja - RPM jest bardzo elastyczny jeśli idzie o jego ustawienia, większość z nich to zmienne (makra), które można przedefiniować. Makr tych jest całe zatrzęsienie, dodatkowo wielka ich część jest zdefiniowana poprzez inne makra, lub jest definiowana warunkowo, więc łatwo się w tym pogubić. A na dodatek nie ma jednego porządnego podręcznika opisującego standardowe makra wykorzystywane przez RPM. Dlatego trzeba sobie jakoś inaczej radzić. Najlepszym sposobem jest chyba szukanie tego, co chcemy zmienić w plikach /usr/lib/rpm/macros, /usr/lib/rpm/rpmrc oraz pokrewnych.
Obok pliku makr istnieje też plik innych ustawień, generalnie dotyczących architektury komputera docelowego. Plik zwie się /usr/lib/rpm/rpmrc, a jego ustawienia można "przykryć" za pomocą pliku ~/.rpmrc. Jeśli jednak poczujesz się zagubiony w gąszczu tych wszystkich deklaracji, możesz skorzystać z polecenia rpm --showrc - wypluje ono aktualną listę wszystkich makr i ustawień, w takiej formie w jakiej by były one używane po przeparsowaniu przez rpm wszystkich plików konfiguracyjnych. Grepowanie wyjścia tego polecenia może bardzo szybko wskazać, które makro odpowiada za zachowanie które chcemy zmienić. Jeśli "ustawienie" w swojej definicji ma postać mniej-więcej nazwa wartość, to można je zmienić wpisem w ~/.rpmmacros. Jeśli jednak wygląda ono raczej tak: nazwa: wartość (chodzi o ten dwukropek), to należy je zmieniać w ~/.rpmrc. Uff.
Wróćmy jednak do konfiguracji RPM. Za główny katalog używany przez RPM przy budowaniu pakietów odpowiada makro %_topdir. Aby wskazać inny niż domyślny katalog, wystarczy w pliku ~/.rpmmacros przedefiniować to makro. A przy okazji można ustawić inne opcje, dotyczące np. podpisywania tworzonych przez nas pakietów naszym kluczem PGP czy inne takie drobnostki. Mały wycinek z mojego ~/.rpmmacros
%_topdir /home/grzegorz/rpm %_tmppath /tmp %packager Grzegorz Niewęgłowski %vendor QuaTrin
Jak widać ustawiam tutaj ścieżkę do głównego katalogu rpm, katalogu tymczasowego, oraz ustawiam pola Packager: oraz Vendor: (widoczne np. w wyjściu rpm -qi) na wartości które lepiej odpowiadają mojemu środowisku :)
Te drobne poprawki pozwalają już na w miarę wygodne budowanie pakietów przy użyciu konta zwykłego użytkownika. A jak będzie wyglądać zawartość katalogu rpm? Będzie on miał w sobie dodatkowe podkatalogi. Będą to SRPMS, RPMS, SOURCES, SPECS i BUILD. SOURCES będzie przechowywał paczki ze źródłami programów. W SPECS znajdą swoją ostoję specyfikacje pakietów, czyli potocznie zwane "spece". Do BUILD będą rozpakowywane źródła pobierane z SOURCES, tam też dokonywała się będzie kompilacja programów. A RPMS i SRPMS będą przechowywały powstałe w ostatniej fazie procesu produkcyjnego binarne i źródłowe pakiety RPM. Powinieneś pozakładać te podkatalogi. W razie wątpliwości skopiuj strukturę z /usr/src/rpm.
Przy budowaniu pakietów system rpm kieruje się zawsze specjalnym, pojedynczym plikiem - skryptem opisującym proces budowy. Jest to tak zwany plik .spec
Pliki .spec mają własną składnię i gramatykę, która może być nieprzejrzysta. Jest to jednak "serce" procesu budowania pakietów i trzeba przez to przebrnąć. Spec podzielony jest na sekcje. Kolejność sekcji zwykle jest obojętna i nie gra roli, chyba że nie jest obojętna i rolę gra ;) Sekcje mogą być banalnie proste lub diabelnie złożone, w zależności od autora pliku .spec, dystrybucji oraz samego pakietowanego programu. W tym rozdziale zamieszczam przykładowy plik .spec (niespecjalnie ładny) wraz z oznaczeniem każdej z sekcji.
Fragment speca:
Name: procps
Version: 3.1.7
Release: 1
Summary: System and process monitoring utilities
License: LGPL, GPL, BSD-like
Group: Applications/System
Packager: <procps-feedback@lists.sf.net>
Source: http://procps.sf.net/procps-%{version}.tar.gz
URL: http://procps.sf.net/
BuildRoot: %{_tmppath}/procps-root
%description
The procps package contains a set of system utilities
which provide system information. Procps includes ps,
free, sysctl, skill, snice, tload, top, uptime, vmstat,
w, and watch. You need some of these.
Objaśnienie:
Preambuła podaje informacje o pakiecie. Składa się z szeregu jednolinijkowych pól, oraz dłuższego opisu rozpoczętego obowiązkową dyrektywą %description. Część pól jest fakultatywna (np. Packager:), inne, jak np. Name:, są obowiązkowe. Część tych pól (jak np. Packager: można zdefiniować np. w pliku ~/.rpmmacros - ale definicje w specu mają zwykle pierwszeństwo. Preambuła może (w nieco zmienionej postaci) wystąpić kilkukrotnie w pliku .spec, w połączeniu ze zwielokrotnionymi sekcjami %files - pozwala to na tworzenie z jednego speca kilku różnych pakietów (np. *-libs, *-static, *-devel itp.).
Fragment speca:
%prep %setup -q
Objaśnienie:
Sekcja rozpoczęta linią %prep służy przygotowaniu źródeł do konfiguracji i kompilacji. Zwykle używa się tu po prostu makra %setup, które weźmie pakiet ze źródłami i rozpakuje go do katalogu przeznaczonego na kompilację. A wszystkie te dane (plik ze źródłami, katalog docelowy) zawarte są albo w preambule, albo w plikach konfiguracyjnych RPM. W sekcji %prep dokonuje się także nakładania patchy na rozpakowane źródła (za pomocą makra %patch). Tutaj też często wywołuje się popularne ./configure i nawet istnieje do tego celu specjalne makro %configure (czego by się można spodziewać) - generalnie robi się tutaj wszystko to, co musi nastąpić przed samą kompilacją, przed wywołaniem "make".
Fragment speca:
%build make CC="gcc $RPM_OPT_FLAGS" LDFLAGS=-s
Objaśnienie:
Sekcja %build odpowiada za kompilację pakietu. Zwykle wywołuje się tutaj tylko "make" z odpowiednimi w danej sytuacji parametrami. Oprócz makr można w plikach .spec posługiwać się zwykłymi poleceniami shella (sekcje pliku .spec są parsowane trochę jak skrypty shellowe), tak że można umieszczać tutaj polecenia tak, jakby się je wpisywało w linii poleceń. Co widać w tym tutaj wywołaniu "make", redefiniującym sobie własne ustawienia zmiennych $CC i $LDFLAGS.
Fragment speca:
%install rm -rf $RPM_BUILD_ROOT make DESTDIR=$RPM_BUILD_ROOT install
Objaśnienie:
Sekcja %install odpowiada za zainstalowanie skompilowanych plików. Jednak pliki nie są instalowane w /, ale w pustym katalogu $RPM_BUILD_ROOT (co zwykle oznacza jakiś katalog w obrębie /var/tmp. A więc ważne tutaj jest, by proces instalacji pozwolił się dać "przekierować" do jakiegoś katalogu (zwanego zwyczajowo "fake root"). Większość plików Makefile pozwala na to za pośrednictwem zmiennej $DESTDIR, ale niektóre używają innych metod, lub nie posiadają takiej możliwości. W najgorszym wypadku trzeba będzie poprawić Makefile programu aby zbudować pakiet rpm.
Fragment speca:
%clean rm -rf $RPM_BUILD_ROOT
Objaśnienie:
Sekcja %clean nie jest wprawdzie obowiązkowa, ale jej zadaniem jest zwykle usunięcie zawartości "fake root". Ja lubię dodawać tutaj od razu usuwanie rozpakowanych źródeł, ale to tylko kwestia moich przyzwyczajeń. Ot, takie udogodnienie które wysprząta poligon po budowie pakietu.
Fragment speca:
%post # add libproc to the cache /sbin/ldconfig
Objaśnienie:
Sekcja %post jest jedną z czterech sekcji, które nie są wykonywane przy budowie pakietu, ale później, przy jego instalacji/usuwaniu. Istnieją sekcje %post, %pre, %postun, %preun które mogą wykonywać różne czynności odpowiednio po instalacji plików, przed instalacją plików oraz po odinstalowaniu i przed odinstalowaniem. Np. po instalacji pakietów zawierających biblioteki warto uruchomić ldconfig - ale trzeba to zrobić po tym, jak pliki zostaną umieszczone gdzie trzeba - dlatego też jest to robione w sekcji %post. Podobnie można by to robić po odinstalowaniu pakietu, w sekcji %postun. Sekcje %pre* też znajdują swoje zastosowanie, gdy trzeba wykonać jakąś ostatnią akcję przy odinstalowywaniu a jest do tego jeszcze potrzebny rzeczony pakiet (np. przywrócenie starego bootsektora przy odinstalowywaniu lilo)
Fragment speca:
%files %defattr(0644,root,root,755) %doc NEWS BUGS TODO COPYING COPYING.LIB README %attr(555,root,root) /lib/libproc.so* %attr(555,root,root) /bin/* %attr(555,root,root) /sbin/* %attr(555,root,root) /usr/bin/* %attr(0644,root,root) /usr/share/man/man1/* %attr(0644,root,root) /usr/share/man/man8/*
Objaśnienie:
%files to druga obok preambuły sekcja która może wystąpić wielokrotnie. Ta sekcja odpowiada za oznaczenie plików które mają być umieszczone w wynikowym pakiecie, dodatkowo określane są domyślne prawa dostępu do plików i inne opcje które nie zostały jednak wykorzystane w tym przykładowym pliku :) Pliki które są tutaj "wybierane" będą wzięte z "fake root", więc muszą tam zostać umieszczone przez działania z sekcji %install
Słowa otuchy: Struktura pliku .spec może wydawać się skomplikowana, a używane polecenia niezrozumiałe, ale na szczęście wszystkie spece są do siebie bardzo podobne, a zestaw poleceń logiczny i uporządkowany. Ważna jest w sumie jedynie pewna konstrukcja - najpierw, w preambule, opisuje się pakiet - nazwa, wersja, zależności, dane osoby pakietującej itp. Potem następuje sekcja %prep, czyli rozpakowanie, patchowanie, konfigurowanie źródeł. Potem sekcja %build, czyli kompilacja. Następna jest sekcja %install, która musi poukładać pliki wynikowe gdzieś w obrębie "fake root". Następnie trzeba podać pliki które mają być włączone w pakiet i określić ich atrybuty, oraz ewentualnie doczepić do pakietu jakieś czynności które mają być wykonywane przy okazji instalowania/usuwania pakietu. I w zasadzie to są jedyne obowiązkowe "cechy" speca. Reszta zależy całkowicie od autora specyfikacji - np. sekcja %install może do zainstalowania plików w "fake root" użyć polecenia "make", może też użyć owijki w postaci makra %makeinstall, można też kopiować pliki za pomocą polecenia "install", makra %install, albo nawet zwykłego "cp". Systemowi RPM jest to absolutnie obojętne, a wynikowy pakiet będzie identyczny niezależnie od użytych technik. Użytkownik ma wolną rękę i naprawdę nie ma "jedynej słusznej" metody na napisanie specfile. Owszem, są zalecenia, przyjęte techniki itp., ale ma się tutaj taką samą dowolność, jak przy pisaniu skryptu shellowego - liczy się zadanie i końcowy efekt, szczególiki są zwykle mało istotne. Może te dodane słowa nadadzą rpm-owi bardziej "ludzką" twarz.
Hmm, może teraz w następnym rozdziale polecimy ze szczegółowym opisem każdej z sekcji wraz z rozpiską dostępnych poleceń, tak aby mogło to potem służyć jako referencja przy pisaniu plików .spec.
Preambuła to tylko opis, charakterystyka. Składa się z pojedynczych tagów oraz bardziej wylewnego opisu zawartości pakietu. Opis ten jest obowiązkowy, tagi już niekoniecznie (część obligatoryjna, część fakultatywna). Każdy z tagów jest definiowany w pojedynczej linii, w prostej formie "Tag: wartość". Proste, prawda? No to może polecę z opisem dostępnych tagów:
Name: mutt
Name:
Nazwa dla pakietu. To było łatwe do przewidzenia :)
Version: 1.4i
Version:
Wersja oprogramowania zawartego w pakiecie.
Release: 3
Release:
Wersja, ale tym razem pakietu. Generalnie pakiety numeruje się
poczynając od jedynki, a numer wersji podwyższa się przy każdym
przebudowywaniu pakietu. Tak przynajmniej jest w dystrybucjach. W warunkach
domowych warto też podwyższać te numerki, bo mówią one rpm-owi że pakiet
jest "nowszy" i można "gładko" robić upgrade pakietów zawierających
oprogramowanie w tej samej wersji. Nie żeby to była jedyna możliwość, ale
odrobina porządku nigdy nie zaszkodzi.
Epoch: 1
Epoch:
Jest to bardzo rzadko używany tag, a w hierarchii tagów
"wersjonujących" stoi najwyżej, tworząc łańcuch epoch:version-release.
Można go użyć gdy pakiet zmienia system numerowania wersji, np. przechodzi
z układu numerowania za pomocą dat na "zwykły" system numerków. Jako że
tag ten jest naprawdę, naprawdę rzadko używany, to chyba nic złego się nie
stanie jeśli poprzestaniemy na wyjaśnieniu, że Epoch: również
określa wersję i jest bardziej znaczący od Version:, nie
wspominając już o Release:. Nie jest wymagany, domyślnie ma wartość
"0", a jego przełączenie na wyższy numerek oznacza jakieś naprawdę
przełomowe zmiany w dotychczasowej numeracji lub właściwościach pakietu.
Muszą to być zmiany, których nie dałoby się jednoznacznie opisać za pomocą
tagów Version: i Release.
License: GPL, BSD
License:
Licencja. Jeśli kod jest na "wielokrotnej licencji" (zdarza się), to
podaje się wszystkie dostępne do wyboru licencje, po przecinku. A, wcześniej
istniał (i nadal istnieje) bliźniaczy tag "Copyright:", ale obecnie jest
on porzucany - wspominam o tym na wypadek, gdyby ktoś natknął się na
"Copyright:" w starszych specach.
Group: Aplikacje/Multimedia
Group:
Grupa do której "należy" pakiet. Zwyczajowo używa się tutaj notacji
foo/bar, gdzie należy myśleć o foo jako kategorii "nadrzędnej", a bar to
jakaś podkategoria w obrębie foo. Np. "Applications/Internet". Co ważne:
nie ma jednolitego standardu nazewnictwa. Powiem więcej - burdel jest. Każda
dystrybucja używa innych określeń, a ja w swoim QuaTrin używam notacji
"prostej", bez podkategorii (które uważam za bzdurne), czyli np. po prostu
"Internet". Tag ten nie ma absolutnie żadnego znaczenia :) Jedyna jego
rola, oprócz dostarczania jakiejś tam informacji o zawartości pakietu,
została ukuta z myślą o jakichś nakładkach na rpm - dzięki kategoriom można
dzielić pakiety w gałęzie, podgałęzie itp. Można śmiało tworzyć bzdurne
nazwy dla kategorii, z chwilą zainstalowania takiego pakietu pojawi się nowa
kategoria. Gdy odinstaluje się ostatni pakiet w jakiejś kategorii, to taka
kategoria zniknie. Proste i niepotrzebne jeśli nie korzysta się z nakładek
na rpm które sortują pakiety w grupy.
Source0: ftp://sunsite.unc.edu/pub/Linux/utils/console/%{name}-%{version}-src.tar.gz
Source1: ftp://sunsite.unc.edu/pub/Linux/utils/console/%{name}-%{version}-extra-src.tar.gz
Source:
Ten tag mówi RPM-owi skąd ma brać źródła programu. Źródła będą
wyszukiwane w podkatalogu SOURCES drzewka "budowlanego" naszego rpm.
W tagu tym baaardzo często używa się URL-i, w formie np.
ftp://foo.bar.com/app/%{name}-%{version}.tar.gz, przy czym rpm
zignoruje wszystko aż do ostatniego ukośnika, więc w tym przykładzie
zrozumie tylko fragment %{name}-%{version}.tgz. Te zmienne których
użyłem w zapisie to wygodny sposób na odwołanie się do zawartości tagów
Name: i Version: - dzięki takiemu odwoływaniu do tagów przy zmianie np.
wersji programu wystarczy gdy zmienię tylko tag Version: - a tag Source:
zmieni się "automagicznie". Tak więc rola tego wpisu jest podwójna
- z jednej strony mówi on, gdzie w sieci można znaleźć pakiet ze źródłami
(choć sam rpm na swoje potrzeby zignoruje wszystko aż do ostatniego
ukośnika), z drugiej strony wskazuje mechanizmowi RPM nazwę pliku ze
źródłami, który ten ma pobrać z katalogu SOURCES przy budowaniu. To oznacza,
że wcale nie trzeba się bawić z notacją URL-ową, można po prostu podać nazwę
pliku... Mam nadzieję, że to zrozumiałe.
Mała uwaga: jeden spec może wykorzystywać więcej niż jedną paczkę ze źródłami. Używa się wtedy wielu wpisów, numerowanych, np. Source0:, Source1:, Source2: itp. Source: oznacza to samo, co Source0:
Źródła powinny być dostarczane w postaci paczek tar skompresowanych programem gzip lub bzip2.
Patch0: foo.patch Patch1: bar.patch.bz2
Patch:
Tutaj podaje się listę patchy zawartych w pakiecie. Powinny się one
znaleźć w tym samym katalogu co paczka ze źródłami, a listę ich podaje się
podobnie do listy wielokrotnych źródeł, czyli Patch0:,
Patch1: itd. Zrozumiałe?
Patche mogą być skompresowane programem gzip lub bzip2, w razie potrzeby rpm je sobie automatycznie rozpakuje.
NoSource: 0 NoSource: 2
NoSource:
Tag ten odgrywa rolę tylko przy budowaniu pakietów źródłowych. Jest
swego rodzaju uzupełnieniem dla tagów SourceX:, bo jako argument
NoSource: podaje się właśnie numer "X" któregoś z wymienionych wcześniej
w preambule tagów Source:. Aby włączyć ten tag np. dla źródła z linii
Source3: wpisuje się "NoSource: 3".
Kod źródłowy oznakowany tym tagiem nie zostanie włączony do pakietu .src.rpm, więc potem do przebudowania takiego pakietu trzeba sobie będzie "zorganizować" ten brakujący kod z jakiegoś zewnętrznego źródła. Ma to swoje zastosowanie, np. wyłączenie fragmentów kodu które nie są OpenSource i nie można ich umieścić/dystrybuować w pakietach rpm, albo gdy potrzebny do kompilacji kod jest ogólnie dostępny wszystkim "adresatom" pakietu i nie opłaca się go wpychać w paczkę. W zasadzie tak zbudowane pakiety źródłowe nie są zwykle niczym więcej jak opakowanymi w paczkę rpm plikami .spec (no, chyba że NoSource: nie wymieni wszystkich numerów tagów Source:).
NoPatch: 1 NoPatch: 5
NoPatch:
Ma się tak samo do Patch:, jak NoSource: do Source:
BuildRoot: /var/tmp/%{name}-%{version}
BuildRoot:
Można tutaj wskazać katalog "fake root", czyli ten do którego będą
"instalowane" źródła w sekcji %install.
BuildArch: i586
BuildArch:
Określenie architektury. Ma to pewien ;) wpływ na budowany pakiet jeśli
używa się domyślnych ustawień RPM-a, dodatkową rolą jest zadecydowanie
o tym, do jakiego podkatalogu w obrębie katalogu RPMS powędruje gotowy
pakiet. Można używać nazwy architektury, np. "i686", można też użyć
"noarch" na określenie pakietu niezależnego od architektury.
URL: http://www.mplayerhq.hu/
URL:
A ten tag z kolei ma wskazać zwykle położenie strony domowej lub
dokumentacji spakietowanego oprogramowania. Nie jest obowiązkowy, ale
w dystrybucjach powinno się go używać (aby użytkownik mógł łatwo znaleźć
stronę domową jakiegoś projektu).
Distribution: Yesod
Distribution:
Nazwa dystrybucji. Fakultatywna.
Vendor: Yesod Software
Vendor:
Ten tag oznacza, hmm, jak by to ująć... osobę lub firmę która
rozprowadza pakiet. To wcale nie jest jednoznaczne z osobą która stworzyła
pakiet ani z konkretną dystrybucją. To spore rozdrobnienie, ale niech
będzie. Ten tag też jest nieobowiązkowy.
Packager: <wodnik@szuwarek.net>
Packager:
Osoba, która stworzyła pakiet. Nie nazwa dystrybucji, nie dystrybutor
pakietu, nie autor oprogramowania, ale osoba która stworzyła speca oraz
pakiet.
Summary: Prosty klient poczty używający gtk+
Summary:
Skrótowy, jednolinijkowy opis (a w zasadzie to tylko lakoniczna
charakterystyka) dotycząca zawartości pakietu.
%description Prosty, fikcyjny opis. Może mieć wiele wierszy tekstu, można też wstawiać całkowicie puste wiersze. Można tworzyć opisy zajmujące linijkę, można "popłynąć" na dziesiątki linii.
%description
To nie jest tak w zasadzie "zwykły" tag, bo po pierwsze nie zajmuje
jednej linii, po drugie jego definicja znajduje się pod nim samym, a po
trzecie nie używa się kończącego dwukropka, za to konieczne jest
prefiksowanie znakiem procenta. Tutaj zamieszcza się wielolinijkowy opis
pakietu, rozciąga się on od wpisu %description aż do wystąpienia
następnej sekcji/tagów. A więc nie ma on jasnego końca - kończy się tam,
gdzie zaczyna się coś nowego :) Dawnymi czasy istniał tag
Description:, ale obecny format pozwala na więcej. Dlatego też
wydawać się może, że %description nie pasuje do reszty preambuły,
ale to po prostu dlatego, że jest to "wyewoluowany" tag.
Provides: apache, wwwserver
Provides:
Ten tag pozwala określić czego "dostarcza" budowany pakiet.
Standardowo pakiet może dostarczać różnych rzeczy - programów, bibliotek,
modułów perla, a nawet tzw. "wirtualnych" zasobów, czyli takich które
fizycznie nie mają swojej jednoznacznej reprezentacji. Np. sendmail może
dostarczać zasobu zwanego "mail daemon". I zwykle używa się
Provides: tylko w tym celu właśnie - aby powiedzieć, że pakiet
dostarcza jakiejś "wirtualnej" zależności. Bo wszystkie inne są
automatycznie(!) wykrywane przy budowaniu pakietu. Np. rpm normalnie (w
przypadku standardowego budowania) wylistuje sobie automatycznie wszystkie
biblioteki czy programy zawarte w pakiecie, oraz doda do tego jedną
wirtualną zależność nazwaną tak samo, jak pakiet (czyli po prostu dla
pakietu xmms-1.2.5 "dostarczy" wszystkich bibliotek, pluginów, no
i wirtualnego zasobu "xmms"). Bardzo proste, bardzo zautomatyzowane,
bardzo fajne.
Ale wracając do tematu: tag Provides: pozwala nam dodać coś do listy oferowanych przez pakiet zasobów. Zwykle używa się tego w dystrybucjach, np. można sprawić by zarówno sendmail, jak i postfix dostarczały zasobu zwanego "mailserver" - i uzależnić programy klienckie od właśnie tego zasobu. Sprawi to, że obojętnie czy użytkownik zainstaluje sendmaila czy postfiksa to program kliencki będzie zadowolony - ale będzie wymagał obecności któregoś z nich. I to użytkownik będzie miał prawo wyboru który serwer zainstalować. Jak widać, tag ten nie znajduje pewnie zbyt często zastosowania. Aha, można podać więcej niż jeden zasób używając listy rozdzielanej przecinkami lub spacjami (lub nawet jednym i drugim naraz)
Requires: svgalib >= 1.9.14
Requires:
Tutaj można podać pakiety, od których budowany pakiet ma zależeć. Zwykle
jest tak, że nie ma potrzeby tego robić ręcznie - przy budowaniu pakietu sam
rpm przeskanuje biblioteki i pliki wykonywalne, skrypty shellowe oraz
perlowe i inne takie, po czym samodzielnie zestawi listę wymaganych
bibliotek, interpreterów, czy nawet modułów perla. Dlatego tag
Requires: zwykle używany jest tylko wtedy, gdy chce się wymusić
zależność od konkretnego innego pakietu, albo od wirtualnej zależności.
A więc jest to lustrzane odbicie Provides:, z tym że znajduje
częściej zastosowanie. A dlaczego częściej? Bo uzależnia się często od
siebie pakiety które mają zależność logiczną, ale niekoniecznie
binarno-linkerowo-whateverową ;) Prosty przykład: Program ViM. Zwykle jest
dzielony na kilka paczek - jedną zawierającą dane używane "runtime", czyli
np. zestawy kolorów do podświetlania różnych składni, jedną zawierającą
zwykłego ViM-a i jedną zawierającą gViM-a. Nazwijmy te paczki
"vim-shared", "vim" oraz "gvim". Teraz tak: "vim-shared" zawiera
wszystkie dane, oprócz binariów. "vim" zawiera zwykłą wersję vim-a,
a "gvim" jego wersję z GUI. Należy uzależnić pakiety "vim" i "gvim" od
"vim-shared", ale RPM nie zrobi tego automatycznie, bo nie posiada
przecież sprawności wróżki :). Dlatego w pakietach "vim"/"gvim" człowiek
je tworzący powinien zamieścić "Requires: vim-shared".
Tutaj jeszcze jedna sprawa: Można przy zależnościach mówić o numerach wersji. Np. jeśli powiem, że vim-6.1 wymaga po prostu "vim-shared", to rpm pozwoli również na zainstalowanie np. dużo starszej (i pewnie niekompatybilnej) wersji "vim-shared-5.0". Fajnie by było, gdyby można było jakoś wymusić dozwalony numer wersji. I faktycznie można. Np. "Requires: vim-shared = 6.1". Można też trochę rozluźnić rygor i powiedzieć np. "Requires: vim-shared >= 6.1". To chyba zrozumiałe?
Jest jeszcze jedno dość częste zastosowanie tej funkcji: Można uzależnić pakiet nie tylko od biblioteki, ale także od konkretnej nazwy pakietu. O co mi chodzi? O to, że oprócz tego że program zgv zależy od libvga.so.1 to można go uzależnić od pakietu tę bibliotekę dostarczającego, czyli svgalib. Ale po co, mógłby ktoś zapytać? Przecież zależność już istnieje, poprzez libvga.so.1? To prawda, samemu systemowi RPM nie jest to potrzebne. Ale może to być wygodne dla użytkownika - jeśli spróbuje zainstalować zgv nie posiadając svgalib, to zobaczy że zgv wymaga takich to a takich bibliotek, oraz czegoś zwanego "svgalib". Będzie mu łatwiej znaleźć potrzebne pakiety. Ale takie praktyki są moim zdaniem na wymarciu. Kiedyś uważało się je za znak wysokiego dopracowania pakietów, jeśli faktycznie oprócz wymagania libgtk.so.1 wymagały także "gtk+ >= 1.4.10". Obecnie jednak takie rzeczy potrafią załatwić za człowieka automaty w stylu programu "Poldek" czy innych. Bo nie wolno zapominać, że wprowadzanie takich "fikcyjnych" zależności zmniejsza przenośność pakietu. Wystarczy, że w jednej dystrybucji pakiet zawierający libvga.so.1 będzie nazywał się "svgalib", w drugiej "SVGAlib", w trzeciej "libsvga" i już nie będzie przenośności między nimi, bo pakiet wymaga koniecznie obecności zasobu "svgalib". Warto to wziąć pod rozwagę...
Autoreq: 0
Autoreq:
Ten tag pozwala wyłączyć automatyczne wyszukiwanie zależności przez RPM.
Wtedy trzeba je wszystkie podać poprzez tag Requires:
Autoprov: 0
Autoprov:
Podobnie, z tym że wyłącza się automatyczne wykrywanie tego, co dany
pakiet dostarcza. Wtedy cała robota spada na autora speca i linijkę
Provides:
Autoreqprov: 0
Autoreqprov:
A to po prostu połączenie obydwu powyższych tagów. Wyłącza od razu
wszystkie mechanizmy automagicznego wykrywania zależności.
BuildRequires: flex
BuildRequires:
Ten tag określa pakiety/biblioteki potrzebne do przebudowania pakietu.
Są to zależności które trzeba podać "ręcznie". Jeśli program np. używa
ncurses, to w BuildRequires: należałoby wpisać "ncurses-devel".
Nie ma to wielkiego wpływu na samo budowanie pakietu, ale użytkownik
przynajmniej dowie się od razu, że czegoś mu brakuje do skompilowania
pakietu - jeśli nie będzie posiadał ncurses-devel, to rpm nawet nie zacznie
budowania pakietu, a od razu się poskarży. Tag ten nie ma zastosowania
w "domowej" produkcji pakietów na własne potrzeby, ale przy szerszej
dystrybucji należy uwzględnić tutaj każdy wykorzystywany pakiet - oszczędzaj
ewentualnych odbiorców swoich pakietów ;).
BuildConflicts: gcc = 2.96.1
BuildConflicts:
A ten tag z kolei pozwala "zablokować" przebudowywania pakietu jeśli
jest obecny w systemie jakiś konkretny pakiet. Jeśli wiadomo, że obecność
jakiegoś pakietu by nie pozwoliła wyprodukować poprawnych binarek, np. można
się zabezpieczyć w ten sposób przed użyciem jakiejś starej, wadliwej wersji
binutils. Rzadko używany tag, ale z przyzwoitości muszę o nim wspomnieć.
Obsoletes: slrn, slrnpull
Obsoletes:
Pakiety wymienione tutaj jako argumenty zostaną usunięte przy instalacji
nowo utworzonego pakietu. Tzn. gdy zainstalujesz ten pakiet, to wszystkie
wymienione w Obsoletes: zostaną automatycznie usunięte. Przydatne
jeśli przygotowywany pakiet ma za zadanie całkowicie zastąpić jakichś swoich
poprzedników (np. noszących inne nazwy). Można np. uznać, że pakiet slrn-pl
jest w stanie zastąpić slrn, albo że elinks może zastąpić linksa. Albo że
nowy pakiet fvwm powinien zastępować fvwm2.
PreReq: /sbin/install-info
PreReq:
Tag ten działa podobnie do Requires:, z tym że zależność ta
konieczna jest do poprawnej instalacji pakietu. Normalne
Requires: oznacza, że pakiet potrzebuje innego do swojej pracy.
PreReq: oznacza, że pakiet potrzebuje innego pakietu/zasobu nie
tyle do swojej pracy, co do swojej instalacji. Potrzebuje czegoś, aby
w ogóle poprawnie się zainstalować/odinstalować. Chodzi tu głównie
o polecenia shella użyte w sekcjach %pre czy %post
BuildPreReq: perl
BuildPreReq:
Dodatek do PreReq:. Działa tak samo jak PreReq:, z tym
że obowiązuje tylko podczas budowania pakietu. Bardzo rzadko używany, pewnie
dlatego, że funkcjonalnie pokrywa się z BuildRequires :)
???
DistURL:
Tag jeszcze nie występujący w pakietach. W zamierzeniu miałby wskazywać na
stronę dystrybucji, ale jego standardyzacja nie dobiegła jeszcze końca.
Icon: czaderska_ikonka_pakietu.xpm
Icon:
Tutaj określa się "namiar" na plik z ikoną. Jedyne tego zastosowanie
to fakt, że niektóre graficzne package-managers będą w stanie pokazać tę
ikonkę jako "ikonkę pakietu". Ikonka powinna być formatu GIF lub XPM
(zalecany to XPM), z przezroczystym tłem (żeby ikonka porządnie wyglądała
w takim np. "gnorpm")
Conflicts: sendmail
Conflicts:
Proste - wskazuje coś, co po prostu nie może koegzystować w systemie
z naszym pakietem. Np. program "masqmail" nie powinien być instalowany
równolegle z sendmailem, bo obydwa robią to samo. No dobra, przykład głupi,
ale nic innego mi do głowy nie przychodzi :)
ExcludeOS: irix
ExcludeArch:, ExcludeOS:, ExclusiveArch:, ExclusiveOS:
Wymieniam je hurtem, bo są nieczęsto używane, powiązane ze sobą i mają
w miarę opisowe nazwy. Regulują "dostępność" pakietu w zależności od
architektury lub systemu operacyjnego. Opcje Exclude* zabraniają
budowania pakietu na określonej architekturze/systemie operacyjnym, a opcje
Exclusive* z kolei zezwalają na budowę tylko i wyłącznie na
podanych architekturach/systemach. Te opcje to coś w stylu "Nie zbudujesz
tego pakietu na..." oraz "Zbudujesz ten pakiet tylko na...". Kwestia
architektury jest jasna - można np. zabronić budowania jakiegoś programu
uzależnionego od instrukcji mmx na maszynach które tego nie potrafią, można
też np. wyłączyć całą rodzinę x86 jeśli program jest przeznaczony dla
komputerów Alpha. Zdziwienie może budzić tylko uzależnianie od systemu
operacyjnego - ale to się da łatwo wyjaśnić: RPM ma ambicje stania się
uniwersalnym systemem pakietowania. Jeśli będzie popularny na większej
ilości systemów operacyjnych, to oczywiście konieczne stanie się odróżnianie
pakietów przeznaczonych dla Linuksa od tych zrobionych specjalnie dla
Hurda.
Prefix: /opt
Prefix:
Definiuje prefiks, czyli początkowy fragment ścieżki, używany przy
instalacji pakietu. Pole to można przedefiniować przy instalacji pakietu, co
spowoduje że pliki wywędrują do innej niż domyślna lokacji. Jeśli program
składa się raptem z kilku binarek i stron manuala, to można stworzyć taki
"relokowalny" pakiet, który użytkownik będzie potem mógł zainstalować albo
w /usr, albo w /usr/local, albo w /opt, albo w /usr/X11R6 - wedle życzenia.
Oczywiście nie zawsze da się coś takiego zrobić, niektóre programy szukają
swoich danych w ściśle określonych miejscach.
Pominąłem kilka już zupełnie rzadkich (lub słabo udokumentowanych) tagów, te wymienione powinny pozwolić na swobodne zbudowanie dowolnego pakietu.
Hurra! Sekcja %prep! (myślałem, że już nigdy poza preambułę nie wyjdę ;)
Tutaj pójdzie nam szybko i miło - po tym jak mamy już opisany nasz pakiet i kilka ważniejszych ustawień dotyczących procesu budowania, nadchodzi czas by zacząć w końcu coś robić. I będzie to właśnie ta sekcja. Jej rola jest prosta - wziąć źródła programu i umieścić je w specjalnym katalogu do tego celu przeznaczonym, czyli w naszym przykładzie w katalogu ~/rpm/BUILD. Potem ewentualnie nakłada się patche lub w inny sposób koryguje kod. Niewiele do zrobienia, prawda?
Źródeł można dostarczyć w dowolny sposób, ale zwykle dysponuje się zgzipowanym archiwum tar, które tylko na to czeka, by ktoś wyjął je z katalogu SOURCES i rozpakował do BUILD. Można to zrobić ręcznie, używając polecenia "cp" oraz "gunzip"/"tar" (ta sekcja jest interpretowana jak zwykły skrypt shellowy, więc można używać normalnych, shellowych konstrukcji i poleceń), używając przy tym jawnych ścieżek dostępu lub zmiennych/makr, można też po prostu użyć makra %setup. To proste makro weźmie z katalogu SOURCES paczkę o nazwie zawartej w preambule w tagu "Source:", a następnie ją rozgzipuje i roztaruje (brzydkie słowa, prawda?). A przy okazji usunie z katalogu BUILD ewentualną kopię starych źródeł. Wygodne, prawda? A wystarczy wpisać %setup.
No dobra, faktycznie najprostsza i całkowicie funkcjonalna wersja tej sekcji może wyglądać tak:
%prep %setup
...i na razie tylko tyle musisz wiedzieć. Ta sekcja weźmie źródła, rozpakuje je, a następnie wejdzie do katalogu o nazwie %{name}-%{version}, złożonej z tego, co przekazano w tagach Name: i Version: preambuły. W 90% przypadków nie trzeba więcej, to wystarczy. Jeśli jednak szukasz czegoś więcej, to makro %setup ma kilka opcji, które można mu przekazywać przy wywołaniu.
%setup
-n <nazwa>
Opcja ta jest konieczna... albo nie, zacznę inaczej. Weźmy sobie jakiś przykład, np. źródła programu "xplanet". Przypuśćmy, że kod źródłowy zawarty jest w paczce xplanet-1.20.tar.gz. Więc w preambule umieściliśmy tagi "Name: xplanet", "Version: 1.20" oraz "Source: %{name}-%{version}.tar.gz" (choć nic nie stoi na przeszkodzie by wpisać tu po prostu nazwę pliku - tyle że tak będzie wygodniej przy zmianie wersji itp. Jeśli jeszcze o tym nie wspomniałem, to zapis %{...} to standardowy sposób na odwoływanie się do wewnętrznych zmiennych/makr RPM-a). A plik xplanet-1.20.tar.gz zawiera katalog xplanet-1.20. W takim wypadku można spokojnie użyć makra %setup bez ozdóbek, nic złego się nie stanie. Ale załóżmy, że paczka ze źródłami nazywa się xplanet-1.20.tar.gz, ale zawiera katalog o nazwie "xplanet", bez numerku wersji. I tu jest pierwszy problem do przeskoczenia. Bo makro %setup nie jest jakieś specjalnie inteligentne. Owszem, ono weźmie te źródła, rozpakuje, ale potem spróbuje wejść do katalogu %{name}-%{version}. A taki katalog nie będzie istniał i proces budowy się wyłoży. Bo %setup nie sprawdza tak naprawdę co rozpakowuje, ale oczekuje że po rozpakowaniu zastanie katalog o nazwie w formie nazwa-wersja, zgodnej z tagami Name: i Version:. A co jeśli jednak nazwa tak powstałego katalogu będzie inna? Przecież bez sensu by było zmieniać z tego powodu tagi Name:/Version:. I tutaj właśnie pojawia się opcja -n. Pozwala ona podać nazwę katalogu, do którego makro %setup ma próbować wejść po rozpakowaniu źródeł. Czyli w naszym hipotetycznym przykładzie z xplanet trzeba by użyć %setup -n xplanet. I po kłopocie! :)
-c
A tutaj mamy podobną sytuację. Załóżmy sobie, że paczka xplanet-1.20.tar.gz nie zawiera jednego katalogu ze źródłami, ale że po rozpakowaniu od razu wypakowuje wszystkie pliki źródłowe. Tak, jakby obciąć jeden katalog z podstawy nazw plików i zamiast ładnie rozpakować się do katalogu xplanet-1.20 od razu wypakowuje jego zawartość. Nie dość, że nie ma katalogu xplanet-1.20 do którego by się dało wejść, to jeszcze robi się straszny bałagan w katalogu BUILD. Także na to jest sposób - opcja -c oznacza, że rpm będzie miał do czynienia właśnie z taką niechlujną paczką i że plikom z paczki brakuje nadrzędnego katalogu - więc rpm go najpierw założy, a dopiero potem wypakuje do niego zawartość paczki, no i przejdzie do tego katalogu. Katalog będzie miał nazwę %{name}-%{version}, ale jeśli koniecznie się chce, to można użyć innej nazwy łącząc -c z opcją -n.
-D
Mówiłem już, że rpm przy rozpakowywaniu źródeł najpierw spróbuje usunąć stare drzewko rozpakowanych źródeł. I o ile jest to zwykle bardzo pożądane, to w niektórych przypadkach (np. przy wypakowywaniu do jednego katalogu kilku kompletów źródeł (pomyśl o tagach Source0:, Source1: itp!) jest to kłodą rzuconą pod nogi. No cóż, opcja -D każe rpm-owi przeskoczyć ten kawałek z usuwaniem starych źródeł.
Prosty przykład zastosowania z życia wzięty: glibc. Zwykle kompiluje się je z dodatkiem linuxthreads. Tyle, że to daje dwie paczki źródeł - najpierw trzeba rozpakować glibc, potem linuxthreads. I dopiero wtedy można kompilować.
-T
A to bardzo sympatyczna opcja. Wyłącza rozpakowywanie domyślnych źródeł :) Makro %setup wykona wszystkie swoje działania, z wyjątkiem rozpakowania paczki ze źródłami. Ma to swoje zastosowanie w połączeniu z opcją -b
-b <numer>
Opcja -b jest używana do rozpakowania określonej paczki ze źródłami - trzeba tego użyć w sytuacji, gdy zadeklarowało się więcej niż jeden tag "Sources:" w preambule. Np. "%setup -b 2" spowoduje rozpakowanie źródeł oznaczonych wcześniej jako "Sources2:". Ale tutaj mały haczyk - makro %setup rozpakuje zawsze także "domyślne" źródła, określone w "Sources:" lub "Sources0:" (czyli po prostu pierwsze źródła). Jest to najczęściej niepożądane przy używaniu opcji -b, dlatego też używa się tutaj zwykle połączenia -b ze wspomnianą już opcją -T. Np. %setup -T -b 0. Wystarczy zapamiętać, że -b łączy się zwykle z -T, bo inaczej %setup będzie zawsze próbował rozpakować "dodatkowo" pierwsze źródła z listy.
-a <numer>
Opcja -a jest bardzo podobna do -b, jedyna różnica to to, że przed rozpakowaniem makro wejdzie do katalogu ze źródłami. Oczywiście ten katalog powinien wcześniej istnieć :) Eee, może przykład: wspominałem już o glibc i linuxthreads, o tym że zwykle te dwa pakiety trzeba rozpakować aby skompilować glibc. Glibc po rozpakowaniu tworzy katalog "glibc-wersja". Linuxthreads po rozpakowaniu tworzy katalog "linuxthreads". Gdyby je rozpakowywać "normalnie", za pomocą opcji -b, to glibc i linuxthreads by wylądowały w oddzielnych podkatalogach w obrębie katalogu ~/rpm/BUILD. Ale do poprawnej kompilacji źródła linuxthreads muszą znaleźć się wewnątrz katalogu z glibc. I opcja -a by zrobiła dokładnie to - najpierw weszła do katalogu %{name}-%{version} (lub prawdopodobnie dowolnego innego podanego za pomocą opcji -n), a dopiero potem rozpakowała źródła o określonym numerze.
A, jeszcze coś - można połączyć -a numer z -c, a wtedy makro %setup stworzy katalog %{name}-%{version} przed próbą wejścia do niego. Tutaj zaczyna być widoczne, dlaczego te wszystkie opcje są tak rozdrobnione i pozornie nieprzydatne - trzeba je po prostu łączyć z sobą.
-q
A to ostatnia już opcja (chyba, że znowu o czymś zapomniałem ;).
Opcja -q to po prostu quiet mode, jeśli użyje się tej opcji to tar rozpakowujący paczkę ze źródłami nie będzie wypisywał nazw rozpakowywanych plików. Przydatna opcja, zwłaszcza w diagnostyce budowy pakietów, bo zasypywanie ekranu spisem plików wypakowanych z paczki źródłowej zwykle nie jest do niczego przydatne.
To by było chyba wszystko co musisz wiedzieć o makrze %setup...
%patch
Ale oprócz %setup w sekcji %prep używa się też czasem makra %patch - makro to nakłada na rozpakowane pliki jakieś patche. Patche trzeba wcześniej wymienić w preambule za pomocą dyrektywy "PatchX:". Załóżmy, że w preambule mamy wiersze:
Patch0: jakiś_patch.diff Patch1: inny_patch.diff Patch2: ostatnia_łata.gz
a aplikuje się je wywołując odpowiednio makra %patch0, %patch1 oraz %patch2. Proste, prawda? Numerek dolepiony do %patch oznacza numer patcha do nałożenia. Jeśli pominie się numer patcha, to nałożony zostanie domyślnie pierwszy patch, czyli ten określony w preambule za pomocą Patch0: lub po prostu Patch:.
Makro %patch posiada kilka lepiej lub gorzej udokumentowanych opcji, z których jednak żadna nie ma wpływu na proces budowy, więc je pominę. Nie ma sensu chyba opisywać np. opcji -b, która pozwala wybrać odmienne rozszerzenie dla kopii oryginalnych wersji plików które uległy popatchowaniu, bo to naprawdę nie ma wpływu na samo budowanie pakietu. Może to być przydatne w sytuacji awaryjnej, gdy patche nie chcą się poprawnie nałożyć, no ale bez przesady - jeśli coś idzie nie tak przy nakładaniu patchy, to jest to pewnie niezależne od rpm-a, więc po co go do tego mieszać?
A więc skoncentruję się na jedynej normalnie używanej opcji:
-p <numer>
Ta opcja jest przekazywana bezpośrednio programowi "patch" i powoduje obcięcie <numer> katalogów od nazw wszystkich patchowanych plików. Jako że jest to tak naprawdę bardzo popularna opcja programu "patch" i związana raczej z samym nakładaniem łatek, bez powiązania z RPM, to nie będę więcej o tym pisał. Wystarczy zajrzeć do manuala programu "patch". W każdym bądź razie: %patch0 -p 1 spowoduje zaaplikowanie pierwszego patcha i przekazanie opcji "-p 1" programowi "patch".
W sekcji %prep można też wykonywać czynności związane z regenerowaniem skryptów "configure" oraz plików "Makefile.am". Ewentualnie można je też wykonywać w następnej sekcji...
To następny krok. Gdy już ma się te źródła rozpakowane a łatki ponakładane, można przejść do właściwej kompilacji. Teraz zwykle należy uruchomić skrypt ./configure z odpowiednimi parametrami, a potem wywołać "make". Można tutaj postąpić "normalnie" i napisać np. taką sekcję:
%build ./configure --prefix=/usr --mandir=/usr/share/man --enable-nls make
Można też pójść o krok dalej i zacząć używać zmiennych ze środowiska RPM, co pozwoli potem łatwiej dopasowywać skrypty itp. bzdury ;)
%build
./configure --prefix=%{_prefix} --mandir=%{_mandir} --enable-nls
make
Można też pójść na całość i napisać tak:
%build %configure --enable-nls make
Tutaj użyłem zamiast "gołego" wywołania ./configure specjalnego, rpm-owego makra "%configure". Makro to uruchamia skrypt ./configure, ale przy okazji przekazuje mu długą listę opcji w stylu "--prefix=%{_prefix} --bindir=%{_bindir} --docdir=%{_docdir}" itd.
Za jednym zamachem makro to definiuje też zmienne CFLAGS, CXXFLAGS oraz FFLAGS. Najpierw próbuje je pobrać ze środowiska, a jeśli to się nie powiedzie (bo zmienne nie były ustawione w shellu z którego uruchomiono rpmbuild), to ustawi brakujące zmienne na wartość %{optflags} - a %{optflags} to domyślne (można w sumie powiedzieć - awaryjne) flagi kompilacji w środowisku RPM. %{optflags} można oczywiście ustawić sobie np. w pliku ~/.rpmrc.
Można się tutaj spierać, czy wywołanie %configure należy umieścić w sekcji %build, czy też może bardziej pasowałoby to do sekcji %prep. Ale to nie jest tutaj ważne.
Po raz kolejny odejdę od głównego tematu i zajmę się skryptem ./configure. Czasem zachodzi potrzeba zregenerowania go - bo albo mamy zbyt nowy pakiet autoconf/automake (a skrypt ./configure był stworzony jakimiś archaicznymi wersjami tych programów), albo po prostu musieliśmy powprowadzać jakieś poprawki do procesu ./configure i trzeba jeszcze raz pozwolić automatom to "przemielić". Albo chcemy skompilować kod z CVS, który często nie zawiera skryptów ./configure a jedynie ich wersje "źródłowe". Regeneracja wszystkich skryptów automatu ./configure nie jest zwykle potrzebna, ale różnie to bywa. I zwykle proces ten sprowadza się do łańcucha poleceń, czegoś w rodzaju:
libtoolize --force aclocal autoheader automake -af autoconf
...przy czym zestaw poleceń i ich opcje mogą się różnić zależnie od sytuacji. Sam pakiet autoconf oferuje osobny program, który powinien być zwykle w stanie wykryć co też wymaga aktualizacji i samodzielnie zadecydować co i z jakimi opcjami uruchomić. Program ten (a właściwie to skrypt shellowy) nazywa się "autoreconf". I często zamiast litanii poleceń wystarczy wklepać "autoreconf".
RPM ma jednak własną wersję podobnego narzędzia - makro %GNUconfigure. Odwala ono w przybliżeniu tę samą robotę co "autoreconf". Obeznani z tematem będą pewnie i tak woleli samodzielnie uruchamiać poszczególne "klocki" w rodzaju "aclocal" czy "autoheader", mniej zaawansowani mogą spróbować skorzystać z automagicznych mechanizmów "autoreconf" czy "%GNUconfigure". Automagia nie zawsze zadziała, ale z pewnością mniej wymaga od użytkownika. Dlatego o niej wspominam, no i udało mi się dzięki temu wpleść trochę informacji o %GNUconfigure ;)
I tutaj znowu nie bardzo wiadomo, czy regeneracja ./configure powinna znajdować się w sekcji %build, czy też może jednak w %prep. Ja osobiście bym się skłaniał ku umieszczeniu tego jeszcze w sekcji %prep, bo to należy według mnie jeszcze do fazy przygotowywania źródeł, podobnie jak patchowanie. Ale to nie gra większej roli, niezależnie od wyboru efekt będzie ten sam :)
A gdy już mamy te źródła skonfigurowane, wystarczy uruchomić "make". Więc przykładowa prosta sekcja %build mogłaby w rezultacie wyglądać tak:
%build %configure --without-alsa make
Wykonywanie poprzedniej sekcji, %build, trochę potrwa. Źródła będą się kompilować, kompilować, a od czasu do czasu włączy się też linker. Po poprawnym wykonaniu całej kompilacji zwykle trzeba to, co zostało skompilowane, zainstalować. Także tutaj nie będzie inaczej. Jedyna różnica dotyczy "miejsca przeznaczenia" w którym wylądują pliki. Przy normalnym "make install" pliki mają zostać umieszczone bezpośrednio w systemie, czyli np. w /usr/bin, /usr/lib, /etc itp. W przypadku rpm nie wchodzi to w rachubę, bo celem jest stworzenie pakietu, a nie zainstalowanie. Pliki lądują zwyczajowo w "fake root", który często będzie znajdował się w /tmp albo w /var/tmp. Zwykle w /var/tmp/%{name}-%{version}. Katalog "fake root" jest dostępny zawsze poprzez zmienną $RPM_BUILD_ROOT, co upraszcza pisanie plików .spec
Nie jest jednak tak, że to rpm w jakiś magiczny sposób przechwytuje próby zapisu do /bin i przekierowuje je do $RPM_BUILD_ROOT/bin, nie, tak zaawansowane to to nie jest. To sam proces instalacyjny (czyli zwykle "make install" musi dać się zmusić do zapisywania plików w $RPM_BUILD_ROOT/usr/lib zamiast w /usr/lib. W przypadku programów bazujących na mechanizmach autoconf/automake zwykle wystarczy podać w wywołaniu "make install" definicję zmiennej $DESTDIR wskazującą na "miejsce przeznaczenia" - make doda zawartość $DESTDIR jako prefix do wszystkich ścieżek instalowanych plików, więc główne drzewko systemowe da się przesunąć do jakiegoś katalogu. I jest to po prostu pewna cecha plików Makefile zbudowanych przy pomocy skryptu ./configure, rpm nie ma z tym nic wspólnego. Programy, w których plik Makefile nie jest tworzony przez ./configure ale został napisany przez autora oprogramowania nie muszą oczywiście trzymać się tej zasady, zmienna $DESTDIR może nie mieć dla nich żadnego znaczenia. Mogą używać też jakiejś innej zmiennej, albo w ogóle nie brać pod uwagę możliwości przekierowania instalacji. I wtedy trzeba będzie, niestety, samemu zaingerować w pliki Makefile. Ale o tych skrajnych przypadkach później. W 80% przypadków wystarczy taka sekcja %install:
%install make install DESTDIR=$RPM_BUILD_ROOT
I tutaj jeszcze jedno słówko o dodatkowym makrze - %makeinstall. Mam w sumie mieszane uczucia jeśli idzie o to makro. Jego definicja powoduje wywołanie "make install" i przekazanie szeregu zmiennych które mają na celu przekierowanie instalacji do %{buildroot} (to zwykle to samo, co $RPM_BUILD_ROOT, tylko wyrażone za pomocą makra rpm, a nie zmiennej środowiskowej). Tyle że nie uważam tego za dobry pomysł. Po pierwsze, jest to przekazywanie zmiennych w stylu "libdir=...", "includedir=.." - a więc jest to skończona lista zmiennych. Jeśli jakaś Makefile używa jednak jeszcze jednej zmiennej, która nie została tutaj uwzględniona, albo po prostu inaczej się nazywa, to całe to makro %makeinstall można sobie podarować, bo po prostu się nie sprawdzi. A po drugie, nie ma żadnego "nakazu" by Makefile używały przy instalacji katalogów definiowanych przez zmienne "libdir" itp. Nawet jeśli są to tak ładnie zdefiniowane Makefile tworzone przez ./configure. Więc naprawdę nie wiem, po co takie makro stworzono, skoro zwykle zadziała ono tylko w jakimś podzbiorze Makefile które by i tak "posłuchały" po prostu zmiennej $DESTDIR. Dlatego raczej odradzam używania %makeinstall, zwykłe make install DESTDIR=$RPM_BUILD_ROOT też powinno zadziałać i to w większej nawet ilości przypadków. Ale wspomnieć o %makeinstall trzeba. Więc wspomniałem. I na tym poprzestanę - bo używać tego makra nie mam po prostu zamiaru :)
Może to zabrzmi patetycznie, ale ta sekcja jest sercem pliku spec. A przynajmniej ja tak myślę. Tutaj określa się, jakie pliki w końcu wylądują w pakiecie, jakie będą miały uprawnienia i inne takie.
Pakiet RPM będzie zawierał tylko te pliki, które wymieni się w tej sekcji. Zwykle trzeba tutaj tylko wylistować pliki/katalogi znajdujące się po fazie %install w "fake root". Teoretycznie RPM mógłby to zrobić za użytkownika i można by uznać tę konieczność za bardzo denerwującą. Ale mimo wszystko lepiej jest jednak, gdy autor pakietu świadomie poustawia te rzeczy. Często będzie tu szło o szczegóły, ale będą to szczegóły bardzo ważne.
Załóżmy, że mamy w "fake root" po instalacji tylko dwa pliki:
$RPM_BUILD_ROOT/usr/bin/program $RPM_BUILD_ROOT/usr/share/man/man1/program.1
...czyli jakiś program i jego stronę manuala. Wiem, nieczęsta sytuacja, ale nie chcę od razu przegiąć z pierwszym przykładem :) Prosta sekcja %files dla tego przykładu mogłaby wyglądać tak:
%files /usr/bin/program /usr/share/man/man1/program.1.gz
Proste, prawda? Jeśli ktoś jednak czyta z uwagą, ten zauważył pojawienie się "znikąd" rozszerzenia .gz przy pliku manuala. To konieczność, bowiem rpm w magiczny sposób kompresuje manuale i dokumentację info w standardowych dla tych plików "lokacjach" (czyli /usr/share/info, /usr/X11R6/man itp.). Nawet jeśli "make install" zainstalowało dokumentację w formie nieskompresowanej, to nie gra roli - rpmbuild wykona jeszcze przed interpretowaniem sekcji %files jeden przebieg po całym "fake root", kompresując manuale. Fajne :) Dlatego w %files zakładam, że manual jest zgzipowany. Muszę tak robić. Ale można pójść krok dalej - makro %configure używało zamiast "jawnych" ścieżek takich jak /usr czy /usr/lib specjalnych makr. I tutaj można zrobić tak samo. Powinno się w każdym razie.
W zmodyfikowanej postaci ta sama sekcja może wyglądać tak:
%files %{_bindir}/program %{_mandir}/man1/program.1.gz
(Tak na marginesie: jeśli ktoś do tej pory nie domyślił się, dlaczego podczas budowania pakietów RPM tak silny nacisk kładzie się na unikanie podawania jawnych ścieżek, to tutaj łopatologiczne wyjaśnienie na przykładzie z historii linuksa:
Kiedyś standardowo kładło się strony manuala do /usr/man/man*/. Ale potem nastąpiły zmiany w standardzie lokowania plików i manuale zaczęto umieszczać w /usr/share/man/man*/. Podobne zmiany mogą zachodzić jeszcze w przyszłości - albo w ramach ogólnolinuksowych standardów, albo w ramach pojedynczej dystrybucji, albo w ramach normalnego dostosowywania pakietów do konkretnego, pojedynczego systemu. Ale do czego dążę? To proste: Wyobraźmy sobie plik .spec, w którym wszędzie są odwołania "proste", w stylu /usr/man. Zarówno przy ./configure, jak i w %files. Jeśli teraz zajdzie potrzeba skompilowania pakietu z przeniesieniem manuali do /usr/local/man (nie, nie pytaj "po co" - bo to tylko abstrakcyjny przykład), no więc jeśli trzeba będzie wprowadzić takie zmiany, to taki plik .spec trzeba będzie zmienić, w dwóch miejscach poprawić. I tak trzeba będzie postąpić z każdym jednym rekompilowanym pakietem. Niezbyt to miłe. A co się stanie, jeśli będzie się używać makra %{_mandir}? Proste - wystarczy zmienić to makro raz, globalnie, w ~/.rpmmacros. Pliki .spec nie zmienią się, ale wynikowe, zrekompilowane pakiety będą umieszczały swoje manuale w /usr/local. Co jest prostsze - zmienić jedną globalną definicję, czy też zmieniać dziesiątki wpisów w plikach .spec? Zalet takiego podejścia jest więcej - np. większa zgodność między systemami, bo wystarczy że na danym systemie te kluczowe zmienne będą zdefiniowane prawidłowo. Dodatkowo te makra można zmieniać w linii poleceń, przy wywołaniu rpmbuild - wystarczy użyć opcji --define aby "w locie" zmienić ustawienie jakiegoś katalogu, np. "--define="_prefix /opt"" Proste, bo nie zmienia ani globalnych ustawień systemu, ani pliku .spec, a działa :)
A po ludzku: używając takich różnych owijek w postaci różnych zmiennych czy makrodefinicji jest później łatwiej nad tym zapanować, kiedy trzeba będzie coś zmienić. Mam nadzieję, że to jasne?
Dobra, teraz dodajmy jeszcze parę plików. Dodajmy do naszego przykładu jakąś drugą binarkę, jej manual, jakąś bibliotekę z której to wszystko korzysta i parę nowych stron manuala. Czyli będziemy bliżej tego, co normalnie spotka się w życiu. O, mam pomysł - weźmy naprawdę przykład z życia - fragment plików z paczki gpm:
%files /etc/gpm-root.conf /usr/bin/disable-paste /usr/bin/gpm-root /usr/bin/mev /usr/include/gpm.h /usr/lib/libgpm.so /usr/lib/libgpm.so.1 /usr/lib/libgpm.so.1.19.0 /usr/sbin/gpm /usr/share/info/gpm.info.gz /usr/share/man/man1/gpm-root.1.gz /usr/share/man/man1/mev.1.gz /usr/share/man/man1/mouse-test.1.gz /usr/share/man/man7/gpm-types.7.gz /usr/share/man/man8/gpm.8.gz
No, już tego więcej. Świetnie. Jak widać, mamy tu wszystkiego po trochę - plik konfiguracyjny w /etc, trzy programy w /usr/bin, jeden plik nagłówkowy, bibliotekę, jeden program w /usr/sbin, parę stron manuala i jeden plik info. Strony manuala i plik info muszą mieć w tej sekcji rozszerzenie .gz - bo jak mówiłem, zostały automagicznie skompresowane. Teraz jeszcze przeróbmy to na formę używającą makr zamiast sztywnych katalogów:
%files %{_sysconfdir}/gpm-root.conf %{_bindir}/disable-paste %{_bindir}/gpm-root %{_bindir}/mev %{_includedir}/gpm.h %{_libdir}/libgpm.so %{_libdir}/libgpm.so.1 %{_libdir}/libgpm.so.1.19.0 %{_sbindir}/gpm %{_infodir}/gpm.info.gz %{_mandir}/man1/gpm-root.1.gz %{_mandir}/man1/mev.1.gz %{_mandir}/man1/mouse-test.1.gz %{_mandir}/man7/gpm-types.7.gz %{_mandir}/man8/gpm.8.gz
Nieźle. I teraz mogę pokazać następny krok - redukowanie wpisów. To będzie, mam nadzieję, proste do zrozumienia:
%files
%{_sysconfdir}/gpm-root.conf
%{_bindir}/*
%{_includedir}/gpm.h
%{_libdir}/*
%{_sbindir}/gpm
%{_infodir}/gpm.info.gz
%{_mandir}/man1/*
%{_mandir}/man7/gpm-types.7.gz
%{_mandir}/man8/gpm.8.gz
W sekcji %files mogę się posługiwać normalnym, shellowym dopasowywaniem, więc jeśli napiszę "katalog/*" to wzorzec ten zostanie zinterpretowany jako "wszystkie pliki w tym katalogu". Bardzo fajnie. No to jeszcze trochę to poprawmy:
%files
%{_sysconfdir}/gpm-root.conf
%{_bindir}/*
%{_includedir}/*
%{_libdir}/*
%{_sbindir}/*
%{_infodir}/*.gz
%{_mandir}/man*/*.gz
Jeszcze bardziej to uprościłem, zwłaszcza część z manualami jest bardzo ładna.
Tutaj uwaga: w ten sposób sam plik .spec stał się bardziej uniwersalny - jeśli w następnej wersji gpm dojdzie np. jakiś dodatkowy plik nagłówkowy w /usr/include, to ta sekcja %files nie będzie potrzebowała modyfikacji i zadziała sama z siebie. Muszę szybko powiedzieć, że nie popełnia się przez to żadnego błędu - bo możnaby co prawda pomyśleć, że robiąc taki przesycony "wildcards" plik .spec może dojść do tego, że jakieś ważne zmiany przejdą niezauważone, że coś będzie nie tak z pakietem jeśli w którejś wersji coś się bardzo pozmienia - ale bez obaw. Jeśli w wyniku ewolucji gpm zniknie np. plik /etc/gpm-root.conf, to rpmbuild powie o tym (powie, że ten plik nie istnieje i odmówi zbudowania paczki). Podobnie będzie jeśli dojdzie coś nowego, co nie jest uwzględnione obecnie w sekcji %files - rpmbuild powie wtedy, że znalazł jakieś przeoczone pliki. Tak więc zachowując zdrowy rozsądek można napisać w miarę uniwersalne pliki .spec, których będzie można używać normalnie zmieniając tylko wersję źródeł i to bez narażania się na powstanie "zdeformowanych" pakietów.
OK, więc mamy już w miarę sensowną sekcję %files. Porozmawiajmy teraz o uprawnieniach. RPM ma jakieś tam uprawnienia domyślne, które będą nakładane na wszystkie pliki z sekcji %files. Ale ja nigdy nie pamiętam jakie one są, zresztą mogą znienacka się nam zmienić przy następnej wersji RPM, więc lepiej jest te domyślne uprawnienia ustawić samemu. Robi się to makrem %defattr(perm_file,user,group,perm_dir). Dziwna notacja, ale już wyjaśniam: makro to wywołuje się z opcjami w nawiasie, trochę jak wywołuje się funkcje z parametrami. "user" i "group" powinny być jasne - to będzie właściciel i grupa nadawane plikom. Ale o co chodzi z "perm_file" i "perm_dir"? Pierwsza z tych opcji oznacza uprawnienia jakie należy nałożyć na zwykłe pliki, druga uprawnienia jakie należy nałożyć na katalogi. Tak, o tym trzeba decydować osobno, choćby z tego powodu, że katalogi zawsze muszą mieć bit wykonywalności by użytkownik mógł do nich wejść, za to zwykłe pliki zwykle wykonywalne nie muszą być. Przykładowy (chyba najczęstszy) zapis będzie wyglądał tak:
%defattr(0644,root,root,0755)
ustawi to sensowne domyślne prawa - root może wszystko, inni mogą sobie co najwyżej popatrzeć, ale nie wykonywać ani zapisywać. Wyjątkiem są katalogi, które mają zawsze ustawiony bit wykonywalności - po to, by każdy mógł do nich zajrzeć.
Mały bonus: mogę nie podać praw dla katalogów, wtedy zostaną użyte prawa dla plików. Zamiast praw czy też właściciela/grupy mogę też po prostu wpisać "-" (minus), wtedy rpm pozostawi takie uprawnienia, jakie ustawił plikom proces instalujący je w fake root. Ale to akurat niezbyt szczęśliwy pomysł, tak pozwalać jakiemuś przygodnemu "make install" mieszać nam w uprawnieniach które przecież potem mogą nam namieszać w całym systemie. Lepiej trzymać rękę na pulsie i nie dać się skusić Mrocznemu Minusowi.
No dobra, tak naprawdę to RPM użyje wtedy swoich domyślnych (fabrycznych) ustawień %defattr, ale to się do tego samego sprowadza.
Te ustawienia domyślne są bardzo fajne i sprawdzają się w 80% przypadków - z jednym wyjątkiem. Gdy idzie o pliki wykonywalne. Bo na tych "defaultach" wszystkie pliki by się stały niewykonywalne - 0644 w końcu swoje robi. A my w naszym przykładzie przecież mieliśmy binarki... i co teraz? Zmienić %defattr? Ale to bez sensu, bo jeśli zacznę nadawać bit wykonywalny wszystkim plikom, to skończę np. z "wykonywalnymi" plikami manuala itp. Mógłbym co prawda skusić się i użyć minusa jako uprawnień, wtedy rpm by wziął to, co dostał od "make install" - ale ja tak nie chcę, bo nie wiem czy "make install" nie ustawił np. suida na jakiejś binarce albo nie zrobił czegoś jeszcze gorszego... Nie, to trzeba zrobić inaczej. Trzeba zmienić uprawnienia pojedynczym plikom z listy, bez zmieniania atrybutów. I temu (a także innym rzeczom) służy makro %attr(perm,owner,group). Wystarczy, jeśli zaznaczę pliki w %{_bindir} i %{_sbindir} nieco innymi flagami...
%files %defattr(0644,root,root,0755) %{_sysconfdir}/gpm-root.conf %attr(0755,root,bin) %{_bindir}/* %attr(0755,root,bin) %{_includedir}/* %{_libdir}/* %{_sbindir}/* %{_infodir}/*.gz %{_mandir}/man*/*.gz
Co zmieniłem? Dodałem linię %defattr (najlepiej wstawić ją zaraz po %files), no i doszły te dwa wywołania %attr. Jak widać, używa się ich w linii z plikami, które mają być zmienione. Po prostu wpisuje się %attr() przed ścieżką do pliku. Tutaj zmieniłem prawa binarkom na 0755, przy okazji zmieniłem grupę na "bin". Ot, tak sobie :)
Zaczyna to wyglądać coraz bardziej złożenie, ale mam nadzieję że nadal jest to zrozumiałe.
A teraz jeszcze sprawa plików konfiguracyjnych. Z plikami konfiguracyjnymi jest tak, że jeśli je sobie ręcznie zmodyfikujemy, to zwykle chcemy by te zmiany zostały zachowane "dla potomnych". Trzeba rpm-owi powiedzieć, który plik jest dla nas tak ważny. Robi się to makrem %config:
%files
%defattr(0644,root,root,0755)
%config %{_sysconfdir}/gpm-root.conf
%attr(0755,-,bin) %{_bindir}/*
%attr(0755,-,bin) %{_includedir}/*
%{_libdir}/*
%{_sbindir}/*
%{_infodir}/*.gz
%{_mandir}/man*/*.gz
Jak widać, makra %config używa się podobnie jak %attr - czyli wpisuje przed ścieżką do pliku. To taki system :) Jeśli trzeba by połączyć więcej makr na jednym pliku, to się je po prostu dopisuje. Aby np. zmienić od razu uprawnienia tego pliku, trzeba by wpisać:
%config %attr(0600,root,root) %{_sysconfdir}/gpm-root.conf
po "załapaniu" podstaw okazuje się to być całkiem proste, prawda? Ot, budowanie z klocków. Kinderspiel, jak mawiają Francuzi ;)
Dobrze, ale nie powiedziałem jeszcze co daje to makro %config. Działanie jest w sumie proste: plik tak oznaczony nie będzie w razie czego usuwany przy odinstalowywaniu pakietu - i gdy przyjdzie nam jednak na myśl na nowo zainstalować jakiś usunięty onegdaj pakiet, to będziemy mieć stary, ręcznie rzeźbiony konfig. Wygodne i proste. Makro %config ma też swoje opcje, podawane w nawiasie, nieco podobnie jak w przypadku %attr - opcje te to "noreplace" i "missingok".
%config(noreplace) %{_sysconfdir}/gpm-root.conf
...spowoduje, że ten plik nigdy nie zostanie automatycznie nadpisany przy upgrade-owaniu pakietu. Pozostanie niezmieniony, zamiast tego ten nowy plik, który powinien wylądować normalnie na jego miejscu, zostanie zapisany obok, z rozszerzeniem .rpmnew. Tej opcji używa się bardzo często, bo rpm nie ingeruje wtedy automatycznie w pliki konfiguracji. Zamiast tego kładzie swoje propozycje obok, z rozszerzeniem .rpmnew, a administrator będzie mógł przejrzeć je i zadecydować "co dalej?".
%config(missingok) %{_sysconfdir}/gpm-root.conf
Ta opcja z kolei wpływa na sposób w jaki działa weryfikowanie poprawności pakietów (rpm -V). Pliki oznaczone tą flagą można spokojnie usunąć, a rpm nie uzna tego potem za "uszkodzenia pakietu". Inaczej: tą flagą oznacza się pliki konfiguracyjne, które wolno usunąć i nie wpłynęłoby to w negatywny sposób na pracę programu. Przykładu zastosowania nie podam, to dla mnie zbyt abstrakcyjne ;)
Oprócz makr %defattr, %attr oraz %config w sekcji %files może wystąpić także kilka innych konstrukcji. Jedną z nich jest makro %dir, którego używa się podobnie jak makra %config (tyle że nie ma żadnych opcji). Tutaj znowu trzeba wyjaśnić kilka spraw - jeśli w sekcji %files podam np. po prostu:
%files /usr/bin
...to w skład pakietu wejdą wszystkie pliki zawarte w /usr/bin oraz sam katalog /usr/bin. A makro %dir oznacza "dołącz do pakietu tylko katalog, nie włączaj automatycznie jego zawartości".
Ale żeby nie było w ogóle żadnych niejasności: Załóżmy, że mamy w $RPM_BUILD_ROOT katalog /usr/bin a w tym katalogu różne pliki. Teraz mamy trzy sposoby na oznaczenie tego w sekcji %files:
1. Do pakietu będzie należał katalog /usr/bin wraz zawartością.
%files /usr/bin
2. Do pakietu będzie należała tylko zawartość /usr/bin, ale bez samego katalogu. Na czym polega różnica? Jeśli katalog by należał do pakietu, jak ma to miejsce w pierwszym punkcie, to rpm przy odinstalowywaniu pakietu będzie próbował usunąć sam katalog (o ile będzie pusty).
%files /usr/bin/*
3. Do pakietu będzie należał tylko katalog /usr/bin, bez zawartości.
%files %dir /usr/bin
Teoria teorią, ale kiedy stosuje się taki, a nie inny sposób zapisu? Pomyślmy... Jeśli instaluje się jakiś program który umieszcza swoje binarki w /usr/bin, to zwykle załącza się tylko pliki, bez katalogu (bo /usr/bin ma pozostać, i pozostanie, na swoim miejscu nawet po odinstalowaniu programu).
Jeśli jednak instaluje się program który tworzy sobie jakiś katalog z własnymi danymi w /usr/share/foo, to zwykle po usunięciu takiego pakietu oczekuje się, że /usr/share/foo zniknie z dysku - bo używał go tylko jeden program, który właśnie usunęliśmy. I w takim wypadku załącza się do pakietu zarówno zawartość /usr/share/foo jak też sam katalog /usr/share/foo. Gdyby nie załączyć tego katalogu, to po usunięciu pakietu na dysku by pozostał pusty katalog /usr/share/foo - zawartość by wyparowała, ale katalog pozostał. A to by było brzydkie, prawda?
A trzeci sposób, czyli załączenie samego katalogu można stosować np. w przypadku pakietu zawierającego szkielet katalogów na dysku. Niektóre dystrybucje mają takie pakiety, które zawierają tylko siatkę katalogów /usr, /usr/bin, /usr/share/man/man5 itp. I tutaj zwykle użyje się tego makra, bo RPM przy budowaniu pakietów domyślnie ignoruje puste katalogi. Albo w sytuacjach, gdy sekcja %files wymienia każdy plik pojedynczo, wtedy też makro %dir znajdzie zastosowanie. Ale to bardzo rzadkie sytuacje...
Następnym makrem jest makro %doc. To makro jest nieco dziwne, bo typowy przykład użycia tego makra to wpis w rodzaju:
%files %doc README LICENSE ChangeLog
I działanie jest nieco odmienne niż by można oczekiwać - makro to pobierze pliki README, LICENSE, ChangeLog z katalogu z rozpakowanymi źródłami, a nie z $RPM_BUILD_ROOT, następnie umieści te pliki w fake root w podkatalogu %{_docdir}/%{name}-%{version} i pooznacza je jako "pliki dokumentacji". Czyli po prostu weźmie kilka luźnych plików z katalogu ze źródłami, założy im w razie czego katalog /usr/share/doc/nazwa-wersja, i skopiuje je tam. To taki sposób na dodanie do pakietu dodatkowej dokumentacji z pakietu źródłowego, dokumentacji która normalnie by nie została zainstalowana (bo "make install" jej nie kopiowało).
Jest jeszcze jeden sposób użycia tego makra, można go jednak faktycznie użyć w odniesieniu do plików zainstalowanych w $RPM_BUILD_ROOT, wtedy zostaną one oflagowane jako "pliki dokumentacji". Użycie przypomina wtedy nieco korzystanie z makra %config. Zastanawiające jest w jaki sposób rpmbuild rozpoznaje, czy makro odwołuje się do pliku z $RPM_BUILD_ROOT czy też jest rozkazem przekopiowania pliku z katalogu ze źródłami. Nie byłem w stanie nigdzie tego wyszperać, a w źródłach rpm-a nie chce mi się już grzebać, ale przypuszczam że rozpoznawane jest to na podstawie wyglądu ścieżki - jeśli ścieżka zaczyna się od ukośnika, to oznacza plik w $RPM_BUILD_ROOT. Jeśli jednak podawana jest sama nazwa pliku, bez początkowego ukośnika (czyli tzw. ścieżka względna), to chodzi o plik z katalogu ze źródłami.
W komplecie z makrem %dir istnieje jeszcze makro %docdir. Oznacza się nim katalog, a wtedy cała jego zawartość zostanie oflagowana jako "pliki z rodzaju dokumentacji". Ważna uwaga - makro to nie załącza plików do pakietu, to znaczy że trzeba najpierw w jakiś sposób "normalnie" włączyć katalog w listę %files, a potem dodatkowo, w osobnej linii, oznaczyć ten katalog za pomocą %docdir.
Ale ja tutaj tak gadam i gadam o dokumentacji, a nie powiedziałem najważniejszego - o co w ogóle chodzi z tym oznaczaniem dokumentacji. Cóż, cel jest tylko jeden - przy instalacji pakietów rpm można za pomocą specjalnego przełącznika (lub ustawienia w ~/.rpmrc) pomijać instalowanie dokumentacji. To znaczy, że pakiet się zainstaluje ale pliki oznaczone jako "dokumentacja" nie zostaną zainstalowane. Przydatne jest to np. przy instalowaniu pakietów na małym serwerze, gdzie cenny będzie każdy megabajt dysku. Przydaje się to też użytkownikom - bowiem polecenie "rpm -qd nazwapakietu wyświetli listę dokumentacji w danym pakiecie - i użytkownik od razu będzie wiedział, jakie pliki można przeczytać.
RPM oznacza niektóre pliki automatycznie jako dokumentację. Będą to pliki lądujące w różnych %{_mandir}, %{_infodir}, %{_docdir}. Czyli tylko sporadycznie trzeba będzie samemu używać flag %doc/%docdir.
Powoli zbliżamy się do końca opisywania sekcji %files, zostało nam w sumie tylko jedno ważniejsze makro do opisania - makro %verify. RPM posiada bardzo fajną możliwość weryfikowania pakietów po instalacji. Oznacza to po prostu, że RPM porównuje stan plików na dysku z tym, co ma zapisane w swoich bazach danych. Jeśli coś się różni, to rpm to zgłasza. A więc wykryje zmiany dat, rozmiaru, uprawnień, sum kontrolnych itp. Fajne. A makro %verify służy do sterowania zachowaniem rpm-a właśnie przy przeprowadzaniu weryfikacji. Makro %verify wywołuje się podobnie jak %config, to makro również przyjmuje opcje w nawiasach. A jakie są to opcje?
md5: Pod weryfikację podpada sprawdzanie sumy kontrolnej md5
size: Wykrywane będą zmiany wielkości pliku
link: Sprawdza, czy plik jest linkiem, a jeśli jest, to czy nie zmieniła się lokacja na którą wskazuje
user: Sprawdza, czy zmianie nie uległ właściciel pliku
group: Sprawdza grupę do której przynależy plik
mtime: Sprawdza czas ostatniej modyfikacji pliku
mode: Wykrywa zmiany w prawach dostępu
Makra używa się np. w taki sposób:
%files
%{_bindir}/*
%verify(mode user group) %{_datadir}/foo/plik
...co spowoduje, że rpm będzie w przypadu pliku "%{_datadir}/foo/plik" wykrywał tylko zmiany właściciela/grupy i praw dostępu. Zmiany rozmiaru, daty modyfikacji itp. będą ignorowane.
Jeśli opcje poprzedzi się pojedynczym słowem "not", to zostaną one zanegowane. Np.:
%files
%{_bindir}/*
%verify(not size md5 mtime) %{_datadir}/foo/plik
oznacza "sprawdzaj wszystko, oprócz rozmiaru, sumy kontrolnej i daty modyfikacji".
W pewnych wypadkach rpm automatycznie wyłączy pewne rodzaje weryfikacji w zależności od typu pliku, np. zliczanie sumy md5 na urządzeniach (np. wpisy w /dev).
Makro %verify istnieje w dwóch postaciach, podobnie jak %attr. Jedna z nich wpływa na pojedyncze wpisy w %files, druga odnosi się globalnie do wszystkich wpisów. Myślę o makrze %defverify, które ma składnię identyczną jak %verify, tyle że używa się go jak %defattr, a więc bez podawania konkretnego pliku.
Istnieje jeszcze jedno bardzo wyspecjalizowane makro, makro %dev. Jest ono używane tylko tam, gdzie pakiet ma zawierać pliki urządzeń (a więc wpisy z katalogu /dev). Makro to działa podobnie jak linuksowe polecenie mknod, a więc zakłada plik urządzenia o podanej charakterystyce. Ale po co ono w ogóle istnieje? Wytłumaczenie jest proste - polecenie mknod wymaga uprawnień superużytkownika, więc do zbudowania pakietu zawierającego pliki urządzeń potrzebne by były te uprawnienia. A budowanie pakietów z nadmiernymi uprawnieniami może być ryzykowne, jeśli np. jakaś operacja kasowania plików wymknie się poza poligon RPM. Dlatego wypracowano obejście tego problemu - zamiast używać mknod użytkownik może posłużyć się makrem %dev które wirtualnie stworzy odpowiedni plik urządzenia w pakiecie. Wirtualnie, bo tak naprawdę urządzenie zostanie założone dopiero w momencie instalacji pakietu, gdy będą dostępne potrzebne uprawnienia - fizycznie pakiet nie będzie zawierał w sobie tych plików. Makro to ma prostą składnię, wzorowaną na mknod:
%dev(c,1,3) %attr(666,root,root) /dev/null
Przyjmuje trzy parametry: typ, numer główny, numer poboczny (albo type, major, minor jeśli ktoś woli anglojęzyczną nomenklaturę). Identycznie jak w mknod, tyle że nazwę pliku podaje się poza makrem. Wskazywany plik nie istnieje fizycznie w %{buildroot}, ale według RPM będzie wchodził w skład pakietu.
I na deser została nam tylko jedna cecha sekcji %files, nie jest to co prawda makro, ale raczej opcja dla samej sekcji. Normalnie sekcję rozpoczyna się deklaracją "%files", po której następuje lista plików wraz z ew. makrami %attr, %verify itp. Jeśli jednak sekcję rozpocznie się w taki sposób:
%files -f plik_z_listą
...to rpmbuild odczyta plik "plik_z_listą" i użyje go tak, jakby jego zawartość wpisać w sekcji %files. Oznacza to, że można nadal normalnie umieścić listę plików w %files, opcja -f działa po prostu trochę jak dyrektywa "#include" w C. Zwykle używa się jej w połączeniu z makrem %find_lang, ale o tym przy innej okazji.
I to by było wszystko co mam do powiedzenia o sekcji %files :)
Sekcje %pre, %post, %preun, %postun... Tutaj nie ma wiele do powiedzenia - te sekcje po prostu służą do wywoływania shellowych poleceń "na styku" czynności, które wykonuje rpm. Czyli zwykle wywołują jakieś polecenie shella przed/po instalacji/deinstalacji pakietu.
Mam tutaj prosty przykład - pliki dokumentacji info. Oryginalna przeglądarka plików info używa specjalnych indeksów, plików "dir". Indeksy te zawierają po prostu spis wszystkich stron info, razem z jednolinijkowymi opisami. Indeks widzi się po wpisaniu "info" bez nazwy strony. Ale tutaj występuje pewna niedogodność - pliki z indeksem trzeba ręcznie aktualizować, to znaczy każdy nowy plik info musi zostać zarejestrowany w pliku "dir", musi także w razie usuwania zostać z pliku "dir" wyrejestrowany. Robi się to narzędziem "install-info". Wymogi, które trzeba spełnić są następujące: pakiet musi zostać zainstalowany, pliki skopiowane na swoje miejsca w systemie, a potem trzeba uruchomić install-info z odpowiednimi parametrami. Przy odinstalowywaniu pakietu trzeba jeszcze raz uruchomić install-info, jedyny haczyk polega na tym, że do wyrejestrowania pliku info z indeksu konieczna jest obecność tego pliku info. Żeby go wyrejestrować, musi on być obecny w systemie. Głupota, ale niestety tak jest. Dlatego przykładowa sekcja może wyglądać tak, zakładając że nasz plik to /usr/share/info/gpm.info.gz
%post
install-info %{_infodir}/gpm.info.gz %{_infodir}/dir
%preun
install-info --delete %{_infodir}/gpm.info.gz %{_infodir}/dir
Sekcja %post odpowiada za fazę po zainstalowaniu pakietu. Sekcja %preun oznacza etap zaraz przed usunięciem plików z dysku. I tak to w sumie działa. Teraz jeszcze warto tylko w preambule dopisać tag "PreReq: /sbin/install-info", żeby rpm z góry wiedział, że install-info jest potrzebne do poprawnego zainstalowania tego pakietu.
Innym popularnym zastosowaniem sekcji %post i pokrewnych jest uruchamianie ldconfig - jeśli zrobi to pakiet, to użytkownik nie będzie musiał o tym pamiętać. Można też połączyć instalowanie pakietu z konfiguracją, np. po zainstalowaniu glibc uruchomić od razu jakiś skrypt regenerujący locale, albo uruchamiający timeconfig. Możliwości są tutaj takie same, jak w skryptach shellowych.
OK, to by były te najważniejsze podstawy dotyczące budowania pakietów. Po takiej części teoretycznej można się zabrać za normalne pakietowanie. Ale najpierw zobaczmy, jak ma się ten pierwszy, przykładowy plik .spec który zacytowałem na początku do wszystkich wytycznych które wymieniałem. Obejrzyjmy go jeszcze raz:
Name: procps
Version: 3.1.7
Release: 1
Summary: System and process monitoring utilities
License: LGPL, GPL, BSD-like
Group: Applications/System
Packager: <procps-feedback@lists.sf.net>
Source: http://procps.sf.net/procps-%{version}.tar.gz
URL: http://procps.sf.net/
BuildRoot: %{_tmppath}/procps-root
%description
The procps package contains a set of system utilities which provide system
information. Procps includes ps, free, sysctl, skill, snice, tload, top,
uptime, vmstat, w, and watch. You need some of these.
%prep
%setup -q
%build
make CC="gcc $RPM_OPT_FLAGS" LDFLAGS=-s
%install
rm -rf $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install
%clean
rm -rf $RPM_BUILD_ROOT
%post
# add libproc to the cache
/sbin/ldconfig
%files
%defattr(0644,root,root,755)
%doc NEWS BUGS TODO COPYING COPYING.LIB README
%attr(555,root,root) /lib/libproc.so*
%attr(555,root,root) /bin/*
%attr(555,root,root) /sbin/*
%attr(555,root,root) /usr/bin/*
%attr(0644,root,root) /usr/share/man/man1/*
%attr(0644,root,root) /usr/share/man/man8/*
A teraz przejrzyjmy to krytycznie. Najpierw preambuła... wygląda normalnie, tyle że w Source: nazwa pakietu jest zaszyta na sztywno - ja bym ją uzależnił od wartości Name:, ale to nic ważnego. Nie ma tutaj wszystkich możliwych do wpisania tagów, ale wystarcza.
Do %description też nie ma się co przyczepić - jest na swoim miejscu.
Sekcja %prep to makro %setup, dodatkowo "wyciszone" opcją -q. Normalka.
Za to w sekcji %build dziwi mnie brak wywołania ./configure - ale najwidoczniej ten program tak może mieć. Przy wywołaniu make ustawiane są od razu zmienne CC i LDFLAGS. Też nic specjalnego.
Sekcja %install najpierw na wszelki wypadek próbuje usunąć stary fake root, potem wywołuje make i przekierowuje instalację za pomocą $DESTDIR. Znowu, wszystko w normie.
O, definicja sekcji %clean. Usunie fake root po zrobieniu pakietu. Nieczęsto widzi się sekcje %clean odbiegające od tego schematu.
Jest nawet sekcja %post, uruchamiająca ldconfig. Pewnie pakiet dodaje jakieś biblioteki do systemu. Nie wiem, czy ldconfig będzie tutaj akurat konieczny, ale nic nie popsuje... /sbin/ldconfig powinien się znajdować w sumie też w PreReq:, ale on należy do glibc więc zawsze powinien być w systemie i nie ma sensu go dodawać siłą do zależności.
I na koniec najciekawsze - sekcja %files. Domyślne atrybuty standardowe, dokopiowanie do dokumentacji paru plików z katalogu ze źródłami, a później błąd - nadawanie bibliotekom w /lib praw do wykonywania. Biblioteki nie potrzebują bitu wykonywalności... no dobra, może to nie jest jakiś straszny błąd, ale po prostu niepotrzebna czynność. Później jeszcze dziwniejsze kwiatki - nadawanie binarkom praw 0555, co oznacza prawo do odczytu i wykonania. Dziwne, odbiera się nawet rootowi prawo zapisu. Dziwne, choć niegroźne - root i tak może wszystko :) Na koniec jeszcze dwa niepotrzebne przypisania bitów 0644 stronom manuala - przecież one i tak by miały takie prawa (poprzez %defattr). Ktoś przesadził pisząc tę sekcję akurat. Niepotrzebne, martwe definicje, poślizg z nadawaniem praw do wykonywania bibliotekom, ogólnie niezbyt ładna sekcja, choć całkowicie funkcjonalna.
Główna wada: nie używa makr, ale jawnych ścieżek (/usr/share/man itp.). Pewnie przez to, że nie użyto ./configure i ścieżki są zaszyte na sztywno w pliku Makefile. Ogólnie plik .spec pewnie by zadziałał, ale nie jest to spec który by można rozpowszechniać w jakiejś dystrybucji.
Teraz, po dosyć ogólnym wprowadzeniu, mogę przejść do strony praktycznej. Czyli do tego, jak wygląda budowanie pakietu.
Zanim przejdziemy jednak do sprawy, trzeba się upewnić że rpm jest poprawnie skonfigurowany. Najpierw plik ~/.rpmrc. U mnie wygląda on tak:
optflags: i686 -march=pentium2 -Os -fomit-frame-pointer -s -pipe -DNDEBUG -DG_DISABLE_ASSERT -z combreloc buildarchtranslate: i686: i686 buildarchtranslate: i586: i686 buildarchtranslate: i486: i686 buildarchtranslate: i386: i686
Pierwsza linijka ustawia zmienną/makro "optflags" na standardowe flagi optymalizacji w moim systemie. W zasadzie to te ustawienia nie będą zbyt często dochodziły do głosu, bo najnowszy RPM i tak daje pierwszeństwo środowiskowym zmiennym $CFLAGS/$CXXFLAGS. Tak że to ustawienie zapewne pozostanie w większości przypadków "ciche", ale na wszelki wypadek... wiadomo. Składnia jest dosyć interesująca, bo RPM pozwala na ustawienie kilku zestawów flag, różnych dla różnych architektur. Architektura to pierwsze słowo po "optflags:", a dopiero potem lecą flagi. Jak widać, u mnie architektura to i686 (rpm nie rozróżnia "odmian" i686 w stylu "pentium2", "k6" itp.). Gdybym chciał zdefiniować różne flagi dla różnych architektur, to mogłoby to wyglądać np. tak:
optflags: i686 -march=pentium2 -Os -fomit-frame-pointer -s -pipe -DNDEBUG -DG_DISABLE_ASSERT -z combreloc optflags: i586 -march=k5 -Os -fomit-frame-pointer -s -pipe -DNDEBUG -DG_DISABLE_ASSERT -z combreloc
RPM potem dobiera sobie właściwy zestaw flag w zależności od ustawionej BuildArch. Chyba, że użyje się wykrętu jakim jest "buildarchtranslate". Ten cały bloczek tych poleceń ma u mnie jedno zadanie - niezależnie od wybranej architektury (od i386 po i686) RPM i tak sięgnie po ustawienia dia i686. Nie, to nie jest potrzebne, wystarczy ustawić sobie flagi dla swojej architektury, ale daje pewność że, niezależnie od sytuacji, rpm nie spróbuje budować pod inną architekturę. Z wyjątkiem architektury "noarch" :)
Dobra, teraz plik ~/.rpmmacros. Moja zawartość:
%_tmppath /tmp
%_mandir %{_prefix}/share/man
%_infodir %{_prefix}/share/info
%packager Grzegorz Niewęgłowski
%vendor QuaTrin
%distribution QuaTrin
%_topdir /home/grzegorz/rpm
%_defaultdocdir %{_datadir}/doc
%_sysconfdir /etc
%_localstatedir /var
%_libexecdir %{_prefix}/lib
Najpierw zmieniam ustawienie %{_tmppath}. Domyślne to /var/tmp, ale ja wymuszam /tmp. Nie, nie ma to specjalnych przesłanek, po prostu tak jest wygodniej na moim systemie.
Potem zmieniam definicje %{_mandir} i %{_infodir}, bo rpm domyślnie ustawia te zmienne niezgodnie z FHS (bez katalogu share). Te ustawienia nie będą poprawne, jeśli %{_prefix} zostanie ustawiony na /usr/local - ale to już zależy od tego, czy ktoś woli instalować do /usr/local, czy do /usr. Na systemach domowych to tylko kwestia kosmetyki, a głównym powodem dla którego ludzie na maszynach domowych instalują coś w /usr/local to fakt, że łatwiej im to potem ręcznie usunąć. W przypadku pakietów RPM ten argument nie ma jednak znaczenia, bo to RPM dba o porządek, więc oprogramowanie może lądować w /usr.
Potem ustawiam jeszcze dane o moim systemie i o mnie, a na koniec ustawiam %{_topdir} oraz %{_defaultdocdir}. To ostatnie to domyślny katalog na dokumentację, w tym przypadku znowu trzeba wymusić zgodność z FHS, bo RPM domyślnie chce używać %{_prefix}/doc, a według FHS katalog doc powinien jednak leżeć wewnątrz podkatalogu share, a nie bezpośrednio w /usr.
Na koniec pozostaje tylko przedefiniować %{_sysconfdir} oraz %{_localstatedir} tak, by były bardziej "politycznie poprawne" (wytyczne Filesystem Hierarchy Standard).
Interesujące może też być ustawienie %{_libexecdir} - domyślnie makro to wskazuje na /usr/libexec, ale jest to niezgodne z FHS, który nie przewiduje w ogóle istnienia takiego katalogu. Dlatego %{_libexecdir} zwykle będzie wskazywać na %{_prefix}/lib
Z tak zmodyfikowanymi plikami nasz system RPM jest gotów do akcji.
Fajno.
OK, więc do dzieła. Co by tutaj wziąć na tapetę... o,
mam. Przejdź do następnego rozdziału :)
Najpierw zajmiemy się spakietowaniem tej dosyć znanej przeglądarki obrazków. W przypadku każdego nowego pakietu raczej nie da się uniknąć jednej "testowej" kompilacji i instalacji, przeprowadzanej poza środowiskiem rpm. Chodzi o to, by poznać konkretny pakiet - wybrać flagi konfiguracyjne, sprawdzić czy nie wystąpią problemy, obejrzeć listę instalowanych plików.
OK, więc jakie czynności nas czekają:
1. Rozpakowanie źródeł :)
2. Konfiguracja. OK, robię "./configure --help" i nie widzę żadnych specjalnych opcji. Decyduję się na użycie tylko dwóch opcji - "--prefix=/usr --disable-nls". Normalka. Jedyne co może dziwić, to wyłączanie wsparcia dla NLS, chociaż gqview ma całkiem przyzwoite polskie tłumaczenie. Ale to tylko mój prywatny odchył. Cały proces ./configure przebiegł bez błędów. OK.
3. Kompilacja. "make". Udało się bez błędów.
4. Próbna instalacja. Potrzebny mi będzie jakiś tymczasowy katalog... na takie (i inne) okazje mam ramdysk w /dev/shm (zlinkowany, w celu łatwiejszego dostępu, do /shm). Ty możesz użyć dowolnego katalogu, gdzieś w swoim $HOME lub w /tmp. Instaluje się tak samo, jak to potem będzie miało miejsce w specu, czyli poprzez $DESTDIR.
make install DESTDIR=/shm
zadziałało u mnie bezbłędnie.
5. Ocena sytuacji. Wchodzę do /shm, zastaję tam następujące drzewko plików:
usr/
\-bin/
|-gqview
|
|-man/
| \-man1/
| \-gqview.1
\-share/
\-gqview/
\-README
OK, dzięki temu będę wiedział, jakie pozycje umieścić w sekcji %files. Wszystko jest OK, za wyjątkiem położenia manuali. Powinny leżeć w /usr/share/man, a nie w /usr/man. Ale to załatwię przy pisaniu pliku .spec.
6. Pisanie pliku .spec Tutaj polecam najpierw stworzenie sobie "szkieletu", którego potem by można używać przy tworzeniu kolejnych speców. Taki "szkielet" którego osobiście używam wygląda następująco:
Summary:
Name:
Version:
Release: 1
License: GPL
Group:
Source: %{name}-%{version}.tar.gz
BuildRoot: /var/tmp/%{name}-%{version}
%description
%clean
rm -rf %{buildroot} %{_builddir}/%{name}-%{version}
%prep
%setup -q
%build
%configure
make
%install
rm -rf %{buildroot}
make install DESTDIR=%{buildroot}
%files
%defattr(0644,root,root,0755)
%attr(0755,root,root)%{_bindir}/*
%{_mandir}/man*/*.gz
Mała preambuła (częściowo pusta), moja odmiana sekcji %clean (usunie "fake root" oraz rozpakowane źródła)), standardowe sekcje %prep, %build, %install. Sekcja %files z dwoma najczęściej powtarzającymi się wpisami - wychwycenie plików w %{_bindir} oraz manuali w %{_mandir}/man*. Wystarczy to skopiować jako "nowy plik .spec" i pouzupełniać potrzebne elementy.
OK, więc weźmy się za uzupełnianie. Najpierw przykładowa preambuła:
Summary: Przeglądarka plików graficznych. Name: gqview Version: 1.2.0 Release: 1 License: GPL Group: Grafika Source: %{name}-%{version}.tar.gz BuildRoot: /var/tmp/%{name}-%{version} %description GQView to niewielka przeglądarka wykorzystująca GTK+, oferuje wszystkie tak potrzebne "bajery" jak zoom, miniaturki, filtrowanie, czy zewnętrzne edytory. A przy tym jest całkiem szybka.
Wystarczy po prostu dopisać do "szkieletu" cechy zależne od konkretnego pakietu - wersja, nazwa, opisy itp. Lećmy dalej.
Sekcję %clean pozostawiam bez zmian, w sumie to raczej nigdy jej nie będę zmieniał - jest "uniwersalna".
Sekcja %prep też pozostaje bez zmian.
Drobne zmiany w sekcji %build:
%build
%configure --disable-nls
make
Jak widać, dodałem "--disable-nls". Ale, ale - gdy konfigurowałem gqview przy kompilacji "testowej", to użyłem również "--prefix=/usr", no i miałem też coś zrobić z położeniem manuali... Więc dlaczego teraz wpisuję tylko --disable-nls? Bo makro %configure zawiera w sobie wszystkie przełączniki ustawiające katalogi w mechanizmach ./configure. Między innymi ustawia --prefix i --mandir, więc nie muszę się o to martwić.
Sekcję instalacyjną również pozostawiam bez zmian, pozostaje tylko sekcja %files. GQview po mojej testowej instalacji zainstalowało raptem 3 pliki - binarkę, manual oraz plik README w /usr/share/gqview. Moja sekcja %files na tę okoliczność wygląda tak:
%files
%defattr(0644, root, root, 0755)
%attr(0755,root,root) %{_bindir}/*
%{_mandir}/man*/*.gz
%{_datadir}/gqview
Wychwytywanie binarek w %{_bindir} oraz manuali w %{_mandir} miałem już wklepane w szablonie, wystarczyło tylko dodać %{_datadir}/gqview (ta notacja, jak w poprzedniej części mówiłem, w odniesieniu do katalogu oznacza "załącz katalog i jego zawartość"). A makro %{_datadir} to nic innego, jak "standardowe" /usr/share. OK, więc cały spec-file wygląda teraz tak:
Summary: Przeglądarka plików graficznych. Name: gqview Version: 1.2.0 Release: 1 License: GPL Group: Grafika Source: %{name}-%{version}.tar.gz BuildRoot: /var/tmp/%{name}-%{version} %description GQView to niewielka przeglądarka wykorzystująca GTK+, oferuje wszystkie tak potrzebne "bajery" jak zoom, miniaturki, filtrowanie, czy zewnętrzne edytory. A przy tym jest całkiem szybka. %clean rm -rf %{buildroot} %{_builddir}/%{name}-%{version} %prep %setup -q %build %configure --disable-nls make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} %files %defattr(0644, root, root, 0755) %attr(0755,root,root) %{_bindir}/* %{_mandir}/man*/*.gz %{_datadir}/gqview
Teraz wystarczy to gdzieś zapisać, np. w ~/rpm/SPECS/gqview.spec
7. Wywołanie rpmbuild. Po tym, jak mamy już w ~/rpm/SPECS napisanego speca, można sprawdzić czy to zadziała. Do katalogu ~/rpm/SOURCES trzeba skopiować źródła gqview-1.2.0.tar.gz, a potem wywołać rpmbuild. Zapewne będziesz teraz przebywał w ~/rpm/SPECS, więc wystarczy jeśli wpiszesz:
rpmbuild -bb gqview.spec
...jeśli wszystko poszło dobrze, to zobaczysz przesuwające się po ekranie komunikaty, a po jakimś czasie rpmbuild zakończy pracę - z sukcesem, miejmy nadzieję. Jeśli wszystko poszło dobrze, to w katalogu ~/rpm/RPMS, w podkatalogu architektury twojej maszyny, znajdziesz gotowy do instalacji, własny pakiet gqview-1.2.0-1.xxx.rpm. Teraz, gdy już wiesz że spec działa, możesz chcieć stworzyć sobie źródłowy pakiet .src.rpm - wystarczy wpisać
rpmbuild -bs gqview.spec
a po chwilce w katalogu ~/rpm/SRPMS znajdziesz odpowiedni pakiet. Pakiet źródłowy w wygodny sposób łączy w sobie kod źródłowy z twoim plikiem .spec, co jeszcze bardziej uprości ewentualne przyszłe rekompilacje.
Po budowaniu warto po sobie posprzątać. Moja wersja makra %clean zajęła się usunięciem "fake root" (/var/tmp/gqview-1.2.0) oraz rozpakowanych źródeł gqview (~/rpm/BUILD/gqview-1.2.0). Jedyne co pozostało, to paczka ze spakowanymi źródłami w ~/rpm/SOURCES. Możesz usunąć ją ręcznie, możesz też wywołać:
rpmbuild --rmsource gqview.spec
...a wtedy rpmbuild przeskanuje plik .spec, samodzielnie "wywęszy" gdzie powinny leżeć źródła i usunie je. Przełącznika "--rmsource" można było użyć już przy budowaniu pakietu - wtedy po udanym skonstruowaniu pakietu rpm usunie źródła, a ty nie będziesz musiał już palcem kiwnąć. Jeśli kompilacja lub budowa samego pakietu się nie powiodła, to rpmbuild oczywiście nie usunie źródeł :)
OK, to by było pierwsze, mam nadzieję że udane, budowanie pakietu rpm. Nie jest to może pakiet nadający się do szerokiej dystrybucji, ale na potrzeby domowe jest jak najbardziej OK. Teraz małe wyjaśnienie: taki sposób instalacji oprogramowania wydaje się być koszmarnie niewygodny i czasochłonny. I faktycznie, tworzenie pierwszego pliku .spec dla jakiegoś programu zawsze zabierze więcej czasu, niż zwykła kompilacja i instalacja. Ale za to gdy nadejdzie ewentualny upgrade będziesz miał łatwiej - gdy ukaże się np. gqview-1.2.1, to wystarczy że skopiujesz paczkę gqview-1.2.1.tar.gz do ~/rpm/SOURCES, a w pliku ~/rpm/SPECS/gqview.spec zmienisz wersję w tagu Version:, po czym uruchomisz
rpmbuild -bb gqview.spec
I to zapewne wystarczy - rpm rozpakuje źródła, skonfiguruje, skompiluje, zbuduje paczkę, a tobie pozostanie zrobić
rpm -Uvh nowa_paczka_z_gqview.rpm
Wysiłek włożony w skonstruowanie specfile zwróci się przy okazji najbliższej aktualizacji lub rekompilacji. Dobrze napisany plik .spec, tzn. taki, w którym sekcja %files będzie się w pewnym stopniu dostosowywać do pojawiania się nowych plików/znikania starych (co czasem dzieje się wraz z rozwojem programów) - taki plik spec będzie sobie dobrze radził z przyjmowaniem nowych wersji programu - ty będziesz tylko zmieniał numerek wersji w jednym tagu i wywoływał rpmbuild. Od czasu do czasu co najwyżej będziesz musiał skorygować listę %files, jeśli instalowany program bardziej się zmieni (z doświadczenia wiem, że to nieczęsto będziesz spotykał).
Czym jest mutt - chyba wszyscy zainteresowani wiedzą. Więc bez zbędnych ceregieli przejdźmy do owieczki, jak zwykle oznacza to próbną instalację "zapoznawczą".
1. Konfiguracja źródeł. Po "./configure --help" wybieram sobie taki zestaw opcji:
./configure --prefix=/usr --with-curses --disable-pop \ --disable-imap --disable-nls
Po pierwsze, wybieram "siłowo" używanie ncurses zamiast libslang (na wszelki wypadek, mam obydwie te biblioteki w systemie ale wolę ncurses), poza tym wyłączam obsługę skrzynek pop/imap (bo do obsługi zewnętrznych skrzynek i tak używam fetchmaila, więc muttowa obsługa pop/imap mi się nie przyda). No i wyłączam nls. Znowu. Po prostu gdy ostatni raz widziałem tłumaczenie Mutta, to na górze ekranu widziałem polskie "Wyjdź", "Usuń", ale na dole tego samego ekranu były już ,Msgs:", "threads/date" i "all" - a takie połowiczne tłumaczenia mnie irytują. Więc wolę mieć całego Mutta po angielsku niż 70% po polsku.
2. Kompilacja. Udaje się :)
3. Testowa instalacja. "make install DESTDIR=/shm" podziałało. To dobrze.
4. Ogląd plików. Po "make install" powstało w /shm drzewko:
usr/
|-bin/
| |-flea
| |-mutt
| |-muttbug
| |-pgpewrap
| \-pgpring
|-doc/
| \-mutt/
| |-COPYRIGHT
| |-ChangeLog
| |-GPL
| |-INSTALL
| |-NEWS
| |-PGP-Notes.txt
| |-README
| |-README.SECURITY
| |-README.SSL
| |-TODO
| |-applying-patches.txt
| |-devel-notes.txt
| |-manual.txt
| |-patch-notes.txt
| |-samples/
| | |-Mush.rc
| | |-Pine.rc
| | |-Tin.rc
| | |-gpg.rc
| | |-pgp2.rc
| | |-pgp5.rc
| | |-pgp6.rc
| | |-sample.mailcap
| | |-sample.muttrc
| | |-sample.muttrc-tlr
| | \-iconv/
| | |-iconv.aix-3.2.5.rc
| | |-iconv.aix-4.1.5.rc
| | |-iconv.aix-4.2.0.rc
| | |-iconv.aix-4.3.2.rc
| | |-iconv.freebsd-3.3.rc
| | |-iconv.glibc-2.1.3.rc
| | |-iconv.glibc-2.1.90.rc
| | |-iconv.hpux-10.01.rc
| | |-iconv.hpux-10.20.rc
| | |-iconv.hpux-11.00.rc
| | |-iconv.irix-6.5.rc
| | |-iconv.osf1-4.0a.rc
| | |-iconv.osf1-4.0d.rc
| | |-iconv.solaris-2.4.rc
| | |-iconv.solaris-2.5.1.rc
| | |-iconv.solaris-2.6-cjk.rc
| | |-iconv.solaris-2.6.rc
| | \-iconv.solaris-2.7.rc
| \-html/
| |-manual-1.html
| |-manual-2.html
| |-manual-3.html
| |-manual-4.html
| |-manual-5.html
| |-manual-6.html
| |-manual-7.html
| \-manual.html
|-etc/
| |-Muttrc
| \-mime.types
\-man/
|-man1/
| |-flea.1
| |-mutt.1
| |-mutt_dotlock.1
| \-muttbug.1
\-man5/
|-mbox.5
\-muttrc.5
W zasadzie wygląda normalnie - katalog bin/, etc/, man/ - wszystko w porządku, pomijając drobne felery dotyczące lokacji etc/ (nie powinno się znajdować w usr/) oraz man/ (powinien być w podkatalogu share/) - ale to zostanie automatycznie skorygowane przez rpm-owe makro %configure. To co mnie zastanawia, to katalog doc/, a konkretnie to jego zawartość. Jest tam 1.2MB danych, z czego blisko pół megabajta to ChangeLog mutta, oraz dosyć rozległy podręcznik mutta, w wersji tekstowej i html. W zasadzie to jest mi to pewnie niepotrzebne, mutt ma całkiem niezłe strony manuala... chociaż może tę dokumentację w html-u warto by sobie zachować... tak, chyba tak zrobię. W trakcie budowania pakietu przeniosę podkatalog doc/mutt/html/ do katalogu doc/%{name}-%{version}/, a sam katalog doc/mutt/ usunę. Takie machlojki nie przystoją dystrybucjom, ale "osobom prywatnym" - czemu nie? W sumie mógłbym nawet w ogóle zrezygnować z dokumentacji innej od stron manuala, ale ta w html-u mi się podoba.
5. Pisanie pliku spec. Zaczynam z wymienionym już szkieletem pliku .spec. Po wstępnych przemyśleniach mam taki plik .spec:
Summary: Klient poczty pracujący w trybie tekstowym i obsługujący MIME. Name: mutt Version: 1.4.1i Release: 1 License: GPL Group: Internet Source: %{name}-%{version}.tar.gz BuildRoot: /var/tmp/%{name}-%{version} %description Mutt jest wysoce konfigurowalny. Ze swoimi zaawansowanymi funkcjami, jak mapowanie klawiatury, możliwość używania makr, wątkowanie wiadomości, wyszukiwania bazujące na wyrażeniach regularnych itp. jest jednym z najczęściej wybieranych przez PU klientów poczty. %clean rm -rf %{buildroot} %{_builddir}/%{name}-%{version} %prep %setup -q %build %configure -with-curses --disable-pop --disable-imap --disable-nls make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} mv %{buildroot}%{_docdir}/mutt/html %{buildroot}%{_docdir}/%{name}-%{version} rm -rf %{buildroot}%{_docdir}/mutt %files %defattr(0644,root,root,0755) %attr(0755,root,root)%{_bindir}/* %{_mandir}/man*/*.gz %config(noreplace) %verify(not size mtime md5) %{_sysconfdir}/* %{_docdir}/%{name}-%{version}
Wszystko jest w sumie jasne, omówić warto tylko sekcje %install i %files. W sekcji %install, po tym jak zainstaluję pliki, "koryguję" ułożenie dokumentacji - przenoszę podkatalog html/ do /var/tmp/mutt-1.4.1i/usr/share/doc/, nadając mu od razu nazwę "mutt-1.4.1i" (tyle że wyrażoną za pomocą makr - w razie upgrade-u "samo" się zmieni). A potem wywalam katalog /var/tmp/mutt-1.4.1i/usr/share/doc/mutt/. Tak to przynajmniej ma wyglądać w teorii.
Sekcja %files wygląda normalnie - wychwycić binarki, manuale, oznaczyć pliki w %{_sysconfdir}/ jako "trwałą" konfigurację i nakazać rpm-owi ignorować zmiany wywołane edycją tejże konfiguracji. No i w katalogu %{_docdir}/ wychwytuję tę moją nową dokumentację w html-u, razem z jej katalogiem. Teraz tylko sprawdzić, czy to zadziała :P
6. Budowanie pakietu. Dobra, kopiuję źródła mutt-1.4.1i.tar.gz do ~/rpm/SOURCES/, swój .spec mam zapisany w ~/rpm/SPECS/mutt.spec, wchodzę do katalogu ze specami, uruchamiam "rpmbuild -bb mutt.spec". I... błąd.
Executing(%prep): /bin/sh -e /tmp/rpm-tmp.9265
+ umask 022
+ cd /home/grzegorz/rpm/BUILD
+ cd /home/grzegorz/rpm/BUILD
+ rm -rf mutt-1.4.1i
+ /bin/gzip -dc /home/grzegorz/rpm/SOURCES/mutt-1.4.1i.tar.gz
+ tar -xf -
+ STATUS=0
+ [ 0 -ne 0 ]
+ cd mutt-1.4.1i
/tmp/rpm-tmp.9265[28]: cd: /home/grzegorz/rpm/BUILD/mutt-1.4.1i - No such file or directory
error: Bad exit status from /tmp/rpm-tmp.9265 (%prep)
RPM build errors:
Bad exit status from /tmp/rpm-tmp.9265 (%prep)
Uuu... coś poszło źle. Wygląda na to, że po rozpakowaniu źródeł nie mógł wejść do katalogu /home/grzegorz/rpm/BUILD/mutt-1.4.1i. Zaglądam do ~/rpm/BUILD/ i faktycznie - nie ma tam takiego podkatalogu. Jest za to mutt-1.4.1. Bez tego "i" na końcu. No tak, paczka nazywa się inaczej, a katalog który rozpakowuje inaczej. Dobra, usuwam ~/rpm/BUILD/mutt-1.4.1, no i muszę skorygować plik .spec...
7. Korygowanie speca. Dobra, więc rozpakowywane źródła lądują w katalogu innym niż %{name}-%{version} (bo w %{version} brakuje "i" na końcu). Mogę to rozwiązać na kilka sposobów, ale najlepiej będzie po prostu poprawić wywołanie makra %setup w sekcji %prep na coś takiego:
%prep
%setup -q -n %{name}-1.4.1
Do tego od razu zmieniam sekcję %clean, bo ona ma usunąć potem katalog ze źródłami, więc też musi znać jego nazwę:
%clean
rm -rf %{buildroot} %{_builddir}/%{name}-1.4.1
I będzie działać. Tyle tylko, że przy zmianie wersji źródeł oprócz poprawienia tagu Version: trzeba będzie też poprawić wersję wpisaną w linii %setup i w sekcji %clean. Ale jest inne wyjście :) Można wypchnąć "główną" wersję pakietu (1.4.1) do specjalnej zmiennej, a później używać tej zmiennej, ewentualnie dolepiając na jej koniec literkę "i". Ale to może innym razem. Sprawdźmy, czy po zmianie makra %setup pakiet się zbuduje...
8. Ponowna próba zbudowania pakietu. OK, wpisuję jeszcze raz "rpmbuild -bb mutt.spec". Sukces! Źródła zaczęły się kompilować! Teraz tylko instalacja i... znowu błąd. Ech:
make[2]: Leaving directory `/home/grzegorz/rpm/BUILD/mutt-1.4.1'
make[1]: Leaving directory `/home/grzegorz/rpm/BUILD/mutt-1.4.1'
+ mv %{buildroot}/usr/share/doc/mutt/html /var/tmp/mutt-1.4.1i/usr/share/doc/mutt-1.4.1i
mv: cannot stat `%{buildroot}/usr/share/doc/mutt/html': Nie ma takiego pliku ani katalogu
error: Bad exit status from /tmp/rpm-tmp.9411 (%install)
RPM build errors:
Bad exit status from /tmp/rpm-tmp.9411 (%install)
Czytam i widzę, że błąd dotyczy mojej korekty położenia plików z dokumentacją. Ale nie rozumiem o co chodzi... zaglądam do /var/tmp/mutt-1.4.1i... no tak. Katalog doc/ znajduje się bezpośrednio w usr/, a nie w usr/share/. Ale dlaczego? Przecież makro %configure przekazało na 100% poprawny argument "--docdir=%{_docdir}". Czyżby mutt go zignorował? Wchodzę do ~/rpm/BUILD/mutt-1.4.1/, jeszcze raz uruchamiam "./configure --help" - i faktycznie, mutt po prostu nie obsługuje opcji "--docdir". Zamiast niej ma "--with-docdir". Ręce opadają. Spec idzie jeszcze raz do poprawki.
9. I kolejna poprawka pliku .spec... No dobra, więc dodaję do wywołania ./configure jeszcze jeden argument, "--with-docdir=", przy okazji wprowadzam wspomnianą poprawkę dotyczącą wersji - wypycham wersję do osobnej zmiennej. Teraz cały spec wygląda tak:
%define wersja 1.4.1 Summary: Obsługujący MIME i pracujący w trybie tekstowym klient poczty. Name: mutt Version: %{wersja}i Release: 1 License: GPL Group: Internet Source: %{name}-%{version}.tar.gz BuildRoot: /var/tmp/%{name}-%{version} %description Mutt jest wysoce konfigurowalny. Ze swoimi zaawansowanymi funkcjami, jak mapowanie klawiatury, możliwość używania makr, wątkowanie wiadomości, wyszukiwania bazujące na wyrażeniach regularnych itp. jest jednym z najczęściej wybieranych przez PU klientów poczty. %clean rm -rf %{buildroot} %{_builddir}/%{name}-%{wersja} %prep %setup -q -n %{name}-%{wersja} %build %configure --with-curses --disable-pop --disable-imap --disable-nls \ --with-docdir=%{_docdir}/mutt make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} mv %{buildroot}%{_docdir}/mutt/html %{buildroot}%{_docdir}/%{name}-%{version} rm -rf %{buildroot}%{_docdir}/mutt %files %defattr(0644,root,root,0755) %attr(0755,root,root)%{_bindir}/* %{_mandir}/man*/*.gz %config %{_sysconfdir}/* %{_docdir}/%{name}-%{version}
Tutaj wyjaśnienia wymagają tylko dwie rzeczy - jedna to dodatkowa opcja do %configure, ale to zależy od samego mutta więc nie ma co wyjaśniać :), a druga rzecz to wprowadzenie nowej zmiennej, %{wersja}, w pierwszej linijce speca. Za pomocą polecenia %define. Nie chciałem tego pokazywać już na tym etapie, no ale tak jakoś wyszło. Składnia jest prosta - dwa argumenty - pierwszy to nazwa zmiennej do zdefiniowania/przedefiniowania, a drugi argument to wartość dla zmiennej. Za pomocą %define można zmienić wartość praktycznie każdej zmiennej używanej przez RPM, można też, jak ja tutaj, zdefiniować sobie jakąś nową zmienną. Teraz w przypadku uaktualniania paczki ze źródłami wystarczy pewnie zmienić numer wersji w tej linijce z %define i przebudować pakiet. Ale najpierw sprawdźmy, czy w ogóle da się ten pakiet zbudować...
10. Któreś tam z rzędu podejście do budowania pakietu. Nieco zrezygnowany wpisuję "rpmbuild -bb mutt.spec" i... udało się :) Mam pakiet binarny. Instaluję go, uruchamiam mutta - działa. W /usr/share/doc/mutt-1.4.1i/ mam dokumentację w formie html - czyli pełen sukces. Teraz tylko buduję sobie źródłowy pakiet RPM ("rpmbuild -bs mutt.spec --rmsource --rmspec") i kopiuję go w bezpieczne miejsce - może się kiedyś jeszcze przydać.
Tym razem było trudniej, niż z gqview - po pierwsze zechciało mi się zmienić domyślny skład pakietu, potem jeszcze musiałem walczyć z różnymi nazwami pakietu mutta i katalogu, który rozpakowywał się ze źródeł, a przeoczona muttowa niestandardowość (--with-docdir) kosztowała mnie jedną kompilację. Ale nie było to coś strasznego i mam swój własny plik .spec oraz źródłowy pakiet .rpm. Jest dobrze.
A teraz kolej na nieco mniejsze narzędzie - sed-a. Sed to jedno z najpopularniejszych narzędzi w "GNU toolbox", więc nadaje się całkiem nieźle jako przykład.
1. Konfiguracja. Skrypt ./configure nie ma wielu ciekawych opcji, ja decyduję się na:
./configure --prefix=/usr --disable-dependency-tracking --disable-nls
Wyłączam NLS, przy czym tutaj mam dodatkowy argument by tak robić - sed nie ma w obecnej chwili polskiego tłumaczenia (wystarczy zajrzeć do katalogu "po". Opcja "--disable-dependency-tracking" z kolei jest warta zapamiętania - jeśli ./configure ją udostępnia, to warto jej użyć. Można tym "urwać" ułamek sekundy/sekundę z każdego wywołania kompilatora. Ma to swoje drobne efekty uboczne przy wielokrotnej kompilacji w tym samym katalogu źródłowym, ale to nie dotyczy nigdy pakietów RPM.
2. Kompilacja. Szybka i pomyślna.
3. Testowa instalacja. Udaje się.
4. Ogląd plików.
usr/
|-bin/
| \-sed
|-info/
| |-dir
| |-sed.info
| |-sed.info-1
| \-sed.info-2
\-man/
\-man1/
\-sed.1
Wszystko w normie, tyle że te strony info mi się nie podobają. Nie lubię rozbijania stron info na kilka plików. Strony info mogą być, tak jak tutaj, rozbite na kilka plików (tutaj 3), albo mogą zostać połączone w jeden über-plik info. Zalety-wady: Kilka mniejszych plików jest łatwiejszych dla obsłużenia przez komputer, wymaga mniej pamięci przy przetwarzaniu (w pamięci znajduje się naraz tylko jeden "rozdział", trzeba odczytać mniej danych przy pokazywaniu strony). Ale przy współczesnych komputerach te cechy nie są już zaletami. Ja wolę mieć wszystkie strony info programu zbite w jeden plik, bo wtedy oszczędzam miejsce, zwłaszcza gdy strony będą skompresowane (mniejsza strata na nie-do-końca-wypełnionych blokach filesystemu - w przypadku 1 pliku jest to jeden "niepełny" blok, w przypadku trzech szansa na zmarnowanie odrobiny miejsca zwiększa się trzykrotnie, po doliczeniu ew. zysków z kompresji über-pliku i uwzględnieniu rozmiarów niektórych stron info (np. glibc-info liczy się w megabajtach) oszczędności mogą być spore. No i mniejszy chaos panuje potem w /usr/share/info/.
Nie jest to konieczny zabieg, ale ja lubię tak robić. Taki prywatny odchył mam.
Tak więc te strony info pójdą do poprawki. Nie jest to taki trudne, wystarczy je wygenerować na nowo. Robi się to programem "makeinfo", bo strony .info to tylko "produkt końcowy" powstający z tzw. źródeł texinfo. Plan działania wygląda tak: zainstalować pliki, usunąć zawartość katalogu info, wejść do niego, z niego wywołać "makeinfo --no-split" (opcja "--no-split" powoduje tworzenie jednego über-info)... jedyny hak, to znalezienie plików źródłowych. Ale w katalogu seda widzę katalog "doc", wchodzę do niego... bingo! Leżą tutaj te "gotowe" już pliki sed.info*, leży też plik sed.texi (pliki źródłowe zwykle będą miały te same nazwy, co pliki wynikowe. Tyle że będą miały rozszerzenie .texi). Warto sprawdzić teraz, czy te pliki da się na nowo wygenerować - bo czasem, jeśli źródła .texi są bardzo złożone, to mogą być porozwalane po kilku podkatalogach, trzeba wtedy powskazywać wszystkie katalogi z "inkludami" (opcją -I programu "makeinfo" - zupełnie jak w przypadku gcc). Najpierw sobie usunę wszystkie pliki sed.info*, potem uruchamiam "makeinfo --no-split sed.texi". Poszło bez zająknięcia. Faktycznie, powstał jeden duży plik sed.info. Czyli podczas budowania pakietu rpm też się to uda, mam nadzieję.
A, jeszcze coś - oprócz stron info w katalogu widać też tajemniczy plik "dir" - jest to indeks stron. On też nie ma prawa znaleźć się w pakiecie.
Jeszcze jedna sprawa - po zainstalowaniu stron info trzeba je będzie zarejestrować w "globalnym", systemowym indeksie stron info (dlatego ten indeksik z seda nie może zostać zainstalowany razem z pakietem - bo by nadpisał (uszkodził) systemowy indeks - systemowy indeks zawiera spis wszystkich stron info w systemie, a ten tutaj zawiera tylko te strony, które zastał w "fake root" - czyli tylko stronę seda). Zarejestrowanie będzie wymagało dodania sekcji %post. Oczywiście do kompletu potrzebne będzie ew. wyrejestrowanie przy odinstalowywaniu, czyli sekcja %preun.
5. Pisanie speca. Zaczynam ze szkieletem i po uzupełnieniu mam takie coś:
Summary: Edytor strumieniowy o imponujących możliwościach Name: sed Version: 4.0.7 Release: 1 License: GPL Group: Tekst Source: %{name}-%{version}.tar.gz BuildRoot: /var/tmp/%{name}-%{version} BuildRequires: /usr/bin/makeinfo PreReq: /sbin/install-info %description SED jest edytorem strumieniowym. Edytor strumieniowy przekształca tekst przyjmowany poprzez strumień, pobieranego z rurki FIFO lub pliku. Choć w pewnym stopniu przypomina to pracę edytorów skryptowalnych (jak np. ED), SED wykonuje swoje zadanie w jednym przebiegu i w związku z tym jest znacznie wydajniejszy. Ale to zdolność do filtrowania tekstu z FIFO wyróżnia SED-a na tle innych edytorów. %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir} %prep %setup -q %build %configure --disable-dependency-tracking --disable-nls make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} cd %{buildroot}%{_infodir} rm -rf sed.info* dir makeinfo --no-split %{_builddir}/%{buildsubdir}/doc/sed.texi -I %{_builddir}/%{buildsubdir}/doc %files %defattr(0644,root,root,0755) %attr(0755,root,root)%{_bindir}/* %{_mandir}/man*/*.gz %{_infodir}/*.gz %post install-info %{_infodir}/sed.info.gz %{_infodir}/dir %preun install-info --delete %{_infodir}/sed.info.gz %{_infodir}/dir
Objaśnień będzie tym razem całkiem sporo potrzebne. W preambule doszły dwa tagi: PreReq: i BuildRequires:. Przy budowie pakietu potrzebne jest "makeinfo" (by zregenerować strony info), a z kolei do instalacji potrzebne jest install-info (by zarejestrować strony info w indeksie /usr/share/dir).
Tutaj można rozważać, czy lepiej jest użyć w zależności nazwy programu (koniecznie ze ścieżką!), czy też może nazwy pakietu z którego to coś pochodzi. Ja jestem za nazwą programu jednak, bo co prawda i makeinfo, i install-info pochodzą z pakietu texinfo, ale widywałem już dystrybucje w których pakiet "texinfo" nie istniał, bo był porozbijany na mniejsze. Dlatego lepiej jest IMO wymagać konkretnego programu niż jakiegoś pakietu o zadanej nazwie. Bo pakiet może się nazywać w jakiejś distro inaczej, ale przecież nikt nie zmieni nazwy binarki "install-info". Prawda?
Zmieniłem sekcję %clean! Wcześniej odwoływałem się do katalogu z rozpakowanymi źródłami poprzez %{_builddir}/%{name}-%{version}. Co było całkiem ładne, tyle że gdy katalog nazywał się inaczej, według innego schematu, to trzeba było to poprawiać. Ale przypadkiem wertowałem sobie pliki źródłowe rpm-a, i "odkryłem" że makro %setup ustawia pewną zmienną tak, by zawierała nazwę katalogu do którego wypakowano źródła. A zmienna ta to właśnie %{buildsubdir}. Czyli zamiast kombinować z %{name}-%{version} mogę tu po prostu wpisać %{buildsubdir}. Łał. Teraz nawet jeśli za pomocą opcji "-n" makra %setup zmienię nazwę katalogu ze źródłami, to nie muszę się martwić o sekcję %clean - bo ona się automatycznie dostosuje.
Sekcję %build sobie darujmy, za to sekcja %install jest ciekawa. Co też takiego się tutaj dzieje... po pierwsze, instaluję pliki. Potem wchodzę do katalogu /var/tmp/sed-4.0.7/usr/share/info/, i usuwam wszystkie pliki "sed.info*" oraz plik indeksu "dir". Potem wywołuję "makeinfo --nosplit", jako argument biorąc plik doc/sed.texi z katalogu ze źródłami (znowu używam nowo odkrytej zmiennej %{buildsubdir} :)
Ponieważ jednak jestem w katalogu innym niż źródła texinfo, to na wszelki wypadek podaję tamten "oryginalny" katalog na liście inkludów za pomocą opcji "-I" - na wszelki wypadek.
Sekcja %files chyba nie wymaga tutaj objaśnień, za to doszły nowe sekcje %post i %preun. Wywołują "install-info", z parametrami którymi są strona info do zarejestrowania i plik indeksu, w którym należy to coś zarejestrować. Proste, wystarczy zajrzeć do manuala install-info.
6. Budowanie pakietu. Wpisuję "rpmbuild -ba --rmsource sed.spec", dostaję zbudowane pakiety binarny i źródłowy, źródła z ~/rpm/SOURCES/ zostają usunięte. Sukces na całej linii. Czuję się dobry :)
Gentoo to niezły filemanager, bardzo rozbudowany i posiadający wiele interesujących funkcji. Ma też oczywiście swoje braki a od jakiegoś czasu jego rozwój stoi w miejscu...
1. Konfiguracja i testowa kompilacja. Program ma skrypt ./configure, więc nie powinno być problemów. Wstępna konfiguracja przechodzi dobrze, ale po "make" widzę ostrzeżenia odnośnie wersji "automake" - skrypty gentoo szukają automake w wersji 1.6, podczas gdy ja mam już 1.7. Nic to, przerywam budowanie, uruchamiam "autoreconf" i jeszcze raz ./configure. Teraz "make" przechodzi już gładko.
2. Testowa instalacja. "make install DESTDIR=/shm" znowu się udaje. To zaczyna być nudne. Oglądam pliki, jakie wejdą w skład pakietu:
usr/
|-bin/
| \-gentoo
|-etc/
| |-gentoorc
| \-gentoogtkrc
\-share/
\-gentoo/
\-icons/
|-AbiWord.xpm
|-Amiga.xpm
|-Animation.xpm
|-Apple.xpm
|-...
Binarka, dwa pliki konfiguracyjne, jeden katalog z mnóstwem ikonek. Nic specjalnego, ale te pliki konfiguracyjne mnie zastanawiają, tzn. nie wiem, gdzie je umieścić. Nie chcę ich wrzucać bezpośrednio do /etc/...
OK, rozmieszczę to w obrębie /usr/X11R6/. W ten sposób binarka pójdzie do /usr/X11R6/bin/, ikonki do /usr/X11R6/lib/X11/gentoo/, a pliki konfiguracyjne do /etc/X11/gentoo/.
3. Plik .spec.
%define _prefix /usr/X11R6 %define _sysconfdir /etc/X11/gentoo %define _datadir /usr/X11R6/lib/X11 Summary: Dwupanelowy filemanager napisany w Gtk+ Name: gentoo Version: 0.11.34 Release: 1 License: GPL Group: Pliki Source: %{name}-%{version}.tar.gz BuildRoot: /var/tmp/%{name}-%{version} %description Gentoo to zaawansowany, dwupanelowy filemanager napisany w Gtk+. Jego wygląd i funkcjonalność przypominają nieco amigowy program Directory Opus, choć gentoo nie jest tak rozbudowany. %prep %setup -q autoreconf %build %configure --disable-nls --disable-dependency-tracking make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} %files %defattr(0644,root,root,0755) %attr(0755,root,root)%{_bindir}/* %config(noreplace) %{_sysconfdir} %{_datadir}/gentoo %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir}
OK, a teraz wyjaśnienia:
Przede wszystkim na szczycie speca pojawiły się 3 linijki %define. Redefiniuję tutaj domyślne wartości niektórych zmiennych, w związku z planowanym przeniesieniem gentoo z /usr/ do /usr/X11R6/. Z listy plików jakie gentoo chce instalować wiem, że muszę zmienić 3 katalogi: katalog z binarkami (%{_bindir}), katalog z konfiguracją (%{_sysconfdir}) oraz katalog na różne dane (/usr/share, czyli %{_datadir}).
Najpierw redefiniuję zmienną %{_prefix}. Co prawda nie musiałem tego robić, ale na wszelki wypadek wolę mieć prawidłowo ustawiony prefiks. Nie muszę dzięki temu zmieniać wartości %{_bindir}, bo w ~/.rpmmacros i tak znajduje się definicja %{_bindir}==%{_prefix}/bin. Zmieniając prefiks zmieniłem od razu %{_bindir}.
Teraz kolej na przeniesienie konfiguracji. Udumałem sobie, że wepchnę od razu te dwa pliki konfiguracyjne do specjalnego podkatalogu o nazwie "gentoo". Żeby ułatwić sobie robotę, definiuję %{_sysconfdir} tak, by wskazywała od razu na ten wymyślony katalog.
W trzeciej linii %define ustawiam katalog %{_datadir}. To w nim wyląduje katalog gentoo/ zawierający mnóstwo ikonek. Ustawiam go na nieco dziwną wartość, ale to dlatego że w /usr/X11R6 nie używa się katalogu share/. Mi to w sumie obojętne, bo i tak nie mam zamiaru tam zaglądać. Ale tak jest "bardziej poprawnie" ;)
Następnie standardowa preambuła, w sekcji %prep dodałem dodatkowo uruchomienie programu "autoreconf" w celu odświeżenia całej machiny ./configure.
Sekcja %files też jest standardowa. Odwołuję się do katalogów poprzez makra - teraz widać bardzo wyraźnie, że mimo iż zmieniłem całe położenie programu to w zasadzie niewiele się zmieniło - doszły tylko te %define na szczycie pliku.
W zasadzie nie ma co tutaj już opisywać, na tym etapie artykułu tak prosty (he he :) plik .spec powinien być w miarę czytelny.
4. Budowanie pakietu. "rpmbuild -bb gentoo.spec" posz-ło! Po chwili kompilowania mam gotowy pakiet. Trochę nieufnie go oglądam, ale wygląda na to, że faktycznie się udało za pierwszym podejściem. Hmm, dziwne. No dobra, trzeba zainstalować...
Przy pierwszym uruchomieniu zauważam, że coś jest nie tak - nie ma ikonek przy plikach. Hmm. Czyżby prawa dostępu? Sprawdzam, czy mogę odczytać pliki w /usr/X11R6/lib/X11/gentoo/ - mogę. Więc to nie jest kwestia uprawnień. Po chwili grzebania w opcjach gentoo znajduję winowajcę - gentoo ma "zaszytą" w domyślnej konfiguracji błędną ścieżkę do ikonek, obecnie wskazuje ona na /usr/X11R6/share/gentoo/icons/. Wygląda na to, że gentoo przy generowaniu tej konfiguracji po prostu wzięło $prefix i dokleiło do niego "share/gentoo/icons". To nie moja wina, to skrypty instalujące gentoo są błędne. Zamiast $prefix powinny brać pod uwagę $datadir. No dobra, nieważne. Poprawiam ścieżkę w opcjach gentoo - i wszystko zaczyna działać.
Gdyby to miał być pakiet w jakiejś dystrybucji, to należałoby znaleźć ten błędny kawałek w konfiguracji gentoo i poprawić go - w końcu pakiet powinien działać od razu po zainstalowaniu, prawda? Ale ja nie robię dla żadnej dystrybucji, więc mnie to nie interesuje :)
Podpowiem tylko, że tutaj akurat należałoby zmienić plik "gentoorc.in", linię 2249. Wygląda ona tak:
<path>"@prefix@/share/@PACKAGE@/icons/"</path>
a powinna wyglądać tak:
<path>"@datadir@/@PACKAGE@/icons/"</path>
Po tej małej poprawce wszystko powinno działać "od razu". Ciekawy jest sposób nanoszenia poprawki: Najprościej by było po prostu zmienić bezpośrednio źródła, ale z wielu powodów nie robi się tego w ten sposób. "Oficjalny" (i w sumie najrozsądniejszy) sposób to stworzenie odpowiedniej łatki (patcha) który by skorygował wadliwy plik/pliki. Takie patche załącza się do pakietu za pomocą tagów "PatchX:" w preambule i aplikuje makrami "%patchX" w sekcji %prep. Ale o tworzeniu drobnych patchy wspomnę nieco później.
Teraz przejdę do nieco innego, mniej typowego zastosowania. Najpierw nakreślę nieco tła: otóż są pewne programy, które aktualizuję sobie poprzez CVS. Z różnych powodów, te są teraz nieistotne. Jeśli ktoś używa oprogramowania z CVS, to zwykle kompiluje je nieco częściej niż ludzie używający tego softu z "normalnych" paczek. I byłoby bardzo fajnie, gdyby cały ten proces dało się jakoś zautomatyzować. I niespodzianka - da się :)
Tutaj do gry wchodzi RPM - jak zwykle chodzi o skompilowanie kodu i złożenie gotowej do instalacji paczki. W zasadzie to prawie nic się nie zmienia w porównaniu ze "zwykłą" paczką RPM, ale myślę że taki przykład będzie dosyć użyteczny. Dobra, padło na "mc". Więc do rzeczy:
1. Źródła. Mam sobie w katalogu domowym taki katalog CVS/. I w katalogu tym mam różne takie inne podkatalogi, w których siedzą sobie różne projekty pobierane regularnie z sieci. Jednym z tych projektów jest mc. Szczegóły dotyczące pobierania można sobie oszczędzić, procedura jest identyczna w przypadku wszystkich danych z CVS.
A co jest ważne? No cóż, na pewno mam katalog ~/CVS/mc/. Zawierający źródła. Więc warto się im przyjrzeć. Najpierw kopiuję sobie ~/CVS/mc/ do /shm/, potrafię być bardzo nieostrożny a nie chcę sobie uszkodzić oryginalnych źródeł. Więc oglądam kopię... i wygląda normalnie. Brak skrypty ./configure, ale to normalne dla CVS. Zamiast tego jest ./autogen.sh, którego zadaniem jest stworzenie skryptu ./configure. Uruchamiam ./autogen.sh i mam tylko cichą nadzieję, że nie wypluje zbyt wielu błędów :)
...autogen.sh działa, i działa, i działa... zupełnie jak króliczki Energizera. Na dodatek uruchomił od razu stworzone przez siebie ./configure Ech. Wreszcie skończył.
Pierwsze co robię, to przeglądam autogen.sh czy nie znajdę wewnątrz jakiegoś
przełącznika który może mi posłużyć do wyłączenia tego automatycznego
uruchamiania ./configure. Czasem się taki znajdzie. Ale nie tym razem. No
dobra, pół biedy. Na szczęście parametry przekazane skryptowi ./autogen.sh
zostaną podane skryptowi ./configure, więc nie jest tak źle. Po prostu nie
chciałbym, żeby autogen.sh uruchomił ./configure na darmo i żebym musiał
potem to samemu jeszcze raz uruchamiać. Przebieg ./configure dosyć długo
trwa. Wiem, że to pewnie tylko mój problem, ale...
...o czym to ja mówiłem? Aha, już wiem:
2. Próbna instalacja. No więc jest ten skrypt ./configure. Po obejrzeniu "./configure --help" mam już zestaw swoich ulubionych opcji:
./configure --prefix=/usr --without-edit --without-ext2undel \ --without-gpm-mouse --without-mcfs --without-samba --with-screen=ncurses \ --disable-nls --disable-dependency-tracking
Opcje są łatwe do wyjaśnienia: najpierw wyłączam wbudowany edytor, bo i tak używam vima. Potem wyłączam dla pewności ext2undel, bo i tak używam XFS. Wyłączam, co ciekawe, obsługę myszy - bo nie będzie mi w mc potrzebne klikanie na przyciski ani przeciąganie przycisków. A zaznaczanie tekstu oraz wklejanie i tak są od tego niezależne.
Wyłączam też mcfs oraz sambę, bo nie mam zamiaru z tego korzystać. Wymuszam używanie ncurses zamiast libslang (nie lubię libslang :)
I na koniec, tradycyjnie już, wyłączam wsparcie dla polskich tłumaczeń i oszczędzam nieco czasu kompilacji na --disable-dependency-tracking. Te wszystkie flagi będę potem musiał przekazać skryptowi ./autogen.sh.
Uruchomienie "make" się udaje, podobnie instalacja z DESTDIR=/shm.
Od razu oglądam listę plików - nic specjalnego:
usr/
|-bin/
| |-mc
| |-mcmfmt
| \-mcview
|-lib/
| \-mc/
| \-cons.saver
|-man/
| |-man1/
| | |-mc.1
| | |-mcedit.1
| | \-mcview.1
| \-man8/
|-sbin/
\-share/
\-mc/
|-cedit.menu
|-edit.indent.rc
|-edit.spell.rc
|-mc.ext
|-mc.hint
|-mc.hint.cs
|-mc.hint.es
|-mc.hint.hu
|-mc.hint.it
|-mc.hint.nl
|-mc.hint.pl
|-mc.hint.ru
|-mc.hint.uk
|-mc.hint.zh
|-mc.hlp
|-mc.lib
|-mc.menu
|-syntax/
| |-Syntax
| |-ada95.syntax
| |-c.syntax
| |-changelog.syntax
| |-cs.syntax
| |-diff.syntax
| |-dos.syntax
| |-fortran.syntax
| |-html.syntax
| |-java.syntax
| |-js.syntax
| |-latex.syntax
| |-lisp.syntax
| |-lsm.syntax
| |-m4.syntax
| |-mail.syntax
| |-makefile.syntax
| |-ml.syntax
| |-nroff.syntax
| |-octave.syntax
| |-pascal.syntax
| |-perl.syntax
| |-php.syntax
| |-po.syntax
| |-python.syntax
| |-sh.syntax
| |-slang.syntax
| |-smalltalk.syntax
| |-spec.syntax
| |-sql.syntax
| |-swig.syntax
| |-syntax.syntax
| |-tcl.syntax
| |-texinfo.syntax
| |-unknown.syntax
| \-xml.syntax
|-extfs/
| |-README
| |-a
| |-apt
| |-audio
| |-bpp
| |-deb
| |-deba
| |-debd
| |-dpkg
| |-extfs.ini
| |-hp48
| |-iso9660
| |-lslR
| |-mailfs
| |-patchfs
| |-rpm
| |-rpms
| |-sfs.ini
| |-trpm
| |-uar
| |-uarj
| |-uha
| |-ulha
| |-urar
| |-uzip
| \-uzoo
|-bin/
| |-mc-wrapper.csh
| |-mc-wrapper.sh
| |-mc.csh
| \-mc.sh
\-term/
|-README.xterm
|-ansi.ti
|-linux.ti
|-vt100.ti
|-xterm.ad
|-xterm.tcap
\-xterm.ti
Binarki, manuale, wykonywalny plik cons.saver w usr/lib/mc/. O dziwo, zainstalowały się też puste katalogi usr/sbin/ i usr/man/man8/. Pewnie jakiś błąd w skrypcie instalacyjnym. Dobra, tym akurat nie muszę sobie głowy zaprzątać - rpm powinien zignorować pusty katalog jeśli go pominę w pliku .spec. Interesujący za to jest katalog usr/share/mc/. Jest tam trochę luźnych plików... widzę pliki mc.hint.*, które zawierają "porady" mc w różnych językach. Te pliki usunę z wynikowego pakietu, nie będą mi potrzebne. Zachowam sobie jedynie anglojęzyczny "mc.hint". Katalogi syntax/ i term/... hmm, term/ będzie mi niepotrzebny, on zawiera różne pliki których można użyć do połatania baz terminali w systemie, ale ja nie mam problemów z terminalami, więc i tak bym niczego nie musiał poprawiać. Więc term/ nie trafi do końcowego pakietu. syntax/ też mi nie jest potrzebny - przecież ja kompiluję mc bez wbudowanego edytora, więc podświetlanie składni jest mi niepotrzebne. Od razu odpadają też związane z edycją pliki łapiące się na wzorzec "*edit*"
Katalog usr/share/mc/bin/ zawiera jakieś owijki dla sh/csh - nigdy nie używałem i nie mam zamiaru zacząć. Więc też wypada.
Po tych operacjach zostaje mi tylko kilka plików oraz podkatalog extfs/. Ten jest jednak potrzebny, bo zawiera skrypty obsługujące różne wirtualne filesystemy. Są to np. skrypty, które pozwalają "zajrzeć" do pliku .rpm. To musi zostać. Trochę kłopotliwe będzie nadawanie praw do plików - wszystkie pliki w tym katalogu będą musiały być wykonywalne, oprócz pliku README i dwóch plików *.ini. To oznacza, że nie będę mógł po prostu "globalnie" załatwić tego katalogu jedną linijką w sekcji %files. Ale tym to się później zajmę.
3. Plik spec.
Summary: Midnight Commander Name: mc Version: %(date +%Y%m%d) Release: 1 License: GPL Group: Pliki BuildRoot: /var/tmp/%{name}-%{version} %description Midnight Commander to rozbudowany filemanager, pracujący w trybie tekstowym. Można go obsługiwać myszą, radzi sobie z połączeniami FTP, potrafi też podglądać różne rodzaje wirtualnych systemów plików, jak np. paczki RPM czy archiwa TAR. Poza tym, jest bardzo konfigurowalny. O jego jakości najlepiej mówi chyba fakt, że jak do tej pory nie wymyślono w tej klasie programów nic lepszego dla trybu tekstowego. %prep %setup -q -c -T cp -a /home/grzegorz/CVS/mc/* . ./autogen.sh --without-edit --without-ext2undel --without-gpm-mouse \ --with-subshell --without-mcfs --without-samba --with-screen=ncurses \ --disable-nls --disable-dependency-tracking --prefix=%{_prefix} \ --mandir=%{_mandir} --libdir=%{_libdir} --datadir=%{_datadir} \ --bindir=%{_bindir} %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} rm -rf %{buildroot}%{_datadir}/mc/{bin,syntax,term,*edit*,mc.hint.*} %files %defattr(0644, root, root, 0755) %attr(0755,root,root) %{_bindir}/* %{_mandir}/man*/*.gz %attr(0755,root,root) %{_libdir}/mc %dir %{_datadir}/mc %{_datadir}/mc/mc.* %dir %{_datadir}/mc/extfs %{_datadir}/mc/extfs/README %{_datadir}/mc/extfs/*.ini %attr(0755,root,root) %{_datadir}/mc/extfs/a* %attr(0755,root,root) %{_datadir}/mc/extfs/b* %attr(0755,root,root) %{_datadir}/mc/extfs/d* %attr(0755,root,root) %{_datadir}/mc/extfs/h* %attr(0755,root,root) %{_datadir}/mc/extfs/l* %attr(0755,root,root) %{_datadir}/mc/extfs/m* %attr(0755,root,root) %{_datadir}/mc/extfs/p* %attr(0755,root,root) %{_datadir}/mc/extfs/r* %attr(0755,root,root) %{_datadir}/mc/extfs/t* %attr(0755,root,root) %{_datadir}/mc/extfs/u* %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir}
Uff. I znowu parę rzeczy do wyjaśnienia: Najpierw ta konstrukcja w tagu "Version:":
Version: %(date +%Y%m%d)
Nie używam tutaj stałego numeru wersji, bo pakiet będzie zawierał program z CVS. Jednak jakiś numerek powinienem wstawić. W przypadku programów z CVS zwykle jako wersję podaję datę, dzień z którego pochodzą źródła (lub dzień zbudowania pakietu, to zwykle to samo). Lubię używać daty w formacie rok-miesiąc-dzień, tyle że bez łączników. Czyli np. '20030428' na oznaczenie '28 kwietnia 2003r'. Teraz mógłbym oczywiście po prostu wpisać ten numerek na stałe i zmieniać go przy każdej kolejnej budowie pakietu, ale to by było męczące. Mogę zmusić do tego maszynę... Używam do tego polecenia "date", które może zwrócić datę w "moim" formacie, jeśli wywołać je jako "date +%Y%m%d". Teraz trzeba tylko zmusić plik .spec do pobierania wyjścia tego polecenia. A robi się to w plikach spec konstrukcją %(...) - zachowuje się ona podobnie do shellowego $(...) lub `...` - zawartość wyrażenia zostanie wykonana, a to, co zostanie zwrócone zostanie podstawione zamiast %(...). Jest to prosty sposób na umieszczanie w plikach .spec treści, które mają być generowane przez zewnętrzne programy. Podsumowując: dzięki temu %(date +%Y%m%d) będę miał automagicznie ustawiany tag Version: na zakodowaną datę bieżącego dnia.
Następna warta opisu linijka to występujące w sekcji %prep makro:
%setup -q -c -T
Zgodnie z tym, co napisałem wcześniej przy opisie %setup, makro to będzie się zachowywało następująco: będzie siedziało cicho, założy katalog o nazwie %{name}-%{version}/ i wejdzie do niego, ale pominie rozpakowywanie źródeł. O, właśnie - w preambule w ogóle nie wymieniłem tagu Source: - prawie zapomniałem o tym powiedzieć. Skoro nie korzystam z żadnej paczki ze źródłami, to nie muszę tego robić. Więc nie robię. Bo źródła wezmę z innego miejsca - z katalogu ~/CVS/mc/, gdzie mam kopię repozytorium CVS Midnight Commandera. A zrobi to linijka:
cp -a /home/grzegorz/CVS/mc/* .
Jej działanie jest proste: skoro jesteśmy w katalogu ~/rpm/BUILD/%{name}-%{version}/, to wystarczy przekopiować tutaj zawartość repozytorium. Jedno wywołanie "cp" i po wszystkim.
Potem następuje wywołanie skryptu autogen.sh, który wygeneruje plik ./configure. autogen.sh gdy tylko wykona swoje zadanie uruchomi skrypt ./configure i przekaże mu wszystkie parametry jakie sam otrzymał, więc ja daję mu od razu wszystkie te opcje, które otrzymać ma ./configure. Ponieważ nie używam makra %configure, muszę samodzielnie zadbać o przekazanie zmiennych konfigurujących potrzebne katalogi.
W sekcji %install jedynym dodatkiem jest linijka
rm -rf %{buildroot}%{_datadir}/mc/{bin,syntax,term,*edit*,mc.hint.*}
Jest to polecenie usunięcia z zainstalowanych plików tych wszystkich rzeczy w usr/share/mc/, o których mówiłem że nie będą mi potrzebne. Żeby uniknąć wpisywania wielu linijek i podawania wszędzie tych samych katalogów połączyłem wszystko w jedno wywołanie, polegając na mechanizmie "brace expansion". Trzeba sobie ułatwiać życie...
Ostatnie co muszę omówić to sekcja %install. Na początku są normalne wpisy dotyczące binarek i manuali. Potem jest wpis katalogu usr/lib/mc/ (wraz z zawartością) - tam leży binarka cons.saver, więc dlatego nadaję bit wykonywalności. Następnie załączam sam katalog usr/share/mc/, potem wszystkie pliki mc.* z jego wnętrza. Po tym zabiegu w tym katalogu do załączenia pozostaje mi tylko podkatalog extfs/. Więc załączam i jego, a potem w dwóch linijkach jego pliki *.info i README. Pozostały mi te skrypty z jego wnętrza, muszą być wykonywalne. Tutaj zauważam, że mogę je wychwycić po ich pierwszych literach (te pliki *.info i README nie załapią się, bo szczęśliwym zbiegiem okoliczności mają "unikatowe" pierwsze litery nazw) - co też robię. Staram się na jak najszerszą skalę używać globbingu ("*", niestety w sekcji %files nie mogę użyć "brace expansion" bo nie jest interpretowana przez shell, tylko przez samego rpm-a), to pozwoli mi zamortyzować ew. pojawienie się nowych skryptów w przyszłych wersjach. Przy odrobinie szczęścia. Jednocześnie konstrukcja którą stworzyłem jest na tyle precyzyjna, by zareagować na pojawienie się nowych plików .info lub pojawienie się konfliktu (rpmbuild mnie wtedy powiadomi o podwójnie opisanych wpisach w %files).
Uwaga końcowa: nie musiałem się tak męczyć. Mogłem "odgórnie" walnąć plikom w /usr/share/mc/ prawa do wykonywania, co mnie obchodzi, że trzy z nich będą niepotrzebnie wykonywalne. Mogłem też wyluzować w inny sposób - mc zainstalował te pliki w 100% poprawnie, tzn. te trzy pliki nie miały bitu '+x' - więc ja mogłem poinstruować rpm-a, by przejął zastane atrybuty i nie forsował własnych.
%attr(-,root,root) %{_datadir}/mc
Tak, tak też mogłem zrobić. I byłoby to bardzo ładne rozwiązanie, dużo bardziej elastyczne od tego, które pokazałem w pliku .spec, bo zastąpiłoby ono linijki
%dir %{_datadir}/mc
%{_datadir}/mc/mc.*
%dir %{_datadir}/mc/extfs
%{_datadir}/mc/extfs/README
%{_datadir}/mc/extfs/*.ini
%attr(0755,root,root) %{_datadir}/mc/extfs/a*
%attr(0755,root,root) %{_datadir}/mc/extfs/b*
%attr(0755,root,root) %{_datadir}/mc/extfs/d*
%attr(0755,root,root) %{_datadir}/mc/extfs/h*
%attr(0755,root,root) %{_datadir}/mc/extfs/l*
%attr(0755,root,root) %{_datadir}/mc/extfs/m*
%attr(0755,root,root) %{_datadir}/mc/extfs/p*
%attr(0755,root,root) %{_datadir}/mc/extfs/r*
%attr(0755,root,root) %{_datadir}/mc/extfs/t*
%attr(0755,root,root) %{_datadir}/mc/extfs/u*
Ale pociąga ono za sobą jedno pytanie - czy ufasz instalatorowi programu? Jesteś pewien, że nałoży zawsze odpowiednie prawa? Akurat w tym przypadku nawet gdyby coś zepsuł, to nie miałoby to wielkiego wpływu na system czy pakiet, bo "przymknąłbym oko" tylko na jeden odosobniony katalog. Ale ja generalnie staram się uniknąć pozwalania pakietowi na zbyt wiele bez mojej kontroli. Wolę mieć rękę na pulsie, nawet jeśli oznacza to dopisanie dodatkowych linijek w sekcji %files.
4. Budowanie pakietu. Co tu dużo gadać - udało się za pierwszym razem
:)
Aha, jeszcze coś - w przypadku "mc" warto zwrócić uwagę na plik
cons.saver. Jest on używany do "zapamiętywania" zawartości terminala przy
przełączaniu ekranów za pomocą C-o lub wykonywaniu zewnętrznych poleceń. Do
tego celu musi mieć dostęp do niektórych plików odwzorowania terminali
w katalogu /dev/. Na niektórych systemach pliki te mają bardzo ostro okrojone
prawa dostępu i cons.saver musi mieć odpowiedni
suid, a jeszcze lepiej sgid i grupę by się do tych plików dobrać. Jeśli twój
mc zawiesza się przy przełączaniu C-o, uruchamianiu zewnętrznych poleceń lub
nawet jeszcze w trakcie startu, to na 90% twój cons.saver potrzebuje
dodatkowych uprawnień.
Nie mogę tutaj pominąć Najlepszego Z Edytorów, więc Vim też musi zostać spakietowany :)
1. Źródła Normalne, z CVS. Zakładam, że lokalnie leżą w katalogu ~/CVS/vim
2. Próbna instalacja Vim z CVS posiada "od razu" skrypt ./configure, więc odpada jego generowanie. Decyduję się na opcje:
./configure --disable-multibyte --disable-hangulinput --enable-gui=no \ --without-gnome --disable-acl --with-tlib=tinfo --disable-gpm \ --disable-nls --without-x --with-compiledby=Hoppke --prefix=/usr
Czyli rezygnuję z obsługi unikodu i języków azjatyckich, wyłączam gui (niedawno przestałem używać gvima, bo doszedłem do wniosku, że zwykły vim w xtermie mi wystarcza. Tak, wiem, dziwak ze mnie. Ale ja naprawdę nie korzystałem z żadnej z funkcji GUI - nie klikałem na ikony na pasku narzędzi, zamiast wybierać "File-<Save" robiłem ":w", itp. GUI było mi i tak niepotrzebne, więc...
Wyłączam też obsługę acl-i (mam XFS, ale nie używam acl-i - więc dla pewności nie pozwolę vim-owi nawet spróbować ich użyć (gdyby kiedyś, omyłkowo zadecydował że jednak mu wolno))
Bibliotekę ncurses mam skompilowaną w dwóch częściach - jako właściwą bibliotekę libncurses i bibliotekę odpytywania terminali libtinfo. Vim domyślnie próbowałby się skompilować w oparciu o "pełną" wersję libncurses, ale tak naprawdę do szczęścia wystarczy mu sama libtinfo. Dlatego też jako biblioteki operującej na terminalu każę mu użyć libtinfo.
Potem jeszcze wyłączam wsparcie dla X-ów i gpm, wyłączam "lokale", no i oczywiście zaznaczam kto też tak okaleczył Vima ;) (to ostatnie będzie widać np. po "vim --version")
"make install DESTDIR=/shm" udaje się, widzę tam potem taką strukturę katalogów (pliki pomijam, zbyt wiele by ich było):
usr/
|-bin/
|-man/
| \-man1/
\-share/
\-vim/
\-vim62b/
|-colors/
|-compiler/
|-doc/
|-ftplugin/
|-indent/
|-keymap/
|-lang/
|-plugin/
|-syntax/
|-tools/
|-tutor/
\-macros/
|-hanoi/
|-life/
|-maze/
\-urm/
Katalog usr/bin/ zawiera główne binarki i parę symlinków do nich. Trzeba będzie tam dodać symlink vim->vi, ze względu na moje lenistwo itd.
Dodatkowo usunąć będę musiał binarkę vimtutor, bo tutorial mi już nie będzie potrzebny.
Strony manuala pozostaną nienaruszone. A, nie, moment - jest tutaj strona manuala dla vimtutor, ją też będę musiał usunąć (skoro usuwam vimtutor, to po co mi manual do niego?)
Katalog usr/share/vim/vim62b/ zawiera różne luźne pliki i podkatalogi. Praktycznie wszystko tutaj jest potrzebne vimowi do pracy, ale widzę że będę mógł wywalić katalog lang/, bo zawiera tylko i wyłącznie pliki związane z menu w różnych językach (a ja nie używam tak czy siak NLS). Taki sam los czeka katalog tutor/. Oglądam też zawartość katalogu tools/... nie, on zawiera tylko jakieś głupie dodatki, też mi się nie przyda. Hmm, to samo chyba mogę powiedzieć i o katalogu keymap/ - on i tak nie będzie mi potrzebny do normalnego pisania tekstów. Reszta katalogów musi pozostać - doc/ zawiera pliki dla "online help" Vima, pozostałe zawierają schematy kolorów, wtyczki dla poszczególnych typów plików, reguły kolorowania składni itp. Jedyny katalog który można by jeszcze w miarę bezpiecznie usunąć, to macros/. Ale ja go sobie zachowam, może mi się przydać jako zbiór przykładów gdy samemu zechcę skrobnąć kilka poważniejszych makr.
3. Plik .spec
Summary: Edytor programisty "Vi Improved" Name: vim Version: %(date +%Y%m%d) Release: 1 License: GPL Group: Edytory BuildRoot: /var/tmp/%{name}-%{version} %description Vim jest jednym z najlepszych edytorów na świecie (jeśli nie najlepszym). Jest szybki, konfigurowalny, skryptowalny i w dużej mierze kompatybilny z najpopularniejszym w świecie Unices edytorem "Vi" %prep %setup -q -c -T cp -a /home/grzegorz/CVS/vim/* . %build %configure --disable-multibyte --disable-hangulinput --enable-gui=no \ --without-gnome --disable-acl --with-tlib=tinfo --disable-gpm \ --disable-nls --without-x --with-compiledby=Hoppke make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} cd %{buildroot}%{_bindir} ln -s vim vi rm vimtutor rm %{buildroot}%{_mandir}/man1/vimtutor.1 cd %{buildroot}%{_datadir}/vim/vim* rm -rf lang tools tutor keymap %files %defattr(0644,root,root,0755) %attr(0755,root,root)%{_bindir}/* %{_mandir}/man*/*.gz %{_datadir}/vim %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir}
Preambuły nie trzeba opisywać, tag z wersją jest generowany identycznie jak w przypadku Midnight Commandera. Sekcja %prep też jest identyczna jak i poprzednio. Jedyna "nowość" dotyczy sekcji %install, gdzie po zainstalowaniu plików najpierw wchodzę do katalogu bin/, tworzę tam symlink od vi do vim oraz usuwam vimtutor. Potem usuwam jeszcze jego stronę manuala i te podkatalogi: lang/ tools/ tutor/ keymap/. Tutaj może tylko pokażę, w jaki sposób wchodzę do katalogu usr/share/vim/vim62b/ - załatwia to linijka:
cd %{buildroot}%{_datadir}/vim/vim*
Dzięki tej gwiazdce nie muszę używać numeru wersji vim-a. Bo prawdę mówiąc, to ja tego numeru nie znam. Teraz gdy np. z wersji 6.2beta vim przeskoczy na "pełną" 6.2, to ja nie będę musiał zmieniać pliku .spec. Ot, drobnostka, ale takie właśnie drobnostki ułatwiają życie.
Sekcja %files jest banalna, standardowe linijki dla binarek i manuali. I jedna dodatkowa załączająca /usr/share/vim/ wraz z zawartością. W porównaniu z Midnight Commanderem tym razem poszło wyjątkowo łatwo, a to dlatego, że nie trzeba było kombinować z prawami dostępu.
4. Budowanie pakietu udaje się za pierwszym podejściem. Ha! :)
ELinks to "najlepsza, psiakrew, przeglądarka" wśród przeglądarek tekstowych. Zalety zaletami, mnie interesuje tylko spakietowanie tego programu.
1. Źródła leżą w ~/CVS/elinks/. A przynajmniej tak tutaj zakładam :)
2. Próbna instalacja nie powinna być trudna. Co prawda po pobraniu z CVS repozytorium ELinksa nie zawiera skryptu ./configure, ale załączony ./autogen.sh go wygeneruje. Czyli sytuacja podobna do MC, z tym że tutaj nie uruchamia automatycznie . Po przejrzeniu dostępnych opcji mam już swój zestaw:
./configure --disable-dependency-tracking --disable-ipv6 --disable-nls \ --disable-mailcap --enable-fastmem --without-xbel --without-lua --prefix=/usr
Czyli: wyłączenie ustawicznego śledzenia zależności w źródłach (przydaje się w sumie tylko gdy skompiluje się kod, uruchomi ./configure z innymi opcjami, i jeszcze raz będzie kompilować źródła, bez "make clean". W przypadku budowania pakietu rpm do takich sytuacji nigdy nie dochodzi.
Potem wyłączam jeszcze obsługę ipv6 (nie używam), obsługę innych języków interfejsu (nie używam, zresztą drastycznie zmniejszy to wynikową binarkę ELinksa), wyłączam też wsparcie dla mechanizmu mailcap (ELinks ma swój własny, bardziej rozbudowany mechanizm i to jego wolę używać - można co prawda używać obydwu równolegle, ale hej, jeśli czegoś nie potrzebuję to po co to wkompilowywać?). Włączam też "szybkie" techniki operowania na pamięci, wyłączam wsparcie dla zapisywania zakładek w plikach formatu XBEL (taki standard pozwalający na wymianę zakładek między niektórymi przeglądarkami), wyłączam też możliwość pisania skryptów pod ELinksa w języku LUA (nie znam tego języka, więc...)
Kompilacja przechodzi bez większych oporów, "make install DESTDIR=/shm" również. Otrzymuję takie drzewko plików:
usr/
|-bin/
| \-elinks
\-man/
|-man1/
| \-elinks.1
\-man5/
|-elinks.conf.5
\-elinkskeys.5
Łeee, to będzie trywialne :)
3. Plik .specSummary: Rozszerzona wersja Linksa Name: elinks Version: %(date +%Y%m%d) Release: 1 License: GPL Group: Internet BuildRoot: /var/tmp/%{name}-%{version} Obsoletes: links %description Rozszerzona wersja Linksa (tekstowej przeglądarki WWW), z dużo bardziej liberalnymi założeniami dotyczącymi pożądanych cech czy stylu rozwijania. ELinks w swoich założeniach ma być ulepszoną, bardziej rozbudowaną wersją Linksa - zawierać więcej opcji, oferować większą konfigurowalność, a przy tym mieć solidny, stabilny kod. %prep %setup -q -T -c cp -a ~/CVS/elinks/* . %build ./autogen.sh %configure --disable-dependency-tracking --disable-ipv6 --disable-nls \ --disable-mailcap --enable-fastmem --without-xbel --without-lua make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} %files %defattr(0644,root,root,0755) %attr(0755,root,root)%{_bindir}/* %{_mandir}/man*/*.gz %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir}
Preambuła jest prosta - automagiczny tag Version: itp. A, dodałem:
Obsoletes: links
Dzięki temu przy instalacji tego pakietu zostanie automatycznie usunięty pakiet "links". Nie ma raczej sensu posiadanie w systemie równolegle linksa i elinksa, zwłaszcza że elinks jest w wielkiej mierze "kompatybilny wstecz" z linksem.
Sekcja %prep jest wzorowana na tych z Vima czy Midnight Commandera. Sekcja %build to wywołanie skryptu ./autogen.sh a następnie makra %configure z odpowiednimi opcjami.
Instalacja i sekcja %files są identyczne jak we wcześniej podanym szkielecie pliku .spec. Nie wymagały w ogóle zmieniania na potrzeby tak prostego pakietu.
4. Budowanie pakietu sprowadza się do rpmbuild -bb elinks.spec. I po krzyku.
FVWM jest często zmieniającym się (a zarazem dosyć sporym) programem. Dlatego używanie wersji z CVS ma swoje zalety - po pierwsze ma się zawsze dostęp do najnowszych "bajerów", po drugie nie trzeba czekać aż developerzy wydadzą kolejną oficjalną "release", po trzecie w razie aktualizacji kodu przez CVS pobiera się tylko poprawki do już posiadanego kodu - a przy pobieraniu normalnych paczek ze źródłami za każdym razem ciągnie się też megabajty kodu, który wcale się nie zmienił od ostatniej "release".
1. Źródła umiejscowione standardowo, ~/CVS/fvwm/.
2. Próbna instalacja nie jest zbyt skomplikowana. FVWM z CVS nie posiada co prawda ani ./configure, ani nawet ./autogen.sh, ale wystarczy uruchomić odpowiedni zestaw poleceń z pakietu autoconf/automake:
aclocal autoheader automake -af autoconf
by otrzymać skrypt konfiguracyjny ./configure. Przeglądam sobie opcje i decyduję się na:
./configure --without-gnome --disable-dependency-tracking \ --disable-command-log --disable-debug-msgs --disable-sm --disable-nls \ --enable-shape --enable-xrender --disable-xinerama --disable-bidi \ --enable-xft --with-gtk-prefix=/dev/null --disable-perllib --prefix=/usr
Wyłączam wsparcie dla gnome-libs (ale nie dla hintów pochodzących od aplikacji Gnome), wyłączam inne opcje których nie potrzebuję, włączam Xft. Xft co prawda i tak jest włączane zawsze gdy to tylko możliwe, ale jeśli explicite podam taką opcję, to ./configure będzie wiedzieć, że bardzo mi na tej opcji zależy. I jeśli nie będzie w stanie użyć Xft, to wywali się z błędem przy konfigurowaniu. A potem jeszcze wyłączam wsparcie dla Gtk+ oraz biblioteki integracji z Perlem (nie korzystam z tych funkcji, więc po co je kompilować/instalować?)
Aha, ten numer "--with-gtk-prefix=/dev/null" to jedyny sposób, by wyłączyć kompilowanie wsparcia dla Gtk+ jeśli posiada się gtk w systemie. Po prostu fvwm nie oferuje żadnego "--disable-gtk".
Jestem już prawie gotowy do kompilacji, ale jest jeszcze jedna rzecz, którą muszę zmienić. Ustawienia myszy. Moja mysz ma pięć przycisków i rolkę (czyli, z punktu widzenia X-ów, jest to siedem przycisków). Fvwm domyślnie przystosowany jest tylko do "normalnych" myszy (trzy przyciski+rolka). W zasadzie to jest tak, że niezależnie od tych ustawień moje przyciski i tak będą działać. Po prostu fvwm będzie ostrzegał przy próbie użycia "nadmiarowych" przycisków, że jednak skompilowany to on był z myślą o mniej zmutowanych myszach. Ja tutaj tylko wyłączę te ostrzeżenia.
Informacja o liczbie przycisków znajduje się w pliku libs/defaults.h. Jest tam linijka
#define NUMBER_OF_MOUSE_BUTTONS 5
którą muszę zmienić. Kopiuję plik libs/defaults.h do libs/defaults.h.new, edytuję libs/defaults.h.new i zmieniam "5" na "7". Teraz, pozostając w głównym katalogu ze źródłami fvwm, generuję odpowiedniego patcha poleceniem
diff -u libs/defaults.h libs/defaults.h.new >fvwm-mouse.patch
Dlaczego tak robię? Bo muszę zmieniać tak źródła przy każdej kompilacji fvwm (muszę ustawiać liczbę przycisków myszy). Więc najprościej jest wygenerować małego patcha, którego rpm później będzie aplikował na źródła. Więc mam już tego mojego patcha w pliku fvwm-mouse.patch, przenoszę go do ~/rpm/SOURCES (patche są przez rpm pobierane z katalogu "źródeł").
Dobra, teraz mogę spokojnie uruchomić kompilację. Ta po jakimś czasie się kończy. Teraz tylko
make install DESTDIR=/shm
i mogę już oszacować uszkodze^W wyniki ;)
usr/
|-bin/
| |-FvwmCommand
| |-fvwm
| |-fvwm-bug
| |-fvwm-config
| |-fvwm-convert-2.4
| |-fvwm-convert-2.6
| |-fvwm-menu-desktop
| |-fvwm-menu-directory
| |-fvwm-menu-headlines
| |-fvwm-menu-xlock
| |-fvwm-perllib
| |-fvwm-root
| |-fvwm2
| \-xpmroot
|-libexec/
| \-fvwm/
| \-2.5.7/
| |-FvwmAnimate
| |-FvwmAudio
| |-FvwmAuto
| |-FvwmBacker
| |-FvwmBanner
| |-FvwmButtons
| |-FvwmCascade
| |-FvwmCommand.pm
| |-FvwmCommand.sh
| |-FvwmCommandS
| |-FvwmConsole
| |-FvwmConsoleC
| |-FvwmConsoleC.pl
| |-FvwmCpp
| |-FvwmDebug
| |-FvwmDragWell
| |-FvwmEvent
| |-FvwmForm
| |-FvwmGtkDebug
| |-FvwmIconBox
| |-FvwmIconMan
| |-FvwmIdent
| |-FvwmM4
| |-FvwmPager
| |-FvwmPerl
| |-FvwmProxy
| |-FvwmRearrange
| |-FvwmSave
| |-FvwmSaveDesk
| |-FvwmScript
| |-FvwmScroll
| |-FvwmTalk
| |-FvwmTaskBar
| |-FvwmTheme
| |-FvwmTile
| |-FvwmWharf
| |-FvwmWinList
| \-FvwmWindowMenu
|-man/
| \-man1/
| |-FvwmAnimate.1
| |-FvwmAuto.1
| |-FvwmBacker.1
| |-FvwmBanner.1
| |-FvwmButtons.1
| |-FvwmCommand.1
| |-FvwmConsole.1
| |-FvwmConsoleC.pl.1
| |-FvwmCpp.1
| |-FvwmDebug.1
| |-FvwmDragWell.1
| |-FvwmEvent.1
| |-FvwmForm.1
| |-FvwmGtk.1
| |-FvwmGtkDebug.1
| |-FvwmIconBox.1
| |-FvwmIconMan.1
| |-FvwmIdent.1
| |-FvwmM4.1
| |-FvwmPager.1
| |-FvwmPerl.1
| |-FvwmProxy.1
| |-FvwmRearrange.1
| |-FvwmSave.1
| |-FvwmSaveDesk.1
| |-FvwmScript.1
| |-FvwmScroll.1
| |-FvwmTaskBar.1
| |-FvwmTheme.1
| |-FvwmWharf.1
| |-FvwmWinList.1
| |-FvwmWindowMenu.1
| |-fvwm-bug.1
| |-fvwm-config.1
| |-fvwm-convert-2.2.1
| |-fvwm-convert-2.4.1
| |-fvwm-convert-2.6.1
| |-fvwm-menu-desktop.1
| |-fvwm-menu-directory.1
| |-fvwm-menu-headlines.1
| |-fvwm-menu-xlock.1
| |-fvwm-perllib.1
| |-fvwm-root.1
| |-fvwm.1
| \-xpmroot.1
\-share/
\-fvwm/
|-ConfigFvwmBacker
|-ConfigFvwmButtons
|-ConfigFvwmDefaults
|-ConfigFvwmIconBox
|-ConfigFvwmIconMan
|-ConfigFvwmIdent
|-ConfigFvwmPager
|-ConfigFvwmProxyDefaults
|-ConfigFvwmScroll
|-ConfigFvwmSetup
|-ConfigFvwmTaskBar
|-ConfigFvwmWinList
|-FvwmForm-Capture
|-FvwmForm-Form
|-FvwmForm-QuitVerify
|-FvwmForm-Rlogin
|-FvwmForm-RootCursor
|-FvwmForm-Setup
|-FvwmForm-Talk
|-FvwmForm-TalkHelp
|-FvwmScript-BaseConfig
|-FvwmScript-BellSetup
|-FvwmScript-Buttons
|-FvwmScript-Colorset
|-FvwmScript-ComExample
|-FvwmScript-Date
|-FvwmScript-FileBrowser
|-FvwmScript-Find
|-FvwmScript-KeyboardSetup
|-FvwmScript-PointerSetup
|-FvwmScript-Quit
|-FvwmScript-ScreenDump
|-FvwmScript-ScreenSetup
|-FvwmScript-Setup95
|-FvwmScript-WidgetDemo
|-fvwm-script-ComExample.pl
|-fvwm-script-setup95.pl
\-system.fvwm2rc-sample-95
Dużo tego, psia jego mać. Dobra... nie powinno być trudno, atrybuty plików w obrębie katalogów są spójne, więc wyliczanka jak w przypadku mc nie będzie konieczna. Katalog bin/ w normie, jeden katalog libexec/fvwm/2.5.7/ z modułami (które muszą mieć atrybut +x)... ten "libexec" nie jest dozwolony w świetle Filesystem Hierarchy Standard i po przetworzeniu przez mój rpm zamieni się to w lib/fvwm/2.5.7/... ale to nie jest teraz specjalnie istotne.
Manuale jak manuale, ale prawie cały katalog share/fvwm zostanie usunięty - on zawiera tylko przykładowe konfiguracje i skrypty konfiguracyjne dla modułu FvwmScript... a ja nie mam zamiaru korzystać z tego chłamu, w końcu moja własna konfiguracja jest o wiele lepsza ;) Pozostać musi w nim tylko plik ConfigFvwmDefaults
I słowo żalu: team FVWM nie ma najwidoczniej nikogo, kto by tak naprawdę znał się na mechanizmach autoconfa. Bo ich "firmowy" skrypt ./configure niestety ma wady, których nikt jakoś nie usuwa (choć zgłosiłem te błędy autorom FVWM). Wady te sprowadzają się do jednego w sumie: nawet gdy wyłączyłem wsparcie dla gtk+ i dla integracji z Perlem fvwm zainstalował dużo plików które wymagają gtk+ i interfejsu fvwm-perl. A to oznacza, że muszę te pliki samodzielnie namierzyć i usunąć. Robię to z dwóch powodów: Ordnung muss sein, a dodatkowo nie mogę sobie pozwolić na pozostawienie w pakiecie choćby jednego skryptu używającego fvwm-owskich modułów Perla (rpm je wyniucha i wciągnie na listę zależności, a potem przy instalacji będzie żądał tych perlowych rozszerzeń).
Więc do dzieła, zacznę od bin/:
Najpierw lecą wszystkie skrypty używające Perla (file okazuje się tu nieocenioną pomocą): fvwm-convert-2.4 fvwm-convert-2.6 fvwm-menu-desktop fvwm-menu-directory fvwm-menu-headlines fvwm-menu-xlock fvwm-perllib. Co prawda część tych skryptów pewnie by zadziałała i z "gołym" Perlem, ale ja i tak nigdy nie używałem żadnego z nich, więc... poza tym, jeśli usunę je wszystkie, to fvwm jako pakiet uniezależni się całkowicie od Perla.
Teraz niewiele już zostało programów w bin/. Na pewno wyleci stąd skrypt fvwm-bug (bo i tak nie wysyłam klasycznym bugreportów, wylewam swoje żale od razu na liście dyskusyjnej fvwm-workers :). Wylecą stąd także fvwm-root i symlink do niego, xpmroot - ten program służy do ustawiania tła, ale ustawianiem teł w moim systemie i tak zajmuje się dużo lepszy Esetroot (szybszy i obsługuje więcej formatów plików graficznych). Zostały tutaj już tylko cztery pliki i one mi wystarczą.
Teraz kolej na libexec/fvwm/2.5.7/. Stąd na sto procent muszą wylecieć pliki FvwmCommand* (nie używam tego prymitywizmu, wolę "pełnowymiarowe" FvwmConsole). Usunę też FvwmConsoleC.pl, bo to tylko głupia nakładka która może dodać kilka niepotrzebnych IMO cech do edytora linii w FvwmConsole (przecież FvwmConsole używa biblioteki readline, tego nie trzeba sztucznie rozszerzać - wystarczy normalnie skonfigurować! Ale hej, najwidoczniej ktoś w Fvwm-devel-team poza Perlem już świata nie widzi. Z armaty do motylka...
Usuwam też moduły *Debug, nie będą mi potrzebne. Zwłaszcza FvwmGtkDebug, który by i tak nie działał bez fvwm-perl i FvwmGtk. O, moduł FvwmPerl... też do śmieci. Tak samo FvwmSave*, one też są wadliwie skonstruowane (mają niby robić coś w stylu session-management, ale nie umieją :) FvwmWindowMenu też jest do wywalenia, on wymaga integracji fvwm-perl. FvwmTalk też jest do usunięcia. FvwmCascade i FvwmTile też są do usunięcia, bo to tylko owijki na FvwmRearrange.
Do /dev/null wędruje też na pewno FvwmBacker (zmienia tło przy zmianie biurka), FvwmBanner (wyświetla logo przy starcie FVWM), FvwmDragWell (oferuje malutkie okienko, z którego można "wysłać" obiekt XDnD, nie wiem do czego to by się mogło przydać), FvwmIconBox (e tam, wolę FvwmIconMan), FvwmTaskBar (nie lubię. Brzydki i nie lubię upodabniania do Windows95), FvwmTheme (ten moduł i tak nie jest nigdy ładowany, jego funkcjonalność jest już od dawna wbudowana na stałe w Fvwm). Usuwam też FvwmWharf (to samo mogę osiągnąć za pomocą FvwmButtons) i FvwmWinList (da się zastąpić przez FvwmIconMan). A na koniec usuwam też "legacy symlink" FvwmAudio. Kasuję FvwmAuto (da się zastąpić bardziej uniwersalnym FvwmEvent) oraz FvwmScroll i FvwmAnimate (nie mam potrzeby używać). Na koniec wywalam też FvwmScript i FvwmForm - ja szybciej napiszę odpowiednią mini-aplikację w Gtk+ która będzie ładniejsza, mniejsza i szybsza niż gdybym miał się uczyć jakichś dziwnych języków skryptowych. Wywalam też FvwmCpp i FvwmM4, bo moje konfiguracje FVWM nie są aż tak złożone, bym musiał używać makrodefinicji i specjalnych parserów. OK, reszta modułów może pozostać :)
Ostra selekcja, ale ja już wiem jakich modułów używam, a jakich nie dotknąłem przez ostatnie pół roku. Więc wiem co mogę wywalić.
Teraz należy jeszcze tak samo oczyścić man/man1. W końcu do /dev/null poleci duża część binarek, więc ich manuale też nie będą mi potrzebne. Usunę pliki fvwm-convert*, fvwm-menu-*, fvwm-perllib.1, fvwm-bug.1, fvwm-root.1, xpmroot.1, FvwmCommand.1, FvwmConsoleC.pl.1, *Debug.1, FvwmPerl.1, FvwmSave*, FvwmWindowMenu.1, FvwmBacker.1, FvwmBanner.1, FvwmDragWell.1, FvwmIconBox.1, FvwmTaskBar.1, FvwmWharf.1, FvwmWinList.1, FvwmAuto.1, FvwmScroll.1, FvwmAnimate.1, FvwmForm.1, FvwmScript.1, FvwmCpp.1 i FvwmM4.1. O, jak widzę znajduje się tutaj też manual FvwmGtk.1 dla modułu, który nawet nie został skompilowany i zainstalowany (wspominałem już, że w fvwm-devel-team najwidoczniej nie ma nikogo, kto by _naprawdę_ znał się na autoconfie?). No cóż, ten manual też idzie do śmieci.
Specjalnie za to pozostawiam manual do FvwmTheme. Co prawda sam moduł jest już "wbudowany" w fvwm, ale jego manual ciągle zawiera informacje które nie wywędrowały do głównego manuala fvwm. Więc powinien pozostać.
OK, to by było wszystko.
3. Plik .spec
Summary: F(?) Virtual Window Manager Name: fvwm Version: %(date +%Y%m%d) Release: 1 License: GPL Group: Powłoki URL: http://www.fvwm.org/ BuildRoot: /var/tmp/%{name}-%{version} Patch0: %{name}-mouse.patch %description FVWM to złożony i wszechstronny menadżer okien dla systemu X Window. Jest zgodny z normami ICCCM, EWMH, obsługuje również Gnome/Mwm/olwm Hints. Duże możliwości, wysoka konfigurowalność, szybkość i nieduże wymagania. Żadnych "ustawień domyślnych". %prep %setup -c -T -q cp -a ~/CVS/fvwm/* . %patch -p1 aclocal autoheader automake -af autoconf %build %configure --without-gnome --disable-dependency-tracking \ --disable-command-log --disable-debug-msgs --disable-sm \ --disable-nls --enable-shape --enable-xrender \ --disable-xinerama --disable-bidi --enable-xft \ --with-gtk-prefix=/dev/null --disable-perllib make %install rm -rf %{buildroot} make install DESTDIR=%{buildroot} mv %{buildroot}%{_datadir}/fvwm/{ConfigFvwmDefaults,.hid} rm -rf %{buildroot}%{_datadir}/fvwm/* mv %{buildroot}%{_datadir}/fvwm/{.hid,ConfigFvwmDefaults} rm %{buildroot}%{_bindir}/{fvwm-convert-2.4,fvwm-convert-2.6,\ fvwm-menu-desktop,fvwm-menu-directory,fvwm-menu-headlines,\ fvwm-menu-xlock,fvwm-perllib,fvwm-bug,fvwm-root,xpmroot} rm %{buildroot}%{_libexecdir}/fvwm/*/{FvwmCommand*,FvwmConsoleC.pl,*Debug,\ FvwmPerl,FvwmSave*,FvwmWindowMenu,FvwmTalk,FvwmCascade,FvwmTile,FvwmBacker,\ FvwmBanner,FvwmDragWell,FvwmIconBox,FvwmTaskBar,FvwmTheme,FvwmWharf,\ FvwmWinList,FvwmAudio,FvwmAuto,FvwmScroll,FvwmAnimate,FvwmScript,FvwmForm,\ FvwmCpp,FvwmM4} rm %{buildroot}%{_mandir}/man1/{fvwm-convert*,fvwm-menu-*,fvwm-perllib.1,\ fvwm-bug.1,fvwm-root.1,xpmroot.1,FvwmCommand.1,FvwmConsoleC.pl.1,*Debug.1,\ FvwmPerl.1,FvwmSave*,FvwmWindowMenu.1,FvwmBacker.1,FvwmBanner.1,\ FvwmDragWell.1,FvwmIconBox.1,FvwmTaskBar.1,FvwmWharf.1,FvwmWinList.1,\ FvwmAuto.1,FvwmScroll.1,FvwmAnimate.1,FvwmForm.1,FvwmScript.1,FvwmCpp.1,\ FvwmM4.1,FvwmGtk.1} %files %defattr(0644,root,root,0755) %attr(0755,root,root)%{_bindir}/* %{_mandir}/man*/*.gz %attr(0755,root,root)%{_libexecdir}/fvwm %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir}
Preambuła jest prosta. Dodałem tylko wskazanie na URL strony projektu FVWM (nie, to nie jest potrzebne :), pojawiła się też linijka
Patch0: %{name}-mouse.patch
która określa używany przez pakiet plik z patchem (przyciski myszy, jeśli ktoś zapomniał).
Sekcja %prep nie jest specjalnie wyuzdana, ot, założyć katalog, wejść do niego, skopiować tam repozytorium CVS. Gdy pliki są już na miejscu, aplikowany jest patch
%patch -p1
a potem konieczna do stworzenia pliku ./configure sekwencja poleceń aclocal, autoheader itp.
W sekcji %install po zainstalowaniu plików wywalam zawartość katalogu /usr/share/fvwm (wystarczy ten jeden plik do zachowania "ukryć" - linijka "rm -rf *" nie wychwyci plików ukrytych"), a potem w trzech sporych bloczkach wywalam niektóre pliki z %{_bindir}, %{_libexecdir} oraz %{_mandir} - tak, jak to wcześniej motywowałem.
Sekcja %files jest krótka, to wpisów "standardowych" doszedł tylko ten załączający zawartość %{_libexecdir} i nakładający wszystkiemu wewnątrz atrybut wykonywalności (tam siedzą moduły fvwm, one muszą być wykonywalne).
4. Budowanie pakietu
Uruchamiam rpmbuild -bb fvwm.spec. Przelatują komunikaty, patch się nakłada, aclocal i spółka, konfiguracja, kompilacja... dobrze, jeśli nie wywali się przy budowaniu listy plików... ależ to trwa...
U mnie skończyło kompilację po równych dziesięciu minutach. I udało się :)
Pamiętasz tę długaśną listę plików po domyślnej instalacji FVWM w /shm? Mój pakiet rpm ma teraz taką zawartość:
usr/
|-bin/
| |-FvwmCommand
| |-fvwm
| |-fvwm-config
| \-fvwm2
|-lib/
| \-fvwm/
| \-2.5.7/
| |-FvwmButtons
| |-FvwmConsole
| |-FvwmConsoleC
| |-FvwmEvent
| |-FvwmIconMan
| |-FvwmIdent
| |-FvwmPager
| |-FvwmProxy
| \-FvwmRearrange
\-share/
|-fvwm/
| \-ConfigFvwmDefaults
\-man/
\man1/
|-FvwmButtons.1.gz
|-FvwmConsole.1.gz
|-FvwmEvent.1.gz
|-FvwmIconMan.1.gz
|-FvwmIdent.1.gz
|-FvwmPager.1.gz
|-FvwmProxy.1.gz
|-FvwmRearrange.1.gz
|-FvwmTheme.1.gz
|-fvwm-config.1.gz
\-fvwm.1.gz
A przy okazji jest o blisko 3MB mniejszy. Ziarnko do ziarnka...
Xwelltris to taki sympatyczny tetris w którym układa się klocki na dnie "studni". Kolejny klon tej odmiany tetrisa. Nic specjalnego, ale jest całkiem porządnie wykonany, jest napisany w C++ i może używać SDL. Jego instalacja przysparza jednak pewnych kłopotów, a więc świetnie nadaje się na dziewiąty przykład :)
1. Źródła wystarczy pobrać i rozpakować.
2. Próbna instalacja
Pakiet rozpakowuje się do katalogu xwelltris-1.0.1, a więc konieczne będzie użycie opcji "-n" w makrze %config. Nie ma zbyt wielu opcji konfiguracyjnych, prawdę mówiąc jedyne dostępne sprowadzają się do wyboru pomiędzy używaniem przez xwelltris "gołych" X-ów a SDL, ja decyduję się na same X-y, bez SDL.
./configure --with-x --prefix=/usr
Konfiguracja przechodzi gładko, zaczynam kompilować, ale zauważam coś dziwnego - w wynikach kompilatora widzę cały czas flagę "-fomit-frame-pointer"... A kompilowane pliki to kod w C++ (używany kompilator to c++, a rozszerzenia plików to .cxx - czyli C++, jak nic). Tyle, że flaga -fomit-frame-pointer jest, w obecnym stadium rozwoju GCC, dla kodu w C++ szkodliwa i nie należy jej w takiej kombinacji za nic w świecie używać. I ja mam odpowiednio ustawione zmienne $CFLAGS i $CXXFLAGS. Ale najwidoczniej xwelltris zignorował $CXXFLAGS i wziął ustawienia z $CFLAGS. No dobra...
Robię:
export CFLAGS="$CXXFLAGS" ./configure --with-x --prefix=/usr
tak, teraz widzę na ekranie poprawne flagi. Czekam na zakończenie kompilacji...
OK, skończył. Teraz instalacja: make install DESTDIR=/shm... kończy się błędem.
/usr/bin/install -c -d -m 755 /usr/share/xwelltris /usr/bin/install: cannot change permissions of `/usr/share/xwelltris': Operacja niedozwolona make: *** [install] Błąd 1
No tak. Najwidoczniej xwelltris ignoruje też zmienną DESTDIR. Hmm, więc może ma jakąś własną zmienną? Czasem tak bywa. W pliku INSTALL nie znajduję jednak żadnych informacji o tym. Przeglądnięcie Makefile upewnia mnie, że xwelltris nie używa żadnych zmiennych by przekierować instalację.
Jest to bez wątpienia jedna z najgorszych rzeczy, jakie mogą się przydarzyć. Wyjścia są tutaj dwa:
1.
Ignoruje się to, kompiluje normalnie program, a w sekcji %install olewa mechanizmy programu i ręcznie kopiuje wybrane kąski z katalogu ze źródłami do %{buildroot}. Można sobie na to pozwolić gdy zna się listę plików które należy zainstalować, no i gdy lista ta nie jest specjalnie długa (wpisywanie dwustu regułek dla "cp" i "mkdir" czy też "install" w pliku .spec może zabić człowieka, zostało to naukowo udowodnione!)
2.
Zmienia się pliki Makefile i pochodne tak, aby zaczęły respektować jakąś zmienną. Może i wymaga większego wysiłku, ale okazuje się być "tym właściwym" rozwiązaniem jeśli poruszamy się na "nieznanym terytorium" (nowy program), lub lista instalowanych plików przekracza choćby kilkanaście/kilkadziesiąt. Ta technika wymaga z pewnością pewnego doświadczenia. Należy znaleźć w plikach Makefile te linijki, które faktycznie instalują pliki, następnie znaleźć ich źródło, bowiem pliki Makefile są często tylko czubkiem góry lodowej i swoje ustawienia czerpią z innych plików. Na dodatek takie rzeczy rozwiązuje się indywidualnie, bo jeśli pakiet nie respektuje $DESTDIR to zwykle jest napisany niestandardowo. Jeśli jest napisany niestandardowo, to znaczy, że jego autor napisał go tak, jak jemu się wydawało, że "będzie dobrze". Czyli istnieje spora szansa, że każdy zrąbany pakiet będzie absolutnie odmienny od innego zrąbanego pakietu, że będzie miał inaczej rozplanowaną sekwencję instalacyjną itp. W tym przypadku (xwelltris) irytujące jest to, że autor użył mechanizmów autoconf... ale nieumiejętnie, popełniając parę istotnych błędów.
W tym przypadku niestety w grę wchodzi tylko sposób nr 2. Niestety. Nie znam listy plików xwelltris, więc muszę go "naprawić".
Notka: Powyższe jest oczywiście kłamstwem :) Spis instalowanych plików jest łatwo poznać. Wystarczyłoby mi skompilować xwelltris z jakimś "bezpiecznym" prefiksem, np. "--prefix=/shm", skompilować i zainstalować przez make install. Potem obejrzeć co też xwelltris natworzył w /shm i użyć tego jako wzornika dla pliku .spec. Na potrzeby tego artykułu jednak muszę wciskać miejscami kit w żywe oczy :)
Po paru minutach śledzenia różnych plików dochodzę do wniosku, że winny jest plik Makefile.in (w przypadku pakietów używających autoconf to akurat normalne, autoconf oznacza równocześnie używanie automake. A automake tworzy pliki Makefile z plików Makefile.in. Więc chcąc poprawić plik Makefile modyfikuje się plik Makefile.in, a ./configure przy następnym uruchomieniu je zaktualizuje (w świetle tego modyfikowanie bezpośrednio plików Makefile to w przypadku automake robota głupiego, walka z wiatrakami).
Wydaje mi się, że zmieniając bloczek
install:
$(INSD) $(INSTLIB)
$(INSTALL_DATA) $(DATAFILES) $(INSTLIB)
$(INSTALL_SHARE_DATA) $(SHARE_DATAFILES) $(INSTLIB)
$(INSTALL_PROGRAM) $(PROGRAM) $(INSTDIR)
na
install:
$(INSD) ${DESTDIR}$(INSTLIB)
$(INSTALL_DATA) $(DATAFILES) ${DESTDIR}$(INSTLIB)
$(INSTALL_SHARE_DATA) $(SHARE_DATAFILES) ${DESTDIR}$(INSTLIB)
$(INSTALL_PROGRAM) $(PROGRAM) ${DESTDIR}$(INSTDIR)
coś mi się uda zdziałać. Wprowadzam te poprawki, jeszcze raz uruchamiam
./configure --prefix=/usr
kompiluję i instaluję za pomocą
make install DESTDIR=/shm
... i, co ciekawe, udaje się :)
Przynajmniej częściowo. Bo zawartość /shm wygląda tak:
usr/
|-bin
\-share/
\-xwelltris/
|-board2.gif
|-polyomino.dat
|-wellcuts.gif
|-welltris.scores
|-font2.gif
|-topnine.gif
\-wellintro.gif
chodzi mi o ten plik usr/bin. Tak, to plik, a nie katalog. Plik wykonywalny. Wiem, co się stało: ten głupi xwelltris próbował kopiować binarkę do /usr/bin bez określenia, że chce ją wkopiować do katalogu. Założył, że katalog bin będzie istniał, więc wykonał coś w stylu
cp xwelltris /usr/bin
I to był błąd. Bo w przypadku gdy bin nie istniało w chwili kopiowania, to cp (lub inny program kopiujący, np. install) umieściło binarkę w /usr/bin. Gdyby autor skryptów zapisał to hiper-poprawnie jako
cp xwelltris /usr/bin/
(chodzi o ten ukośnik na końcu linii), to wtedy program kopiujący by zgłosił błąd. Pamiętaj: jeśli piszesz skrypty kopiujące coś do jakiegoś katalogu i masz zamiar udostępniać te skrypty innym ludziom, to wyraźnie oznaczaj typy plików. Bywają różne systemy, różne układy katalogów itp. Lepiej jeśli program od razu się wyłoży, niż gdyby miał po cichu zmasakrować binarkę umieszczając ją w dziwnym miejscu.
Dobra, ten błąd nie jest groźny, wystarczy w pliku .spec utworzyć odpowiedni katalog %{buildroot}%{_bindir} przed rozpoczęciem instalacji.
Ale jest jeszcze jedna rzecz która mnie zastanawia - brak manuali. Hmm. Dla pewności przeglądam katalog ze źródłami... Nie, najwidzoczniej xwelltris nie ma dokumentacji innej, niż te parę luźnych plików README na krzyż. No dobra, to czas wygenerować patcha. Eee, ale ja nie mam już oryginalnego pliku Makefile.in. W ferworze walki nadpisałem go tym zmienionym. Dobra, kopiuję Makefile.in do Makefile.in.new i jeszcze raz rozpakowuję źródła. Uruchamiam
diff -u Makefile.in Makefile.in.new >~/rpm/SOURCES/xwelltris-destdir.patch
i już mam patcha. Teraz można przejść do rpm-owania.
3. Plik .spec
Summary: Kolejny klon tetrisa Name: xwelltris Version: 1.0.1 Release: 1 License: GPL Group: Rozrywka Source: %{name}-%{version}.src.tar.bz2 BuildRoot: /var/tmp/%{name}-%{version} Patch0: %{name}-destdir.patch %description Tetris w konwencji 2,5D ;) %prep %setup -q %patch0 -p0 %build export CFLAGS=$CXXFLAGS %configure --with-x make %install rm -rf %{buildroot} mkdir -p %{buildroot}%{_bindir} make install DESTDIR=%{buildroot} %files %defattr(0644,root,root,0755) %attr(0755,root,root)%{_bindir}/* %{_datadir}/xwelltris %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir}
W zasadzie tutaj nie ma już co wyjaśniać - normalna preambuła, z deklaracją jednego patcha. Sekcja %setup z nałożeniem patcha. W sekcji %build najpierw ustawiam $CFLAGS na wartość $CXXFLAGS (bo xwelltris ma ten dziwny błąd z braniem flag kompilatora C do kompilacji C++). W sekcji %install najpierw zakładam katalog %{buildroot}%{_bindir} (bo xwelltris ma ten dziwny błąd... zresztą, sam wiesz :). Aha, opcja "-p" podana mkdir powoduje, że będzie on zakładał "łańcuch" katalogów, tak jak polecenie mkdirhier.
Sekcja %files też w normie. Jedyne zmiany to wywalenie linijki dla manuali i dodanie linijki dla %{_datadir}/xwelltris.
4. Budowanie pakietu zaczęło się, podziałało, skończyło. Pakiet wynikowy powstał - czyli sukces na całej linii. Teraz tylko uruchamiam sobie
rpmbuild -bs --rmspec --rmsource xwelltris.spec
a rpm tworzy w ~/rpm/SRPMS pakiet źródłowy zawierający speca, patcha i źródła w jednej milutkiej paczuszce. A ta wędruje do mojego składowiska Rzeczy Absolutnie Wartych Przechowywania. Teraz mogę w prosty sposób ponownie przekompilować cały pakiet, w razie upgrade'u wystarczy podmienić paczkę ze źródłami - plik .spec i patch nie powinny się zdezaktualizować.
OK, weźmy się teraz za groffa. Groff to system formatowania dokumentów. Przyjmuje specjalny rodzaj dokumentów źródłowych składający się z tekstu i znaczników, a następnie produkuje z tego dokument wyjściowy, przeznaczony do pokazania na urządzeniu znakowym (jako ascii/ansi/html), ekranie X, czy też wydrukowaniu na drukarkach zgodnych z PCL lub PostScript. Choć i tak pewnie 95% zastosowań groffa to formatowanie stron manuala na potrzeby programu "man".
1. Źródła są do pobrania z dowolnego serwera FTP zawierającego kopię zasobów GNU (czyli praktycznie z każdego większego)
2. Próbna instalacja
Hmm, skrypt ./configure nie ma żadnych dodatkowych opcji, więc przekazuję mu na potrzeby testu tylko "--prefix=/usr". Kod jest chyba w całości napisany w C++, ale groff poprawnie użył flag pochodzących z $CXXFLAGS, więc wszystko jest OK. Standardowa kompilacja trochę potrwa... o, udało się. Teraz:
make install DESTDIR=/shm
i... błąd. groff próbuje usunąć plik /usr/bin/groff (pewnie próbuje usunąć starą wersję przed instalacją), co mu się nie udaje (właśnie dlatego, dla takich sytuacji nie warto bawić się w kompilowanie/budowanie pakietów na uprawnieniach roota). Nie wiem czy groff w ogóle ignoruje $DESTDIR, czy też może brakuje mu przekierowania jedynie we fragmentach które mają oczyścić mu pole operacyjne. Nieważne - trzeba go poprawić, tak czy siak. Dla pewności robię:
grep -r DESTDIR .
i... nic. Zero wyników. To znaczy, że groff po prostu pojęcia nie ma o ustrojstwie zwanym $DESTDIR. Dobra, po przeglądaniu plików Makefile, wyników:
make -k install DESTDIR=/shm
(chcę zobaczyć wszystkie instalowane pliki, nawet jeśli proces instalacji się nie powiedzie) i zwyczajowej w takich sytuacjach grzebaninie w różnych plikach, po paru nieudanych zmianach dochodzę do wniosku, że wystarczy zmienić w głównym pliku Makefile.in linie rozłożone w różnych miejscach, definicje: @prefix@, @exec_prefix@, @bindir@, @libdir@, @datadir@, @infodir@ i @mandir@, poprzedzając je odpowiednio prefiksem $(DESTDIR), np. linia
datadir=@datadir@
przeszła w
datadir=$(DESTDIR)@datadir@
Notka: gdy ingeruje się w takie zmienne, to powinno się uruchamiać ./configure z takimi parametrami, jakich będziemy potem używać wewnątrz rpm-a. Nie chodzi o konkretne wartości, tzn. nie gra większej roli czy użyje się /usr/man czy /usr/share/man, chodzi o samą obecność przełączników --mandir, --libdir itd. Skrypty Makefile.in zwykle pisane są tak, że zmienne tworzą sieć względnych definicji - jedna zmienna jest zdefiniowana tak, druga wykorzystuje jej definicję i dodaje jakiś swój sufiks, itd. Niektóre zmienne jednak są niezależne. Czasem jeszcze większe zamieszanie powodują "wartości domyślne". Np. załóżmy, że @mandir@ jest w pliku Makefile.in zdefiniowane jako @mandir@. Czyli żadnej zależności od innych wartości, bierze to, co się mu poda. A przynajmniej tak to by mogło wyglądać, bowiem jeśli użyję --mandir=/usr/share/man, to @mandir@ będzie miało wartość /usr/share/man, faktycznie. Jeśli jednak pominę ten argument, to ./configure zdefiniuje tę wartość jako @mandir@=@prefix@/man, bo taki ma "default", o czym zresztą mówi "./configure --help". Chodzi o to, że robi się z tego deklaracja łańcuchowa "po cichu", chociaż początkowo wcale na taką to nie wyglądało. Jeśli ja zmienię tylko linię definicji zmiennej @prefix@ na $(DESTDIR)@prefix@ w Makefile.in, to @prefix@ rozwinie się po zdefiniowaniu DESTDIR przy instalacji np. w /shm/usr, a @mandir@ w /shm/usr/man. Czyli świetnie. Jeśli jednak użyję opcji --mandir=/usr/share/man, to @mandir@ rozwinie się w /usr/share/man (bo nie ma już zależności od @prefix@, ta zależność istniała tylko wtedy, gdy ./configure uzupełniało zmienne swoimi domyślnymi wartościami).
Trzeba pamiętać, że makro %configure używa "rozwiniętych" wartości opcji --prefix, --bindir, --mandir itp., czyli np. '--bindir=/usr/bin' a nie '--bindir=%{_prefix}/bin'. Co prawda definicje w pliku ~/.rpmmacros mają postać wiązaną, łańcuchową, ale w momencie użycia makra w pliku .spec rpm rozwija makro do jego postaci końcowej. Jeśli rpm używa wartości dosłownych, to skrypt ./configure dostanie definicje wszystkich katalogów, nic nie otrzyma definicji "domyślnej". W plikach Makefile prawdopodobnie znikną wszystkie łańcuchowe odwołania, zostaną zastąpione dosłownymi opisami.
W przypadku groffa wystarczy załatać linijkę z @prefix@ by po skonfigurowaniu jako
./configure --prefix=/usr
pakiet dał się zainstalować w $DESTDIR. Taka poprawka by jednak zawiodła przy późniejszym budowaniu pakietu rpm, bo rpm by przekazał nie tylko --prefix, ale i cały komplet opcji. Co by spowodowało, że zmienne @infodir@ itp. przestałyby się odwoływać do @prefix@, przez co utraciłyby jakikolwiek związek z $DESTDIR, przez co by znowu próbowały się zainstalować bezpośrednio w /usr... skomplikowane to, ale daje się sprowadzić do jednej prostej reguły - jeśli zmieniasz zmienne w Makefile.in, to sprawdzaj to potem używając opcji jak najbardziej zbliżonych do tych, jakie wystąpią w pliku .spec. Nie chodzi zbytnio o wartości, ale o samą obecność przełączników w linii poleceń. Warto używać jednak wartości docelowych, bo dzięki temu czasem odkryjesz, że jakiś pakiet np. nie kieruje się według podanych mu opcji lub ma inne wady.
Poprawki które wprowadziłem do Makefile.in dadzą dziwne wyniki jeśli nie poda się explicite ważniejszych opcji. Ale że jest to patch tylko na potrzeby budowy pakietu, to wszystko jest w porządku.
Konfiguruję ponownie, za pomocą
./configure --prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin \ --libexecdir=/usr/lib --datadir=/usr/share --sysconfdir=/etc \ --localstatedir=/usr/var --libdir=/usr/lib --includedir=/usr/include \ --infodir=/usr/share/info --mandir=/usr/share/man --exec-prefix=/usr
(tutaj doskonale sprawdza się zsh i jego dopełnianie parametrów skryptów ./configure - oszczędza wklepywanie do minimum i eliminuje możliwość popełnienia błędu/literówki)
Kompiluję, próbuję zainstalować... błąd:
The prefix directory `/shm/usr' doesn't exist
OK, najwidoczniej zakłada, że katalog /usr będzie istniał (to trochę jak z tym katalogim bin w przypadku xwelltris, tyle że tutaj prawidłowo proces się wyłożył zamiast kombinować za plecami). Dobra, jeszcze raz:
mkdir /shm/usr make install DESTDIR=/shm
I wreszcie się udaje. No. Wynikowe drzewko plików (z obciętymi gałęziami):
usr/
|-bin/
| |-addftinfo
| |-afmtodit
| |-[...]
| |-tfmtodit
| \-troff
|-lib/
| \-groff/
| \-site-tmac/
\-share/
|-doc/
| \-groff/
| \-1.18.1/
| |-examples/
| | |-README.mom
| | |-elvis_syntax
| | |-grnexmpl.g
| | |-grnexmpl.me
| | |-[...]
| | |-typewrite.ps
| | |-webpage.ms
| | \-webpage.ps
| |-html/
| | \-momdoc/
| | |-appendices.html
| | |-cover.html
| | |-[...]
| | |-typesetting.html
| | \-using.html
| |-meintro.me
| |-meintro.ps
| |-meref.me
| |-meref.ps
| |-pic.ms
| \-pic.ps
|-groff/
| |-1.18.1/
| | |-eign/
| | |-font/
| | | \-[...]
| | \-tmac/
| | \-[...]
| \-site-tmac/
| |-man.local
| \-mdoc.local
|-info/
| |-dir
| |-groff
| |-groff-1
| |-groff-10
| |-[...]
| |-groff-8
| \-groff-9
\-man/
|-man1/
| |-addftinfo.1
| |-afmtodit.1
| |-[...]
| |-tfmtodit.1
| \-troff.1
|-man5/
| |-groff_font.5
| |-groff_out.5
| \-groff_tmac.5
\-man7/
|-ditroff.7
|-groff.7
|-[...]
|-groff_www.7
\-roff.7
Nie ma tu nic, co by zasługiwało na większą uwagę. Na pewno wywalę z końcowego pakietu zawartość katalogu /usr/share/doc, bo tam są tylko informacje dla ludzi chcących pisać dokumenty. Do odczytywania wystarczą mi informacje zawarte w stronach manuala. Strony info standardowo są podzielone na wiele plików, więc to też będę musiał zwalczyć. Ale poza tym nie powinno tu być już więcej kłopotów.
A, nie, moment. Przypomniało mi się coś. W serii 1.18.x groffa zmieniło się zachowanie systemu - wyjdzie to przy oglądaniu stron manuali. Starszy groff prawdopodobnie automatycznie reformatował strony tak, by wypełniały całą szerokość strony. Nowy tego nie robi, należy mu podać odpowiednie opcje. Problem polega na tym, że "man" się do tego jeszcze nie dostosował. Albo używa starego sposobu zmuszania groffa do współpracy, albo nawet nie próbuje tego robić - w efekcie strony manuala będą formatowane tak, aby mieć szerokość 80 znaków. Istnieje proste rozwiązanie: "man" uruchamia skrypt "nroff". A skrypt "nroff" jest niczym innym, jak prostą owijką na program "groff" (który z kolei uruchamia "troff", ale to nie jest tutaj ważne :). Nroff jest na tyle prosty, że można go zmodyfikować i dodać opcje, które spowodują dobieranie szerokości dokumentu do szerokości terminala. Wystarczy zmodyfikować jedną linię. Nie wiem czy to "ideowo poprawne", w zasadzie należałoby to naprawiać pewnie w źródłach programu "man", ale łatwiej jest to załatać w ten sposób.
3. Plik .spec
Summary: groff - frontend systemu formatowania dokumentów groff Name: groff Version: 1.18.1 Release: 1 License: GPL Group: Tekst Source: %{name}-%{version}.tar.bz2 BuildRoot: /var/tmp/%{name}-%{version} BuildRequires: /usr/bin/makeinfo PreReq: /sbin/install-info Patch0: groff-destdir.patch Patch1: groff-nroff.patch %description "groff" jest frontendem do systemu formatowania dokumentów groff. Normalnie uruchamia on program troff i odpowiedni dla wybranego urządzenia postprocesor (zawarte już w tym pakiecie). %prep %setup -q %patch0 -p0 %patch1 -p0 %build %configure make %install rm -rf %{buildroot} mkdir -p %{buildroot}%{_prefix} make install DESTDIR=%{buildroot} rm -rf %{buildroot}/%{_docdir} rm -f %{buildroot}/%{_infodir}/{dir,groff*} cd %{buildroot}/%{_infodir} makeinfo --no-split %{_builddir}/%{buildsubdir}/doc/groff.texinfo \ -I %{_builddir}/%{buildsubdir}/doc -o groff.info %files %defattr(0644,root,root,0755) %attr(0755,root,root)%{_bindir}/* %{_mandir}/man*/*.gz %{_libdir}/* %{_datadir}/groff %{_infodir}/groff.info.gz %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir} %post install-info %{_infodir}/groff.gz %{_infodir}/dir %preun install-info --delete %{_infodir}/groff.gz %{_infodir}/dir
Nie ma tu specjalnie ciekawych rzeczy - w preambule doszły linie
BuildRequires: /usr/bin/makeinfo PreReq: /sbin/install-info Patch0: groff-destdir.patch Patch1: groff-nroff.patch
oznaczające zależności wymagane na etapie budowania i instalacji pakietu. Deklaruję też dwa patche, które potem aplikuję w sekcji %prep.
Sekcja %install przed wykonaniem instalacji najpierw tworzy katalog usr/, a po instalacji wywala dokumentację z usr/share/doc, oczyszcza katalog usr/share/info, wchodzi do niego, a następnie generuje na nowo jeden duży plik info. Przy okazji zmieniam mu nazwę na bardziej standardową, z rozszerzeniem .info.
Sekcja %files jest prosta - binarki, manuale, zawartość %{_libdir}, katalog usr/share/groff oraz strona info. Stronę info wymieniam "z imienia" - gdyby któregoś dnia do pakietu groff doszły inne strony, zapewne też podzielone na wiele plików, to wolałbym o tym wiedzieć (i je również poprawić). Jeśli deklaruję w katalogu usr/share/info tylko jeden plik .info, to gdy dojdą inne (jeśli kiedyś dojdą w ogóle) mechanizm rpmbuild mnie zaalarmuje.
4. Budowanie pakietu kończy się stuprocentowym sukcesem.
Smartmontools to zestaw narzędzi do odpytywania dysków twardych używających SMART. Można dobrać się do danych statystycznych jakie dyski gromadzą podczas całej swojej pracy - ile razy talerze były zatrzymywane/rozkręcane, ile godzin dysk pracuje, czy zarejestrował jakieś błędy podczas swojej pracy (a jeśli tak, to kiedy), temperatura dysku, liczba realokowanych wewnętrznie (przez elektronikę dysku) uszkodzonych sektorów itp. Smartmontools jest bezpośrednim następcą pakietu smartsuite.
1. Źródła można znaleźć np. poprzez Freshmeat
2. Próbna instalacja
Po rozpakowaniu źródeł okaże się, że pakiet nie ma skryptu ./configure, że w ogóle nie bazuje na standardowych narzędziach autoconf/automake GNU. Używa zwykłego pliku Makefile. Respektuje $DESTDIR, a flagi kompilatora wyrażone są wewnętrzą zmienną CFLAGS, którą można przedefiniować przy wywoływaniu "make". Problem dotyczy późniejszej instalacji: co prawda pliki instalują się przez $DESTDIR, ale trzeba im chyba najpierw pozakładać katalogi (to akurat niewielki problem), gorzej że przy instalacji skrypt próbuje zmieniać atrybuty plików. A konkretniej, to próbuje zmieniać im grupę/właściciela na "root". Do czego ma prawo tylko root. Nie da się tego zmienić bez ingerencji w Makefile (niektóre linie musiałem połamać przy przeklejaniu, bo mi się layout rozjeżdżał nawet w szerokich oknach przeglądarki):
install:
if [ ! -f smartd -o ! -f smartctl ]
then echo -e "\n\nYOU MUST FIRST DO \"make\"\n"
exit 1
fi
/bin/gzip -c smartctl.8 > smartctl.8.gz
/bin/gzip -c smartd.8 > smartd.8.gz
/bin/gzip -c smartd.conf.5 > smartd.conf.5.gz
rm -f $(DESTDIR)/usr/share/man/man8/smartctl.8
rm -f $(DESTDIR)/usr/share/man/man8/smartd.8
install -m 755 -o root -g root -D smartctl $(DESTDIR)/usr/sbin/smartctl
install -m 755 -o root -g root -D smartd $(DESTDIR)/usr/sbin/smartd
install -m 755 -o root -g root -D smartd.initd $(DESTDIR)/etc/rc.d/init.d/smartd
install -m 644 -o root -g root -D smartctl.8.gz $(DESTDIR)/usr/share/man/man8/smartctl.8.gz
install -m 644 -o root -g root -D smartd.8.gz $(DESTDIR)/usr/share/man/man8/smartd.8.gz
install -m 644 -o root -g root -D smartd.conf.5.gz $(DESTDIR)/usr/share/man/man5/smartd.conf.5.gz
install -m 644 -o root -g root -D CHANGELOG $(DESTDIR)/usr/share/doc/smartmontools-5.1/CHANGELOG
install -m 644 -o root -g root -D COPYING $(DESTDIR)/usr/share/doc/smartmontools-5.1/COPYING
install -m 644 -o root -g root -D README $(DESTDIR)/usr/share/doc/smartmontools-5.1/README
install -m 644 -o root -g root -D TODO $(DESTDIR)/usr/share/doc/smartmontools-5.1/TODO
install -m 644 -o root -g root -D VERSION $(DESTDIR)/usr/share/doc/smartmontools-5.1/VERSION
install -m 644 -o root -g root -D WARNINGS $(DESTDIR)/usr/share/doc/smartmontools-5.1/WARNINGS
install -m 644 -o root -g root -D smartd.conf $(DESTDIR)/usr/share/doc/smartmontools-5.1/smartd.conf
install -m 644 -o root -g root -D smartd.conf $(DESTDIR)/etc/smartd.conf.example
if [ ! -f $(DESTDIR)/etc/smartd.conf ]
then install -m 644 -o root -g root -D smartd.conf $(DESTDIR)/etc/smartd.conf
To sekcja instalacyjna wycięta z Makefile. Teraz jakie poprawki należy wprowadzić? Po pierwsze można wyciąć te linie które kompresują strony manuala, bo to i tak zrobi rpm. Można też wyciąć te fragmenty usuwające stare manuale. Z tych wszystkich linijek "install..." trzeba usunąć fragmenty '-o root -g root'. Można też uprościć ten kawałek instalujący konfigurację. Po przeróbkach będzie to wyglądało tak:
install:
if [ ! -f smartd -o ! -f smartctl ]
then echo -e "\n\nYOU MUST FIRST DO \"make\"\n"
exit 1
fi
install -m 755 -D smartctl $(DESTDIR)/usr/sbin/smartctl
install -m 755 -D smartd $(DESTDIR)/usr/sbin/smartd
install -m 644 -D smartctl.8 $(DESTDIR)/usr/share/man/man8/smartctl.8
install -m 644 -D smartd.8 $(DESTDIR)/usr/share/man/man8/smartd.8
install -m 644 -D smartd.conf.5 $(DESTDIR)/usr/share/man/man5/smartd.conf.5
install -m 644 -D CHANGELOG $(DESTDIR)/usr/share/doc/smartmontools-5.1/CHANGELOG
install -m 644 -D COPYING $(DESTDIR)/usr/share/doc/smartmontools-5.1/COPYING
install -m 644 -D README $(DESTDIR)/usr/share/doc/smartmontools-5.1/README
install -m 644 -D TODO $(DESTDIR)/usr/share/doc/smartmontools-5.1/TODO
install -m 644 -D VERSION $(DESTDIR)/usr/share/doc/smartmontools-5.1/VERSION
install -m 644 -D WARNINGS $(DESTDIR)/usr/share/doc/smartmontools-5.1/WARNINGS
install -m 644 -D smartd.conf $(DESTDIR)/usr/share/doc/smartmontools-5.1/smartd.conf
install -m 644 -D smartd.conf $(DESTDIR)/etc/smartd.conf
Przy okazji wywaliłem też linijkę instalującą plik /etc/rc.d/init.d/smartd, bo mój system nie używa skryptów startowych SysV i to już powinno działać. Ale ze względów dydaktycznych chcę jeszcze pokazać, jak można sobie "udanymicznić" ścieżki dostępu: tutaj są one zakodowane na sztywno, np. /usr/sbin. Aby było bardziej rpm-owo, to powinno być możliwe do późniejszego zmieniania bez edycji plików. Ale jak to zrobić? Rozwiązanie jest w sumie proste - zamiast katalogów trzeba użyć zmiennych. Zmienne te się potem zainicjuje przy wywołaniu "make" odpowiednimi makrami. Sekcja po przeróbkach mogłaby wyglądać tak:
install:
if [ ! -f smartd -o ! -f smartctl ]
then echo -e "\n\nYOU MUST FIRST DO \"make\"\n"
exit 1
fi
install -m 755 -D smartctl $(DESTDIR)$(H_SBINDIR)/smartctl
install -m 755 -D smartd $(DESTDIR)$(H_SBINDIR)/smartd
install -m 644 -D smartctl.8 $(DESTDIR)$(H_MANDIR)/man8/smartctl.8
install -m 644 -D smartd.8 $(DESTDIR)$(H_MANDIR)/man8/smartd.8
install -m 644 -D smartd.conf.5 $(DESTDIR)$(H_MANDIR)/man5/smartd.conf.5
install -m 644 -D CHANGELOG $(DESTDIR)$(H_DOCDIR)/smartmontools-5.1/CHANGELOG
install -m 644 -D COPYING $(DESTDIR)$(H_DOCDIR)/smartmontools-5.1/COPYING
install -m 644 -D README $(DESTDIR)$(H_DOCDIR)/smartmontools-5.1/README
install -m 644 -D TODO $(DESTDIR)$(H_DOCDIR)/smartmontools-5.1/TODO
install -m 644 -D VERSION $(DESTDIR)$(H_DOCDIR)/smartmontools-5.1/VERSION
install -m 644 -D WARNINGS $(DESTDIR)$(H_DOCDIR)/smartmontools-5.1/WARNINGS
install -m 644 -D smartd.conf $(DESTDIR)$(H_DOCDIR)/smartmontools-5.1/smartd.conf
install -m 644 -D smartd.conf $(DESTDIR)$(H_SYSCONFDIR)/smartd.conf
Teraz w pliku .spec wystarczy wywołać:
make install H_SBINDIR=%{_sbindir} H_MANDIR=%{_mandir} \
H_DOCDIR=%{_docdir} H_SYSCONFDIR=%{_sysconfdir} DESTDIR=%{buildroot} \
CFLAGS="$CFLAGS"
Ale tutaj nasuwają mi się dwie refleksje: jedna to to, że będziemy mieć zmienne położenie pliku konfiguracyjnego. Ale jego lokacja jest zaszyta gdzieś na stałe w kodzie programu... i ją też trzeba by zmieniać. Po krótkim grepowaniu przez źródła znalazłem odpowiedzialny fragment, w pliku smartd.h znajduje się wpis:
#define CONFIGFILE "/etc/smartd.conf"
Ale to zwykła dyrektywa #define, więc można ją przykryć z linii poleceń, dodając do flag kompilatora w trakcie kompilacji:
make CFLAGS="$CFLAGS -DCONFIGFILE=\"%{_sysconfdir}/smartd.conf\""
I to powinno załatwić sprawę. Ale ta druga rzecz która przyszła mi do głowy, ona jest dużo większego kalibru: nie da się wygenerować patcha z tych zmian! To znaczy OK, dobra, patcha zrobić to się da, ale on nie da się potem zaaplikować gdy wyjdzie nowa wersja smartmontools. A dlaczego? Bo w patchowanych linijkach jest numer wersji (w nazwie katalogu docelowego dla dokumentacji). A to oznacza, że przy upgradzie ten numer się zmieni. A to z kolei oznacza, że patch nie rozpozna tych linijek. Rezultat: patch się nie zaaplikuje. Czyli można sobie zabawę z patchem podarować.
Alternatywnie można napisać regułkę sed-a która by powprowadzała te zmiany, a na dodatek była na tyle sprytna, by ignorować numerki wersji. To da się zrobić, ale nie chcę. Pójdę w inną stronę: instalowanych plików jest niewiele i mam podaną na tacy ich listę, łącznie z przeznaczeniem... przecież ja zamiast robić "make install" mogę po prostu przekleić te linijki które instalują pliki prosto do sekcji %install! Trochę drobnych poprawek i powinno być po krzyku. To nie jest idealne wyjście, ale na pewno o niebo lepsze niż robienie łatek jednorazowego użytku.
3. Plik .spec
%define sub_release -1 Summary: Narzędzia do monitorowania i kontroli S.M.A.R.T Name: smartmontools Version: 5.1 Release: 1 License: GPL Group: System Source: %{name}-%{version}%{sub_release}.tar.bz2 BuildRoot: /var/tmp/%{name}-%{version} Obsoletes: smartsuite %description Pakiet ten pozwala monitorować i kontrolować nośniki używające technologii S.M.A.R.T. (Self-Monitoring, Analysis and Reporting Technology System) %prep %setup -q -n %{name}-%{version}%{sub_release} %build make CFLAGS="$CFLAGS -DCONFIGFILE=\"%{_sysconfdir}/smartd.conf\"" LDFLAGS="$LDFLAGS" %install rm -rf %{buildroot} install -m 755 -D smartctl %{buildroot}%{_sbindir}/smartctl install -m 755 -D smartd %{buildroot}%{_sbindir}/smartd install -m 644 -D smartctl.8 %{buildroot}%{_mandir}/man8/smartctl.8 install -m 644 -D smartd.8 %{buildroot}%{_mandir}/man8/smartd.8 install -m 644 -D smartd.conf.5 %{buildroot}%{_mandir}/man5/smartd.conf.5 install -m 644 -D smartd.conf %{buildroot}%{_sysconfdir}/smartd.conf %files %defattr(0644,root,root,0755) %{_mandir}/man*/*.gz %attr(0755,root,root)%{_sbindir}/* %config(noreplace) %verify(not size md5 mtime) %{_sysconfdir}/smartd.conf %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir}
Plik nie jest bardzo skomplikowany. W preambule jedyne ciekawostki to użycie Obsoletes: i zdefiniowanie pomocniczego makra %{sub_release} które ułatwia mi poradzenie sobie z dziwną nazwą pakietu źródłowego (*-5.1-1.tar.bz2). Bo nie mogę użyć w pliku .spec jako wersji po prostu tego 5.1-1, znak minusa jest niedozwolony przy określaniu wersji.
Sekcja %prep też normalna, tylko ostrzegam RPM że rozpakowany katalog ma niestandardową nazwę. W sekcji %build definiuję zmienne dla "make" - ustawiam LDFLAGS i CFLAGS. Przy ustawianiu CFLAGS od razu dodaję definiowanie stałej CONFIGFILE na potrzeby kompilatora. To spowoduje, że zmieniając makro %{_sysconfdir} zmiana ta znajdzie odzwierciedlenie w skompilowanym kodzie binarnym i programy będą szukać swojego pliku konfiguracyjnego we właściwym miejscu.
Następnie sekcja instalacyjna: przeklejone z Makefile linijki, tyle że nieco zmienione - pozamieniałem odwołania do katalogów docelowych makrami RPM-a, przy okazji usunąłem też niechciane pliki dokumentacji.
Na tym etapie artykułu opisywanie tak wtórnej sekcji %files byłoby chyba już przesadą, prawda? Więc jej nie opiszę, chyba jest już jasna i zrozumiała.
4. Budowanie pakietu się udaje, w końcu to już któryś z rzędu pakiet :)
Ostatni pakiet jaki poddam oglądowi w tym artykule to zgv. Zgv to bardzo sympatyczna i dopracowana przeglądarka używająca svgalib. Jeśli nie używasz framebuffera, to Zgv pewnie ci się spodoba.
1. Źródła od jakiegoś już czasu nie są aktualizowane. Ale Zgv jakoś specjalnie tego nie potrzebuje, swoje zadania wypełnia dobrze i daje się nadal kompilować coraz nowszymi wersjami gcc.
2. Próbna instalacja.
Zgv jest podobna do smartmontools - też używa tylko gotowego Makefile. Flagi kompilatora da się tak samo przekazać w linii poleceń, przy czym trzeba dodatkowo przekazać jedną opcję "-D..." wskazującą na bazę kolorów X-ów - jest ona wykorzystywana przy pokazywaniu niektórych plików .xpm. Można skompilować i bez tego, ale że mam X-y zainstalowane to nic mi nie szkodzi. Zgv nie zna zmiennej $DESTDIR, ale instalację można przekierować redefiniując po kompilacji zmienną PREFIX. Tyle, że Zgv tak samo jak smartmontools próbuje zmieniać właściciela/grupę plików na root.root. Nawet nie chce mi się zastanawiać, po prostu zrobię tak samo, jak w przypadku smartmontools. Zainstaluję ręcznie. Tutaj Makefile jest ciekawszy, bo główny Makefile tylko "zsyła" proces instalacji do podkatalogów src i doc, Analizuję sobie tamte Makefile i dochodzę do wniosku, że Zgv instaluje tylko binarkę i strony info/man. Hej, tyle to i ja potrafię :) Będzie łatwo.
3. Plik .spec
Summary: Przeglądarka grafiki oparta na svgalib Name: zgv Version: 5.6 Release: 1 License: GPL Group: Grafika Source: %{name}-%{version}.tar.bz2 BuildRoot: /var/tmp/%{name}-%{version} %description Zgv potrafi szybko i sprawnie wyświetlać pliki graficzne na vt, wykorzystując do tego celu bibliotekę svgalib. Może pokazywać miniaturki itp. W ogóle to dobry kawałek softu. %prep %setup -q %build make CFLAGS="$CFLAGS -DRGB_DB_FILE=\\\"\${RGB_DB}\\\"" %install rm -rf %{buildroot} install -D %{_builddir}/%{buildsubdir}/src/zgv %{buildroot}%{_bindir}/zgv install -D %{_builddir}/%{buildsubdir}/doc/zgv.1 %{buildroot}%{_mandir}/man1/zgv.1 install -d %{buildroot}%{_infodir} makeinfo --no-split %{_builddir}/%{buildsubdir}/doc/zgv.texi -o %{buildroot}%{_infodir}/zgv.info %files %defattr(0644,root,root,0755) %{_mandir}/man1/zgv.1.gz %attr(0755,root,root)%{_bindir}/zgv %{_infodir}/zgv.info.gz %clean rm -rf %{buildroot} %{_builddir}/%{buildsubdir} %post install-info %{_infodir}/zgv.info.gz %{_infodir}/dir %preun install-info --delete %{_infodir}/zgv.info.gz %{_infodir}/dir
Plik jest prosty. Przy kompilacji redefiniuję odpowiednio CFLAGS na potrzeby make (przełącznik RGB_DB_FILE przekleiłem z oryginalnego pliku Makefile). Instalacja też jest prosta, z katalogu src kopiuję binarkę, z doc manual. Potem tylko generuję pojedynczy dokument info i umieszczam go na właściwym miejscu. Zrezygnowałem z podawania BuildReq:/PreReq: w preambule, bo mi się po prostu już nie chciało wpisywać tam zależności od install-info i makeinfo :) Gdy robi się pakiety dla siebie, to można sobie na to pozwolić. Sekcja %files jest nieciekawa, a sekcje %post i %preun standardowe.
4. Budowanie pakietu.
Pakiet się buduje i jest po chwili gotowy do instalacji. Więcej naprawdę nie można wymagać.
W tym momencie mogę chyba uznać, że pokazałem jak wyglądają przeciętne czynności wykonywane przy paczkowaniu programów, jakie są najczęściej spotykane problemy i jak sobie z nimi radzić. Zaprezentowane do tej pory informacje powinny pozwolić na budowanie domowej jakości pakietów, może nie nadających się do ogólnoświatowej dystrybucji, ale za to dopasowanych do konkretnej maszyny i spełniających dobrze swoje zadanie. W razie wątpliwości jeszcze raz przeglądaj przykłady i spróbuj zbudować samodzielnie paczki dla prostych programów, najlepiej takich, które nie zawierają zbyt wielu plików i krótko się kompilują. Wydaje mi się, że całą niezbędną wiedzę już powinieneś mieć przyswojoną - teraz musisz tylko potrenować. Gdy proste paczkowanie zacznie Ci iść w miarę gładko, możesz zabrać się za czytanie kolejnych stron tego kursu, wtedy będzie on łatwiejszy do przełknięcia.
Oryginalnie strona ta rozpoczynała trzecią serię tekstów "o budowaniu pakietów RPM". Podchodzę nieco niepewnie do niej, bo... Gdy zaczynałem pisać pierwszą serią, udumałem sobie prosty rozkład: napiszę trylogię, część pierwszą o składni i podstawowej ideologii, drugą bardziej praktyczną, no i trzecią w której wymienię jakieś bardziej ogólne uwagi, których nie byłem w stanie sensownie wpleść w fabułę dwóch poprzednich tomików.
Ale minęło już tyle czasu odkąd postawiłem ostatnią kropkę w części drugiej, że sam nie wiem co miałem zamiar tutaj napisać. Nie mam pomysłów, pozapominałem już. Niepotrzebnie zrobiłem sobie taką długą przerwę. Ale OK, zrobię to tak: zacznę pisać o pierwszej rzeczy która mi przyjdzie do głowy, a potem może jakoś, poprzez skojarzenia i nawiązywanie, to się potoczy. Ten tomik może okazać się bardziej wodolejny i przegadany niż poprzednie, może też "wyjść" dużo bardziej techniczny. Hihi, w taki sam sposób napisałem swoją maturę :)
W każdym razie ten tomik będzie na pewno ostatnim w tej serii.
OK, więc zapewne masz już podstawy pozwalające ci budować pakiety RPM. To dobrze. Teraz nie musisz już tak bardzo przejmować się zastanawianiem nad tym, jakimi środkami możesz osiągnąć cel - możesz zacząć szlifować technikę ;)
Nie ma jednego, uniwersalnego sposobu na budowanie pakietów. Liczy się tak naprawdę tylko rezultat. Zwłaszcza jeśli robisz pakiety tylko na swoje potrzeby, lub na potrzeby małej sieci - gdzie to ty jesteś administratorem. Oczywiście są pewne rzeczy, pewne problemy, które można rozwiązać na wiele sposobów - przy czym któryś ze sposobów będzie zawsze uważany za "bardziej elegancki" niż inne. Ale w bardziej ogólnych sprawach masz wolną rękę. Dlatego możesz sobie wyznaczyć pewne priorytety, do których będziesz przykładał większą wagę. Ignorując zupełnie inne kwestie.
Nie wiem co będzie dla ciebie najważniejsze, nie chcę też w żaden sposób niczego ci narzucać - ale może objaśnię nieco poprzedni akapit na swoim przykładzie, porównując np. pakiety które ja sobie buduję z tymi które produkują developerzy PLD (nie, to nie ma być chwalenie ani ganienie żadnej ze stron, po prostu pakietom PLD regularnie się przyglądam, jako że chyba z wszystkich dystrybucji to właśnie w PLD robi się najlepszy użytek z funkcji oferowanych przez RPM - nawet w RH, kolebce RPM, nie wykorzystuje się tego narzędzia w tak pełny sposób). Ale wracając do właściwego tematu - w PLD daje się łatwo zauważyć np. wielojęzyczność pakietów - opisy pakietów, ich kategorie - wszystko to jest przetłumaczone na kilkanaście języków, w każdym pakiecie. Podobnie z plikami tłumaczeń dla programów czy stronami manuali. Wiadomo, PLD to dystrybucja, nie wiadomo jakiej narodowości będą użytkownicy. Ale ja nie muszę tego robić - mojego systemu używają tylko osoby dobrze mi znane. Więc nie muszę się bawić w pisanie wielojęzycznych opisów. O, albo podział oprogramowania na mniejsze paczki - PLD, jak i inne dystrybucje, dzieli pakiety według klucza xxx-libs i xxx-devel, dodatkowo niektóre większe projekty (np. Perl czy XFree86) są dzielone na dodatkowe, mniejsze jeszcze komponenty, np. w XFree86 będą to poszczególne rodzaje fontów, sterowników do różnych kart graficznych itp. Cudowne w dystrybucji, bo pozwala użytkownikom o różnych wymaganiach skompletować sobie tylko te niezbędne im paczki, pomijając inne, zbyteczne (np. Niemiec nie będzie miał zapewne potrzeby instalowania fontów iso-8859-2). Bardzo fajne w dystrybucji, ale całkowite marnowanie czasu w pakietach "na osobisty użytek". Ja zwykle dobrze wiem już w momencie budowania pakietu co też będzie mi potrzebne. Mogę pousuwać zbędne mi pliki, dokumentację, części programu - często tworząc pakiet dużo mniejszy niż ten z dystrybucji. Do tego mam mniej pakietów zarejestrowanych w systemie, bo każdy pakiet staje się odrębną całością (np. całe XFree86, w konfiguracji jakiej potrzebuję, mam w jednym, jedynym pakiecie - a nie w osiemnastu pomniejszych pakietach połączonych siatką zależności).
Ja często rezygnuję z tzw. i18n, gdy "locale" (np. pliki tłumaczeń) są niekompletne lub złej jakości - operowanie w aplikacjach anglojęzycznych nie jest dla mnie problemem. Oczywiście w dystrybucji nie można sobie na to pozwolić, ale ja mogę. I pozwalam sobie. Dystrybucje powinny mieć hiper-poprawne pakiety, co oznacza że pakiet źródłowy powinien dokładnie opisywać wszystkie swoje zależności, zwłaszcza te wymagane przy budowaniu (BuildRequires:) - ja tego robić nie muszę, bo znam swój system na tyle dobrze, by wiedzieć że zawsze mam kompletne środowisko developerskie - dlatego nie muszę umieszczać w zależnościach np. autoconfa czy gcc, bo przecież jestem świadomy, że te narzędzia są niezbędne. Ale robię to tylko dlatego, że buduję pakiety dla siebie - nie oznacza to w żadnym wypadku niechlujności, tego nie można powiedzieć. Po prostu pomijam części dla mnie zbyteczne. Ktoś inny może mieć oczywiście inne priorytety.
Dodatkowo mam kilka innych "bzików" które objawiają się w różnych sytuacjach. Np. z uporem maniaka redukuję dokumentację info w każdym pakiecie do pojedynczych, wielkich plików info (zamiast dziesiątek mniejszych, np. jeden gcc.info zamiast gcc.info-1, gcc.info-2 itp.). Z niechęcią podchodzę do dokumentacji w /usr/share/doc, zwykle ją usuwając z pakietów, chyba że jest naprawdę dobra (np. dokumentacja mutta czy slrn). Wycinam z pakietów części, których na pewno nie będę używał (np. online-help gimpa). Kompiluję programy z często dziwacznymi przełącznikami. Ogólnie to wszystko podpada pod jedno "schorzenie" - staram się jak najbardziej system ściągnąć "w sobie", realizując tytułową kontrakcję. Oznacza to pakiety dopasowane jak najbardziej do pracy z sobą, z oszlifowanymi krawędziami, skompilowane tak, by zajmować jak najmniej miejsca i pamięci operacyjnej (bez zagrażania jednak wydajności). Pakiety okrojone ze zbędnych modułów - znowu, redukcja rozmiaru. To jest mój główny priorytet i odstępuję od niego niezmiernie rzadko. Skoro już doszedłem do tego niewątpliwie bardzo ważnego punktu ;) i zająłem stanowisko, mogę zaprezentować kilka środków których można użyć aby zrealizować ową "kontrakcję". Jeszcze raz: to moje, osobiste nastawienie. Jeśli będziesz uważać to, co wyłożę za stratę czasu - to nie martw się, wcale nie musisz być od razu szalony - każdy ma pełne prawo mieć swoje, własne priorytety. To taka zaleta bycia linuksiarzem - jest jeden główny nurt, ale rzeka jest na tyle szeroka, że wykształca się w niej wiele "wariacji", nawet nurtów wstecznych - tak że każdy znajdzie swoją niszę.
Pierwsze co mi przychodzi do głowy w związku z "dodatkami" do RPM to stripowanie. Plików wykonywalnych i bibliotek. Tak, jak to opisałem w artykule o kompilowaniu programów. Tutaj jednak pojawia się pewna niedogodność - w pakietach RPM całość procesu budowania jest zautomatyzowana. Powstały pakiet nie będzie zawierał tak silnie zestripowanych binarek. Można oczywiście potem (po instalacji) wejść np. do /usr/bin czy /usr/lib i ręcznie zestripować pliki, ale po takim zabiegu zmieni się ich rozmiar, data modyfikacji itp., przez co rpm zacznie uważać te pliki za "zmienione" (no i słusznie) co może być irytujące przy weryfikowaniu zainstalowanych pakietów za pomocą "rpm -V".
RPM jest jednak silnie zautomatyzowanym procesem i po odrobinie dłubania doszedłem do wniosku, że to może stać się zaletą. Można mianowicie zmusić rpmbuild do stripowania plików za nas - ba, przy odrobinie "magii" może być to dla użytkownika rpmbuild niezauważalne (nie musi nic dodatkowego robić), pakiety będą powstawały od razu odpowiednio zestripowane, a ponieważ będzie to robił automat z samego użytkownika zostanie zdjęte brzemię wyszukiwania plików do stripowania i dobierania innych flag do bibliotek statycznych a innych do plików wykonywalnych.
Aby coś takiego uzyskać konieczna będzie jednak pewna modyfikacja systemu RPM. Nic wielkiego, nie wiąże się to z rekompilowaniem RPM-a ani niczym takim. Nawet nie trzeba mieć do tego uprawnień root-a. Ot, poprawka kilku makrodefinicji a każdy następny zbudowany pakiet będzie doświadczał radosnego cudu agresywnego stripowania.
rpmbuild operuje przy budowaniu pakietu w kilku fazach, nie chcę się teraz w żadną z nich zagłębiać więc wystarczy jeśli powiem, że przy każdym momencie, na początku i na końcu "wykonywania" każdej z sekcji pliku .spec istnieje możliwość podpięcia globalnie swoich działań, w formie skryptów shellowych. Proces stripowania musi znaleźć się gdzieś pomiędzy sekcją %install a %files - po %install wszystkie pliki są już na miejscu i można je zacząć zmieniać, a przed %files jeszcze nie są one wliczane w skład pakietu, jeszcze można zmieniać ich rozmiar itp. Logiczne byłoby podpięcie się na końcu fazy instalacyjnej (%install). I, prawdę mówiąc, RPM (myślę cały czas o "waniliowej" wersji 4.1, bo takiej aktualnie używam) tak robi. Ma tam podpięte np. kompresowanie stron manuali czy info oraz właśnie... stripowanie. Ale stripowanie bardzo słabe (IMO). Stripuje tylko binarne pliki wykonywalne, a i to nie zawsze. Pomija w ogóle stripowanie bibliotek, na dodatek flagi stripujące można uczynić bardziej "pazernymi", jeszcze bardziej redukując rozmiar plików.
Ja stawiam na prosty sposób postępowania: binarki stripować tak bardzo, jak tylko się da. Biblioteki dynamiczne, niezależnie od tego czy są wykonywalne (mają bit +x), czy też nie, należy tak samo potraktować. Z bibliotekami statycznymi jest nieco trudniej, bo trzeba użyć nieco innych flag przy stripowaniu - ale również należy to zrobić. Do rozpoznawania plików należy używać połączonej logiki rozpoznawania atrybutów plików jak również, kontrowersyjnie kosztownego, wywoływania "file" - bowiem nie każdy plik wykonywalny jest binarką ELF którą można stripować, nie każdy plik z rozszerzeniem *.so jest biblioteką dynamiczną, nie można też ograniczać się do "standardowych" lokacji takich jak np. /usr/lib i /usr/bin, bo równie dobrze stripować można też np. pluginy w różnych programach, które zwykle są bardzo podobne do bibliotek i mogą być jak takowe traktowane (np. pluginy xmms czy gaim-a). Po uwzględnieniu tych wszystkich warunków jak też analizie błędów popełnionych w oryginalnych, rpm-owych mechanizmach stripujących zestawiłem własny podsystem dla rpm, podsystem który stripuje silniej, agresywniej, jeszcze lepiej usuwa plamy z tłuszczu a jego ActivPiana® wnika teraz w każde włókno :)
Ta modyfikacja RPM-a opiera się na pojedynczym skrypcie shellowym i ustawieniach makr aktywujących ten skrypt w odpowiednim momencie. Wpis zmieniający makra można wykonać w lokalnym ~/.rpmmacros danego użytkownika, można też umieścić go globalnie w głównym pliku makr rpm-a (zapewne w /usr/lib/rpm/macros), ale tego bym nie polecał - lepiej jest zachowywać "główną" instalację rpm tak bardzo "nienaruszoną", jak to tylko możliwe. Ewentualne modyfikacje proponuję wprowadzać lokalnie - poza tym łatwiej później tym zarządzać. Skrypt można umieścić w dowolnym miejscu, w makrach podaje się potem ścieżkę dostępu do niego. Skrypt jednak polecam umieścić w /usr/lib/rpm, ze względu na to, że w /usr/bin raczej nie ma czego szukać (nie jest "programem" który samodzielnie coś wykonuje), no i umieszczenie go tam, gdzie inne skrypty tej klasy lądują wydaje mi się dosyć eleganckim wyjściem. Ma unikatową nazwę, więc łatwo można go potem znaleźć i ew. usunąć.
Ze względów "bezpieczeństwa" potrzebne modyfikacje zamieszczam w formie plików do pobrania - nie ma sensu ich przeklejać na WWW, poza tym składnia tych akurat makr jest bardzo wrażliwa na jakiekolwiek przypadkowe złamanie wierszy itp., więc lepiej nie ryzykować.
Do pobrania: skrypt stripujący "brp-strip-max"
#!/bin/sh
if [ -z "$RPM_BUILD_ROOT" -o "$RPM_BUILD_ROOT" = "/" ]; then
exit 0
fi
# Stripowanie wykonywalnych binarek ELF.
for f in `find $RPM_BUILD_ROOT -type f \( -perm -0100 -or -perm -0010 -or -perm -0001 \) -exec file {} \; | \
grep -v ' shared object,' | sed -n -e 's/^\(.*\):[ ]*ELF.*/\1/p'` ; do
strip -R .comment -R .note -R .note.ABI-tag $f || :
done
# Stripowanie dynamicznych obiektów ELF z rozszerzeniem .so (prawdopodobnie biblioteki lub pluginy)
for f in `find $RPM_BUILD_ROOT -type f -regex '.*\.so\(\(\.[0-9]*\)*\)*' -exec file {} \; | \
grep ' shared object,'|sed -n -e 's/^\(.*\):[ ]*ELF.*/\1/p'` ; do
strip -R .comment -R .note -R .note.ABI-tag $f || :
done
# Stripowanie statycznych bibliotek
for f in `find $RPM_BUILD_ROOT -type f -name '*.a' -exec file {} \; | \
grep ' current ar archive'|sed -n -e 's/^\(.*\):[ ]*current ar archive.*/\1/p'`; do
strip -g -X -x $f || :
done
oraz : modyfikacja makr "rpmmacros-strip-addon"
%__os_install_post \
/usr/lib/rpm/brp-compress \
%{?_no_powerstrip:/usr/lib/rpm/brp-strip; /usr/lib/rpm/brp-strip-comment-note} \
%{!?_no_powerstrip:/usr/lib/rpm/brp-strip-max}\
%{nil}
Po skopiowaniu skrypt stripujący umieść w /usr/lib/rpm (i nadaj mu ew. prawa do wykonywania przez wszystkich zainteresowanych), a zawartość rpmmacros-strip-addon dodaj do swojego lokalnego pliku ~/.rpmmacros. I to by była cała "instalacja". Zastosowanie jest proste: buduj pakiety jak zwykle, całość "automagicznie" wejdzie do gry i zestripuje wszystkie pliki tak, by maksymalnie pomniejszyć biblioteki, pluginy i binarki. Nie trzeba tego już w żaden sposób uaktywniać (robi to ta mała modyfikacja makr w ~/.rpmmacros)
Nieco bardziej technicznie: modyfikacja wyłącza domyślne, rpm-owe mechanizmy stripujące, jako że sama działa IMO dużo lepiej - uruchamianie jeszcze tych "standardowych" redhatowych stripperów nie popsułoby nic, ale też nic by już nie dało, więc nie ma sensu uruchamiać tych skryptów na darmo. Tak agresywne stripowanie potencjalnie może mieć nieznane skutki uboczne. Potencjalnie, bo ja go używam już od długiego czasu i jak do tej pory wszystko działa nienagannie, a modyfikacja ta brała już udział w budowaniu przeróżnych pakietów, takich jak np. gcc, XFree86 czy glibc - więc można uznać, że działa zgodnie ze specyfikacją i jest całkowicie bezpieczna. Jeśli jednak zechesz ją wyłączyć, załóżmy że nie w przypadku awarii, ale np.... o, mam, np. gdy chcesz jednak zbudować jakiś pakiet z pełnym "debug info" w bibliotekach i binarkach, zaimplementowałem od razu tylne wyjście - całą tę modyfikację można łatwo deaktywować definiując makro o nazwie '_no_powerstrip'. Jeśli w jakimś pliku .spec umieścisz linijkę:
%define _no_powerstrip 1
to przy budowaniu tego speca modyfikacja pozostanie pasywna, w zamian robiąc "fallback" do domyślnych, rpm-owych mechanizmów stripujących. To chyba rozsądne rozwiązanie.
Obok stron manuala to właśnie format Info jest najpopularniejszym sposobem prezentowania dokumentacji pod Linuksem. Jest on spotykany praktycznie tylko w projektach GNU (GNU w zasadzie nie uznało stron manuala jako sensownego formatu, stąd strony manuala załączane w projektach GNU są tradycyjnie bardzo skąpe i zwykle zawierają dokładnie to samo, co wynik 'polecenie --help' - cała "prawdziwa" dokumentacja jest zawarta w stronach info. Generalnie Info zawsze zawierają więcej informacji niż manuale, o ile oczywiście można wybierać między tymi formami dokumentacji.
Ale wracając do rzeczy - Info różni się od manuali kilkoma "detalami" - z czego niektóre są ważne dla użytkowników, niektóre zaś dla administratorów. Użytkownik zapewne "doceni" koszmar jakim jest oficjalna przeglądarka stron Info, mogąca służyć jako przykład jak nie powinno się robić takich rzeczy (ale jest po części tłumaczona tym, że jest zasadzie kawałkiem kodu Emacsa wyrwanym poza swoje środowisko). Użytkownik też zauważy zalety, jakie niesie z sobą hipertekstowość Info, a więc łatwe do nawigowania spisy treści i rozdziały, albo możliwość uruchomienia przeglądarki od razu w konkretnym rozdziale dokumentacji. Administrator jednak zainteresuje się "technikaliami" jakie związane są z Info. Tak samo musimy zrobić i my - niestety.
W przypadku stron manuala instalacja jest prosta - wystarczy umieścić plik strony manuala w odpowiednim katalogu i presto! - już jest dostępny. Łatwiej nie można. Info ma jednak inny "pomysł" na załatwianie takich spraw: po pierwsze, jeden dokument Info może być albo zawarty w jednym pliku, albo podzielony na dziesiątki mniejszych - nie wpływa to jednak w żaden sposób na treści zwracane przez przeglądarkę, dzielenie dokumentacji na kawałki miało na celu jedynie "odciążenie" przeglądarki od ładowania za jednym zamachem całej dokumentacji, która, w zależności od opisywanych treści, może mieć nawet kilka megabajtów (np. około 2.5MB dla glibc.info). Po drugie, w każdym katalogu z dokumentami Info (np. /usr/share/info) musi istnieć specjalny plik-indeks, o nazwie "dir" (choć np. alternatywna przeglądarka Info/manuali, pinfo, nie wymaga już tego pliku - niestety oprócz kolorowania nie oferuje zbyt wielu funkcji oryginalnej przeglądarki Info czy choćby nawet pagera "less", autor nie miał chyba wystarczająco wiele czasu by się nią zająć - ktoś mógłby się nią zaopiekować).
Więc są dwie charakterystyczne cechy Info - pierwsza to rozbijanie jednej dokumentacji na kilka/kilkanaście plików (nie zawsze jednak spotykane), druga to istnienie czegoś nieznanego w świecie manuali - centralnych indeksów.
Wieloczłonowość Info
Cecha ta wiąże się obecnie raczej z odczuciami estetycznymi niż z racjonalnością, nie ma już wiele powodów by utrzymywać pliki Info w "wieloczłonowej" formie, ja w każdym razie wolę, gdy glibc instaluje jeden większy plik glibc.info niż 20 mniejszych, numerowanych pliczków. Ot, tak jakoś wolę. Sam nie wiem tak naprawdę dlaczego. Pakiety z oprogramowaniem jednak będą zwykle instalowały "rozczłonkowaną" dokumentację, częściowo dlatego, że taki jest domyślny tryb pracy narzędzia makeinfo którego używa się do tworzenia plików Info. Narzędzie to operuje na specjalnych plikach źródłowych texinfo, o rozszerzeniu (zwykle) .texi (geneza tego formatu jest dosyć pokrętna, to mariaż kilku wcześniejszych formatów ze szczyptą magii którą dodał RMS). Z plików tych można tworzyć, obok domyślnych plików Info, także dokumentację HTML, DocBook czy ogólny XML. W momencie tworzenia dokumentacji można za pomocą opcji '--no-split' wymusić stworzenie jednego, dużego pliku zamiast wielu małych. Chcesz tak robić? Nie musisz, nie ma to wpływu na samą funkcjonalność Info, ale tak jest zwykle z "względami estetycznymi". W każdym razie, po instalacji próbnej pakietu zawierającego dokumentację Info sprawdź, czy zamieszcza on dokumentację dzieloną na dziesiątki małych plików. Zwykle tak właśnie będzie (choć kilka nowych projektów jakie widziałem już tak nie robi! :) Cały "numer" polega na tym, by przy budowaniu pakietu wygenerować na nowo całą dokumentację Info. Czyli gdzieś w sekcji %install będziesz musiał usunąć całą zawartość %{buildroot}%{_infodir} i od nowa skompilować dokumenty Info. Z moich doświadczeń wynika, że najwygodniej jest wejść do katalogu zawierającego źródła texinfo dokumentacji, a stamtąd uruchomić coś w stylu:
makeinfo --no-split foo.texi -o %{buildroot}%{_infodir}/foo.info
To w 90% przypadków załatwia sprawę. Czasem trzeba jeszcze podać jakieś katalogi z "inkludami" dla dokumentu texinfo, opcją "-I". A czasem jeszcze bardziej nakombinować - najlepiej w takich chwilach usunąć z danego katalogu wszystkie wynikowe *.info, uruchomić "make" i patrzeć, w jaki sposób są na nowo generowane. Czasem trudne może być samo znalezienie odpowiednich plików źródłowych, gdy projekt jest bardzo rozległy i ma wiele podkatalogów - wtedy pomaga zwykle wyszukiwanie plików *.info, pliki źródłowe zazwyczaj leżą gdzieś w pobliżu.
W każdym razie powinieneś mieć teraz w %{buildroot}%{_infodir} już na nowo "zrobione" dokumenty Info, w zgrabniutkich, dużych plikach. Po jednym na każdy dokument Info.
Czasem zdarzają się programy, które dostarczają dokumentację Info już w odpowiednio "skondensowanej" formie i nie trzeba tego ręcznie zmieniać. Tutaj mam jedną radę czysto techniczną - w pliku .spec należy wówczas używać czegoś takiego:
%{_infodir}/*
Bo jeśli kiedyś, w przyszłości pakiet zacznie jednak rozbijać Info na malutkie Infiki to możesz tego nie zauważyć nawet. Dlatego lepsze jest albo wymienianie każdego pliku Info osobno (jeśli jest jeden-dwa, to żaden problem), albo użycie wzorca w stylu
%{_infodir}/*.info.gz
bo taki wzorzec nie powinien nigdy pasować do Info wieloczłonowych, i "jakby co", to rpmbuild da nam cynk ;) Tutaj warto też od razu podać inną regułę: jeśli nie ingerujesz w to, co "make install" wrzuca do katalogu %{_infodir}, to zrób chociaż jedno - usuwaj pliki "dir" z pakietów. Te pliki nie mają prawa nigdy znaleźć się w końcowym pakiecie, wyjaśnienie znajdziesz poniżej.
Plik indeksu Info
Indeksy są tą bardziej kłopotliwą częścią całego systemu Info. Indeks taki, zawarty w pliku "dir" funkcjonuje jako coś w rodzaju "spisu treści" - jest to plik tekstowy w który zarejestrowany musi być każdy zainstalowany w danym katalogu dokument Info. Gdy uruchomisz samo "info", bez podania tematu jaki cię interesuje, to zobaczysz stronę "początkową" która jest właśnie wszystkimi dostępnymi indeksami z wszystkich katalogów z dokumentami Info. Każdy plik Info zawiera w nagłówku pewne informacje, opisujące dane jakie powinien umieścić w indeksie. Czasem będzie to jeden wpis, czasem kilkanaście bardziej szczegółowych.
Procedura "instalacyjna" jest dosyć uciążliwa - każdy nowy plik należy zarejestrować w indeksie za pomocą programu "install-info. Jeśli potem ma się zamiar usunąć plik jakiegoś dokumentu Info, to używając "install-info" najpierw należy go wyrejestrować, a dopiero potem usunąć. Może to być skrajnie uciążliwe. Niewygodne jest choćby to, że by wyrejestrować jakiś plik trzeba mieć go ciągle dostępnego (inaczej install-info nie wie, które wpisy usunąć z indeksu). Niestety trzeba brać na siebie ten cały cyrk, bo inaczej info nie będzie miało pojęcia o dostępności jakiegoś podręcznika, nie będzie on zarejestrowany w indeksie. Można sobie z tym problemem radzić na kilka sposobów, ale skoro ma to być artykuł o RPM, to nas interesuje tylko ten fragment, który jest powiązany z budowaniem/instalowaniem pakietu.
Można wykorzystać sekcje %post i %preun aby rejestrować/wyrejestrowywać dokumenty Info. Jest to "najniższa" metoda na robienie tego, bardzo męcząca jeśli idzie o pisanie plików .spec. Wiąże się z wklepywaniem dwóch linijek poleceń dla każdego dokumentu Info zawartego w pakiecie. Można też zrezygnować z rejestrowania plików Info i robić to ręcznie, sporadycznie - ale to nie pasuje do automatyzacji systemu RPM. Można też uprościć sobie pracę i zamiast rejestrować pojedyncze pliki po prostu usuwać stary indeks i generować nowy, używając wszystkich dostępnych dokumentów Info - ale to może trwać nawet kilkanaście, a na wolnych maszynach kilkadziesiąt sekund - wydłużając o tyle instalację każdego pakietu zawierającego dokumenty Info. Ja na swoje potrzeby wybrałem coś pośredniego - napisałem skrypt, który sam zajmuje się dbaniem o aktualność indeksu, robiąc to raczej inteligentnie, pomijając pliki już zarejestrowane, wyrejestrowując pliki już niedostępne, oraz dorabiając minimalne wpisy w indeksie dla dokumentów Info, które same takich wpisów nie zapewniają (niedoróbki, ale się trafiają - np. dokumentacja "bc"). W przyszłości może funkcję wyrównywania layoutu gotowego indeksu, np. w PLD na razie robi się to pracowicie patchując dokumentację Info - ale mój skrypt mógłby to robić z dowolnymi dokumentami Info, przezroczyście dla użytkownika i administratora.
Skrypt uruchamia się w kontekście jakiegoś katalogu z dokumentacją, tworzy sobie tam jeden mały ukryty plik (na późniejszy użytek), a następnie dba o aktualność indeksu. Jeśli nie trzeba nic zmieniać, to nic nie robi. Jeśli coś doszło, to on to wykryje. Jeśli czegoś ubyło, to też to wykryje. Skrypt wykorzystuje ogólnodostępne narzędzia, takie jak zgrep (z pakietu gzip), install-info (tak czy siak konieczny, z pakietu texinfo) no i diff. Oprócz tego kilka tradycyjnych shellowych "narządek" takich jak rm, ls czy cut. Starałem się zachować wymagania na jak najniższym poziomie, tak by nie trzeba było używać basha czy perla. I udało się - skrypt spełnia doskonale swoją rolę, jest bardzo szybki, a dodatkowo mogę z radością zakomunikować, że łata przy okazji kilka niedostatków samego install-info.
Wystarczyło go wrzucić do /usr/bin lub /usr/local/bin (lub gdziekolwiek indziej), a potem jeszcze zmodyfikować nieco mój ~/.rpmmacros poprzez dodanie linijki:
%tidyinfo /usr/bin/tidyinfo %{_infodir}
Od tej chwili w plikach .spec mam dostępne nowe makro - %tidyinfo. Jeśli pakiet zawiera dokumentację Info, to w sekcjach %post oraz %postun wystarczy je wywołać. Reszta się "zrobi sama" - nieważne ile plików zawierał pakiet, czy były dodawane czy usuwane itp. Niezależnie od tych szczegółów wystarczy, że wpiszę w pliku .spec
%post %tidyinfo %postun %tidyinfo
Makra tego używam z powodzeniem już od jakiegoś czasu i znacznie uprościło mi ono zarządzanie dokumentacją Info. Dodatkowo jeśli zechcę dodać jakąś funkcjonalność, to wystarczy prawdopodobnie przerobić nieco skrypt - pakiety nie muszą być przebudowywane.
O, przy okazji opracowałem sobie ciut lepszy plik podświetlania składni dla ViM-a - nieco poprawiony, podświetla makro %tidyinfo, tagi NoSource/NoPatch oraz parę innych rzeczy których w oryginale zabrakło. Wystarczy go wrzucić do ~/.vim/syntax/
Inne rozmaitości związane z Info
Regenerowanie dokumentacji Info stwarza kilka nowych możliwości. Po pierwsze można ustalić długość wierszy w wynikowych plikach za pomocą stosownej opcji "makeinfo" - oryginalny browser Info niestety nie dostosowuje szerokości tekstu do szerokości urządzenia - jest to, jak dla mnie, wielka wada, bo nie pozwala na optymalne wykorzystanie nieco szerszych terminali. Jeśli mamy zamiar zwykle używać urządzenia o jednej, stałej szerokości, to możemy wygenerować sobie dokumentację pod jego szerokość.
Druga opcja którą może warto rozważyć to fakt, że "makeinfo" nie musi koniecznie produkować dokumentacji Info. Może też tworzyć np. dokumentację html. Jeśli ktoś ma zamiar przebudowywać tak czy siak większość pakietów w swoim systemie (np. systemy klasy LFS), to przy odpowiednim podejściu i odrobinie wysiłku można zbudować sobie alternatywny mechanizm dokumentacji, oparty o html. To wcale nie jest takie trudne i samemu się nad tym zastanawiam. Jakiś sprytny skrypt by wyszukiwał potrzebne strony i uruchamiał je w ulubionej przeglądarce (Ognioptak lub ELinks). Wygodna, znajoma nawigacja, możliwość zakładania zakładek, trzeba by tylko dobudować do tego jakiś szkielet który by to obsługiwał, ale to nie powinno być trudne. Zamiast Info dokumentacja w html - warto się nad tym zastanowić. Kilka skryptów, określone miejsce na dysku na cele składowiska dokumentacji, parę makr dla rpmbuild żeby całość jak najbardziej zautomatyzować. Być może kiedyś sobie coś takiego opracuję, bo pomysł zaczyna mi się podobać. Wadą jest znowu rozbicie - poszczególne węzły dokumentacji muszą być umieszczone w osobnych plikach. Do tego nie można takiej dokumentacji tak łatwo kompresować, bo pojawi się problem z jej odczytem w przeglądarkach (choć ELinks bezproblemowo odczytuje skompresowany html). Ale być może wygoda korzystania z takiej dokumentacji by przeważała. Jeśli już coś robisz samemu, to możesz to przy okazji uczynić trochę lepszym, prawda? Tutaj jednak trzeba też zaznaczyć, że strony manuala również można zamienić na html. Wtedy może warto by było pójść o krok dalej i całą dokumentację systemu, man oraz info, przerobić na html i dorobić jeden, spójny interfejs który by pozwalał na więcej, niż obecnie man lub info (np. wyszukiwanie manuali według słów kluczowych w opisie lub treści - coś w stylu bardziej zaawansowanego "apropos")... Mi by się takie coś chyba spodobało, ELinks jako przeglądarka byłby znakomity...
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.
A teraz pora na zapoznanie się z bardzo przyjemną funkcją rpm-a, jaką jest tzw. budowanie multipackage. Polega ono na tym, że jeden pakiet źródłowy rpm (albo ogólniej jeden plik .spec) produkuje więcej niż jeden pakiet wynikowy. Tradycyjnie chodzi o to, by pliki powstałe w kompilacji jakiegoś programu rozbić na kilka pakietów, tak by użytkownik mógł nie instalować całości programu. O ile jest to oczywiście możliwe. Wtedy jeden program ma, z punktu widzenia systemu pakietów, budowę modularną i to końcowy użytkownik/administrator decyduje, które części są mu potrzebne. Albo mamy program złożony z paru komponentów, które można jednak wymieniać/aktualizować korzystając z innych źródeł - np. XFree86 instaluje libfreetype. Jeśli ją wydzielić do osobnego pakietu, to będzie potem ją można aktualizować bez ingerowania w pozostałe części XFree86. Brzmi to może bardzo skomplikowanie, ale jest w praktyce duużo prostsze od multisource.
Budowa specfile przeznaczonego do budowy "mnogopaków©" ;) nie różni się specjalnie od budowy zwykłego speca. Różnica polega na tym, że niektóre sekcje są replikowane dla każdego pakietu wynikowego, a więc np. jest kilka sekcji %files. Nic wielkiego. Czas na przykład, ale nie wiem co wybrać. O, mam: coreutils. Coreutils to połączenie sh-utils, textutils i fileutils w jedną dystrybucję. Wygodne, bo faktycznie te programy tworzą rdzeń każdego systemu więc nie ma sensu rozprowadzanie ich w osobnych paczkach. W każdym bądź razie coreutils to produkt GNU. Jako produkt GNU ma obszerną dokumentację w formacie Info oraz bardzo słabą dokumentację w postaci stron manuala. Stąd wniosek, że dokumentacja w formie stron manuala nie jest być może w ogóle przydatna, skoro równolegle jest dostępna dużo lepsza dokumentacja Info (naprawdę, strony manuala produkowane przy projektach GNU to zwykle coś w stylu "ls --help" - takie ostentacyjne olewanie manuali). Ale ja sam ciągle nie wiem, czy nie jestem tak przyzwyczajony do manuali że jednak chcę je mieć. Dlatego przy budowaniu coreutils powstaną dwa pakiety - jeden zawierający programy i dokumentację Info, oraz drugi zawierający tylko strony manuala. Ponieważ to są już dosyć specyficzne zmiany, więc zamieszczę cały plik .spec, oznaczając części charakterystyczne dla budowy mnogopaków.
Summary: Podstawowe programy GNU używane w linii poleceń
Name: coreutils
Version: 5.0
Release: 2
License: GPL
Group: System
Source: %{name}-%{version}.tar.bz2
BuildRoot: /var/tmp/%{name}-%{version}
Obsoletes: sh-utils, fileutils, textutils, stat
%package manpages
Summary: Zbędne strony podręcznika dla coreutils
Group: Dokumentacja
Requires: %{name} = %{version}-%{release}
%description
Te programy to podstawowe polecenia ze skrzynki narzędziowej GNU. Pakiet
"coreutils" powstał w wyniku fuzji rozdzielnych dotąd pakietów GNU sh-utils,
GNU fileutils oraz GNU textutils. Większość z tych programów jest
doskonalsza od swoich uniksowych odpowiedników - różnice to zwykle
zwiększona prędkość działania, dodatkowe opcje i funkcjonalność, jak również
zniesienie pewnych arbitralnych ograniczeń które występowały w oryginalnych,
uniksowych poleceniach..
%description manpages
Pakiet ten zawiera dokumentację GNU coreutils w formacie stron manuala.
Dokumentacja ta została wydzielona do osobnego pakietu, bo jest szczątkowa.
Strony manuali w coreutils powstają w wyniku przetworzenia wyjścia
"polecenie --help", nie zawierają żadnych dodatkowych informacji. Pełna,
prawdziwa dokumentacja składowana jest w postaci stron Info, instalowanych
razem z głównym pakietem coreutils
%prep
%setup -q
%build
%configure --disable-dependency-tracking --disable-assert --disable-nls
make
%install
rm -rf %{buildroot}
make install DESTDIR=%{buildroot}
mkdir %{buildroot}/bin
mv %{buildroot}%{_bindir}/{cat,chgrp,chmod,\
chown,cp,date,dd,df,echo,false,ln,ls,mkdir,\
mknod,mv,pwd,rm,rmdir,stty,sync,true,uname} %{buildroot}/bin/
rm %{buildroot}%{_infodir}/dir
#te trzy programy znajdują się już w innych pakietach (i są akurat lepsze od
#ich odpowiedników GNU, zwłaszcza "hostname" który w wersji GNU jest,
#powiedzmy to otwarcie, śmiechu warty)
rm %{buildroot}%{_bindir}/{hostname,kill,uptime}
rm %{buildroot}%{_mandir}/man1/{hostname,uptime,su}.1
%files
%defattr(0644,root,root,0755)
%attr(0755,root,root)%{_bindir}/*
%attr(0755,root,root)/bin/*
%{_infodir}/coreutils.info.gz
%files manpages
%defattr(0644,root,root,0755)
%{_mandir}/man1/*.gz
%clean
rm -rf %{buildroot} %{_builddir}/%{buildsubdir}
%post
%tidyinfo
%postun
%tidyinfo
Pakiet jest fajny, bo przy okazji używa paru miłych cech rpm-a - po pierwsze, zastępuje (Obsoletes:) te pakiety które teraz są częścią coreutils. Po drugie, jest prosty. Po trzecie, jest mnogopakowy. OK, jak widać oznaczone są trzy bloczki - dodatkowa sekcja %files, dodatkowa sekcja %description oraz coś nowego - sekcja %package, która wygląda trochę jak drastycznie okrojona preambuła. Zacznę od %package.
Jest to podstawowa sekcja która dochodzi w takich sytuacjach. Mówi ona, że będzie produkowany dodatkowy pakiet wynikowy. Obok wywołania %package podaje się od razu nazwę tego dodatkowego pakietu - a w zasadzie to nie pełną nazwę, bo "%package manpages" nie stworzy pakietu "manpages", ale %{name}-manpages. Po prostu podane słowo/słowa zostaną po łączniku doklejone do nazwy głównego pakietu. Sekcja %package to faktycznie mini-preambuła dla tego dodatkowego pakietu. Zawiera dużo mniej wpisów, bo większa część danych jest identyczna dla wszystkich budowanych pakietów, część jest nawet zbyteczna (jak np. Source:). Ale niektóre są obowiązkowe i trzeba je podać przy każdym kolejnym %package - będą to Summary: oraz Group:. Dodatkowo można podać też inne tagi jeśli to potrzebne, ja w tym przykładzie dodaję jeszcze Requires: uzależniając pakiet ze stronami manuala od obecności głównego pakietu coreutils. Tak że %package to taka malutka replika preambuły na potrzeby tych dodatkowych pakietów. Jeśli o czymś zapomnisz, to rpmbuild i tak ci przypomni.
Drugi bloczek to dodatkowa sekcja %description. Od oryginalnej różni się tym, że od razu podana jest nazwa podpakietu do którego się odnosi. Poza tym zachowuje się tak samo, jak zwykła sekcja %description.
Trzeci i ostatni tutaj bloczek to dodatkowa sekcja %files - jest ona znowu, tak jak %description, sekcją na takich samych prawach jak oryginalne %files, tyle że od razu zaznaczone jest, jakiego pakietu dotyczy. Zaczyna byc tu widoczny pewna regularność - jeśli nie jest powiedziane o jaki pakiet chodzi, to chodzi o pakiet podstawowy, domyślny. Jeśli w linii rozpoczynającej sekcję dopisze się coś jeszcze, to znaczy że sekcja ma dotyczyć jakiegoś pod-pakietu, zadeklarowanego poprzez odpowiednią sekcję %package. Jeśli ma powstać więcej pakietów, to odpowiednio deklaruje się więcej sekcji %package, %files, %description - każdą z unikatową nazwą pakietu. Bardzo proste. Są oczywiście sekcje, które nie odnoszą się do jakiegoś konkretnego pakietu (jak np. %clean), ale inne, jak %files lub %description są przypisane konkretnym pakietom i muszą być wymienione tyle razy, ile jest pakietów. No i oczywiście istnieją też sekcje, które są opcjonalne - jak np. sekcje skryptów postinstalacyjnych (w końcu jakiś pod-pakiet może mieć specjalne wymagania co do sekwencji okołoinstalacyjnej). Po zrozumieniu tego, że duplikuje się sekcje i dopisuje do nich nazwy pakietów (zdefiniowanych wcześniej za pomocą wywołań %package) robi się to całkiem zrozumiałe i intuicyjne. Tak mi się przynajmniej wydaje.
Tutaj uwaga co do nazewnictwa pakietów - jak powiedziałem, to co my podamy zostanie doklejone do nazwy głównego pakietu. Tak jest zwykle najwygodniej. Ale czasem może zajść potrzeba nazwania pakietu zupełnie inaczej i na szczęście rpmbuild na to pozwala. Wtedy wszędzie tam gdzie podaje się nazwę pakietu należy dodatkowo użyć opcji '-n'. Czyli np. '%package -n manpages' stworzy pakiet o nazwie 'manpages' - dosłownie. Opcji -n trzeba użyć wtedy jednak wszędzie - a więc również w '%files -n manpages', '%description manpages' itp.
I jeszcze jedna uwaga - coś, czego tutaj nie widać ale co pojawia się często w mnogopakach - współdzielenie. Czasem użyje się globbingu który "pokryje" te same pliki w kilku sekcjach %files - inaczej mówiąc, kilka pakietów będzie zawierało te same pliki. rpmbuild nie powie nic przy budowaniu pakietu, bo taki w końcu może być teoretycznie zamiar architekta paczki, ale zwykle jest to po prostu niedopatrzenie. Powiem to wprost: to, że załączam pliki w paczce "A" nie oznacza, że magicznie zostaną one wyłączone z pozostałych paczek. Nie, tak nie jest. Tutaj są tylko dwa w sumie wyjścia - albo uważnie konstruować wzorce - tak, by nie "łapały" więcej niż powinny (co często oznacza dosłowne wpisywanie nazw plików w %files), albo użyć makra %exclude. Makra tego używa się w sekcji %files w taki sam sposób, jak np. makr %dir czy %config, a jego zadaniem jest wykluczenie podanej pozycji z wynikowego pakietu. Brzmi idealnie, ale jest jeden hak - makro to co prawda wykluczy pliki z pakietu, ale rozmiar pakietu (zwracany np. przez "rpm -qi" będzie błędnie podawany - będzie taki, jak gdyby te pliki jednak wchodziły w skład pakietu. To oznacza, że rozmiar zwracany przez bazę pakietów a faktyczna zajętość na dysku będą dwoma różnymi liczbami - baza będzie kłamać. Nie mam pojęcia czy to jakiś błąd w moim rpm-4.1.0 czy też może tak właśnie ma działać to makro bo jego dokumentacji nie udało mi się nigdzie odszukać. Być może jest to jakieś zapomniane przez developerów polecenie, kto tam by to wiedział.
Ale to oznacza, że raczej będziesz zmuszony do korzystania ze sposobu pierwszego - uważnego pisania wzorców dopasowujących nazwy plików. Jakub Panachida, który od początków istnienia tych artykułów był nieocenioną pomocą w wyszukiwaniu błędów lub zachowań niezgodnych z dokumentacją przyglądał się wzorcom używanym przez rpmbuild i doszedł do wniosku, że jest to zasadniczo kod przeportowany z basha, używający jego najprostszego globbingu. Ale to oznacza również, że dostępna jest bashowa negacja wzorców, czyli coś w rodzaju "małego exclude". Nie jest ona zbyt potężna, nadal brakuje możliwości użycia prawdziwych wyrażeń regularnych, ale często bashowa negacja wzorca może być tym, co oszczędzi rzeźbienia kilkudziesięciu linijek dodatkowych wzorców - zainteresowani znajdą stosowne szczegóły w każdym manualu basha.
A teraz, na zakończenie, może jeszcze jeden przykład - tym razem coś z większą liczbą wynikowych pakietów, ale nadal prostego w swojej budowie - IceWM. Tutaj główny pakiet będzie zawierał binarki, dokumentacja wyląduje w osobnym pakiecie (bo pewnie wiele osób i tak z niej nie korzysta), no i w osobnych pakietach wylądują poszczególne wystroje. Do tego dzieki fikuśnym zależnościom instalowanie IceWM będzie wymagało równoczesnego zainstalowania któregoś z tematów - dowolnego, może nawet więcej niż jednego, ale przynajmniej jednego - tak, żeby zmusić użytkownika do zainstalowania któregoś z tematów ale pozostawić mu wolną rękę. A oto plik %spec, tym razem już bez oznaczania części specyficznych dla mnogopaków.
%define theme_dependency icewm-theme
Summary: Może i niewielki, ale obdarzony sporymi możliwościami WM
Name: icewm
Version: 1.2.7
Release: 1
License: GPL
Group: Powłoki
Source: %{name}-%{version}.tar.bz2
URL: http://www.icewm.org/
BuildRoot: /var/tmp/%{name}-%{version}
Requires: %{theme_dependency}
%package docs
Group: Dokumentacja
Summary: Dokumentacja dla IceWM w formie HTML
Requires: icewm
%package infadel2
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%package gtk2
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%package metal2
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%package motif
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%package nice
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%package nice2
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%package warp3
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%package warp4
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%package win95
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%package icedesert
Group: Powłoki
Summary: Dodatkowy temat dla IceWM
Requires: icewm
Provides: %{theme_dependency}
%description
Zarządca okien dla X Window System. Niewielki rozmiar, wbudowany taskbar
z zegarem i monitorkami CPU oraz sieci. Można zmieniać jego wygląd za pomocą
themes. Ogólnie dobre narzędzie, nie popada ani w skrajną prostotę, ani
patologiczną konfigurowalność.
%description docs
Dokumentacja IceWM w formie plików HTML.
%description infadel2
Dodatkowy temat dla IceWM.
Autor: xerithane@nerdfarm.org
%description gtk2
Dodatkowy temat dla IceWM.
Autor: Marko Macek
%description metal2
Dodatkowy temat dla IceWM.
Autor: Marko Macek
%description motif
Dodatkowy temat dla IceWM.
Autor: Marko Macek
%description nice
Dodatkowy temat dla IceWM.
Autor: Marko Macek
%description nice2
Dodatkowy temat dla IceWM.
Autor: Marko Macek
%description warp3
Dodatkowy temat dla IceWM.
Autor: Marko Macek
%description warp4
Dodatkowy temat dla IceWM.
Autor: Marko Macek
%description win95
Dodatkowy temat dla IceWM.
Autor: Marko Macek
%description icedesert
Dodatkowy temat dla IceWM.
Autor: Marko Macek
%prep
%setup -q
%build
%configure --disable-nls --disable-sm \
--enable-shaped-decorations --enable-antialiasing \
--disable-xinerama --enable-x86-asm \
--with-libdir=%{_datadir}/icewm \
--with-cfgdir=%{_sysconfdir}/icewm \
--with-docdir=%{_docdir}
make
%install
rm -rf %{buildroot}
make install install-docs DESTDIR=%{buildroot}
%files
%defattr(0644,root,root,0755)
%attr(0755,root,root)%{_bindir}/*
%dir %{_datadir}/icewm
%{_datadir}/icewm/icons
%{_datadir}/icewm/keys
%{_datadir}/icewm/ledclock
%{_datadir}/icewm/mailbox
%{_datadir}/icewm/menu
%{_datadir}/icewm/preferences
%{_datadir}/icewm/programs
%{_datadir}/icewm/taskbar
%{_datadir}/icewm/toolbar
%{_datadir}/icewm/winoptions
%dir %{_datadir}/icewm/themes
%files docs
%defattr(0644,root,root,0755)
%{_docdir}/icewm-%{version}
%files infadel2
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/Infadel2
%files gtk2
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/gtk2
%files metal2
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/metal2
%files motif
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/motif
%files nice
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/nice
%files nice2
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/nice2
%files warp3
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/warp3
%files warp4
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/warp4
%files win95
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/win95
%files icedesert
%defattr(0644,root,root,0755)
%{_datadir}/icewm/themes/icedesert
%clean
rm -rf %{buildroot} %{_builddir}/%{buildsubdir}
Długie, ale łatwe do zrozumienia. Mam na szczycie zdefiniowane makro %{theme_dependency} - ono opisuje nazwę pewnej wirtualnej zależności która ma być spoiwem między pakietem z IceWM a pakietami z tematami. Wyrzuciłem ją do specjalnego makra, bo nie zastanawiałem się nad nazwą zbyt długo i wolałem mieć możliwość szybkiej tego zmiany. Każdy pakiet z tematem dostarcza tego "zasobu", a paczka z IceWM tego zasobu z kolei wymaga. Dlatego nieważne ile paczek z tematami zainstalujesz - IceWM będzie zawsze wmagał minimum jednej. Jeśli spaczkujesz jakieś zewnętrzne, niezależne tematy i też określisz by zapewniały ten określony "zasób", to będziesz mógł odinstalować wszystkie tematy fabryczne.
To nie jest ładny plik .spec, w idealnym przypadku każdy temat powinien sprawdzać przy instalacji/usuwaniu czy "domyślny" temat ustawiony w globalnej konfiguracji IceWM wskazuje na któryś z dostępnych tematów. To by oznaczało takie średnio skomplikowane sekcje post/postun, ale nie chciało mi się tego już pisać - IceWM tak naprawdę nie używam, więc nie miałem motywacji.
Hmm, chyba wszystko już powiedziałem co było do powiedzenia na ten temat. Reszta jest już w twoich rękach.
Czasem "lokalizuje" się programy - tak, by zachowywały się nieco inaczej w zależności od środowiska, od ustawień "locales". Pakiety RPM również mają pewne możliwości w tej dziedzinie - podstawowa rzecz którą można "zlokalować" to opisy pakietu, można też od ustawień języka uzależnić listę instalowanych plików - można np. instalować niemieckojęzyczną wersję dokumentacji tylko wtedy, gdy użytkownik deklaruje używanie tego języka. Pozwala to na stworzenie pakietów które będą przedstawiały się użytkownikowi w jego ojczystym języku i które nie będą instalować niepotrzebnych mu plików - np. francuskich czy japońskich stron manuala. Przy odpowiednim nakładzie pracy można stworzyć uniwersalny pakiet "wielojęzyczny".
Zacznę może od opisów pakietów. Co można przełożyć na nasz język? Standardowo tłumaczy się pola Summary: i Description:, bo one zawierają opisowe teksty. Pola Name: raczej się już nie zmienia. Niektórzy tłumaczą też nazwy zawarte w polu Group:...
Ja nie tworzę "wielojęzykowych" pakietów, bo nie mam takiej potrzeby. Mam z góry ustalone preferencje językowe więc nie muszę programować tutaj żadnych alternatyw. Ale jeśli chcesz tworzyć pakiety dla szerszej publiczności, to obowiązuje jedna zasada - podstawowym językiem musi być angielski. Pozostałe są tylko dodatkami, które mogą zakryć podstawowe teksty. Tak więc wszystkie pola, jak Group: czy też Summary: muszą być wypełnione w języku angielskim. Tak samo należy bezwzględnie instalować oryginalną, anglojęzyczną dokumentację. Bo nie wszystkie języki da się "obsłużyć", nie dla każdego da się dostarczyć opisy czy dokumentację - więc musi być jakiś język "uniwersalny" - i w świecie współczesnej informatyki jest to angielski. Tak samo zresztą, jak w wielu innych współczesnych dziedzinach - chemii czy medycynie. Oczywiście jeśli nie masz aspiracji tworzyć uniwersalnych pakietów dla wszystkich nacji tego świata możesz po prostu pominąć ten rozdział.
Zacznę od fragmentu preambuły. Zwykła, podstawowa może wyglądać np. tak:
Summary: General Purpose Mouse support for Linux
Name: gpm
Version: 1.20.1
Release: %{_rc}.2
License: GPL
Group: Daemons
Załóżmy, że chcemy teraz dodać wsparcie dla języka polskiego i niemieckiego. Będzie to wyglądało tak:
Summary: General Purpose Mouse support for Linux Summary(de): Allgemeine Mausunterstützung für Linux Summary(pl): Uniwersalny daemon myszy w Linuksie Name: gpm Version: 1.20.1 Release: %{_rc}.2 License: GPL Group: Daemons Group(de): Dämonen Group(pl): Daemony
Jak widać, jest to tutaj bardzo proste - jeśli chcę dostarczyć jakiegoś pola w jego przetłumaczonej wersji, to po prostu po nazwie pola, w okrągłych nawiasach, podaję kod języka jakiego opis dotyczy. A jak to działa w praktyce? RPM magazynuje sobie wszystkie wersje tych pól. Pola bez określonych języków (tutaj anglojęzyczne) są zawsze polami "domyślnymi". RPM wewnętrznie operuje właśnie na nich, ale w interakcji z użytkownikiem sprawdza, czy nie ma przypadkiem dostępnych odpowiednio "zlokalizowanych" wersji - jeśli tak, to prezentuje je zamiast wersji domyślnej. Jeśli użytkownik ma lokale ustawione na np. "pl" ale pakiet nie oferuje tekstów w wersji (pl), to zobaczy wersję domyślną. Jest to prosty i skuteczny sposób.
Poza lokalizowaniem "nagłówków" pakietu można też zrobić to samo z wpisami plików w %files, ba. Robi się to dyrektywą/makrem %lang. Przyjmuje ono jeden argument - kod języka. Linijka która jest prefiksowana makrem %lang będzie "ignorowana" przy budowaniu pakietu, ale odezwie się przy instalacji. Pliki oznaczone za pomocą %lang zostaną zainstalowane tylko jeśli locale środowiska będą uwzględniały dany język. Np.:
%files
%defattr(0644,root,root,0755)
%{_mandir}/man*/*.gz
%lang(pl) %{_mandir}/pl
%lang(de) %{_mandir}/de
taka definicja spowoduje zainstalowanie "ogólnych" manuali, a jeśli środowisko będzie uwzględniało (jako języki "obsługiwane" przez zmienne $LC_*) język polski lub niemiecki, to dodatkowo zainstalowane zostaną wersje zlokalizowane manuali.
I jeszcze coś: standardowo w dystrybucji rpm-4.1 znajduje się skrypt find-lang.sh, podpięty pod makro %find_lang. Ma on w domyśle przeszukać drzewko plików w %{buildroot} i znaleźć wszystkie pliki "lokalizacyjne" (zależne od języka) po czym spreparować plik tekstowy zawierający ich listę razem z odpowiednimi dyrektywami %lang na potrzeby włączenia do sekcji %files (pamiętasz, %files może włączyć sobie "polecenia" z zewnętrznego pliku tekstowego). Wszystko to powinno ułatwić pracę, ale tak nie jest, niestety. Skrypt wydaje się być niedokończony, np. wykrywa i odpowiednio dzieli pliki z tłumaczeniami *.mo oraz zlokalizowane części online-help środowiska Gnome, ale np. w ogóle ignoruje różne wersje językowe manuali, co sprawia że pakując te pliki i tak musisz odwalać spory kawałek roboty ręcznie. Ale mimo wszystko skrypt ten jest interesującym materiałem który możesz chcieć dostosować do swoich potrzeb. Albo zupełnie zignorować, twój wybór. Ja w każdym razie nie będę tego teraz opisywał, skrypt jest dobrze okomentowany.
Pliki .spec oferują szerokie pole do popisu gdy idzie o automatyzację, ale czasem konieczna jest współpraca paczki z osobą paczkującą. Inaczej mówiąc: stworzenie jakiegoś interfejsu, który pozwoli pakującemu mieć wpływ na proces budowania pakietu i to polegający na czymś innym niż ręczna edycja speca. Sprowadza się to do jednego mechanizmu: instrukcji warunkowych, pozwalających uzależnić część pliku .spec od istnienia lub wartości jakiejś zmiennej czy też makra. Znajduje to czasem zastosowanie, najczęściej chyba właśnie w tzw. budowaniu warunkowym. Polega ono w uproszczeniu na tym, że plik .spec oferuje kilka wariantów, zwykle polegających na wybraniu jakichś opcji ./configure, które wpływają na wynikową paczkę binarną.
Ale zacznę od podstaw: jak w pliku .spec sprawdzić, czy jakieś makro jest zdefiniowane i podjąć akcję? Załatwia to konstrukcja %{?...:...}:
%{?_debug:echo Debug selected}
Zaraz po znaku zapytania podaje się nazwę makra, potem dwukropek, a po dwukropku akcję jaką chce się podjąć. "Akcja" to niezbyt właściwe słowo, bo może to być dowolna linia pliku .spec, w dowolnej lokacji. Jeśli warunek (sprawdzane makro) nie jest spełniony, to cała linia jest ignorowana przez rpmbuild - po prostu nie istnieje. Warunki można negować:
%{!?_debug:echo Debug selected}
Najczęściej łączy się to z opcjami --with i --without rpmbuild. Opcje te pozwalają w wygodny sposób zdefiniować przy wywołaniu rpmbuild pewne makra, np. wywołanie
rpmbuild --with SDL --without sound
spowoduje zdefiniowanie dwóch makr: _with_SDL i _without_sound. A w pliku .spec można sprawdzać, czy makra te są dostępne i jakoś na nie zareagować. Np. ustawiając opcje %configure:
%configure \
--disable-dependency-tracking %{?_with_SDL:--with-sdl} \
%{!?_without_sound:--enable-esd} --disable-debug
proste i funkcjonalne. Jeśli jakaś opcja ./configure wpływa na listę generowanych plików, to można oczywiście i w sekcji %files uzależnić te pliki od wartości odpowiedniego makra. To samo dotyczy zależności, jeśli ktoś używa zależności od nazw pakietów to może sobie wpisać w preambułę coś w rodzaju
Requires: %{!?_without_sound:esd} %{?_with_SDL:SDL >= 1.2.5}
Temat ten jest bardzo rozległy, ale podstawy już nakreśliłem. Ot, jedna konstrukcja do sprawdzania warunków. Może teraz kilka luźnych uwag: po pierwsze, opcje --with i --without się nawzajem nie negują. To znaczy, że definiując równocześnie "--with sdl --without sdl" tak naprawdę dojdzie do definicji dwóch makr. Należy więc rozsądnie konstruować pliki .spec. Po drugie, wiersz z instrukcją sprawdzająca warunki nie może być łamany, nie jest możliwe wykonanie bloczku instrukcji w jednym warunku - na jeden warunek może przypaść tylko jeden wiersz "akcji". Jeśli masz więcej linijek podpadających pod jeden warunek to musisz po prostu każdą linijkę uwarunkować osobną konstrukcją %{?...:...}. Po trzecie, aktualne wartości wszystkich makr można poznać podając opcję "--showrc" do rpm lub rpmbuild. Nie jest wtedy wykonywana żadna akcja, ale rpm pokaże listę makr która by obowiązywała przy tej akcji.
Standardowy RPM nie oferuje wygodnego sposobu na podejrzenie jakie opcje --with/--without są akceptowane przez pakiet. W dystrybucji PLD wprowadzono jednak modyfikację polegającą na dodaniu opcji --bcond, która to opcja w użyciu z jakimś plikiem .spec pokaże listę dostępnych --with/--without. Modyfikację tę, bardzo użyteczną, można zreplikować na dowolnym systemie RPM bez potrzeby rekompilacji. Bazuje ona na mechanizmie "popt" - jest to system, który pozwala definiować nowe opcje dla jakiegoś polecenia bez jego rekompilacji. W przypadku PLD stworzono nową opcję i podpięto pod nią skrypt, który wyciąga z pliku .spec charakterystyczne zmienne a potem prezentuje je, po obróbce, użytkownikowi. W PLD wprowadzono też bardzo sympatyczne ujednolicenie, dodatkową warstwę dla makr budowania warunkowego. Ich nazwy są tam tłumaczone z postaci _without_foo/_with_foo na bcond_off_foo/bcond_on_foo. Zainteresowanych odsyłam do repozytoriów CVS. Ja prywatnie z tego nie korzystam, ale też nie przywiązuję wielkiej wagi do warunkowego budowania pakietów. Moje potrzeby zaspokajają całkowicie te standardowe mechanizmy, choć ich PLD-owska wariacja jest wygodniejsza.
Często ważne jest śledzenie zmian w pakietach. RPM oferuje w tym celu changelogi wbudowane w każdy z pakietów. Taki changelog powinien opisywać zmiany w samym pakiecie (a nie w programie który jest pakietowany, tzn. nie powinien być kopią changelogu kodu źródłowego). ChangeLog pakietu może zawierać np. informacje o usunięciu literówek z opisu pakietu, zmianie domyślnych praw dostępu do pliku, rozlokowania plików, nałożenia dodatkowych, dystrybucyjnych patchy itp. Jak powiedziałem, changelog ten jest trzymany bezpośrednio w pakiecie RPM (a dokładniej w jego pliku .spec) i przy instalacji pakietu jest również zapisywany w bazie danych o pakietach.
Sam ChangeLog wprowadza się do pliku .spec za pomocą sekcji %changelog. Taka sekcja jest zwykle umieszczana na samym końcu pliku, ale jest to tylko zwyczaj, nie wymóg. Za to jeśli idzie o formę to istnieją już konkretne wymogi, rpmbuild może odmówić stworzenia pakietu jeśli changelog będzie "błędnie" zbudowany. Ogólnie w światku dystrybucji używających RPM najszerzej przyjął się taki standard:
%changelog * Mon Dec 18 2000 Yukihiro Nakai <ynakai@redhat.com> - Add Japanese patches. * Mon Dec 11 2000 Bernhard Rosenkraenzer <bero@redhat.com> - Requires(preun,post) fileutils * Fri Oct 20 2000 Bernhard Rosenkraenzer <bero@redhat.com> - Fix yet another security problem (MANSECT overrun), Bug #19351 * Fri Oct 13 2000 Bernhard Rosenkraenzer <bero@redhat.com> - Fix trailing garbage when a man page doesn't end with a newline (Bug #9026) - Look for files in other man directories if the first match isn't accessible (Bug #10254)
Każda "sesja" w changelogu zaczyna się od gwiazdki, następnie przychodzi spacja i data dodania wpisu. Data jest specyficznie sformatowana i należy tego przestrzegać - najpierw skrócona nazwa dnia tygodnia, potem skrócona nazwa miesiąca, dzień miesiąca i rok. Zapis daty anglojęzyczny (niedozwolone są wpisy "zlokalizowane" - zresztą nie są potrzebne, bo potem przy odczytywaniu danych RPM automatycznie przetłumaczy je na lokale użytkownika (zgodnie z $LC_TIME)).
Potem kolejna spacja, imię i nazwisko osoby która wprowadziła zmiany i na końcu jej adres e-mail. RPM potrafi być bardzo irytujący jeśli odstąpi się od tego wzorca.
Po takim wpisie rozpoczynającym tradycyjnie wprowadza się jeden pusty wiersz, a następnie wymienia pokrótce wszystkie wprowadzone zmiany - podbicie numerku wersji, dodanie nowego patcha, zmiana zależności pakietu... Każdą zmianę oznacza się znakiem myślnika, w jednym wpisie (pochodzącym z jednej daty, od jednej osoby) takich zmian można wymienić wiele. Aha, językiem w którym wprowadza się tutaj wpisy jest język angielski, nawet w dystrybucji PLD :)
Tyle odnośnie najczęściej używanego formatu. Spotyka się też inne wariacje, ale po obejrzeniu różnych pakietów z różnych dystrybucji stwierdzam, że ten powyżej przedstawiony jest najpopularniejszy. Więc chyba warto się do niego dostosować. Linia rozpoczynająca wpis (ta z gwiazdką na początku) wydaje się być przekombinowana, ale wystarczy sobie zdefiniować w swoim ulubionym edytorze tekstu makro które wstawia odpowiedni wpis z aktualną datą i problem z głowy.
To wszystko! Ten rozdział był króciutki, no bo co jeszcze można powiedzieć o ChangeLogach?