Druckversion

Unix Werkzeuge - Make und Makefiles

GNU make - Überblick Weiter

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 Zurück Anfang Weiter

Make kann überall dort eingesetzt werden, wo bestimmte Aktionen notwendig sind, sobald eine konkrete Datei verändert wurde:

  • Bei allen Programmierprojekten
  • Bei Backups: »Wurde(n) diese Datei(en) modifiziert, erzeuge die Backup-Datei neu.«
  • Bei der Systemverwaltung: "Wurde diese Konfigurationsdatei verändert, dann generiere eine neue Datenbasis." (dieses Prinzip wird u.a. für die NIS-Datenbasis angewandt.)
  • Bei LaTeX-Dokumenten: "Erzeuge die Postscript-Datei neu, falls eine der Quelldateien geändert wurde."
  • ...?
GNU make - Aufruf und Optionen Zurück Anfang Weiter

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 Zurück Anfang Weiter

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:

  1. Sie könnten alle Zieldateien löschen (dann wird »make« jedes neu erzeugen)
  2. Sie können die Modifikationszeiten aller Quelldateien ändern, z.B.:

    user@sonne> touch *.h *.c

Jetzt kennen Sie die wahre Berufung des Kommandos »touch«.

Einführung in Makefiles Zurück Anfang Weiter

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 Zurück Anfang Weiter

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 Dvi-Datei (Device independend) soll erzeugt werden, falls eine der Quelldateien modifiziert wurde
  • Die Postscript-Datei soll erzeugt werden, wenn sich die DVI-Datei geändert hat
  • Es soll ein Archiv mit allen Quelltexten erstellt werden
  • Die Postscript-Datei soll in verschiedene Formate konvertiert werden können
  • Ein Ziel soll das Löschen aller temporären Dateien ermöglichen

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 Zurück Anfang Weiter

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 Zurück Anfang Weiter

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 Zurück Anfang Weiter

Unser Makefile besitzt einige Schwächen:

  • Es wirkt ziemlich verwirrend...
  • Wenn die Pfade zu den enthaltenen Kommandos nicht in der »PATH«-Variable des Nutzers stehen, wird »make« das Kommando nicht finden...
  • Ein Archiv zu erstellen, wäre einleuchtender mit z.B. »make archiv« zu realisieren...

Dateilisten in Variablen speichern

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 und Kommandooptionen in Variablen speichern

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

Prägnante Namen für das Ziel

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.

Das neue Makefile

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 Zurück Anfang Weiter

Phony - Ausführung immer erzwingen

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

Dateinamensubstitution

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)
...

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:

...
clean:
         @$(RM) book.[^t][^e][^x] book.ps *~ booksrc.tar.gz

Makefile - Die fertige Datei Zurück Anfang

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«).

 Korrekturen, Hinweise?
Startseite Nächste Seite Nächstes Kapitel Vorherige Seite Kapitelanfang