Środowisko programisty/Automatyzacja kompilacji - make: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Pan (dyskusja | edycje)
Podział na sekcje
Pan (dyskusja | edycje)
Pierwszy makefile: Pierwsza wersja
Linia 51: Linia 51:


== Pierwszy makefile ==
== Pierwszy makefile ==
Program <code>make</code> czyta co ma zrobić z pliku o nazwie <code>makefile</code>, zatem cała sztuka użycia <code>make</code> sprowadza się do umiejętności pisania pliku <code>makefile</code>.
Najważnieszą składowa pliku <code>makefile</code> są ''reguły''. Reguła wygląda następująco:
cel: zależności
        polecenie
Reguła określa w jakis sposób należy budować '''cel'''. Znaczenie poszczególnych składników.
;cel:nazwa pliku, który ma powstać z tej reguly,
;zależności:lista plików od których zależy '''cel''', tzn. zmiana któregokolwiek z tych plików oznacza, że '''cel''' też się zmieni,
;polecenie:jest to polecenie za pomocą, którego ma zostać wytworzony '''cel'''.
'''Uwaga!''' Istotne jest, aby '''polecenie''' było wcięte za pomocą jednego znaku tabulacji, a nie przypadkiem spacji. Jest to dosyć kłopotliwe, ale cóż, każde narzędzie miewa swoje widzimisie.
Dla przykładu reguła
komunikat.o: komunikat.c komunikat.h
        gcc -Wall -c komunikat.c -o komunikat.o
mówi w jaki sposób tworzyć plik <code>komunikat.o</code>. Zależy on od dwóch plików źródłowych <code>komunikat.c</code> i <code>komunikat.h</code>.
W jaki sposób znajdować pliki zależna? Można posłużyć się opcją <code>-MM</code> polecenia <code>gcc</code>.
gcc -MM plik.c
Powyższe polecenie wyrzucie linię
plik.o: plik.c inne pliki źródłowe
pokazującą jakie są jeszcze inne pliki źródłowe, od których zależy dany plik. Forma wyjścia nie jest przypadkowa. Jest taka, aby było można ją łatwo wstawić do pliku <code>makefile</code>.
Z pomocą <code>gcc -MM</code> lub bez tworzymy nasz pierwszy <code>makefile</code>, który wygląda tak:
<pre>
program: komunikat.o main.o test.o
        gcc -lm komunikat.o main.o test.o -o program
komunikat.o: komunikat.c komunikat.h
        gcc -Wall -c komunikat.c -o komunikat.o
main.o: main.c test.h
        gcc -Wall -c main.c -o main.o
test.o: test.c test.h komunikat.h
        gcc -Wall -c test.c -o test.o
