Kürzere Build-Konfiguration mit Polyglot for Maven

Maven Tycho ohne pom.xml

Jan Sievers

©iStockphoto.com/270770

Bis vor Kurzem erforderten Builds mit Maven zwingend eine pom.xml-Datei. Für Tycho als Maven-Plug-in bedeutete dies für jedes einzelne Eclipse-Plug-in und Feature eine zusätzliche Datei mit wenig Mehrwert. Wer wissen möchte, wie man mittels Polyglot for Maven pom.xml für Tycho Builds los wird, oder wer sich schon immer über die sprichwörtlichen „fünf Kilometer pom.xml“ aufgeregt hat, der sollte hier weiterlesen.

Ziel des Tycho-Projekts ist es, Eclipse-Artefakte wie Plug-ins, Features und Update Sites/p2 Repositories mit Maven bauen zu können. Sowohl Maven als auch Eclipse waren zu Beginn des Tycho-Projekts längst etablierte Projekte und brachten jeweils eigene Modelle für im Build wichtige Begriffe wie Artefakt, Versionierung, Abhängigkeiten zwischen Artefakten, Abhängigkeitsauflösung und Artefakt Repositories mit sich. Tycho bildet die Brücke zwischen der Maven- und Eclipse-Welt. Um zu verstehen, warum die pom.xml-Datei nötig war und was ihren Inhalt bestimmt, schauen wir uns zunächst die Artefaktmodelle von Maven und Eclipse an.

The Maven Way

In Maven werden Artefakte durch sogenannte GAV-Koordinaten global eindeutig identifiziert (GAV = GroupId, ArtifactId, Version). Diese Koordinaten sowie alle sonstigen Build-relevanten Einstellungen müssen in einer pom.xml-Datei (POM = Project Object Model) konfiguriert werden. Ein Maven-Artefakt hat genau ein POM. Auch Abhängigkeiten zu anderen Artefakten werden in pom.xml deklariert. Der Maven Dependency Resolver Aether löst diese Abhängigkeiten zum Zeitpunkt des Builds automatisch auf, sowohl lokal als auch von entfernten Maven-Artefakt-Repositories (Listing 1).

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>my-project</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
  </dependency>
  </dependencies>
</project>

The OSGi Way

Im Gegensatz dazu benutzen Eclipse-Plug-ins ähnliche Konzepte aus OSGi: Ein Eclipse-Plug-in ist ein OSGi Bundle; ein Bundle wird eindeutig durch seinen Bundle-SymbolicName-Header im OSGi Manifest (Datei META-INF/MANIFEST.MF) identifiziert. Der Bundle-Version-Header deklariert die Version. Abhängigkeiten zu anderen Bundles und/oder Java-Paketen werden über Require-Bundle oder Import-Package-Header angegeben (Listing 2).

Bundle-ManifestVersion: 2
Bundle-SymbolicName: org.example.bundle
Bundle-Version: 1.0.0.qualifier
Require-Bundle: org.eclipse.core.runtime
Import-Package: org.junit

Die Abhängigkeiten werden zur Laufzeit vom OSGi Resolver und im Fall von Eclipse zum Installationszeitpunkt von p2 aufgelöst. p2 bietet den Dependency Resolver und das binäre Artefakt-Repository-Format für Tycho, also in etwa äquivalent zu Maven Aether und Maven Remote Artifact Repositories.

The Manifest-first Way

Der Build von Eclipse-Plug-ins folgt dem so genannten Manifest-First-Ansatz. Manifest First bedeutet, dass das Bundle-Manifest die maßgebliche Quelldatei für die eben erwähnten Metadaten wie ArtifactId, Version und Abhängigkeiten ist. Die PDE-Tools zur Entwicklung von Eclipse-Plug-ins (PDE = Plug-in Development Environment) basierten von Anfang an auf diesem Ansatz, sowohl in Eclipse selbst als auch für den Headless Build. Um einen Manifest First Build mit Maven zu ermöglichen, müssen einige Konzepte in Maven durch entsprechende Eclipse-/OSGi-Konzepte ausgetauscht werden:

  • Abhängigkeiten werden nicht mehr in pom.xml deklariert, sondern im OSGi-Manifest.
  • Abhängigkeiten werden nicht mehr von Aether über Maven Remote Artifact Repositories, sondern von p2 über p2 Repositories aufgelöst.

sievers_tab1

Zitat von der Tycho-Homepage: „One important design goal in Tycho is to make sure there is no duplication of metadata between POM and OSGi“. Dieses Ziel wurde für die Abhängigkeiten auch erreicht. Allerdings ist für den Maven Build eine statische pom.xml-Datei mit GAV-Koordinaten nötig, die zu Bundle-SymbolicName und Bundle-Version nach den Mapping-Regeln aus Tabelle 1 passen müssen, also für unser Bundle aus Listing 2 das in Listing 3 gezeigte Beispiel.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.example</groupId>
  <artifactId>org.example.bundle</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>eclipse-plugin</packaging>
