Maven

Introduction

Maven est un outil permettant de gérer les dépendances, la compilation, le "packaging" et bien d'autres choses. Il s'utilise au moyen de fichier "pom" décrivant votre projet.
Maven est développé en Java et le script "mvn" utilise la variable d'environnement "JAVA_HOME" pour déterminer quelle version du langage Java doit être utilisée. Afin d'éviter tout soucis, initialiser cette variable dans votre ".bashrc" (linux) ou ".bash_profile" (mac) ou dans les propriétés systèmes sous windows.
export JAVA_HOME="/path/to/your/java/Home"

La vidéo suivante présente maven est les notions principales dans la description des projets.

Transparents présentés dans la vidéo

Page de téléchargement de Maven : maven.apache.org/download.html
Archive maven 3.3.3 à extraire. Ajouter le répertoire bin/ à votre PATH, puis vérifier en tapant mvn -v ; vous devriez obtenir quelque chose de ce type :


Apache Maven 3.3.3 (7994120775791599e205a5524ec3e0dfe41d4a06; 2015-04-22T13:57:37+02:00)
Maven home: /Users/guillaume/install/apache-maven-3.3.3
Java version: 1.8.0_11, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_11.jdk/Contents/Home/jre
Default locale: fr_FR, platform encoding: UTF-8
OS name: "mac os x", version: "10.10.2", arch: "x86_64", family: "mac"

Le principal intérêt de Maven est de gérer des dépendances vers des librairies. Ces librairies sont téléchargées depuis un "repository", généralement le repository "central" maven.

Le répertoire .m2 contiendra toutes les librairies téléchargées (dans 'repository') par Maven au fur et à mesure de son utilisation. Il est possible que ce répertoire devienne assez lourd avec le temps.
Afin de le pas encombrer votre compte avec ces fichiers, je vous recommande de modifier l'endroit où Maven stockera les librairies. Ajouter ceci, en l'adaptant, dans votre fichier "settings.xml" :
<localRepository>/local/username/m2<localRepository>

Pom.xml

Il faut faire de votre projet Java, un projet "maven". Cela se fait simplement en ajoutant un fichier "pom.xml" à la racine.
Un fichier pom décrit notre projet en "artefact", dans un "group" et possédant une "version".
Le projet est "packagé" selon un format défini.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org2001/XMLSchema-instance"
           xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apahe.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
      
  <groupId>fr.eservices.xxVotreNomxx</groupId>
  <artifactId>projet-jee</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

<project>

Le pom contiendra également le nom du projet et différentes informations nécessaires à la compilation ou au packaging de notre projet.
Le plus intéressant : cela contiendra des dépendances vers des librairies, qui elles-mêmes peuvent en tirer d'autres par dépendances transitives.
Les dépendances sont normalement présentes sur un serveur "central" maven. C'est un site regroupant toutes les librairies de projets qui souhaitent être diffusées/utilisées.
Il est aussi possible d'ajouter d'autres "repository" par de la configuration dans le pom, ou dans les settings de maven.

Repository

Les dépendances seront stockées sur votre ordinateur dans un repository local, par défaut dans ~/.m2/repository.
Il est possible d'ajouter manuellement des librairies dans ce repository. Ce qui peut être utile pour les librairies qui ne sont pas fournies au format maven (avec un pom ou sur le repo central ...).

La conception interne de Maven est modulaire et chaque "plugin" est responsable d'une fonctionnalité. Ces mêmes plugins sont également des artefacts maven disponibles sur le repo central et téléchargés au besoin.
Ainsi, c'est le plugin "install" qui va gérer les insertions dans le repo local.
Pour mettre un jar quelconque dans votre repo local on utilisera la commande :

mvn install:install-file -Dfile=fichier.jar -DgroupId=mon.groupe -DartifactId=nom-librairie -Dversion=version -Dpackaging=typeArtefact
La version est nécessairement numérique, séparée par des points.
typeArtefact, généralement jar est le format du fichier géré pour cette dépendance.

Dépendances

