Skip to content

Latest commit

 

History

History
1094 lines (805 loc) · 45.2 KB

chapitre 3.adoc

File metadata and controls

1094 lines (805 loc) · 45.2 KB

Chapitre 3

Un peu plus que compiler

Jusqu’à présent, Maven s’est montré plutôt efficace pour traiter les difficultés d’organisation de notre projet, en proposant des conventions et des mécanismes automatisés qui nous évitent de prendre des chemins hasardeux. Nous allons voir maintenant comment il poursuit cet effort lorsque notre projet "dévie" progressivement de l’exemple si simple que nous avons utilisé pour l’instant.

Êtes-vous prêt pour Java 7 ?

Le prototype à l’origine de notre projet a été écrit il y a belle lurette et utilise la syntaxe Java 1.2. Maven n’a pas de grande difficulté pour le compiler, ce qui aurait été un comble. Nous sommes cependant au xxie siècle, et utilisons Java 7 comme environnement d’exécution. Nous avons donc tous un OpenJDK 7 à jour installé sur nos postes de développement.

Confiants dans Maven qui, pour l’instant, nous apporte entière satisfaction, nous retravaillons un peu le code historique de gestion des listes de courses pour bénéficier d’une syntaxe moderne, alliant annotations, généricité, autoboxing et arguments variables. Devant un code qui semble nettement plus moderne, nous lançons fièrement la compilation par Maven, avant de tomber sur un message d’erreur fort désagréable :

[INFO] -------------------------------------------------------------

[ERROR] COMPILATION ERROR :

[INFO] -------------------------------------------------------------

[ERROR] D:\noubliepaslalistedescources\src\main\java\org\ noubliepaslalistedescources\model\MesCourses.java:[3,66] diamond operator is not supported in -source 1.5

[INFO] 1 error

Le « diamond operator »[9], c’est en effet une évolution du langage Java proposée par Java 7. Maven ne serait-il compatible qu’avec Java 5 ? Les choses sont même bien pires que cela si on essaie de compiler notre projet avec Maven 2 :

[INFO] Compilation failure

 

D:\noubliepaslalistedescources\src\main\java\org\ noubliepaslalistedescources\model\MesCourses.java:[57,5] annotations are not supported in -source 1.3

(use -source 5 or higher to enable annotations)

Pardon ? Maven2 est compatible uniquement avec Java 1.3 ? Pas de panique, les choses sont plus subtiles que cela et, heureusement pour nous, moins définitives. Gardez à l’esprit que Maven est un projet qui a déjà de nombreuses années et une très large base d’utilisateurs. L’une des préoccupations majeures des développeurs est d’assurer une construction de projet qui soit totalement reproductible, quel que soit l’environnement de développement. Cette exigence est essentielle pour que vous puissiez bâtir vos projets sur une base irréprochable.

Maven a été conçu sur la base de la plateforme Java 1.4, version "moderne" de l’époque. Sur ce JDK, les valeurs par défaut des options source et target du compilateur sont respectivement 1.3 et 1.2[10]. Par contre, sur le JDK Java 6, cette valeur par défaut est "1.6"[11] pour les deux, et source 1.6, target 1.7 pour le JDK 7 ; autant dire que le JDK utilisé peut fortement influencer le résultat de notre compilation !

Plutôt que de laisser cette option sans valeur déterministe, ce qui aurait rendu la construction du projet dépendante de l’environnement utilisé par un développeur, le compilateur utilisé par Maven2 est configuré, par défaut, pour cette valeur 1.3 et pour Maven3 par défaut à 1.5 (voir notre note un peu plus loin).

Notre code Java 7 n’a donc aucune chance d’être accepté par le compilateur. Le choix de Maven a été de s’assurer que le projet sera construit de la même façon quel que soit le JDK utilisé, sur la base de son exigence minimale qui est le JDK 1.4. Ce choix peut sembler archaïque mais c’est la seule façon de gommer les différences qui existent entre les versions de Java.

Comment modifier ce comportement protecteur mais pénalisant, qui vise juste à nous éviter des déconvenues dues aux inconsistances entre versions du JDK ? Nous avons vu que Maven associe à tout projet un patron de référence, regroupant les étapes applicables à la très grande majorité des projets, dont la compilation des sources .java. Cette convention nous évite de devoir explicitement indiquer à Maven quand et comment effectuer la compilation. Allons-nous devoir faire machine arrière ? Non, car Maven prévoit également la possibilité de reconfigurer ces étapes standard, lorsque leur fonctionnement par défaut ne suffit plus.

Plugins

Maven confie chaque opération élémentaire de la construction du projet à un plugin, un fragment de logiciel qui se spécialise dans une tâche donnée. La compilation est un exemple de plugin, mais pensez aussi à l’assemblage sous forme d’un JAR ou à l’inclusion de fichiers de ressources, etc. Chaque plugin propose un certain nombre d’options et de paramètres qui permettent d’ajuster son fonctionnement, avec des valeurs par défaut qui sont choisies pour coller au mieux aux conventions de Maven et à une utilisation standard. Le plugin de compilation (compiler) utilise les options source et target avec comme valeurs par défaut 1.3 et 1.2, correspondant à la plateforme Java de référence utilisée par Maven.

