Macht der Wechsel alles besser?

Buildsysteme im Vergleich: Apache Ant

Nachdem die volle Freiheit bei Apache Ant durch Apache Maven sehr eingeschränkt wurde, hat Gradle sie wieder aufgelockert. Letztendlich ist ein Buildsystem aber nur Hilfsmittel, um wieder produzierbare Ergebnisse zu erstellen. Damit ist die Frage berechtigt, warum man sein bestehendes Build-System wechseln sollte. Was können andere Systeme besser? An einem Beispiel werden die drei Kandidaten Ant, Maven und Gradle einem Vergleich unterzogen. Ein Resümee soll bei der Auswahl des passenden Systems unterstützen. Abgenommen wird die Auswahl aber nicht.

Eine Umfrage auf JAXenter bestätigt, dass der Markt der Build-Systeme für Java-Projekte sich vor allem auf die schon altbewährten Ant und Maven aufteilt. Auf einem guten dritten Platz der Umfrageergebnisse befindet sich bereits der Neuling Gradle (Ergebnis: 12 %). Interessant ist auch, dass 4 % der Umfrageteilnehmer angegeben haben, kein Build-System einzusetzen. Somit soll kurz die Frage erlaubt sein, wann und warum es überhaupt Sinn macht, ein Build-System im Projekt einzusetzen.

Die hauptsächliche Entwicklung eines Java-Projekts erfolgt in einer Entwicklungsumgebung (IDE) wie Eclipse, Netbeans oder IntelliJ IDEA. Ständig führt der Entwickler die drei wichtigen Schritte aus: Edit, Compile und Run. Mit diesem Entwicklungszyklus sind Änderungen schnell sichtbar, und der Entwickler kann die Anwendung direkt aus der Entwicklungsumgebung heraus starten. Je größer und komplizierter die Anwendung wird, umso mehr Einstellungen sind erforderlich, um die Anwendung zu starten. Sobald es aber daran geht, dass Teammitglieder mit verschiedenen Entwicklungsumgebungen arbeiten bzw. die Anwendung auf ein anderes System gebracht werden muss, wird eine Unabhängigkeit von der Entwicklungsumgebung benötigt. Laufzeiteinstellungen für ein Projekt sollten deswegen nicht in einer lokalen Entwicklungsumgebung festgehalten werden. Dies erschwert die schnelle Übernahme auf ein anderes System und ist der Ansatzpunkt für ein Build-System. Selbst bei wachsender Quellcodebasis und steigender Anzahl der Entwickler kann durch das Build-System sichergestellt werden, dass die Zielartefakte unabhängig von einem Entwicklerrechner erzeugt werden können. Die Möglichkeiten, die sich durch solch ein System eröffnen, sind mannigfaltig.

Die drei Systeme Ant, Maven und Gradle werden anhand eines Beispielprojekts einem Vergleich unterzogen. Alle drei Systeme (Ant ab Version 1.6.1) basieren auf der Apache License Version 2.0. Der Artikel hat nicht den Anspruch, alle Systeme vollständig zu erklären, die wichtigsten Konzepte werden aber erläutert.

[ header = Seite 2: Die Spielregeln ]

Die Spielregeln

Um einen guten Eindruck der drei Probanden zu bekommen, wird die Vorstellung anhand eines Beispiels mit typischen Projektanforderungen durchgeführt. Es wurde versucht, einige der typischen Anforderungen an den Build-Prozess zu definieren. Eine vollständige Abdeckung kann dieser Artikel aber nicht leisten. Größere Projekte sind häufig in Module (Teilprojekte) aufgeteilt, dies spiegelt das Demoprojekt durch drei Module wieder. Das Modul core stellt dabei eine Basisbibliothek dar, die von den anderen beiden Modulen wiederverwendet wird. Diese Module tragen die Namenrichclient und webclient. In Abbildung 1 ist die Abhängigkeit zwischen den drei Modulen visualisiert. Die Projekte verwenden dabei unterschiedliche Bibliotheken. Alle drei Projekte enthalten Unit-Tests auf der Basis von JUnit 4. In den Testklassen werden Bibliotheken eingesetzt, die teilweise nur in diesen benötigt werden und somit nicht im Zielartefakt (war-Archiv beim Projekt webclient) landen sollen.

