Gentoo Logo

Uwaga: Oryginalna wersja tego artykułu została opublikowana w IBM developerWorks i jest własnością Westtech Information Services. Poniższy dokument jest poprawioną przez zespół GDP wersją oryginalnego tekstu i nie jest już aktualizowany.


Bash w przykładach - część trzecia

Spis treści:

1.  Poznawanie systemu ebuildów

Wkraczanie do systemu ebuildów

Po zapoznaniu się z podstawami programowania w bashu poprzez pierwszą i drugą część cyklu Bash w przykładach, możemy zagłębić się w bardziej zaawansowane zagadnienia, jak strategie projektowy czy programowanie prawdziwych aplikacji. W tym artykule otrzymamy słuszną dawkę praktycznych doświadczeń projektowych. Będą one przedstawione w oparciu o opis projektu, nad którym autor tego artykułu spędził wiele godzin, kodując go i ulepszająć. Projektem tym jest system ebuildów Linuksa Gentoo.

Autor artykułu jest głównym architektem Linuksa Gentoo - systemu operacyjnego nowej generacji. Jednym z jego głównych zadań jest zapewnienie, aby wszystkie binarne pakiety (podobne do pamietów RPM) były poprawnie tworzone i mogły właściwie ze sobą współpracować. Jak prawdopodobnie wiemy, standardowe systemy Linuksowe nie składają się z pojedynczego jednolitego drzewa źródłowego (jak BSD), lecz budowane są z ponad 25 pakietów, które pracując razem tworzą rdzeń systemu. Oto niektóre z tych pakietów:

Pakiet Opis
linux Jądro systemu
util-linux Kolekcja różnorodnych programów poziązanych z Linuksem
e2fsprogs Kolekcja programów powiązanych z systemem plików ext2
glibc Biblioteka GNU C

Każdy pakiet znajduje się w oddzielnym tarballu i jest utrzymywany przez niezależnych deweloperów lub ich grupy. Zbudowanie systemu polega na pobraniu wszystkich pakietów, ich kompilacji i połączeniu w systemie. Za każdym razem gdy pakiet musi zostać naprawiony, zaktualizowany lub ulepszony, konieczne jest powtórzenie kompilacji i łączenia (a zdarza się to naprawdę często). Aby wyeliminować konieczność powtarzania tych samych kroków w celu budowania i tworzenia systemu, powstał system ebuildów. Został on napisany niemal całkowicie w bashu. Aby zwiększyć naszą wiedzę o bashu, przyjrzymy się jak krok po kroku zostały zaimplementowane funkcje odpowiedzialne za rozpakowywanie archiwów i kompilowanie źródeł. Wyjaśniając każdy etap, omówimy dlaczego podjęte zostały pewne decyzje projektowe. Po przeczytaniu tego artykułu nie tylko poznamy techniki programowania na dużą skalę, ale również zaimplementujemy spory fragment kompletnego systemu automatycznej kompilacji.

Dlaczego bash?

Bash stanowi podstawę systemu ebuildów Linuksa Gentoo. Został on wybrany ze względu na wiele jego przydatnych cech. Po pierwsze, posiada on nieskomplikowaną i intuicyjną składnię, która szczególnie dobrze nadaje się do wywoływania zewnętrznych programów. System automatycznej kompilacji w głównej mierze wywołuje inne aplikacje, więc bash nadaje się doskonale do jego implementacji. Po drugie, obsługa funkcji pozwoliła na stworzenie modularnego i łatwego w czytaniu kodu. Po trzecie, system ebuildów wykorzystuje obsługę zmiennych środowiskowych w bashu, co pozwala konserwatorom i deweloperom na łatwe konfigurowanie pakietów "w locie".

Przegląd procesu instalacji programu

Zanim zagłębimy się w system ebuildów, zastanówmy się jakie działania składają się na kompilację i instalację pakietów. Jako przykład wykorzystamy pakiet programu sed - standardowego narzędzia strumieniowej edycji tekstu, będącego częścią wszystkich dystrybucji Linuksa. Pierwszym krokiem jest pobranie tarballa ze źródłami (sed-3.02.tar.gz, link w dziale Źródła informacji). Umieścimy to archiwum w katalogu /usr/src/distfiles, który jest wskazywany przez zmienną środowiskową $DISTDIR. W tym katalogu przechowywane są wszystkie nasze tarballe ze źródłami; jest to swoisty magazyn źródeł.

Kolejny etap to stworzenie tymczasowego katalogu o nazwie work, w którym umieścimy rozpakowane źródła. Będziemy odwoływać się do tego katalogu przy użyciu zmiennej środowiskowej $WORKDIR. Aby tego dokonać przejdźmy do katalogu, w którym mamy prawo do zapisu i wykonajmy polecenia:

Listing 1.1: Rozpakowanie źródeł programy sed do tymczasowego katalogu

$ mkdir work
$ cd work
$ tar xzf /usr/src/distfiles/sed-3.02.tar.gz

Źródła z tarballa zostaną rozpakowane do katalogu sed-3.02. Będziemy się do niego odwoływać poprzez zmienną środowiskową $SRCDIR. Aby skompilować program wykonajmy komendy:

Listing 1.2: Kompilacja programu sed

$ cd sed-3.02
$ ./configure --prefix=/usr
(autoconf generuje właściwy plik make; może to zająć trochę czasu)
$ make
(kompilacja źródeł - również może zająć trochę czasu)

Nie uwzględniamy tutaj komendy make install, ponieważ artykuł ten dotyczy wyłącznie rozpakowywania i kompilacji. Gdybyśmy napisali skrypt basha, który wykonałby wszystkie dotychczasowe kroki, wyglądałby on mniej więcej tak:

Listing 1.3: Przykładowy skrypt wykonujący rozpakowywanie i kompilację programu

#!/usr/bin/env bash

if [ -d work ]
then
# usunięcie starego katalogu work, jeśli taki istnieje
      rm -rf work
fi
mkdir work
cd work
tar xzf /usr/src/distfiles/sed-3.02.tar.gz
cd sed-3.02
./configure --prefix=/usr
make

Generalizacja kodu

Chociaż powyższy skrypt działa poprawnie, nie jest on zbyt przydatny ze względu na małą elastyczność. Zasadniczo zawiera on jedynie listę komend jakie wpisywaliśmy w linii poleceń. Rozwiązanie jest poprawne, jednak lepiej byłoby stworzyć bardziej ogólny skrypt, który można łatwo skonfigurować do rozpakowania i kompilacji dowolnego pakietu poprzez zmianę kilku jego linijek. W ten sposób konserwator pakietu może znacznie szybciej i prościej dodać nową wersję do dystrybucji. Pierwszym krokiem ku temu jest wykorzystanie kilku zmiennych środowiskowych do przedstawienia danych zależnych od programu, co sprawi że skrypt będzie bardziej zgeneralizowany:

Listing 1.4: Bardziej zgeneralizowana wersja skryptu

#!/usr/bin/env bash

# P jest nazwą pakietu

P=sed-3.02

# A to nazwa archiwum

A=${P}.tar.gz

export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work
export SRCDIR=${WORKDIR}/${P}

if [ -z "$DISTDIR" ]
then
# ustawiamy DISTDIR jako /usr/src/distfiles jeśli nie zostało to
zrobione wcześniej
        DISTDIR=/usr/src/distfiles
fi
export DISTDIR

if [ -d ${WORKDIR} ]
then
# usuwamy stary katalog work jeśli istnieje
        rm -rf ${WORKDIR}
fi

mkdir ${WORKDIR}
cd ${WORKDIR}
tar xzf ${DISTDIR}/${A}
cd ${SRCDIR}
./configure --prefix=/usr
make

Dodaliśmy sporo zmiennych środowiskowych do skryptu, jednak generalnie wciąż wykonuje on te same zadania. Natomiast teraz, aby skompilować jakiś program oparty o autoconf wystarczy skopiować powyższy skrypt do nowego pliku (o właściwej nazwie, która będzie nawiązywała do pakietu jakie instalujemy) i zmienić wartości zmiennych $A i $P. Wszystkie inne zmienne środowiskowe automatycznie dopasują się do nowych wartości i skrypt zadziała zgodnie z oczekiwaniami. Jest to bardzo poręczne, jednak można jeszcze bardziej ulepszyć nasz skrypt. Obecnie kod jest znacznie dłuższy od jego początkowej wersji. Jednym z głównym celów każdego projektu programistycznego powinna być redukcja nadmiernej złożoności kodu. Dobrze byłoby więc odchudzić skrypt lub przynajmniej lepiej do zorganizować. Możemy tego dokonać poprzez pewien zgrabny wybieg - rozdzielimy kod na dwa pliki. Poniższy fragment zapiszmy jako sed-3.02.ebuild:

Listing 1.5: sed-3.02.ebuild

# plik ebuild programu sed - niezwykle prosty!
P=sed-3.02
A=${P}.tar.gz

Pierwszy plik jest trywialny i zawiera tylko dwie zmienne, jakie muszą być modyfikowane dla różnych pakietów. Poniżej znajduje się drugi plik, zawierający zasadniczą część operacji. Zapiszmy go pod nazwą ebuild i nadajmy mu prawa do wykonywania:

Listing 1.6: Skrypt ebuild

#!/usr/bin/env bash


if [ $# -ne 1 ]
then
        echo "Oczekiwano jednego argumentu."
        exit 1
fi

if [ -e "$1" ]
then
        source $1
else
        echo "Plik ebuild $1 nie został odnaleziony."
        exit 1
fi

export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work
export SRCDIR=${WORKDIR}/${P}

if [ -z "$DISTDIR" ]
then
        # ustawiamy DISTDIR jako /usr/src/distfiles jeśli nie zostało to wcześniej zrobionea
        DISTDIR=/usr/src/distfiles
fi
export DISTDIR

if [ -d ${WORKDIR} ]
then
        # usuwamy stary katalog work jeśli istnieje
        rm -rf ${WORKDIR}
fi

mkdir ${WORKDIR}
cd ${WORKDIR}
tar xzf ${DISTDIR}/${A}
cd ${SRCDIR}
./configure --prefix=/usr
make

W ten sposób podzieliliśmy nasz skrypt na dwie części. Jak on teraz działa? Aby skompilować program sed należy wykonać komendę:

Listing 1.7: Testowanie powyższego skryptu

$ ./ebuild sed-3.02.ebuild

Gdy wykonujemy skrypt, próbuje ona wykonać polecenie source z parametrem $1. Co to oznacza? Zgodnie z wcześniejszymi artykułami o bashu, zmienna $1 odwołuje się do pierwszego argumentu wywołania skryptu - w tym przypadku do łańcucha sed-3.02.ebuild. Komenda source czyta zawartość tego pliku i wykonuje ją tak, jakby pojawiła się w miejscu wywołania source, więc source ${1} spowoduje wykonanie zawartości pliku sed-3.02.ebuild, czyli zainicjowanie zmiennych $P i $A. Jest to bardzo przydatne, gdyż jeśli będziemy chcieli skompilować inny program zamiast sed, wystarczy, że stworzymy nowy plik .ebuild i przekażemy jego nazwę jako argument do skryptu ebuild. Dzięki temu pliki .ebuild są bardzo proste, a wszystkie operacje wykonywane są w innym miejscu - w skrypcie ebuild. W ten sposób możemy aktualizować i rozbudowywać system ebuildów poprzez edytowanie skryptu ebuild, utrzymując cały czas szczegóły implementacji poza plikiem .ebuild. Oto przykładowy plik .ebuild dla programu gzip:

Listing 1.8: gzip-1.2.4a.ebuild

#kolejny niezwykle prosty skrypt ebuild!
P=gzip-1.2.4a
A=${P}.tar.gz

Zwiększanie funkcjonalności

Zrobiliśmy już spory postęp, jednak jest kilka dodatkowych funkcjonalności, które warto byłoby dodać. Jedną z takich funkcjonalności jest obsługa drugiego parametru wywołania, który może przyjmować wartość compile, unpack lub all. Będzie on decydował, które etapy procesu mają zostać przeprowadzone. Dzięki temu będziemu mogli nakazać skryptowi aby rozpakował archiwum, ale nie kompilował źródeł (gdybyś chcieli przejrzeć kod przed jego kompilacją). Dodamy tą funkcję wykorzystując konstrukcję case, która sprawdzi wartość zmiennej $2 i wykonana różne czynności w zależności od jej wartości. Obecnie kod ma postać:

Listing 1.9: ebuild, wersja druga

#!/usr/bin/env bash

if [ $# -ne 2 ]
then
        echo "Proszę podać dwa argumenty - nazwę pliku .ebuild oraz unpack, compile lub all"
        exit 1
fi


if [ -z "$DISTDIR" ]
then
 # ustawiamy DISTDIR jako /usr/src/distfiles
        DISTDIR=/usr/src/distfiles
fi
export DISTDIR

ebuild_unpack() {
         #upewnijmy się, że jesteśmy we właściwym katalogu
        cd ${ORIGDIR}

        if [ -d ${WORKDIR} ]
        then
                rm -rf ${WORKDIR}
        fi

        mkdir ${WORKDIR}
        cd ${WORKDIR}
        if [ ! -e ${DISTDIR}/${A} ]
        then
            echo "${DISTDIR}/${A} nie istnieje. Najpierw pobierz ten plik."
            exit 1
        fi
        tar xzf ${DISTDIR}/${A}
        echo "Rozpakowany ${DISTDIR}/${A}."
        #źródła zostały rozpakowane
}


ebuild_compile() {

         #upewniamy się, że jesteśmy we właściwym katalogu
        cd ${SRCDIR}
        if [ ! -d "${SRCDIR}" ]
        then
                echo "${SRCDIR} nie istnieje - najpierw rozpakuj źródła."
                exit 1
        fi
        ./configure --prefix=/usr
        make
}

export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work

if [ -e "$1" ]
then
        source $1
else
        echo "Plik ebuild $1 nie istnieje."
        exit 1
fi

export SRCDIR=${WORKDIR}/${P}

case "${2}" in
        unpack)
                ebuild_unpack
                ;;
        compile)
                ebuild_compile
                ;;
        all)
                ebuild_unpack
                ebuild_compile
                ;;
        *)
                echo "Użyj unpack, compile lub all jako drugiego argumentu."
                exit 1
                ;;
