GNU make - Überblick |
Die Komplexität verschiedener Programmierprojekte hat schon lange Dimensionen
erreicht, in denen das Programm, aus reiner Notwendigkeit heraus, nicht mehr ein einziger
großer Klumpen ist, sondern sich aus Hunderten von Modulen zusammensetzt.
Ein durchdacht strukturiertes Projekt ermöglicht den Austausch der einzelnen Teile
- bei Wahrung der Schnittstellen -, ohne dass der Funktionalität des Gesamten
Einschränkungen widerfährt.
So eine in sich abgeschlossene Einheit eines Programms kann selbst wieder aus mehreren
Tausend Quelldateien bestehen. Stellt sich die Frage: Wenn Programmierer A die
Dateien X, Y, Z im Modul B verändert... welche Module sind neu zu
übersetzen?
Nehmen Sie sich den Netscape her, dessen Grundgerüst zusammen mit allen
peripheren Programme an die 100 MByte Quelldateien umfasst... Wird nun eine Schnittstelle
modifiziert, welche der Programme oder Bibliotheken sind neu zu generieren?
Das gesamte Paket neu durch den Compiler zu jagen, kann selbst auf schneller Hardware
Stunden verschlingen. Und bevor alle Fehler aus einem Programm beseitigt sind, werden
einige tausend Compiler-Durchläufe notwendig (und selbst dann sind solche
großen Pakete wie der Netscape nicht fehlerfrei).
Hier kommt make ins Spiel. Make benötigt für seine Arbeit eine Steuerdatei, üblicherweise makefile genannt. In einem solchen Makefile sind nun die Abhängigkeiten der einzelnen Programmteile von den betreffenden Quelldateien beschrieben. Make erkennt anhand der Modifikationszeiten der einzelnen Dateien, in welchen Teilen sich die Quellen geändert haben. Genau für diese Teile (man bezeichnet diese als Targets - Ziele) wird »make« den Compilervorgang anstoßen.
Das Erzeugen eines solchen Makefiles erfordert eine gewisse Disziplin; seine Komplexität wächst mit dem Fortschritt des Projektes. Ein sorgsam gepflegtes Gerüst aus Makefiles kann den Aufwand beim Übersetzen von Projekten enorm reduzieren.
GNU make - Nicht nur für Programmierer |
Make kann überall dort eingesetzt werden, wo bestimmte Aktionen notwendig sind, sobald eine konkrete Datei verändert wurde:
GNU make - Aufruf und Optionen |
Im einfachsten Fall wird »make« ohne Argumente gestartet:
user@sonne> make |
Make sucht jetzt im aktuellen Verzeichnis nach einer Datei mit dem Namen »GNUmakefile«, »makefile« oder »Makefile«. Make sucht in beschriebener Reihenfolge und verwendet die erste gefundene Steuerdatei (und nur diese!). Findet make kein Makefile, so ist die Antwort:
user@sonne> make make: *** No targets. Stop. |
In einem Makefile sind die Abhängigkeiten zu den einzelnen Zielen (Targets) beschrieben. Per Voreinstellung beginnt »make« mit der Abarbeitung des ersten Zieles aus der Steuerdatei und endet, sobald alle Aktionen zu diesem Ziel erledigt sind.
Soll nun ein anderes als das erste Ziel betrachtet werden, ist dies »make« mitzuteilen:
user@sonne> make anderes_ziel |
Make können mehrere Ziele als Argumente mitgegeben werden; Existieren die Ziele in der Steuerdatei, wird »make« sie in dieser Reihenfolge abarbeiten. Im anderen Fall hagelt es eine Fehlermeldung:
user@sonne> make gibts_nicht make: *** No rule to make target `gibts_nicht'. Stop. |
Weitere wichtige Optionen sind:
user@sonne> make -C /../anderer_Pfad/ |
Make wechselt zunächst das Arbeitsverzeichnis und arbeitet dann wie gehabt.
user@sonne> make -d |
Make gibt erweiterte Debug-Informationen zum Vorgang aus.
user@sonne> make -f Steuerdatei |
Make bezieht seine Instruktionen aus der mit Steuerdatei benannten Datei.
user@sonne> make -j 2 |
Make versucht die Aktionen zu parallelisieren (im Beispiel 2 Prozesse - sinnvoll bei SMP-Maschinen).
user@sonne> make -t |
Make setzt die Modifikationszeit der Ziele (so dass sie nicht neu erzeugt werden, selbst wenn ihre Quelldateien modifiziert wurden).
GNU make - Aktionen erzwingen |
In manchen Situationen ist es notwendig, die Zielaktionen durchzuführen, selbst wenn sich in den abhängigen Dateien nichts geändert hat. Z.B. werden Sie Ihr Projekt im Endstadium mit optimierenden Compileroptionen übersetzen wollen. Die alleinige Änderung der Compiler-Flags juckt »make« so ziemlich gar nicht. Ebenso werden Sie, um ganz sicher zu gehen, dass Ihnen keine Fehler unterlaufen sind, vor der Auslieferung von Software eine vollständige Neuübersetzung aller Quellen erzwingen wollen.
Sie haben nun (mind.) zwei Möglichkeiten, eine erneute Abarbeitung aller Aktionen zu erzwingen:
user@sonne> touch *.h *.c |
Jetzt kennen Sie die wahre Berufung des Kommandos »touch«.
Einführung in Makefiles |
Makefiles bestehen aus Regeln (»rules«). Jede Regel besitzt folgendes Format:
Ziel: Abhängigkeit(en) Kommando Kommando ... |
Ziel | Ziel ("target") ist normalerweise der Name des Programm(moduls), das durch die nachfolgenden Kommandos generiert wird. Ziel kann aber auch der Name einer Aktion sein. |
Abhängigkeiten | Hier stehen meist die Namen der Dateien und Ziele, von denen dieses Ziel abhängt. |
Kommando | Hier stehen die Aktionen, die im Falle einer erfüllten Abhängigkeit auszuführen sind. Jede Aktion muss auf einer eigenen Zeile stehen und durch einen Tabulator eingerückt sein (keine Leerzeichen). Sehr lange Zeilen können umgebrochen werden, indem am Ende jeder Zeile - bis auf die letzte - ein Backslash »\« angefügt wird (auch hier gilt: keine weiteren Zeichen nach dem Backslash!). |
Lernen am Beispiel - Die Ausgangssituation |
Die wichtigen Bestandteile von Makefiles möchte ich schrittweise anhand eines Beispieles erläutern. Konkret soll es um ein LaTeX-Dokument gehen, das in mehrere Dateien aufgeteilt ist.
Das Makefile soll am Ende folgende Ziele enthalten:
Die Quelldateien nennen sich »ch01.tex«, »ch02.tex«,..., »ch09.tex« und »book.tex«. Weiterhin existieren die Bilddateien »picture1.eps« und »picture2.eps« (Encapsulated Postscript). Die fertige Postscript-Datei soll »book.ps« heißen; das Archiv nennt sich »book.tar.gz«.
Makefile - Erster Versuch |
Dvi-Datei: Diese Datei ist nur von den »tex«-Quelldateien abhängig. Um sie zu erzeugen, ist das Kommando »latex« mit dem Namen des Hauptdokuments »book.tex« als Argument aufzurufen. »latex« generiert aus »book.tex« die Datei »book.dvi«. Also lautet der gesamte Eintrag:
book.dvi: ch01.tex
ch02.tex ch03.tex ch04.tex \ ch05.tex ch06.tex ch07.tex ch08.tex \ ch09.tex book.tex latex book.tex |
Da die Liste der Abhängigkeiten sehr lang ist, wurde diese auf mehrere Zeilen verteilt. Man beachte, dass unmittelbar nach dem Backslash der Zeilenumbruch stehen muss. Ist nur eine der genannten Dateien neuer als das Ziel »book.dvi«, wird die nachfolgende Kommandozeile ausgeführt.
Postscript-Datei: Die Datei muss erzeugt werden, falls entweder eines der Bilder modifiziert wurde oder eine neuere »dvi«-Datei vorliegt:
book.ps: picture1.eps picture2.eps book.dvi dvips -D 600 book.dvi |
Da »book.dvi« ein Ziel desselben Makefiles ist, werden automatisch auch dessen Abhängigkeiten überprüft und die Datei eventuell neu erstellt. Die Option »-D 600« bewirkt eine Skalierung der Postscript-Fonts auf 600 dpi (Voreinstellung ist 300 dpi).
Archiv: Das Archiv (Format »tar«, gepackt) des Quelltextes muss erstellt werden, sobald sich eine dieser Dateien geändert hat. Zusätzlich soll das Makefile im Archiv enthalten sein.
booksrc.tar.gz: ch01.tex ch02.tex ch03.tex ch04.tex \ ch05.tex ch06.tex ch07.tex ch08.tex \ ch09.tex book.texpicture1.eps \ picture2.eps makefile tar czf booksrc.tar.gz ch01.tex ch02.tex ch03.tex \ ch04.tex ch05.tex ch06.tex ch07.tex ch08.tex ch09.tex\ book.tex picture1.eps picture2.eps makefile |
Konvertierung: Beispielhaft soll die Konvertierung der Postscript-Datei in das Pdf-Format vorgenommen werden. Die Pdf-Datei ist nur von der entsprechenden Postscript-Datei abhängig:
book.pdf: book.ps ps2pdf book.ps book.pdf |
Löschen: Die Kommandos »latex« und »dvips« legen eine Reihe von Hilfsdateien an (z.B. Index-Verzeichnisse...). Diese sollen bei Aufruf von »make clean« entfernt werden. Weiterhin zu Löschen sind alle Zieldateien und das Archiv:
clean: rm -f book.[^t][^e][^x] book.ps *~ booksrc.tar.gz |
Das Ziel »clean« ist von nichts abhängig, deswegen steht es allein auf einer Zeile. Wenn man weiß, dass die von »latex« und »dvips« erzeugten Dateien alle eine dreistellige Erweiterung aufweisen, so sollte erkennbar sein, dass man mit »book.[^t][^e][^x]« genau jene erfasst, nicht aber »book.ps«, da diese nur eine zweistellige Namenserweiterung besitzt. Mit »*~« löscht man Backup-Dateien, so wie sie z.B. der Editor Vi anlegt. Die Option »-f« unterdrückt Fehlerausgaben, falls dem Kommando »rm« einmal keine Dateien übergeben wurden (z.B. bei zweimaligem Aufruf von »make clean«).
Die Reihenfolge der Ziele |
Wird das Kommando »make« mit einem Ziel als Argument aufgerufen, so sucht es zunächst nach einem Makefile und anschließend in diesem nach einem Ziel mit diesem Namen. Einzig die Zeilen dieses Zieles werden dann abgearbeitet.
Wird das Kommando »make« ohne Argumente aufgerufen (gemeint sind hier Ziele - keine Optionen), so wird - falls ein Makefile gefunden wurde - das erste Ziel aus diesem Makefile abgearbeitet..
Es liegt also nahe, das »gebräuchlichste« Ziel als erstes zu nennen. Ebenso hat es sich eingebürgert, ein Ziel wie »clean« an das Ende einer Steuerdatei zu verbannen. Damit erhalten wir folgendes Makefile:
book.ps: picture1.eps picture2.eps book.dvi dvips -D 600 book.dvi book.dvi: ch01.tex ch02.tex ch03.tex ch04.tex \ ch05.tex ch06.tex ch07.tex ch08.tex \ ch09.tex book.tex latex book.tex booksrc.tar.gz: ch01.tex ch02.tex ch03.tex ch04.tex \ ch05.tex ch06.tex ch07.tex ch08.tex \ ch09.tex book.tex picture1.eps picture2.eps makefile tar czf booksrc.tar.gz ch01.tex ch02.tex ch03.tex \ ch04.tex ch05.tex ch06.tex ch07.tex ch08.tex ch09.tex \ book.tex picture1.eps picture2.eps makefile book.pdf: book.ps ps2pdf book.ps book.pdf clean: rm -f book.[^t][^e][^x] book.ps *~ booksrc.tar.gz |
Wollen wir nun die Postscript-Datei neu erstellen, rufen wir »make book.ps« oder - da dieses Ziel das erste im Makefile ist - »make« auf. Ein Archiv erzeugen wir mit »make booksrc.tar.gz« und zum Aufräumen hilft »make clean«.
Makefile - Variablen |
Unser Makefile besitzt einige Schwächen:
In unserem Makefile werden mehrfach die Dateien »ch01.tex«,..., »ch09.tex« usw. genannt. Anstatt die Liste an jeder Stelle zu nennen, sollte diese besser einmal zu Beginn des Makefiles definiert werden. Neben der gewonnenen Übersichtlichkeit ist eine solche Liste auch einfacher zu modifizieren - nur an einer einzigen Stelle in der Steuerdatei sind Änderungen erforderlich.
TEXFILES = ch01.tex ch02.tex ch03.tex ch04.tex \ ch05.tex ch06.tex ch07.tex ch08.tex \ ch09.tex book.tex PICTURES = picture1.eps picture2.eps |
Auf eine solche Variable kann anschließend mittels »$(VARIABLENNAME)« zugegriffen werden.
Kommandos werden bei verschiedenen Systemen häufig in unterschiedlichen Verzeichnissen abgelegt. Es ist sicherlich kein Fehler, anstelle des Kommandos selbst innerhalb des Makefiles dieses über eine Variable aufzurufen. So muss bei Verwendung des Makefiles auf einem anderen System nur die Zeile der Variablendefinition geändert werden. Eine ähnliche Überlegung legt die Verwendung von Variablen für Optionen nahe, da auch diese oft differieren. Kommandoname und -optionen können auch in einer Variable vereinbart werden:
LATEX = /usr/bin/latex DVIPS = /usr/bin/dvips DVIPSOPTS = -D 600 RM = rm -f TAR = tar -z |
Um ein Archiv zu erzeugen, ist es sicherlich lästig, immer den Namen »booksrc.tar.gz« angeben zu müssen... Einfach den Namen des Zieles zu ändern, würde die Erzeugung des Archivs immer bewirken, selbst wenn sich an den Dateien nichts geändert haben sollte (da keine Datei unter dem Namen dieses Zieles existiert).
Die Einführung eines neuen Ziels »archiv«, das von »booksrc.tar.gz« abhängt und keine Kommandos besitzt, bringt das gewünschte Ergebnis:
archiv: booksrc.tar.gz booksrc.tar.gz: $(TEXFILES) $(PICTURES) makefile tar czf booksrc.tar.gz $(TEXFILES) $(PICTURES) makefile |
»make archiv« ruft jetzt implizit »make booksrc.tar.gz« auf.
Unser neues Makefile besitzt nun folgende Gestalt:
TEXFILES = ch01.tex ch02.tex ch03.tex ch04.tex \ ch05.texch06.tex ch07.tex ch08.tex \ ch09.tex book.tex PICTURES = picture1.eps picture2.eps LATEX = /usr/bin/latex DVIPS = /usr/bin/dvips DVIPSOPTS = -D 600 RM = /bin/rm -f TAR = /bin/tar z PS2PDF = /usr/bin/ps2pdf book.ps: $(PICTURES) book.dvi $(DVIPS) $(DVIPSOPTS) book.dvi book.dvi: $(TEXFILES) $(LATEX) book.tex booksrc.tar.gz: $(TEXFILES) $(PICTURES) makefile $(TAR) cf booksrc.tar.gz $(TEXFILES) $(PICTURES) makefile archiv: booksrc.tar.gz book.pdf: book.ps $(PS2PDF) book.ps book.pdf clean: $(RM) book.[^t][^e][^x] book.ps *~ booksrc.tar.gz |
Makefile - Weitere Möglichkeiten |
Angenommen im aktuellen Verzeichnis existiert eine Datei »clean«... Dann würde ein Aufruf von »make clean« nichts bewirken, da dieses Ziel von nichts abhängig ist und »make« es damit für "up to date" hält.
Um die Abarbeitung eines Zieles immer zu erzwingen, muss diese von einem "Phony"-Target abhängen:
.PHONY: clean |
Ist ein Ziel von Dateinamen eines bestimmten Musters abhängig, lassen sich die Abhängigkeiten auch mit Hilfe von Metazeichen ausdrücken:
... booksrc.tar.gz: *.tex *.eps [Mm]akefile ... |
Allerdings funktioniert eine solche Substitution nicht in Variablendefinitionen. Hier muss auf die Funktion »wildcard« zurückgegriffen werden:
... TEXFILE = $(wildcard *.tex) ... |
»make« schreibt die Kommandozeile des Makefiles, die aktuell bearbeitet wird, immer auf die Standardausgabe. Um bestimmte Ausgaben zu vermeiden, muss einem solchen Kommando ein »@« vorangestellt werden:
... clean: @$(RM) book.[^t][^e][^x] book.ps *~ booksrc.tar.gz |
Makefile - Die fertige Datei |
Die beschriebenen Möglichkeiten zum Umgang mit Makefiles behandeln nur einen Bruchteil des tatsächlichen Funktionsumfangs. Dennoch sollten diese Mechanismen den Systemverwalter befähigen, Einsatzpunkte von »make« für die Verwaltungsaufgaben zu erkennen und entsprechende Steuerdateien zu verfassen.
Mit unserem bisherigen Kenntnissen sollte ein Makefile, das die obigen Anforderungen erfüllt, in etwa so aussehen:
TEXFILES = $(wildcard *.tex) PICTURES = $(wildcard *.eps) LATEX = /usr/bin/latex DVIPS = /usr/bin/dvips DVIPSOPTS = -D 600 RM = /bin/rm -f TAR = /bin/tar z PS2PDF = /usr/bin/ps2pdf ARCHIV = booksrc.tar.gz ARCHIVFILES = $(TEXFILES) $(PICTURES) ARCHIVFILES += makefile book.ps: $(PICTURES) book.dvi @$(DVIPS) $(DVIPSOPTS) book.dvi book.dvi: $(TEXFILES) @$(LATEX) book.tex $(ARCHIV): $(ARCHIVFILES) @$(TAR) cf $(ARCHIV) $(ARCHIVFILES) archiv: $(ARCHIV) book.pdf: book.ps @$(PS2PDF) book.ps book.pdf .PHONY: clean clean: @$(RM) book.[^t][^e][^x] book.ps *~ booksrc.tar.gz |
Anmerkungen: Im Beispiel wurden zwei weitere Varianten der Variablendefinition eingebaut. Zum einen die Verwendung einer bereits definierten Variable bei einer weiteren Definition (»ARCHIVFILES = $(TEXFILES) $(PICTURES)«); zum anderen das Hinzufügen von Elementen zum Inhalt einer Variable (»ARCHIVFILES += makefile«).