Abbildung 1

Die Verzeichnisstruktur des Demoprojekts (Abb. 2) entspricht der Standardverzeichnisstruktur eines Maven-Projekts. Diese hat sich inzwischen in vielen Java-Projekten etabliert und bewährt.

Abbildung 2

Um die Anforderungen im weiteren Verlauf wieder zu identifizieren, sind sie durchnummeriert und in der nachfolgenden Tabelle kurz erläutert.

Nummer

Beschreibung

1

Jedes der drei Teilprojekte kann im Build-Prozess einzeln erzeugt werden. Zusätzlich soll aber auch das Gesamtprojekt über einen einzigen Aufruf erzeugt werden können.

2

Aus dem Projekt core soll ein JAR-Archiv erzeugt werden. Dieses JAR-Archiv soll eine angepasste Manifest-Datei enthalten. Der Name der erzeugten Datei ist core-1.0.0.jar. Die Version 1.0.0 kann konfiguriert werden.

3

Aus dem Projekt richclient soll ein JAR-Archiv erzeugt werden. Auch dieses JAR-Archiv enthält eine angepasste Manifest-Datei. Der Name der erzeugten Datei istrichclient-1.0.0.jar. Für die Erzeugung des Projekts soll die Bibliothek core-1.0.0.jar eingebunden werden.

4

Aus dem Projekt webclient soll ein WAR-Archiv erzeugt werden. Der Name der erzeugten Datei ist webclient-1.0.0.war. Für die Erzeugung des Projekts soll die Bibliothek core-1.0.0.jar verwendet werden. In der erzeugten Archivdatei soll im Verzeichnis WEB-INF/lib die Datei core-1.0.0.jar liegen.

5

Alle Projekte enthalten Unit-Tests auf Basis von JUnit 4. Diese Tests sollen vom Build-Prozess ausgeführt werden. Das Projekt webclient benötigt für die Kompilierung und die Unit-Tests servlet-api.jar und das Spring Framework. Diese Bibliotheken dürfen nicht im Zielarchiv enthalten sein.

6

Auf den Projekten soll Checkstyle ausgeführt werden.

Bei der Erfüllung der Anforderungen soll den Build-Systemen Hilfe zugestanden werden. Wenn eine Anforderung nicht mit der Grundausstattung des Build-Systems umgesetzt werden kann, wird versucht, sie mit vorhandenen Erweiterungen umzusetzen.

[ header = Seite 3: Apache Ant ]

Apache Ant

Apache Ant kann als der Urvater der Java-Build-Systeme bezeichnet werden. Da Ant schon viele Jahre erfolgreich in Java-Projekten eingesetzt wurde, kann es als sehr stabil und ausgereift betrachtet werden. Die Releasezyklen liegen derzeit bei einer Dauer von ca. 1,5 Jahren (Kasten: „Was ist neu in Apache Ant 1.8? „).

Was ist neu in Apache Ant 1.8?
Anfang Februar 2010 wurde nach 1,5 Jahren eine neue Version von Ant, nämlich 1.8, veröffentlicht. In dieser Version sind knapp 300 Bugs behoben worden. Neben diesen Fehlerbehebungen sind Änderungen hinzugekommen, bei denen es sich lohnt, näher hinzusehen. Für die bessere Modularisierung von Ant Builds ist es nun möglich, zu einem Task einen Erweiterungspunkt zu definieren. Hierfür wurde das Element extensionpoint eingeführt. Zusätzlich wurde die Definition eines Tasks in der Form des Elements target um das Attribut extensionOf erweitert, um sich an solch einem Erweiterungspunkt einzuhängen.
Die nächste merkbare Änderung ist, dass der Auswertungsmechanismus für if/unless geändert wurde. Mit Ant 1.8 wird nicht lediglich geprüft, ob die Property gesetzt ist, sondern der Wert wird ausgewertet und geprüft, ob er positiv validiert. Diese Änderung führt dazu, dass es sich bei Ant 1.8 nicht um ein „Drop-In Replacement“ handelt. Zusätzlich wurde mit include ein weiterer Mechanismus zum Einbinden von Build-Dateien eingeführt. Die genauen Unterschiede zwischen include und import sind in der Dokumentation aufgeführt.