Note

Plus précisément, la version du plugin compiler qui est utilisée par Maven sans indication de votre part utilise les options source=1.3 et target=1.2 par défaut. A partir de la version 2.3, le plugin compiler prend pour valeurs par défaut source=1.5 et target=1.5, ce qui est le cas si vous utilisez Maven3.

Cependant, afin que votre IDE détecte correctement cette configuration, nous vous encourageons à toujours définir explicitement ces valeurs.

La modification des options par défaut d’un plugin s’effectue dans le fichier POM du projet, au sein de son bloc <build> :

<build>

    <plugins>

        <plugin>

            <groupId>org.apache.maven.plugins</groupId>

            <artifactId>maven-compiler-plugin</artifactId>

            <version>2.3.2</version>

            <configuration>

                <source>7</source>

                <target>7</target>

            </configuration>

        </plugin>

    </plugins>

</build>

Chaque plugin peut ainsi être reconfiguré. Un plugin, comme tout artefact manipulé par Maven, est identifié par le triplet [identifiant de groupe, identifiant d’artefact, version]. Nous indiquons ici le plugin de compilation dont nous désirons ajuster le fonctionnement. Le bloc <configuration> permet de lui passer des valeurs qui vont remplacer celles par défaut. Chaque plugin ayant son propre paramétrage, nous devons consulter la documentation du plugin[12] pour connaître toutes les options disponibles (voir Figure 03-01).

Figure 03-01

Le site de documentation du plugin compiler.

Comme vous pouvez le constater, le plugin dispose d’un grand nombre de paramètres, qui lui permettent de répondre sans difficulté aux cas de figure les plus délicats. En plus des options "standard" de javac, vous pouvez par exemple utiliser un compilateur alternatif comme celui d’Eclipse JDT si votre projet nécessite cette option pour une raison quelconque. Dans notre cas, seuls les paramètres source et target sont nécessaires pour obtenir le résultat attendu, les autres paramètres pouvant conserver leur valeur par défaut.

Info

Chaque plugin Maven dispose d’un site de documentation, en particulier les plugins standard, sur http://maven.apache.org/plugins/. La documentation fournit une description de chaque option, les valeurs par défaut utilisées et, dans la plupart des cas, quelques exemples de configuration pour les utilisations les plus fréquentes. Ces sites documentaires sont générés à partir du code source du plugin et diffusés en même temps que lui. Ils sont donc toujours synchrones avec la version courante du plugin.

Attention cependant, car le site web généré correspond généralement à la version en cours de développement du plugin, aussi soyez attentif à l’indication "@since" ajoutée à certains paramètres.

Propriétés

La modification du fonctionnement du plugin de compilation nous permet enfin de valider la syntaxe Java 7 que nous avons introduite dans le projet. Ce besoin tout simple a cependant nécessité une configuration significative, ce qui peut vous laisser perplexe : pas moins de 10 lignes dans le fichier POM.xml, là ou deux attributs suffisent dans un script Ant !

Ce principe de reconfiguration des plugins est la version "lourde" de la solution, même si elle a l’avantage de nous ouvrir les portes de toutes les options de configuration. Il existe cependant une autre voie, plus légère bien qu’ayant certaines limites. La consultation de la page documentaire du plugin de compilation révèle que les paramètres source et target sont associés à une expression, respectivement maven.compiler.source et maven.compiler.target. De quoi s’agit-il ?

Les valeurs par défaut utilisées par un plugin peuvent être modifiées via un élément <plugin> dans le POM, mais aussi par l’exploitation d’un mécanisme de Maven appelé "interpolation", qui consiste à évaluer au moment de l’exécution les valeurs à utiliser en se fondant sur des "expressions". Celles-ci peuvent être comparées aux mécanismes utilisés dans les applications par l'_expression language_des JSP. La chaîne maven.compiler.source est évaluée juste avant que Maven n’utilise le plugin, en fonction de l’environnement dans lequel il s’exécute. En particulier, cette notion d'"environnement" inclut les variables système passées sur la ligne de commande avec l’option -D. Nous pouvons donc activer la compilation Java 7 en lançant la commande :

mvn compile -Dmaven.compiler.source=7 -Dmaven.compiler.target=7

Nous savons donc comment modifier à la demande la configuration utilisée par le plugin de compilation sans modifier le fichier POM. Cela peut être très utile, en particulier pour modifier très ponctuellement le comportement de Maven sans toucher à la configuration. Mais pour notre problème de compilation Java 7, le prix à payer est lourd : la ligne de commande que nous devons taper dans une console s’allonge dangereusement !

Comme les développeurs de Maven sont un peu fainéants comme tout bon développeur, ils ont pensé à une solution intermédiaire pour nous éviter de telles lignes de commande, sans pour autant devoir ajouter des dizaines de lignes à notre fichier POM : les propriétés. Il s’agit tout simplement de figer les variables d’environnement dans le fichier POM, à l’intérieur d’un bloc <properties>. La valeur indiquée sera prise en charge exactement de la même manière par l’interpolation, tout en étant encore modifiable via le -D sur la ligne de commande. Cela permet de définir en quelque sorte des valeurs par défaut applicables sur le projet et sur lui seul :

