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ą proste 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 mechaniźmie ,,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ń.