</project>

Die Abhängigkeiten tauchen im POM nicht mehr auf, neben der groupId und dem packaging enthält die pom.xml-Datei aber keine neuen Informationen, die nicht schon im OSGi-Manifest vorhanden wären. Im Gegenteil, artifactId und version müssen in pom.xml doppelt gepflegt werden. Zwar werden die Mapping-Regeln aus Tabelle 1 von Tycho zum Build-Zeitpunkt validiert, Änderungen beispielsweise an der Bundle-Version ohne Anpassung der POM-Version sind aber unnötig fehleranfällig.

Sowohl Maven als auch PDE benutzen automatisch inkrementierte Versionen während der Entwicklung, sodass jeder neue Build eine neue (im Vergleich zum vorhergehenden größere) Version erzeugt. Bei beiden wird ein „magisches“ Versionssuffix .qualifier bzw. -SNAPSHOT benutzt, das während des Builds ersetzt wird, typischerweise durch Zeitstempel etwa der Form ‚YYYYMMddHHmm‘. Wir brauchen also noch zusätzlich die Mapping-Regel für Entwicklungsversionen aus Tabelle 2.
sievers_tab2

Zusammengefasst enthält also der typische Tycho Build für jedes Plug-in und Feature eine (fast leere) pom.xml-Datei, die zwar rein formal für Maven erforderlich ist, deren Inhalt aber Informationen aus bereits vorhandenen Dateien dupliziert.

The Polyglot Way

Der mit Maven 3.3.1 eingeführte Core-Extension-Mechanismus erlaubt erstmals andere Serialisierungsformate als XML für das POM, auch bekannt als „Polyglot Maven“. Das POM-Modell blieb dabei unverändert, lediglich die Serialisierung wurde austauschbar gemacht. Dies ermöglicht neue POM-Formate wie zum Beispiel das im Vergleich zu Listing 1 wesentlich kürzere YAML (Listing 4).

modelVersion: 4.0.0
groupId: org.example
artifactId: my-project
version: 1.0.0-SNAPSHOT
dependencies:
- {groupId: junit, artifactId: junit, version: '4.11', scope: test}

Polyglot for Maven bietet auch ein Werkzeug zur Übersetzung der verschiedenen POM-Formate ineinander, das zur Erzeugung von Listing 4 aus Listing 1 benutzt wurde:

mvn io.takari.polyglot:polyglot-translate-plugin:translate
     -Dinput=pom.xml -Doutput=pom.yml

The POM-less Way

Ausgehend von diesem Beispiel ist die Idee vom Tycho Build ohne pom.xml nicht mehr weit: Der Standardfall für ein Tycho POM lässt sich, wie oben erklärt, komplett aus anderen Dateien (MANIFEST.MF, feature.xml) herleiten. Genau das macht nun die tycho-pomless-Erweiterung: Sie registriert ähnlich wie polyglot-yaml eine POM-Deserialisierung, die gemäß den obigen Mapping-Regeln aus MANIFEST.MF- bzw. feature.xml-Dateien ein POM-Modell erzeugen kann.

Im Weiteren benutzen wir das Tycho-Demoprojekt. Solange Tycho 0.24.0 noch als Entwicklungsversion (-SNAPSHOT) vorliegt, bitte auch die Anleitung in den Release Notes beachten. Um das Demoprojekt auf tycho-pomless umzustellen, löschen wir kurzerhand alle pom.xml-Dateien für Plug-ins und Features und fügen im Wurzelverzeichnis eine Datei .mvn/extensions.xml hinzu (Listing 6).

<?xml version="1.0" encoding="UTF-8"?>
<extensions>
  <extension>
    <groupId>org.eclipse.tycho.extras</groupId>
    <artifactId>tycho-pomless</artifactId>
    <version>0.24.0-SNAPSHOT</version>
  </extension>
</extensions>

Wir nutzen hier den neuen Core-Extension-Mechanismus von Maven. Nur so kann man Erweiterungen registrieren, die schon automatisch heruntergeladen und aktiviert werden, noch bevor die erste POM-Datei geparst wird. Dies war zwar im Prinzip auch schon früher möglich, indem von Hand Dateien ins lib/ext/-Verzeichnis der lokalen Maven-Installation kopiert wurden. Dieses Vorgehen war aber höchst unpraktikabel und nicht mit dem ansonsten von Maven erwarteten Verhalten konform, dass ein simples mvn clean install keine weiteren manuellen Vorbereitungen benötigt und alle deklarierten Abhängigkeiten automatisch auflöst und herunterlädt. Wenn tycho-pomless als Core Extension registriert ist, werden also die POMs für Plug-ins und Features jetzt direkt aus MANIFEST.MF bzw. feature.xml hergeleitet. Um das zu prüfen, führen wir mittels mvn clean install einen Test-Build unseres Demoprojekts ohne pom.xml für Plug-ins und Features durch, der genau wie vor der Umstellung verlaufen sollte. Damit dieses Beispiel „einfach so funktioniert“, trifft tycho-pomless noch ein paar weitere Annahmen.

