Narzędzia

Każdy większy projekt potrzebuje jakiegoś sposobu na zapanowanie nad procesem kompilacji. Do rozwiązania tego problemu można użyć zwykłego skryptu powłoki, ale o wiele lepszym sposobem jest użycie narzędzia budowania. Jednym z najczęściej używanych narzędzi tego typu (szczególnie w świecie opensource) jest make. Poza samym procesem kompilacji make umożliwia częściową rekompilację programu, poprzez sprawdzenie, które moduły zostały zmienione, a które nie. Choć nie jest to zbyt wyrafinowany test i odbywa się za pomocą porównania daty ostatniej modyfikacji pliku źródłowego z datą skompilowanego modułu.

Opis polecenia make

Wydanie polecenia make powoduje interpretację pliku zawierającego informacje co gdzie i jak ma zrobić make. Plik ten może mieć jedną ze standardowych nazw: GNUmakefile, makefile, Makefile lub dowolną inną określoną przez opcję -f.
Bezpośrednio w linii poleceń możemy także podać nazwę celu który ma zostać wykonany, choć nie jest to konieczne (jeżeli jej nie podamy zastanie wykonany pierwszy cel w pliku nie rozpoczynający się od kropki).

Składnia polecenia make

make opcje cel

Parametr opcje :

Najważniejsze opcje polecenia make
-e zmienne z pliku make są przysłaniane przez zmienne środowiskowe o tych samych nazwach
-f nazwa_pliku nazwa pliku make, który zostanie użyty
-o nazwa_pliku plik o podanej nazwie nie zostanie zmieniony (rekompilowany)
-r wyłącza domyślne reguły i przyrostki

Parametr cel

Jak już wcześniej pisałem tym parametrem możemy określić, który cel wykona make. Jest to parametr opcjonalny i gdy go nie podamy make wykona pierwszy cel nie zaczynający się od kropki.

Plik Makefile

Skrypt programu make jest najczęściej nazywany Makefile, więc będę używał tej nazwy, należy jednak pamiętać że można użyć dowolnej nazwy (dzięki opcji -f).
Plik Makefile jest plikiem tekstowym zawierającym listę reguł mówiących w jaki sposób należy odświeżyć/rekompilować pliki.

Pojedyncza reguła ma następującą składnię :

Cel : obiekty zależne
    polecenia

Cel

Celem jest zwykle nazwą pliku docelowego, lub specjalna nazwą dla bloku poleceń (tzw. cel typu phony). Celem jest na przykład plik o rozszerzeniu .omain.o , a najbardziej znanym celem phony jest install – wywoływał go każdy kto instalował jakiś program ze źródeł w systemie linux.

Obiekty zależne

Obiektami zależnymi są wszystkie pliki i inne cele, od których zależy nasz cel np. Jeżeli naszym celem jest main.o to obiektem zależnym jest main.c i main.h

Polecenia

Słowo polecenia określa blok komend, które trzeba wykonać aby uzyskać cel, np.

gcc -c main.c -o main.o

Przykład reguły

main.o : main.c main.h
    gcc -c main.c -o main.o

Powyższa reguła mówi, że plik main.o zależy od main.c i main.h oraz że aby uzyskać main.o należy wykonać polecenie: gcc -c main.c -o
main.o

Typy reguł

Reguły dzielimy na :

  • proste – jak np. ta powyżej – mamy określone pliki i polecenie do ich wykonania
  • oparte na wzorcach – wyglądają tak samo jak reguły proste z tą różnicą, że część nazw plików jest maskowana znakiem % np. %.c – może oznaczać każdy plik *.c

    Przykład reguły opartej na wzorcach :

    %.o : %.c
        gcc -c $< -o $@

    powyższa reguła mówi w jaki sposób otrzymać plik *.o. Każe make znaleźć odpowiadający plik .c i wykonać na nim polecenie: gcc -c $< -o $@. Nazwa celu kryje się pod zmienną wewnętrzną $@ a nazwa obiektu zależnego pod nazwą $<. Może się zdarzyć, że kilka reguł będzie pasowało do tego samego pliku wówczas wykonywana jest pierwsza odpowiadająca reguła z pliku.

  • oparte na przyrostkach – reguła oparta na przyrostkach zaczyna się od dwóch rozszerzeń plików np. .c.o, pierwsze rozszerzenie oznacza plik źródłowy a drugie plik wynikowy. Reguły przyrostkowe nie mogą mieć żadnych obiektów zależnych, wszelkie zdefiniowane obiekty zależne są ignorowane.

    Przykład reguły opartej na przyrostkach :

    .c.o:
        gcc -c $< -o $@

    powyższa reguła mówi że aby otrzymać plik *.o trzeba znaleźć odpowiadający jej plik *.c i uruchomić polecenie gcc z nazwą celu $< i nazwą obiektu zależnego $@.

    make standardowo definiuje kilka przyrostków między innymi .c .o aby zdefiniować własne należy użyć celu specjalnego .SUFFIXES

 

Cele specjalne

