23 października 2008

Dłubiemy w systemie - cz. 1: Kompilacja oprogramowania

Chciałbym tym wpisem rozpocząć krótką serie, w której opisywać będę najpopularniejsze metody dostrajania systemu i oprogramowania. Zaczynamy!

Ręczna kompilacja oprogramowania.
Zanim zaczniemy zastanówmy się dlaczego warto cokolwiek ręcznie kompilować. No właśnie - po co? Najczęstsze przyczyny to:

  • Brak danego programu/biblioteki repozytorium dystrybucji,
  • Chęć lepszego dostosowania oprogramowania do naszych potrzeb,
  • Optymalizacja kodu (flagi kompilatora, flagi linkera, stripowanie),
  • Chcemy zaingerować w kod programu/biblioteki (łaty, własne modyfikacje)
  • Gdy nie mamy dostępu do root'a, a koniecznie musimy zainstalować jakiś program, podczas gdy admin wypoczywa przy szklance soku w Pcimiu Dolnym.

Pierwszym krok""iem jest zaopatrzenie się w odpowiednie narzędzia - niezbędny będzie kompilator (gcc), linker (ld, wchodzi w skład binutils), biblioteki (przede wszystkim glibc-devel), automake, autoconf i inne. Skąd mieć pewność, że mamy wszystko co potrzebne - o tym za chwilę.

Następny krok to zaopatrzenie się w źródła (kod źródłowy) danego programu/biblioteki. W tym celu musimy udać się na stronę danego projektu lub zapytać wujka google'a. Najczęściej występują w postaci archiwum tar skompresowanych gzip'em/bzip'em (*.tar.gz, *.tar.bz2, czasem skracane do *.tgz, *.tbz2). Naukę najlepiej zacząć od niewielkich programów, np. irssi - konsolowy klient IRC.

W konsoli przechodzimy do katalogu, gdzie zapisaliśmy archiwum (zalecam wypakowywanie do pustego katalogu), następnie rozpakowujemy je poleceniem:

$ tar jxvf nazwa_archiwum.tar.bz2

lub

$ tar zxvf nazwa_archiwum.tar.gz

Flaga (opcja) 'z' informuje program tar, że mamy do czynienia z archiwum spakowanym gzipem, a w przypadku 'j' - bzipem. 'x' mówi że chcemy coś wypakować, 'v' oznacza tryb "gadatliwy" (ang. verbose), a 'f' wskazuje, że chcemy rozpakować plik, którego nazwa jest dalej. Szczegółowe opisy opcji polecenia tar można znaleźć w man tar

Jeśli w wyniku tego procesu otrzymamy nowy podkatalog - przechodzimy do niego (ok. 85% przypadków).

Na moment zostawię konsolę w spokoju, aby poruszyć kwestię optymalizacji. Informacje o tym jak ustawiać flagi, które z nich są 'bezpieczne', a które nie, można znaleźć w Hoppke Repo. Dodam tylko, że zauważyłem, iż w ostatnim czasie stosowanie flagi -fomit-frame-pointer dla C++ ($CXXFLAGS) przestało powodować problemy (sam używam tej flagi, korzystam z gcc serii 4.3).Nawet jeśli ktoś nie ma zamiaru skupiać się na studiowaniu flag kompilatora, to nie zaszkodzi na stałe ustawić popularnych i bezpiecznych flag dla konkretnego modelu procesora. Chyba, że zależy nam na przenośności (np. tworzymy paczkę, która ma być dostępna dla każdego użytkownika danej dystrybucji), wtedy najlepiej kompilować pod i686 (pentium pro i nowsze).

Wróćmy do procesu kompilacji. Etapem wstępnym do właściwej kompilacji jest konfiguracja. Odbywa się to poprzez wywołanie skryptu configure z odpowiednimi opcjami. Własnie - jakimi opcjami? Listę dostępnych opcji uzyskamy po wpisaniu:

$ ./configure --help

