Java Getriebe

Java und NetBeans

Extension Points in Ant

Ant ist das Buildsystem für die NetBeans Platform. Es ist zwar möglich eine Anwendung auf Basis der NetBeans Rich Client Platform (RCP) mit Hilfe von Maven oder Ant zu erstellen, aber für diese Basis selbst ist Ant notwendig. Es finden aktuell zwar auch einige Diskussionen dazu statt das Buildsystem von NetBeans zu wechseln, aber selbst mit dem aktuellen System wären einige Optimierungen möglich. Insbesondere eine Feature von Ant ist derzeit noch komplett ungenutzt: Extension Points.

Seit der Version 1.8 unterstützt Ant neben den bekannten <target/> auch eine alternative <extension-point/>. In den von NetBeans erstellten Ant-basierten JavaSE Projekten war mehr oder weniger ein ähnliches Konzept bereits implementiert worden: Man erstellt ein leeres Target mit zum Beispiel dem Namen „-pre-compile“ und trägt dieses als Abhängigkeit in „compile“ ein. Im „Hauptskript“ kann man nun ein eigenes Target mit dem Namen „-pre-compile“ erstellen und dieses wird automatisch vor jedem Kompilierungsvorgang ausgeführt. Das vorher im generierten Skript erstellt Leere Target ist quasi auch ein „Extension Point“. Allerdings sind die <extension-point/> noch etwas flexibler und bieten mehr Möglichkeiten.

Leider sind diese vordefinierten Punkte (bisher) im Buildsystem einer NetBeans Platform-Anwendung nicht verfügbar. Möchte man hier das vorgegebene System ergänzen muss das eigentliche Target überschrieben und über die Dependency-Liste explizit noch einmal aufgerufen werden.

common-build.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project name="common-functions">
    <target name="clean">
        <!-- some tasks -->
    </target>
    <target name="compile">
        <!-- some tasks -->
    </target>
    <target name="jar" depends="compile">
        <!-- some tasks -->
    </target>
</project>

build.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project name="projectbuild" default="jar" basedir=".">
    <import file="common-build.xml"/>
    <target name="jar" depends="common-functions.jar,post-jar-tasks"/>
    <target name="post-jar-tasks">
        <!-- some post jar actions -->
    </target>
</project>

Gerade bei größeren Projekten kann dies schon mal etwas unübersichtlich werden und es ist in einer so Modularen Umgebung wie einer NetBeans RCP Anwendung auch schnell fehleranfällig. So sind in unseren internen Erweiterungen zum Buildsystem diverse zusätzliche Ant-Skripte im Einsatz, die mal verwendet werden müssen, mal nicht. Je nach Modul auch schon mal verschiedene. Jedes dieser Skripte benötigt aber für verschiedene Targets einen eigenen (ergänzenden) Eintrag in die build.xml des Moduls. Und an genau dieser Stelle sind die Ant <extension-point/> dem System mit dem „überschreiben“ der Targets überlegen.

Auf dem ersten Blick wird das allgemeine Skript etwas unübersichtlicher, da mehrere Einträge hinzukommen:
common-build-ext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project name="common-functions-ext">
    <target name="clean">
        <!-- some tasks -->
    </target>
    <target name="compile">
        <!-- some tasks -->
    </target>
    <extension-point name="-pre-jar" depends="compile"/>
    <extension-point name="-post-jar" depends="-pre-jar,-do-jar"/>
    <target name="jar" depends="-post-jar"/>
    <target name="-do-jar" depends="compile">
        <!-- some tasks -->
    </target>
</project>

Der Vorteil kommt aber im Haupt-Skript zum Tragen. Hier muss jetzt nicht mehr das eigentliche Target überschrieben werden, sondern es kann ein (oder auch mehrere!) Targets erstellt werden, die als „extensionOf“ markiert werden. Alle so definierten Erweiterungen werden automatisch ausgeführt wenn eigentlich der <extension-point/> in der Aufrufliste von Ant dran wäre:

<?xml version="1.0" encoding="UTF-8"?>
<project name="projectbuild" default="jar" basedir=".">
    <import file="common-build-ext.xml"/>
    <target name="release" extensionOf="-post-jar" depends="post-jar-tasks">
        <!-- some release actions -->
    </target>
    <target name="post-jar-tasks" extensionOf="-post-jar">
        <!-- some post jar actions -->
    </target>
</project>

Da die Reihenfolge der Extensions explizit nicht definiert ist, müssen die entsprechenden Targets durch die eigene Abhängigkeitsliste in eine bestimmte Reihenfolge gebracht werden, wenn dies notwendig erscheint. In diesem Beispiel wäre die Aufrufliste der Targets beim Erstellen der Jar folgende:

compile -> -pre-jar -> -do-jar -> -post-jar -> post-jar-tasks -> release -> jar

Ein weiterer Vorteil der Extension Points in Ant ist, dass diese nicht nur im Hauptskript eingetragen werden können, wie es beim „Überschreiben“ der Targets notwendig ist, sondern dass diese auch in einem eingebundenen Skript stehen können. Es ist dann nicht mehr notwendig die Anhängigkeitslisten in den build.xml der einzelnen Module zu pflegen, sondern es genügt die gewünschte Funktion per <import/> einzubinden. Keine weitere Änderung im Skript oder ein Überschreiben bestehender Targets ist nötig.

Zum Teil nutzen wir diesen Mechanismus bereits für unsere WZL Gear Toolbox, aber mein Ziel ist es auch für das „Harness“ von NetBeans selbst diese Erweiterungsmöglichkeit hinzuzufügen.