Für die Installation von Ant ist lediglich ein Archiv von der Downloadseite herunterzuladen. Für die aktuelle Version 1.8.0 wird Java in der Version 1.4 vorausgesetzt. Nach dem Entpacken (entpackte Distribution siehe Abb. 3) kann Ant über das Kommandozeilenskript ant (Verzeichnis bin in der Distribution) ausgeführt werden. Für den ersten Test der Installation kann über den Befehl ant -version die Version ausgegeben werden.

Abb. 3: Entpackte Distribution von Apache Ant 1.8.0

Die Steuerung von Ant erfolgt über ein Build-Skript, das im XML-Format definiert wird. Der Standardname dieser Datei lautet build.xml. Das Build-Skript ist dabei aus einzelnen Tasks aufgebaut, die voneinander abhängen können. Sollte man nicht schon eine Vorlage besitzen, muss man bei einem neuen Projekt mit einer leeren Datei beginnen. Nachfolgender Quellcode zeigt ein einfaches Build-Skript mit einem Task. Verpflichtend muss im Attribut default der Standardtask angegeben werden, der ausgeführt wird, wenn kein spezieller Task angegeben wird. Über das Skript ant wird im aktuellen Verzeichnis die Datei build.xml gesucht und der Standard-Task ausgeführt. Über die Option -buildfile kann ein alternativer Name für das Build-Skript angegeben werden. Zur Ausführung eines anderen Tasks als des Standard-Tasks ist der Name des Tasks als Parameter anzugeben. Es können auch mehrere Tasks getrennt durch Leerzeichen angegeben werden. Über die Option -p werden alle Tasks des Build-Skripts ausgegeben, die eine Beschreibung im Attribut description gesetzt haben:

<project name="core" default="setup" basedir=".">
<target name="setup">
<echo message="setup ..." />?>
</target>
</project>

Für das Erstellen der Build-Skripts stellt Ant viele vorgefertigte Tasks zur Verfügung. Der erste Schritt soll nun sein, Tasks für die Übersetzung und das Aufräumen zu definieren. Einstellungen werden dabei über das Element property vorgenommen. Neben dieser Variante existiert auch die Möglichkeit, solche Einstellungen über eine Property-Datei zu steuern. Im Listing 1 ist das Skript mit den Tasks compile und clean dargestellt. Der Klassenpfad für die Übersetzung wird in diesem Beispiel über das Element path definiert. Dies empfiehlt sich, um den Pfad auch für andere Tasks wiederverwenden zu können.

<project name="core" default="compile" basedir=".">

<property name="target.dir" value="target" />
<property name="classes.dir" value="${target.dir}/classes" />

<!-- Definition compile-classpath -->
<path id="classpath.compile">
<pathelement path="/Developer/3rdParty/commons-lang-2.4/commons-lang-2.4.jar"/>
</path>

<!-- Aufraeumen -->
<target name="clean" description="removes the output directory">
<echo message="remove the target directory" />
<delete dir="${target.dir}" />
</target>

<!-- Quellcode uebersetzen -->
<target name="compile" depends="clean" description="compile the sources">
<mkdir dir="${classes.dir}" />
<javac srcdir="src/main/java" destdir="target/classes"
includeAntRuntime="false" classpathref="classpath.compile"
/>
</target>
</project>

[ header = Seite 4: Listing 2 bis Ende ]

Für die Erzeugung eines JAR-Achivs stellt Ant ebenfalls einen Task (jar) zur Verfügung. Diesem Task können über das Subelement manifest die Attribute für die Manifest-Datei mitgegeben werden:

<!-- JAR-Archiv erzeugen -->
<target name="package" depends="compile">
<jar destfile="${target.dir}/core-${project.version}.jar">
<fileset dir="${target.dir}"/>
<manifest>
<attribute name="Built-By" value="${user.name}"/>
<attribute name="Implementation-Version" value="${project.version}"/>
</manifest>
</jar>
</target>

Um die Projekte richclient und webclient zu übersetzen, wird die core-Bibliothek benötigt. Sie wird einfach über eine Definition im Klassenpfad eingebunden:

<!-- Definition compile-classpath -->
<path id="classpath.compile">
<pathelement path="../core/target/core-1.0.0.jar"/>
</path>