Część opcji występuje zawsze, lub bardzo często, natomiast spora część bywa unikatowa, tylko dla danego oprogramowania. Ich opisy znajdziemy po wywołaniu w.w. polecenia, ale pozwolę sobie opisać (moim zdaniem) najważniejszą, czyli --prefix=... Czym jest prefix? W Linuksie oraz Uniksie stosuje sie filozofię, iż biblioteki lądują w katalogu lib, binarki w bin lub sbin, pliki nagłówkowe w includes itd. Są to założenia standardu FHS
Prefix definuje ścieżkę gdzie umieszczona ma zostać w.w. struktura. Prosze zauważyć że podobne katalogi znajdziemy w /,
/usr, /usr/local, a czasem /opt/nazwa_programu/. Za pomocą prefiksu definiujemy gdzie zainstalujemy dany program. Ktoś teraz zapyta: "super, mamy wybór gdzie chcemy instalować oprogramowanie, ale co nam to daje i dlaczego ten nawiedzony człowiek nazwał to najwazniejsza opcją?" Otóż jest kilka istotnych powodów:

  • Wyobraźmy sobie sytuację, że posiadamy konto shellowe, bez uprawnień pozwalających na instalację pakietów binarnych, czy zapisu w /usr, /usr/local, czy /opt. W takim przypadku możemy ustawić prefix na miejsce, do którego mamy pełne prawa i gdzie nie zajrzy nam inny użytkownik - takim miejscem jest katalog domowy użytkownika, jednak by nie robic sobie balaganu proponuje utworzyc specjalny katalog na wlasne oprogramowanie i dodac go do sciezki podanej w prefixie. Przyklad użycia:

    $ ./configure --prefix=~/moje_programy [--inne-opcje]

    Przy zastosowaniu takiego egzotycznego prefiksu standardowo nie będziemy mogli uruchamiać programów poprzez samo wpisane ich nazwy. Musimy podawać pełną ścieżkę do binarki - czyli ~/moje_programy/bin/nazwa_programu. Da się to łatwo zmienić. Wystarczy zmodyfikować zmienną środowiskową $PATH przechowującą listę katalagów, gdzie powłoka ma szukać programów. Poszczególne ścieżki oddzielone są dwukropkami, a im wcześniej dana lokalizacja występuje tym wyższy ma priorytet. W praktyce wystarczy zmodyfikować/utworzyć ~/.bashrc (ukryty (kropka na początku nazwy) plik teksotwy, którego zawartość jest wykonywana podczas każdego uruchomienia powłoki BASH). Umieszczamy w nim następującą linijkę (najlepiej dopisać do końca):

    export PATH=~/moje_programy/bin:$PATH


    Jak widać nowa ściezka będzie miała najwyższy priorytet (jeśli w systemie jest już zainstalowany dany program, ale np. jego wersja/konfiguracja nam nie odpowiada, to mamy pewność, że po wpisaniu samej nazwy polecenia uruchomi się "nasz" program). Aby 'wczytać' nowe ustawienia wydajemy polecenie:

    $ source ~/.bashrc



  • Domyślną wartością opcji prefix jest /usr/local. Jak mówi FHS jest to specjalne miejsce na oprogramowanie instalowane lokalnie, wydzielone w celu uniknięcia 'kolizji' plików należących do pakietów binarnych (dla których przeznaczono /usr) z 'naszymi' podczas ew. aktualizacji systemu. Nie polecam instalacji z prefixem do /usr, chyba że wiemy co robimy (np. konfigurujemy żródła z myślą o budowie pakietu binarnego dla naszej ulubionej dystrybucji)

To właśnie ./configure i jego opcje są jedna z największych zalet własnoręcznej kompilacji i systemów typu source-based. Możemy zdecydowac, z których funkcji programu będziemy korzystać i (co za tym idzie) maja zostać wkompilowane, a które sa z naszego punktu zbędne i chcemy odchudzić z nich program.

Działanie skryptu możemy podzielic na 2 fazy - sprawdzającą i generującą. Podczas pierwszej sprawdzana jest dostępność niezbędnych bibliotek i narzędzi w systemie, im więcej ficzerów (opcji, funkcji - używam tutaj popularnego spolszczenia od ang. 'features') wybierzemy (--with-costam) tym większe zalezności ("wymagania software'owe") będzie miał nasz kompilat. Działa to również w drugą stronę - jeśli ./configure przerywa działanie i krzyczy że brakuje mu libcośtam najpierw sprawdzamy czy dana biblioteka/narzędzie nie ma związku z ficzerem, którego nie zamierzamy używać - jeśli tak to możemy spokojnie ogołocić nasz program z danej funkcji uruchamiając skrypt jeszcze raz, tym razem z dodatkową opcja --without-cośtam. W przeciwnym wypadku - musimy "spełnić zależności" czyli doinstalowac co trzeba. Jeśli mimo tego ./configure dalej nie 'widzi' biblioteki/narzędzia, to nie trzeba się załamywać - prawdopodobnie to wina nietypowego prefiksu i wystarczy odp. skonfigurować konsolidator dynamiczny/zmienną środowiskową $PATH aby rozwiązać problem, ale to już temat na osobny wpis.

