Ant - Das Apache Build-Werkzeug für Java

Ein Vergleich mit dem Make-System
Simon Lembke (WI4139)


... [ Seminar WS 2002/03 ] ... [ Ant ] ... [ Variablen / Properties ] ... [ Größere Projekte ] ... [ Größere Projekte (Fortsetzung) ] ...

Übersicht : Größere Projekte


Der Task javac

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.


Dateien und Verzeichnisse spezifizieren

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.


Packages mit Make

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.


Pattern-rules

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.


Automatische Variablen

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.


Implicit rules

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.


... [ Seminar WS 2002/03 ] ... [ Ant ] ... [ Variablen / Properties ] ... [ Größere Projekte ] ... [ Größere Projekte (Fortsetzung) ] ...