Wenn man in C Programme schreibt, die sich auch mal über mehrere Quell-Dateien erstrecken,
ist es mitunter ziemlich viel Aufwand, alle diese Objektdateien zu übersetzen und zu linken.
Da sind oft einige Befehlszeilen notwendig, und möglicherweise vergisst man auch die ein oder andere
Datei zu übersetzen/linken. Ein sehr nützliches Tool ist hier "Make".
Mit Make kann man Abhängigkeiten der Quelldateien definieren und flexibel verschiedene Compileraufrufe
zusammenfassen.
Das Programm make bezieht seine gesamten Informationen über die Abhängigkeiten und
Ziele der Quelldateien aus einer einizigen Datei, dem Makefile. Dieses Makefile ist eine normale
Textdatei. Wenn make nicht explizit mittels dem Parameter -f aufgerufen wird, sucht es im
aktuellen Verzeichnis nach Makefiles mit dem Namen GNUmakefile, makefile
oder Makefile.(In dieser Reihenfolge).
Bevor wir uns nun aber in Erklärungen stürzen nehmen wir ein Makefile als Beispiel:
#Konstanten
#Compiler
cc=gcc
opt= -Wall -O3
#Gtk
gtk=`gtk-config --cflags --libs`
gtk_o=`gtk-config --cflags`
#Dependencies
all: main xmain
main: strturn.o bytefunc.o
$(cc) main.c strturn.o bytefunc.o -o main -lm $(opt)
xmain: strturn.o callbacks.o bytefunc.o editable_handlers.o
$(cc) xmain.c strturn.o callbacks.o bytefunc.o editable_handlers.o
-o xmain -lm $(opt) $(gtk)
strturn.o:
$(cc) -c strturn.c -o strturn.o $(opt)
bytefunc.o:
$(cc) -c bytefunc.c -o bytefunc.o $(opt)
callbacks.o:
$(cc) -c callbacks.c -o callbacks.o $(opt) $(gtk_o)
editable_handlers.o:
$(cc) -c editable_handlers.c -o editable_handlers.o $(opt) $(gtk_o)
tar:
tar -cvzf umrechner.tar.gz Makefile README *.c *.h
clean:
-rm editable_handlers.o
-rm bytefunc.o
-rm strturn.o
-rm callbacks.o
-rm main
-rm xmain
Zeilenumbrüche sind nur zu besseren Lesbarkeit gemacht worden, in Wirklichkeit
steht natürlich alles in einer Zeile.
Dieses Makefile stammt übrigens aus dem Binär/Dezimal-wandler aus dem Kapitel "Zahlensystemumrechnung",
und kann als umrechner.tar.gz heruntergeladen werden. (Bitte die README beachten)
Ein Makefile ist folgendermaßen aufgebaut: Man bestimmt ein bestimmtes Ziel (Target)
und Abhängigkeiten (Dependencies) für dieses Ziel, also "Dinge" die erledigt werden müssen um dieses Ziel
zu erfüllen.
Diese "Dinge" können Kommandos/Programmaufrufe oder Abhängigkeiten von anderen Zielen sein.
Ein Ziel wird folgendermaßen definiert:
Ziel: Abhängigkeit1 Abhängigkeit2...
Kommando1
Kommando2
.
.
.
ACHTUNG: vor den Kommandos MUSS immer ein Tabulatorzeichen (Tabstop)stehen!
Ein makefile wird ausgeführt, indem man mit make Ziel das gewünschte Ziel
abarbeiten läßt, oder mittels make bzw. make all alles
abarbeiten lässt. (siehe Besonderheiten).
Ein Beispiel
xmain: strturn.o callbacks.o bytefunc.o editable_handlers.o
$(cc) xmain.c strturn.o callbacks.o bytefunc.o editable_handlers.o
-o xmain -lm $(opt) $(gtk)
Das Ziel xmain beschreibt hier die Compilierung des ausführbaren Programms xmain.
Diese kann erst erfolgen wenn die Abhängigkeit von strturn.o, callbacks.o, bytefunc.o und editable_handlers.o
erfüllt ist, also diese Ziele abgearbeitet sind (sie sorgen übrigens dafür, das die Objectdateien des Projekts erzeugt werden.)
Nach der Erfüllung dieser Abhängigkeiten fährt make mit
der Ausführung des Kommandos
$(cc) xmain.c strturn.o callbacks.o bytefunc.o editable_handlers.o -o xmain -lm $(opt) $(gtk) fort.
(mehr zu diesem Kommando später)
Ein Ziel muss übrigens nicht immer eine Abhängigkeit besitzen, wie das folgende Beispiel zeigt:
tar:
tar -cvzf umrechner.tar.gz Makefile README *.c *.h
das Ziel tar erzeugt an dieser Stelle nur ein Archiv, das den Quellcode des Projekt beeinhaltet.
Eine besondere Bedeutung hat bei make das Ziel all. Wenn make ohne Zielangaben oder Parameter aufgerufen wird, wird all abgearbeitet, in dem normalerweise alle Abhängigkeiten definiert werden um das
komplette Projekt zu übersetzen.
Fehlt das Ziel all, wird das erste definierte Ziel abgearbeitet.
Eine weitere Besonderheit ist, das man make zwingen kann bedingt fortzufahren, wenn ein bestimmtes Kommando nicht
ausgeführt werden konnte. Dies kann zwar auch per Befehlsparameter erzwingen, dann werden aber alle Fehler
ignoriert, was i.d.R nicht zum gewünschten Ergebnis führt.
Sehen wir uns wieder ein Beispiel an:
clean:
rm editable_handlers.o
rm bytefunc.o
Hier soll erreicht werden, das die Dateien editable_handlers.o und bytefunc.o gelöscht
werden. Wäre jetzt aber z.B. bytefunc.o schon manuell gelöscht worden, würde make an
dieser Stelle abbrechen, da das Kommando "rm bytefunc.o" meldet, das die Datei nicht
existiert.
Wenn man nun aber ein Minuszeichen '-' vor das auszuführende Kommando hängt, wird der
Fehler ignoriert und make fährt mit dem nächsten Kommando fort, wie im folgenden
Beispiel auch gewünscht (sprich: man möchte die Dateien löschen, wenn möglich):
clean:
-rm editable_handlers.o
-rm bytefunc.o
-rm strturn.o
-rm callbacks.o
-rm main
-rm xmain
zu beachten ist, das auch hier der Tabstop nicht vergessen wird!
Innerhalb eines Makefiles können auch Konstanten definiert werden, z.B. um bestimmte
Compilerparameter zentral festzulegen, so das man diese nur an einer Stelle ändern
muss, und nicht bei jedem Kommando.
Konstanten definiert man durch einfache Zuweisung:
gtk=`gtk-config --cflags --libs`
opt= -Wall -O3
Hier werden z.B. die Konstanten gtk und opt definiert (mit dem Inhalt der hinter dem '=' steht).
Diese Konstanten kann man dann einfach in einem Kommando benutzen, wenn man ein '$'
voranstellt, gefolgt vom Konstantennamen in Klammern:
$(cc) xmain.c strturn.o callbacks.o bytefunc.o editable_handlers.o -o xmain -lm $(opt) $(gtk)
|