Makefiles schreiben

Linux Makefiles schreiben

Immer dann, wenn man mit Hilfe sich wiederholender Befehle ein Dateiformat in ein anderes überführen will, empfiehlt sich der Einsatz von Make. Make schaut dabei nach, ob die Quelldatei verändert wurde, um so festzustellen, ob die gewünschte Ziel Datei erneut erzeugt werden muss. So kann man beispielsweise erreichen, dass nur die neuen bzw. geänderten Dateien verarbeitet werden, was einen enormen Zeitgewinn bedeuten kann. Außerdem kann Make auch Abhängigkeiten von Dateien untereinander berücksichtigen. Es benötigt dabei ein paar Regeln, die man in eine Datei, das Makefile, schreiben muss. Hier soll anhand einiger Beispiele gezeigt werden, wie man Make sinnvoll einsetzen kann und wie man Makefiles schreibt.

Grundlegendes

Zunächst einmal ist es nicht unwesentlich zu wissen, dass Kommentarzeilen in Makefiles durch ein "#" Symbol eingeleitet werden. Außerdem werden Makefiles in der Regel unter dem Dateinamen "Makefile" abgespeichert, so dass Make die Datei auch finden kann. Make liest die Datei und verarbeitet die dort beschriebenen Regeln. Im Grunde genommen besteht bei Make jede Regel aus zunächst erstmal zwei Teilen. Im ersten Teil wird die Abhängigkeit von Dateien beschrieben und dann kommt ein Teil mit Kommandos, die ausgeführt werden sollen. Die Abhängigkeiten haben die Folgende Form:

ZielDatei: QuellDatei1 QuellDatei2 ...

Dabei kann die Liste der Quelldateien beliebig lang sein, sofern sie nicht von einem Zeilenumbruch unterbrochen wird. Möchte man aus ästhetischen oder anderen Gründen die Liste der Quelldateien auf mehrere Zeilen verteilen, muss man einen Backslash einbauen. Etwa so:

ZielDatei: QuellDatei1\
        QuellDatei2\
        QuellDatei3

Aufgrund dieser Regel weiß Make nun, dass die Datei vor dem Doppelpunkt irgendwie aus denen danach erzeugt werden soll. Wie das genau geschehen soll, muss in einer danach folgenden Kommando-Beschreibung angegeben werden. Dabei schreibt man einfach die Kommandos hin, die man auch in einem Xterm oder auf der Konsole verwenden würde. Wichtig ist jedoch, einen Tabulator an den Anfang der Zeile zu setzen, denn sonst wird das Kommando nicht als solches erkannt und es kommt zu einem Fehler (Der Tabulator fehlt zum Beispiel, wenn du den Text hier einfach kopierst.). Also sieht eine Regel zur "Umwandlung" von Dateien nun so aus:

ZielDatei: QuellDatei1 QuellDatei2
        Kommando1
        Kommando2

Auch die Kommandos können über mit Hilfe des Backslash über mehrere Zeilen verteilt werden. Dies ist alles freilich wenig konkret und deshalb soll hier gleich ein Beispiel folgen:

# Kleines Makefile zum Erzeugen eines
# PDF-Dokumentes aus einer Latex-Datei.

Dokument.pdf: Dokument.tex
         pdflatex Dokument.tex

# ENDE

Wenn man die obige Datei unter dem Namen "Makefile" speichert, dann kann man im gleichen Verzeichnis durch den Aufruf von

make Dokument.pdf

die Ausführung des Kommandos anstoßen. Momentan lässt sich da kein großer Vorteil ersehen, weil man genauso gut das Kommando selbst eingeben könnte. Allerdings führt Make das Kommando nur aus, wenn es nötig ist. Dazu werden die Zeiten der letzten Modifikation der Dateien verglichen. Nur dann, wenn die Quelldatei neuer ist als die Ziel Datei, wird das Kommando gestartet.

Variablen

Nun möchte man sicherlich nicht immer sein gesamtes Makefile durchwühlen, wenn sich einfach nur der Name einer Datei ändert. Für einen solchen Fall bietet es sich an, Variablen zu verwenden. Das könnte etwa so aussehen:

# Kleines Makefile zum Erzeugen eines
# PDF-Dokumentes aus einer Latex-Datei.

DOKUMENT = Dokument

$(DOKUMENT).pdf: $(DOKUMENT).tex
         pdflatex $(DOKUMENT).tex

# ENDE

Hier wird sogar beim Dateinamen auf das Suffix verzichtet, weil davon ausgegangen werden kann, dass die Latex-Datei auf ".tex" und das PDF-Dokument auf ".pdf" endet. Bei der Festlegung der Variablen muss man auf jeden Fall darauf achten, dass rechts und links vom Gleichheitszeichen ein Leerzeichen steht. Sonst erkennt GNU Make die zuweisung nicht. Möchte man die Variable benutzen, muss sie von Klammern umschlossen und ein "$" Symbol vorangestellt werden. Dann wird dieser Ausdruck einfach durch die zuvor definierte Zeichenkette ersetzt.

Standard Ziele

Genau genommen ist es so, dass ein Ziel keine Datei sein muss. Man kann sich beliebige Namen für Ziele ausdenken. Es gibt Ziele, die standardmäßig eingesetzt werden. So wird "all" zum Beispiel verwendet, wenn alle Aktionen des Makefiles auf einmal ausgeführt werden sollen und "clean" wird gern genommen, wenn nach einer Übersetzung wieder alles aufgeräumt werden soll. Fehlt ein Kommando nach einer Abhängigkeits-Beschreibung, wird einfach die Quelldatei auf den neuesten Stand gebracht, sofern dies notwendig ist.