</pre>
Teraz w wyniku wykonania polecenia <code>make</code> mamy taki efekt:
$ make
gcc -Wall -c komunikat.c -o komunikat.o
gcc -Wall -c main.c -o main.o
gcc -Wall -c test.c -o test.o
gcc -lm komunikat.o main.o test.o -o program
Zostały utworzone odpowiednie pliki <code>*.o</code> oraz program wykonywalny <code>program</code>.
W jaki sposób zadziałał <code>make</code>? Otóż jeśli nie podamy mu żadnego argumentu, to próbuje on utworzyć cel występujący w pierwszej regule pliku <code>makefile</code>. W tym celu tworzy wpierw wszystkie pliki od których on zależy, jeśli takowe jeszcze nie istnieją. Do tworzenia plików <code>*.o</code> używa dalszych reguł. Tworzenie celu kończy się porażką jeżeli zajdzie jeden z przypadków:
* plik, od którego zależy cel, nie zostanie znaleziony,
* nie będzie można znaleźć reguły, do utworzenia danego pliku,
* polecenie podane w regule skończy się porażką.
Ponadto <code>make</code> potrafi stwierdzać, czy jest potrzeba ponownego wykonania poleceń kompilacji. Wykonajmy go jeszcze raz:
$ make -f simple.mak
make: `program' jest aktualne.
co oznacza, że plik wykonywalny <code>program</code> jest aktualny i nie trzeba nic uruchamiać.
Teraz zmodyfikujmy jakiś plik. Zasymulujemy to poleceniem <code>touch</code>:
$ touch komunikat.h
Uruchamiamy <code>make</code> jeszcze raz:
$ make -f simple.mak
gcc -Wall -c komunikat.c -o komunikat.o
gcc -Wall -c test.c -o test.o
gcc -lm komunikat.o main.o test.o -o program
Tym razem ponownie zostały utworzone te pliki wynikowe, które zależały od <code>komunikat.h</code>, a mianowicie są to <code>komunikat.o</code> i <code>test.o</code>. Oczywiście zostało też ponowione linkowanie.


== Reguły jak polecenia ==
== Reguły jak polecenia ==

Wersja z 15:45, 27 wrz 2006

Wstęp

Przypuśćmy, że piszemy program w C, który składa się z kilku plików, a mianowicie main.c, komunikat.c, komunikat.h, test.c, test.h. Dla przykładu niech to będą bardzo proste źródła .

Kompilujemy je stopniowo. Wpierw tworzymy pliki *.o z plików *.c, a następnie je linkujemy. Czyli kolejne polecenia kompilacji wyglądają następująco:

gcc -Wall -c komunikat.c -o komunikat.o
gcc -Wall -c main.c -o main.o
gcc -Wall -c test.c -o test.o
gcc -lm komunikat.o main.o test.o -o program

Dla ułatwienia kompilacji możemy sobie te polecenia zapisać do skryptu i uruchamiać ten skrypt za każdym razem, gdy zmodyfikujemy jakieś źródła. Skrypt ma jednak parę wad. Po pierwsze jeśli dodamy lub usuniemy pliki źródłowe C, to będziemy musieli przeedytować skrypt. Możemy z tym problemem sobie poradzić, jeśli w danym katalogu znajdują się tylko pliki źródłowe należące do danego programu. Wtedy wystarczy przerobić skrypt.

for f in *.c; do
  gcc -Wall -c $f -o ${f%c}o
done
gcc -lm *.o -o program

Druga wada skryptu jest taka, że kompilujemy za każdym razem wszystkie źródła, a przecież wystarczy skompilować tylko te co się zmieniły i powtórzyć linkowanie plików *.o w jeden program. Taki skrypt jesteśmy w stanie napisać, ale zrobiłoby się to bardzo skomplikowane.

Po za tym co jak będziemy chcieli zmienić opcje kompilacji, linkowania, itp. Będziemy musieli rozbudować nasz skrypt znacznie. Tymczasem gotową automatyzację udostępnia program make i skupimy się dalej na wykorzystaniu tego narzędzia do ułatwienia życia przy rekompilacji programu.

Pierwszy makefile

Program make czyta co ma zrobić z pliku o nazwie makefile, zatem cała sztuka użycia make sprowadza się do umiejętności pisania pliku makefile.

Najważnieszą składowa pliku makefilereguły. Reguła wygląda następująco:

cel: zależności
        polecenie

Reguła określa w jakis sposób należy budować cel. Znaczenie poszczególnych składników.

cel
nazwa pliku, który ma powstać z tej reguly,
zależności
lista plików od których zależy cel, tzn. zmiana któregokolwiek z tych plików oznacza, że cel też się zmieni,
polecenie
jest to polecenie za pomocą, którego ma zostać wytworzony cel.

Uwaga! Istotne jest, aby polecenie było wcięte za pomocą jednego znaku tabulacji, a nie przypadkiem spacji. Jest to dosyć kłopotliwe, ale cóż, każde narzędzie miewa swoje widzimisie.

Dla przykładu reguła

komunikat.o: komunikat.c komunikat.h
       gcc -Wall -c komunikat.c -o komunikat.o

mówi w jaki sposób tworzyć plik komunikat.o. Zależy on od dwóch plików źródłowych komunikat.c i komunikat.h.

W jaki sposób znajdować pliki zależna? Można posłużyć się opcją -MM polecenia gcc.

gcc -MM plik.c

Powyższe polecenie wyrzucie linię

plik.o: plik.c inne pliki źródłowe

pokazującą jakie są jeszcze inne pliki źródłowe, od których zależy dany plik. Forma wyjścia nie jest przypadkowa. Jest taka, aby było można ją łatwo wstawić do pliku makefile.

Z pomocą gcc -MM lub bez tworzymy nasz pierwszy makefile, który wygląda tak:

program: komunikat.o main.o test.o
        gcc -lm komunikat.o main.o test.o -o program

komunikat.o: komunikat.c komunikat.h
        gcc -Wall -c komunikat.c -o komunikat.o

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

test.o: test.c test.h komunikat.h
        gcc -Wall -c test.c -o test.o

Teraz w wyniku wykonania polecenia make mamy taki efekt:

$ make
gcc -Wall -c komunikat.c -o komunikat.o
gcc -Wall -c main.c -o main.o
gcc -Wall -c test.c -o test.o
gcc -lm komunikat.o main.o test.o -o program

Zostały utworzone odpowiednie pliki *.o oraz program wykonywalny program.

W jaki sposób zadziałał make? Otóż jeśli nie podamy mu żadnego argumentu, to próbuje on utworzyć cel występujący w pierwszej regule pliku makefile. W tym celu tworzy wpierw wszystkie pliki od których on zależy, jeśli takowe jeszcze nie istnieją. Do tworzenia plików *.o używa dalszych reguł. Tworzenie celu kończy się porażką jeżeli zajdzie jeden z przypadków:

  • plik, od którego zależy cel, nie zostanie znaleziony,
  • nie będzie można znaleźć reguły, do utworzenia danego pliku,
  • polecenie podane w regule skończy się porażką.

Ponadto make potrafi stwierdzać, czy jest potrzeba ponownego wykonania poleceń kompilacji. Wykonajmy go jeszcze raz:

$ make -f simple.mak 
make: `program' jest aktualne.

co oznacza, że plik wykonywalny program jest aktualny i nie trzeba nic uruchamiać.

Teraz zmodyfikujmy jakiś plik. Zasymulujemy to poleceniem touch:

$ touch komunikat.h

Uruchamiamy make jeszcze raz:

$ make -f simple.mak 
gcc -Wall -c komunikat.c -o komunikat.o
gcc -Wall -c test.c -o test.o
gcc -lm komunikat.o main.o test.o -o program

Tym razem ponownie zostały utworzone te pliki wynikowe, które zależały od komunikat.h, a mianowicie są to komunikat.o i test.o. Oczywiście zostało też ponowione linkowanie.

Reguły jak polecenia

Zmienne

Reguły schematy