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	
w3cw3c
automatyka przemysłowa