Open Source im professionellen Einsatz

Eigene Ant-Erweiterungen schreiben

Kleiner Helfer, ganz groß

Ant ist das Make für Java-Projekte, kann aber viel mehr: Wer Sonderwünsche hat, kann Ant beliebig erweitern und neue Tasks mit eigenen Java-Klassen verbinden. Wie das geht, zeigt dieser Coffee-Shop. Bernhard Bablok

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 }

Diesen Artikel als PDF kaufen

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook