Dieser Artikel ist im Franzis-Verlag in der Zeitschrift "mc", Ausgabe 04/1989 erschienen.
Heutige Entwicklungsumgebungen sind meist grafisch und viel komfortabler.
In den meisten Fällen basieren sie jedoch immer noch auf MAKE und den
hier erläuterten Mechanismen. Es kann sich daher aus diesen und anderen
Gründen auch heute noch lohnen, sich mit diesem Thema zu beschäftigen.
Der damals als Beispiel gewählte Compiler Microsoft C 5.1 ist heute
natürlich veraltet, aber - wie gesagt - an den Prinzipien hat sich nichts
geändert. Lediglich die Syntax könnte eine andere sein.
Das Problem...
Arbeitet man an einem größeren Programmprojekt in C, so wird es
oft vorkommen, daß eine Datei aus Gründen der Übersichtlichkeit in
mehrere kleinere Dateien aufgeteilt werden muß. Oder von
vorneherein wird jede C-Funktion in einer eigenen Datei definiert.
C ermöglicht es dabei, alle Dateien einzeln zu übersetzen, so daß
sich sogar eine Zeitersparnis ergibt.
Bei zunehmender Zahl der Dateien wird es nun immer schwerer, hier
den Überblick zu behalten. Fragen wie "Welche Dateien habe ich
jetzt geändert" oder "Welche Dateien müssen neu übersetzt werden"
sind immer schwieriger zu beantworten. Schließlich wird der Wunsch
aufkommen, alle Dateien automatisch durch den Rechner verwalten zu
lassen. Genau das leistet MAKE. MAKE ist ein Utility-Programm, das
zu den meisten C-Compilern mitgeliefert wird.
...und die Lösung
Wie funktioniert MAKE? Nun, in dem gesamten System der
auftretenden Dateien existieren ja gewisse Abhängigkeiten. So
übersetzt der Compiler z.B. eine Quelldatei test.c in eine
Objektdatei test.obj. Dabei ist meist eine Include-Datei test.h zu
berücksichtigen. Die Objektdatei ist somit abhängig von test.c und
test.h. Das bedeutet: Wird eine der beiden Dateien test.c oder
test.h verändert, muß der Compiler eine neue Datei test.obj
bilden.
Genauso hängt die ausführbare Datei test.exe von test.obj und
eventuell von einer weiteren Objektdatei test2.obj ab. Wird eine
der beiden durch den Compiler neu gebildet, muß der Linker wieder
in Aktion treten und test.exe erneuern.
Bleibt nur noch die Frage, wie MAKE erkennen kann, ob Dateien
geändert wurden oder nicht. Das aber ist ganz einfach. Das
Programm braucht sich ja nur den Directory-Eintrag der
betreffenden Datei anzusehen. Dort steht ganz genau, wann die
Datei erstellt wurde. Ist beispielsweise die Objektdatei älter als
die Quelldatei, wurde diese seit der letzten Übersetzung geändert.
Sie muß also neu übersetzt werden.
Die Abhängigkeiten muß der Anwender selbst angeben. Zu diesem
Zweck muß er eine eigene Datei schreiben, die sog. MAKE-Datei.
Hierin ist anzugeben, welche Abhängigkeiten zwischen den
vorkommenden Dateien bestehen und wie bestimmte Dateien zu
aktualisieren bzw. überhaupt zu erstellen sind.
Die Syntax, die MAKE für den Inhalt einer derartigen Datei
erwartet, ist leider nicht einheitlich. Das Grundmuster ist aber
immer dasselbe. In diesem Artikel wird als Beispiel die MAKE-
Utility für den Microsoft-C-Compiler in der Version 5.1 für MS-DOS
betrachtet.
Ein Beispiel
Nun wieder zurück zu dem Beispiel von eben. An Quelldateien seien
test.c und test2.c vorhanden. Beide benutzen eine Include-Datei
test.h. Somit ist die Objektdatei test.obj abhängig von test.c und
test.h. Die Objektdatei test2.obj hängt von test2.c und test.h ab.
Weitere Quelldateien sind nicht vorhanden, so daß die ausführbare
Datei test.exe von test.obj und test2.obj abhängt. Insgesamt
handelt es sich also um drei Dateien, die je nach Gegebenheit neu
erstellt werden müssen. Dies sieht folgendermaßen aus:
test.obj: test.c test.h
cl -c -W2 -AL -Ox test.c
test2.obj: test2.c test.h
cl -c -W2 -AL -Ox test2.c
test.exe: test.obj test2.obj
link test+test2,test.exe,,llibc /ST:16000 /SE:512
|
Die gesamte Datei ist in drei Abschnitte eingeteilt. Bei jedem
dieser Abschnitte handelt es sich um eine "MAKE-Beschreibung".
Eine solche ist nach bestimmten Regeln aufgebaut. In der ersten
Zeile muß jeweils die Abhängigkeit der Dateien angegeben werden.
Da steht immer am Anfang eine Datei, deren Aktualität getestet
werden soll, die sog. "Zieldatei". Hinter einem Doppelpunkt folgen
dann die Dateien, von denen die Zieldatei abhängt. Findet MAKE die
erste Datei nicht, oder ist sie älter als eine der anderen
Dateien, muß sie aktualisiert werden. Wie das geschieht, ist in
der nächsten Zeile angegeben.
Genau genommen handelt es sich also jeweils um "wenn-dann"-
Beziehungen. Wenn die Zieldatei veraltet oder nicht vorhanden ist,
dann werden die folgenden Anweisungen ausgeführt. Syntaktisch ist
zu beachten, daß diese Zeileneinteilung genau eingehalten werden
muß. Es dürfen keine zusätzlichen Leerzeilen eingefügt werden, da
diese die verschiedenen MAKE-Beschreibungen trennt. Die Liste der
Dateien, von denen die Zieldatei abhängt, darf nicht einfach in
der nächsten Zeile fortgesetzt werden, hierzu muß, falls
erforderlich, der Backslash \ benutzt werden.
Für Anweisungen sind beliebig viele Zeilen erlaubt, sie müssen
jeweils um mindestens ein Leerzeichen eingerückt werden.
Zusätzlich zu diesen Beschränkungen sind lediglich Kommentare
erlaubt. Sie folgen auf das Zeichen # und dürfen ansonsten eine
beliebige Zeichenfolge enthalten. Kommentare sind am Ende jeder
Zeile sowie zwischen der Beschreibung der Dateiabhängigkeit und
den Anweisungszeilen erlaubt. An dieser Stelle muß # unbedingt in
der ersten Spalte der Zeile stehen.
Die Anweisungen zum Übersetzen und Linken benutzen hier
verschiedene Optionen, die natürlich je nach Anwendung geändert
werden müssen. Bleibt nur noch die Frage, wie die MAKE-Datei nun
benutzt wird. Sie muß zunächst abgespeichert werden. Hierzu wird
oft der Name des Programms ohne Extension gewählt, hier also
"test". Zur Ausführung kommt die Datei durch das Kommando "make
test".
"test" war nun ein ganz kleines Beispiel. Bei größeren Projekten
können durchaus 20 oder mehr Quelldateien zusammenkommen. Die
Anweisung zum Übersetzen wird aber immer dieselbe sein und müßte
trotzdem 20-mal hingeschrieben werden. So wäre es sinnvoll, diese
einmal als Regel zu notieren, so daß sie bei jeder Übersetzung
ausgeführt wird. Die MAKE-Utility des Microsoft-C-Compilers
gestattet die einfache Formulierung derartiger Regeln.
Benutzung von Regeln
Für solche Regeln werden oft Variablen benutzt, das Handbuch
spricht hier von Makros. Die Definition eines Makros geschieht wie
eine Wertzuweisung in der Sprache C. Ein Beispiel wäre T=test. Ein
abschließendes Semikolon wie in C entfällt jedoch. Der Wert des
Makros wird mit Hilfe des Dollar-Symbols erhalten, also etwa $(T).
Zusätzlich gibt es spezielle Makros, z.B. $* oder $@. $* gibt den
Namen der Zieldatei an, dies ohne Extension. Der gesamte Name mit
Extension ist im Makro $@ enthalten. Und mit $** erhält man die
gesamte Liste der Dateien, von denen die aktuelle Zieldatei
abhängt.
.c.obj:
cl -c -W2 -AL -Ox $*.c
.obj.exe:
link $**,$*.exe,,llibc /ST:16000 /SE:512
test.obj: test.c test.h
test2.obj: test2.c test.h
test.exe: test.obj test2.obj
|
Dies ist nun die mit Regeln versehene MAKE-Datei. Als
erstes ist dort die Regel zum Übersetzen einer C-Quelldatei zu
finden. Eine solche hat die Extension .C, während die aus der
Übersetzung resultierende Datei die Extension .OBJ besitzt. Das
wird in der ersten Zeile festgelegt. Dann folgt die schon bekannte
Anweisung zum Übersetzen. Im Unterschied zu Listing 1 ist der
konkrete Dateiname durch ein Makro ersetzt worden, die Extension
wurde an dieses angehängt.
Wie wirkt sich diese Änderung aus? Nun, interessant wird es, wenn
das MAKE-Programm an eine Zeile kommt, in der eine Zieldatei mit
Extension .OBJ angegeben wird, die von einer Datei gleichen Namens
mit Extension .C abhängt. Dann wird wie bisher geprüft, ob die
Objekt-Datei gebildet werden muß. Als nächstes stellt sich die
Frage, ob direkt unter der Abhängigkeit Anweisungen stehen. Wenn
ja, werden diese ausgeführt. Wenn nein, wird eine passende Regel
gesucht. Eine solche sollte dann natürlich vorhanden sein, denn
sie wird nun ausgeführt. An die Stelle des Makros tritt jeweils
der Name der aktuellen Zieldatei.
Die zweite Regel, die zur Bildung einer ausführbaren Datei gedacht
ist, benutzt nun auch das Makro $**. Dies enthält bei Ausführung
alle Dateien, von denen test.exe abhängt. Das sind die Objekt-
Dateien test.obj und test2.obj. Diese müssen dazugelinkt werden,
daher müssen sie in der Link-Anweisung auftreten. Das Makro $**
erleichtert die Arbeit sehr, denn die Objekt-Dateien müssen nur
einmal, nämlich bei der Definition der Abhängigkeiten angegeben
werden.
Makros
Ein nächster Schritt wäre, die MAKE-Datei durch Benutzung von
Makros komfortabler zu machen. Ein Vorschlag:
Prg = test
Mod = L
# ***** Regeln *****
.c.obj:
cl -c -W2 -A$(Mod) -Ox $*.c
.obj.exe:
link $**,$*.exe,,$(Mod)libc /ST:16000 /SE:512
# ***** Abhängigkeiten *****
$(Prg).obj: $(Prg).c $(Prg).h
test2.obj: test2.c $(Prg).h
$(Prg).exe: $(Prg).obj test2.obj
|
Zunächst wird das Makro Prg auf den aktuellen Namen
"test" gesetzt. Dieses Makro wird dann bei den Abhängigkeiten am
Ende der Datei benutzt. Zwar muß auch dort normalerweise etwas
geändert werden, eine Abhängigkeit der ersten Form ist jedoch
meist vorhanden, auch bei der letzten Abhängigkeit kann das Makro
benutzt werden.
Nun wird ein Makro Mod mit einem Wert belegt. Mod nimmt ein
Zeichen auf, das für das zu benutzende Speichermodell steht. Mod=L
wählt hier also das Large Model aus. Diese Information wird an
zwei Stellen der MAKE-Datei benutzt, nämlich beim Übersetzen und
beim Linken.
Beim Übersetzen geschieht die Wahl des Speichermodells durch die
Option -A. Dahinter muß ein Buchstabe folgen, der das gewünschte
Speichermodell festlegt. Es wird das zuvor definierte Makro Mod
benutzt, so daß die Option die Gestalt -A$(Mod) erhält. Für $(Mod)
wird der Buchstabe L eingesetzt, so daß sich dieselbe Option wie
bisher ergibt.
Beim Linken ist darauf zu achten, daß in Abhängigkeit des
gewählten Speichermodells die richtigen Libraries hinzugelinkt
werden. Die Standard-Library hat jeweils einen Namen LIBC.LIB, dem
der Kennbuchstabe des Speichermodells vorangestellt wird. Für das
Large Model also LLIBC.LIB. Auch das kann sehr leicht durch $(Mod)
berücksichtigt werden. Dies funktioniert übrigens nicht beim Huge
Model, da dieses dieselben Libraries wie das Large Model
verwendet.
Ein großer Vorteil von Makros ist die Tatsache, daß deren Werte
auch von außen, d.h ohne Änderung der MAKE-Datei geändert werden
können. So werden Environment-Variablen wie PATH oder LIB
automatisch als Makros betrachtet. Andererseits können die Werte
von Makros auch auf der Kommandozeile gesetzt werden. So könnte
das Programm test durch den Aufruf "make test Mod=S" auf einfache
Weise als Small-Model-Programm übersetzt werden (falls das möglich
ist).
Weitere Makros sind denkbar, in denen ebenfalls Parameter gesetzt
werden, die in den folgenden Regeln zum Übersetzen und Linken
angewandt werden. Beispielsweise könnte der zu verwendende
Warning-Level einem Makro zugewiesen werden. Dieser wird dann
einheitlich bei allen Übersetzungen, sei es durch Hochsprachen-
Compiler, sei es durch Assembler, verwendet. So etwas kann in
derselben Weise realisiert werden wie die Auswahl des
Speichermodells. Wer längere Zeit in C Programme entwickelt, wird
sich meist eine eigene MAKE-Datei schreiben, in der für ein neues
Projekt nur einige Makros neu gesetzt werden müssen. Dies wird in
ähnlicher Weise geschehen wie gezeigt.
Weitere Verwendung und Fazit
Am Ende bleibt zu erwähnen, daß die Anweisungen einer MAKE-Datei
keinesfalls nur Compiler- oder Linkeraufrufe sein müssen. Nein, es
sind hier alle DOS-Kommandos sowie Aufrufe von ausführbaren
Programmen und Batch-Dateien erlaubt. Es können also z.B. auch
Dateien kopiert oder gelöscht werden. Und MAKE kann
selbstverständlich auch für Compiler anderer Programmiersprachen
verwendet werden.
MAKE ist also ein für die Programmierung wertvolles Hilfsmittel,
da es die Entwicklungszeit verkürzen kann. Denn nur die jeweils
nötigen Übersetzungs- und Link-Anweisungen werden ausgeführt.