esac

Wprowadziliśmy wiele zmien - przyjrzymy się im dokładnie. Po pierwsze, etapy rozpakowywania i kompilacji znajdują się teraz w oddzielnych funkcjach o nazwach ebuild_compile() iebuild_unpack(). Jest to dobry krok, ponieważ kod robi się coraz bardziej skomplikowany, a funkcje dają modularność pomocną w organizacji programu. W pierwszej linii każdej funkcji wykonujemy komendę cd z parametrem w postaci katalogu, w którym chcemy operować. Warto jest zastosować ten element, ponieważ im bardziej kod jest modularny i zgeneralizowany, tym łatwiej jest pomylić się w czasie jego wywoływania i zrobić to z innego miejsca niż powinniśmy. Komenda cd przenosi nas dokładnie tam, gdzie powinniśmy być i chroni tym samym przed kolejnymi błędami. Jest to bardzo ważne, szczególnie jeśli w przyszłości dodamy do funkcji komendy usuwające pliki.

Na początku funkcji ebuild_compile() dodaliśmy również warunek sprawdzający czy istnieje katalog $SRCDIR, a jeśli nie istnieje drukujący komunikat o błędzie i kończący wykonanie skryptu. Jeśli chcemy możemy zmienić to zachowanie na przykład tak, aby skrypt automatycznie wypakowywał źródła, jeśli nie zostało to zrobione wcześniej. Zrobimy to zastępując aktualną wersję funkcji ebuild_compile() poniższym kodem:

Listing 1.10: Alternatywna wersja funkcji ebuild_compile()

ebuild_compile() {
        #upewniamy się, że jesteśmy we właściwym katalogu
        if [ ! -d "${SRCDIR}" ]
        then
                ebuild_unpack
        fi
        cd ${SRCDIR}
        ./configure --prefix=/usr
        make
}

Główną zmianą dla przebiegu skryptu jest pojawianie się konstrukcji case na końcu kodu. Sprawdza ona wartość drugiego argumentu wywołania i wykonuje dalsze operacje odpowiednio do jego wartości. Spróbujmy wykonać komendę:

Listing 1.11: Wywołanie skryptu bez drugiego argumentu

$ ebuild sed-3.02.ebuild

Powinniśmy otrzymać komunikat o błędzie. Skrypt ebuild wymaga teraz drugiego argumentu, wskazującego jakie akcje ma podjąć:

Listing 1.12: Rozpakowywanie

$ ebuild sed-3.02.ebuild unpack

lub:

Listing 1.13: Kompilacja

$ ebuild sed-3.02.ebuild compile

lub:

Listing 1.14: Rozpakowywanie i kompilacja

$ ebuild sed-3.02.ebuild all

