Tutoriel Maven

1. Introduction

  • Après avoir développé avec succès une Calculatrice en Java, vous souhaitez partager votre succès rayonnant avec tout le monde.

  • Et vous ne souhaitez pas vous arrêter là, vous souhaitez aussi que d’autres développeurs participent à votre projet et contribuent à son amélioration.

  • Vous avez de la chance, la fondation Apache a créé Maven, un outil de gestion de la construction de logiciels.

  • Maven vous aidera à compiler et tester votre logiciel, mais aussi à le distribuer et à le rendre simple à comprendre par d’autres développeurs.

1.1. Convention avant la configuration

  • L’organisation d’un projet Maven respecte toujours la même convention : l’emplacement du code source, des tests, des ressources, etc. est toujours le même.

  • Cette convention a deux conséquences :

    1. La prise en main d’un nouveau projet est plus simple : le nouveau développeur connaît d’emblée la structure du projet et ne perdra pas de temps à chercher l’emplacement des fichiers sources, des tests, etc.

    2. Il n’est pas nécessaire de configurer Maven avant de l’utiliser. Il trouvera tout seul le code source et les tests unitaires de votre projet.

  • Les conventions proposées par Maven sont très complètes. En voici quelques exemples :

Dossier Contenu

src/main/java

Fichiers source Java

src/test/java

Tests unitaires JUnit ou TestNG.

src/test/resources

Ressources utilisées pendant les tests.

target

Fichiers générés pendant la construction du logiciel.

1.2. Identification et création d’un projet

  • Tout projet Maven possède un identifiant unique, composé de trois éléments :

    1. un identifiant de groupe,

    2. un identifiant de l’artefact et

    3. sa version.

  • Cet identifiant permet de créer des dépendances entre projets, qui sont gérées par Maven.

  • Par exemple, si votre projet utilise JUnit, vous n’avez pas besoin de télécharger une archive Java et de l’ajouter au projet, mais simplement de déclarer que votre projet a besoin de JUnit, identifié par le groupe junit, l’artefact junit et la version 4.12.

  • Ainsi, avant de créer votre projet, vous devez l’identifier. L’identifiant est souvent celui de l’organisation à laquelle vous appartenez.

  • Par exemple, fr.unantes. Ensuite, l’identifiant de l’artefact est son nom, par exemple, calculatrice.

Une fois que votre projet est identifié, vous pouvez le créer par la commande Unix suivante :

 mvn archetype:generate -DarchetypeVersion=1.4 -DgroupId=fr.unantes -DartifactId=calculatrice -Dversion=1.0-SNAPSHOT -DinteractiveMode=false
  • Cette commande créera un dossier appelé calculatrice, contenant différents dossiers vides et un fichier XML de configuration et d’information du projet, nommé pom.xml, appelé couramment un fichier POM.

  • Notez que cette commande spécifie aussi un «archétype», c’est à dire, la structure de projet à être construite.

  • L’archétype generate est un des plus simples et vous posera quelques questions durant la création.

  • Le contenu de ce fichier doit ressembler à 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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>fr.unantes</groupId>
  <artifactId>calculatrice</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>calculatrice</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>
  • Comme vous pouvez le constater, ce fichier contient l’identifiant du projet, une propriété (le type d’encodage) et une seule dépendance, vers la version 4.11 de JUnit.

  • Le fichier POM contient aussi une liste de plugins sous la balise <pluginManagement>.

    • C’est une manière d’indiquer à Maven les versions souhaitées des ses plugins et éviter qu’il utilise des versions très anciennes.

  • Il existe d’autres moyens de créer un projet Maven, par exemple, directement dans Netbeans, VS Code ou dans IntelliJ IDEA.

  • Il existe aussi d’autres archétypes, proposés par la fondation Apache ou par d’autres contributeurs.

  • Vous pouvez aussi modifier un projet existant et le transformer en projet Maven.

    • Pour cela, il suffit de créer ou copier un fichier POM et de respecter la structure de dossiers de Maven.