<properties>

    <maven.compiler.source>7</maven.compiler.source>

    <maven.compiler.target>7</maven.compiler.target>

</properties>

La plupart des plugins Maven proposent cette option pour leurs principaux paramètres de configuration ; cependant, cette pratique n’est pas généralisée à tous les paramètres ni à tous les plugins. Il s’agit plus d’une bonne pratique que les développeurs de plugins devraient connaître pour satisfaire au mieux leurs utilisateurs. Dans le cas contraire, seule l’option " lourde " reste envisageable.

Quand Java ne suffit plus

MangaArnaud

Bien que nous ayons introduit la syntaxe Java 7 dans notre code, Arnaud est loin d’être satisfait par sa lisibilité. Selon lui, de nombreux passages techniques pourraient être nettement plus simples si nous… renoncions à la syntaxe Java ! Après vérification du contenu de sa tasse de café, nous comprenons qu’Arnaud est tout à fait à jeun (il faut dire qu’il est tout juste 9 heures du matin) et tout à fait sérieux. Il évoque, en fait, avec un savant effet de suspens la possibilité d’utiliser le langage Groovy pour coder notre application, ou tout du moins certains composants qui s’y prêtent très bien.

Info

Groovy est un langage dynamique qui s’exécute sur la machine virtuelle Java, au même titre que jRuby ou Jython par exemple. L’environnement d’exécution Java actuel ne se limite plus au seul langage de programmation Java et accueille un nombre croissant de langages via des interpréteurs ou des compilateurs spécialisés. Vous pouvez par exemple développer une application en PHP et l’exécuter sur un serveur Java ! Ce qui pourrait sembler a priori un mariage contre nature ouvre en réalité des perspectives étonnantes, en fonction des points forts de certains langages dans des domaines précis, ou tout simplement des développeurs dont vous disposez.

Quelques exemples bien choisis (Arnaud a bien préparé son coup) nous convainquent rapidement des améliorations que Groovy apporterait à notre projet. Reste un petit écueil : le "projet type" utilisé par Maven pour définir les tâches exécutées lors de la construction d’un projet n’inclut certainement pas l’exécution du compilateur Groovy ! La grande majorité des projets Java n’utilisent pas ce langage aujourd’hui. Il n’y a donc aucune raison pour que Maven en ait tenu compte nativement.

En consultant la documentation en ligne de Groovy[13], nous constatons cependant qu’un plugin Maven a été développé. Il suffit de le déclarer dans le POM du projet pour obtenir cette nouvelle étape dans la construction de notre binaire. La notion de plugin (greffon) prend alors tout son sens : pour prendre en charge le besoin X, il suffit d’ajouter au projet le plugin X. L’approche déclarative de Maven économise la déclaration des opérations réalisées par le plugin et de la façon dont elles s’intègrent dans le projet.

Où placer les sources

Nous l’avons déjà dit, les conventions de Maven sont un élément décisif dans sa capacité à prendre en charge de manière automatisée le projet. En particulier, la structure type d’un projet Maven est la suivante (voir Figure 03-02).

Figure 03-02 

La structure de base d’un projet Maven.

La logique est plutôt simple : à la racine, on trouve le fichier POM qui gouverne toute la gestion Maven du projet. L’ensemble des sources est placé dans un répertoire src, tandis qu’un répertoire target sert de zone temporaire pour toutes les opérations réalisées sur le projet. Cela a au moins l’avantage de faciliter grandement la configuration de votre gestionnaire de code source ! Il suffit d’exclure target (en plus des fichiers spécifiques de votre IDE) et vous êtes sûr de ne pas inclure par mégarde des fichiers de travail qui n’ont pas à être partagés.

Sous le répertoire des sources, Maven effectue un découpage explicite entre ce qui fait partie du projet – ce que vos utilisateurs vont utiliser – et ce qui sert d’outillage de test. Deux sous-répertoires, main et test, marquent cette distinction.

Enfin, dans chacune de ces branches, un dernier niveau de répertoires sépare les fichiers sources par langage : java pour le code source de vos classes java, resources pour les fichiers de ressources (configuration XML ou fichiers de propriétés…), webapp pour les fichiers statiques d’une application web.

Le plugin Groovy ajoute son lot de conventions qui viennent compléter celles déjà définies par Maven. Les fichiers source Groovy ont ainsi leur propre répertoire de code source sous src/main/groovy. Il en est de même pour les tests écrits dans ce langage avec src/test/groovy. Ces conventions sont alignées sur celles de Maven pour obtenir un ensemble cohérent. D’autres plugins qui apportent le support de langages autres que Java suivront la même logique.

Ajouter un plugin

Ces répertoires créés pour accueillir le code, il nous reste à déclarer le plugin Groovy dans notre POM. Sur l’exemple du plugin compiler, nous ajoutons :

<build>

    <plugins>

        <plugin>

            <groupId>org.codehaus.groovy.maven</groupId>

            <artifactId>gmaven-plugin</artifactId>

            <version>1.0</version>

            <configuration>

<!-- les valeurs par défaut nous conviennent très bien :) -→

            </configuration>

        </plugin>

    </plugins>

</build>

Astuce

