In der Open-Source-Bewegung darf eines ganz sicher nicht fehlen: "make". Mittels dieser vier Buchstaben wird das Kompilieren von einfachen oder auch sehr komplexen Programmen so kinderleicht, dass kaum ein Programm auf dieses Tool verzichtet.
Damit "make" auch weiĂ, was es zu tun hat, muss das sogenannte "Makefile" vorhanden sein. Um solche Makefiles besser verstehen zu können, wollen wir eines fĂŒr ein imaginĂ€res C-Programm schreiben. Es spielt aber wirklich keine Rolle, ob "make" fĂŒr C, C++, Fortran oder fĂŒr irgendetwas anderes als eine Programmiersprache eingesetzt wird. "make" lĂ€sst sich ĂŒberall dort nĂŒtzlich, wo es gilt, stupide Wiederholungsarbeit zu leisten.
Mit einem "make"-Aufruf wird entweder nach der Datei "makefile" oder "Makefile" in dem Verzeichnis gesucht, in dem der Anwender sich gerade befindet. Andere Namen fĂŒr das Makefile sind ebenfalls machbar. "make" muss dann aber mit "make -f Makefile.new" darauf aufmerksam gemacht werden.
Grundlegendes
ZunĂ€chst sollten wir uns klar machen, was beim Programmieren eines Programms ablĂ€uft: Unser imaginĂ€res C-Programm bestehe aus fĂŒnf C-Dateien und, sagen wir, einer Header-Datei. Zur Erzeugung des ausfĂŒhrbaren Programms sind mehrere Schritte notwendig: ZunĂ€chst mĂŒssen die C-Dateien unter Einbindung des Header-Files kompiliert werden. Zwar taucht unsere Header-Datei beim Kompilieren nirgends auf, doch wird diese in den C-Dateien mit...
#include "inlag.h"
... eingebunden. Nach diesem Schritt stehen fĂŒnf Object-Dateien zur VerfĂŒgung (Endung ".o"). Diese sind nun nur noch zu binden (linken) und schon haben wir das fertige Programm. Von Hand mĂŒssten wir mindestens folgendes eingeben:
gcc -c imag1.c
gcc -c imag2.c
gcc -c imag3.c
gcc -c imag4.c
gcc -c imag5.c
gcc imag1.o imag2.o imag3.o imag4.o imag5.o -o imag
Nach diesen sechs Befehlen hĂ€tten wir unser Endprodukt "imag". Aber all das wollen wir einfach durch Eintippen von "make" erreichen. Und am liebsten wĂ€re uns, wenn auch wirklich nur die Dateien kompiliert werden wĂŒrden, die von uns wirklich verĂ€ndert wurden bzw. die von einer verĂ€nderten Datei abhĂ€ngen. Also ans Eingemachte:
Schaffe, schaffe, HĂ€usle baue.
Das Makefile ist im Wesentlichen aus Makros, Schaltern, Regeln und Blöcken aufgebaut. Die Blöcke teilen "make" mit, was zu tun ist. Ohne einen solchen im Makefile passiert nichts. Sofern nicht anders aufgerufen, interessiert sich "make" nur fĂŒr den ersten Block im Makefile.
Der Aufbau eines Blockes gestaltet sich nicht allzu kompiliziert: In der Zuordnungszeile stehen zu-iĂ€chst das Target (Ziel, also zum Beispiel der Name der zu erzeugenden Datei), gefolgt von einem Doppelpunkt und den Dateien, von denen das Target abhĂ€ngig ist. Nach der Zuordnungszeile können eine oder mehrere Anweisungszeilen folgen. Diese beginnen mit einem TAB gefolgt von dem eigentlich auszufĂŒhrenden Befehl. Diese Befehle sind in der Regel Shell-Kommandos. Das Ganze sĂ€he dann ungefĂ€hr so aus:
imag1.o: imag1.c imag.h <TAB> gcc -c imag1.c
Wird eine Anweisungszeile enorm lang, lÀsst sich diese mit einem Backslash "" umbrechen. Das "" muss in diesem Fall das letzte Zeichen in der Anweisungszeile sein, sonst kommt es zu Fehlern.
Die Dateien in der ersten Zeile werden benötigt, damit ĂŒberprĂŒft werden kann, ob dieser Schritt wirklich notwendig ist. Sind die Dateien "imag1.c" und "imag.h" Ă€lter als die evtl. schon vorhandene Datei imag1.o, so fĂŒhrt ,make" die Anweisungszeile nicht aus, Das zu erzeugende "imag1.o" muss dann nĂ€mlich noch auf dem aktuellen Stand sein. Dies wird man auf jedem Rechner zu schĂ€tzen wissen, da sich so das Kompilieren deutlich verkĂŒrzen lĂ€sst, wenn nur schnell mal was an einer Datei geĂ€ndert wurde und diese Ănderung nun getestet werden soll. Im Grunde könnten wir den obigen Block fĂŒnf mal leicht modifiziert hinschreiben und noch einen Block...
imag: imag1.o imag2.o imag3.o imag4.o imag5.o
<TAB> gcc imag1.o imag2.o imag3.o imag4.o imag5.o -o imag
...davor hinzufĂŒgen, und wir wĂ€ren fertig. Das ist aber -echt viel Schreibarbeit und beim HinzufĂŒgen einer neuen C-Datei zu unserem Projekt wĂ€ren recht viele Ănderungen am Makefile nötig. Also werden wir uns das Leben gleich noch einfacher machen. Unser Makefile sieht im Moment in etwa so aus:
# Schlechtes Beispiel fĂŒr ein Makefile
# Kommentare werden mit"#" eingefĂŒgt
imag: imag1.o imag2.o imag3.o imag4.o imag5.o
<TAB> gcc imag1.o imag2.o imag3.o imag4.o imag5.o -o imag
imag1.o: imag1.c imag.h
<TAB> gcc -c imag1.c
imag2.o: imag1.c imag.h
<TAB> gcc -c imag2.c
imag3.o: imag1.c imag.h
<TAB> gcc -c imag3.c
imag4.o: imag1.c imag.h
<TAB> gcc -c ima94.c
imag5.o: imag1.c imag.h
<TAB> gcc -c imag5.c
Dabei wird in etwa so vorgegangen: Oberster Block ist imag, beim Aufruf von "make" wird also zunĂ€chst dieser Block abgearbeitet. Da imag von imag1.o, ..., imag5.o abhĂ€ngt, springt "make" nacheinander diese Blöcke an und ĂŒberprĂŒft, ob diese Targets "uptodate" sind. Ist dem nicht so, wird dies durch den Befehl in der Anweisungszeile nachgeholt. Sind schlieĂlich imag1.o ... imag5.o auf dem neuesten Stand, so wird die Anweisungszeile des ersten Blockes ausgefĂŒhrt. Aber dies passiert auch nur, wenn die Object-Dateien nicht alle Ă€lter sind als eine evtl. schon vorhandene Datei "imag". Anstatt "imag.h" bei jedem Block in der Zuordnungszeile aufzufĂŒhren, lieĂe sich auch ein neuer Block definieren:
imag1.o imag2.o imag3.o imag4.o imag5.o: imag.h
Bei diesem Block darf allerdings keine Anweisungszeile mehr folgen, da dann nicht erichtlich wÀre, welchen Block es bei der Erzeugung einer Object-Datei aufzurufen gilt.
Von Makros und Regeln. Ein Makro lÀsst sich sehr leicht definieren:
OBJLIST = imag1.o imag2.o imag3.o imag4.o imag5.o
Somit enthÀlt OBJLIST unsere bisher benötigten Object-Files. Soll im Makefile wieder auf ein Makro zugegriffen werden, geschieht dies durch $(OBJLIST). Bei der Definition lassen sich auch bereits definierte Makros wieder verwenden (siehe nÀchstes Makefile-Beispiel). In unserem Beispiel könnten wir also den ersten Block durch...
imag: $(OBJLIST)
<TAB> gcc $(OBJLIST) -o imag
... ersetzen. Beim Aufruf von "make" besteht auch die Möglichkeit in dem Makefile definierte Makros zu ĂŒberschreiben. Das obige OBJLIST lieĂe sich mit...
make OBJLIST="imag1.o imag2.o imag3.o imag4.o imag5.o imag6.o"
... um eine Object-Datei erweitern. Eine sinnvolle Anwendung liegt in der Kompilierung fĂŒr verschiedene Prozessoren. Wird zur Auswahl des Zielprozessors ein Makro CPU benutzt, so lĂ€sst sich der Prozessor bequem mit "make CPU=030" auswĂ€hlen.
Doch mit dem Makro OBJLIST hĂ€tten wir nur eine kleine Vereinfachung geschaffen. Das soll uns aber noch nicht genĂŒgen. Der Rest sind Blöcke, die sich nur minimal duch die Namen der zu kompilierenden Dateien unterscheiden. Eine Regel kann uns da viel Schreibarbeit abnehmen. Der Aufbau ist einem Block Ă€hnlich: ZunĂ€chst steht eine Art Zuordnungszeile, an der sich erkennen lĂ€sst, weiche Endung in welche ĂŒberfĂŒhrt werden soll. Danach steht eine Anweisungszeile in der beschrieben ist, wie sich das bewerkstelligen lĂ€sst. Die Regel sieht bei uns also wie folgt aus:
.c.o:
<TAB> gcc -c $<
$< ist die Kurzschreibweise von $(<), einem Pseudomakro, das in "make" bereits implementiert ist. Es enthÀlt den Namen der aktuellen Ausgangsdatei. Soll beispielsweise imag1.o erzeugt werden, beinhaltet $< imag1.c, bei imag2.o beinhaltet $< imag2.c und sofort. Es gibt noch andere solcher Pseudomakros, zum Beispiel:
- $@ enthÀlt den Namen der aktuellen Zieldatei also beispielsweise "imag1.o".
- $* enthÀlt nur den aktuellen Stammnamen, zum Beispiel "imag1".
Unser neues und wesentlich besseres Makefile mit zusÀtzlichen Compilerflags sieht nun so aus:
# Besser zu handhabendes Makefile
# Makros
OBJLIST = imag1.o imag2.o imag3.o imag4.o imag5.o
CC = gcc
CPU = 020
CFLAGS = -02 -M68$(CPU)
#Regel .c.o: <TAB> $(CC) $(CFLAGS) -c $<
#Blöcke imag: $(OBJLIST) <TAB> $(CC) $(OBJLIST) -o imag
$(OBJLIST): imag.h
Soll nun das Projekt um eine weitere C-Datei erweitert werden, ist lediglich eine Ănderung des Makros OBJLIST nötig.
TatsĂ€chlich hĂ€tten wir die Regel gar nicht benötigt, da in "make" bereits viele solcher Regeln zum Beispiel fĂŒr verschiedene Programmiersprachen vorhanden sind. Mit unserer eigenen Regel haben wir die bereits vorhandene ĂŒberschrieben. HĂ€tten wir keine Regel definiert, so hĂ€tte "make" bei der internen Regel auf den in $(CC) stehenden Compiler und zusĂ€tzlich auf die in $(CFLAGS) definierten Compilerflags zurĂŒckgegriffen. Um die interne Regel zu benutzen, mĂŒssen diese Makros jedoch nicht definiert sein.
Mach mich an!
Gewisse Eigenarten von "make" lassen sich per Schalter auch im Makefile steuern. Um die oben erwÀhnten internen Regeln abzuschalten und nur seine eigenen zuzulassen, wird der SUFFIXES-Schalter benutzt:
.SUFFIXES:
.SUFFIXES: c.o
Somit existiert bei diesem Aufruf von "make" nur noch unsere eigene Regel. Eigene Regeln sind dann sinnvoll, wenn die internen Regeln nicht den eigenen Anforderungen entsprechen. In unserem Beispiel könnte also getrost die interne Regel fĂŒr C verwendet werden. Weitere Schalter sind:
- ".SILENT:": jede ausgefĂŒhrte Anweisung wird von "make" auf dem Bildschirm ausgegeben, was schnell viele Bildschirme fĂŒllt. Um das zu unterdrĂŒcken, kann ".SILENT:" verwendet werden. Alternativ lĂ€sst sich "make -s" aufrufen, was den gleichen Effekt zeigt.
- ".IGNORE:": Sobald eine von "make" aufgerufene Anweisung mit einem Fehler abbricht (Returncode ungleich o), wird die Abarbeitung des Makefiles gestoppt. Mit ".IGNORE: " wird der Returncode ignoriert und die Abarbeitung unabhÀngig davon fortgesetzt.
Auch Makros ermöglichen die weitere Anpassung von "make" an die eigenen BedĂŒrfnisse. Die oben erwĂ€hnten CC und CFLAGS gehören dazu. Da wir jedoch unsere Regel selbst definiert haben, mĂŒssten wir diese nicht verwenden. Ein weiteres Makro ist das SHELL-Makro. Damit wird festgelegt, an welche Shell die Anweisungszeilen ĂŒbergeben werden sollen. Dies ist insbesondere fĂŒr diejenigen interessant, fĂŒr die Shell-Programmiereung kein Fremdwort ist, da so auch im Makefile mit seiner Lieblingsshell gearbeitet werden kann. Auf in einer Shell definierten Makros lĂ€sst sich mit einem doppelten DMzugreifen. Ein (date) beispielsweise liefert im Makefile das aktuelle Datum, sofern die benutzte Shell dieses Makro anbietet.
Zum Schluss...
noch ein paar kleinere ErgÀnzungen: HÀufig finden sich in Makefiles Manipulationen von Dateinamen. In unserem Beispiel bedeutet dies, dass wir nicht die Object-Dateien auflisten, sondern die Source-Dateien:
SRCLIST = imag1.c imag2.c imag3.c imag4.c imag5.c
Das Makro OBJLIST erzeugen wir jetzt aus dem Makro SRCLIST, in dem wir die Endung c durch o ersetzen:
OBJLIST = $(SRCLIST:.c=.o)
Dies kann zum Beispiel nĂŒtzlich sein, wenn ein Backup der aktuellen Version erzeugt werden soll. In diesem Fall werden die Namen der Quelltexte und nicht die der Object-Files benötigt. Unser endgĂŒltiges Makefile mit vielen der oben aufgezeigten Möglichkeiten sieht dann etwa so aus:
# EndgĂŒltige Version des Makefiles
#Makros
SRCLIST = imag1.c imag2.c imag3.c ima94.c imag5.c
OBJLIST = $(SRCLIST:.c=.o)
INCLIST = imag.h
PROGNAME = imag
CC = gcc
CPU = 020
CFLAGS = -O2 -m68$(CPU)
SHELL /bin/bash
#Schalter
.SUFFIXES =
.SUFFIXES = c.o
.SILENT
#Regel
.c.o:
<TAB> $(CC) $(CFLAGS) -c $<
#Blöcke
$(PROGNAME): $(OBJLIST)
<TAB> $(CC) $(OBJLIST) -o $@
<TAB> strip $@
$(OBJLIST): $(INCLIST)
clean:
<TAB> rm -f $(PROGNAME) *.o
backup:
<TAB> mkdir backup/(date +%y%m%d)
<TAB> cp $(SRCLIST) $(INCLIST) backup/(date+%y%m%d)
help:
<TAB> echo "Parameter dieses Makefiles:"
<TAB> echo "Hier können die durch die Blöcke
definierten Möglichkeiten stehen"
Um nun andere Blöcke als den ersten aufzurufen, genĂŒgt ein "make <target>", also zum Beispiel "make help". Das hier gezeigte Beispiel kratzt nur an den Möglichkeiten des mit "make" Machbaren. Beispielsweise haben wir ganz auĂer Acht gelassen, dass sich "make" auch rekursiv aufrufen lĂ€sst. In Verbindung mit dem Ăberschreiben von Makros, kann so zum Beispiel die SRCLIST automatisch generiert werden. Insbesondere druch gute ShellProgrammierung lassen sich diesem universellen Tool viele Anwendungen entlocken. Nicht vergessen werden sollte, dass viele unterschiedliche Versionen von "make" existieren, die in ihren FĂ€higkeiten zum Teil stark variieren. Ist gar kein "make" auf dem Rechner vorhanden, muss es evtl. mit "gmake" versucht werden. Die hier abgehandelten Möglichkeiten sollten jedoch mit allen "make"-Versionen und -Variationen möglich sein. Unter [1] lĂ€sst es sich als rpm-Package fĂŒr Sparemint herunterladen. Sozobon C fĂŒr TOS findet sich unter [2] und beinhaltet ebenfalls ein "make", was mit jeder Shell (beispielsweise Mupfel) benutzt werden kann. Mit [3] schlieĂlich steht eine ausfĂŒhrliche Anleitung zur VerfĂŒgung.
Nicht nur fĂŒr Programmierer ist "make" es Wert, einmal nĂ€her betrachtet zu werden. ZugegebenermaĂen scheint das Schreiben eines Makefiles zunĂ€chst etwas kryptisch und umstĂ€ndlich. Doch wenn diese HĂŒrde erstmal genommen ist, möchte man "make" nie mehr missen. STC
[1] http://sparemint.atariforge.net/sparemint/html/packages/make.html
[2] http://www.nic.funet.fi/index/atari/programming/sozobonx/
[3] http://wm.gnu.org/manual/make-3.77/html-chapter/make-toc.html