Drugą fazą procesu jest automatyczne tworzenie plików makefile, które są fundamentem kolejnego etapu, czyli właściwej kompilacji. I tu niespodzianka bo..

..właściwa kompilacja sprowadza się tylko do jednego polecenia:

$ make [opcje]

Warto wspomnieć o opcji -jN (gdzie N to liczba procesorów + 1). Definiuje ona maksymalną ilość operacji wykonywanych jednocześnie.
Jeśli zdefiniowaliśmy własne flagi kompilatora i eksportujemy je globalnie (np. w
/etc/profile albo /etc/make.conf) lub lokalnie (np. w ~/.bashrc) wystarczy nam samo polecenie make. W przeciwnym wypadku właśnie w tej chwili możemy podać nasze flagi kompilatora. Przykład:

$ CFLAGS="-march=athlon-xp -O2 -fomit-frame-pointer -DNDEBUG" CXXFLAGS=$CFLAGS LDFLAGS="-s -z combreloc" make -j2

Jeśli otrzymamy komunikat informujący o 'braku celu do wykonania' oznacza to, że pliki makefile, z których korzysta make nie zostały utworzone. Prawdopodobnie przeoczyliśmy, że ./configure zakończyło się niepowodzeniem.

Ostatnim krokiem jest instalacja. Służy do tego proste polecenie:

# make install

Prosze zwrócić uwagę na znak zachęty - jest to #, ponieważ tę operację wykonujemy z root'a (tzn. wykonujemy ją z poziomu tego użytkownika, który ma prawo zapisu w katalogu podanym jako prefiks. Jeśli instalujemy oprogramowanie w naszym katalogu domowym, możemy ten krok wykonać z poziomu zwykłego użytkownika).

Co prawda mamy możliwość odinstalowania programu poprzez wywołanie polecenia make uninstall w katalogu, jednak nie jest to najwygodniejsze rozwiązanie ponieważ:

  • w przypadku instalacji kilku wersji, z tym samym prefiksem, może dojść do usunięcia plików, których nie chcielibyśmy stracić
  • musimy trzymać katalog 'roboczy', lub pamiętać jakich opcji/prefiksów używaliśmy aby w celu usunięcia programu z systemu wypakować źródła, uruchomić ./configure jeszcze raz, a zaraz po nim make uninstall

Lepsze metody? Tworzenie pakietów! I wcale nie trzeba studiować dokumentacji, a potem wklepywać poleceń jedno po drugim - istnieje automat, zastępujący polecenie make install. Ten automat to checkinstall

Jego użycie nie powino sprawiać większych problemów, można tworzyć zarówno pakiety TGZ (Slackware, Zenwalk i pochodne), DEB (Debian, Ubuntu i pochodne) oraz RPM (Red Hat, CentOS, Fedora Core i pochodne).

Mimo wszystko jeśli chce się zostać developerem "pełną gębą" (dokładniej opiekunem kilku pakietów) jakiejś dystrybucji, założyć własne repozytorium, czy 'paczkować' programy z poza repozytorium i umieszczać np. na linuxpackages.net, wypada poznać natywne (klasyczne) metody tworzenia pakietów. Checkinstall jest świetnym narzędziem, ale nie oferuje 100% kontroli nad tym co się dzieje. Jeśli tworzymy pakiety binarne tylko dla siebie, będzie niezastąpiony.

W następnej części cyklu opiszę klasyczną metodę tworzenia pakietów TGZ dla Slackware. Podstawy już są ;) Zapraszam do komentowania.

4 komentarze:

Anonimowy pisze...

Do takich rzeczy przydaje się system portów (na bsd) lub portage (Gentoo), kompilacja wszystkiego po kolei to straszna robota...

Hwast pisze...

Opis ma na celu wprowadzenie do samego zagadnienia kompilacji. Nie chodzi o to, żeby zaraz budowac sobie toolchain i kompilowac system od zera. Ten post może byc przydatny np. zielonemu userowi, który musi doinstalować jakiś sterownik a ten jest dostepny jedynie w postaci źródeł lub gdy potrzebuje (użytkownik) narzędzia z poza repozytorium.

Pozdrawiam

sl3dziu pisze...

O ile do notki nie mam zastrzeżeń, to zrobiłbyś coś z szablonem - męczące jest czytanie tak wąskiego tekstu..

Hwast pisze...

Od jakiegoś czasu przymierzam się do modyfikacji szablonu, myślę że w ten weekend uda się rozpocząć radykalne działania ;)