Vous constaterez, si vous utilisez un éditeur XML, que l’élément version n’est pas obligatoire pour les plugins. Le comportement de Maven se traduit alors par prendre la "dernière version stable disponible". C’est une fausse bonne idée ! En effet, si vous reprenez une version de votre projet d’il y a six mois pour une correction urgente, vous risquez de ne pas utiliser le même plugin que prévu initialement. Si la compatibilité ascendante n’est pas parfaite, attention à la casse. Pour cette raison, il est fortement recommandé de toujours spécifier la version de vos plugins. À partir de Maven 2.0.9, ceux qui sont utilisés par défaut dans le build Maven ont une version prédéfinie en interne pour éviter ce piège.

Au lancement de Maven, nous constatons avec plaisir le téléchargement de fichiers POM et JAR associés au plugin Groovy. Voici une autre explication de la dépendance de Maven à un accès Internet : les plugins, comme les bibliothèques, sont téléchargés à la demande depuis un dépôt de bibliothèques. L’installation de Maven est ainsi limitée à un noyau et tous les plugins qui lui permettent d’exécuter des tâches sont obtenus de sa connexion au réseau, d’où les interminables téléchargements lors de la première exécution !

Cependant, nos sources Groovy ne sont pas prises en compte, et les traces d’exécution de la console ne laissent entendre aucun traitement particulier de ce langage. Nous avons dû brûler une étape…

Plugin et tâches

La notion de plugin permet à Maven d’isoler, dans un sous-projet dédié la gestion, des opérations élémentaires qui sont utilisées pour construire divers projets. Cela ne signifie pas pour autant qu’un plugin n’est concerné que par un seul traitement. Si l’on reprend l’exemple du plugin de compilation, celui-ci doit compiler le code source Java de l’application, mais aussi le code source des tests. Un plugin regroupe donc des tâches élémentaires qui partagent un même domaine.

Chaque plugin définit ainsi plusieurs tâches (ou goals) et il ne suffit pas de déclarer un plugin pour ajouter un traitement à notre projet, nous devons également préciser lequel (ou lesquels) de ces traitements unitaires nous souhaitons intégrer à la construction du projet.

<build>

    <plugins>

        <plugin>

            <groupId>org.codehaus.groovy.maven</groupId>

            <artifactId>gmaven-plugin</artifactId>

            <version>1.0</version>

            <executions>

                <execution>

                    <goals>

                        <goal>compile</goal>

                    </goals>

                </execution>

             </executions>

        </plugin>

    </plugins>

</build>

Un élément <execution> permet de définir les tâches définies par le plugin considéré que Maven devra exécuter.

Figure 03-03

Le cycle de vie du projet et les plugins qui viennent s’y greffer.

Miracle, nous pouvons compiler notre code source Groovy. Sortez la boule à facettes !

Compiler… en JavaScript

Avec ce putsch de Groovy sur le projet, Arnaud a réussi un tour de force. Pour ne pas le laisser s’endormir sur ses lauriers, Nicolas relève le défi de secouer une nouvelle fois nos petites habitudes.

MangaNicolas

Notre application dispose d’une interface web qui permet de saisir sa liste de courses depuis n’importe quel navigateur. C’est le cas de très nombreuses applications J2EE, qui exploitent le navigateur comme environnement universel pour exécuter une application sans que vous deviez rien installer sur votre ordinateur. Il est d’ailleurs très probable que vous consultiez le solde de votre compte bancaire de cette façon !

Les premiers jets de cette "application web" fonctionnent mais sont assez peu sexy. Rien à voir avec ces sites hauts en couleur et en effets visuels qui parsèment le Web et qui révolutionnent notre utilisation d’Internet. Nicolas s’attarde donc quelques instants sur le tableau blanc que nous utilisons pour griffonner nos dernières idées… et le tableau est rapidement noir de petits croquis, de flèches en tout genre et de notes sur le comportement idéal de notre site web.

Figure 03-04

Notre document officiel de spécifications pour l’application web.

Les réactions ne tardent pas : c’est bien joli, mais qui se sent les épaules de faire tout ça ? Et avec quel outil ? Nous n’y connaissons rien en JavaScript, le langage utilisé sur les navigateurs web pour animer les pages. Avant que la surprise ne laisse la place à une réaction épidermique face à l’ampleur de la tâche, Nicolas lâche son arme secrète : GWT.

Info

Google Web Toolkit (GWT) est un outil développé par Google pour offrir aux développeurs Java les portes du Web. Capable de traduire en JavaScript du code source Java, il permet à ces derniers de conserver le confort de leur langage préféré et de leur outillage habituel, tout en développant des applications web qui réagissent au moindre mouvement de souris. La prouesse technique est impressionnante, et les portes que cela ouvre aux développeurs Java ne font encore que s’entrouvrir.

Une petite démonstration sur le PC portable qui traînait comme par hasard sur un coin de table fait taire les derniers incrédules. Effectivement, développer pour le Web n’est finalement pas si compliqué que ça. Reste à faire tourner cet ovni issu de la galaxie Google dans un projet Maven ! Heureusement pour nous, d’autres ont eu le même souci et un plugin est disponible pour marier GWT avec notre projet.

      <plugin>

        <groupId>org.codehaus.mojo</groupId>

        <artifactId>gwt-maven-plugin</artifactId>

        <version>2.2.0</version>

        <executions>

          <execution>

            <goals>

              <goal>compile</goal>

              <goal>generateAsync</goal>

            </goals>

          </execution>

        </executions>

        <configuration>

          <extraJvmArgs>-Xmx512M</extraJvmArgs>

        </configuration>

      </plugin>

