Versionsnamen mit Maven: Erzeugen des Versionsnamens

In vielen Fällen kann es hilfreich sein, einer Anwendung ihre aktuelle Versionsnummer entnehmen zu können: Man sieht sofort, welche Version auf welcher Stage deployt ist, die Versionsangabe kann Missverständnisse in Fehlerberichten vermeiden, etc.

Während der Entwicklung hilft es oftmals noch mehr, nicht nur die Versionsnummer, sondern auch weitere Informationen, wie Build-Nummer, Zeitstempel, Branch und Commit ID, in die Anwendung einzufügen.
Dieser Artikel zeigt wie dies völlig automatisch mit Maven erledigt werden kann.
Wie das Auslesen in verschiedenen Arten von Anwendungen funktioniert zeigt der nächste Artikel in dieser Serie – „Versionsnamen mit Maven: Auslesen des Versionsnamens“, der in Kürze hier im Blog erscheinen wird.

Grundlagen

Im Folgenden wird der Begriff „Versionsname“ verwendet, um die mit weiteren Informationen angereicherte Versionsnummer zu bezeichnen.
Der generelle Mechanismus, um den Versionsnamen in die Anwendung zu schreiben, sieht wie folgt aus: Es wird das Maven Property versionName erzeugt, das mittels Resource Filtering in Dateien der Anwendung geschrieben wird. In diesem Artikel wird dies für eine Properties-Datei, eine HTML-Datei und die Manifest-Datei gezeigt.
Der einfachste Versionsname besteht aus der Maven Version, die im <version> Tag spezifiziert wird:

<version>1.0-SNAPSHOT</version>
<properties>
    <!-- Printable version name -->
    <versionName>${project.version}</versionName>
</properties>

Das Property versionName enthält so während des Builds beispielsweise folgenden Wert:
1.0-SNAPSHOT.
Im Folgenden wird dieser Versionsname um Zeitstempel, SCM-Informationen (Branch und Commit), Build-Nummer und einen speziellen Namen für Releases erweitert.

Versionsnamen in eine Properties-Datei schreiben

Zunächst muss der Versionsname mittels Maven Resource Filtering in Dateien geschrieben werden. Von dort kann er von der Anwendung gelesen werden. Ein universell einsetzbares Vorgehen ist es, den Versionsnamen in eine Properties-Datei zu schreiben. Beispielsweise legt man eine Datei src/main/resources/app.properties an, die das oben definierte Property versionName enthält:

versionName=${versionName}

Der Platzhalter wird während des Maven Builds wie folgt ersetzt:

<build>
    <resources>
        <resource>
            <!-- Filter for version name in properties -->
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
            <includes>
                <include>app.properties</include>
            </includes>
        </resource>
    </resources>
</build>

Die Properties-Datei, die während des Builds im Maven target Verzeichnis abgelegt wird, sieht dann wie folgt aus:

versionName=1.0-SNAPSHOT

Dies funktioniert sowohl für Maven Projekte die als JAR-Datei gepackt sind, als auch für solche, die als WAR-Datei gepackt sind.

Versionsnamen in eine HTML-Datei schreiben

In Webanwendungen kann man es sich noch einfacher machen. Man schreibt den Versionsnamen direkt in eine HTML-Datei, die an den Client ausgeliefert wird. Beispielsweise legt man eine Datei src/main/webapp/index.html an, die das oben definierte Property versionName enthält:

<html>
  <body>
    <h2>${versionName}</h2>
  </body>
</html>

Der Platzhalter wird während des Maven Builds mittels Resource Filtering des maven-war-plugins wie folgt ersetzt:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.0.0</version>
            <configuration>
                <!-- Filter for version name in html-->
                <webResources>
                    <resource>
                        <directory>src/main/webapp</directory>
                        <filtering>true</filtering>
                        <includes>
                            <include>index.html</include>
                        </includes>
                    </resource>
                </webResources>
            </configuration>
        </plugin>
    </plugins>
</build>

Die HTML-Datei, die während des Builds im Maven target Verzeichnis abgelgt wird, sieht dann wie folgt aus:

<html>
  <body>
    <h2>1.0-SNAPSHOT</h2>
  </body>
</html>

Versionsnamen ins Manifest schreiben

Wenn man eine JAR-Datei ausliefert hat man eine weitere Möglichkeit: Das Schreiben des Versionsnamens in die Manifest-Datei. Dies hat den Vorteil, dass keine weitere Properties-Datei angelegt werden muss. Dies kann entweder mittels maven-jar-plugin oder maven-assembly-plugin erfolgen, wobei der <configuration> Tag jeweils gleich aussieht. Das folgende Beispiel zeigt, dies anhand des maven-assembly-plugins:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.6</version>
            <configuration>
                <archive>
                    <!-- Add version name to manifest -->
                    <manifestEntries>
                        <versionName>${versionName}</versionName>
                    </manifestEntries>
                    <manifest>
                        <mainClass>${mainClass}</mainClass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

Die Manifest-Datei, die während des Builds innerhalb der JAR-Datei unter META-INF/MANIFEST.MF erstellt wird, sieht dann beispielsweise wie folgt aus:

Manifest-Version: 1.0 
Archiver-Version: Plexus Archiver 
Built-By: jschnatterer 
versionName: 1.0-SNAPSHOT 
Created-By: Apache Maven 3.3.9 
Build-Jdk: 1.8.0_101 
Main-Class: de.triology.versionname.App

Versionsname um Zeitstempel erweitern

Im Folgenden wird gezeigt, wie sich der Versiosname sukzessive um weitere Informationen erweitern lässt. Ein Zeitstempel kann mit Maven Bordmitteln hinzugefügt werden. Dazu definiert man zunächst das maven.build.timestamp.format Property und verwendet dann das maven.build.timestamp Property im versionName Property:

<properties>
    <!-- Printable version name -->
    <maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
    <versionName>${project.version} (${maven.build.timestamp})</versionName>
</properties>

Das Property versionName enthält so während des Builds beispielsweise folgenden Wert:
1.0-SNAPSHOT (2016-09-26 09:07).

Versionsnamen um SCM-Informationen erweitern

Möchte man zusätzliche Informationen aus dem Source Code Management (SCM) anzeigen, bietet sich die Verwendung des buildnumber-maven-plugin an. Es arbeitet mit den SCMs subversion, git, mercurial und perforce. Nach der Ausführung des Goals create (standardmäßig in der initialize Phase des Maven Build Zyklus) stehen die Properties buildnumber und scmBranch zur Verfügung. Das folgende Beispiel zeigt wie dieses für ein Multi-Module-Projekt unter Verwendung von Git aussehen kann:

<build>
    <plugins>
        <!-- Write the current git revision into ${buildnumber} and populate ${scmBranch} -->
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>buildnumber-maven-plugin</artifactId>
            <version>1.4</version>
            <executions>
                <execution>
                    <goals>
                        <goal>create</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <!-- Get the scm revision once for all modules -->
                <getRevisionOnlyOnce>true</getRevisionOnlyOnce>
                <!-- Don't fail on modified local resources -->
                <doCheck>false</doCheck>
                <!-- Don't update SCM -->
                <doUpdate>false</doUpdate>
                <!-- Use short version of git revision -->
                <shortRevisionLength>7</shortRevisionLength>
            </configuration>
        </plugin>
    </plugins>
</build>

Im Versionsnamen kann man dann die Properties, die durch das Plugin bereitgestellt werden, verwenden:

<properties>
    <!-- Printable version name -->
    <versionName>${project.version} (${maven.build.timestamp}, branch ${scmBranch}, commit ${buildNumber})</versionName>
</properties>

Das Property versionName enthält so während des Builds beispielsweise folgenden Wert:
1.0-SNAPSHOT (2016-09-26 09:07, branch master, commit e2f18bb).

Versionsnamen um Build-Nummer erweitern

Bei Verwendung eines Continuous Ingetration (CI) Tools wie Jenkins kann zudem die Build-Nummer in den Versionsnamen einfließen. Dadurch können dann lokale Entwickler-Builds von CI-Builds unterschieden werden. Um in Maven den CI-Build von anderen zu unterscheiden, bietet sich die Verwendung eines Maven Profiles an. Jenkins injiziert die aktuelle Build-Nummer des Jobs in die Umgebungsvariable BUILD_NUMBER. Diese kann sowohl als zur Aktivierung des Profils, als auch in der Build-Nummer selbst verwendet werden. Das folgende Snippet führt dazu, dass auf Jenkins ohne weitere Konfiguration zusätzlich die Build-Nummer in den Versionsnamen aufgenommen wird:

<profiles>
    <profile>
        <!-- Profile that extends the printable version number by an optional build.
            It is activated when an environment variable called BUILD_NUMBER exists (as in Jenkins) -->
        <id>versionNameBuildNumber</id>
        <activation>
            <property>
                <name>env.BUILD_NUMBER</name>
            </property>
        </activation>
        <properties>
            <versionName>${project.version} build #${env.BUILD_NUMBER} (${maven.build.timestamp}, branch ${scmBranch}, commit ${buildNumber})
            </versionName>
        </properties>
    </profile>
</profiles>

Das Property versionName enthält während des Builds beispielsweise folgenden Wert:
1.0-SNAPSHOT build #17 (2016-09-27T07:55:43Z, branch master, commit 4dd3cf5)

Bei lokalen Builds bleibt der im vorherigen Absatz beschriebene Versionsname bestehen.

Speziellen Versionsnamen für Release erzeugen

Mit den oben gezeigten Erweiterungen ist der Versionsname nun sehr lang und enthält Informationen, die primär für Entwickler von Interesse sind. Tatsächlich ist die Versionsnummer einer Anwendung für sich in Produktion schon ein eindeutiges Merkmal. Insofern sind diese zusätzlichen Informationen beim Release nicht unbedingt notwendig. Wenn man also zwischen Entwicklungs- und Release-Versionen unterscheiden möchte, lässt sich dies ebenfalls mittels Maven Profiles realisieren. Das maven-release-plugin setzt beispielsweise das Attribut performRelease, während das Release erstellt wird (Goal release:perform). Dieses bietet sich an dieser Stelle als Best Practice zur Aktivierung des Profils an:

<profiles>
    <profile>
        <!-- For releases, use a simple version name, consisting of the version number.
             Don't add timestamp, SCM info, build number, etc. -->
        <id>versionNameForRelease</id>
        <activation>
            <property>
                <name>performRelease</name>
            </property>
        </activation>
        <properties>
            <versionName>${project.version}</versionName>
        </properties>
    </profile>
</profiles>

Wenn man das Plugin nicht verwenden möchte, kann man das Attribut beim Release auch manuell setzen: mvn <goals> -DperformRelease

Das Property versionName enthält so während des Builds beispielsweise folgenden Wert:
1.0-SNAPSHOT

Fazit und Ausblick

Dieser Artikel zeigt wie man einen Versionsnamen mit Maven in verschiedene Datei-Typen schreibt, von wo aus er in der Anwendung verwendet werden kann. Darauf aufbauend werden Möglichkeiten aufgezeigt, wie der Versionsname mit bei der Entwicklung nützlichen Informationen angereichert werden kann. Abschließend zeigt der Artikel, wie während der Release-Erstellung wieder die simple Versionsnummer als Versionsname verwendet werden kann.

Die gezeigten Beispiele sind in einem ausführbaren Beispiel bei GitHub zusammengefasst. Darin ist auch bereits das programmatische Auslesen des Versionsnamen enthalten. Dies wird im Detail im nächsten Artikel dieser Serie beschrieben – „Versionsnamen mit Maven: Auslesen des Versionsnamens“.

Diesen Beitrag teilen

Johannes Schnatterer
Solution Architect
Mit besonderem Fokus auf Qualität, Open Source Enthusiasmus, einem Hauch von Pedantismus und der Pfadfinderregel im Gepäck versucht er, die IT-Welt jeden Tag ein winziges Bisschen besser zu machen.