Aller au contenu

Écosystème de programmation II

Dans cette unité de cours, nous allons passer en revue la motivation de base et les principes du système de construction Maven. Nous apprendrons pourquoi un bon logiciel inclut des instructions de construction explicites, comment les dépendances Maven sont configurées et résolues à l'exécution, comment les plugins de construction permettent de configurer un processus de construction personnalisé, et comment les profils de construction sont utilisés pour configurer des variantes logicielles.

Essentiel du cours

Les systèmes de construction utilisent un fichier de configuration de projet explicite, afin de permettre une création rapide et fiable des livrables.

Illustration

Lorsque vous faites un gâteau, vous commencez généralement par une recette, c’est-à-dire des instructions écrites qui vous indiquent :

  1. Quels ingrédients vous devez utiliser. (Peut être un mélange préparé)
  2. Comment préparer le gâteau (ordre des ingrédients, température du four, durée, etc.)
  3. Optionnel : Description des variantes (remplacer le sucre glace par de la stévia, remplacer le lait par du lait de soja pour une variante végétalienne, etc.)

Recettes, du point de vue de l’ingénierie

Une recette est une instruction de construction. Une recette vous indique quoi obtenir et comment combiner les composants pour atteindre le résultat souhaité.

Recettes de construction logicielle

Nous conviendrions probablement tous qu’avoir des instructions précises incluant les trois éléments ci-dessus est préférable à trouver simplement un tas chaotique d’ingrédients.

  • Idéalement, construire un logiciel est comme suivre une recette : nous voulons des instructions claires sur les ingrédients nécessaires, comment préparer le plat et les variantes optionnelles.
  • Tout au long de cette unité, nous examinerons un autre composant de l’écosystème de programmation pour garantir un processus de construction structuré et explicite : les systèmes de build.
    Plus en détail, nous apprendrons :
    • ... la gestion des dépendances basée sur la configuration pour obtenir les ingrédients logiciels.
    • ... les plugins de systèmes de build, pour configurer le processus exact de construction et préparer notre "plat" logiciel.
    • ... les profils de build, pour configurer et choisir entre différentes variantes de préparation logicielle d’un simple clic.

Projets Maven

  • Dans le cadre de ce cours, nous travaillerons exclusivement avec Maven, le système de build le plus répandu pour l'écosystème Java.
  • Maven nous fournit deux composants essentiels :
    • Un format standardisé pour écrire notre recette : le fichier pom.xml.
    • Un outil en ligne de commande capable de consommer la recette automatiquement (et de construire notre logiciel).

Avant de commencer à examiner les détails de la spécification des ingrédients, de la préparation et des variantes, nous jetons un premier regard rapide sur les exigences techniques pour utiliser Maven.

Organisation d'un projet Maven

  • Les projets Maven imposent une structure interne spécifique :
    • Un fichier pom.xml (la recette)
    • Un dossier src, contenant le code source de notre logiciel.
      À l'intérieur du dossier src, Maven distingue le code de production et le code de test :
      • src/main/java : code de production
      • src/test/java : code de test

Remarque : les sources doivent toujours être organisées en packages, et les packages sont de même reflétés en dossiers dans la structure du projet.

Ainsi, la structure de dossiers minimale d’un projet Hello World, avec un package de projet ca.uqam.info, serait la suivante :

MavenHelloWorld/
├── pom.xml
└── src
    ├── main
    │   └── java
    │        └── ca
    │            └── uqam
    │                └── info
    │                    └── App.java
    └── test
        └── java
            └── ca
                └── uqam
                    └── info
                        └── AppTest.java

12 directories, 3 files

Les projets Maven imposent leur propre structure

Passer de projets standards à des projets Maven peut être un peu fastidieux, car Maven attend une structure spécifique et ne trouvera pas les composants logiciels si la structure de votre projet s'en écarte.

Maven Hello World

Nous n'avons pas besoin de créer manuellement la structure du projet, nous pouvons utiliser une commande Maven pour initialiser un nouveau projet :

  mvn archetype:generate \
  -DgroupId=ca.uqam.info \
  -DartifactId=MavenHelloWorld \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DinteractiveMode=false

Remarque : Certains systèmes (Windows) ne peuvent pas gérer les commandes sur plusieurs lignes. Supprimez le \ et placez tout sur une seule ligne.