Comme pour l’intégration de Groovy, nous n’avons au niveau du projet Maven qu’à ajouter l’identifiant exact du plugin utilisé, définir une éventuelle configuration si les valeurs par défaut ne nous conviennent pas, et préciser dans une <execution> quelles tâches doivent être exécutées lors de la construction du projet.

En consultant la documentation du plugin GWT[14], nous découvrons quelque chose qui nous intéresse : la tâche eclipse du plugin propose de générer automatiquement des scripts de lancement pour exécuter directement l’application web depuis notre environnement de développement – au moins pour ceux d’entre nous qui utilisent Eclipse !

Nous ajoutons cette tâche à notre execution, et nous lançons en toute confiance un mvn install :

…​

[INFO] -----------------------------------------------------------------

[INFO] BUILD SUCCESSFUL

[INFO] -----------------------------------------------------------------

INFO] Total time: 50 seconds

Voilà qui est encourageant… mais pas grand chose de visible concernant notre application web ! Rien d’étonnant à cela : le lancement du serveur web pour faire une démo de notre IHM n’est pas une étape standard de la construction d’un projet. Comment Maven pourrait-il connaître notre intention et déterminer les étapes nécessaires à la construction du projet ?

Invoquer un plugin

Les commandes que nous avons passées jusqu’ici étaient de la forme mvn xxx, avec pour xxx la phase de construction du projet que nous désirerions atteindre, par exemple compile. Maven permet également d’invoquer directement un plugin, et lui seul, via une forme différente de la ligne de commande :

mvn gwt:run

Ici, nous ne demandons pas la construction du projet, mais l’exécution isolée de la tâche run du plugin gwt. Il s’agit d’ailleurs d’une version contractée de la commande complète :

mvn org.codehaus.mojo:gwt-maven-plugin:2.2.0:run

Le raccourci est appréciable, mais il vaut mieux garder en tête cette syntaxe qui pourra parfois se révéler indispensable.

L’invocation directe d’un plugin n’est généralement utile que pour des tâches annexes du projet, comme ici le lancement du serveur web de développement de GWT. La plupart des plugins et des tâches qu’ils définissent sont prévus pour se greffer dans le cycle de construction du projet. Il est donc inutile d’invoquer directement une tâche d’un plugin qui n’a pas été prévu dans ce sens ; d’ailleurs, cela aboutirait dans la majorité des cas à une erreur.

Cette nouvelle découverte nous amène à nous demander ce qui différencie dans ce plugin GWT la tâche run de la tâche compile. La première s’exécute seule par invocation directe, la seconde sait se greffer dans le cycle de construction du projet. Mais comment fait Maven pour déterminer quand l’exécuter ?

Cycle de vie

Ce que nous avons jusqu’ici qualifié de "projet type" utilisé par Maven pour identifier et enchaîner les tâches de base d’un projet Java est en réalité composé de deux éléments : le cycle de vie d’un côté et les plugins et tâches qui y sont attachés de l’autre.

Le cycle de vie est une série de phases ordonnées qui doit couvrir les besoins de tout projet. Ces phases sont purement symboliques et ne sont associées à aucun traitement particulier, mais elles permettent de définir les étapes clés de la construction du projet. On retrouve ainsi :

Tableau 3.1 : Le cycle de vie défini par Maven

 

Phase Description

validate

validation du projet Maven

initialize

initialisation

generate-sources

génération de code source

process-resources

traitement des fichiers de ressources

compile

compilation des fichiers sources

process-classes

Post-traitement des fichiers binaires compilés

test-compile

compilation des tests

test

exécution des tests

package

assemblage du projet sous forme d’archive Java

install

mise à disposition de l’archive sur la machine locale pour d’autres projets

deploy

mise à disposition publique de l’archive java

Il s’agit d’une liste simplifiée : le cycle complet définit de nombreuses phases intermédiaires, dont vous trouverez la description complète dans la documentation en ligne de Maven[15].

Quels que soient le projet et ses particularités, tout traitement réalisé pour le "construire" viendra naturellement se greffer sur l’une de ces étapes.

Pour un projet standard (sans indication de <packaging>), Maven considère que le binaire à construire est une archive JAR. Chaque plugin propose des tâches qui correspondent à un traitement unitaire. Maven associe un certain nombre de tâches à ces phases du cycle de vie. La tâche compile du plugin de compilation, par exemple, est associée à la phase compile, et la tâche jar du plugin d’archivage à la phase package. L’invocation de la commande mvn deploy va alors dérouler une à une les étapes du cycle de vie jusqu’à la phase demandée (deploy), et exécuter pour chacune d’elles les tâches des plugins qui lui sont associés :

Tableau 3.2 : Les plugins et les tâches associés par défaut au cycle de vie d’un projet JAR

Phase

Plugin

Tâche

process-resources

maven-resources-plugin

Resource

compile

maven-compiler-plugin

Compile

process-test-resources

