Dieser und noch weitere Artikel wurde von SideWinder erstellt.


Folgende Themen werden von diesem Artikel berührt:


Druckversion des Artikels


Build-Systeme Teil 2: Apache Ant

Inhalt:
  1. Voraussetzungen
  2. Einführung
  3. Basiswissen
  4. Ein Beispielprojekt
    4.1 Property-Dateien
    4.2 Implementierung der Build-Datei
    4.3 Implementierung der Sub-Targets
  5. Ein paar Dinge mehr
  6. What comes next...
  7. Anhang
  8. Quellen


1 Voraussetzungen:

- Kenntnisse über Build-Systeme im Allgemeinen (Erhältlich im 1. Teil dieser Serie)
- Basis-Kenntnisse im Bereich XML (Erhältlich auf http://www.w3schools.com)
- Um die Beispiele sofort ausprobieren zu können ist Ant zu installieren (Siehe http://ant.apache.org/manual/install.html#installing)

2 Einführung:

Herzlich Willkommen beim 2. Teil unserer Artikel-Serie über Build-Systeme. Nachdem GPC die theoretischen Grundlagen für mich erledigt hat, werde ich euch das Build-Tool Apache Ant näher bringen. Das von der Apache Software Foundation frei zur Verfügung gestellte Build-System ist reich an Funktionalität und einfach einzusetzen. Ant bezeichnet sich selbst als "Make[1] without wrinkles", was das Build-System meiner Meinung nach sehr treffend beschreibt.

Einige Vorteile von Ant sind:
-> Eine sehr mächtige BuiltIn-Bibliothek mit oft benötigten Funktionen
-> Ant ist in reinem Java geschrieben und daher auf nahezu allen modernen Plattformen einsetzbar
-> Dank XML sind die Build-Dateien gut strukturiert und wartbar

Andere Build-Systeme gehen meist den Weg eingespeiste Befehle auf der Shell des Systems auszuführen, das hat den Vorteil, dass sie für jede Programmiersprache gleich gut aber auch gleich schlecht eingesetzt werden können. Ant geht einen anderen Weg: Befehle werden über eine BuiltIn-Bibliothek angeboten, das vereinfacht die Anwendung ungemein und ermöglicht mächtige Features mit wenigen Zeilen einzubinden. Jede Medaille hat aber auch ihre Kehrseite: Die BuiltIn-Bibliothek ist nahezu ausschließlich auf Java ausgelegt, was Ant auf diese Sprache einschränkt.

Hinweis: Die BuiltIn-Bibliothek ist mit eigenen Modulen erweiterbar. Ant wäre also durchaus in der Lage auch mit anderen Programmiersprachen als Java eingesetzt zu werden. Der Aufwand ist allerdings nicht gerechtfertigt, ein anderes Build-Tool liegt da näher.

Was diese BuiltIn-Bibliothek alles leistet wird spätestens bei folgendem Beispiel deutlich:
Auftraggeber schrieb:

Sämtliche Source-Dateien liegen auf einem CVS-Server[2], die neueste Version ist natürlich vor dem Build von dort zu beziehen. Danach soll der eigentliche Build-Prozess von statten gehen. Wird dieser korrekt beendet sollen automatisierte Tests am Ergebnis durchgeführt werden (Kurzfassung der Ergebnisse per Mail an den Projektleiter). Zu guter Letzt soll die computergenerierte Entwickler-Dokumentation erneuert werden und der fertige Build mit der richtigen Build-Nummer auf einen Web-Server geladen werden.

Mit einem auf Shellbefehlen basierendem Build-System würde man hier gehörig ins Schwitzen kommen, mit Ant reichte es in diesem Fall, einen Ferialpraktikanten einzustellen.

Um auch anderen zu zeigen wie einfach Ant in ein bestehendes System zu integrieren ist, möchte ich mein Wissen - learning by doing - an einem Beispielprojekt weitergeben.

3 Basiswissen

Nach den theoretischen Grundlagen kommen wir nun zur praktischen Anwendung. Im Gegensatz zu Make werden bei Ant die auszuführenden Targets im XML-Schema zugeführt. Der grundlegende Aufbau gleicht dem der meisten Build-Systeme:
Code:
- Projekt (= Projekt)
- Target (= Ziel)
  Typisches Beispiel: build, create_docs
- Task (= Aufgabe)
  Typisches Beispiel: javac

Während die Targets von uns definiert werden, verwenden wir Tasks aus der BuiltIn-Bibliothek.

Kommen wir also zur Build-Datei. Die Datei heißt gewöhnlich build.xml und liegt im Projekt-Root-Verzeichnis. Durch Übersetzen des oben gezeigten Schemas in XML erhalten wir folgenden Grundaufbau:
HTML:
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<project name="MeinProjekt">
 
    <target name="Ziel">
 
        <task param="value" />
        <task param="value" />
        ...
 
    </target>
    ...
 
</project>


Hinweis: Kommentare können im XML-Format gemacht werden:
HTML:
<!-- Kommentar -->
<!-- Auch
    mehrzeilig
    möglich.
-->



4 Ein Beispielprojekt:

Damit wir den genauen Aufbau der Build-Datei nicht am Trockendock der Theorie erlernen müssen, muss ein fiktiver Taschenrechner als Beispielprojekt herhalten. Die Source-Dateien liegen relativ zur Build-Datei im Verzeichnis source. Mehr ist noch nicht vorhanden. In Zukunft möchten wir im Verzeichnis /docs stets die aktuelle JavaDoc für unser Projekt haben und im Verzeichnis release jeweils die aktuelle Jar-Datei für unseren Taschenrechner (mit aktueller Build-Nummer im Namen).

4.1 Property-Dateien:

Bevor wir uns jetzt allerdings an die Implementierung machen können muss ich noch ein kurzes Wort über Property-Dateien verlieren. Sehr oft hat man es in Build-Dateien mit Konstanten zu tun (Pfade, Dateien, Bezeichner, etc.), damit diese nicht hardcoded in der Build-Datei - sicherlich auch noch redundant und unauffindbar für Änderungen - ihr Dasein fristen, werden sie in Property-Dateien zusammengefasst. Diese tragen die Endung ".properties" und man verwendet für die Standard-Property-Datei analog zur "build.xml" den Namen "build.properties".

Ein Property selbst ist nicht mehr als ein Key-Value-Paar bestehend aus Konstanten-Name (Key) und -Wert (Value) getrennt durch ein Gleich-Zeichen. Alle Konstanten aus unserem Beispielprojekt ergeben folgende Property-Datei:
HTML:
project.name=Taschenrechner
project.version=1.2
 
path.source=source
path.dest=classes
path.docs=docs
path.release=release


4.2 Implementierung der Build-Datei:

Nun denn, wollen wir unserem Taschenrechner ein ordentliches Build-System verpassen. Erweitern wir den Rahmen aus dem letzten Kapitel und wir erhalten folgende Build-Datei:
HTML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0"?>
<!-- Unser Projekt
    name ... Projektname
    basedir ... Basis-Verzeichnis für alle relativen Pfadangaben
    default ... Standard-Target, das ausgeführt wird, wenn Ant ohne Parameter gestartet wird
-->
<project name="Taschenrechner" basedir="." default="full">
 
 
    <!-- Kleine Beschreibung wozu die vorliegende Build-Datei gut ist -->
    <description>
    Automatisierung des Build-Prozesses für Taschenrechner 1.2
    </description>
 
 
    <!-- Laden unserer Property-Datei für Zugriff auf Konstanten (Details folgen unten) -->
    <property file="build.properties" />    
 
 
    <!-- Legt die richtig hochgezählte Build-Nummer in eine Konstante (Details folgen unten) -->
    <buildnumber />
 
 
    <!-- Unser Standard-Target
        name ... Target-Name
        depends ... Abhängigkeiten von anderen Targets, diese müssen ausgeführt worden sein *bevor* full ausgeführt wird
   -->
    <target name="full" depends="compile,javadoc,jar">
    </target>
 
 
    <!-- Sub-Targets -->
    <target name="compile">
        ...
    </target>
 
    <target name="javadoc">
    <!-- Nicht abhängig von compile - JavaDoc wird aus den Source-Dateien erzeugt und nicht aus den compilierten Class-Dateien! -->
        ...
    </target>
 
    <target name="jar" depends="compile">
        ...
    </target>
 
</project>

Es hat sich einiges getan an unserer Basis-Datei. Wir haben das Gesamt-Target in 3 Sub-Targets aufgeteilt. Zudem haben wir einige globale Tasks (= Tasks ohne Zugehörigkeit zu einem Target) angelegt, diese werden unabhängig vom gewählten Target immer ausgeführt. Gehen wir die Tasks also Schritt für Schritt durch:

Code:
[b]Name:       description[/b]
Attribute:  -
Inhalt:     Beschreibung des Projekts

Dieser Task wird von Ant ignoriert, er dient lediglich der Beschreibung, damit ein Entwickler weiß, um welche Datei es sich handelt.

Code:
[b]Name:       property[/b]
Attribute:  file ... Datei-Name
Inhalt:     -

Dieser Task lädt unsere Property-Datei die wir im vorangegangenen Kapitel angelegt haben. Ab jetzt können wir mit ${key} auf die Konstanten zugreifen.

Code:
[b]Name:       buildnumber[/b]
Attribute:  -
Inhalt:     -

Dieser Task legt im Verzeichnis eine zusätzliche Datei "build.number" an und speichert dort die aktuelle Build-Nummer. Wird die Build-Datei von Ant abermals ausgeführt findet der Task die Datei und erhöht die Build-Nummer. Zudem legt der Task die aktuelle Build-Nummer automatisch in das Property "build.number", auf das wir dann mit Hilfe von ${build.number} zugreifen können.

4.3 Implementierung der Sub-Targets:

Eigentlich hat man jetzt schon genügend Basiswissen um mit Ant vernünftig zu arbeiten. Die gesamte BuiltIn-Bibliothek ist sehr detailliert im Manual[->1] beschrieben. Dennoch möchte ich noch einige Feinheiten zeigen, daher erstmal die fehlenden Teile unserer Build-Datei:
HTML:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    <target name="compile">
 
        <echo>Compiling (Beispiel-Konsolen-Ausgabe)</echo>
        <javac
           srcdir="${path.source}"
           destdir="${path.dest}"
           failonerror="true"
       />
 
    </target>
 
    <target name="javadoc">
 
        <javadoc
           sourcepath="${path.source}"
           destdir="${path.docs}"
           packagenames="*"
           access="private"
           source="1.5"
           use="true"
           windowtitle="${project.name}"
       />
 
    </target>
 
    <target name="jar" depends="compile">
 
        <jar
           basedir="${path.dest}"
           destfile="${path.release}/${project.name}-${project.version}.${build.number}.jar"
       >
            <manifest />
        </jar>
 
    </target>

Endlich greifen wir ordentlich auf die BuiltIn-Bibliothek von Ant zurück. Zuerst wieder eine kleine Übersicht über die benützten Tags:

Code:
[b]Name:       echo[/b]
Attribute:  -
Inhalt:     Nachricht

Gibt eine Nachricht auf der Konsole aus.

Code:
[b]Name:       javac[/b]
Attribute:  srcdir ... Pfad in dem unsere Java-Dateien liegen
            destdir ... Pfad in den die Class-Dateien kommen sollen
Inhalt:     -

Dieser Task ruft den Java-Compiler auf.

Code:
1
2
3
4
5
6
7
8
9
[b]Name:       javadoc[/b]
Attribute:  sourcepath ... Pfad in dem unsere Java-Dateien liegen
            destdir ... Pfad in dem die fertige JavaDoc landen soll
            packagenames ... Packages die dokumentiert werden sollen (* = alle Packages)
            access ... Bis zu welchem Grad soll dokumentiert werden (private = Alle Methoden, public = Nur öffentliche Methoden, etc.)
            source ... Java-Version des Source
            use ... Soll ein Use-File erstellt werden?
            windowtitle ... Kommt in den <title>-Tag der HTML-Dokumente
Inhalt:     -

Dieser Task ruft den JavaDoc-Builder auf, er lässt sich sehr detailliert konfigurieren über Ant. Alle Attribute findet man im Ant-Manual.

Code:
[b]Name:       jar[/b]
Attribute:  basedir ... Pfad in dem die Class-Dateien liegen
            destfile ... Angabe des Pfad- und Dateinamens an dem die fertige Jar-Datei gespeichert werden soll
Inhalt:     -

Ruft den Jar-Packer auf.

Code:
[b]Name:       manifest[/b]
Attribute:  -
Inhalt:     -

Weist den Jar-Packer darauf hin, auch eine Manifest-Datei zu erzeugen.


Damit haben wir erst einmal unsere Build-Datei fertig gestellt, mit einem Aufruf von:
Code:
ant

im Verzeichnis der build.xml führt Ant das Default-Target für uns aus, ein bestimmtes Target kann natürlich ebenfalls mit angegeben werden:
Code:
ant javadoc


5 Ein paar Dinge mehr:

-> Manchen Tasks kann ein bestimmtes Attribut gesetzt werden, in dem man etwas in den Body des Tags schreibt. So muss man z. B. nicht <echo message="Blubb" /> schreiben sondern kann bequem <echo>Blubb</echo> verwenden
-> Viele Tasks besitzen ein Attribut failonerror, das - wenn auf "true" gesetzt - den gesamten Ant-Build-Prozess beendet wenn ein Fehler auftritt (so kann man z. B. verhindern, dass trotz Fehler im Compile-Vorgang ein Release auf einen Webserver geladen wird)
-> Einige Tasks unterstützen Sub-Tasks, so verhilft uns der Task <manifest> im <jar>-Task zu einer Manifest-Datei (siehe oben), diese Sub-Tags fallen unter den Begriff "Nested Tags"

Den Start von Ant auch noch automatisieren...
...ist ebenfalls kein Problem. Alle gängigen Betriebssysteme bietet heutzutage Möglichkeiten dazu: Unix - cronjob, Windows - Geplante Tasks.

Erweiterung von Ant:
Ant ist über Java-Klassen erweiterbar. So gibt es externe 3rd-Party-Module zum fertigen Download aber man ist auch in der Lage eigene Module zu entwickeln. Das würde allerdings den Rahmen des Artikels sprengen (Bei Interesse zeige ich es vielleicht in einem zukünftigen Artikel).

6 What comes next...

Im nächsten Artikel unserer Serie wird GPC uns SCons näher bringen :)

7 Anhang:

[1] Make ist ebenfalls ein Build-Tool, mehr Informationen zu Make findet man auf http://de.wikipedia.org/wiki/Make .
[2] CVS ist ein sehr verbreitetes Version-Control-System. Version-Control-Systeme ermöglichen eine nachvollziehbare und merge-konfliktfreie gleichzeitige Entwicklung an Software von mehreren Entwicklern, mehr Informationen zu CVS findet man auf https://www.cvshome.org .
[3] Ant erlaubt es Tasks mittels Java selbst zu schreiben, mehr Informationen findet man im Ant-Manual auf http://ant.apache.org/manual/develop.html#writingowntask .

8 Quellen:

[1] http://ant.apache.org/manual/index.html

Sie können Kommentare zu diesem Artikel im Forum schreiben. (Eine Registrierung ist nicht notwendig.)

Logo-Design: MastaMind Webdesign