Décomposons la commande ci-dessus :

  • archetype signifie "nous voulons utiliser un modèle de projet"
    • Il existe différents archétypes, pour différents usages. Par exemple, pour une application web ou un serveur backend, nous aurions utilisé un archetypeArtifactId différent.
  • Comme pour toute dépendance dont vous pourriez avoir besoin, votre propre logiciel doit avoir un identifiant unique. D'autres développeurs pourraient effectivement utiliser votre logiciel comme bibliothèque !
    • groupId représente une chaîne spécifique à l'organisation, généralement le nom de domaine inversé de l'entreprise pour laquelle vous travaillez. Comme nous sommes tous au département d’informatique de l’UQAM nous utilisons ca.uqam.info
    • artifactId correspond au logiciel que vous construisez. Il doit être un nom descriptif, indiquant ce que fait votre logiciel.

Classe App initiale

Le fichier pom.xml initial contient simplement une classe HelloWorld de base :

package ca.uqam.info;

/**
 * Hello world!
 *
 */
public class App {
  public static void main(String[] args) {
    System.out.println("Hello World!");
  }
}

Structures de packages

Remarquez comment l'argument initial groupId a influencé le nommage des packages du projet et la structure interne des dossiers ?

Fichier pom initial

Le fichier pom initial ressemble à ceci, tel que créé :

<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>ca.uqam.info</groupId>
    <artifactId>MavenHelloWorld</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>MavenHelloWorld</name>
    <url>http://maven.apache.org</url>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Nous voyons déjà une première entrée de dépendance, à savoir pour junit.

  • Dans l'esprit d'un bon développement logiciel, Maven suppose que nous allons tester notre logiciel.
  • Cependant, JUnit ne fait pas partie de Java standard. Par conséquent, nous avons besoin d'un bloc de dépendances.
Quelque chose de particulier dans le bloc de dépendances ?

Le bloc de dépendances pour JUnit contient en fait une entrée supplémentaire <scope>test</scope>. Cela s'explique par le fait que Maven fait la distinction entre les dépendances nécessaires pour construire un logiciel et celles nécessaires pour exécuter un logiciel. JUnit n'est pas nécessaire à l'exécution, donc Maven a ajouté un tag test supplémentaire.

Construire avec Maven

Utilisons Maven pour construire le projet, c'est-à-dire créer le bytecode Java. La commande correspondante est mvn compile.

  • La première fois que vous exécutez mvn package, nous verrons en fait comment Maven télécharge JUnit (notre première dépendance).

    • Il y aura quelques messages de journalisation :
      ...
      Downloading from central: 
        https://repo.maven.apache.org/maven2/org/apache
        /maven/surefire/common-java5/3.2.5/common-java5-3.2.5.pom
      Downloaded from central: 
        https://repo.maven.apache.org/maven2/org/apache
        /maven/surefire/common-java5/3.2.5/common-java5-3.2.5.pom
      (2.8 kB at 156 kB/s)
      ...
      
  • Une fois la commande terminée, nous trouverons un nouveau répertoire target, avec le contenu suivant :

    target/
    ├── MavenHelloWorld-1.0-SNAPSHOT.jar
    ├── classes
    │        └── ca
    │              └── uqam
    │                     └── info
    │                            └── App.class
    ...
    
    21 directories, 10 files
    

  • Entre autres, c'est exactement le même résultat que nous aurions pu créer manuellement, en utilisant le compilateur Java :

    • Un fichier JAR
    • Des fichiers de classes pour notre code source

Ne partagez pas target

Tout ce qui se trouve dans le répertoire target est généré par Maven. Cela signifie que nous n'avons pas besoin de partager le répertoire target (par exemple, dans un dépôt git). L'objectif d'un système de build est de rendre le processus de construction si facile que n'importe qui peut générer rapidement et de manière fiable une construction du projet.

Gestion des dépendances

Nous commencerons par le cadre rouge dans la figure ci-dessus :

  • Pour un gâteau, les ingrédients se réfèrent à ce que vous devez préparer avant de commencer la cuisson. Habituellement farine, sucre, beurre, lait, ...
  • Pour un logiciel, les dépendances se réfèrent aux autres logiciels (bibliothèques) que vous appelez depuis votre code.
Comment intégrer et utiliser les bibliothèques, déjà ?

Dans l'unité de cours précédente, nous avons appris que la plupart des bibliothèques doivent être téléchargées sous forme de JAR, placées activement dans le classpath et leurs packages importés. Ce n'est qu'alors que Java peut réellement utiliser la bibliothèque invoquée.

