Druckversion | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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. 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? 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.
Make kann überall dort eingesetzt werden, wo bestimmte Aktionen notwendig sind, sobald eine konkrete Datei verändert wurde:
Im einfachsten Fall wird »make« ohne Argumente gestartet:
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:
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:
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:
Weitere wichtige Optionen sind:
Make wechselt zunächst das Arbeitsverzeichnis und arbeitet dann wie gehabt.
Make gibt erweiterte Debug-Informationen zum Vorgang aus.
Make bezieht seine Instruktionen aus der mit Steuerdatei benannten Datei.
Make versucht die Aktionen zu parallelisieren (im Beispiel 2 Prozesse - sinnvoll bei SMP-Maschinen).
Make setzt die Modifikationszeit der Ziele (so dass sie nicht neu erzeugt werden, selbst wenn ihre Quelldateien modifiziert wurden).
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:
Jetzt kennen Sie die wahre Berufung des Kommandos »touch«.
Makefiles bestehen aus Regeln (»rules«). Jede Regel besitzt folgendes Format:
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«.
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:
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:
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.
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:
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:
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«).
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:
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«.
Unser Makefile besitzt einige Schwächen:
Dateilisten in Variablen speichernIn 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.
Auf eine solche Variable kann anschließend mittels »$(VARIABLENNAME)« zugegriffen werden. Kommandos und Kommandooptionen in Variablen speichernKommandos 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:
Prägnante Namen für das ZielUm 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:
»make archiv« ruft jetzt implizit »make booksrc.tar.gz« auf. Das neue MakefileUnser neues Makefile besitzt nun folgende Gestalt:
Phony - Ausführung immer erzwingenAngenommen 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:
DateinamensubstitutionIst ein Ziel von Dateinamen eines bestimmten Musters abhängig, lassen sich die Abhängigkeiten auch mit Hilfe von Metazeichen ausdrücken:
Allerdings funktioniert eine solche Substitution nicht in Variablendefinitionen. Hier muss auf die Funktion »wildcard« zurückgegriffen werden:
Ausgaben von make unterdrücken»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:
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:
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«). |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Korrekturen, Hinweise? |