maven-resources-plugin

testResources

test-compile

maven-compiler-plugin

testCompile

test

maven-surefire-plugin

Test

package

maven-jar-plugin

Jar

intall

maven-install-plugin

Install

deploy

maven-deploy-plugin

Deploy

Maven fournit un moyen pour venir greffer d’autres plugins à ce cycle, en plus de ceux qu’il aura associés par défaut.

Figure 03-05

Cycle de vie du projet et plugins exécutés pour chaque phase.

Générer du code

Suite aux nombreuses évolutions que nous avons apportées, notre projet est aujourd’hui capable d’invoquer des services Web SOAP pour s’intégrer avec d’autres applications. Ce code a été développé via l’un des nombreux assistants qui peuplent les environnements de développement intégrés modernes. Nous lui avons fait ingurgiter le WSDL du système partenaire et il a généré pour nous un squelette de code que nous n’avons eu qu’à compléter.

L’intégration de générateurs de code dans les environnements de développement, masqués derrière des interfaces graphiques colorées et des barres de progression, nous ferait presque oublier la complexité technique de ces outils. Nous allons pourtant être rapidement rappelés à l’ordre.

Après une migration technique importante, notre partenaire nous transmet la nouvelle version de son contrat de service web, un nouveau fichier WSDL. Seulement Fabrice, responsable de la mise en œuvre de ce service web, est en congé aux Caraïbes pour un mois. Il va donc falloir se débrouiller sans lui.

Première question : comment utilise-t-on ce fameux assistant de création de service web ? Les options sont nombreuses et, sans un bon bagage technique, il nous est difficile de savoir lesquelles choisir. La stratégie du "tout par défaut" ne nous garantit pas la pertinence du résultat.

Seconde interrogation : les classes précédemment générées avaient-elles été modifiées ? Nous pourrions écraser purement et simplement le package Java correspondant au code généré, mais sommes-nous sûrs que Fabrice n’y a pas fait des adaptations ?

En fouillant dans les notes de Fabrice, nous trouvons heureusement le petit guide du développeur de service web qui répond à nos questions (et donc pas besoin de le déranger d’urgence durant ses vacances bien méritées).

Astuce

Nous ne doutons pas que, sur vos projets, vous disposiez d’une documentation très complète et toujours à jour pour décrire ces procédures. Pensez tout de même au temps que nécessite la maintenance de ces documents et au temps perdu par un néophyte pour se plonger dedans quand il en a besoin.

Cela ne signifie pas pour autant que Maven rende un système documentaire inutile. Cependant, autant que possible, automatisez et simplifiez les choses et ayez plutôt le réflexe wiki que document de synthèse validé par quinze relecteurs.

Maven propose une autre approche à ce problème, une fois de plus via ses plugins. Rappelons que, pour Maven, le répertoire src ne doit contenir que le code source et que le répertoire target est dédié à tous les fichiers intermédiaires de la construction du projet. Maven considère que des fichiers générés ne sont pas des fichiers sources, même s’ils sont écrits dans la syntaxe du langage Java. Le fichier source est le contrat WSDL qui permet de les produire. Rien n’interdirait à l’outil de génération de produire directement du code binaire dans des fichiers class (si ce n’est que c’est nettement plus compliqué). Il n’y a donc aucune raison de placer ce code généré dans notre arborescence src.

Le plugin cxf-codegen associé à notre pile de services web Apache CXF sait prendre en charge la procédure de génération de code. Il s’associe à la phase generate-source du cycle de vie qui est prévue pour ce type de plugins. Il prend en paramètre les fichiers WSDL à traiter et les options de génération ; aussi plaçons-nous notre fichier WSDL dans un répertoire de ressources dédié à ce format : src/main/resources/wsdl.

<plugin>

   <groupId>org.apache.cxf</groupId>

   <artifactId>cxf-codegen-plugin</artifactId>

   <version>2.3.3</version>

   <executions>

     <execution>

          <goals>

            <goal>wsdl2java</goal>

          </goals>

     </execution>

   </executions>

   <configuration>

     <defaultOptions>

        <noAddressBinding>true</noAddressBinding>

    </defaultOptions>

   </configuration>

</plugin>

Le code généré par ce plugin est placé dans le répertoire target/generated-sources/cxf. Il s’agit également d’une convention de Maven, qui permet à chaque plugin générateur de code d’avoir son propre répertoire de génération tout en conservant une certaine cohérence :

<répertoire de build "target">/generated-(re)sources/<nom du plugin>

Ces répertoires sont automatiquement ajoutés dans le chemin de compilation du projet et seront donc pris en considération lors de la phase de compilation qui suit. Par ailleurs, étant placés dans l’arborescence target, ce code peut être facilement isolé du code source que nous produisons, par exemple lorsque nous le publierons dans notre gestionnaire de code source.

La stratégie utilisée par Maven pour les générateurs de code résout donc nos deux problèmes : la procédure de génération n’a tout simplement plus besoin d’être documentée. Elle est systématiquement exécutée, ce qui a l’avantage non négligeable de nous assurer la totale cohérence entre le fichier source qui sert à cette génération et qui seul fait foi, et le code qui en est dérivé et est utilisé par l’application. Ensuite, une modification du code généré est tout simplement impossible, celui-ci n’étant pas sauvegardé dans le gestionnaire de sources. Il vous suffit généralement d’étendre les classes générées pour développer le code propre à votre application, plutôt que de venir modifier ce code et de risquer de tout perdre lors de la génération suivante, ou de devoir comparer deux versions et reporter manuellement vos modifications.