Wszystkie cele rozpoczynające się od kropki są przez make uważane za specjalne i służą do sterowania pracą make (w tym reguły przyrostkowe).

Najważniejsze cele specjalne :

.SUFFIXES – cel ustawiający rozpoznawalne przez make przyrostki, ma znaczenie tylko gdy używamy reguł przyrostkowych. Różne wersje make definiują różne standardowe przyrostki więc najlepiej ustalić własne.

Aby ustawić własne przyrostki należy najpierw zresetować poprzednie poprzez użycie celu .SUFFIXES bez żadnych przyrostków, i ponownie z nowymi przyrostkami:

.SUFFIXES:
.SUFFIXES: .c .o .h

 

.PHONY – mówi make, że podane za nim cele są nazwą bloku poleceń, a nie realnym plikiem (tzw. phony). Cel tego typu jest zawsze wykonywany gdy wywoła go make niezależnie czy istnieje plik o jego nazwie.

Przykład celów .PHONY:

.PHONY: all clean

all: main.o
    gcc main.o -o program
main.o : main.c
    gcc -c

clean:
    rm *.o temp

W powyższym przykładzie mamy zdefiniowane dwa cele typu phony: clean i all.

Gdy zostanie uruchomiony cel all make dowiaduje się, że najpierw musi zostać wykonany cel main.o i dopiero po jego przetworzeniu (jeżeli plik main.c jest nowszy od main.o) wykonywane są polecenia z celu all czyli linkowanie.

Jeżeli uruchomimy cel clean (za pomocą polecenia: make clean) usuwane są wszystkie pliki *.o i plik temp.
Cel .PHONY mówi make, że cele all i clean są tylko nazwami dla bloków poleceń i należy je wykonywać nie zależnie czy w katalogu istnieją pliki all lub clean.

Zmienne wewnętrzne

Najważniejsze zmienne wewnętrzne:
$@ nazwa celu, który trzeba stworzyć np. modul.o
$< nazwa aktualnego obiektu zależnego
$? nazwy wszystkich obiektów zależnych, które się zmieniły
$^ nazwy wszystkich obiektów zależnych oddzielone od siebie spacją
$* wycinek nazwy, odpowiadający % np jeżeli wzorzec jest a%.c a nazwa pliku aha.c to $* ma wartość 'ha’. Jeżeli reguła została zapisana w starym sposobie przyrostków to $* jest nazwą bez przyrostku.

Każdą zmienną wewnętrzną możemy wykorzystywać w odwołaniu tylko do ścieżki dostępu lub nazwy pliku.

Najważniejsze zmienne wewnętrzne – odwołanie do ścieżki dostępu lub samej nazwy pliku
$(@D) ścieżka dostępu z nazwy celu np. programc z programc/main.o
$(@F) nazwa pliku z nazwy celu np. main.o z programc/main.o
$(*D) część z wycinka nazwy %, określająca ścieżkę dostępu.
$(*F) część z wycinka nazwy %, określająca nazwę pliku
$(<D) ścieżka dostępu z nazwy obiektu zależnego
$(<F) sama nazwa pliku z pełnej nazwy obiektu zależnego
$(^D) lista wszystkich ścieżek dostępu z wszystkich nazw obiektów zależnych
$(^F) lista samych nazw plików z wszystkich obiektów zależnych
$(?D) lista ścieżek dostępu z wszystkich nazw obiektów zależnych które się zmieniły.
$(?F) lista samych nazw plików bez ścieżki dostępu określających obiekty zależne, które się zmieniły

Przykładowy plik make i jego interpretacja

SHELL=/bin/sh
.PHONY: all clean cleanall install uninstall

all: main.o modul1.o modul2.o
    gcc main.o modul1.o modul2.o -o superprogram

%.o : %.c
    gcc -c < -o $@

clean:
    rm -f *.o 

cleanall: 
    clean rm -f superprogram

install:
    cp ./superprogram /usr/local/bin

uninstall: 
    rm -f /usr/local/bin/superprogram

Po uruchomieniu make bez określenia celu, make rozpoczyna działanie od obiektu all.

Make musi spełnić zależności czyli sprawdzić czy istnieją pliki main.o modul1.o modul2.o i czy są one aktualne, wykorzystuje do tego regułę %.o : %.c. Jeżeli jakiś plik nie jest aktualny zostaje on powtórnie przekompilowany za pomocą polecenia z reguły %.o : %.c

W naszym przykładzie make może zostać wywołane do wykonywania innych zadań np. instalacji – make install , czyszczenia plików tymczasowych – make clean , pozostawienia w katalogu jedynie plików źródłowych – make cleanall, odinstalowania programu – make uninstall.

Ogólno przyjęte konwencje w pisaniu plików make

  • każdy plik make powinien posiadać linijkę deklarującą zmienną SHELL :
    SHELL=/bin/sh
  • skrypty powinny być pisane bez wykorzystania rozszerzeń różnych powłok np. bash

 

Fot. flattop341, CC BY 2.0