Home Ostatnie zmiany Kontakt

make

Dość często zdarza mi się pisać różne, małe projekty w ramach przedmiotów na wydziale. Teraz jestem w trakcie pisania “kolejnego programu do katalogowania muzyki” na JPR222. Projekt powstaje w odgórnie narzuconym C++.

C++ w miare znam, napisałem ileś tam hello worldów, nigdy nie przypadł mi do gustu na tyle bym klepał w nim coś większego. Dlatego aby nadać projektowi jakiś sens dydaktyczny postanowiłem samemu zająć się stworzeniem Makefile’a. Zadanie stosunkowo proste, łatwe i przyjemne :)

Dlaczego warto używać make

Pisząc programy zazwyczaj tworzy się kilka(dziesiąt) plików źródłowych. Przed uruchomieniem programu wszystkie pliki muszą przejść przez kompilator i linker.

Naturalnie odpowiednie polecenia można wpisać w terminalu, jendak jest dość męczące. Drugie możliwe rozwiązanie to stworzenie skryptu kompilującego program. To już jest dość proste w obsłudze rozwiązanie, jednak nie pozbawione wad. Zakładając, że projekt zawiera klikadziesiąt plików, których kompilacja zajmuje trochę czasu, skrypt będzie bezmyślnie kompilował wszystkie pliki źródłowe mimo, że w większości przypadków rekompilacji wymagają tylko modyfikowane pliki i zależne od nich. Ponadto tworzenie skryptów kompilujących nie jest w żaden sposób ustandaryzowane, osoby kompilujące program będą zmuszone do przeczytania dokumentacji lub, w przypadku braku odpowiednich informacji, analizowania źródeł skryptu.

W tym momencie na scene wkracza make. Narzędzie to służy do automatyzacji takich zadań jak kompilacja, choć tak naprawdę jest na tyle uniwersalne, że można za jego pomocą zarządzać całym projektem. make ma możliwość wykrywania zmian w plikach i definiowania zależności między nimi, dzięki czemu można zaoszczędzić sporo czasu na kompilacji. Ponadto, w połączeniu z gcc, można zautomatyzować wykrywanie zależności między plikami, dzięki czemu obsługa make’a stanie się banalna.

Aby umożliwić make’owi działanie, konieczne jest utworzenie pliku o nazwie Makefile bądź makefile. Osobiście preferuje pierwszą wersję, w momencie wyświetlania zawartości katalogu Makefile znajduje się w okolicach początku listy. Makefile zawiera opis projektu, określa działania jakie make musi podjąć, aby skompilować program.

Reguły

Makefile składa się z reguł. Za co odpowiadają reguły? Określają kiedy pliki staja się nieaktualne i jakie polecenia należy wykonać, aby te pliki powstały.

Ogólnie reguła wygląda tak:

cel: wymagania
	polecenie
	...

cel i wymagania to nazwy plików, mogą zawierać metaznaki ? i *, które działają tak samo jak w powłoce. cel to plik lub pliki wyjściowe, wymagania wzbogacają make’a o wiedzę, od jakich plików pliki celu są zależne. W momencie, gdy którykolwiek plik wymagany jest nowszy niż docelowy, zostaną wykonane polecenia zawarte w dalszych liniach.

Polecenia wcięte są znakami tabulacji (wcięcia są konieczne do prawidłowej pracy make’a). Warto pamiętać, że make uruchamia osobną powłokę dla kazdego polecenia, próby kombinowania ze zmiennymi środowiska są zapominane między kolejnymi linijkami.

Powszechne jest tworzenie złożonych zależności między plikami w Makefile’u. Złożonych w sensie określania reguł dla plików wymaganych w innych regułach, np.:

amp3lib: amp3lib.o blablabla.o
	gcc -o amp3lib amp3lib.o blablabla.o

amp3lib.o: amp3lib.c amp3lib.h
	gcc -c amp3lib.c

blablabla.o: blablabla.c blablabla.h
	gcc -c blablabla.c