Ważne: Podanie dla drugiego argumentu innej wartości niż w przykładach powyżej spowoduje wypisanie komunikatu o błędzie i zakończenie skryptu. Odpowiada za to wzorzec * w konstrukcji case.

Dzielenie kodu na moduły

Teraz, gdy kod jest już całkiem zaawansowany i cechuje się pewną funkcjonalnością, możemy stworzyć więcej plików .ebuild do instalacji innych programów. Jednak jeśli tak zrobimy, wcześniej czy później natrafimy na aplikacje, które nie korzystają z autoconf (./configure), lub na inne, cechujące się niestandardowym procesem instalacji. Konieczne jest wprowadzenie dodatkowych zmian do systemu ebuildów aby zapewnić obsługę również dla tych aplikacji. Jednak zanim zaczniemy kodować, zastanówmy się jak zrealizować ten cel.

Zaletą umieszczenia w skrypcie kodu ./configure --prefix=/usr; make jest to, że zazwyczaj daje on oczekiwany efekt. Musimy jednak przystosować nasz system do obsługi źródeł, które nie korzystają z autoconf, lecz ze zwykłych plików make. Aby rozwiązać ten problem, ustalmy, że skrypt ebuild domyślnie będzie postępował w następujący sposób:

  1. Jeśli w katalogu ${SRCDIR} istnieje skrypt configure, wykonujemu go: ./configure --prefix=/usr. Jeśli nie - pomijamy ten krok.
  2. Wykonujemy komendę make

Skrypt ebuild wykona polecenie ./configure tylko jeśli istnieje odpowiedni skrypt. Dzięki temu automatycznie dostosowujemy się do programów, które nie korzystają z autoconf i posiadają pliki make. Co jednak zrobić, jeśli sama komenda make nie wystarczy do zainstalowania aplikacji? Musimy zastąpić wtedy nasz domyślny kod pewnymi specyficznymi poleceniami, które pozwolą na instalację tych programów. W tym celu rozbijemy funkcję ebuild_compile() na dwie inne. Pierwsza z nich, którą można nazwać "rodzicem", wciąż będzie nazywać się ebuild_compile(). Drugą natomiast nazwiemy user_compile() i umieścimy w niej domyślne zachowanie naszego skryptu:

Listing 1.15: Funkcja ebuild_compile() rozbita na dwie części

user_compile() {
        #jesteśmy już w katalogu ${SRCDIR}
        if [ -e configure ]
        then
                #uruchamiany sktypt configure, jeśli istnieje
                ./configure --prefix=/usr
        fi
        #uruchamiamy make
        make
}

ebuild_compile() {
        if [ ! -d "${SRCDIR}" ]
        then
                echo "${SRCDIR} nie istnieje - najpierw rozpakuj źródła."
                exit 1
        fi
        #upewniamy się, że jesteśmy we właściwym katalogu
        cd ${SRCDIR}
        user_compile
}

Możliwe, że nie jest w tym momencie do końca jasne dlaczego dokonaliśmy takiego podziału, ale zaraz wszystko zostanie opisane. Podczas gdy powyższy kod działa niemal identycznie jak w poprzedniej wersji, teraz możemy zrobić coś, czego nie byliśmy w stanie zrobić wcześniej - możemy nadpisać funkcję user_compile, tworząc jej nową wersję w pliku sed-3.02.ebuild. Więc jeśli domyślne działania nie odpowiadają potrzebom danego programu, możemy zdefiniować nową w pliku .ebuild, która będzie zawierać komendy potrzebne do instalacji danej aplikacji. Poniżej znajduje się przykładowy plik ebuild dla programu e2fsprogs-1.18, który wymaga nieco innego wywołania skryptu ./configure:

Listing 1.16: e2fsprogs-1.18.ebuild

#ten plik nadpisze funkcję user_compile() w skrypcie ebuild
P=e2fsprogs-1.18
A=${P}.tar.gz

user_compile() {
       ./configure --enable-elf-shlibs
       make
}

Teraz e2fsprogs zostanie skompilowany dokładnie tak jak powinien. Dla większości pakietów pominiemy umieszczanie funkcji user_compile() w pliku .ebuild i wykorzystana zosatnie standardowa funkcja w skrypcie ebuild.

