In der Einleitung der Ant-Dokumentation heißt es "Makefiles are inherently evil" (Makefiles sind von Grund auf böse)[1]. Die unsichtbaren Tabs als bedeutungsvolle Zeichen zu benutzen führe nämlich zu schwer auffindbaren Fehlern. Der Autor dieses Beitrags ist hier anderer Meinung: Jedes strukturierte Dokument hat seine Konventionen, bei Python ist es die Einrückung, bei Makefiles sind es eben Tabs und Doppelpunkte und bei XML-Dateien die spitzen Klammern.
Der große Vorteil von Ant ist aber, dass es unabhängig vom Betriebssystem und recht leicht zu erweitern ist. Bei Makefiles gibt es genau einen Weg, um mehr Funktionalität hinzuzufügen: Man ruft ein Programm oder Shellskript auf. Auch Ant kann externe Programme ausführen, aber damit beginnt wieder die Abhängigkeit vom Betriebssystem. Besser ist es, eine eigene Task (in Ant definierter Arbeitsschritt) zu schreiben oder sich die Funktionalität aus vorhandenen Tasks zusammenzubasteln.
Dieser Coffee-Shop konzentriert sich auf die zweite Lösung. Er setzt grundlegende Kenntnisse von Ant voraus, siehe dazu den einführenden Artikel in einem früheren Heft[2]. Entwickelt wurde unter Ant 1.6.0, ältere Versionen unterstützen nicht alle Möglichkeiten. Der gesamte Code ist auf dem Listing-Server des Linux-Magazins verfügbar[3].
Versionsnummern verwalten
Die Beispiel-Task soll die Programmversion eines Java-Projekts pflegen. Listing 1 zeigt einen Ausschnitt aus dessen Buildfile. In Zeile 14 lädt es eine Eigenschaft (Property) aus der Datei »version.properties«, die sehr einfach aufgebaut ist:
app.version = 0.4.2
Die Version setzt sich aus Major-Nummer, Minor-Nummer und Patch-Level zusammen und wird in weiteren Tasks verwendet, etwa bei der Paketierung (hier landen die Dateien wie bei vielen Projekten üblich in einem Unterverzeichnis » AppName-Version«). Aufgabe der Task »VersionManager« ist es, die einzelnen Komponenten hochzuzählen. Dabei soll sie untergeordnete Komponenten auf »0« setzen, wenn sie eine übergeordnete Komponente erhöht. Listing 2 zeigt Beispiele für den Aufruf des »VersionManager«.
Die Task hat zwei Attribute: das optionale »file« mit dem Defaultwert »version.properties« sowie das Attribut »inc« mit den möglichen Werten »major« (Zeile 77), »minor« (Zeile 73) und »patchlevel« (Zeile 69).
Listing 1: »build.xml«
|
001 <?xml version="1.0" encoding="ISO-8859-1"?>
008
009 <project name="VersionManager" default="compile" basedir=".">
010
011 <!-- ====== Property Definitions ====== -->
012
013 <property file="build.properties"/>
014 <property file="version.properties"/>
015 <property file="${user.home}/build.properties"/>
016
017 <property name="app.name" value="version-manager"/>
|
Listing 2: Aufrufbeispiele »VersionManager«
|
062 <target name="deftask" description="Define task VersionManager" depends="jar">
063 <taskdef name="VersionManager"
064 classname="VersionManager"
065 classpath="${app.lib}"/>
066 </target>
067
068 <target name="inc-pl" description="Increment patch-level" depends="deftask">
069 <VersionManager file="${version.file}" inc="patchlevel"/>
070 </target>
071
072 <target name="inc-minor" description="Increment minor-level" depends="deftask">
073 <VersionManager inc="minor"/>
074 </target>
075
076 <target name="inc-major" description="Increment major-level" depends="deftask">
077 <VersionManager inc="major"/>
078 </target>
079
080 <target name="inc-fail" description="Test invalid inc-type" depends="deftask">
081 <VersionManager inc="foo"/>
082 </target>
|
Strukturiert durch Tasks
Jede Task wird durch eine Java-Klasse implementiert. Das Buildfile legt die jeweilige Klasse für eigene Tasks fest, siehe Zeilen 63 bis 65 in Listing 2. Die Klasse erweitert typischerweise »org.apache.tools.ant.Task«. Eventuell ist eine andere Basisklasse sinnvoller, zum Beispiel »org.apache.tools.ant.taskdefs.MatchingTask«, falls die Task Dateien über »exclude«- oder »include«-Patterns verarbeitet.
Für jedes Attribut ist eine Setter-Methode notwendig, im Beispiel also »setFile« und »setInc«, in Listing 3 die Zeilen 75 und 85. Der Typ dieser Methoden ist »public void«. Je nach Argumenttyp sorgt Ant für eine entsprechende Parameterumwandlung, etwa nach Boolean oder im Beispiel zum Typ »java.io.File«. Die Methode »void execute()« ab Zeile 109 implementiert dann die eigentliche Arbeit der Task.
Listing 3: »VersionManager.java«
|
022 import java.io.*;
023 import java.util.*;
024 import org.apache.tools.ant.*;
025
040 public class VersionManager extends Task {
041
046 private File iVersionFile = new File("version.properties");
052 private String iInc;
059 private final String TYPE_MAJOR = "major",
060 TYPE_MINOR = "minor",
061 TYPE_PL = "patchlevel";
067 private int iMajorVersion, iMinorVersion, iPatchLevel;
068
075 public void setFile(File versionFile) {
076 iVersionFile = versionFile;
077 }
078
085 public void setInc(String inc) {
086 iInc = inc;
087 validateInc();
088 }
089
096 private void validateInc() {
097 if (! iInc.equals(TYPE_MAJOR) &&
098 ! iInc.equals(TYPE_MINOR) &&
099 ! iInc.equals(TYPE_PL))
100 throw new BuildException("Invalid value of inc: " + iInc);
101 }
102
109 public void execute() {
110 try {
111 readFile();
112 if (iInc.equals(TYPE_MAJOR)) {
113 log("incrementing major-version");
114 iMajorVersion += 1;
115 iMinorVersion = 0;
116 iPatchLevel = 0;
117 } else if (iInc.equals(TYPE_MINOR)) {
118 log("incrementing minor-version");
119 iMinorVersion += 1;
120 iPatchLevel = 0;
121 } else {
122 log("incrementing patch-level");
123 iPatchLevel += 1;
124 }
125 writeFile();
126 log("new version is: " +
127 iMajorVersion + "." + iMinorVersion + "." +
128 iPatchLevel);
129 } catch (IOException e) {
130 throw new BuildException("Failed with IOException: " + e.toString());
131 }
132 }
182 }
|
« Zurück
1
2
3
4
Weiter »