1.3. Dépendances

  • Comme expliqué dans l’introduction, un projet Maven ne contient pas les archives Java des artefacts qu’il utilise.

  • Par exemple, la dépendance à Junit 4.11, ajouté par défaut dans le fichier POM, dit à Maven de télécharger l’archive dans un référentiel local.

    • Plus précisément, dans le dossier ~/.m2/repository/junit/junit/4.11/.

    • Cet artefact n’est pas spécifique à votre projet, il peut être utilisé par d’autres projets Maven.

  • Si vous souhaitez utiliser une version plus récente de JUnit, il suffit de mettre à jour cette dépendance :

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.11.3</version>
    <scope>test</scope>
</dependency>
  • Maven se chargera de télécharger cette archive lorsque ça sera nécessaire. Sauf si elle a déjà été téléchargée.

  • En fait, avant de télécharger une dépendance, Maven vérifie si elle n’est pas présente dans le référentiel local et si c’est le cas, il ne la téléchargera pas une deuxième fois.

  • Notez que la dépendance spécifie aussi la portée (scope) de l’artefact.

  • Dans ce cas précis, il n’est utilisé que pendant les tests unitaires :

    • En termes Java, il n’est pas ajouté à la variable CLASSPATH pendant la compilation, mais le sera pendant les tests unitaires.

  • Si vous souhaitez utiliser d’autres artefacts, il suffit de les ajouter au fichier POM.

  • Si par exemple, si vous souhaitez utiliser les annotations proposées par la JSR 308, il suffit de chercher son identifiant (utilisez par exemple le site MVNRepository) et de l’ajouter au fichier POM :

<dependency>
    <groupId>net.java.loci</groupId>
    <artifactId>jsr308-all</artifactId>
    <version>1.1.2</version>
</dependency>

1.4. Production

  • Comme dans tout outil de production depuis plus de 40 ans (Make a été créé en 1977), la production suit une séquence spécifique de phases.

  • Par exemple, pour créer une archive Java, vous devez d’abord tester votre code. Et pour tester le code, vous devez d’abord le compiler.

  • Maven n’échappe pas à la règle, la production suit une séquence précise de phases, appelées cycle de vie.

  • Au début, vous devez connaître 3 phases :

Phase Description

compile

Compile le code Java, après avoir téléchargé les dépendances nécessaires.

test

Exécute les tests unitaires du projet, après l’avoir compilé.

package

Crée une archive Java du projet, après l’avoir testé.

  • D’autres phases sont disponibles, par exemple pour nettoyer le dossier target (mvn clean) ou générer le site web du projet (mvn site).

1.5. Configuration des plugins

  • Chaque phase de production (compilation, test, etc.) est associée à un plugin.

  • Dans la plupart des cas, vous n’avez pas besoin de les configurer, les valeurs par défaut sont suffisantes et marchent dans la plupart des cas.

  • Dans les autres cas, il est possible de configurer précisément chaque plugin à l’intérieur du fichier POM.

Par exemple, si vous souhaitez que votre code soit compilé par la version 9 de Java (en ligne de commande, javac -source 1.9 -target 1.9), vous pouvez ajouter les balises suivantes au fichier POM :

<project>
  [...]
  <build>
    [...]
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.7.0</version>
        <configuration>
          <source>1.9</source>
          <target>1.9</target>
        </configuration>
      </plugin>
    </plugins>
    [...]
  </build>
  [...]
</project>

2. Maven, La suite

Maintenant que vous avez créé votre premier projet Maven, vous vous voyez confrontés à un scénario un peu différent. Deux de vos amis ont développé une application capable de gérer des agendas et de les synchroniser avec Google Calendar.

Vous trouvez leur application géniale, mais vous ne savez pas comment l’intégrer à votre projet.

En effet, bien que sympathiques, vos amis sont fainéants : ils ne vous ont laissé que le code source et un fichier README succinct.

Deux options se présentent à vous :

  1. copier leur code dans votre projet ou

  2. le gérer comme un projet indépendant et utiliser Maven pour le transformer en un artefact réutilisable.

Comme vous faites souvent de bons choix (et accessoirement, vous avez remarqué que ceci est un tutoriel sur Maven et non sur la copie de fichiers), vous avez opté pour la deuxième option.

Vous allez élitiser [1] leur projet.

Avant de commencer, créez une copie locale du projet :

git clone https://gitlab.univ-nantes.fr/sunye-g/agenda.git && cd agenda

2.1. Configuration du projet

  • Notre première étape sera de créer le modèle objet du projet, le fichier pom.xml.

  • Cette fois, nous n’allons pas utiliser un archétype pour créer le squelette du projet, car le projet existe déjà, nous allons tout simplement créer le fichier manuellement.

  • Bien évidemment, vous pouvez copier le modèle d’un autre projet et modifier ensuite les identifiants de groupe et d’artefact.

  1. Créez un fichier appelé pom.xml à la racine du projet Agenda et ajoutez-y les balises XML suivantes :

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://maven.apache.org/POM/4.0.0"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>fr.univnantes.student</groupId>
    <artifactId>agenda</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
</project>

Vous pouvez désormais compiler le projet avec Maven :

mvn compile
  • Comme vous pouvez constater, la compilation est un succès.

  • Cela n’est pas dû à la qualité du code de vos amis, mais tout simplement parce que le compilateur Java n’a pas trouvé de code à compiler.

2.2. Réorganisation des dossiers

  • Il ne s’agit pas d’une erreur, dans le cadre d’un projet Maven, le compilateur Java cherche le code source dans le dossier src/main/java, exclusivement.



Il est bien sûr possible de dire à Maven de chercher le code source ailleurs, mais ce choix va à l’encontre d’un des principes de base de Maven :

La convention plutôt que la configuration.

  • Nous allons donc respecter ce principe et déplacer le code source à sa place appropriée.

Utilisez les commandes shell suivantes pour réorganiser le projet :

mkdir -p src/main/java
mv src/univ src/main/java

Compilez à nouveau le projet avec Maven :

mvn compile
  • Cette fois, le compilateur trouve plusieurs erreurs, ce qui est normal.

  • Il a trouvé le code source, mais n’arrive pas à trouver les artefacts logiciels utilisés par ce code.

2.3. Configuration des dépendances

  • Le fichier README.adoc nous donne une piste importante : le code source utilise les artefacts MiG Layout et Google Data.

  • La bonne nouvelle c’est que ces deux artefacts sont disponibles sur Maven Central.

  • Cependant, pour les retrouver Maven aura besoin de leurs identifiants, composés de trois parties :

    1. leurs identifiants de groupe (groupId),

    2. leurs identifiants d’artefact (artifactId) et

    3. leurs versions (version).

  • Utilisez le site MVNRepository pour retrouver les dépendances et ajoutez-les au fichier pom.xml.

Pour gagner du temps, utilisez les mots-clés com.google.gdata et miglayout.
  • A la fin, vous devez avoir une balise appelée <dependencies>, qui a comme mère directe la balise <project> et comme filles, deux balises <dependency> :

<project>
    <!-- ... -->
    <dependencies>
        <dependency>
            <groupId>com.google.gdata</groupId>
            <artifactId>core</artifactId>
            <version>1.47.1</version>
        </dependency>
        <dependency>
            <groupId>com.miglayout</groupId>
            <artifactId>miglayout</artifactId>
            <version>3.7.4</version>
        </dependency>
    </dependencies>
    <!-- ... -->
</project>

Recompilez le projet à l’aide de Maven avec mvn compile.

  • Cette fois, la compilation réussit avec quelques avertissements : le code utilise des méthodes qui ont été dépréciées.

  • Ces avertissements sont importants, car ils aideront les développeurs à mettre à jour le code pour garder la compatibilité avec les versions futures des artefacts utilisés.

  • Nous allons exploiter cette information plus tard.

2.4. Documentation du projet

  • Maintenant que le projet compile (presque) correctement, nous allons générer un site web contenant la documentation technique du projet.

  • Par défaut, Maven produit un rapport assez riche, que nous allons améliorer par la suite.

  • Nous allons commencer par configurer le plugin responsable de la phase site de la construction.

Ajoutez la balise suivante au fichier pom.xml :

