Aller au contenu
  1. Articles/

Utiliser une propriété unique comme version Maven

·5 mins·

Article publié il y a 9 ans : les outils et les commandes décrits ont pu changer depuis.

Quand je travaille sur un projet Maven multi-modules (plusieurs dizaines) sur plusieurs branches en même temps, je me retrouve souvent avec des conflits à résoudre pour chaque merge sur les versions définies un peu partout dans les pom.xml… Et c’est tout sauf marrant.
Tour d’horizon des solutions pour réduire le nombre de <version> déclarées dans tous ces pom.xml.

Notre projet exemple de départ est donc constitué de deux modules :

.
├── pom.xml
├── submodule-1
│   └── pom.xml
└── submodule-2
    └── pom.xml

Le pom.xml du projet parent ressemble à :

<project>
    <groupId>com.github.jcgay.example.version</groupId>
    <artifactId>parent-pom</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <modules>
        <module>submodule-1</module>
        <module>submodule-2</module>
    </modules>
        
</project>

et ceux des modules :

<project>
    <parent>
        <groupId>com.github.jcgay.example.version</groupId>
        <artifactId>parent-pom</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>submodule-1</artifactId>

</project>

La version du projet est répétée dans chaque pom.xml des modules, pour les changer ce n’est pas bien compliqué avec le plugin Versions. Par contre les merges entre branches sont vite pénibles, les conflits sont fréquents et généralement on ne peut pas se contenter de ne garder les changements que d’une des deux branches 😓.

Définir la version dans une propriété
#

Première tentative, extraire la version dans une propriété Maven. Le pom parent devient donc :

<project>
    <groupId>com.github.jcgay.example.version</groupId>
    <artifactId>parent-pom</artifactId>
    <version>${my-version}</version>
    <packaging>pom</packaging>

    <properties>
        <my-version>1.0-SNAPSHOT</my-version>
    </properties>

    <modules>
        <module>submodule-1</module>
        <module>submodule-2</module>
    </modules>

</project>

et un module utilise également la propriété ${my-version} pour référencer son parent :

<project>
    <parent>
        <groupId>com.github.jcgay.example.version</groupId>
        <artifactId>parent-pom</artifactId>
        <version>${my-version}</version>
    </parent>

    <artifactId>submodule-2</artifactId>

</project>

Etonnement ça fonctionne 😲
La seule arnaque provient des messages d’avertissements affichés par Maven.

Some problems were encountered while building the effective model for com.github.jcgay.example.version:submodule-1:jar:1.0-SNAPSHOT
'version' contains an expression but should be a constant. @ com.github.jcgay.example.version:parent-pom:${my-version}, /Users/jcgay/dev/toolbox-examples/unique-maven-version/pom.xml, line 7, column 14

Some problems were encountered while building the effective model for com.github.jcgay.example.version:submodule-2:jar:1.0-SNAPSHOT
'version' contains an expression but should be a constant. @ com.github.jcgay.example.version:parent-pom:${my-version}, /Users/jcgay/dev/toolbox-examples/unique-maven-version/pom.xml, line 7, column 14

Some problems were encountered while building the effective model for com.github.jcgay.example.version:parent-pom:pom:1.0-SNAPSHOT
'version' contains an expression but should be a constant. @ com.github.jcgay.example.version:parent-pom:${my-version}, /Users/jcgay/dev/toolbox-examples/unique-maven-version/pom.xml, line 7, column 14

It is highly recommended to fix these problems because they threaten the stability of your build.

For this reason, future Maven versions might no longer support building such malformed projects.

En l’état ce n’est pas vraiment sérieux, et puis il y a un autre problème avec cette solution mais on va la détailler dans la seconde.

Continuous delivery friendly version
#

Depuis Maven 3.2.1 il est possible d’utiliser des propriétés réservées pour éviter les messages d’avertissements précédents.

Si on remplace notre propriété my-version de la solution précédente par une autre nommée revision, plus de messages qui font peur 🎆.

Seulement utiliser des propriétés plutôt que des versions stables pose réellement un problème. Quand Maven installe dans le repository local ou déploie dans un repository distant nos artifacts, les pom.xml sont copiés en l’état. Si on a donc un projet qui dépend d’un de nos modules (comprendre qui n’est pas construit dans ce même projet multi-modules), la résolution des dépendances échouera car la propriété ${revision} n’existera pas dans ce projet extérieur.

Exemple avec ce projet tout simple qui n’est pas un module de notre parent-pom :

<project>
    <groupId>com.github.jcgay.example.version</groupId>
    <artifactId>independant-project</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <dependencies>
        <dependency>
            <groupId>com.github.jcgay.example.version</groupId>
            <artifactId>submodule-1</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

Quand on lance un build :

$> mvn test

Scanning for projects...

Using the MultiThreadedBuilder implementation with a thread count of 4