make analizuje cały Makefile i na podstawie tego ustala zależności między plikami. W powyższym przykladzie make skonsoliduje pliki amp3lib.o i blablabla.o pod warunkiem, że są nowsze od amp3lib. Wcześniej jednak zostanie sprawdzony wiek plików amp3lib.o i blablabla.o i porównany z wiekiem odpowiednich plików źródłowych i nagłowkowych. Jeżeli którykolwiek z tych plików okaże się starszy, nastąpi jego kompilacja. Wszystkie cele zależne od niego staną się automatycznie nieaktualne, więc make wykona polecenia określone dla celu amp3lib.

Powyższy Makefile jest kompletnym działającym przykladem. Użycie ogranicza się do jednego polecenia w terminalu:

ruanda@hestia:~$ make amp3lib
gcc -c amp3lib.c
gcc -c blablabla.c
gcc -o amp3lib amp3lib.c blablabla.c

W razie potrzeby, można również wykonać jeden z celów amp3lib.o lub blablabla.o. Poniższe polecenia są również prawidłowe:

ruanda@hestia:~$ make amp3lib.o
gcc -c amp3lib.c
ruanda@hestia:~$ make blablabla.o
gcc -c blablabla.c

Po ponownym wywołaniu make’a widać, że kompilacja nie jest już wykonywana:

ruanda@hestia:~$ make amp3lib
ruanda@hestia:~$

Uzyskanie takiego efektu skryptem w bashu nie jest nie możliwe, ale z całą pewnoscią dużo bardziej czasochłonne.

Cele

Cel zazwyczaj jest nazwą pliku bądź plików. Można jednak określić cele, które nie są powiązane z jakimkolwiek plikiem. Bardzo często w Makefile’ach występują następujące cele:

Powyższe cele nie produkują bezpośrednio żadnych plików, dlatego powinno się to jasno określić w Makefile’u przy użyciu reguły .PHONY:

.PHONY: all clean install

all: amp3lib

clean:
	-rm -f *.o

install:
	cp amp3lib /usr/local/bin

amp3lib: amp3lib.o blablabla.o
	gcc -o amp3lib amp3lib.o blablabla.o

amp3lib.o: amp3lib.c amp3lib.h
	gcc -c amp3lib.c

blablabla.o: blablabla.c blablabla.h
	gcc -c blablabla.c

Powyższy Makefile zadziałałby prawidłowo również po usunięciu reguły .PHONY, jednak według dokumentacji, cele nieprodukujące plików działają wydajniej w momencie odpowiedniego opisania, dlatego warto stosować reguły .PHONY.

Znak minusa w poleceniu wykonującym cel clean jest pewnym zabezpieczeniem przed ewentualnymi błędami wywoływanego polecenie. rm -f *.o usunie wszystkie pliki obiektowe w aktualnym katalogu, jednak, jeżeli żaden plik nie zostanie usunięty, zwrócony zostanie błąd. Błędy zwracane przez polecenia zatrzymują działanie make’a. Takie działanie nie jest jednak zbyt pożadane w momencie czyszczenia plików.

Oczywiście make install wykonany musi być jako root z uwagi na ograniczenia możliwości zapisu w katalogu /usr/local/bin.

Reguły przetwarzania plików

make ma wbudowaną wiedzę na temat przekształcania róznych rodzajów plików. Nie ma potrzeby określania w jaki sposób skompilować plik oraz jak przeprowadzić konsolidację. Reguła

amp3lib: amp3lib.o blablabla.o

wystarczy aby make skompilował kolejno pliki amp3lib.c i blablabla.c, a następnie przeprowadził ich konsolidację. Dzięki domyślnym regułom przetwarzania plików można znacząco skrócić Makefile.

W razie konieczności domyślne reguły można nadpisać.

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

% w nazwie celu oznacza dowolny ciąg znaków. %.o oznacza więc wszystkie pliki obiektowe. W liście wymagań % ma nieco inne znaczenie. Pozwala on zdefiniować relację wymagania do celu, tzn. % jest zastępowany przez tekst jaki został dopasowany w określeniu celu. Jeżeli wzorzec %.o pasuje do plików amp3lib.o i blablabla.o, to nasz Makefile działa identycznie do następującego:

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

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

$< zawsze zawiera listę plików wymaganych przez aktualną regułę, $@ zawiera listę plików docelowych.

Źródła zewnętrzne


Ostatnio edytowane 2009-03-30 21:04 UTC przez ruanda (różnice)