Einige Jahre lang habe ich Ant genutzt, um die Übersetzung und Installation meiner Software-Projekte zu automatisieren. Doch nach und nach wurden diese Projekte auf andere Buildverfahren migriert, erst auf Maven und jetzt wird für die aktuellen Projekte in der Regel Gradle verwendet. Nachdem ich nach einer Abstinenz von etwa sechs Jahren wieder einmal mit Ant arbeiten zu musste zeigte sich, dass Ant immer noch in der Lage ist, in Funktionsumfang und Leistungsfähigkeit den Konkurrenten Parole zu bieten.
Im August 2000 hat mich ein Projekteinsatz nach Montpellier geführt, um dort den Prototypen einer größeren J2EE Applikation ausgiebig zu testen. Stefan Tilkov, der ebenfalls dort war, hat mich auf ein neues Werkzeug hingewiesen, dessen erste Version damals gerade eben freigegeben wurde: Apache Ant 1.1. Für diesen Testaufbau hatte wir bis zu diesem Zeitpunkt make
verwendet. Damit hatte ich in den Jahren zuvor viele gute Erfahrungen gesammelt, doch für die Übersetzung unserer Java-Projekte war es dann doch nicht das richtige Werkzeug. Ant lieferte eine Antwort auf viele Probleme, die wir damals hatten, und so wurde das Buildsystem für diesen Test dann kurzerhand umgestellt.
Ant verwendet eine XML-Datei namens build.xml
, um die Projektkonfiguration und die Transformationsregeln zu beschreiben. Die einzelnen Schritte werden dabei in Form von target
Elementen adressiert, die untereinander in Beziehung stehen und bei Bedarf, in definierter Reihenfolge ausgeführt werden. Innerhalb der target
Elemente stehen die einzelnen Tasks, die sequentiell abgearbeitet werden.
<?xml version="1.0"?>
<project name="Beispiel" basedir="." default="build">
<target name="init">
<echo>Alles klar, keine Initialisierung notwendig.</echo>
</target>
<target name="build" depends="init">
<echo>Hallo Welt.</echo>
<target name="build">
</project>
In den nachfolgenden Jahren habe ich immer weitere Projekte mit Ant automatisiert. Aber mit jedem Projekt stiegen unsere Anforderungen, welche Funktionen durch Ant zu automatisieren seien uns so wuchs der Umfang und die Komplexität der build.xml
Dateien immer weiter an. Abhängig von den Projekte waren damals build.xml
Dateien von mehr als 1000 Zeilen durchaus nicht ungewöhnlich.
Diese Größe und Komplexität hatte aber auch seinen Preis: nur wenige Kollegen konnten diese Dateien überhaupt noch warten und auch die bestehenden Builds tendierten dazu, bei kleinsten Veränderungen des Umfelds zu brechen.
Wenn wir dann ein neues Projekt aufgesetzt haben, wurden einzelne Build-Regeln aus bestehenden build.xml
Dateien zusammenkopiert. Selbst das einfache Kopieren war erstaunlich Zeitaufwändig und das Ergebnis fast immer schlechter als die vorhandenen Vorlagen.
Für mich war das dann einer der Gründe, nach Alternativen Ausschau zu halten. Letztlich wurden dann auch nach und nach die Projekte auf Maven und Gradle migriert.
In den letzten Monaten ergab sich nun projektbedingt die Notwendigkeit, nach einer Abstinenz von etwa sechs Jahren wieder einmal mit Ant arbeiten zu müssen. In der Anfangsphase des Projekts habe ich den Einsatz von Gradle favorisiert, doch es gab auch gute Gründe1, für die vielen bereits existierenden Projekte weiterhin Ant zu verwenden.
Der erste Kontakt mi Ant nach dieser Zeit war dann auch erst einmal mühsamer als von mir im Vorfeld erwartet. Zu viele Details waren in der Zwischenzeit in Vergessenheit geraten. Ich schäme mich daher auch nicht, dass ich dann in einer nächtlichen Aktion die mir vorliegende Bücher zu diesem Thema noch einmal quergelesen zu haben.
Mit meinen Kollegen stand ich vor einem wirklich nicht-trivialen Problem und wollte dennoch ein Ergebnis erstellen, dass diese bekannten Defizite nicht aufweist. Schnell haben wir uns dann auf ein tragfähiges Konzept geeinigt…
Teile und herrsche
Kernstück unserer Idee war, dass wir in jedem Projekt eine minimale build.xml
Datei hinterlegen. In dieser Datei sind nur die wirklich grundlegenden für das Projekt relevanten Einstellungen hinterlegt. Im Wesentlichen entspricht dieses Buildfile dem pom.xml
, auch hier wird rein deklarativ festgehalten, welcher Natur das jeweilige Projekt ist. Alles weitere findet sich dann an anderer Stelle.
Nach unserem Konzept sieht eine projektspezifische build.xml
Datei daher etwa so aus:
<?xml version="1.0"?>
<project name="project" basedir="." default="build">
<!-- predefined setting -->
<property name="project.name" value="SampleProject" />
<property name="project.groupId" value="de.reswi" />
<property name="project.version" value="1.0" />
<property name="project.nature" value="JAR" />
<property file="local.properties" />
<property file="default.properties" />
<!-- settings, local overload possible -->
<property name="build.root" value="ant-build" />
<import file="${build.root}/build_root.xml" />
</project>
Eine Reihe von properties
definieren die grundsätzlichen Attribute des Projekts. Hier wird neben dem Projektnamen auch eine groupId
definiert — ich schätze den Einsatz eines Nexus
als Artefakt Repository.
Die “Magie” startet jedoch mit dem Einlesen der Dateien local.properties
und default.properties
.
Die default.properties
Datei wird nur aus Gründen der Bequemlichkeit unterstützt. Das unkomplizierte Format dieser Dateien mach die Pflege der Einstellungen einfacher und schneller. Da hier weitere wichtige Informationen zum Projekt hinterlegt sind, wird diese Datei selbstverständlich über das SCM System 2 gepflegt.
Zusätzlich gibt es dann noch eine optionale Datei namens local.properties
in die Entwickler ihre jeweiligen lokalen Einstellungen hinterlegen können. Bei meinem Arbeitgeber ist es eine seit Jahren etablierte Konvention, dass es parallel zur defailts.properties
stets diese local.properties
Datei gibt, in der beispielsweise die lokalen Pfade, URLs und Connection Informatioen der Testsysteme hinterlegt werden können. Die Einstellungen in der defaults.properties
sind jedoch so ausgelegt, dass als Ergebnis des Builds die Projektartefakte so konfiguriert sind, dass sie in die Zielinformationen installiert werden können.
Da die local.properties
Dateien immer benutzerspezifisch sind, wird durch einen Eintrag in der .gitignore
Datei sichergestellt, dass diese Einstellungen nicht in das SCM System übernommen werden.
Der wichtigste Punkt jedoch ist, dass am Ende die eigentlichen Verarbeitungsregeln über den <import>
Task in das Buildfile eingefügt werden. So haben wir erreicht, dass wir ein einheitliches Buildsystem über alle unterschiedlichen Projekte hinweg bereitstellen können.
Gelesen wird das Buildsystem über den Pfadnamen, der im build.root
abgelegt ist. Im Projekt haben wir das Buildsystem über ein Netzwerklaufwerk bereitgestellt, da eigentlich alle Entwickler dieses zentrale Laufwerk verwenden. Für meine eigenen, privaten Projekte sieht das etwas anders aus. Die Entwicklung findet in der Regel auf Notebooks statt, die nur unter Schwierigkeiten eine zentrale Ressource ansprechen können. Daher setze ich hier jetzt einfach ein Git submodule
name ant-build
ein.
Beide Lösungen haben unterschiedliche Vor- und Nachteile:
-
Die zentrale Ablage stellt sicher, dass alle Projekte automatische die freigegebene Version des Buildsystems verwenden. Ergeben sich aber mit einer neuen Version inkompatible Änderungen, können betroffene Projekte nicht mehr gebaut werden. Diese müssen durch einen Entwickler dann erst einmal wieder bearbeitet werden.
-
Über das Git Submodule nutzen die Projekte einen klar definierten Stand des Buildsystems und sind daher auch nicht betroffen, wenn eine inkompatible Erweiterung freigegeben wird. Sie profitieren allerdings auch nicht automatisch von diesen Erweiterungen oder durchgeführten Fehlerkorrekturen. Erscheint eine neue Version des Buildsystems, müssen daher alle Projekte dezidiert auf diese Version umgestellt werden.
Ich habe mich für die zweite Version entschieden, da die erste Variante ein Netzlaufwerk erfordert, das mir eben nicht immer zur Verfügung steht. Allerdings habe ich in meinem Jenkins Server einen Job eingetragen, der diese Aktualisierung automatisch durchführt. Über die zentralen Git Server erhalte ich dann so automatisch mit dem nächsten git pull
eine neue Version.
Hinweis
Mit diesen wenigen Sätzen ist der projektspezifische Teil des Buildsystems nun eigentlich schon vollständig abgehandelt. Dazu gibt es natürlich noch die build_root.xml
Datei, in der noch ein wenig “Gehirnschmalz” steckt. Für die nächsten Wochen habe ich mir vorgenommen, die “guten Teile” des Ant-Builds hier nach und nach dann noch einmal zu durchdenken und zu dokumentieren.
Ich werde ein Buildsystem und ein entsprechend konfiguriertes Projekt in den nächsten Tagen bei Bitbucket und Github einstellen. Bei diesen Quellen handelt es sich dabei um eine Zusammenfassung der Beispiele, die ich schrittweise entwickeln möchte, es wird keine Kopie eines anderenorts genutzten System sein und im Wesentlichen einen rein illustrierenden Charakter haben. Wer also darauf hofft einen Einblick in ein produktives Buildverfahren zu erhalten, sei damit schon einmal enttäuscht.
Ich habe auch keine konkrete Planung für die Termin, zu denen ich mich noch einmal den Themen zuwenden möchte. Allerdings habe ich mir für dieses Jahr vorgenommen, jede Woche einen Beitrag an dieser Stelle einzustellen. Der Aufbau eines Buildsystems mit Ant wird dabei sicherlich einen breiteren Raum einnehmen. Doch es gibt auch immer wieder andere Punkte, die ich hier für mich notieren möchte.
In dieser Artikelserie über den Ant sind weitere Artikel erschienen:
-
Eine ersten Artikel mit ersten Gedanken zu einem neuen projekt-übergreifendem Buildsystem
-
Definition eines einheitlichen und projekt-unabhähgigen Ant Lifecycle, Beschreibung von Erweiterungspunkten und Erweiterungen.
-
Für alle Projekte muss man sich auch mindestens folgende Frage stellen: “Werden die angestrebten Änderungen von den Beteiligten akzeptiert?” und “Sind die Kunden in der Lage, mit den Veränderungen umzugehen?”. Können diese Fragen nicht mit Sicherheit positiv beantwortet werden, ist eine technisch bessere Lösung zu diesem Zeitpunkt dann eben nicht angeraten. ↩
-
Privat verwende ich dazu natürlich Git. Aber auch im letzten Projekt wurde dieses SCM System verwendet, um die Projektquellen und -konfigurationen zu verwalten. ↩