Ganz ohne pom.xml funktioniert auch dieser pomless Build nicht, denn es gibt Build-spezifische Informationen im Parent POM, wie z. B. für die Auflösung externer Abhängigkeiten benötigte p2 Repositories, die nicht aus existierenden Dateien hergeleitet werden können. Der Einfachheit halber nimmt tycho-pomless an, dass der Parent POM sich im Verzeichnis eine Ebene über dem jeweiligen Plug-in oder Feature befindet. Vom Parent wird auch die Maven groupId an Plug-ins und Features vererbt, die sich auch nicht eindeutig aus den anderen Dateien ergeben würde. Schließlich wird der Maven Packaging Type aufgrund der vorgefundenen Datei festgelegt (Tabelle 3).
sievers_tab3

Welches POM-Modell zum Beispiel für das example-bundle erzeugt wurde, lässt sich nach Ausführen von mvn clean install nachvollziehen: In ${user.home}/.m2/repository/example/group/example-bundle/0.1.0-SNAPSHOT/ findet sich weiterhin die XML-Serialisierung des POM example-bundle-0.1.0-SNAPSHOT.pom, obwohl es in Quellform jetzt nicht mehr als pom.xml vorliegt. Für die (seltenen) Fälle, in denen zusätzliche Konfiguration direkt im Plug-in oder Feature POM benötigt wird, kann nach wie vor eine pom.xml-Datei benutzt werden. Falls sie existiert, hat sie höhere Priorität als die von tycho-pomless hergeleitete Form. Man kann hiermit also tycho-pomless bei Bedarf übersteuern.

Polyglot for Maven erlaubt im Prinzip beliebige POM-Dialekte (mehr Beispiele online), und tycho-pomless ist nur einer davon. Um das an unserem Beispiel etwas zu übertreiben (oder für Leute mit genereller Abneigung gegenüber XML), können wir auch die noch verbleibende Parent-pom.xml-Datei mit dem polyglot-translate-plugin beispielsweise nach pom.yml übersetzen. Danach fügen wir in .mvn/extensions.xml neben tycho-pomless die polyglot-yaml-Erweiterung hinzu:

<extension>
  <groupId>io.takari.polyglot</groupId>
  <artifactId>polyglot-yaml</artifactId>
  <version>0.1.11</version>
</extension>

Und mvn clean install liefert nun einen Tycho Build gänzlich ohne pom.xml.

Fazit

Obwohl eine der Errungenschaften von Maven der standardisierte Build ist, gibt es doch Fälle, für die das vorgegebene pom.xml-Format zu unflexibel ist. Mit Polyglot for Maven bleibt zwar das POM-Modell erhalten, das Serialisierungsformat ist jedoch nicht mehr zwingend auf XML festgelegt. Die Maven Core Extension tycho-pomless nutzt diese neuen Freiheiten, um das POM-Modell für Eclipse-Plug-ins und Features stattdessen aus MANIFEST.MF und feature.xml abzuleiten. Für typische Tycho Builds kann pom.xml für Plug-ins und Features damit komplett entfallen. Der standardisierte und in sich abgeschlossene Build bleibt erhalten, die fehleranfällige Doppelpflege von duplizierten Artefaktmetadaten in pom.xml ist jedoch nicht mehr notwendig.

Verwandte Themen:

Geschrieben von
Jan Sievers
Jan Sievers
Jan Sievers arbeitet als Softwarearchitekt bei SAP im Bereich Technology Platform. Er hat fünfzehn Jahre Erfahrung mit Enterprise-Java, insbesondere mit Design Time und Build-Ttools. Jan ist Koleiter des Eclipse-Projekts Tycho. Twitter: @sieversjan
Kommentare

Hinterlasse einen Kommentar

2 Kommentare auf "Maven Tycho ohne pom.xml"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Lothar Wendehals
Gast

Ist es vorgesehen, dass das parent pom in einem anderen Verzeichnis liegen kann als in dem übergeordnetem Verzeichnis? Eine Konfigurationsmöglichkeit in der extensions.xml wäre sehr praktisch.

Jan Sievers
Gast

bisher nicht. Dieses feature wurde von anderen schon unter

https://bugs.eclipse.org/bugs/show_bug.cgi?id=478704

reported.