1 Theorie |
Das Kommandozeilen-Programm "make" bzw. "nmake" (Windows) wird
in größeren Projekten eingesetzt, um nur die Module neu zu
compilieren, deren Quelltexte geändert wurden.
Das Programm verarbeitet eine Datei "makefile" bzw. "Makefile", in
der die Abhängigkeiten zwischen den einzelnen Dateien beschrieben
sind.
Für jedes Paar von Quelldatei(en) und Zieldatei wird anhand des
Zeitpunktes der letzten Dateiänderung entschieden, ob eine
Neuübersetzung bzw. Neuerstellung der Zieldatei erforderlich
ist.
Das Programm make wird hier nur oberflächlich behandelt. Es gibt verschiedene Implementierungen von make, jede Implementierung hat ihre Besonderheiten.
Das Verzeichnis "hsmet" enthält zwei makefiles als Beispiele:
Das makefile ist eine zeilenweise aufgebaute Textdatei. Diese Datei enthält Angaben, welche Datei von welcher anderen Datei bzw. welchen anderen Dateien abhängt.
Mit
prefix=/usr/local bindir=$(prefix)/bin
können Makros definiert werden. Auf diese Makros kann dann mit
"$(name)" Bezug genommen werden.
Im Beispiel werden zwei Makros "prefix" und "bindir" definiert. Für
die Definition von "bindir" wird der Wert von "prefix"
verwendet.
Werden Makros verwendet, die nicht im makefile definiert wurden, wird der Inhalt einer gleichnamigen Umgebungsvariablen eingesetzt, falls die Umgebungsvariable existiert.
Bei der Auflösung von Makros wird folgende Suchreihenfolge verwendet:
make CC=cc ...
Wird make mit der Option "-e" aufgerufen, wird die Reihenfolge von Umgebungsvariablen und Definitionen im makefile vertauscht.
Folgende Makros werden häufig in makefiles verwendet:
-DName=WertKonstanten definiert bzw. mit
-IVerzeichnisEinstellungen für den Include-Suchpfad vorgenommen.
-LVerzeichnisbzw. unter Windows mit
/LIBPATH:"Verzeichnis"Der Suchpfad für Bibliotheksdateien eingestellt
Regeln bestehen aus einer oder mehreren Abhängigkeitszeilen und optional einer oder mehreren Kommandozeilen.
Eine Abhängigkeitszeile gibt an, von welcher Datei bwz. welchen Dateien eine Zieldatei abhängt.
Kommandozeilen beginnen mit einem führenden Tabulator. Sie enthalten die auszuführenden Kommandos, um aus den Quelldateien die Zieldatei zu erzeugen.
Nachfolgend ein makefile-Abschnitt, um das Programm ex018 unter Linux/Unix zu
erstellen.
Im Beispiel wird ein Makro OBJEX018 definiert, dieses enthält die
Dateinamen der Objektmodule. Ohne das Makro müssten diese
Dateinamen alle einmal in der Abhängigkeitszeile und einmal im
Kommando angegeben werden. Das Makro erhöht hier die
Übersichtlichkeit.
In der Abhängigkeitszeile (die Zeile mit dem Doppelpunkt) wird
festgelegt, dass die Datei ex018 (bzw. unter Windows die Datei
ex018.exe) von den Objektmodulen abhängig ist. Die Objektmodule
müssen also zuerst erstellt/aktualisiert werden, bevor die
Zieldatei erstellt werden kann. Um aus den Objektmodulen die
Zieldatei zu erstellen, wird der Linker $(LD) (bzw. unter Windows
LINK) aufgerufen. Neben einer ganzen Reihe anderer Optionen wird
mit "-o ex018" (bzw. unter Windows /out:ex018.exe) der Name der zu
erzeugenden Datei angegeben. Weiterhin werden die Namen der zu
verlinkenden Objektmodule angegeben.
Nach dem Linken werden unter Linux/Unix die Berechtigungen gesetzt,
unter Windows wird das Manifest in die *.exe-Datei eingebunden.
OBJEX018= ex018.o he-read.o he-geom.o he-strch.o ex018: $(OBJEX018) $(LD) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o ex018 $(OBJEX018) -lm chmod 755 ex018
Beispiel für nmake unter Windows (Visual Studio 2008 mit Platform SDK Version 7):
OBJEX018= ex018.obj he-read.obj he-geom.obj he-strch.obj ex018.exe: $(OBJEX018) LINK /nologo /subsystem:console /incremental:no /release /MANIFEST /LIBPATH:"C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\x64" /LIBPATH:"C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib" /LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\Lib\AMD64" /LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\Lib" kernel32.lib gdi32.lib advapi32.lib user32.lib /out:ex018.exe $(OBJEX018) mt.exe -manifest $@.manifest -outputresource:$@;1
Im Windows-Beispiel ist zu sehen, dass Verzeichnisse verwendet
werden, die u.a. die Versionsnummer des verwendeten Visual Studio
und des optional zusätzlich installierten Platform SDK enthalten.
Auch sind die Optionen hier recht lang.
Dies legt den Einsatz von Makros nahe.
Weiter unten im Abschnitt Bedingte makefile-Verarbeitung
ausgewählter make-Varianten → nmake.exe wird gezeigt, wie mit
einem Konfigurations-Abschnitt und einem Übersetzungs-Abschnitt
diese Angaben vereinfacht erfolgen können und leicht anpassbar
gemacht werden.
Mit
make -p
bzw.
nmake -p
wird das komplette benutzte Regelwerk (also die vordefinierten
Makros und Regeln und die aus dem Makefile) angezeigt.
Ein Aufruf in einem Verzeichnis ohne die Dateien "makefile" bzw.
"Makefile" zeigt dann nur die vordefinierten Regeln und Makros
an.
Um nicht alle Dateien einzeln angeben zu müssen, können auch Regeln für Dateiendungen angegeben werden.
Mit der Zeile
.SUFFIXES: .o .c
bzw. für nmake unter Windows
.SUFFIXES: .exe .obj .c .res .rc
werden die Dateiendungen für *.c- bzw. Objekt-Dateien in die
Liste der zu berücksichtigenden Dateiendungen aufgenommen.
Optional kann in der Zeile vorher mit einer leeren SUFFIXES-Liste
die Liste geleert werden:
.SUFFIXES: .SUFFIXES: .c .o
Bzw. für nmake unter Windows:
.SUFFIXES: .SUFFIXES: .exe .obj .c .res .rc
Mit der Regel
.c.o: $(CC) $(CPPFLAGS) $(CFLAGS) -c $<
bzw. für nmake unter Windows
.c.obj: CL $(CPPFLAGS) $(CFLAGS) /c $*.c
wird festgelegt, wie *.o- bzw. *.obj-Dateien aus *.c-Dateien erzeugt werden.
Mit
.c: $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $* $*.c chmod 755 $*
bzw. unter Windows mit Visual Studio 2008 und Platform SDK 7.0
.c.exe: CL /nologo /W3 /DNDEBUG=1 /D_CONSOLE=1 /I. /I"C:\Program Files\Microsoft SDKs\Windows\v7.0\Include" /I"C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\Include" /c $*.c LINK /nologo /subsystem:console /incremental:no /release /MANIFEST /LIBPATH:"C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib\x64" /LIBPATH:"C:\Program Files\Microsoft SDKs\Windows\v7.0\Lib" /LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\Lib\AMD64" /LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\Lib" kernel32.lib gdi32.lib advapi32.lib user32.lib /out:$*.exe $*.obj mt.exe -manifest $*.exe.manifest -outputresource:$*.exe;1
wird festgelegt, wie aus einer einzelnen *.c-Datei die entsprechende ausführbare Datei erzeugt wird.
Mit
make -n Ziel
werden die Kommandos, um das angegebene Ziel neu zu erstellen, nur angezeigt, aber nicht ausgeführt.
Standardmäßig bricht make die Bearbeitung ab, wenn bei der Ausführung eines Kommandos ein Fehler auftritt.
Soll ein Fehler bei der Ausführung eines Kommandos nicht zum
Abbruch von make führen, muss dem Kommando ein Minuszeichen
vorangestellt werden.
Beispiel:
install: all -mkdir $(bindir)
Falls im o.g. Beispiel das bindir-Verzeichnis bereits existiert, erfolgt hier kein Abbruch, wenn das Verzeichnis nicht neu angelegt werden kann. Die von mkdir ausgegebenen Fehlermeldungen können jedoch den Nutzer verunsichern, daher wäre auf POSIX-Systemen
install: all -[ -d $(bindir) ] || (mkdir -p $(bindir) ; chmod 755 $(bindir) )
besser. Hier wird zunächst getestet, ob das bindir-Verzeichnis existiert. Falls es noch nicht existiert, wird es angelegt und mit sinnvollen Berechtigungen versehen.
Folgende Ziele werden für makefiles empfohlen:
Sollen Pakete für die Installation über Paketmanagement erstellt werden, erfordert manche Paketierungssoftware weitere Ziele im makefile.
Mitunter soll - analog zum Präprozessor des C-Compilers - unter bestimmten Bedingungen nur ein bestimmter Teil des makefile benutzt werden.
Die verschiedenen make-Implementierungen bieten hierfür eine unterschiedliche Syntax.
ifdef VARIABLE makefile-Abschnitt wird nur benutzt, wenn VARIABLE definiert ist. else makefile-Abschnitt wird nur benutzt, wenn VARIABLE nicht definiert ist. endif
ifndef VARIABLE makefile-Abschnitt wird nur benutzt, wenn VARIABLE nicht definiert ist. else makefile-Abschnitt wird nur benutzt, wenn VARIABLE definiert ist. endif
ifeq (A,B) makefile-Abschnitt wird nur benutzt, wenn Texte A und B gleich sind. else makefile-Abschnitt wird nur benutzt, wenn Texte A und B nicht gleich sind. endif
ifeq "A" "B" makefile-Abschnitt wird nur benutzt, wenn Texte A und B gleich sind. else makefile-Abschnitt wird nur benutzt, wenn Texte A und B nicht gleich sind. endif
ifeq 'A' 'B' makefile-Abschnitt wird nur benutzt, wenn Texte A und B gleich sind. else makefile-Abschnitt wird nur benutzt, wenn Texte A und B nicht gleich sind. endif
ifneq (A,B) makefile-Abschnitt wird nur benutzt, wenn Texte A und B nicht gleich sind. else makefile-Abschnitt wird nur benutzt, wenn Texte A und B gleich sind. endif
ifneq "A" "B" makefile-Abschnitt wird nur benutzt, wenn Texte A und B nicht gleich sind. else makefile-Abschnitt wird nur benutzt, wenn Texte A und B gleich sind. endif
ifneq 'A' 'B' makefile-Abschnitt wird nur benutzt, wenn Texte A und B nicht gleich sind. else makefile-Abschnitt wird nur benutzt, wenn Texte A und B gleich sind. endif
Der else-Zweig ist bei allen o.g. Konstrukten optional.
Die Direktiven können auch verschachtelt werden.
!IF Konstant-Ausdruck makefile-Abschnitt wird nur Benutzt, wenn der konstante Ausdruck ungleich 0 ist. !ELSE makefile-Abschnitt wird nur Benutzt, wenn der konstante Ausdruck nicht einen Wert ungleich 0 ergibt. !ENDIF
!IFDEF Makroname makefile-Abschnitt wird nur benutzt, wenn das angegebene Makro definiert ist. !ELSE makefile-Abschnitt wird nur benutzt, wenn das angegebene Makro nicht definiert ist. !ENDIF
!IFNDEF Makroname makefile-Abschnitt wird nur benutzt, wenn das angegebene Makro nicht definiert ist. !ELSE makefile-Abschnitt wird nur benutzt, wenn das angegebene Makro definiert ist. !ENDIF
Der !ELSE-Zweig ist bei allen o.g. Konstrukten optional.
Um eine Kette mehrerer Bedingungen zu testen, stehen folgende Konstrukte zur Verfügung:
!IF Bedingung1 Falls Bedingung1 erfüllt ist, wird dieser makefile-Abschnitt benutzt. !ELSEIF Bedingung2 Falls Bedingung1 nicht erfüllt ist, aber Bedingung2 erfüllt ist, wird dieser makefile-Abschnitt benutzt. !ELSEIF Bedingung3 Falls Bedingung1 und Bedingung2 nicht erfüllt sind, aber Bedingung3 erfüllt ist, wird dieser makefile-Abschnitt benutzt. !ELSE Falls keine der Bedingungen erfüllt ist, wird dieser makefile-Abschnitt benutzt !ENDIF
In analoger Weise stehen !ELSEIFDEF und !ELSEIFNDEF zur Verfügung.
In der Beispiel-Datei makefile.vc im Verzeichnis hsmet wird dieser Mechanismus folgendermaßen benutzt: