Makefile
-- Sebastian Pawlak
Kompilacja oraz linkowanie wielu komponentów w jeden plik nie muszą być
wykonywane za każdym razem z linii poleceń.
Aby zautomatyzować procedurę budowy projektu, można skorzystać z usług
programu "make".
Za jego pomocą kompilacja ograniczy się do wprowadzenia nazwy "make"
w katalogu, w którym znajduje się plik "makefile" lub "Makefile"
(można tworzyć pliki o innych nazwach ;
"make" należy wtedy wywoływać:
"make -f moj_makefile") zawierający procedurę kompilacji.
"make" dokona kompilacji plików źródłowych (tylko tych,
których kod źródłowy uległ zmianie, lub jeśli nie istnieją ich
skompilowane wersje), a następnie wykona
linkowanie. Sposób kompilacji, w pliku "makefile", definiuje sam użytkownik.
Poniżej podam zasady tworzenia plików "makefile".
Załużmy, że mamy projekt zawierający pliki: - svga.c : biblioteka z funkcjami graficznymi, - svga.h : deklaracje funkcji z biblioteki graficznej svga.c, - main.c : główny program, - stdio.h : deklaracje funkcji dołączanych do main.c.
Aby otrzymać program wynikowy należy dokonać kompilacji i linkowania.
kompilacja pliku źródłowego:
cc -c plik.c -I path
linkowanie plików obiektowych:
cc plik.o -o plik.out -L path -l libname
cc plik.o -L path -l libname - domyślny plik wynikowy "a.out"
cc plik.c -o plik.out - kompilacja + linkowanie
Linkowania plików składowych projektu należy dokonać w odpowiedniej kolejności. Linkowanie niektórych plików może wymagać
bibliotek, które powinny być skompilowane uprzednio. Zależności pomiędzy poszczególnymi elementami można przedstawić
tak jak na poniższym grafie:
Wynika stąd, że utworzenie programu "program.out" zależy od istnienia plików "svga.o" oraz "main.o". Z kolei, te pliki
mogą powstać dzięki plikom "svga.c", "svga.h" dla "svga.o" oraz "svga.h", "main.c", "stdio.h" dla "main.o".
W pliku "makefile" opisujemy zależności pomiędzy plikami:
program.out: svga.o main.o cc svga.o main.o -o program.out svga.o: svga.c svga.h cc -c svga.c main.o: svga.h main.c stdio.h cc -c main.c
Dzięki tym zdefiniowanym zależnościom, "make" zbuduje program wynikowy, dokonując kompilacji i linkowania
plików składowych w odpowiedniej kolejności. Użycie "make`a" ma jeszcze jedną zaletę. Załóżmy, że w jednym z plików
(np. "main.c") istnieje błąd. Program wynikowy, po uruchomieniu nie działa poprawnie. Programista modyfikuje błąd,
edytując w tym celu plik "main.c". Aby uruchomić poprawny program, ponownie trzeba go zbudować "make`em".
Należy tu zauważyć, że niezbędne jest skompilowanie tylko plików wchodzących w skład "main.o", gdyż w "svga.*" nie
dokonano żadnych zmian. "Make" na podstawie daty plików źródłowych
oraz plików z obiektami, automatycznie stwierdzi, czy wymagana jest kompilacja plików wskazanych w "makefile".
Takie podejście przyśpiesza tworzenie projektu.
Tak wygląda szkielet pliku "makefile":
target(s): source file(s) command - ten wiersz musi rozpoczynać się od tabulacji
Powyższy plik "makefile" można jeszcze skrócić. Podając "svga.o: svga.c svga.h" program "make" potrafi automatycznie wygenerować "cc -c svga.c". Tak więc polecenie kompilacji można pominąć. Oto zawartość skróconego pliku "makefile":
program.out: svga.o main.o cc svga.o main.o -o program.out svga.o: svga.c svga.h main.o: svga.h main.c stdio.h
Kolejne skrócenie "makefile" można wykonać wykorzystując fakt, iż istnieje możliwość podawania kilku plików w polu "target":
program.out: svga.o main.o cc svga.o main.o -o program.out svga.o main.o: svga.c svga.h main.o: main.c
Należało tu skorzystać z cechy, że jeśli jakiś plik pojawi się jako "target" więcej niż raz, wtedy wszystkie podane dla niego pliki źródłowe są łączone.
Jak już wspomniałem, program "make" automatycznie rozpoznaje, które plik zostały zmienione i wykonuje odpowiednie akcje.
Można jednak samemu wskazać jakie polecenie ma być wykonane, dla każdego zmienionego pliku odzielnie:
target :: source1 command1 target :: source2 command2
Jeśli plik "source1" zostanie zmieniony to "target" zostanie stworzony komendą "command1".
W pliku "makefile" można podać kilka targetów. Poniższy plik daje możliwość wywołania programu "make" z parametrem:
"make clean" co spowoduje skok do etykiety "clean".
program.out: svga.o main.o cc svga.o main.o -o program.out svga.o main.o: svga.c svga.h main.o: main.c clean: rm *.o
***
Składnia plików "makefile" zwiera wiele cech. Możliwe jest definiowanie makr. Poniższy przykład pokazuje ich użycie:
PLIKI_O = svga.o main.o program.out: $(PLIKI_O) cc $(PLIKI_O) -o program.out svga.o: svga.c svga.h cc -c svga.c main.o: svga.h main.c stdio.h cc -c main.c
Wartość makra można także podać z linii poleceń podczas uruchamiania programu "make":
make 'PLIK_O=svga.o main.o nowy.o' program.out
Istniejąca w pliku "makefile" wartość makra "PLIK_O" zostanie nadpisana przez wartość podaną w linii poleceń.
"Make" zawiera także szereg makr wewnętrznych:
CC Zawiera nazwę domyślnego kompilatora (domyślnie "cc" - można przedefiniować), $@ Nazwa aktualnego "target`a", $? Lista plików zależnych od aktualnie przetwarzanego "target`a", które uległy zmianie.
Możliwa jest także taka konstrukcja:
$(PLIK_O:.o=.c)
która dla początkowej wartości "PLIK_O = plik.o" wygeneruje "PLIK_O = plik.c".
***
Długie linie można podzielić stawiając znak "\" na końcu wiersza:
pierwsza_czesc druga_czesc powyższa linia odpowiada tej: pierwsza_czesc \ druga_czesc
Komentarze uzyskuje się poprzez wstawienie znaku "#", po którym następuje tekst komentarza:
# # Makefile do programu xxxxxxx # main.o: main.c # plik main.o zalezy od main.c
***
if ... elif ... else ... endif
%if $(CC) == cc # kompilacja "cc" %elif $(CC) == gcc # w przeciwnym wypadku jesli "gcc" %else # w przeciwnym wypadku %endif