Il faut distinguer différents ensembles de dépendances, nommés "scope" dans le vocabulaire mMven.
» compile : (par défaut) indique que cette librairie est nécessaire pour la compilation et à l'exécution.
» runtime : uniquement nécessaire à l'exécution.
» provided : sera disponible à l'exécution mais n'est pas fourni par ce projet. Exemple : le "servlet-api" est fourni par Tomcat et n'est pas amené par notre projet web. "servlet-api" est donc "provided". Cette librairie reste nécessaire à la compilation.
» test : librairie nécessaire uniquement dans l'exécution de tests unitaires. Exemple : junit.
» system : librairie disponible à travers le système de fichiers local. Ce type de dépendance n'est pas tiré depuis les repository distants. A éviter, sauf si vous committez vos librairies avec le projet (c'est mal ...)

Maven gère le classpath pour vous lors des phases de compilation et d'exécution.
Les dépendances directes et transitives sont toutes ajoutées au classpath.
Les dépendances "compile", "runtime" sont transitives. Le scope "test" est par contre limité à votre projet.

Les dépendances se gèrent dans la sections "dependencies". Pour Chaque dépendance, il faut indiquer le groupId, l'artifactId, la version et le scope (par défaut compile).
Exemple de dépendance pour ajouter JUnit pour nos tests :

  <dependencies> 
    ...
    <dependency> 
      <groupId>junit</groupId> 
      <artifactId>junit</artifactId> 
      <version>4.12</version> 
      <scope>test</scope> 
    </dependency> 
    ...
  </dependencies>

Structure des dossiers

Par convention, Maven suppose une certaine structure de dossiers de votre projet :

Le répertoire de compilation sera normalement "target".
Tout cela est configurable à travers la directive "build" de votre pom.xml.

Vous trouverez dans ce fichier pom.xml une structure de base décrivant votre projet.
Vous pouvez ensuite exécuter mvn dependency:resolve pour résoudre et télécharger toutes les dépendances.
Elles se rangeront dans votre repo local qui pourra vite devenir assez volumineux si vous travaillez régulièrement avec Maven sur différents projets.
Rappel : Vous pouvez modifier votre .m2/settings.xml pour modifier l'emplacement où Maven rangera toutes les dépendances.
Visualisez l'arbre des dépendances avec mvn dependency:tree.

Le screencast suivant montre commment créer un projet maven sans outillage particulier afin de vous expliquer les mécanismes sous-jacent à son fonctionnement.

Code présenté dans le screencast

Le reste de cette page détaille des scénario d'usages moins courants.

Propriétés

Maven offre un système de gestion de propriétés, utilisables pour différentes tâches.
Il est par exemple possible de définir des "propriétés" dans votre POM afin de définir la version d'un ensemble de dépendances.
La mise à jour de cette version pour mettre à jour ces librairies devient ainsi plus facile.

Prenons l'exemple des artefacts du framework Spring. Les projets utilisant Spring ont souvent besoin de plusieurs artefacts issus du framework. Heureusement, ceux-ci suivent tous une version cohérente afin de faciliter le choix de la version à mettre en dépendance.
Il est donc possible de définir une propriété "spring.version" et de l'utiliser ensuite dans la déclaration de nos dépendances.

Pour définir une propriété, le plus simple est de la mettre dans le pom du projet à l'aide de la balise properties.
Dans cette balise, nous déclarons un ensemble de balises portant le nom de nos propriétés et contenant la valeur attendue.
par exemple ...

<properties>
  <spring.version>5.2.4.RELEASE</spring.version>
</properties>

Les dépendances de votre projet peuvent alors s'écrire comme ceci :

  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
  </dependency>
  
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
  </dependency>

Notez que les propriétés Maven peuvent être surchagées dans la ligne de commande qui lance une tâche.
Il est donc possible de lancer un test avec une autre version de Spring pour s'assurer de la compatibilité de notre code avec celle-ci.

mvn -Dspring.version=5.2.5.RELEASE test

Ce système de propriété permet aussi de modifier le comportement des tâches Maven.
Les propriétés maven.compiler.source et maven.compiler.target permettent par exemple de préciser la version de Java de vos sources et celle dans laquelle vous souhaitez compiler vos classes. Pour des raisons historiques, la valeur par défaut utilisée est Java 1.5 (1.6 à partir de la version 3.8.0 de Maven), ce que vous voudrez nécessairement changer.

