Domptez vos versions

Apache MavenDans un soucis de qualité et de traçabilité Apache Maven a introduit et imposé dès le départ la notion de version pour identifier les projets et leurs livrables. Cependant il faut bien avouer que pendant longtemps il n’y avait pas d’outil à la disposition des équipes pour analyser et manipuler les différentes versions utilisées dans les descripteurs Maven. Ce manque a été en partie comblé il y a un peu plus d’un an par la création du plugin versions, hébergé sur http://mojo.codehaus.org que je me propose de vous présenter.
Entendu sur Les Cast CodeursCet article est un complément aux explications que j’ai pu donner dans la rubrique "les mains dans le cambouis" de l’épisode 16 du podcast Les Cast Codeurs.

Cet article est basé sur la version 1.1 du plugin versions publiée en octobre 2009.

Abracadabra, de version tu changeras !

Le fait que Maven considère que dans un projet tout module doive pouvoir être construit indépendamment des autres, toutes les références internes (héritages, dépendances, …) répètent inlassablement le numéro de version du projet. Il existe bien des palliatifs comme utiliser la propriété ${project.version} pour ne pas répeter cette information au niveau des dépendances. Pourtant cela ne fonctionne pas au niveau de l’héritage car on retombe sur le problème de la poule et de l’oeuf. Si ma version n’est que dans le pom parent de mon projet, comment mon module va trouver son parent sans connaitre sa version ?

Cela crée donc souvent la panique à bord dans l’équipe projet lorsque pour une raison X ou Y il est nécessaire de changer la version en cours de développement. Les plus geek sortiront des commandes find, sed ou autre mais cela n’est pas toujours sans erreurs surtout si l’on a la malchance d’avoir notre numéro de version à remplacer utilisé par ailleurs (dans une dépendances externe par exemple).

Le plugin versions propose donc le mojo:set pour simplifier cette opération.

versions:set
Sets the current projects version, updating the details of any child modules
as necessary.

Il suffit de passer en paramètre la nouvelle version à utiliser pour que le plugin remplace partout (héritage, dépendances, …) l’ancienne valeur par la nouvelle.

mvn versions:set -DnewVersion=1.2.3-SNAPSHOT

Mais que faire si l’on avoulu faire la modification à la main et que l’on s’est planté ? Si par exemple la chaine d’héritage est rompue au sein de vos modules vous risquez de vous retrouver avec ce genre d’erreur (C’est promis on à amélioré la qualité des erreurs remontées dans Maven 3) :