<project>
    <!-- (...) -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.12.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  • En réalité, on ne configure pas vraiment le plug-in maven-site-plugin ici, on se contente de préciser à Maven qu’il doit utiliser une version récente du plug-in : en l’occurrence, la 3.12.1.

  • Cette précision est nécessaire, car elle empêchera Maven d’utiliser une version plus ancienne de ce plug-in.

Exécutez maintenant la phase site grâce à la commande suivante :

mvn site
  • Le plug-in génère les pages HTML dans le dossier target/site/.

Utilisez un navigateur pour ouvrir le fichier target/site/index.html et découvrir le rapport généré.

  • Parmi d’autres informations, vous trouverez les plug-ins utilisés par Maven, ainsi que les dépendances (directes et transitives) du projet.

2.5. Génération de la Javadoc

  • Vous avez probablement remarqué qu’à la racine du projet, il y a un dossier appelé javadoc qui contient la Javadoc générée par vos amis.

  • Ils ont pensé que c’était une bonne idée d’ajouter tous les fichiers générés au gestionnaire de versions, plutôt qu’un simple script capable de les générer.

  • Ce n’est pas le cas.

  • L’idée est mauvaise, mais vous pouvez les remercier : comme vous n’aurez pas le temps de faire toutes les erreurs possibles en une seule vie, vous pouvez apprendre des erreurs des autres.

  • Ou, pour citer Confucius :

L’homme sage apprend de ses erreurs, l’homme plus sage apprend des erreurs des autres.

— Confucius
  • Vous avez compris, vous pouvez utiliser Maven pour générer automatiquement la Javadoc pendant le processus de build.

  • En effet, Maven est capable d’intégrer d’autres rapports au site généré et c’est ce que nous allons faire avec la Javadoc.

Ajoutez les balises suivantes au fichier pom.xml :

<project>
    <!-- (...) -->
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>3.5.0</version>
                <configuration>
                    <additionalJOption>-Xdoclint:none</additionalJOption>
                </configuration>
            </plugin>
        </plugins>
    </reporting>
</project>
  • La balise <reporting> nous permet de configurer les rapports qui apparaîtront dans le site web généré, plus précisément dans la section Project Reports.

  • Le plug-in maven-javadoc-plugin permet plusieurs options.

  • Ici, on lui dit de ne pas arrêter la génération lorsqu’il rencontre des erreurs de syntaxe dans les commentaires.

  • Bien évidemment, il n’est pas nécessaire de dire que vos amis apprécient particulièrement ce type d’erreur.

Exécutez à nouveau la commande mvn site et rechargez la page index.html.

  • Le site généré contient désormais la Javadoc.

  • Remarquez que la Javadoc générée contient des liens hypertexte vers les classes de la JDK, comme Object ou ActionListener.

2.6. Évaluation de la qualité du code source

  • La Javadoc est certes une bonne source d’informations pour ceux qui souhaitent utiliser le code fait par vos amis, mais elle ne donne aucune information sur la qualité de ce code.

  • En effet, toujours fainéants, vos amis n’ont écrit aucun test, ni unitaire, ni d’intégration.

  • Vous ne savez ni si le code est fiable ni s’il est maintenable.

  • Heureusement pour vous, plusieurs outils d’analyse statique de code Java existent et pourront vous aider à estimer la qualité du code source et à l’améliorer.

  • Par exemple :

  • PMD,

  • Checkstyle,

  • Spotbugs,

  • Error Prone,

  • The Checker Framework

  • BlockHound et quelques autres.

  • Ces outils peuvent s’intégrer aux rapports produits par Maven, grâce à des plug-ins spécifiques.

  • Pour évaluer le code de vos amis, nous allons en utiliser deux, qui permettent à Maven d’utiliser PMD et Spotbugs.

Ajoutez les balises suivantes à l’intérieur de la balise <reporting> de votre fichier pom.xml :

<reporting>
    <plugins>
        <!-- (...) -->
        <plugin>
            <groupId>com.github.spotbugs</groupId>
            <artifactId>spotbugs-maven-plugin</artifactId>
            <version>4.8.3.0</version>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-pmd-plugin</artifactId>
            <version>3.21.2</version>
        </plugin>
    </plugins>
