Ein Vergleich mit dem Make-System
  Simon Lembke (WI4139)
Es macht eigentlich immer Sinn, die class-Dateien getrennt von den Quelldateien aufzubewahren. Damit Make die class-Dateien trotzdem findet, kann man entweder den Pfad an jeder Stelle einfügen, an der man sich auf sie bezieht, oder man erweitert den Pfad, in dem Make normalerweise sucht. Wenn nichts anderes angegeben ist wird nur das aktuelle Verzeichnis durchsucht. Um dies zu ändern hat man wiederum zwei Möglichkeiten: man schreibt die zu durchsuchenden Pfade in die Variable VPATH oder man benutzt die Anweisung vpath. Im ersten Fall werden alle Dateien, die als Quelle oder Ziel auftauchen, zusätzlich in den angegebenen Verzeichnissen gesucht. Mehrere Pfade werden durch Leerzeichen oder Doppelpunkte (unter Unix) bzw. Semikolons (unter Windows) getrennt.
VPATH := classes
Die Anweisung vpath bewirkt genau dasselbe, nur das man zusätzlich noch einschränken kann, nach welchen Dateien in den angegebenen Verzeichnissen gesucht wird.
vpath *.class classes
In diesem Fall wird nur nach Dateien, deren Name mit .class endet zusätzlich im Verzeichnis classes gesucht. Betrachten wir zum Beispiel folgende Regel:
Test.class : Test.java
	javac -d ${classdir} Test.java
Beim ersten Durchlauf wird der Java-Compiler die Datei Test.java kompilieren und im Verzeichnis ablegen, das in der Variablen classdir steht. Rufen wir Make direkt im Anschluss und ohne die Quelle geändert zu haben wieder auf, so wird Make den Java-Compiler erneut aufrufen, da es die Datei Test.class nicht findet. Wenn wir aber, wie oben angebenden, den Suchpfad ändern, so wird Make, unabhängig von der gewählten Methode, die class-Datei finden und feststellen, dass das Ausführen des Befehls unnötig ist.
Bisher haben wir Make dazu gebracht, eine einzige Regel für eine ganze 
  Gruppe von Dateien zu benutzen und Dateien nicht nur im aktuellen Verzeichnis 
  zu suchen. Jetzt fehlt nur noch eine einfache Möglichkeit Make mitzuteilen, 
  welche Klassen am Ende existieren sollen. Denn was bringt es, wenn eine Regel 
  für mehrere gesuchte Dateien verwendet werden kann, aber keine Dateien 
  gesucht werden?
  Folgender Ausschnitt aus einem Makefile kann den gewünschten Erfolg nicht 
  bringen:
compile : *.class
%.class : %.java javac -d . $<
In diesem Fall hängt zwar compile von allen class-Dateien im Verzeichnis ab, wenn jedoch eine class-Datei noch nicht existiert wird sie auch niemals erstellt werden. Dieses Verhalten wird an einem Beispiel sehr schnell deutlich. Angenommen wir haben in unserem Verzeichnis die Dateien A.java, B.java und C.java aber nur A.class und B.class, weil C.java vielleicht gerade erst erstellt wurde. Beim Aufruf von make compile geschieht nun folgendes: compile hängt von A.class und B.class ab. Diese beiden werden mit Hilfe der Pattern-rule bei Bedarf aktualisiert. Danach sind alle Vorbedingungen für compile erfüllt, C.java wurde aber noch nicht kompiliert.
Noch weniger Sinn macht es, compile von *.java abhängig 
  zu machen, denn die Java-Dateien sind die Quellen, aus denen die class-Dateien 
  erzeugt werden, wie man an der Pattern-rule sehr schön sehen kann. Aber 
  die Idee geht in die richtige Richtung. Am besten wäre es, die Namen von 
  allen vorhandenen java-Dateien zu nehmen, .java durch .class 
  auszutauschen und compile von diesen Dateien abhängig zu machen.
  Für solche und ähnliche Fälle gibt es Funktionen in Make, mit 
  denen man Texttransformationen durchführen kann. Dazu gehören das 
  Ausfiltern von bestimmten Mustern, das Sortieren von Listen oder eben das Austauschen 
  von bestimmten Zeichenketten. Funktionen werden ähnlich wie Variablen benutzt:
$(funktion parameter)
${funktion parameter}
Für unsere Zwecke benötigen wir zwei Funktionen, nämlich wildcard um eine Liste der Java-Dateien zu erhalten und patsubst um die einzelnen Namen zu ändern. wildcard erwartet ein oder mehrere Muster, und liefert die Namen von allen Dateien im aktuellen Verzeichnis als Leerzeichen getrennte Liste zurück, die einem der Muster entsprechen.
FILES := ${wildcard *.java}
In der Variablen FILES steht danach z.B. A.java B.java C.java. Die Funktion patsubst tauscht alle Zeichenketten in einer Liste, die auf das übergebene Muster passen gegen eine andere übergebene Zeichenkette aus.
FILES := ${patsubst %.java,%.class,${FILES}}
Das Ersetzen mit Hilfe des „%“ funktioniert hier genau wie im Abschnitt Pattern-rules beschrieben. Nach dem Aufruf der Funktion sind in der Variablen FILES alle java-Dateien durch class-Dateien ersetzt. Der Inhalt lautet nun also A.class B.class C.class und kann als Voraussetzung für compile verwendet werden:
FILES := ${wildcard *.java}
FILES := ${patsubst %.java, %.class, ${FILES}}
compile : ${FILES}
%.class : %.java
	javac -d . $<