Un autre usage courant concerne la propriété skipTests permettant de compiler ou packager en ignorant la phase de test.

mvn -DskipTests=true package

Il est aussi possible de définir la valeur d'une propriété dans un projet "parent", dans un "profile" ou dans le fichier settings de m Maven.
La valeur passée en ligne de commande sera toujours prioritaire. Si votre propriété est définie à plusieurs endroits, la priorité est donnée dans l'ordre suivant :

Projet multi-modules

Lorsqu'un projet devient relativement important, il est pertinent de scinder son contenu en différents artefacts.
Cela permet une meilleure compréhension des "bouts" de votre projet et d'améliorer l'efficience de sa maintenance. Le besoin peut aussi naître de la volontée de mettre à disposition d'autres projets de plus petits artefacts, afin de faciliter la réutilisabilité tout en limitant les dépendances transitives.
Quelle que soit la raison qui vous amène à faire différents artefacts pour votre projet, il reste pratique de lancer une tâche sur l'ensemble du projet afin de le construire ou de le tester.
C'est dans ce cas de figure que la notion de "module" et de projet "multi-modules" devient intéressante.

Un projet multi-modules se matérialise sous la forme d'un répertoire contenant un pom et de différents sous-répertoires pour chacun de nos sous-modules.
Le pom à la racine contient les éléments habituels d'un projet Maven mais sera de type "pom" dans son packaging.
Il définira une balise modules contenant le nom des sous-modules (répertoires) dans des balises module.

Certains de vos modules peuvent être dépendants les uns des autres et l'ordre de compilation devient alors important.
L'ordre de déclaration des sous-modules dans le pom n'a pas d'importance, Maven prend en charge l'ordonancement en analysant les dépendances des modules.
Cela se fait simplement par l'analyse du graphe orienté des dépendances. Il n'est pas possible de définir un cycle dans les dépendances décrites, cela empécherait cette analyse.
Si le besoin se faisait sentir de décrire un tel cycle, il vous sera nécessaire de repenser la distribution de vos classes ou l'architecture de votre code. Il est communément admis que cette "contrainte" vous oriente vers la construction de briques applicatives plus simples et réutilisables.

Voyons la déclaration d'un projet multi-module.

<project>
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.demo</groupId>
  <artifactId>project-parent</artifactId>
  <packaging>pom</packaging>
  <version>1.0.0-SNAPSHOT</version>

  <modules>
      <module>project-module1</module>
      <module>project-module2</module>
  </modules>
</project>

Chaque sous-module sera un projet maven "classique" mais devra indiquer son parent. Le parent étant l'artefact décrivant l'ensemble des modules du projet.
Bien que ce ne soit pas courant ou recommandable, un sous-module pourrait donc avoir un "parent" qui n'est pas le projet du répertoire principal.
Un projet "fils" récupère par défaut le groupId et la version du parent, il héritera aussi de toutes les propriétés définies dans celui-ci. Il est possible de surcharger ces valeurs si nécessaire en les re-déclarant.
Voyons un exemple de projet "fils" selon notre exemple.

<project>
    <modelVersion>4.0.0</modelVersion>

    <parent>
      <groupId>com.demo</groupId>
      <artifactId>project-parent</artifactId>
      <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>project-module1</artifactId>
    <packaging>jar</packaging>

    // ...
</project>

Déployer votre artefact

Pour mettre à disposition votre artefact, il suffit de l'héberger sur un serveur accessible en HTTP et organisé de la même manière que votre repository local.
Maven vous permet de déployer selon différents modes de distribution avec des extensions :

La configuration se fait dans la directive "distributionManagement", voici un exemple.

<distributionManagement> 
  <repository> 
    <id>id-repository</id>
    <url>ftp://server/path</url>
  </repository> 
</distributionManagement>

Chaque mode de distribution est pris en charge par une extension, à configurer dans "build" :

<build> 
  <extensions> 
    <extension> 
      <groupId>org.apache.maven.wagon</groupId>
      <artifactId>wagon-ftp</artifactId>
      <version>1.0-beta-6</version> 
    </extension> 
  </extensions> 