</reporting>

Exécutez à nouveau la commande mvn site et rechargez la page index.html.

  • Trois nouveaux rapports s’affichent à côté de celui de la Javadoc : SpotBugs, PMD et CPD.

  • Ce dernier, Copy Paste Detector, affiche les morceaux de code dupliqués du projet.

  • Comme vous pouvez constater, le format des rapports est similaire, il s’agit d’un tableau contenant le nom de la règle (ou bug dans SpotBugs), la classe et les lignes de code où la règle n’a pas été respectée.

  • Un inconvénient de ces rapports est qu’il n’est pas possible de les utiliser pour accéder directement au code source.

  • Un autre plug-in de Maven peut résoudre cet inconvénient : JXR.

  • En effet, JXR permet la création de références croisées entre les rapports et le code source.

  • Son utilisation se fait en deux étapes : (i) ajout du plug-in à la génération du site et (ii) configuration des autres plug-ins.

Après ces modifications, la balise <reporting> doit ressembler à :

<reporting>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-javadoc-plugin</artifactId>
            <version>3.5.0</version>
            <configuration>
                <additionalJOption>-Xdoclint:none</additionalJOption>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jxr-plugin</artifactId>
            <version>3.1.1</version>
        </plugin>
        <plugin>
            <groupId>com.github.spotbugs</groupId>
            <artifactId>spotbugs-maven-plugin</artifactId>
            <version>4.8.3.0</version>
            <configuration>
                <linkXref>true</linkXref>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-pmd-plugin</artifactId>
            <version>3.21.2</version>
            <configuration>
                <linkXref>true</linkXref>
            </configuration>
        </plugin>
    </plugins>
</reporting>

3. Conclusion

  • Né au sein du projet Jakarta de la fondation Apache, Maven est devenu un outil incontournable de construction automatique de projets Java, créant les bases de l’intégration continue.

  • Actuellement, Maven a un concurrent de poids, Gradle, dont le fichier de configuration utilise un format plus lisible que le XML des fichiers POM. Si Gradle n’est pas encore aussi populaire que Maven, il a le potentiel de le devenir.

  • La bonne nouvelle c’est que Gradle respecte les conventions établies par Maven et sait utiliser les référentiels local et distant des projets Maven.

  • On peut passer d’un projet Maven à un projet Gradle (et vice-versa) sans beaucoup d’effort.

  • Maven n’est pas seulement utile lors de la création d’un nouveau projet, il l’est aussi pour les projets existants.

  • L’adoption des conventions Maven entraîne des efforts supplémentaires, mais qui ne présente que des avantages.

  • En effet, elles ne sont pas exclusives aux projets Maven, elles sont aussi respectées par d’autres outils de production, comme Gradle ou Bazel.

  • Respecter ces conventions c’est rendre le code plus accessible aux autres développeurs.

  • Enfin, les outils d’analyse statique de code fournissent des informations très intéressantes sur le code source.

  • Mais ces informations ne doivent pas être considérées individuellement, mais dans leur globalité et dans le contexte d’un projet.

  • Ces outils sont personnalisables et peuvent s’adapter aux règles de chaque projet.

4. Après le TP

  1. Convertissez certaines classes du projet agenda en Kotlin et configurez Maven pour compiler les sources Kotlin et Java

Les sources Kotlin doivent êtres placées dans src/main/kotlin/
  1. Trouvez la commande Maven qui liste toutes les dépendances d’un projet

  2. Remplacez la dépendance à Guava version 13.0.1 par une version plus récente

  1. Trouvez et configurez un plugin Maven capable de générer un ficher SBOM (Software Bill Of Materials) à la fin de la production

  1. Ajoutez JUnit Jupiter à vos dépendances

  2. Écrivez au moins 1 test unitaire (en utilisation JUnit)

  3. Configurez le plugin surefire pour exécuter vos tests unitaires

  4. Utilisez Jacoco et PITest pour évaluer la qualité de vos tests unitaires


1. Le verbe "maveniser" n’existant pas et "Maven" signifiant "expert" ou "connaisseur", je me permets cette localisation.