[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[ERROR] FATAL ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Error building POM (may not be this project's POM).
Project ID: com.foo.bar:bar-child:jar:null
Reason: Cannot find parent: com.foo.bar:bar for project: com.foo.bar:bar-child:jar:null

Une fois de plus le plugin versions vient à vote secours avec le mojo versions:update-child-modules

versions:update-child-modules
Scans the current projects child modules, updating the versions of any which
use the current project to the version of the current project.

Lancez la commande mvn -N versions:update-child-modules et votre soucis se sera envolé (attention à ne pas oublier l’option -N car sinon maven essaiera de résoudre vos modules et il echouera à cause du problème).

Références :

Boule de cristal : Dis-moi si j’utilise les dernières versions de mes dépendances ou plugins ?

Tôt ou tard dans la vie d’un projet se pose la question de savoir si nous utilisons des versions récentes des plugins Maven ou des dépendances. Cela peut être suite à la découverte d’un bug, ou alors tout simplement pour essayer de rester à jour afin de conserver un support actif sur des livrables open-sources par exemple (essayez donc de demander à un véritable développeur open-source – qui le fait de bon coeur sur son temps libre – de corriger un bug et livrer un correctif sur une version
qui a plusieurs années !!). Le nombre important de plugins et de dépendances rentrant en compte dans un projet est vite faramineux et cette tâche devient vite une peine si l’on doit aller consulter un à un tous les sites web impartis ou si l’on doit rechercher ou naviguer dans le repository central Maven pour identifier les mises à jour.

Pour répondre à ce besoin le plugin versions fournit une série de mojo qui vont automatiquement analyser les plugins et dépendances que vous utilisez pour comparer vos versions actuelles avec celles disponibles sur les référentiels d’artifacts que vous avez déclaré dans votre projet ou paramètres utilisateurs. Le plugin vous permet de visualiser le résultat soit sous la forme d’un rapport en ligne de commande (versions:display-dependency-updates, versions:display-plugin-updates) soit dans le site web du projet généré par Maven (versions:display-dependency-report, versions:display-plugin-report). Cela fonctione aussi si vous avez stocké vos numéros de versions dans des propriétés (versions:display-property-updates, versions:property-updates-report).

versions:display-dependency-updates
Displays all dependencies that have newer versions available.
versions:dependency-updates-report
Generates a report of available updates for the dependencies of a project.
versions:display-plugin-updates
Displays all plugins that have newer versions available.
versions:plugin-updates-report
Generates a report of available updates for the dependencies of a project.
versions:display-property-updates
Sets properties to the latest versions of specific artifacts.
versions:property-updates-report
Generates a report of available updates for properties of a project which are
linked to the dependencies and/or plugins of a project.

Exemple de rapport en ligne de commande pour les mises à jour de dépendances :

[INFO] --- versions-maven-plugin:1.1:display-dependency-updates (default-cli) @ maven-my-plugin ---
[INFO] The following dependencies in Dependencies are using the newest version:
[INFO] org.apache.maven:maven-core .................................... 2.2.1
[INFO] org.apache.maven:maven-toolchain ................................. 1.0
[INFO] org.apache.maven.shared:maven-common-artifact-filters ............ 1.2
[INFO] plexus:plexus-utils ............................................ 1.0.1
[INFO] rhino:js ....................................................... 1.7R1
[INFO]
[INFO] The following dependencies in Dependencies have newer versions:
[INFO] org.apache.maven:maven-artifact ................. 2.2.1 -> 3.0-alpha-7
[INFO] org.apache.maven:maven-plugin-api ............... 2.2.1 -> 3.0-alpha-7

Exemple de rapport intégré dans le site web maven pour les mises à jour de dépendances :

Vous noterez que ce rapport est bien plus complet et vous permet de facilement identifier les dernières versions majeurs, mineurs,correctives disponibles. Je vous conseille de configurer le plugin pour utiliser la méthode de comparaison "Mercury" (celle de maven 3) plutot que la version par defaut (celle de maven 2) pour obtenir des résultats plus judicieux sur certaines librairies qui ont publié des livrables avec des versions exotiques.

Autre chose très utile, le rapport sur les mises à jour de plugin vous alerte si les versions des plugins utilisés ne sont pas définies dans votre projet (ce qui est une mauvaise pratique Maven puisque vous risquez d’avoir une mise à jour qui détériore votre build.

[INFO] [versions:display-plugin-updates]
[INFO]
[INFO] The following plugin updates are available:
[INFO] maven-checkstyle-plugin .................................. 2.1 -> 2.2
[INFO] maven-clean-plugin ....................................... 2.1 -> 2.2
[INFO] maven-deploy-plugin ...................................... 2.3 -> 2.4
[INFO] maven-javadoc-plugin ..................................... 2.4 -> 2.5
[INFO] maven-site-plugin .......................... 2.0-beta-6 -> 2.0-beta-7
[INFO]
[WARNING] The following plugins do not have their version specified:
[WARNING] maven-compiler-plugin ..................... (from super-pom) 2.0.2
[WARNING] maven-deploy-plugin ......................... (from super-pom) 2.3
[WARNING] maven-install-plugin ........................ (from super-pom) 2.2
[WARNING] maven-javadoc-plugin ........................ (from super-pom) 2.4
[WARNING] maven-site-plugin .................... (from super-pom) 2.0-beta-6
[WARNING] org.codehaus.mojo:build-helper-maven-plugin .................. 1.2

Exemple de rapport sur les mises à jour de propriétés qui contiennent des versions :

[INFO] [versions:display-property-updates]
[INFO]
[INFO] The following version property updates are available:
[INFO] ${doxiaVersion} ........................................ 1.0 -> 1.1.1
[INFO] ${doxia-sitetoolsVersion} .............................. 1.0 -> 1.1.1

 

Références :

Je suis ton père !

Avec le mécanisme d’héritage des descripteurs de projets, il est simple de mettre en place des descripteurs parents au niveau d’une entreprise, d’une forge ou simplement de plsuieurs projets pour partager un certain nombre de paramètres communs. Ces descripteurs ont leur propre cycle de vie, mais il est souvent conseillé d’utiliser la dernière version disponible. Au lieu d’aller voir manuellement quelle est la dernière version disponible il suffit d’appeler la commande versions:update-parent

Exemple :


[INFO] [versions:update-parent]
...
[INFO] artifact org.codehaus.mojo:mojo: checking for updates from codehaus.org
[INFO] artifact org.codehaus.mojo:mojo: checking for updates from central
[INFO] Updating parent from 14 to 17

Cela ne vous évitera pas cependant d’aller chercher la release note associée à la nouvelle version pour comprendre les impacts espérés (ou non) sur votre build.

Vous pouvez aussi préciser un intervalle pour la mise à jour :

mvn versions:update-parent -DparentVersion="[14,16)"

Et même autoriser les SNAPSHOTs :

mvn versions:update-parent -DallowSnapshots=true

Références :

Maitrisez le chaos !

Le concept de versions dans Maven propose deux types de versions qui ne sont pas stables dans le temps :

  • Les SNAPSHOTs : Elles représentent les projets en cours de développement. Les livrables avec des versions dites SNASPHOT sont régulièrement actualisées par Maven (une fois par jour par défaut). Ces livrables sont stockés avec un identifiant unique lorsqu’ils sont déployés sur un référentiel distant.
  • Les ranges : Ce sont des intervals de versions. Au lieu de dire que l’on veut une version donnée d’un livrable, on laisse le choix à maven de choisir la version la plus récente dans un ensemble prédéfini.

Le principale problème en utilisant ces types de versions dans votre projet c’est que du jour au lendemain Maven peut les mettre à jour et que cela peut entrainer des régressions. Pour l’éviter il est utile de pouvoir figer ces versions soit en définissant exactement avec leurs identifiants uniques les SNASPHOTs que l’on utilise, soit en choisissant les versions aujourd’hui utilisées dans les intervales (ranges).

Pour cela le plugin versions propose les mojos versions:lock-snapshots, versions:unlock-snapshots et versions:resolve-ranges qui vous permettent d’éditer ces versions.

versions:lock-snapshots
Attempts to resolve unlocked snapshot dependency versions to the locked
timestamp versions used in the build. For example, an unlocked snapshot
version like '1.0-SNAPSHOT' could be resolved to '1.0-20090128.202731-1'. If a
timestamped snapshot is not available, then the version will remained
unchanged. This would be the case if the dependency is only available in the
local repository and not in a remote snapshot repository.
versions:unlock-snapshots
Attempts to resolve unlocked snapshot dependency versions to the locked
timestamp versions used in the build. For example, an unlocked snapshot
version like '1.0-SNAPSHOT' could be resolved to '1.0-20090128.202731-1'. If a
timestamped snapshot is not available, then the version will remained
unchanged. This would be the case if the dependency is only available in the
local repository and not in a remote snapshot repository.
versions:resolve-ranges
Attempts to resolve dependency version ranges to the specific version being
used in the build. For example a version range of '[1.0, 1.2)' would be
resolved to the specific version currently in use '1.1'.

Vous y trouverez aussi des options pour inclures ou exclures certaines dépendances afin de cibler vos mises à jour.

mvn versions:lock-snapshots -Dincludes=org.codehaus.plexus:*,junit:junit

Références :

Mettez à jour ce que vous voulez !

Pour terminer le plugin mojo offre toute une série de mojos pour mettre à jour vos dépendances vers les versions suivantes ou les toutes dernières versions. On peu utiliser soit la prochaine version release ou snapshot. On peut aussi controler/filtrer les dépendances à mettre à jour.

versions:update-properties
Sets properties to the latest versions of specific artifacts.
versions:use-latest-releases
Replaces any release versions with the latest release version.
versions:use-latest-snapshots
Replaces any release versions with the latest snapshot version (if it has been
deployed).
versions:use-latest-versions
Replaces any version with the latest version.
versions:use-next-releases
Replaces any release versions with the next release version (if it has been
released).
versions:use-next-snapshots
Replaces any release versions with the next snapshot version (if it has been
deployed).
versions:use-next-versions
Replaces any version with the latest version.
versions:use-releases
Replaces any -SNAPSHOT versions with the corresponding release version (if it
has been released).

Références :

Oopps !!!!

Même si une large partie des projets sont supposés être gérés dans un gestionaire de versions de sources, le plugin versions intègre par défaut sont propre système de validation/annulation pour toutes les opérations dans lesquelles il modifie vos descripteurs de projets. Pour cela il crée des copies de sauvegarde de vos descripteurs. Vous pouvez alors les supprimer (et donc valider ses changements) avec la commande versions:commit, ou vous pouvez tout annuler avec versions:revert. ATTENTION, cette sauvegarde ne gère pas d’historique. Elle ne conserve que la dernière modification. Pensez à valider ou rejeter vos changements après chaque modification.

versions:commit
Removes the initial backup of the pom, thereby accepting the changes.
versions:revert
Restores the pom from the initial backup.

Références :

19 thoughts on “Domptez vos versions”

  1. Merci beaucoup pour cet excellent article. Je devais utiliser ce plugin aujourd’hui, et je suis tombé par hasard sur ton billet.

    @++,
    Michael.

  2. Salut Arnaud,

    Pas mal cet article. Je connaissais un peu le plugin, mais avec des exemples on comprend mieux à quoi ça sert. Cependant, ça commence à faire beaucoup de lignes de commandes “magiques” à se rappeler. On s’éloigne du simple mvn clean install. Vivement que ce soit intégré dans m2eclipse.

    Sinon, il y a un mauvais copier/coller dans la description de versions:unlock-snapshots (à partir de la ligne 9).

    ++

    1. C’est evident mais ce sont des commandes que l’on utilise à l’occasion. Pas tous les jours. Effectivement avec un bon support des IDEs on oubliera ces problèmes. J’ai envie de rapatrier ce plugin chez Maven maintenant que son principal developpeur est devenu committer du projet. Ca permettrait d’avoir un meilleur support par les outils externes comme les IDEs.
      Sinon pour le copier/coller tu as presque raison, sauf que c’est pas mon article que je dois corriger, mais la description du mojo 🙂 Je le ferai demain. Merci pour ce rapport de bug 🙂

  3. Merci pour toutes ces informations qui complètent bien la section “Les mains dans le cambouis (avec ou sans mitaines)” des castcodeurs.

    Mes prochaines phases de “release” vont adorer ce plugin!

    Petite question subsidiaire, peut-on utiliser ce plugin pour détecter les “spaghettis snapshot” ?
    Je m’explique :
    J’ai un projet A en version 1.1-SNAPSHOT avec 3 modules A1, A2 et A3.
    J’ai un projet B en version 1.0-SNAPSHOT avec 3 modules B1,B2 et B3.
    B1 dépend de A1, B2 dépend de B1 et de A2.
    Je veux être sur que personne n’ajoute une dépendance entre un module de A vers un module de B (comme A3 dépendrait de B3). Toutes les snapshots sont dans le repository donc techniquement cela est possible mais bloquerait la prochaine release. A étant réleasé avant B.
    Actuellement, pour contrôler cela, dans mon système d’intégration continue, je compile tous les projets une première fois pour mettre à jour le reposirory local, puis je supprime du repository local tous mes projets (pas les libs tierces). Et je recompile en mode offline. Un module de A ne peut pas utiliser un module de B car il n’est pas encore compilé.
    Le plug-in version permet-il d’effectuer cela de manière plus élégante?

    Jer.

    PS : j’aime bien le duo entre l’aspect light et convivial du podcast et tes articles plus détaillés servant de référence pour une mise en pratique sur nos projets.

      1. Merci Arnaud pour l’information sur le plugin enforce, mais en général, je ne souhaite pas faire de dépendances inverses entre les projets. J’ai bien compris que le plugin version ne remplissait pas cette tâche, mais peut-être qu’il correspond le plus et que pour une prochaine version, cela serait envisageable, …

        Pour des idées d’articles, que penses-tu d’aborder le sujet du couplage de maven avec les systèmes d’intégration continue. Doit-on découpler la vérification des builds de la génération de rapport? mvn install ou deploy des snapshots? L’organisation de son CI pour avoir un beau tableau de board comme http://builder.exoplatform.org/hudson/ 😉
        Un article donc plus porté sur les bonnes pratiques que la configuration elle même, afin de partager ton expertise avec nos humbles expériences.

        Jer.

  4. Très bon article,

    Après quelques essais, quelques désillusions : le versionning de Spring ne semble pas convenir au plugin versions :

    [INFO] Updated ${spring.version} from 3.0.2.RELEASE to 2.5.6

    Y a-t-il un moyen propre d’exclure une propriété de l’analyse du mojo update-properties ?

    1. Ils m’énervent à ne pas suivre les conventions de nom de versions reconnues par Maven :
      http://mojo.codehaus.org/versions-maven-plugin/version-rules.html
      C’est le .RELEASE qui doit fiche la m…
      Faut que je me renseigne par ce que je ne comprends pas pourquoi sur ces mojo contrairement à ceux utilisés pour faire les rapports on ne peux pas utiliser la résolution de maven 3 (mercury).
      Pour exclure cette propriété il te suffit de lancer :
      mvn versions:update-properties -DexcludeProperties=spring.version
      cf : http://mojo.codehaus.org/versions-maven-plugin/update-properties-mojo.html#excludeProperties

  5. Il y a un autre cas intéressant qui ne semble pas être géré par le plugin “versions”. Imagine que tu as un projet qui utilise un framework technique comme suit :

    framework technique
    -> moduleFrameworkDao
    -> moduleFrameworkService
    -> moduleFrameworkController

    projet metier eBanking
    -> module eBankingDao (hérite de moduleFrameworkDao)
    -> module eBankingService (hérite de moduleFrameworkService)
    -> module eBankingController (hérite de moduleFrameworkController)

    Dans cette exemple, les versions de eBankingDao, eBankingService et eBankingController devraient suivre celles de eBanking. Cependant le plugin versions ne semble pas le gérer.
    Est-ce que tu penses que c’est une fonctionnalité qui pourrait être implémentée pour la suite ?

    Michael.

    1. Je pense que ça pourrait être implémenté, par contre est ce souhaitable je ne sais pas. Comme je le dis dans mes prez lors des JUGs, l’héritage Maven est technique. Il est là pour simplifier la conf des projets en factorisant. Ne pas utiliser un héritage classique (parent = module reacteur du dessus) limite beaucoup l’utilisation de Maven. Dans ton cas lorsque tu fais un nouvelle release de ton framework tu vas devoir modifier tous les parents de eBanking pour changer la version. En plus tu ne peux rien partager comme info entre les modules eBanking car tu as un héritage simple et tu dois donc tout dupliquer. Je ne suis pas fan de l’héritage à gogo dans ce sens car cela complexifie pour beaucoup le projet et sa maintenance. Pour moi c’est une contrainte actuellement et je préfère ne pas la contourner (quitte à faire des sacrifices) en attendant des solutions propres et supportées dans Maven comme les Mix-ins.

  6. Est-il possible d’exécuter le goal versions:display-dependency-updates pour un ensemble restreint de dépendances (dépendances appartenant à même groupId par exemple) ?

  7. Bonjour,

    Y at-il un plugin ou une commande magique pour lister les artifacts qui dépendent (de manière directe ou indirecte) d’un artifact donné – en exploitant un repository (comme nexus par exemple) ?

    1. Désolé pour la réponse tardive, je n’ai pas vu la notification du commentaire 🙁 Je ne connais pas de plugin pour le faire. C’est possible depuis un module donné avec dependency:tree, et dependency:list mais ces derniers ne s’exécutent avec un GroupID/ArtifactId/Version en paramètre. Ca pourrait être une demande d’évolution.

  8. Bonjour,

    très bon article. Une petite question existe-il un plugin jenkins permettant d’afficher sur la page d’un build, une liste des dépendances qui ne seraient pas à jour ?

    Merci

    1. Bonjour,

      Ca ne me dit rien. Faudrait demander cela a Stephen Connolly qui est l’auteur du plugin versions pour maven. Il travail chez Cloudbees et code pas mal de choses pour Jenkins donc il n’aurait pas de mal à faire cela 🙂

      Arnaud

  9. Salut Arnaud,
    à tout hasard tu n’aurais pas une formule magique pour que le plugin versions ne me propose pas des versions alpha/beta ? (par exemple par exclusion de mot-clés.

    Pour l’instant j’ai ça :
    org.hibernate:hibernate-core ………….. 4.1.9.Final -> 4.3.0.Beta1
    et j’aurais préféré avoir :
    org.hibernate:hibernate-core ………….. 4.1.9.Final -> 4.1.11.Final

    Ca reste un détail, ce plugin reste super utile.

    Michael.

Comments are closed.