Gestion manuelle des dépendances

Les JAR sont un moyen simple de transmettre des fonctionnalités, mais à mesure que les projets grandissent, plusieurs problèmes persistent :

  • Plus vous avez de dépendances, plus vous transportez de JAR.
    • Où stocker les JAR ? Dans le dépôt ? Que faire si vous avez besoin du même JAR dans plusieurs projets, le stockez-vous deux fois ?
    • Chaque fois qu'un nouveau développeur rejoint le projet, vous devez lui transmettre tous les JAR et les faire ajouter manuellement à son classpath.
    • Compiler votre projet devient fastidieux, car vous devez toujours vérifier qu'une longue liste de dépendances est correctement installée.
    • Le client se plaint que votre logiciel ne fonctionne pas. Très probablement, il a oublié d'installer un JAR ou a installé la mauvaise version. Comment savoir lequel ?
  • Un JAR est un instantané, c'est une version fixe.
    • Que faire si une vulnérabilité de sécurité est trouvée dans un JAR que vous avez téléchargé ? Comment le savoir ?
    • Vous avez perdu un JAR nécessaire à la construction de votre projet, où le retrouver ? Quelle version fonctionne avec votre projet ?

Une véritable histoire d'horreur

Dans un laboratoire de recherche précédent, nous avions un logiciel particulièrement difficile à utiliser. Avant qu'un développeur ne puisse écrire une seule ligne de code, il devait passer au moins 30 minutes à 1 heure à configurer manuellement le projet. Le projet contenait même des JAR dont personne ne savait exactement d'où ils venaient, s'ils étaient encore nécessaires, ou ce qu'ils apportaient exactement. Il y avait une rumeur sur un stagiaire qui était là il y a environ 3 ans et qui avait créé ces JAR. Mais le stagiaire était parti depuis longtemps et personne n'avait ses coordonnées. En même temps, ce sont des artefacts logiciels volumineux qui alourdissaient notre exécutable.
D'innombrables heures de développeurs ont été perdues à cause d'une mauvaise gestion des dépendances.

Je ferai également référence à cette forme de gestion des dépendances comme "implicite", c'est-à-dire que les JAR nécessaires à la construction d'un projet sont présents implicitement, mêlés aux autres composants du projet.

Gestion descriptive des dépendances

La gestion explicite des dépendances vise à éliminer tous les problèmes mentionnés ci-dessus en spécifiant plutôt quelles dépendances existent (et où les obtenir), au lieu de gérer manuellement les fichiers JAR.

Essentiellement, les exigences pour utiliser un outil de gestion des dépendances sont :

  • Un dépôt en ligne, archivant systématiquement toutes les versions de toutes les bibliothèques
  • Un fichier de configuration local, décrivant pour chaque dépendance :
    • Un identifiant unique, par exemple "bibliothèque Google GSON"
    • La version spécifique, par exemple "2.11.0"

Avantages :

  • Les fichiers de configuration sont textuels et légers. Ils peuvent être stockés dans le projet lui-même.
  • Les fichiers de configuration sont écrits dans une syntaxe interprétable par machine. Un outil peut collecter toutes les dépendances pour vous et même modifier le classpath si nécessaire.
  • Vous avez une trace claire de toutes les versions exactes des dépendances. Vous pouvez facilement analyser votre projet pour détecter des vulnérabilités de sécurité.
  • Aucune perte si vous perdez un JAR, vous pouvez le récupérer facilement depuis le dépôt.

Gestion des dépendances avec Maven

Maven est un système de build pour Java qui offre exactement ces deux composants :

  • Un dépôt central, contenant presque toutes les bibliothèques Java jamais créées : mavencentral.org
  • Un fichier de configuration de projet qui (entre autres) liste toutes les dépendances du projet : pom.xml
    • POM signifie "Project Object Model"
    • XML est un format de fichier lisible par machine
    • Une dépendance est déclarée comme suit :
      <dependency>
          <groupId>com.google.code.gson</groupId>
          <artifactId>gson</artifactId>
          <version>2.11.0</version>
      </dependency>
      

Au lieu de télécharger nous-mêmes des fichiers JAR et de les placer dans le classpath, nous demandons à Maven de s'assurer que toutes les dépendances listées sont en place.

Jamais, vraiment jamais