Für das Projekt webclient wird über den Task war das war-Archiv erzeugt. Für den Aufbau des Archivs stehen entsprechende Subelemente (z. B. lib) zur Verfügung. Anforderungen 2, 3 und 4 sind damit erfüllt:

<!-- WAR-Archiv erzeugen -->
<target name="package" depends="compile">
<war destfile="${target.dir}/webclient-${project.version}.war" webxml="src/main/webapp/WEB-INF/web.xml">
<classes dir="${classes.dir}" />
<lib file="../core/target/core-1.0.0.jar" />
</war>
</target>

In der Distribution von Ant wird zwischen Core Tasks und Optional Tasks unterschieden. Bei einigen der optionalen Tasks wird nur die Task-Definition mitgeliefert, nicht aber die Bibliothek. Diese Bibliothek muss dann manuell heruntergeladen werden. JUnit ist solch ein optionaler Task. Für die Ausführung der JUnit-Tests (Anforderung 5) muss die JUnit-Bibliothek in den Klassenpfad für den Task junit aufgenommen werden. Vor Ant 1.7 hätte der Entwickler die Bibliothek im lib-Verzeichnis von Ant ablegen oder eine eigene taskdef-Definition mit Klassenpfad zur Verfügung stellen müssen. Für den junit Task reicht es, diese Bibliothek in den Übersetzungspfad aufzunehmen.

Für die Übersetzung der Tests wurde ein weiterer Task mit dem Namen –compile.test definiert. Durch das Zeichen -an einem Task wird vermieden, dass der Task direkt von der Kommandozeile aufgerufen werden kann. Im Ant-Plug-in von Eclipse können solche Tasks trotzdem ausgeführt werden (Listing 2).

<target name="test" depends="-compile.test" description="execute the unittests.">
<junit printsummary="yes" haltonfailure="yes">
<classpath>
<path refid="classpath.test.compile"/>
<pathelement path="${classes.dir}" />
</classpath>

<batchtest fork="yes" todir="${target.dir}/tests">
<fileset dir="src/test/java">
<include name="**/*Test.java"/>
</fileset>
</batchtest>
</junit>
</target>

Richtlinien für das Schreiben von Quellcode (Coding Coventions) sind in Projekten weit verbreitet. Ein bewährtes Werkzeug zum automatischen Überprüfen dieser Richtlinien ist Checkstyle. Ant selbst hat keinen Task für Checkstyle integriert. Diesen liefert das Projekt aber direkt mit. Um Checkstyle verwenden zu können, muss der Task über taskdef dem Build-System bekannt gemacht werden. Mit dieser Definition kann der Task verwendet werden (Anforderung 6):

<taskdef resource="checkstyletask.properties" classpath="/Developer/3rdParty/checkstyle-5.0/checkstyle-all-5.0.jar"/>
<target name="checkstyle" depends="compile">
<checkstyle config="/Developer/3rdParty/checkstyle-5.0/sun_checks.xml">
<fileset dir="src/main/java" includes="**/*.java"/>
</checkstyle>
</target>

Für die komplette Erfüllung von Anforderung 1 fehlt noch eine Erweiterung. Die Erzeugung jedes einzelnen Projekts wurde dadurch erreicht, dass für jedes Teilprojekt ein eigenes Build-Skript erstellt wurde. Um nun alle Teilprojekte mit einem Aufruf zu erzeugen, wird ein weiteres Build-Skript benötigt. Dieses Build-Skript ruft die Build-Skripte der Teilprojekte auf. Für den Aufruf anderer Build-Skripte stellt Ant den Task ant zur Verfügung:

<project name="buildsysteme" default="all" basedir=".">
	
<target name="all">
<ant antfile="build.xml" dir="core" target="package" />
<ant antfile="build.xml" dir="richclient" target="package" />
<ant antfile="build.xml" dir="webclient" target="package" />
</target>

</project>

Im nächsten Teil widmen wir uns Apache Maven. Stay tuned!

Markus Stäuble ist Senior IT-Consultant bei der MRM Worldwide GmbH. Er schreibt regelmäßig Artikel für diverse Fachzeitschriften und gibt sein Wissen gerne in Vorträgen wieder.
Geschrieben von
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.