Skąd dokładnie skrypt ebuild wie którą wersję funkcji user_compile() ma wykorzystać? Jest to całkiem proste. W skrypcie ebuild domyślna funkcja user_compile jest zdefiniowana przez dołączeniem pliku e2fsprogs-1.18.ebuild. Jeśli w pliku e2fsprogs-1.18.ebuild znajduje się inna wersja funkcji user_compile, to nadpisuje ona wcześniej zdefiniowaną. Jeśli nie - domyślna funkcja jest jedyną jaka może zostać wykorzystana.

Podnieśliśmy znacznie elstyczność skryptu bez konieczności wprowadzania żadnego skomplikowanego kodu. Choć nie zostanie to tutaj opisane, możliwe jest wprowadzenie podobnych modyfikacji w funkcji ebuild_unpack(), co pozwoliłoby na zmianę standardowego sposobu rozpakowywania źródeł. Będzie to przydatne jeśli źródła jakiegoś programu znajdują się w kilku archwiach, a także w wielu innych sytuacjach. Dobrym pomysłem jest również taka modyfikacja kodu rozpakowującego, aby automatycznie rozpoznawał archiwa bzip2.

Pliki konfiguracyjne

Poznaliśmy już wiele przemyślnych technik programowania w bashu, a teraz zapoznamy się z jeszcze jedną. Często przydatne jest aby program posiadał globalny plik konfiguracyjny, rezydujący w katalogu /etc. Na szczęście łatwo jest zrobić to korzystając z basha. Po prostu stwórzmy następujący plik i zapiszmy go jako /etc/ebuild.conf:

Listing 1.17: /ect/ebuild.conf

# /etc/ebuild.conf: ogólnosystemowe ustawienia konfiguracyjne

# MAKEOPTS zawiera opcje przekazywane jako parametry do make
MAKEOPTS="-j2"

W powyższym przykładzie ujęliśmy tylko jedną opcję konfiguracyjną, ale możemya dodać ich znacznie więcej. Wspaniają cechą basha jest to, że plik konfiguracyjny może zostać wykorzystany poprzez zwykłe dołączenie go komendą source do skryptu. Jest to wybieg, który działa w większości interpretowanych języków. Gdy dołączymy plik /etc/ebuild.conf zmienna $MAKEOPTS zostanie zdefiniowana wewnątrz skryptu ebuild. Będziemy ją wykorzystywać, aby pozwolić użytkownikom na przekazywanie parametrów dla make. Zazwyczaj powyższa przykładowa opcja jest przekazywana aby zezwolić na wykonywanie równoległych wywołań make. Zostało to wyjaśnione poniżej.

Uwaga: Czym są równoległe wywołania make? Aby przyspieszyć kompilację na wieloprocesorowych komputerach, make dopuszcza równoległe kompilacje programu. Oznacza to, że zamiast kompilować jeden plik źródłowy naraz, make kompiluje ilość plików określoną przez użytkownika (dzięki czemu wykorzystywany jest potencjał wieloprocesorowych maszyn). Kompilacje równoległe są możliwe poprzez przekazanie opcji -j # do make, na przykład: make -j4 MAKE="make -j4". Ten kod umożliwi kompilację czterech plików źródłowych równocześnie. Argument MAKE="make -j4" nakazuje przekazywanie argumentu -j4 do wszystkich podrzędnych wywołań make.

Oto ostateczna wersja skryptu ebuild:

Listing 1.18: Skrypt ebuild - wersja ostateczna

#!/usr/bin/env bash