Ne jamais, vraiment jamais, interférer manuellement avec la gestion des dépendances dans un projet prêt pour Maven. Si vous avez besoin d'une bibliothèque supplémentaire, éditez le pom.xml, mais ne glissez jamais un fichier JAR dans votre projet, ni ne modifiez manuellement le classpath.

Dépôts

Le dépôt local :

  • Maven maintient également un dépôt local sur votre ordinateur, le répertoire ~/.m2. Chaque bibliothèque que vous avez utilisée est mise en cache dans ce répertoire.
  • Le dépôt local a deux objectifs :
    • Performance : il est plus rapide de réutiliser un fichier JAR mis en cache que de le télécharger depuis Internet à chaque fois.
    • Mode hors ligne : vous n'êtes pas toujours en ligne. Avec les dépendances mises en cache, vous pouvez développer sans connexion Internet.

Dépôts tiers :

  • Vous pourriez rencontrer des situations où vous avez besoin d'une bibliothèque qui n'est pas dans le dépôt central officiel de Maven.
  • Exemples :
    • Bibliothèques qui ne sont pas libres d'utilisation et donc non accessibles publiquement.
    • Vos propres bibliothèques, que vous ne souhaitez pas télécharger.
  • Chacun peut créer son propre dépôt :
    • Un dépôt en ligne n'est que quelques fichiers accessibles via un serveur HTTP.
    • Cependant, par défaut, Maven ne connaît pas les dépôts tiers. Si vous voulez que Maven cherche dans votre dépôt personnel, vous devez éditer le fichier pom.xml et indiquer l'emplacement de votre dépôt tiers.

Algorithme de résolution des dépendances de Maven