------------------------------------------------------------------------
Building independant-project 1.0-SNAPSHOT
------------------------------------------------------------------------
Downloading: https://repo.maven.apache.org/maven2/com/github/jcgay/example/version/parent-pom/$%7Brevision%7D/parent-pom-$%7Brevision%7D.pom
------------------------------------------------------------------------
BUILD FAILURE
------------------------------------------------------------------------
Total time: 0.882 s (Wall Clock)
Finished at: 2016-08-09T15:26:09+02:00
Final Memory: 12M/193M
------------------------------------------------------------------------
Failed to execute goal on project independant-project: Could not resolve dependencies for project com.github.jcgay.example.version:independant-project:jar:1.0-SNAPSHOT: Failed to collect dependencies at com.github.jcgay.example.version:submodule-1:jar:1.0-SNAPSHOT: Failed to read artifact descriptor for com.github.jcgay.example.version:submodule-1:jar:1.0-SNAPSHOT: Could not find artifact com.github.jcgay.example.version:parent-pom:pom:${revision} in central (https://repo.maven.apache.org/maven2) -> [Help 1]

Et en effet le pom.xml présent dans mon repository local défini toujours ${revision} en tant que <version> du pom-parent.

Filtrer les pom à l’installation/déploiement
#

Ce problème de propriétés non remplacées m’a rappelé un vieil article dans lequel l’auteur s’était amusé à filtrer (dans le sens maven-resources-plugin filtering) ses fichiers pom.xml et les installer à la place des originaux.
J’ai donc recherché s’il était possible de faire de même mais avec une extension Maven (la dose de XML nécessaire et le côté gros hack de la solution décrite m’a découragé…). Et c’est effectivement faisable avec Aether (utilisé dans Maven depuis la version 3) via un org.eclipse.aether.impl.MetadataGenerator. On peut donc écrire un gros hack dans son langage préféré à la place 🤔.

Le code est disponible sur GitHub. Il prend la forme d’une extension Maven qui se charge de transformer les artifacts de type pom à l’installation et au déploiement. Toutes les occurrences de ${revision} sont remplacées par l’actuelle version de l’artifact traité (c’est à dire la valeur résolue de ${revision}). Le problème de résolution de dépendance est dans ce cas résolu !

On peut l’utiliser via le mécanisme d’extensions de Maven en créant (modifiant) le fichier ${maven.multiModuleProjectDirectory}/.mvn/extensions.xml :

<?xml version="1.0" encoding="UTF-8"?>
<extensions>
    <extension>
      <groupId>fr.jcgay.maven.extension</groupId>
      <artifactId>unique-revision-maven-filtering</artifactId>
      <version>[latest-version]</version>
    </extension>
</extensions>

ou bien en copiant directement le jar dans %M2_HOME%/lib/ext.

Conclusion
#

On a donc vu comment utiliser une propriété unique pour définir la version de tous les modules d’un projet. Clairement la fonctionnalité proposée par Maven (continuous delivery friendly version) ne semble pas prévue pour l’usage décrit ici, mais au moins je ne m’embête plus avec mes merge ! J’ai peut-être complètement raté une solution plus simple ? (dans ce cas je la veux bien 😉).

Partager

Articles connexes

Le cycle de vie de son build Maven

·3 mins
Avec Maven, je me suis souvent retrouvé un peu seul devant un POM en me demandant ce que pouvait bien exécuter mon build sans devoir aller lister toutes les exécutions déclarées. Depuis Maven 3 il est possible de prévoir ce qu’un build va exécuter au sein de son cycle de vie. Etrangement il n’y a rien qui permet d’afficher facilement ce plan de build calculé. Voici le buildplan-maven-plugin.

Commentaires archivés (3)

Discussion d'origine, conservée telle quelle. Elle est close.

Thomas Queste

Certainement lié mais l'auteur ne parle pas des warnings Maven:
https://axelfontaine.com/bl...

Est-ce que tu penses que la solution d'Axel Fontaine est la même que la première que tu cites, ou une alternative ? (je ne suis pas sûr de bien voir les différences).

Merci,
Tom

Jean-Christophe GAY

Oui ses articles sont très bien. Ton lien pointe vers sa dernière version et en effet il n'a pas de warning car il utilise bien une des propriétés "officielles" de Maven. Les warnings tu ne les as qu'avec un Maven < 3.2.1 ou alors si tu n'utilises pas une des propriétés compatibles: Continuous delivery friendly version.

Dans sa première série d'articles je crois qu'il n'utilisait pas de propriétés et qu'il lançait des mvn versions:set avant ses build dans Jenkins (j'ai pas tout relu).

Donc en fait c'est la même solution que https://jeanchristophegay.c... sauf qu'il me semble qu'il n'adresse pas le problème qui survient quand un projet en dehors de ton reactor dépends d'un de tes modules (le $revision qui n'est pas remplacé dans les pom déployés).

Mais depuis je suis retombé sur un autre article: http://blog.soebes.de/blog/... et en fait il existe un plugin Maven qui fait la même chose que mon extension, c'est à dire remplacer les $revision dans les pom.xml au moment du déploiement.

Thomas Queste

OK, merci pour les infos.
L'article que tu cites est aussi très clair.
Même avec Maven 3.5.1, on n'est pas encore outillé nativement pour du CD...