if [ $# -ne 2 ]
then
        echo "Podaj nazwę pliku .ebuild oraz unpack, compile lub all"
        exit 1
fi

source /etc/ebuild.conf

if [ -z "$DISTDIR" ]
then
        # ustawiamy DISTDIR jako /usr/src/distfiles jeśli nie zostało to zrobione wcześniej
        DISTDIR=/usr/src/distfiles
fi
export DISTDIR

ebuild_unpack() {
        #upewniamy się, że jesteśmy we właściwym katalogu
        cd ${ORIGDIR}

        if [ -d ${WORKDIR} ]
        then
                rm -rf ${WORKDIR}
        fi

        mkdir ${WORKDIR}
        cd ${WORKDIR}
        if [ ! -e ${DISTDIR}/${A} ]
        then
                echo "${DISTDIR}/${A} nie istnieje. Najpierw pobierz źródła."
                exit 1
        fi
        tar xzf ${DISTDIR}/${A}
        echo "Wypakowano ${DISTDIR}/${A}."
        #źródła zostały wypakowane
}

user_compile() {
        #jesteśmy już w ${SRCDIR}
        if [ -e configure ]
        then
                #uruchamiamy skrypt configure, jeśli istnieje
                ./configure --prefix=/usr
        fi
        #uruchamiamy make
        make $MAKEOPTS MAKE="make $MAKEOPTS"
}

ebuild_compile() {
        if [ ! -d "${SRCDIR}" ]
        then
                echo "${SRCDIR} nie istnieje - najpierw rozpakuj źródła."
                exit 1
        fi
        #upewniamy się, że jesteśmy we właściwym katalogu
        cd ${SRCDIR}
        user_compile
}

export ORIGDIR=`pwd`
export WORKDIR=${ORIGDIR}/work

if [ -e "$1" ]
then
        source $1
else
        echo "Plik ebuild $1 nie istnieje."
        exit 1
fi

export SRCDIR=${WORKDIR}/${P}

case "${2}" in
        unpack)
                ebuild_unpack
                ;;
        compile)
                ebuild_compile
                ;;
        all)
                ebuild_unpack
                ebuild_compile
                ;;
        *)
                echo "Podaj unpack, compile lub all jako drugi argument."
                exit 1
                ;;
esac

Zwróćmy uwagę na fakt, że plik /etc/ebuild.conf jest dołączany na początku skryptu. Zważmy również, iż używamy zmiennej $MAKEOPTS w naszej domyślnej funkcji user_compile(). Być może nie jest do końca jasne w jaki sposób to działa - w gruncie rzeczy odwołujemy się do $MAKEOPTS przed dołączeniem pliku /etc/ebuild.conf, w którym zmienna ta jest definiowana. Szczęśliwie dla nas, działa to poprawnie, ponieważ ekspansja zmiennych zostaje przeprowadzona tylko kiedy wywołujemy funkcję user_compile(). Gdy dokonujmy wywołania tej funkcji plik /etc/ebuild.conf jest już dołączony i zmienna $MAKEOPTS posiada prawidłowe wartości.

Potrafimy coraz więcej

Poznaliśmy w tym artykule wiele technik programowania w bashy, jednak w istocie dotkneliśmy jedynie podstaw. Dla przykładu system ebuildów Gentoo poza rozpakowywaniem i kompilowaniem programów potrafi również:

  • Pobierać źródła jeśli nie ma ich w katalogu $DISTDIR.
  • Weryfikować poprawność źródeł przy użyciu sum MD5.
  • Jeśli jest to wymagane, zapamiętywać wszystkie pliki instalowane w systemie, w celu ich łatwej deinstalacji.
  • Jeśli jest to wymagane, pakować skompilowane aplikacje do tarball (skomperowane tak, jak tego chcemy), co umożliwia ich późniejszą instalację na innym komputerze lub w czasie instalacji systemu.

Dodatkowo posiada on wiele innych globalnych opcji konfiguracji, pozwalających użytkownikowi na wybór flag optymalizacyjnych wykorzystywanych w czasie instalacji i możliwości wsparcia dla innych programów - na przykład dla GNOME.

Oczywiste jest, że bash posiada znacznie bogatsze możliwości niż te, które poznaliśmy w tej krótkiej serii artykułów. Nauczyliśmy się całkiem sporo, więc korzystajmy niesamowitego narzędzia jakim jest bash, aby przyspieszyć naszą codzienną pracę i usprawnić nasze projekty.

2.  Źródła informacji

Przydatne linki



Drukuj

Zaktualizowano 9 października 2005

Podsumowanie: W trzecim z serii artykułów o bashu Daniel Robbins opisuje system ebuildów Linuksa Gentoo, który stanowi świetny przykład możliwości powłoki bash. Krok po kroku opisuje on jak system ebuildów został zaimplementowany i pokazuje wiele przydatnych technik i strategii projektowych. Po przeczytaniu tego artykułu czytelnik będzie miał przejrzysty pogląd na to, co składa się na tworzenie dobrej aplikacji w bashu i będzie mógł zacząć kodowanie własnego systemu na kształt systemu ebuildów w Gentoo.

Daniel Robbins
Autor

Waldemar Korłub
Tłumacz

Donate to support our development efforts.

Copyright 2001-2014 Gentoo Foundation, Inc. Questions, Comments? Contact us.