Pour construire un projet, Maven tente de satisfaire toutes les dépendances avec les artefacts correspondants (les fichiers JAR et certaines métadonnées). Pour satisfaire une dépendance, Maven va :

  1. Vérifier d'abord le dépôt local .m2 pour un fichier en cache.
  2. Si non mis en cache, vérifier si un dépôt tiers est défini. (Généralement aucun n'est défini)
  3. Contacter les serveurs du dépôt Maven officiel pour récupérer l'artefact nécessaire.
flowchart LR
    resolve[\Resolve depdendency/]
    resolve --> localcheck{Artifact in local repo ?}
    localcheck -.  yes .-> done([Success])
    localcheck ==>|no| remotecheck{3rd party repo defined ?}
    remotecheck -.  yes .-> 3rdpartycheck{Artifact in 3rd party ?}
    3rdpartycheck -.  yes .-> done
    3rdpartycheck -.  no .-> centralcheck{Artifact in central ?}
    remotecheck ==>|no| centralcheck
    centralcheck ==>|yes| done
    centralcheck -.  no .-> fail([Fail])
Que se passe-t-il lorsqu'un projet est construit pour la deuxième fois ?

Maven aura déjà toutes les dépendances en cache. Il empruntera le chemin le plus direct.

Exécution des artefacts Maven

Exécuter les artefacts générés est presque identique à l'exécution de binaires créés manuellement.

Fichiers de classes

Nous pouvons exécuter sans problème les fichiers de classes générés. Notez toutefois que nous devons être à la racine de la structure des packages pour appeler notre programme :

  • Appeler le programme App.class depuis un mauvais emplacement :
$ cd target/classes/ca/uqam/info; java App
Error: Could not find or load main class App
Caused by: java.lang.NoClassDefFoundError: App
(wrong name: ca/uqam/info/App)
  • Appeler le programme App.class depuis la racine du package :
    $ cd target/classes/
    $ tree
    .
    └── ca
        └── uqam
            └── info
                └── App.class
    $ java ca/uqam/info/App
    Hello World!
    

Cycles de vie, phases et plugins

Les cycles de vie peuvent être vus comme des procédures standard, communes à la préparation de tout plat.

Pour rester dans notre exemple initial, on peut imaginer qu'il existe certaines phases communes pour préparer un gâteau :

  • Nettoyer les surfaces
  • Suivre les étapes de préparation par défaut : mélanger la pâte, chauffer le four, mettre la pâte dans un moule à ressort, cuire et attendre, décorer le gâteau.

Cycles de vie

De manière similaire, la construction de logiciels comporte quelques séquences standard de phases.

  • Dans le contexte de Maven, ces séquences standard de phases sont appelées "Cycles de vie".
  • Maven offre trois cycles de vie intégrés. Chaque cycle de vie sert un intérêt macro lié à la construction :
    • clean - effacer tout ce qui est généré
    • build - créer un nouvel artefact à partir des sources
    • site - envoyer un artefact précédemment construit vers un serveur
      (ceci ne doit pas être confondu avec les serveurs Git ; l'intérêt de site est de partager un exécutable ou autre résultat de build, pas le code source !)

Cependant, gardez à l'esprit :

  • Chaque cycle de vie apporte son propre ensemble de phases.
  • Les phases au sein d'un cycle de vie sont ordonnées : il existe une séquence claire de phases.
  • Les phases au sein d'un cycle de vie sont immuables : aucune nouvelle phase ne peut être ajoutée.

Clean

Le cycle de vie "clean" est le plus simple, il ne comporte qu'une seule phase :

  1. Phase clean : Supprimer le dossier target.

On peut voir cette phase comme l'équivalent du nettoyage des surfaces de cuisine. Nous ne voulons aucun reste des préparations précédentes lorsque nous préparons notre nouveau gâteau.

Pourquoi suffit-il de supprimer le dossier target ?

Maven génère toutes ses sorties dans le dossier target. Supprimer target garantit de repartir sur une base propre.

Default

Le cycle de vie "default" est celui le plus intéressant pour construire des logiciels :

  1. Phase validate : Vérifier la structure du projet et la disponibilité des métadonnées
  2. Phase compile : Compiler le code source
  3. Phase test : Exécuter les tests unitaires
  4. Phase package : Emballer les fichiers compilés dans un format distribuable (par ex. JAR)
  5. Phase verify : Exécuter les tests d'intégration
  6. Phase install : Installer l'artefact généré dans le dépôt local (.m2)
  7. Phase deploy : Envoyer l'artefact généré vers un dépôt distant, si configuré.

Quel est l'intérêt de clean ?

Les artefacts des builds précédents ne sont pas nécessairement effacés par les builds suivants, surtout si les builds n'exécutent pas des phases équivalentes. Exemple : si vous faites d'abord package, puis compile, les fichiers jar créés uniquement lors du build précédent restent et peuvent ne pas refléter l'état actuel du programme. Un clean supplémentaire lors du second build garantit que tous les artefacts proviennent du build le plus récent.

Site

Le cycle de vie site permet d'envoyer automatiquement un site web de projet généré vers un serveur.

  1. Phase site : Générer la documentation
  2. Phase site-deploy : Envoyer la documentation sur le serveur

Le site n'a pas d'intérêt particulier pour ce cours.

Invocation des phases

  • Les commandes Maven spécifient toujours des phases, et non des cycles de vie.
  • Lorsqu'on spécifie une phase, par exemple mvn package...
    • Toutes les phases du cycle de vie jusqu'à (et y compris) cette phase sont exécutées, dans l'ordre.
    • Les phases restantes du cycle de vie sont ignorées.

Exemple : mvn package exécute toutes les phases suivantes, dans l'ordre :

  1. validate
  2. compile
  3. test
  4. package

Pourquoi ne pas appeler mvn package clean ?

Bien que valide, l'ordre des phases serait : 1. validate 2. compile 3. test 4. package 5. clean
La dernière phase supprimerait (presque) tous les efforts précédents, car le fichier jar généré serait immédiatement effacé. Très probablement, ce n'est pas ce que vous souhaitez.

Plugins

  • Les plugins sont un mécanisme permettant d’ajouter un comportement supplémentaire ou de modifier le comportement par défaut d’une phase spécifique du cycle de vie.
  • Chaque plugin possède une phase par défaut à laquelle il est attaché, mais nous pouvons manuellement modifier la phase ciblée.

Illustration :

  • Le plugin JavaDoc permet de créer des fichiers HTML à partir des commentaires JavaDoc présents dans le code source.
  • Nous pouvons attacher le plugin à la phase package.
  • Dans ce cas, exécuter mvn package (ou toute phase ultérieure du cycle de vie par défaut) déclenchera le plugin.
  • Nous retrouverons ensuite la documentation HTML dans le dossier target.

Pourquoi mvn clean compile ne génère-t-il pas de JavaDoc ?

Le plugin javadoc est associé à la phase package. clean ne fait que supprimer le dossier target et compile n’exécute que les phases validate et compile du cycle de vie par défaut. La phase package associée au plugin n’est jamais exécutée, donc la JavaDoc n’est pas générée.

Définition des plugins

En revenant à notre comparaison initiale entre recettes de gâteau et configurations de build, nous allons maintenant examiner la section plugins du fichier pom.xml.

Chaque plugin est un petit (ou parfois plus long) extrait dans une section dédiée plugins du pom.xml. Il peut y avoir autant de plugins que nécessaire dans le fichier pom.xml :

<project>
    <build>
        <plugins>
            <!-- First plugin details -->
            <plugin>
                ...
            </plugin>
            <!-- Second plugin details -->
            <plugin>
                ...
            </plugin>
            ...
        </plugins>
    </build>
</project>

Common plugins

Il existe une multitude de plugins permettant de modifier le processus de build, mais les plus pertinents pour les projets Java standards sont :

  • Checkstyle : impose une mise en forme de code standardisée (et des limites de complexité cyclomatique).
  • JavaDoc : garantit que toutes les classes et méthodes sont documentées.
  • PMD : linter qui veille à l’absence de code vulnérable.
  • Surefire : configuration avancée de l’exécution des tests.
  • Exec : configure les informations de lancement pour exécuter directement les classes compilées.
  • Jar : configure la construction du fichier JAR cible, par exemple en ajoutant les informations du lanceur dans le fichier MANIFEST.

Dans la suite, nous allons examiner plus en détail les plugins exec, jar et javadoc.

Exec

Le plugin exec vous permet de spécifier une classe principale de votre code qui doit être appelée par défaut lors de l’exécution du programme.

  • C’est l’équivalent du célèbre triangle vert (« ») dans un IDE.
  • Il suffit d’indiquer la classe principale à exécuter :
<!-- Specify main class for exec goal -->
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>exec-maven-plugin</artifactId>
    <version>1.6.0</version>
    <executions>
        <execution>
            <goals>
                <goal>java</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <mainClass>full.package.name.YourMainClassLauncher</mainClass>
    </configuration>
</plugin>

Une fois le plugin défini, vous pouvez exécuter votre programme facilement avec :
mvn clean compile exec:java

Ajouter une configuration d’exécution Maven dans l’IDE

Une fois le plugin exec défini dans votre pom.xml, modifiez la « configuration d’exécution » de votre IDE (autrement dit, ce qui est lancé quand vous cliquez sur le triangle vert) pour qu’elle appelle simplement le plugin exec de Maven !

Maven Jar

Le plugin Maven Jar permet d’ajouter des informations supplémentaires lorsque votre programme est empaqueté dans un fichier JAR.

  • Nous avons vu précédemment qu’un JAR produit par Maven ne peut pas être exécuté sans préciser explicitement la classe principale.
  • Le plugin maven-jar-plugin permet d’indiquer cette information par défaut, en spécifiant quelle classe principale doit être inscrite dans le manifeste du JAR.
<!-- specify main class for JAR manifest-->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>3.2.0</version>
    <configuration>
        <archive>
            <manifest>
                <mainClass>full.package.name.YourMainClassLauncher</mainClass>
            </manifest>
        </archive>
    </configuration>
</plugin>

JavaDoc

Lors de la deuxième séance de laboratoire, vous avez appris une commande pour extraire manuellement toutes les informations JavaDoc de votre code, afin de générer un site web lisible par les humains.
Le plugin JavaDoc permet d’automatiser cette étape, comme un composant standard du processus de construction.

  • Activer le plugin JavaDoc est également une bonne pratique, car cela vous permet de voir immédiatement s’il existe des problèmes dans le style ou la documentation de votre code, chaque fois que vous le compilez.
  • Idéalement, le plugin est configuré pour échouer en cas d’avertissement, afin qu’aucun développeur ne soit tenté de travailler avec ou de produire du code non documenté.
    • « Je le documenterai plus tard » finit souvent par devenir « Je ne le documenterai jamais. »
<!-- Plugin to ensure all functions are commented and generate javadoc -->
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>3.4.1</version>
    <configuration>
        <javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
        <reportOutputDirectory>${project.reporting.outputDirectory}/docs
        </reportOutputDirectory>
        <failOnWarnings>true</failOnWarnings>
        <quiet>true</quiet>
    </configuration>
    <executions>
        <execution>
            <id>attach-javadocs</id>
            <goals>
                <goal>jar</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Profils

  • Souvent, vous ne développez pas qu’une seule version de votre logiciel, mais toute une palette de variantes.
  • Exemple : vous développez un jeu de Halma et vous souhaitez deux versions :
    • Une version gratuite jouable, mais qui ne prend en charge que des joueurs IA simples.
    • Une version premium offrant des joueurs IA plus avancés.
  • Vous ne voulez pas maintenir deux projets distincts, mais plutôt choisir quelle version construire d’un simple commutateur.
  • C’est un cas d’utilisation typique des profils de construction Maven.

Syntaxe des profils

  • Les profils de construction sont simplement des sections de votre pom.xml marquées comme conditionnelles.
    • Si le profil n’est pas actif, c’est comme si les lignes qu’il contient n’existaient pas.
    • Vous pouvez définir un profil par défaut et autant de profils alternatifs que nécessaire.
  • Toutes les autres lignes du pom.xml s’appliquent à tous les profils.

La syntaxe générale des profils de construction dans pom.xml est :

<project>
    ...

    <dependencies>
        ... dependencies shared by both build profiles here.
    </dependencies>

    <profiles>
        <!--Default build profile-->
        <profile>
            <id>default-profile</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            ... build profile specific pom.xml content here.
        </profile>

        <!--Some alternative build profile-->
        <profile>
            <id>some-other-profile</id>
            ... build profile specific pom.xml content here.
        </profile>
    </profiles>
</project>

Utilisation en ligne de commande

  • Tant qu’aucun profil spécifique n’est demandé lors du processus de construction, Maven construira toujours celui marqué comme activeByDefault - true.
  • Pour demander explicitement la construction avec un profil non par défaut, utilisez l’option -P (sans espace avant le nom du profil) :
 mvn clean compile exec:java -Ppremium-version

Cas d’utilisation plus avancés

Profils de construction permettant de sélectionner...

  • l’emplacement du serveur de base de données ou le type de connexion,
  • le niveau de journalisation — du mode verbeux au mode silencieux,
  • le passage entre environnement de test et de production,
  • la plateforme d’exécution cible (.exe pour Windows, .dmg pour macOS, etc.),
  • et bien plus encore.

Divers (MISC)

Maven n’est qu’un exemple d’outil de système de construction.

  • D’autres systèmes de construction pour Java existent, comme Gradle.
  • Presque chaque langage possède ses propres outils pour garantir une bonne gestion des dépendances.
    Les composants communs à tous ces systèmes sont :
    • un fichier de configuration local pour déclarer explicitement les dépendances ;
    • un dépôt central pour obtenir les artefacts ;
    • un cache local pour stocker les dépendances déjà résolues.

Gestion avancée des artefacts

Dans cette section, nous examinerons plusieurs stratégies avancées pour obtenir des artefacts non pris en charge par les serveurs Maven officiels.

  • Le cas d’usage courant est celui où vous souhaitez réutiliser du code existant comme bibliothèque — c’est-à-dire comme artefact réutilisable dans d’autres projets.
    • Exemple : vous avez peut-être implémenté une petite fonctionnalité de Tic-Tac-Toe, mais vous voulez expérimenter avec différentes interfaces tout en réutilisant le même contrôleur et la même logique métier.
  • Vous ne voulez pas copier-coller le code source dans tous les projets de test, car du code dupliqué devient rapidement ingérable.

Installation de bibliothèques locales

La première option consiste à créer manuellement un artefact dans votre dossier local .m2.

  • Comme vous vous en souvenez, Maven recherche toujours d’abord dans votre dossier .m2 local.
  • Si un artefact n’est pas disponible sur les serveurs officiels de Maven, nous pouvons néanmoins l’injecter manuellement.
  • Si l’artefact en question est lui-même un projet Maven, il suffit d’exécuter la phase package.

Exemple :

  • Nous pouvons cloner le code source du contrôleur et du modèle de TicTacToe.
  • C’est un projet Maven, donc nous pouvons exécuter mvn clean install.
  • Le projet est compilé, crée un JAR, et la phase install stocke en plus le JAR comme artefact indexé dans notre répertoire local .m2:
    $ cd ~/.m2/repository/
    $ tree ca
    ca
      └── uqam
          └── xoxinternals
              ├── 1.6
                 ├── _remote.repositories
                 ├── xoxinternals-1.6-javadoc.jar
                 ├── xoxinternals-1.6-sources.jar
                 ├── xoxinternals-1.6.jar
                 └── xoxinternals-1.6.pom
              └── maven-metadata-local.xml
    
  • Nous pouvons maintenant réutiliser la fonctionnalité TicTacToe existante dans tous nos projets de test, simplement en ajoutant une déclaration de dépendance :
    <dependency>
        <groupId>ca.uqam</groupId>
        <artifactId>xoxinternals</artifactId>
        <version>1.7</version>
    </dependency>
    

Chargement latéral (Sideloading)

  • La stratégie précédente suppose que votre dépendance est elle-même un projet Maven (sinon, vous ne pouvez pas exécuter mvn clean install).
  • Cependant, de nombreuses bibliothèques Java ne reposent pas sur Maven.
  • Heureusement, il existe une solution de contournement permettant d’injecter directement n’importe quel fichier JAR dans le dépôt local à l’aide de la ligne de commande Maven.

  • Exemple :

    • Supposons que nous ayons le fichier JAR : xoxinternals.jar (nous partons du principe que xoxinternals n’est pas un projet Maven et que nous avons simplement obtenu le fichier JAR).
    • Nous pouvons charger ce JAR dans un artefact personnalisé de notre dépôt local .m2 avec :
      mvn install:install-file -Dfile=xoxinternals.jar -DgroupId=eu.kartoffelquadrat -DartifactId=xoxinternals -Dversion=1.6 -Dpackaging=jar -DcreateChecksum=true
      
  • Ce qui entraîne la même entrée dans le dépôt local :
    $ cd ~/.m2/repository/
    $ tree eu
    eu
      └── kartoffelquadrat
      └── xoxinternals
      ├── 1.6
         ├── _remote.repositories
         ├── xoxinternals-1.6-javadoc.jar
         ├── xoxinternals-1.6-sources.jar
         ├── xoxinternals-1.6.jar
         └── xoxinternals-1.6.pom
      └── maven-metadata-local.xml
    

Dépôts tiers

  • Préparer un artefact local avec mvn clean package ou le charger manuellement n’est une solution viable que pour le développement sur une seule machine.
    • Les autres développeurs ne bénéficient pas du fait que vous ayez ajouté un artefact manuellement à votre dépôt local.
  • Les dépôts en ligne permettent de partager des artefacts (et non le code source !) avec d’autres développeurs.
    • Le dépôt Maven officiel présente certaines limites :
      • Tout y est public.
      • Rien n’y est révocable.
      • La soumission est assez fastidieuse (preuve de possession de domaine, signature numérique du code source avec une clé PGP, etc.).

        Le dépôt officiel est destiné à des versions stables et bien testées à long terme — pas aux versions bêta ou aux partages rapides d’artefacts entre membres d’une équipe.

    • Une alternative consiste à utiliser des dépôts tiers :
      • L’accès restreint est possible.
      • Vous contrôlez entièrement le contenu (tout est révocable).
      • La soumission est aussi simple que le dépôt d’un fichier sur un serveur.

Gestion des vulnérabilités

  • Les bibliothèques publiques sont plus attrayantes pour les attaquants que votre propre code :
    • Une faille de sécurité dans votre code ne touche que vous ; une faille dans une bibliothèque touche tous ses utilisateurs.
    • Chaque jour, de nouvelles failles exploitables sont découvertes dans des bibliothèques publiques.
      • Certaines sont signalées discrètement pour être corrigées.
      • D’autres sont vendues en secret au plus offrant.
  • Qu’est-ce que cela signifie pour vous ?
    • À moins que vous ne développiez une application à fort profil, il est peu probable qu’on vous cible directement.
    • Mais chaque bibliothèque que vous utilisez est une menace potentielle.
  • Que pouvez-vous faire ?
    • Soyez très sélectif avec les bibliothèques que vous incluez. Si vous n’en avez pas réellement besoin, abstenez-vous.
    • Assurez-vous de ne pas utiliser de versions contenant des vulnérabilités connues.

Utilisez un analyseur de vulnérabilités

L’un des grands avantages des systèmes de construction est la présence de fichiers de dépendances explicites et textuels.
Rien qu’en lisant un fichier pom.xml, vous savez exactement de quelles bibliothèques et de quelles versions vous dépendez.
Ces informations peuvent être automatiquement analysées par un outil d’évaluation des vulnérabilités afin de vous alerter des risques potentiels.

Littérature

Inspiration et lectures complémentaires pour les esprits curieux :

Voici le lien pour accéder à l’unité de labo : Lab 07