Wenn man nun die verschiedenen Konzepte aus den vorigen Abschnitten gemeinsam 
  betrachtet, ergibt sich eine Möglichkeit, die Kompilation ganzer Pakete 
  über ein Makefile zu steuern. Allein über das Anpassen des Suchpfades 
  wird es jedoch nicht funktionieren, da Namen von Quelldateien durchaus doppelt 
  vorkommen können, solange sie in verschiedenen Verzeichnissen liegen. Es 
  könnte also passieren, dass einer Quelle eine falsche Zieldatei zugeordnet 
  wird, und sie dadurch eventuell nicht oder aber unnötig kompiliert wird.
  Für unseren Zweck erweitern wir das Beispiel aus dem vorigen Abschnitt 
  so, dass nicht nur Quelldateien im aktuellen Verzeichnis berücksichtigt 
  werden, sondern auch diejenigen, die in ausgewählten Unterverzeichnissen 
  liegen. Zunächst legen wie ein paar Variablen an, die Quell- und Zielverzeichnis 
  sowie alle zu berücksichtigenden Unterverzeichnisse enthalten:
SRCDIR = src BUILDDIR = build SUBDIRS = . de de/fh-wedel
Die Quellen liegen also im Verzeichnis src, die kompilierten Klassen werden in build abgelegt und berücksichtigt werden sollen alle Dateien im eigentlichen src-Verzeichnis (gekennzeichnet durch den Punkt), dessen Unterverzeichniss de und darunter zusätzlich das Verzeichniss fh-wedel. Wir müssen nun eine Liste der Quelldateien in allen angegebenen Verzeichnissen erstellen. Dazu benutzen wir zum einen die bereits vorgestellte Funktion wildcard, zum anderen eine Art for-Schleife, wie man sie aus den meisten Programmiersprachen kennt. Die foreach-Funktion in Make fügt, vereinfacht gesagt, für jedes Element in einer übergebenen Liste einen Text an eine Veriable an. Dieser angehängte Text kann beliebig zusammengesetzt sein und z.B. aus einem anderen Funktionsaufruf stammen. Der allgemeine Aufruf lautet:
$(foreach var,list,text)
Bei jedem Durchlauf wird der Variablen var der nächste Wert aus list zugewiesen, und erst danach text ausgewertet. Man kann also in text auf den jeweils aktuellen Wert von var zugreifen. Wenn die Schleife durchgelaufen ist wird der komplette Text aus allen Durchläufen zurückgegeben. Wir verwenden als Parameter text die wildcard-Funktion, um uns die Java-Dateien im jeweiligen Verzeichnis zurückgeben zu lassen.
FILES := $(foreach dir,$(SUBDIRS),$(wildcard ${SRCDIR}/${dir}/*.java))
Für jedes Verzeichnis in SUBDIRS wird also die wildcard-Funktion 
  aufgerufen und damit eine Liste von Java-Dateien aus allen Unterverzeichnissen 
  von SRCDIR erstellt. Durch den Punkt in SUBDIRS werden auch 
  die Quellen aus SRCDIR mit aufgenommen.
  Wie im letzten Abschnitt beschrieben müssen noch die Dateinamen von .java 
  in .class und, weil vor jedem Dateinamen der Wert von SRCDIR 
  steht, der Anfang des Pfades durch den Wert von BUILDDIR ersetzt werden, 
  denn schließlich sollen die kompilierten Klassen dort gesucht werden.
CLASSES := $(patsubst %.java,%.class,$(FILES)) CLASSES := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(CLASSES))
Zum Schluss erstellen wir noch zwei Regeln: in der ersten, und damit der Standard-Regel, geben wir als Abhängigkeiten alle class-Dateien an, damit bei jedem Aufruf geprüft wird, ob eine oder mehrere neu erstellt werden müssen.
all : $(CLASSES)
Die zweite Regel ist natürlich eine Pattern-rule, da alle Klassen auf die gleiche Art und Weise kompiliert werden. Dabei müssen wir jedoch beachten, dass die Verzeichnisstruktur unterhalb des BUILDDIR auf das Verzeichnis SRCDIR abgebildet werden muss.
$(BUILDDIR)/%.class : $(SRCDIR)/%.java javac -classpath $(SRCDIR):$(BUILDDIR) -d $(BUILDDIR) $<
Damit wird jeder class-Datei in einem Unterverzeichnis von BUILDDIR ihre java-Datei im gleichen Unterverzeichnis von SRCDIR zugeordnet. Ein Aufruf von make all erstellt nun alle Klassen bzw. nur die, bei denen die Quellen geändert wurden.