Le seul inconvénient de cette pratique est que le générateur de code sera invoqué à chaque construction du projet par Maven. L’outil de génération peut être assez lourd et son lancement systématique, pénalisant pour votre productivité. Aussi, les plugins Maven associés utilisent généralement des mécanismes permettant de ne lancer la génération que lorsque c’est réellement nécessaire, soit parce que le répertoire de génération n’existe pas, soit parce que le fichier qui sert de référence a été modifié.

Produire autre chose qu’un JAR

MangaFrançois

Amusé par notre interface web en GWT, François se joint à notre équipe. La particularité de François est qu’il n’est pas seulement un développeur Java, mais aussi  un spécialiste de la plateforme Flex d’Adobe. Il décide donc de nous développer une interface web faisant appel à toute la richesse du plugin Flash.

Comme pour les cas précédents, nous découvrons avec plaisir qu’il existe un plugin Maven, le projet Flex-mojos[16], qui prend en charge la compilation spécifique des sources Flex. Cependant, Flex n’est pas Java, et une application Flash s’assemble sous forme d’un fichier SWF qui n’a pas grand-chose en commun avec un JAR. Il ne suffira donc pas d’ajouter à notre fichier POM des déclarations de plugins, il faut complètement changer le cycle de vie et les plugins par défaut utilisés par Maven.

Ce cycle de vie par défaut est sélectionné par Maven en fonction de l’élément <packaging> de notre POM, qui prend par défaut la valeur jar. Nous pouvons tout aussi bien lui donner la valeur war pour construire une application web, ou ear pour une archive d’entreprise (voir Chapitre 8). Pour créer une application Flash, nous allons utiliser le packaging SWF. Cette valeur n’est, bien sûr, pas comprise par Maven sans un peu d’aide.

Maven est conçu pour être fortement extensible, aussi l’association du packaging avec un cycle de vie est réalisée à l’exécution et peut être assistée par des compléments, appelés extensions. Un plugin peut lui-même apporter des extensions : c’est le cas du plugin flex-mojos.

Le fichier POM du projet proposé par François inclut donc, par rapport à un projet Java "classique" :

·     Des déclarations classiques de dépendances vers le SDK Adobe Flex, dont les artefacts sont de type SWC et non JAR.

·     La déclaration de répertoire de sources et de tests, propre au langage Flex qu’il utilise.

·     La déclaration du plugin flex-mojos. Le point clé est l’élément <extension>true</extension> qui signale à Maven que ce plugin propose des compléments qu’il faudra prendre en compte avant de déterminer le cycle de vie et les tâches à exécuter.

·     La version du compilateur Flex à utiliser par le plugin. Le plugin n’est pas lié à une version particulière de SDK Flex, aussi l’ajout d’une dépendance au plugin permet de spécifier la version qu’il devra utiliser.

Le Listing 3.1 montre le POM utilisé par François pour son projet qui nous fait mettre un pied en dehors du monde Java.[17]

Listing 3.1 : Production d’un binaire SWF

<project>

   <modelVersion>4.0.0</modelVersion>

   <groupId>fr.noubliepaslalistedescourses</groupId>

   <artifactId>ihm-flash</artifactId>

   <version>1.0</version>

   <packaging>swf</packaging>

 

   <build>

      <sourceDirectory>src/main/flex</sourceDirectory>

      <testSourceDirectory>src/test/flex</testSourceDirectory>

      <plugins>

         <plugin>

            <groupId>org.sonatype.flexmojos</groupId>

            <artifactId>flexmojos-maven-plugin</artifactId>

            <version>4.0-beta-5</version>

            <extensions>true</extensions>

 

         </plugin>

      </plugins>

   </build>

   <dependencies>

      <!-- Flex SDK dependencies -→

      <dependency>

         <groupId>com.adobe.flex.framework</groupId>

         <artifactId>flex-framework</artifactId>

         <version>4.5.0.18623</version>

         <type>pom</type>

      </dependency>

      …​

   </dependencies>

</project>

Note

Si le numéro de version en -beta du plugin vous inquiète, ne perdez pas de vue que ces composants sont développés en open-source, et donc proposent à intervalle régulier des versions intermédiaires. « Release early, release often » est une devise de développement agile très prisée dans ce milieu.

Cela ne signifie pas que les fonctionnalités ne soient pas au rendez-vous. Les versions « finales » ne bénéficient pas d’un meilleur support ou de soins particuliers, et ces versions intermédiaires vous apportent les dernières corrections. Par contre, les versions intermédiaires peuvent remettre en question certaines options, même si la compatibilité et respect des utilisateurs est la règle généralement suivie, aussi vous devrez consulter les release-notes pour adapter votre projet le cas échéant.

