I. La théorie de Maven▲
I-A. Philosophie▲
Le Makefile ou Ant réalisent la compilation d'un projet via la réalisation de scripts pour les différentes étapes de la vie du projet. Même avec les directives très abouties de Ant, le script redéfinit pour chaque projet des directives telles que : « compiler les fichiers sources du répertoire src dans le répertoire classes ».
Maven propose de renverser le problème. Ce ne sont plus les opérations à faire qui sont définies, mais le projet qui est décrit. En sachant où se trouvent les éléments dont il a besoin dans le projet, il est à même de réaliser les opérations dessus. On n'écrit plus de script avec Maven.
Maven propose une configuration par défaut très complète qui permet une standardisation de la structure des projets Java. Au besoin, Maven permet de surcharger les configurations pour s'adapter à plus de situations.
Maven 1 était encore mixte dans le passage entre le scripting de tache et la description du projet. Il proposait à la fois la description du projet et la création de script. Maven 2 est plus strict et ne passe plus que par la description du projet. Maven 1 n'est plus supporté et il est conseillé de migrer les anciens projets sur Maven 2. La procédure est facilitée par un plugin Maven 2 dédié. Ce document traite exclusivement de Maven 2.
I-B. Installation et fonctionnement▲
Installation : le plus simple est encore de suivre la procédure officielle Télécharger Maven . Il faut surtout retenir que plusieurs variables d'environnement sont à positionner : JAVA_HOME si ce n'est pas déjà fait, M2_HOME et ajouter le répertoire bin de Maven dans le PATH afin de pouvoir utiliser les commandes Maven.
Utilisation : se positionner en ligne de commandes dans le répertoire du projet. Avec la commande mvn et les goals décrits plus bas, il est alors possible de réaliser toutes les opérations nécessaires sur le projet.
Un projet qui utilise Maven contient simplement un fichier pom.xml qui représente la description du projet pour Maven. Toutes les informations pour le projet sont à renseigner dans ce fichier.
Alors qu'un fichier build.xml de Ant décrit entièrement la procédure pour réaliser une opération technique, le fichier pom.xml décrit le projet et suffit pour que Maven sache le manipuler.
POM est l'acronyme de Project Object Model. Il s'agit donc d'un fichier XML qui a pour but de définir tous les éléments relatifs à un projet au sens large.
Un projet est un artifact dans Maven, il s'agit de la brique de base que manipule Maven. Pour démarrer un pom, il faut au moins définir l'identification de l'artifact du projet. La définition complète d'un artifact est donnée plus loin.
Le fichier pom.xml minimaliste ressemblera donc à ceci :
<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/maven-v4_0_0.xsd"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.developpez</groupId>
<artifactId>
cours-maven-example</artifactId>
<packaging>
jar</packaging>
<name>
Projet d'exemple pour le cours Maven</name>
<version>
1.0</version>
</project>
I-C. Apport de Maven▲
Grâce à la définition du projet dans le pom.xml, Maven 2 est capable de gérer toute la vie du projet. Il traite de la compilation, mais aussi de packaging de l'application (faire une archive War ou Ear n'est pas toujours évident), la gestion des dépendances, la génération des rapports de suivis des développements (checkstyles, tests unitaires…) et même le déploiement de l'application sur un environnement d'exécution.
Généralisé sur une grande majorité des projets Java Open Source (Spring, Hibernate, Struts…), Maven 2 uniformise ainsi la gestion d'un projet Java et offre la possibilité d'utiliser des outils qui permettent l'industrialisation du développement via la génération automatique de rapports ou des systèmes d'intégration continue.
II. Organisation des fichiers▲
Maven propose une structure de fichier complète. Il s'agit de la configuration par défaut, mais elle est surchargeable.
Le principe général est de limiter le répertoire racine du projet à trois éléments : le fichier de description du projet pom.xml , le répertoire src qui contient uniquement les sources du projet et le répertoire target qui contient tous les éléments créés par Maven.
Structure des répertoires |
|
---|---|
src/main/java |
Contient les sources Java de l'application |
src/main/resources |
Contient les ressources de l'application |
src/main/webapp |
Contient les fichiers de l'application web |
src/test/java |
Contient les sources Java pour les tests unitaires |
src/test/resources |
Contient les ressources pour les tests unitaires |
src/site |
Contient les fichiers pour le site |
target |
Répertoire de destination de tous les traitements Maven |
Le tableau ci-dessus ne présente que les répertoires les plus courants. Aucun de ces répertoires n'est indispensable il est tout à fait possible de ne créer, par exemple, que le répertoire src/main/java pour commencer un projet.
Faut-il tout configurer ou laisser les valeurs par défaut ?
Laisser la configuration par défaut apporte la standardisation des projets. Cela peut s'avérer très important. Le passage d'une grande majorité des projets Java à Maven 2 apporte une grande lisibilité. Quelque soit le framework (par exemple Spring ou Hibernate), si on en consulte les sources, on trouve le code Java dans src/main/java, les tests unitaires dans src/test/java, etc. Si on a téléchargé les sources, on pourra même compiler ou packager le projet avec des commandes Maven.
En apportant une première classe Java au projet défini avec le pom.xml créé précédemment, la structure du projet ressemble alors à ceci :
+- cours-maven-example/
+- src
+- main
+- java
+- Test.java
+- pom.xml
Il est alors déjà possible d'utiliser Maven pour compiler les sources du projet. Il faut pour cela utiliser la commande mvn compile (en se positionnant dans le répertoire du projet). Le résultat sera le suivant :
~/cours-maven-example$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [compile]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to ~\cours-maven-example\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Tue Aug 12 11:38:35 CEST 2008
[INFO] Final Memory: 2M/9M
[INFO] ------------------------------------------------------------------------
+- cours-maven-example/
+- src
+- main
+- java
+- Test.java
+- target
+- classes
+- Test.class
+- pom.xml
III. Cycle de vie et goal Maven▲
Maven a pour objectif de gérer tout le cycle de vie de l'application. Il définit pour cela une liste d'étapes ordonnées qui sont pour lui le cycle de vie par défaut d'un projet. Chaque type de projets (jar, war…) pourra associer des actions différentes pour chaque étape.
Le cycle de vie par défaut définit les étapes suivantes :
- validate Valide que le projet est correctement défini ;
- compile Compile les sources ;
- test Lance les tests unitaires ;
- package Prépare la distribution du projet. (archives Jar, War, Ear…) ;
- integration-test Lance les tests d'intégration ;
- verify Lance des tests de validation du package créé ;
- install Installe le package en local sur la machine pour pouvoir être réutilisé comme dépendance ;
- deploy Déploie le package sur un serveur pour qu'il puisse être réutilisé par tout le monde.
Maven gère des concepts qui ne le limitent pas à la manipulation de projets Java. Il existe des plugins Maven dédiés au support d'autres technologies que le Java. C'est le cas notamment de PHP, .net, Ruby, Flex.
III-A. Goals Maven▲
III-A-1. Définition▲
On fournit à Maven 2 une liste de goals à exécuter. Un goal est une tâche précise que Maven est en mesure de réaliser à partir des informations qu'il pourra trouver dans le fichier pom.xml.
À partir de la compréhension qu'il a du projet, il est capable d'exécuter énormément d'opérations dessus, chaque opération correspondant à un goal.
Pour lancer un goal Maven sur un projet, il faut se placer à la racine du projet et exécuter la commande :
mvn <nom du goal>
Il est possible d'enchaîner deux ou plusieurs goals avec la commande :
mvn <nom du goal 1> <nom du goal 2> <nom du goal 3>
III-A-2. Localisation des goals▲
Tous les goals se trouvent dans des plugins Maven. Pour exécuter un goal, Maven va donc commencer par résoudre le nom du goal pour en déduire le plugin dans lequel il se trouve et le télécharger.
Tous les goals maven s'écrivent de la façon suivante :
<nom du plugin>:<nom du goal>
Dans notre projet précédemment créé, on peut lancer le goal compile du plugin compiler :
~/cours-maven-example$ mvn compiler:compile
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'compiler'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [compiler:compile]
[INFO] ------------------------------------------------------------------------
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to ~\cours-maven-example\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Tue Aug 12 14:33:40 CEST 2008
[INFO] Final Memory: 2M/5M
[INFO] ------------------------------------------------------------------------
III-B. Cycle de vie et goals▲
Chaque type de projet définit un ou plusieurs goals par défaut associés aux étapes du cycle de vie. Maven gère alors la progression dans le cycle de vie. Lors du traitement d'un goal associé à une étape du cycle de vie, Maven lancera tous les goals associés aux étapes précédentes.
Il est possible de lancer la commande mvn avec comme paramètre une étape du cycle de vie.
mvn <étape du cycle de vie>
Maven exécute alors dans l'ordre tous les goals correspondants à chaque étape du cycle précédent celle qui a été demandée.
C'est ce mécanisme qui permet d'utiliser des commandes tu type :
mvn compile
ou
mvn install
Ce ne sont pas des noms de goal, mais des noms d'étapes du cycle de vie. Des goals Maven y sont associés : compiler:compile pour le cycle compile, et war:war qui est exécuté pour un install dans un projet de type war (après avoir parcouru les étapes précédentes dans le cycle de vie).
Si on utilise une partie importante du cycle de vie par défaut d'un projet Jar, on peut identifier la liste des goals que Maven utilise : [resources:resources], [compiler:compile], [compiler:testCompile], [surefire:test], [jar:jar], [install:install]
~/cours-maven-example$ mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [install]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to ~\cours-maven-example\target\classes
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] No sources to compile
[INFO] [surefire:test]
[INFO] No tests to run.
[INFO] [jar:jar]
[INFO] Building jar: ~\cours-maven-example\target\cours-maven-example-1.0.jar
[INFO] [install:install]
[INFO] Installing ~\cours-maven-example\target\cours-maven-example-1.0.jar to
~\.m2\repository\com\developpez\cours-maven-example\1.0\cours-maven-example-1.0.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9 seconds
[INFO] Finished at: Tue Aug 12 14:36:05 CEST 2008
[INFO] Final Memory: 8M/14M
[INFO] ------------------------------------------------------------------------
Le cycle clean est une exception. Il ne fait pas partie directement du cycle de vie, mais peut toujours être utilisé. Il permet de nettoyer (supprimer) le répertoire de travail de Maven. Il fait référence au goal clean du plugin clean.
Sur notre projet :
~/cours-maven-example$ mvn clean
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [clean]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] Deleting directory ~\cours-maven-example\target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Tue Aug 12 14:51:24 CEST 2008
[INFO] Final Memory: 3M/5M
[INFO] ------------------------------------------------------------------------
IV. Gestion des plugins Maven▲
IV-A. Dans Maven, tout est plugin▲
Quand on télécharge Maven, il ne comprend que le moteur qui sert à télécharger des plugins. Tous les goals Maven sont dans des plugins même les plus indispensables comme le plugin compiler.
Ainsi, il faut s'attendre à voir Maven télécharger énormément de plugins lors de la première exécution d'un goal.
IV-B. Cartographie▲
IV-B-1. Core▲
clean : nettoie le répertoire de travail du projet : suppression des fichiers générés, etc.
compiler : compilation des sources du projet.
resources : copie les ressources du projet dans le répertoire de build (classes ou test-classes).
site : génère le site web du projet.
surefire : joue les tests unitaires.
Et aussi : deploy, install, verifier
IV-B-2. Packaging▲
jar : construit un jar à partir du projet.
war : construit un war à partir du projet.
Et aussi : ear, ejb, rar, shade.
IV-B-3. Outils▲
archetype : génère une structure de projet vide à partir d'un modèle (voir le chapitre associé).
assembly : génère une distribution de sources / fichiers binaires.
dependency : manipulation et analyse des dépendances.
help : donne des informations sur l'environnement de travail du projet.
Et aussi : ant, antrun, enforcer, gpg, invoker, one, patch, release, remote-resources, repository, scm, source, stage, etc.
IV-B-4. Reporting▲
checkstyle : génère un rapport d'audit de code checkstyle.
javadoc : génère la javadoc du projet.
pmd : génère un rapport PMD.
project-info-reports : génère un rapport standard du projet.
surefire-reports : génère le rapport de tests unitaires.
jdepend : génère un rapport de métriques de code.
cobertura : génère un rapport de couverture de tests.
Findbugs : génère un rapport d'audit de code findbugs.
Et aussi : changelog, changes, clover, doap, docck, jxr, etc.
IV-B-5. IDE▲
eclipse : génère un fichier .project pour intégration du projet dans Eclipse.
Et aussi : idea.
IV-B-6. Tous les plugins▲
IV-C. Configurer les plugins▲
Il est possible de configurer les plugins utilisés dans le pom.xml :
<build>
<plugins>
...
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
2.0.2</version>
<configuration>
<source>
1.5</source>
<target>
1.5</target>
</configuration>
</plugin>
...
</plugins>
</build>
En rappelant le groupId et l'artifactId du plugin, on identifie de façon certaine le plugin. Il est alors possible de spécifier des paramètres génériques ou des paramètres spécifiques au plugin.
Spécifier la version a son importance. Tous les plugins peuvent avoir des mises à jour et par défaut Maven vérifiera une fois par jour si une nouvelle version est disponible. Il arrive que les meilleurs plugins sortent des versions buggées, et sans fixer la version, le projet peut alors poser des problèmes à la compilation d'un jour à l'autre parce que la nouvelle version a été téléchargée automatiquement.
Dans le meilleur des scénarios, grâce à la dépendance entre pom étudié ci-dessous, un projet héritera d'un pom générique qui sélectionne au mieux les versions de chaque plugin.
Les configurations spécifiques au plugin se positionnent dans la balise <configuration>. Chaque paramètre se définit de la façon suivante : <nom du paramètre>valeur</nom du paramètre>
Le plus judicieux est de toujours se référer à la page de rapport du plugin afin de savoir quels paramètres peuvent lui être précisés. Pour trouver un plugin Maven, la recherche Google « maven <nom du plugin> plugin » apportera pratiquement toujours une réponse satisfaisante.
Sur le site du plugin, vous trouverez ensuite toujours la page : « Project Reports/Plugin documentation » . Il s'agit d'un rapport automatique de Maven.
La page d'accueil listera toujours les goals du plugin.
Cliquer sur un goal proposera la liste des paramètres qui peuvent lui être passés.
Pour s'assurer de compiler notre projet avec Java 5, il faut donc enrichir le fichier pom.xml de cette façon :
<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/maven-v4_0_0.xsd"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.developpez</groupId>
<artifactId>
cours-maven-example</artifactId>
<packaging>
jar</packaging>
<name>
Projet d'exemple pour le cours Maven</name>
<version>
1.0</version>
<build>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
2.0.2</version>
<configuration>
<source>
1.5</source>
<target>
1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
V. Repository Maven▲
V-A. Définition d'un artifact▲
Tous les artifacts gérés par Maven sont identifiés par quatre informations :
- groupId : Définit l'organisation ou groupe qui est à l'origine du projet. Il est formulé sous la forme d'un package Java (org.archetypejs par exemple) ;
- artifactId : Définit le nom unique dans le groupe pour le projet. ;
- version : Définit la version du projet. Les numéros de version sont souvent utilisés pour des comparaisons et des mises à jour ;
- type : Type de projet. On trouvera le plus souvent : jar, war, maven-plugin, pom…
V-B. Repository Maven 2▲
Maven 2 définit une structure de fichiers qui permet de publier les artifacts. Cette structure de fichiers est souvent publiée sur le web. En connaissant simplement la racine, Maven peut alors chercher sur le site tout ce dont il a besoin. Tous les repository Maven ne contiennent pas forcement tous les artifacts.
Que ce soit pour les plugins Maven ou pour les dépendances ci-dessous, Maven gère un localrepository là où il s'exécute. Il y recopie tout ce qu'il télécharge.
Le localrepository se trouve toujours par défaut dans le répertoire ~/.m2/repository (Sous Windows, cela donne : C:\Document And Settings\<User>\.m2\repository). Parcourir ce répertoire permet de se faire une idée de la façon dont Maven gère ses fichiers. Il est rarement utile d'aller modifier directement des fichiers dans ce répertoire.
La structure dans un repository est la suivante : le groupId est divisé sur le caractère « . », chaque morceau est utilisé pour créer des répertoires du même nom (« com.developpez » devient « com/developpez »). Ensuite on retrouve un répertoire correspondant à l'artifactId et enfin un répertoire pour le numéro de version. Dans ce dernier répertoire, on trouve alors tous les fichiers constituant l'artifact.
Maven vérifie d'abord dans son localrepository la disponibilité d'un projet avant de le chercher sur Internet. Ainsi il n'y a que la première exécution d'un nouveau goal ou d'une nouvelle résolution de dépendance qui prend du temps. La seconde fois, les paquets seront déjà disponibles en local.
V-C. Configurer des repositories▲
Maven utilise, par défaut, un serveur central qui contient énormément de jar et pratiquement tous les plugins de base de Maven.
Pour ajouter des repository, il faut ajouter dans le pom.xml :
<repositories>
<repository>
<id>
repo</id>
<name>
repo name</name>
<url>
url</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>
plugin repo</id>
<name>
plugin repo name</name>
<url>
url</url>
</pluginRepository>
</pluginRepositories>
Comme on peut le voir, Maven différencie les repository qui contiennent les plugins de ceux qui contiennent les dépendances.
Par défaut dans Maven, un repository principal est configuré. Il est extrêmement bien fourni et suffit souvent à trouver les frameworks les plus courants. Même s'il a eu des problèmes de disponibilités dans le passé, il est maintenant très stable. Ses références sont les suivantes :
<repositories>
<repository>
<id>
central</id>
<name>
Maven Repository Switchboard</name>
<layout>
default</layout>
<url>
http://repo1.maven.org/maven2</url>
<snapshots>
<enabled>
false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>
central</id>
<name>
Maven Plugin Repository</name>
<url>
http://repo1.maven.org/maven2</url>
<layout>
default</layout>
<snapshots>
<enabled>
false</enabled>
</snapshots>
<releases>
<updatePolicy>
never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
Il n'y a pas si longtemps, il était inévitable de rajouter plusieurs autres repository public notamment pour trouver les artifact JBoss (dont Hibernate), etc. Ce n'est plus le cas, ils sont tous aujourd'hui centralisés dans le repository central.
Pour des questions de performances et de centralisation, il est aujourd'hui préférable de n'ajouter des repositories que pour des repositories privés ou pour des projets en devenir qui ne sont pas encore sur le repository central.
Le cycle install déploie le projet dans le repository local. Après avoir lancé mvn install , il est possible de vérifier que notre projet a été copié dans le repository local :
+- ~
+- .m2
+- repository
+- com
+- developpez
+- cours-maven-example
+- 1.0
+- cours-maven-example-1.0.jar
+- cours-maven-example-1.0.pom
VI. Gestion des dépendances du projet▲
Pour en revenir aux principes de Maven, le répertoire src ne doit contenir que des fichiers sources apportés au projet. Dans cette idée, des librairies externes utilisées par le projet ne doivent être que des liens vers d'autres artifacts Maven et surtout pas copiées dans le répertoire src du projet.
Maven propose de le définir par configuration dans le pom.xml. C'est ensuite le plugin Maven de gestion de dépendances qui ira télécharger sur les repositories distants les fichiers jar indiqués comme dépendances, s'ils ne se trouvent pas dans le repository local.
VI-A. Dépendances du projet▲
La configuration à ajouter dans le fichier pom.xml est la suivante :
<dependencies>
...
<dependency>
<groupId>
org.springframework</groupId>
<artifactId>
spring-beans</artifactId>
<version>
2.0.5</version>
<type>
jar</type>
<scope>
compile</scope>
</dependency>
...
</dependencies>
Un grand nombre de jars est disponible sur les repositories Maven. Pour les trouver, le plus simple est d'utiliser le « Google » des paquets Maven : http://www.mvnrepository.com/
Les seuls paramètres obligatoires sont le groupId et l'artifactId.
Il est très vivement recommandé de toujours spécifier la version. Sans cela, Maven utilise toujours la dernière version en date. Il est tout à fait possible que la mise à jour d'une dépendance publiée dans une version alpha soit automatiquement utilisée et empêche le projet de tourner alors qu'aucune modification n'y a été apportée.
Le type n'est jamais nécessaire sauf dans des certains cas.
Le paramètre scope est parfois nécessaire. Les différentes valeurs à prendre en compte sont les suivantes :
- compile : C'est la valeur par défaut, la dépendance sera toujours disponible dans le classpath ;
- provided : Indique que la dépendance est nécessaire pour la compilation, mais sera fournie par le container ou le JDK et donc ne sera pas fournie dans le package ;
- runtime : Indique que la dépendance est nécessaire pour l'exécution, mais pas pour la compilation ;
- test : Indique que la dépendance est nécessaire pour la compilation et l'exécution des tests unitaires.
Le scope provided est très intéressant pour les servlet. Les jars sont fournis automatiquement par Tomcat (ou Jetty…), mais il est nécessaire de les avoir pour la compilation.
Par exemple, si on veut utiliser log4j dans notre projet, il faut enrichir le pom.xml avec la dépendance :
<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/maven-v4_0_0.xsd"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.developpez</groupId>
<artifactId>
cours-maven-example</artifactId>
<packaging>
jar</packaging>
<name>
Projet d'exemple pour le cours Maven</name>
<version>
1.0</version>
<dependencies>
<dependency>
<groupId>
log4j</groupId>
<artifactId>
log4j</artifactId>
<version>
1.2.13</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
2.0.2</version>
<configuration>
<source>
1.5</source>
<target>
1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
On peut alors ajouter dans le code Java l'utilisation de la dépendance.
package
com.developpez;
import
org.apache.log4j.Logger;
public
class
Test {
Logger logger =
Logger.getLogger
(
this
.getClass
(
));
public
void
main
(
String[] args) {
logger.debug
(
"Hello, World"
);
}
}
Enfin, lors de la compilation, la dépendance sera automatiquement résolue :
~/cours-maven-example$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [compile]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
Downloading: http://repo1.maven.org/maven2/log4j/log4j/1.2.13/log4j-1.2.13.pom
389b downloaded
Downloading: http://repo1.maven.org/maven2/log4j/log4j/1.2.13/log4j-1.2.13.jar
349K downloaded
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to ~\cours-maven-example\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6 seconds
[INFO] Finished at: Tue Aug 12 15:38:23 CEST 2008
[INFO] Final Memory: 3M/8M
[INFO] ------------------------------------------------------------------------
VI-B. Gestion précise des dépendances▲
VI-B-1. Dépendances transitives▲
La gestion des dépendances de Maven permet des dépendances transitives. Si un artifact X dépend d'un artifact Y qui dépend d'un artifact Z, la résolution des dépendances de X trouvera Y et Z.
Ce mécanisme implique souvent le téléchargement de beaucoup de librairies. Chaque artifact va dépendre de tous les autres dont il est susceptible d'avoir besoin.
La réponse à la multiplication des dépendances est la division en modules des grands frameworks (voir plus loin les projets modulaires). Cela permet de n'utiliser que certains morceaux d'un framework et de s'abstraire des dépendances des modules qu'on n'utilisera pas.
Une commande bien utile permet d'afficher dans la console la résolution des dépendances de Maven sur le projet. Cela permet souvent de découvrir pourquoi un artifact que l'on ne veut pas est inclus dans notre projet.
mvn dependency:tree
Sur notre projet, nous pouvons voir le résultat de cette commande :
~/cours-maven-example$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'dependency'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [dependency:tree]
[INFO] ------------------------------------------------------------------------
[INFO] [dependency:tree]
[INFO] com.developpez:cours-maven-example:jar:1.0
[INFO] \- log4j:log4j:jar:1.2.13:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12 seconds
[INFO] Finished at: Tue Aug 19 13:40:35 CEST 2008
[INFO] Final Memory: 8M/18M
[INFO] ------------------------------------------------------------------------
VI-B-2. Exclusions▲
En allant plus loin, il est possible de trouver des situations ou les dépendances transitives posent problème.
Par exemple, une dépendance transitive sur un framework dans une version trop vieille peut poser problème si votre application utilise une version récente. Suivant les versions de Maven et le plugin qui utilise la résolution de dépendance, il n'est pas possible de savoir précisément quelle version de l'artifact sera utilisée. Notamment dans les packagings war, il est possible que les deux fichiers jar avec les deux versions différentes soient présents dans le répertoire WEB-INF/lib.
Pour gérer ce cas de figure, il faut utiliser les exclusions qui permettent d'interdire les dépendances transitives. La syntaxe sera la suivante :
<dependencies>
...
<dependency>
<groupId>
org.hibernate</groupId>
<artifactId>
hibernate</artifactId>
<version>
3.2.6.ga</version>
<exclusions>
<exclusion>
<groupId>
net.sf.ehcache</groupId>
<artifactId>
ehcache</artifactId>
</exclusion>
</exclusions>
</dependency>
...
</dependencies>
L'exclusion ne fonctionne qu'à un seul niveau de profondeur. L'exemple ci-dessous fonctionne, car EhCache est une dépendance directe d'Hibernate. Si EhCache était une dépendance d'une dépendance d'Hibernate, l'exclusion n'aurait aucun effet.
Sur notre projet, pour ajouter la dernière version d'Hibernate avec la dernière version d'EhCache, il faudrait ajouter le code suivant :
<dependencies>
<dependency>
<groupId>
log4j</groupId>
<artifactId>
log4j</artifactId>
<version>
1.2.13</version>
</dependency>
<dependency>
<groupId>
org.hibernate</groupId>
<artifactId>
hibernate</artifactId>
<version>
3.2.6.ga</version>
<exclusions>
<exclusion>
<groupId>
net.sf.ehcache</groupId>
<artifactId>
ehcache</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>
net.sf.ehcache</groupId>
<artifactId>
ehcache</artifactId>
<version>
1.5.0</version>
</dependency>
</dependencies>
L'arbre des dépendances ressemble alors à cela :
~/cours-maven-example$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'dependency'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [dependency:tree]
[INFO] ------------------------------------------------------------------------
[INFO] [dependency:tree]
[INFO] com.developpez:cours-maven-example:jar:1.0
[INFO] +- log4j:log4j:jar:1.2.13:compile
[INFO] +- org.hibernate:hibernate:jar:3.2.6.ga:compile
[INFO] | +- javax.transaction:jta:jar:1.0.1B:compile
[INFO] | +- commons-logging:commons-logging:jar:1.0.4:compile
[INFO] | +- asm:asm-attrs:jar:1.5.3:compile
[INFO] | +- dom4j:dom4j:jar:1.6.1:compile
[INFO] | +- antlr:antlr:jar:2.7.6:compile
[INFO] | +- cglib:cglib:jar:2.1_3:compile
[INFO] | +- asm:asm:jar:1.5.3:compile
[INFO] | \- commons-collections:commons-collections:jar:2.1.1:compile
[INFO] \- net.sf.ehcache:ehcache:jar:1.5.0:compile
[INFO] +- backport-util-concurrent:backport-util-concurrent:jar:3.1:compile
[INFO] \- net.sf.jsr107cache:jsr107cache:jar:1.0:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12 seconds
[INFO] Finished at: Tue Aug 19 13:40:35 CEST 2008
[INFO] Final Memory: 8M/18M
[INFO] ------------------------------------------------------------------------
VI-C. Installation du projet▲
Tous les projets sont définis comme des paquets Maven. Il est donc possible de publier ces paquets.
Tout d'abord pour publier dans le localrepository, il suffit d'utiliser le goal install :
mvn install
Pour l'installer sur un repository externe, il faut lui configurer dans le pom.xml la gestion de la distribution :
<distributionManagement>
<repository>
<id>
ganesh3-repo</id>
<name>
Ganesh Repository for Maven2</name>
<url>
file://${deploy.repository}</url>
</repository>
</distributionManagement>
L'URL peut être exprimée au moyen de beaucoup de protocoles, ici on voit file, mais cela peut être également scp, http (à condition qu'il y ait un webdav), etc.
VI-D. Ajouter un jar dans un repository▲
On finit toujours par utiliser un jar qui n'est sur aucun repository Maven. Pourtant, les principes de Maven nous interdisent d'ajouter un jar directement dans les sources du projet.
Pour venir à bout de cette particularité, Maven propose la possibilité d'ajouter manuellement des artifacts dans les repository. Une fois installé, il est possible d'en dépendre de la façon habituelle.
Pour installer dans le repository local un artifact à partir d'un fichier, il faut utiliser le goal install:install-file . La commande n'est pas très belle à voir, car elle prend énormément de paramètres. Il ne faut pas en avoir peur, il s'agit simplement de renseigner en ligne de commande les informations nécessaires à définir l'artifact qui correspondra au fichier qu'on installe :
mvn install:install-file -Dfile=your-artifact-1.0.jar \
[-DpomFile=your-pom.xml] \
[-DgroupId=org.some.group] \
[-DartifactId=your-artifact] \
[-Dversion=1.0] \
[-Dpackaging=jar] \
[-Dclassifier=sources] \
[-DgeneratePom=true] \
[-DcreateChecksum=true]
Il existe la même commande pour installer un artifact dans un repository distant. Il s'agira cette fois-ci du goal deploy:deploy-file .
mvn deploy:deploy-file -Durl=file://C:\m2-repo \
-DrepositoryId=some.id \
-Dfile=your-artifact-1.0.jar \
[-DpomFile=your-pom.xml] \
[-DgroupId=org.some.group] \
[-DartifactId=your-artifact] \
[-Dversion=1.0] \
[-Dpackaging=jar] \
[-Dclassifier=test] \
[-DgeneratePom=true] \
[-DgeneratePom.description="My Project Description"] \
[-DrepositoryLayout=legacy] \
[-DuniqueVersion=false]
VI-E. Proxy d'entreprise▲
Si on prend les sources d'un projet Maven, elles ne contiennent pas les dépendances. Pourtant, dès qu'on lancera une commande de compilation, les dépendances seront téléchargées sur le poste.
Ce mécanisme est très puissant, mais repose sur une supposition qui peut avoir ses limites : toutes les librairies sont toujours disponibles sur Internet. Le corollaire est que si certains serveurs web sont en panne au moment où l'on désire compiler notre projet, la compilation va échouer.
Il est également souvent nécessaire dans une entreprise de posséder un repository interne qui permet de rendre accessibles facilement les librairies de l'entreprise.
Le principe du proxy d'entreprise répond à ces attentes. Son fonctionnement est le suivant : lorsqu'une instance de Maven sur un poste de développeur demande un artifact, il s'adresse au proxy (via la configuration dans le pom). Le proxy va alors chercher l'artifact sur Internet et lui rendre. Lors de la seconde demande, l'artifact sera immédiatement disponible sur le proxy.
Le plus souvent, le proxy d'entreprise propose aussi la fonctionnalité de repository d'entreprise et propose des solutions simplifiées pour déployer des artifact dessus.
Les solutions les plus courantes pour fournir ce service sont les suivantes :
VII. Ressources et filtrage▲
VII-A. Ressources▲
Dans un projet Maven, src/main/java correspond au répertoire principal dans lequel doivent se trouver toutes les sources applicatives du projet. Néanmoins il est très courant d'avoir des fichiers de configuration à positionner dans le classpath.
Le répertoire src/main/resources répond à cette attente. Il s'agit d'un autre répertoire qui sera compilé et ajouté au classpath. Son rôle est de contenir l'ensemble des fichiers de configuration du projet.
Le répertoire src/main/resources est configuré par défaut dans Maven comme étant un répertoire de ressource. Pour en ajouter un autre il est possible d'ajouter au pom le code suivant :
<build>
...
<resources>
<resource>
<directory>
src/main/config</directory>
</resource>
</resources>
...
</build>
VII-B. Filtrage▲
Mais les ressources n'ont pas pour seul objectif de séparer les fichiers de config pour améliorer la visibilité. Même si ce n'est pas activé par défaut, le principal atout est de pouvoir réaliser du filtrage sur ces fichiers.
Filtrer les fichiers correspond à remplacer à l'intérieur de tous les fichiers et à chaque compilation tous les ${properties} par leur valeur. Pour activer le filtrage sur un répertoire de ressources, il faut ajouter le code suivant :
<build>
<resources>
<resource>
<directory>
src/main/resources</directory>
<filtering>
true</filtering>
</resource>
</resources>
</build>
Par défaut, les properties qui seront utilisés pour le filtrage sont les properties actifs dans Maven, il y en a plusieurs positionnées par défaut tel que pom.name, pom.version…
Il est possible d'ajouter un fichier de properties qui sera utilisé pour le filtrage. Ils sont alors positionnés par convention dans le répertoire src/main/filters et se configurent avec le code suivant :
<build>
<filters>
<filter>
src/main/filters/filter.properties</filter>
</filters>
</build>
VII-C. Stratégies et risques du filtrage▲
Le filtrage a été fait pour gérer les configurations par environnements pour les projets. Il y a plusieurs stratégies possibles pour gérer les environnements :
- sélectionner le fichier de filtrage en fonction de l'environnement : <filter>src/main/filters/${env}.properties</filter> ;
- gérer des profils (voir le chapitre consacré aux profils) afin de positionner des properties par profils.
Quelle que soit la solution utilisée, cela permet de sélectionner l'environnement pour lequel on veut configurer l'application et les fichiers de configuration seront automatiquement positionnés avec les bonnes valeurs.
Le risque de cette méthode réside dans la phase de développement. Si dans la phase de développement, la gestion de la compilation n'est pas gérée par Maven, le filtrage ne sera pas effectué. Comme les fichiers du répertoire resources seront remplis de ${bdd.url} (par exemple), ils ne pourront être utilisés directement.
L'utilisation des filtres lie donc le développeur avec le mécanisme de compilation de Maven.
VII-D. Stratégie de sélection des ressources▲
Il peut être intéressant de choisir un répertoire de ressources avec un nom dépendant de l'environnement :
<build>
<resources>
<resource>
<directory>
src/main/resources-${env}</directory>
</resource>
</resources>
</build>
L'avantage est que si l'IDE ne gère pas le filtrage, il est possible de lui donner le répertoire de ressources à utiliser en local, il ne connaitra pas les ressources pour les autres environnements, mais cela ne l'empêchera pas de fonctionner correctement.
Cela nécessite par contre de dupliquer tous les fichiers de configuration présents dans ces répertoires pour en avoir une version dans chaque répertoire.
Dans notre projet, nous allons rajouter en ressource le fichier de configuration log4j.properties pour qu'il soit filtré en fonction de l'environnement.
Pour cela, on précise que le répertoire resources doit être filtré, que le fichier de filtrage est ${env}.properties.
Afin de donner une valeur par défaut à la variable env, on rajoute la balise properties au bas du pom. Si une valeur est donnée à la variable env par la commande « -Denv=xxx » elle sera prioritaire.
<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/maven-v4_0_0.xsd"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.developpez</groupId>
<artifactId>
cours-maven-example</artifactId>
<packaging>
jar</packaging>
<name>
Projet d'exemple pour le cours Maven</name>
<version>
1.0</version>
<dependencies>
<dependency>
<groupId>
log4j</groupId>
<artifactId>
log4j</artifactId>
<version>
1.2.13</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>
src/main/resources</directory>
<filtering>
true</filtering>
</resource>
</resources>
<filters>
<filter>
src/main/filters/${env}.properties</filter>
</filters>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
2.0.2</version>
<configuration>
<source>
1.5</source>
<target>
1.5</target>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<env>
LOCAL</env>
</properties>
</project>
Dans les fichiers, on rajoute le fichier src/main/resources/log4j.properties ainsi :
log4j.rootLogger=${log.level}, CONSOLE_APP
log4j.appender.CONSOLE_APP=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE_APP.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE_APP.layout.ConversionPattern= %d{dd-MM-yyyy HH:mm:ss:SSS} %-4r %-5p %c %x - %m%n
On crée les fichiers src/main/filters/LOCAL.properties et src/main/filters/PROD.properties dans lesquels on définit la clé log.level dans le premier à DEBUG , dans le second à WARN .
Après compilation par mvn compile , on trouvera le fichier target/classes/log4j.properties
log4j.rootLogger=DEBUG, CONSOLE_APP
log4j.appender.CONSOLE_APP=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE_APP.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE_APP.layout.ConversionPattern= %d{dd-MM-yyyy HH:mm:ss:SSS} %-4r %-5p %c %x - %m%n
Après compilation par mvn compile -Denv=PROD , on trouvera le fichier target/classes/log4j.properties
log4j.rootLogger=WARN, CONSOLE_APP
log4j.appender.CONSOLE_APP=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE_APP.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE_APP.layout.ConversionPattern= %d{dd-MM-yyyy HH:mm:ss:SSS} %-4r %-5p %c %x - %m%n
VIII. Packaging▲
Le packaging fait partie intégrante du cycle de vie d'un projet Maven. Il est toujours nécessaire de transformer un projet en une archive unique afin de pouvoir le publier dans un repository.
Maven est construit à partir de tous les standards de SUN, le packaging de Maven en revient donc toujours à construire l'archive suivant ces standards.
La commande pour avoir le packaging de son projet est :
mvn package
Bien évidemment, Maven prend en compte le type du projet et fera un .jar pour une librairie, un .war pour une application web, etc.
Dans notre projet Jar, le cycle package est implémenté par le goal jar:jar. On peut voir dans l'exécution de la commande que Maven prépare automatiquement un jar respectant les normes sur l'appel du goal jar:jar.
~/cours-maven-example$ mvn package
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [package]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to d:\workspace\IL\cours-maven-example\target\classes
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] No sources to compile
[INFO] [surefire:test]
[INFO] No tests to run.
[INFO] [jar:jar]
[INFO] Building jar: ~\cours-maven-example\target\cours-maven-example-1.0.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6 seconds
[INFO] Finished at: Tue Aug 12 16:14:29 CEST 2008
[INFO] Final Memory: 7M/12M
[INFO] ------------------------------------------------------------------------
IX. Intégration avec Eclipse▲
Maven a pour but de gérer le projet pendant tout son cycle de vie. Depuis le poste de développement jusqu'à la livraison en production (du moins sur le papier).
Eclipse permet à l'utilisateur d'agir efficacement sur le projet, pour cela il a besoin de le comprendre en détail.
Conclusion, les deux outils utilisent une configuration complète et indépendante du projet.
« Intégrer » les deux outils correspond alors à faire communiquer les configurations de Maven et d'Eclipse. La partie critique correspond souvent à fournir à Eclipse le classpath qui correspond à la gestion des dépendances de Maven.
Plusieurs stratégies qui peuvent être mises en place pour cela. Les 3 qui sont présentées ici sont viables, elles présentent chacune leurs avantages et inconvénient.
IX-A. External builder▲
Maven propose son plugin d'intégration pour Eclipse. Sa principale compétence est d'être capable d'écrire la configuration Eclipse à partir de son pom.xml.
mvn eclipse:eclipse
Ce mécanisme est sûr et abouti. La configuration Eclipse générée par Maven est toujours valide et lorsque la configuration existe déjà, Maven se contente de la compléter pour la bonne marche du projet. Noter que l'utilisation du plugin peut être affinée notamment pour activer la prise en charge de WTP.
mvn eclipse:eclipse -Dwtpversion=1.5
Pour avoir tous les détails, se référer au site du plugin : maven-eclipse-plugin
Pour automatiser les développements, il faut alors configurer le lancement des commandes Maven dans les « builders » Eclipse. Pour cela, il faut aller dans les propriétés du projet, puis builders. Ajouter un nouveau builder externe avec la configuration suivante :
- location : l'emplacement de mvn.bat ;
- working directory : le projet sur lequel on veut exécuter la tâche ;
- arguments : la ou les directives Maven à utiliser.
On associera le plus souvent le déclenchement du builder uniquement sur les builds manuels afin de ne pas avoir Maven qui se déclenche dès qu'on sauvegarde un fichier.
Avec ce mécanisme, le plus pratique est de créer le builder suivant :
- se déclenche sur la modification du fichier pom.xml ;
- exécute la directive eclipse:eclipse ;
- met à jour tout le projet une fois terminé.
Avec ce builder, les dépendances du projet seront toujours à jour sous réserve d'avoir demandé explicitement un build.
Pour notre projet, utiliser le Maven Eclipse Plugin donne cela :
~/cours-maven-example$ mvn eclipse:eclipse
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'eclipse'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [eclipse:eclipse]
[INFO] ------------------------------------------------------------------------
[INFO] Preparing eclipse:eclipse
[INFO] No goals needed for project - skipping
[INFO] [eclipse:eclipse]
[INFO] Using source status cache: d:\workspace\IL\cours-maven-example\target\mvn-eclipse-cache.properties
[INFO] Wrote settings to D:\workspace\IL\cours-maven-example\.settings\org.eclipse.jdt.core.prefs
[INFO] Wrote Eclipse project for "cours-maven-example" to d:\workspace\IL\cours-maven-example.
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Tue Aug 12 16:18:08 CEST 2008
[INFO] Final Memory: 4M/8M
[INFO] ------------------------------------------------------------------------
IX-A-1. Avantages▲
Cette méthode permet de gérer précisément les directives Maven à utiliser et leur déclenchement.
IX-A-2. Inconvénients▲
À force d'édition des fichiers de configuration d'Eclipse par Maven, l'interface a tendance à perdre les pédales. Les fichiers de configuration ne sont jamais altérés, mais Eclipse a parfois besoin de plusieurs « F5 » pour retrouver sa configuration.
IX-B. m2Eclipse plugin▲
Le plugin m2Eclipse est le plus abouti des plugins Maven pour Eclipse à l'heure actuelle (juillet 2008). Ses principales fonctionnalités sont de gérer une librairie dynamique représentant les dépendances de Maven et de fournir un builder Maven simplifié.
À noter qu'il est également intégré dans le plugin Eclipse de Maven, aussi vous pouvez utiliser
mvn eclipse:m2eclipse
Pour pousser l'intégration jusqu'au projet web lançable avec Eclipse WTP, vous pouvez utiliser
mvn eclipse:m2eclipse -Dwtpversion=1.5
m2eclipse propose maintenant une interface complète pour importer un projet Maven dans Eclipse. Soit à partir d'un système de gestion de ressource :
- cliquer sur File -> Import… ;
- sélectionner Other -> Checkout Maven project from SVN.
Soit à partir du projet sur le disque dur :
- cliquer sur File -> Import … ;
- sélectionner General -> Maven project.
Pour notre projet, le projet avec m2eclipse se présentera ainsi :
IX-B-1. Avantages▲
Permet de s'abstraire du builder eclipse:eclipse et des problématiques de rechargement de la configuration Eclipse.
IX-B-2. Inconvénient▲
m2eclipse a récemment effacé ses principales faiblesses et se positionne comme le projet qui fera un jour partie intégrante d'Eclipse.
Il n'a de faiblesse qu'en cherchant les fonctionnalités avancées de Maven, par exemple le filtrage (bien qu'il propose des solutions) ou encore la gestion des projets multimodules.
IX-C. q4e▲
q4e est le principal concurrent de m2eclipse. La version 0.7 actuellement disponible est déjà très stable.
En termes de service, il propose la gestion dynamique des dépendances Maven tout comme m2Eclipse. Il propose également plusieurs interfaces dont une, pour lancer des directives.
IX-C-1. Avantages▲
Il est déjà opérationnel et se montre très prometteur.
IX-C-2. Inconvénient▲
Le fonctionnel semble encore trop limité pour les projets utilisant des fonctionnalités avancées de Maven.
X. Intégration avec Tomcat▲
Par défaut Maven gère les fichiers sources dans le répertoire src et les fichiers compilés dans le répertoire target. Et ce, même si la compilation ne fournit pas d'autre valeur ajoutée que la recopie du fichier.
La question du fonctionnement avec le développement sous Tomcat se pose alors. On a l'habitude de pouvoir modifier directement les fichiers utilisés par Tomcat ce qui permet de ne pas le redémarrer pour tester les modifications (du moins pour les fichiers statiques).
On démarre toujours Tomcat dans un répertoire webapp racine. La question est alors de savoir si on démarre Tomcat dans le répertoire src/main/webapp ou target/<final-name>.
X-A. Dans src/main/webapp : war:inplace▲
Cela va un peu à l'encontre des grands principes de Maven. Si on démarre Tomcat dans le répertoire src, cela veut dire qu'il faudra ajouter dans src/main/webapp/WEB-INF/classes les fichiers compilés et dans src/main/webapp/WEB-INF/lib les dépendances.
La commande war:inplace réalisera cette opération automatiquement. En configurant Eclipse pour positionner sa sortie de compilation dans le répertoire src/main/webapp/WEB-INF/classes, tout fonctionnera correctement.
Cette solution est à privilégier dès qu'elle est possible, car elle apporte les meilleures possibilités aux développeurs. Malheureusement certaines limitations la rendent inutilisable dans certains cas.
X-A-1. Avantages▲
C'est une solution très pratique, car après une initialisation par la commande war:inplace, il est possible de développer avec Tomcat sans utiliser Maven et en profitant de toutes les modifications dans Tomcat instantanément.
X-A-2. Inconvénients▲
- En ne passant plus par Maven pour la compilation, on perd beaucoup des fonctionnalités de Maven. Par exemple, il n'est pas possible d'utiliser le filtrage de ressources. Comme Eclipse ne le gère pas, les fichiers qui seront copiés dans src/main/webapp/WEB-INF/classes ne seront pas filtrés.
- Dans certains projets, il y a un mécanisme de dépendance entre applications web. Cela permet de récupérer dans la webapp des fichiers d'une dépendance. Dans ce cas, le goal war:inplace va recopier les dépendances dans le répertoire src/main/webapp. Pour gérer les commits des sources du projet, il faut faire particulièrement attention à ne pas commiter les fichiers hérités dans le projet au risque de surcharger les fichiers et de perdre la dépendance.
- Si le projet est modulaire (voir plus bas) aucun répertoire ne pourra servir de base à Tomcat, le fonctionnement est donc impossible.
X-B. Dans target/<finalName> : war:exploded▲
La propriété final name dans Maven correspond au nom du package à utiliser. Il se configure comme ceci :
<build>
<final-name>
package-name</final-name>
...
</build>
La commande war:exploded va créer un répertoire target/<final-name> qui contiendra le war de l'application sans réaliser l'archive.
En configurant Tomcat pour démarrer dans ce répertoire, il utilisera les fichiers compilés par Maven.
X-B-1. Avantages▲
On reste entièrement dans le cadre de Maven, pas d'entorse au concept : sources d'un côté, fichiers compilés de l'autre. On profite de tous les mécanismes de Maven : filtrage, projets modulaires, dépendances des fichiers de la webapp…
X-B-2. Inconvénients▲
Il faut passer par Maven pour toutes modifications de fichier. Cette opération peut être fastidieuse.
XI. Configuration du site▲
Maven propose de générer un site présentant le projet. Toujours fidèle à ses principes, Maven pourra générer un site minimal même sans disposer de configuration spécifique. La commande à utiliser est la suivante :
mvn site
(ou)
mvn site:site
Le site est alors généré dans le répertoire target/site. Une fois habitué à ce genre de site, vous reconnaitrez plusieurs sites officiels de grands frameworks générés par ce moyen : Tapestry , Struts , EhCache ou bien sur Maven lui même.
Un des gros atouts de cette fonctionnalité est de permettre la régénération automatique et fréquente du site présentant la documentation ainsi que les rapports du type JavaDoc ou tests unitaires. Le plus souvent, avec l'utilisation de systèmes d'intégration continue, la génération du site est configurée pour être lancée à chaque build afin de pouvoir suivre les rapports au fur et à mesure.
XI-A. Structure▲
Par défaut, Maven va créer quelques pages présentant les informations présentes dans le pom (si elles y sont) comme la description générale, la gestion des sources ou l'outil de ticketing…
Les fichiers pour le site seront à déposer dans le répertoire src/site prévu à cet effet. On l'organisera alors ainsi :
+- src/
+- site/
+- apt/
| +- index.apt
|
+- xdoc/
| +- other.xml
|
+- fml/
| +- general.fml
| +- faq.fml
|
+- site.xml
Le fichier site.xml permet de gérer la structure du site et principalement le menu, se référer à la documentation pour la syntaxe.
Il y a ensuite deux fonctionnalités principales pour enrichir le contenu du site : rajouter du contenu, et rajouter des rapports.
XI-B. Contenu▲
Il est possible de rajouter des pages de contenu dans plusieurs formats différents :
- le format Xdoc utilisé dans Maven 1.x ;
- le format APT, « Almost Plain Text » APT Format ;
- le format FML soit le format pour les FAQ.
Les fichiers pour chaque format doivent être positionnés dans le répertoire correspondant. Dans le site généré, une page écrite au format apt dans le fichier src/site/apt/toto/toto.apt sera générée dans target/site/toto/toto.html et c'est ainsi pour tous les types de fichiers.
XI-C. Rapports▲
Certains plugins Maven proposent des rapports en plus de goals. Certains plugins ne proposent d'ailleurs que des rapports. À la génération du site, Maven exécutera tous les rapports configurés pour les calculer.
Pour ajouter un rapport dans le site Maven, il faut rajouter dans le pom une configuration de ce type :
<reporting>
<plugins>
...
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-project-info-reports-plugin</artifactId>
<version>
2.0.1</version>
</plugin>
...
</plugins>
</reporting>
En fait, le site par défaut contient justement le rapport du plugin maven-project-info-reports-plugin
Les plugins les plus courants proposant des rapports sont les suivants :
- changelog : génère la liste des changements récents du gestionnaire de source ;
- changes : génère le rapport du système de ticketing du projet ;
- checkstyle : génère le rapport de checkstyle ;
- javadoc : génère la JavaDoc du projet ;
- pmd : génère le rapport PMD ;
- project-info-reports : génère les rapports standards Maven ;
- surefire-report : génère le rapport des résultats des tests unitaires.
Pour générer un site qui propose quelques informations, nous allons enrichir le pom.xml avec une petite description et l'activation du rapport Javadoc.
Après la commande mvn site , le site sera disponible dans target/site/index.html
<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/maven-v4_0_0.xsd"
>
<modelVersion>
4.0.0</modelVersion>
<groupId>
com.developpez</groupId>
<artifactId>
cours-maven-example</artifactId>
<packaging>
jar</packaging>
<name>
Projet d'exemple pour le cours Maven</name>
<description>
Projet de démonstration des fonctionnalités de Maven</description>
<version>
1.0</version>
<dependencies>
<dependency>
<groupId>
log4j</groupId>
<artifactId>
log4j</artifactId>
<version>
1.2.13</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>
src/main/resources</directory>
<filtering>
true</filtering>
</resource>
</resources>
<filters>
<filter>
src/main/filters/${env}.properties</filter>
</filters>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
2.0.2</version>
<configuration>
<source>
1.5</source>
<target>
1.5</target>
</configuration>
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</reporting>
<properties>
<env>
LOCAL</env>
</properties>
</project>
~/cours-maven-example$ mvn site
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] task-segment: [site]
[INFO] ------------------------------------------------------------------------
...
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] ------------------------------------------------------------------------
[INFO] No goals needed for project - skipping
[INFO] Preparing javadoc:test-javadoc
[INFO] ------------------------------------------------------------------------
[INFO] Building Projet d'exemple pour le cours Maven
[INFO] ------------------------------------------------------------------------
[INFO] No goals needed for project - skipping
[INFO] [site:site]
[WARNING] No URL defined for the project - decoration links will not be resolved
[INFO] Generating "JavaDocs" report.
...
[INFO] Generating "Continuous Integration" report.
[INFO] Generating "Dependencies" report.
[INFO] Generating "Issue Tracking" report.
[INFO] Generating "Project License" report.
[INFO] Generating "Mailing Lists" report.
[INFO] Generating "About" report.
[INFO] Generating "Project Summary" report.
[INFO] Generating "Source Repository" report.
[INFO] Generating "Project Team" report.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 19 seconds
[INFO] Finished at: Tue Aug 12 16:45:38 CEST 2008
[INFO] Final Memory: 18M/35M
[INFO] ------------------------------------------------------------------------
XII. Héritage de pom▲
Un pom très complet peut devenir très conséquent. De plus, une partie importante de son contenu sera souvent partagé entre plusieurs projets (mêmes rapports, mêmes repository…)
Un mécanisme d'héritage permet d'organiser et de factoriser le contenu des pom.xml
Le code à utiliser est le suivant :
<project ...>
<parent>
<groupId>
...</groupId>
<artifactId>
...</artifactId>
<version>
...</version>
</parent>
...
</project>
Via ce code, toutes les informations contenues dans le pom.xml de l'artifact ciblé seront ajoutées au pom.xml courant. Les informations du pom courant sont forcément prioritaires en cas de doublons.
Attention : on a vite tendance à mettre les repository dans un pom hérité pour alléger le pom sur lequel on travaille, mais il faut toujours bien penser que le pom hérité sera recherché sur les repository connus, il faut donc que le pom sur lequel on travaille connaisse au moins le repository où chercher son parent.
En entreprise, beaucoup d'éléments sont partagés entre différents projets : des repository, un skin pour le site, des rapports… Il est alors intéressant de créer un pom parent commun à toute l'entreprise. Souvent, on créera encore un autre pom qui héritera de celui de l'entreprise et qui précisera des éléments spécifiques à l'équipe.
Cette solution est très intéressante pour éviter de rendre les pom illisibles parce que trop longs et encore plus intéressante lorsque l'on travaille sur plusieurs projets. Il faut donc rapidement utiliser ce mécanisme et ne pas repousser sa mise en place trop longtemps.
Une fois qu'on connait ce mécanisme, il faut savoir que les valeurs par défaut dans le pom Maven sont en fait gérées par un héritage systématique à un super POM. Ce super POM est décrit ici : Le super POM
Il existe un goal Maven qui permet d'afficher le pom complet utilisé par Maven après l'application de tous les héritages :
mvn help:effective-pom
XIII. Intégration continue▲
XIII-A. Principes▲
Chaque développeur committe son travail sur le système de gestion de sources. Le système d'intégration continue, qui est connecté au système de gestion de sources va lancer une intégration à chaque commit.
L'intégration consiste alors en l'enchaînement de la compilation, de l'exécution des tests unitaires, du déploiement de l'application et la génération du site de rapports.
XIII-B. Bénéfices▲
Le mécanisme réduit les problèmes d'intégration :
Permet de s'apercevoir au plus tôt des problèmes
- liés à l'intégration du code des différents développeurs ;
- liés à l'intégration sur une plateforme différente (OS, structure, etc.).
Souligne les éventuelles régressions
Permet une application « Snapshot » disponible en permanence
- visibilité dans l'équipe :
- visibilité interne au département, à l'entreprise, etc. :
- visibilité au client :
- réduit l'effet tunnel (puisque la version courante de l'application est toujours visible).
XIII-C. Intégration continue avec Maven▲
L'architecture et les principes de Maven favorisent cette pratique. Le processus de build Maven est le même, quel que soit l'environnement. L'utilisation des goals Maven peut être facilement automatisée.
Plusieurs produits Open Source permettent de mettre en œuvre une intégration continue avec Maven :
- CruiseControl Le système historique, mais vieillissant ;
- Continuum Le système proposé par Maven, intéressant, mais propose moins de fonctionnalités ;
- Hudson Le système qui a la côte en ce moment : riche et agréable d'utilisation. N'hésitez pas à consulter cet article pour l'utilisation d'Hudson : https://linsolas.developpez.com/articles/hudson/.
Le système d'intégration continue surveille le système de gestion des sources et lance un cycle Maven complet : compilation, install, tests, déploiement, génération du site, etc.
C'est le POM du projet qui est utilisé pour paramétrer ce cycle, la configuration est donc la même que ce soit en local ou sur la plateforme d'intégration.
Maven gérant également les déploiements sur des applications tels que le déploiement d'un War dans Tomcat, avec un goal Maven supplémentaire dans l'intégration continue, l'application peut être redéployée à chaque build.
XIV. Projets modulaires▲
Dans les projets web, il est très souvent intéressant de séparer le code middle du projet de la couche de présentation. Cela permet notamment de localiser le middle dans un jar que l'on pourra réutiliser dans le site web, mais aussi dans les batchs, les web Services, etc.
Les projets modulaires dans Maven permettent de déclarer un métaprojet qui correspond au regroupement de plusieurs sous-projets Maven.
XIV-A. Définition▲
Pour créer un projet multimodules, le principe est de créer plusieurs projets Maven dans un même sous-répertoire. À la racine de ce répertoire, il faut ajouter un fichier pom.xml qui va référencer les sous-projets.
+- project/
+- pom.xml
+- sub-project1/
| +-pom.xml
+- sub-project2/
| +-pom.xml
+- sub-project3/
+-pom.xml
Le méta projet n'est pas obligatoirement placé à la racine des sous-projets. C'est tout de même préférable notamment pour la gestion des releases.
Pour référencer les sous-projets, le métaprojet devra ressembler à ça :
<project ...>
<modelVersion>
4.0.0</modelVersion>
<groupId>
...</groupId>
<artifactId>
...</artifactId>
<packaging>
pom</packaging>
<modules>
<module>
sub-project1</module>
<module>
sub-project2</module>
</modules>
</project>
Attention pour travailler ensemble, les sous-modules doivent gérer leurs dépendances eux même. Par exemple, si les deux sous projets sont monProjetMiddle et monProjetFront, il sera toujours nécessaire de définir monProjetMiddle comme dépendance de monProjetFront.
Ce n'est pas parce que plusieurs projets sont référencés dans le même métaprojet qu'ils se connaissent les uns les autres.
XIV-B. Utilisation▲
Maven propose étonnamment peu de fonctionnalités qui se basent directement sur le mécanisme des modules. La seule fonctionnalité directe est le « reactor ».
Le « reactor » est le mécanisme qui fait que lorsqu'on lance un goal sur un métaprojet, ce goal va d'abord être lancé successivement sur tous les sous-projets. Le « reactor » prendra garde à analyser les dépendances entre les différents modules pour les compiler dans le bon ordre.
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] sub project 1 .......................................... SUCCESS [12.892s]
[INFO] sub project 2 .......................................... SUCCESS [15.000s]
[INFO] sub project 3 .......................................... SUCCESS [0.313s]
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1 minute 32 seconds
[INFO] Finished at: Tue Jul 08 17:19:38 CEST 2008
[INFO] Final Memory: 15M/33M
[INFO] ------------------------------------------------------------------------
Dans la définition des goals Maven, il est possible de traiter le fait de s'exécuter ou non dans le cadre d'un projet multimodule. Il est donc possible de trouver des plugins Maven qui ont un comportement particulier pour les projets multimodules, c'est le cas notamment pour le plugin Maven pour Lutèce.
XIV-B-1. Intérêts et développements▲
On l'a vu, Maven propose peu de fonctionnalités directement liées aux modules, pourtant l'utilisation en est de plus en plus importante.
En effet l'un des intérêts majeurs de Maven est sa gestion fine des dépendances. Il est de plus en plus souvent intéressant de profiter de cette gestion au sein même d'un projet.
Comme on l'a vu, dans un projet web, on va rapidement extraire le middle dans un projet à part afin de pouvoir en dépendre depuis plusieurs projets différents.
Dans les grands frameworks, ce besoin est encore plus évident : pour les projets volumineux comme Spring, cela permet de ne pas avoir tout en dépendance. Par exemple, si l'on n'utilise pas la fonctionnalité de connexion au LDAP de Spring, il n'est pas nécessaire de dépendre de l'artefact spring-ldap ni des dépendances nécessaires à la connexion LDAP, etc.
Spring est aujourd'hui découpé en plus de 40 modules, Hibernate, Tapestry, Struts sont déjà tous des projets Maven modulaires…
XV. Archetypes▲
Un Archetype est un outil pour faire des templates de projet Maven. Avec des Archetypes existant déjà pour des grands projets tels que Tapestry , il est possible de créer un projet prêt à compiler et déployer.
XV-A. Utiliser un Archetype Maven▲
La commande à lancer pour créer un projet à partir d'un Archetype est presque toujours décrite avec l'Archetype. Néanmoins, la commande est la suivante :
mvn archetype:create -DarchetypeGroupId=org.apache.tapestry -DarchetypeArtifactId=quickstart
-DgroupId=org.example -DartifactId=myapp
-DpackageName=org.example.myapp -Dversion=1.0.0-SNAPSHOT
L'Archetype est un projet Maven identifié avec un groupId et artifactId comme les autres. Les paramètres à renseigner correspondent donc à la localisation du paquet de l'Archetype auxquels on ajoute les paramètres du projet que l'on va créer : son nom, les packages des sources et sa version.
Cette commande va générer tout le nécessaire pour lancer un projet démo avec Tapestry.
Pour les projets web en Java, le projet AppFuse utilise de façon importante la technologie des Archetypes Maven pour proposer plusieurs types de projets sur lesquels démarrer pour la réalisation de projets web en Java. Voici la liste des types de projets qu'il propose :
Liste des Archetypes AppFuse |
|
---|---|
JSF Basic |
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-basic-jsf -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject |
Spring MVC Basic |
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-basic-spring -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject |
Struts 2 Basic |
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-basic-struts -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject |
Tapestry Basic |
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-basic-tapestry -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject |
JSF Modular |
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-modular-jsf -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject |
Spring MVC Modular |
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-modular-spring -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject |
Struts 2 Modular |
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-modular-struts -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject |
Tapestry Modular |
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-modular-tapestry -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject |
Core (backend only) |
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-core -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject |
XV-B. Créer un Archetype Maven▲
Un Archetype Maven est un projet Maven avec des particularités qui lui permettront ensuite de remplacer avec les valeurs paramétrées lors du mvn archetype:create.
Pour les détails concernant la création d'un Archetype Maven, il y a deux sources principales : créer son propre Archetype Maven sur le site Maven et le site du plugin Archetype.
XVI. Conclusion▲
Maven 2 se démocratise comme étant la solution de gestion de projet Java. Tous les projets Open Source l'adoptent sauf quelques récalcitrants. Les outils de développements industrialisés se basent aujourd'hui tous sur la maîtrise du projet que propose Maven.
Aujourd'hui, même si le ticket d'entrée pour l'utilisation de Maven peut s'avérer couteux, les possibilités qu'il offre le rendent indispensable dans le cadre d'un développement professionnel.
XVI-A. Références▲
- Le site officiel de Maven http://maven.apache.org/ est la principale référence même si on n'y trouve pas toujours tout de suite ce qu'on cherche.
- On notera spécialement dans ce site la procédure d'installation http://maven.apache.org/download.html ainsi que le getting started http://maven.apache.org/guides/getting-started/index.html qui passe en revue les principales fonctionnalités.
- Plusieurs ressources Maven sont disponibles sur developpez.com : FAQ Maven 2 et Introduction à Maven 2
- Une liste d'arguments intéressants à présenter pour l'utilisation de Maven se trouve ici : http://ebxmlrr.wiki.sourceforge.net/whymaven
- Un livre complet est entièrement disponible sur le web http://www.sonatype.com/community/definitive_guide.html
- Et ne pas rater tous les liens relatifs aux différents chapitres dans le corps du document…
XVI-B. Remerciements▲
- Tous les relecteurs : Romain Linsolas , Eric Siber , Baptiste Wicht , Frédéric Oblé, Cédric Exbrayat.
- La société qui m'emploie : Atos Worldline pour l'autorisation de réaliser et publier cette documentation.