Ein Vergleich mit dem Make-System
Simon Lembke (WI4139)
Nachdem der javac-Task schon mehrfach in Beispielen aufgetreten ist, soll er nun auch genauer erklärt werden. Laut Ant-Dokumentation durchsucht dieser Task das Quellverzeichnis und dessen Unterverzeichnisse nach Java-Dateien und überprüft anhand der Änderungsdaten, welche Dateien neu kompiliert werden müssen. Zur Steuerung dieses Tasks stehen diverse Attribute zur Verfügung. Über srcdir wird festgelegt, welche Verzeichnisse die Sourcen enthalten. Mehrere Verzeichnisse können dabei entweder durch einen Doppelpunkt oder ein Semikolon getrennt werden, da Ant das Trennzeichen automatisch an das aktuelle System anpasst. Die kompilierten Klassen werden in dem unter destdir angegebenen Verzeichnis abgelegt. Damit javac prüfen kann, ob eine Quelle neu kompiliert werden muss, muss die Verzeichnisstruktur im Quellverzeichnis den Paketzugehörigkeiten der Quellen entsprechen. Ansonsten werden die Klassen bei jedem Aufruf des Tasks neu erstellt.
Es gibt die Möglichkeit, einzelne Dateien oder ganze Verzeichnisse auszuschließen. Mit Hilfe des Attributs includes gibt man zunächst an, welche Dateien dazugehören sollen. Hierzu können sogenannte Wildcards angegeben werden, die, genau wie in einer Shell, auf eine ganze Gruppe von Dateien passen. Besondere Bedeutungen haben dabei „*“, „?“ und „**“. Der „*“ steht für kein oder beliebig viele Zeichen, das „?“ für genau ein Zeichen und „**“ für eine beliebige Anzahl von Verzeichnissen. Die Angabe von „**“ ermöglicht es, Dateien in beliebigen Unterverzeichnissen anzugeben.
*.java passt auf alle Dateien im Verzeichnis, deren Endung .java
lautet.
?.xml passt auf alle Dateien im Verzeichnis, deren Endung .xml
lautet und deren Namen genau ein Zeichen vor dem Punkt haben.
**/Test.java passt auf alle Dateien, die Test.java heissen
und in einem beliebigen Unterverzeichnis liegen.
Mehrere Muster werden entweder durch Kommata oder durch Leerzeichen voneinander
getrennt. Wenn das Attribut includes weggelassen wird benutzt Ant die
Standardeinstellung, in der alle Java-Dateien unterhalb des srcdir
ausgewählt werden.
Das Gegenteil von includes ist excludes. Hier können
die gleichen Muster verwendet werden, nur das hiermit beschrieben wird, welche
Dateien nicht dazugehören. Wenn excludes nicht angegeben wird,
werden alle Dateien, auf die eines der includes-Muster passt, kompiliert.
Allgemein werden alle Dateien kompiliert, die mindestens einem der includes-
und keinem der excludes-Muster entsprechen.
Wie gesehen ist das Erzeugen von Klassen in verschiedenen Paketen unter Ant sehr einfach zu realisieren. Der Task „javac“ übernimmt das Auffinden der java- und der dazugehörigen class-Dateien, kompiliert bei Bedarf und lässt sich sehr leicht konfigurieren. Natürlich lassen sich auch mit Make mehrere Packages kompilieren, nur ist dazu ein wenig mehr Aufwand erforderlich. Bisher gibt es nur die Möglichkeit, den einzelnen Quelldateien ihre Zieldateien zuzuweisen. Wie man sich denken kann, ist das nicht nur mühsam sondern auch sehr fehleranfällig.
Um nicht für jede Datei eine eigene Regel schreiben zu müssen, kann
man sogenannte Pattern-rules schreiben, in denen man für ein bestimmtes
Muster angibt, wie mit Dateien verfahren werden soll, die auf dieses Muster
passen. Eine Pattern-rule wird genauso definiert wie eine normale Regel,
enthält aber im Namen der Zieldatei ein „%“. Dieses „%“
steht für eine beliebige Anzahl von Zeichen, mindestens jedoch für
eins. Ein eventuell im Quelldateinamen vorkommendes „%“ wird durch
die Zeichenkette ersetzt, für die es im Zieldateinamen stand.
Nehmen wir an, Make sucht nach einer Regel für die Datei Test.class,
es ist aber entweder keine vorhanden oder sie enthält keine Befehle. Stattdessen
existiert folgende Pattern-rule:
%.class : %.java <irgendein Kommando>
Die Datei Test.class passt auf das Muster %.class, wobei das „%“ den Teil Test darstellt. Daher wird im Quelldateinamen das „%“ durch Test ersetzt. Die Regel wäre in diesem Fall also identisch mit folgender Definition:
Test.class : Test.java <irgendein Kommando>
Der entscheidende Unterschied ist, dass die obere Regel auch für HelloWorld.class, FooBar.class usw. funktioniert.
Da Make nichts über die Befehle weiß, die ausgeführt werden sollen, stellt sich die Frage, wie man die aktuellen Dateinamen in die Befehlszeilen bekommt. Dazu existieren in Regeln die automatischen Variablen, die bei jedem Ausführen einer Regel die aktuellen Werte zugewiesen bekommen.
$@ enthält den Namen der Zieldatei
$^ enthält die Namen der Quelldateien
$< enthält den Namen der ersten Quelldatei
Darüber hinaus existieren noch weitere Variablen, auf die hier nicht weiter eingegangen werden soll. Während die Variablen in normalen Regeln „nur“ Redundanz und Tippfehler vermeiden, sind sie in Pattern-rules die einzige Möglichkeit, an die entsprechenden Dateinamen zu gelangen. Wir können das obige Beispiel also wie folgt erweitern:
%.class : %.java javac -d . $<
Damit ist es möglich den Java-Compiler aufzurufen ohne zu wissen, welche Datei bearbeitet werden soll.
Eine besondere Form der Pattern-rules sind die implicit rules. Diese sind, wie der Name schon andeutet, implizit vorhanden und müssen nicht extra aufgeschrieben werden. Make weiß z.B. wie Objekt-Dateien aus C-Quellen erstellt werden können. Die Befehle, die in impliziten Regeln verwendet werden, sind größtenteils aus Variablen zusammengesetzt, so dass der Nutzer weiterhin Einfluss auf die Ausführung hat. Um das eben beschriebene Verhalten bezüglich der Objekt-Dateien zu erreichen, existiert ungefähr folgende Regel implizit:
%.o : %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
Obwohl sie auf den ersten Blick ziemlich verwirrend aussieht und Regeln wie diese vermutlich dazu beigetragen haben, das Makefiles gemeinhin als unleserlich gelten, ist sie doch einfach zu verstehen. Um eine Objekt-Datei aus der entsprechenden C-Quelle zu erstellen, wird das Programm, dessen Name in der Variablen CC steht, mit den folgenden Parametern ausgeführt. In der Variablen CFLAGS stehen Anweisungen an den C-Compiler, in CPPFLAGS Anweisungen an den C-Prä-Prozessor. $@ und $< sind alte Bekannte aus dem vorigen Abschnitt. Nach der Substitution der Variablen entsteht beispielsweise ein ganz normaler Aufruf des GNU C-Compilers gcc.
gcc -c -pedantic -DDEBUG=1 -o test.o test.c
Es gibt eine ganze Reihe dieser implicit rules, die hauptsächlich für C- und C++-Programmierer interessant sind. Für Java existieren (noch) keine Regeln, man kommt aber auch gut ohne sie aus.