Ce fichier POM n’a rien de très différent de ce que nous avons utilisé jusqu’à présent, et pourtant il s’adresse à une plateforme très différente de Java. Maven montre ici ses capacités d’adaptation et d’extensibilité. Un simple plugin dédié à un langage ou à une plateforme différente permet d’utiliser Maven dans un cadre pour lequel il n’a pas du tout été prévu initialement. Le cycle de vie du projet peut être totalement adapté pour des besoins très particuliers, enchaînant les tâches adéquates d’un plugin dédié.

Nous sommes bluffés par la démonstration de François, qui nous présente une interface web d’un très haut niveau, mais nous sommes presque plus stupéfaits de la facilité avec laquelle il a pu intégrer un langage a priori très éloigné du monde Java dans notre mécanisme de construction de projet. Et cela ne s’arrête pas là, nous dit-il, car avec l’utilisation d’outils comme flexunit,  flexpmd, flexcoverage et l’intégration dans sonar ( que nous vous présenterons au chapitre 12) la couverture de services autour des projets flex avec Maven est proche de ce que l’on obtient avec du Java traditionnel.

Des plugins pour tout ?

Dans les exemples que nous venons de voir, nous avons fait appel à différents plugins utilisant des identifiants de groupe variés. Le plugin de compilation est développé dans le cadre de Maven lui-même et porte donc l’identifiant de groupe org.apache.maven.plugins. Quelques plugins de base sont également dans ce cas, et leur documentation est accessible sur le site de Maven[18].

Le plugin GWT est développé dans le cadre du projet Mojo, qui est en fait plus une communauté qu’un projet à proprement parler. Elle regroupe des développeurs qui contribuent à une grande variété de plugins ou expérimentent des idées dans un "bac à sable". Ces plugins sont associés à l’identifiant de groupe org.codehaus.mojo. La liste de ces plugins est longue et s’enrichit régulièrement, vous trouverez très probablement votre bonheur dedans.

Le plugin CXF est, lui, développé en marge du projet Apache CXF, autrement dit l’équipe de développement de ce projet prend elle-même en charge son intégration avec Maven. Ce cas est de plus en plus courant avec la place importante que prend Maven dans le monde Java.

D’autres plugins, développés en marge des grandes fondations ou entreprises contributrices d’open-source,  utilisent leurs propres dépôts. C’est le cas par exemple pour le plugin onejar (http://onejar-maven-plugin.googlecode.com) qui héberge la documentation et le dépôt Maven du plugin directement dans son Subversion. Pour utiliser un dépôt non standard de plugins, il nous faut ajouter au projet la déclaration adéquate :

   <pluginRepositories>

      <pluginRepository>

         <id>onejar-maven-plugin.googlecode.com</id>

      </pluginRepository>

   </pluginRepositories>

De très nombreux plugins vivent indépendamment, développés sur les services d’hébergement SourceForge, Googlecode, GitHub, ou par des sociétés qui le diffusent depuis leur propre site web. Il n’en existe pas d’index officiel ou de catalogue toujours à jour vous permettant de trouver la perle rare. Votre moteur de recherche préféré est souvent votre meilleur ami, associé au forum des utilisateurs de Maven.

Note

Faut-il le préciser, si vous développez votre propre plugin Maven, nous vous encourageons à le publier via le dépôt officiel « central ». Au prix d’un petit effort initial, vous simplifierez grandement la vie de vos utilisateurs. La procédure est expliquée en détail sur le site : http://maven.apache.org/guides/mini/guide-central-repository-upload.html.

Une autre option que nous ne pouvons que vous recommander est de proposer votre plugin en contribution au projet Mojo (http://mojo.codehaus.org), ce qui lui donnera une bien meilleure visibilité, ainsi que l’aide possible des développeurs de la communauté.

Maven2 ne fait pas de séparation très claire entre les dépôts d’artefacts que vous déclarez dans votre POM et vos déclarations dépôts de plugins – un dépôt de plugin étant en général aussi un dépôt d’artefacts.

Maven 3 est plus strict et refusera d’utiliser un plugin, même s’il est présent dans l’un des dépôts déclarés, sauf si vous déclarez clairement le <pluginRepository> adéquat. De même, les dépendances déclarées par le plugin ne seront recherchées que dans son dépôt d’origine et non dans vos propres dépôts.

Une fois encore, ce n’est pas pour vous embêter, mais pour fiabiliser le projet et avoir un build reproductible quelque soit l’environnement. Sans cette précaution, certains plugins peuvent voir leur liste de dépendances modifiées par le projet qui les utilise et se comporter bizarrement.

Conclusion

Maven propose un cadre de développement strict qui permet de compléter le projet grâce à de nombreux plugins sans interaction néfaste entre eux. Via son cycle de vie, chaque plugin trouve sa place et contribue à l’outillage global du projet par petites touches. Un outillage complet du cycle de vie du projet permet de contrôler toutes ses étapes en une seule commande et, surtout, regroupe toutes les infos et tous les paramètres de configuration nécessaire en un seul endroit.

Quels que soient le projet Maven auquel vous participez et son langage, la commande mvn install sera toujours le seul et unique point d’entrée pour construire le projet, en intégrant toutes les étapes nécessaires. La structure du projet sera toujours identique et vous permettra d’être rapidement productif, sans devoir passer par un long "guide de démarrage" précisant les bibliothèques à utiliser et le rôle de chaque paramètre.