# Kleines Makefile zum Erzeugen eines
# PDF-Dokumentes aus einer Latex-Datei.

DOKUMENT = Dokument

all: $(DOKUMENT).pdf

$(DOKUMENT).pdf: $(DOKUMENT).tex
         pdflatex $(DOKUMENT).tex

# ENDE

Das Ziel "all" wird zudem standardmäßig von Make genommen, wenn kein spezielles Ziel angegeben wird. So reduziert sich jetzt die Übersetzung auf die Eingabe von

make

Prinzipiell kann dabei das Problem auftreten, dass im aktuellen Verzeichnis eine Datei existiert, die "all" heißt. Make würde dann die Modifikations-Zeitpunkte der Datei "all" und "Dokument.pdf" vergleichen und prüfen, ob etwas getan werden muss. Da hier aber immer die Aktualisierung von "Dokument.pdf" angestoßen werden soll, muss Make wissen, dass das Ziel "all" ein Spezialfall ist. Dafür gibt es auch ein spezielles Ziel: ".PHONY" Ebenfalls eine Sonderstellung hat das Ziel ".SILENT", welches nur dazu dient, die Ausgabe der Kommandos bei den entsprechenden Zielen zu unterdrücken.

# Kleines Makefile zum Erzeugen eines
# PDF-Dokumentes aus einer Latex-Datei.

DOKUMENT = Dokument

all: $(DOKUMENT).pdf

$(DOKUMENT).pdf: $(DOKUMENT).tex
        pdflatex $(DOKUMENT).tex

silent:
        echo "Hier sieht man nur diesen Text."

nixsilent:
        echo "Hier wird das Kommando mit ausgegeben."

.PHONY: all nixsilent silent
.SILENT: all silent

# ENDE

Gibt man bei Verwendung des obigen Makefiles nun

make silent

ein, so sieht man nur den Text, während beim Ziel "nixsilent" das Kommando mit dem echo-Befehl auch noch sieht. Hier sieht man übrigens auch, dass Kommandos nach Zielen ohne Abhängigkeiten immer ausgeführt werden.

Suffix Regeln

Ein besonderes "Leckerli" sind die Suffix-regeln, die man in Makefiles einbauen kann. Dieser Mechanismus basiert auf der Annahme, dass Dateien gleichen Typs auch eine gleiche Endung haben, was auch in den meisten Fällen stimmt. Eine Suffix-Regel sieht zum Beispiel so aus:

%.pdf: %.dvi
        Kommando

Dabei wird angegeben, dass alle Dateien, die auf .pdf enden aus Dateien erzeugt werden sollen, die auf .dvi enden. Auf diese Weise weiß Make auch dann, was es tun soll, wenn man eine konkrete Datei als Ziel angibt.

make Dokument.dvi

trifft also auf diese Regel zu. Damit man auch bei den Kommandos auf die Verwendung konkreter Dateinamen verzichten kann, gibt es bestimmte Makros, die dort eingesetzt werden können.

Makro Bedeutung
$* Dateiname ohne Suffix
$< Quelldatei
$@ Zieldatei

Zur praktischen Anwendung sei hier ein kleines Beispiel eingestreut:

# Makefile zum Konvertieren von eingescannten pnm-Bildern
# nach PostScript und per Lyx erzeugten DVI-Dokumenten nach PDF.
# Der Sinn dieses Makefiles besteht darin, dass man seine
# Original-Bilder behalten will und aus Platzgründen die
# *.ps Dateien nur dann erzeugt, wenn man sie wirklich
# braucht. Außerdem soll Lyx verwendet werden, um die Bilder
# ins Dokument einzufügen und Lyx erzeugt leider kein PDF.
#
# Autor: Jens-D. Neppe
# Lizenz: Mach was Du willst damit!

all: images doc

# Dieses Ziel ist nur dazu da, um für jede Datei, die im
# aktuellen Verzeichnis liegt und auf dvi endet Make aufzurufen.
doc:
        for datei in $$(ls *dvi | cut -d \. -f 1) ; do make $$datei.pdf ; done

%.pdf: %.dvi
        dvips -o $*.ps $*.dvi
        ps2pdf $*.ps

images:
        for datei in $$(ls *pnm | cut -d \. -f 1) ; do make $$datei.ps ; done

%.ps: %.pnm
        pnmtops $< > $@

clean:
        rm *.ps
        rm *.pdf

.PHONY: all images clean
.SILENT: images

# ENDE

Wie man sieht, ist es auch möglich, Shell-Scripte in Makefiles einzubetten, wobei man Semikola einsetzen muss, da man keine Zeilenumrüche einbauen kann. Denn mit jedem Zeilenumbruch wird das nächste Kommando in einer neuen Shell gestartet, so dass zuvor definierte Shell-Variabeln keine Gültigkeit mehr haben. Bei der Verwendung der Shell-Variabeln muss man zwei "$"-Symbole voranstellen, um Verwechslungen zu Variablen, die im Makefile selbst definiert werden, zu vermeiden.

Artikelbewertung: 
3
Average: 3 (1 vote)
War dieser Artikel hilfreich?