</build>

Les login et mot de passe de connexion ne sont pas à mettre dans le pom.xml (ce fichier étant publique). Vous devez ajouter un "server" dans votre fichier settings.xml :

<settings> 
... 
  <servers> 
    <server> 
      <id>id-repository</id> 
      <username>yourLogin</username> 
      <password>yourPassword</password> 
    </server> 
  </servers> 
... 
</settings>

Ajouter un repository

Sans configuration particulière, Maven recherche d'abord les artefacts dans votre repository local, puis dans le repository central Maven (https://repo.maven.apache.org/maven2).
Il est possible de modifier votre configuration globale pour ajouter un repository dans le settings.xml, mais ce repository sera utilisé pour tous vos projets. Ce n'est donc pas nécessairement une solution idéale.
Il est aussi possible d'ajouter un repository dans le pom de votre projet dans la section "repositories".
Voici un exemple, que vous pourrez adapter ...

<repository> 
  <id>id-repository</id> 
  <name>Name of your repository</name> 
  <url>http://host:port/path/to/maven/</url> 
  <snapshots> 
    <enabled>true</enabled> 
  </snapshots> 
</repository>

la notion de "snapshot" indique à Maven que les versions d'artefacts "-SNAPSHOT" seront mises à jour. Il lui sera nécessaire de vérifier la dernière version disponible de l'artefact de temps en temps afin de mettre votre repository à jour.
Les versions "stable" d'artefacts ne sont pas sensées changer. Une fois l'artefact déployé sur un repository, vous devriez immédiatement l'augmenter de version et lui ajouter le suffixe -SNAPSHOT.
Maven ne téléchargera pas un artefact "stable" qu'il possède en local.

Lorsque vous re-définissez les repositories dans votre pom vous devez aussi ajouter le central Maven, sinon seul votre nouveau repository sera consulté. Le central Maven peut être ajouté comme un autre repository à la liste :

<repository> 
  <id>central2</id> 
  <name>Maven Central repo</name> 
  <url>http://central.maven.org/maven2/</url> 
</repository>

Le plugin Maven d'Eclipse

Eclipse, comme de nombreux IDE, inclut maintenant un plugin pour faciliter la gestion des projets Maven.
Il suffit de passer par le menu "Import" < "Maven" < "Existing maven projects".
L'édition du pom est facilitée par un menu présentant les différentes sections, tout en permettant d'éditer directement le source XML du fichier.

Ce plugin est fort pratique mais a tendance à consommer beaucoup de mémoire, notamment lors de l'indexation des artefacts disponibles sur les repositories. Cela peut amener Eclipse à planter faute de mémoire suffisante.
Pour cette raison, il peut être intéressant d'utiliser le plugin eclipse de Maven. Celui-ci génère les fichiers compatibles pour l'import de projet classique Java dans Eclipse (voir ci dessous).
Ou alors de cocher l'option "Do not automatically update dependencies from remote repositories" dans "Préférences" > "Maven".

Le plugin Eclipse de Maven

Un plugin permet de mettre à jour les dépendances d'Eclipse en fonction de ce que contient le pom.xml
Je parle ici d'un plugin eclipse pour Maven, à ne pas confondre avec l'inverse (ex: plugin m2e d'eclipse).
Ce plugin va en fait mettre à jour (ou créer) les fichiers .project et .classpath utilisés par Eclipse.
Cela permettra d'ajouter les dépendances dans le classpath d'Eclipse.
Pour cela utilisez mvn eclipse:eclipse
Et rafraîchissez votre projet avec F5, ou clic droit/refresh.


Il faudra également définir la variable "M2_REPO" dans votre workspace pour indiquer à Eclipse où est rangé le repository maven.
Rendez-vous dans Outils (ou Eclipse) / Préférence / Java / Build Path / Classpath Variables.
Ajouter une variable "M2_REPO" qui pointe vers "home"/.m2/repository

L'utilisation d'une variable permet à plusieurs personnes de se partager le fichier ".classpath" sans avoir de soucis avec l'endroit où se trouvent les librairies sur leur environnement de travail. La variable est indépendante du projet, elle est liée au workspace Eclipse.