Systèmes de Construction (Les bases)
Dans cette unité, nous aborderons les bases des systèmes de construction et illustrerons leur fonctionnement à l'exemple de Maven. Nous commencerons par une brève récapitulation de la compilation de langages, notamment dans le contexte du langage de programmation Java, puis nous examinerons les défis associés à l'assemblage des binaires. Enfin, nous examinerons les caractéristiques principales de Maven, notamment la gestion des dépendances, et nous ferons nos premiers essais pour personnaliser le comportement du système de construction à l'aide d'un fichier de configuration.
Résumé de la lecture
Les systèmes de construction ont un seul but : s'assurer que votre code source peut être traduit de manière fiable en un produit utilisable. Bien que cela puisse sembler simple, ce n'est pas du tout une tâche facile. Les systèmes de construction sont un moyen puissant et hautement configurable d'apporter de l'ordre et de la fiabilité dans le chemin allant du code source au produit.
Rappel: Packages, imports et le classpath
Pour les débutants, l’instruction import est souvent perçue comme « quelque chose qu’il faut écrire au début de la
classe pour que le message d’erreur disparaisse ».
Pour répondre à la question « pourquoi avons-nous besoin des imports ? » (ne serait-il pas plus simple si tout était
importé automatiquement ?), nous allons examiner les principes de base suivants :
- Comment le contexte du code est structuré en
packages Java, et pourquoi c’est important. - Pourquoi vous devez importer certaines classes mais pas d’autres, et les exceptions à cette règle.
- Pourquoi il n’est pas conseillé d’importer plus que le strict nécessaire.
Rappel sur les packages
- Java organise le code en packages
- Les packages fournissent un contexte pour un ensemble de classes connexes.
- Exemple :
Mouse.classpeut être une classe imitant le comportement d’une souris biologique, ou bien une classe gérant un périphérique d’entrée humain. - Les packages environnants
animalsetdevicespermettent d’éliminer l’ambiguïté.
- Par défaut, vous ne pouvez accéder qu’aux classes définies dans le même package (ou dans
java.lang).- Exemple : Lorsqu’on travaille dans le package
animals, l’instructionnew Mouse()n’est pas ambiguë — elle créera toujours la classe imitant le comportement d’une souris biologique.
- Exemple : Lorsqu’on travaille dans le package
---
title: Example of ambigous context
---
classDiagram
namespace animal {
class Mouse {
+String species
+void squeak()
}
}
namespace devices {
class Ꮇouse {
+String brand
+void click()
+void scroll()
}
}
Importations
- Par défaut, il n’est pas possible d’instancier (ou même d’appeler) des classes situées à l’extérieur de votre package
actuel.
- Cela vous protège contre les ambiguïtés accidentelles.
- Exemple : Lorsque vous travaillez à l’extérieur des packages
animalsetdevices, que devrait-il se passer si vous créez unnew Mouse()? Il n’est pas clair de quelle classe il s’agit.
- Les importations vous permettent d’étendre le contexte de votre package courant en y ajoutant des classes
supplémentaires :
import animals.Mousevous permettra d’utiliser le modèle de la souris biologique (new Mouse()), mais pas celui du périphérique d’entrée humain.
classDiagram
namespace animals {
class Mouse {
+String species
+void squeak()
}
}
namespace devices {
class Ꮇouse {
+String brand
+void click()
+void scroll()
}
}
class Main
Mouse <.. Main: import
Illustration de
Mainutilisant une instructionimport animals.Mouse.
Pourquoi les packages et les imports sont-ils des antagonistes sémantiques ?
Les packages définissent des frontières de contexte, tandis que les imports étendent ces frontières existantes.
Qu’en est-il de String
Il existe certaines classes qui n’ont pas besoin d’être importées, c’est-à-dire que vous pouvez les utiliser dans votre
code sans ajouter explicitement une instruction import, même si elles ne font pas partie du package courant.
Un exemple est String. Lorsque vous avez écrit votre premier programme « HelloWorld », vous avez défini une chaîne de
caractères et appelé la classe System, mais vous n’avez eu besoin d’aucune importation :
// No import for String required here...
public class HelloWorld
{
public static void main(String[] args)
{
System.out.println("Hello, world!");
}
}
Mais nous venons tout juste d’apprendre que l’appel à une classe externe nécessite une importation. Que se passe-t-il ici ?
Il existe une exception à la règle
java.lang.* est importé automatiquement. Les classes de ce package sont tellement courantes que toutes sont accessibles par défaut : String, System, Math, etc.
L’astérisque
Les importations ne sont pas nécessairement limitées à une seule classe : vous pouvez aussi utiliser le caractère
générique * pour importer tout un package.
ca.quam.mgl7010.animals.*importera tous les éléments présents dans le package.- Cependant, il est généralement déconseillé d’utiliser les caractères génériques.
Que pourrait-il arriver en utilisant un caractère générique (*) ?
Les caractères génériques importent tout le contenu d’un package. Que se passe-t-il si le contenu du package change (par exemple, si une nouvelle classe y est ajoutée) et qu’un conflit survient avec quelque chose que vous importiez déjà ? Il vaut mieux importer uniquement ce dont vous avez réellement besoin — évitez les importations inutiles.
Limitations du classpath
- L’instruction
importpermet d’accéder à des classes connues de la JVM, qui seraient autrement hors contexte.- « Connue de la JVM » signifie : une classe présente sur le classpath (une variable qui indique tous les répertoires que la JVM parcourt pour trouver des classes).
Classpath et importations
Vous ne pouvez importer que ce qui se trouve sur le classpath. Si vous trouvez une bibliothèque utile sur Internet, vous ne pouvez pas simplement l’importer sans l’avoir d’abord téléchargée et ajoutée à votre classpath. La JVM ne peut pas explorer Internet pour vous !
Livraisons logicielles
Dans la plupart des cas, votre client s’intéresse peu à votre code source, mais plutôt à un fichier exécutable unique. Pour lui, peu importe comment votre programme fonctionne ; l’important est qu’il puisse facilement l’exécuter.
- Peu pratique :
- Devoir installer le Java Development Kit.
- Devoir installer un IDE.
- Devoir compiler lui-même les sources.
- Devoir se souvenir d’une commande pour lancer votre code.
- Pratique :
- Un seul fichier sur lequel le client peut simplement double-cliquer, et votre magnifique programme démarre.
Java propose un format de fichier dédié exactement à cet usage : les fichiers JAR, ou fichiers « Java ARchive ».
Fichiers JAR
- Les JAR sont des fichiers zip.
- Ils peuvent contenir tout ce que vous y mettez. Pour créer une version livrable, vous devriez ajouter :
- Tout le bytecode : (
*classfichiers,CAFEBABE…) - Un manifeste indiquant (entre autres) quelle classe sert de lanceur.
- Tout le bytecode : (
- Si vous ajoutez ces éléments, une JVM peut directement consommer (interpréter et exécuter) le fichier JAR.
- Votre client n’a besoin que d’une JVM, pas d’un environnement complet de développement Java.
- Comme le JAR contient du bytecode, il fonctionnera sur n’importe quelle plateforme.
Créer une version JAR
-
Commençons avec un programme simple :
-
Créer un fichier JAR à partir des sources est relativement simple :
# Compiler tous les fichiers java en fichiers *.class, et les placer dans un nouveau répertoire build javac -d ./build *java # Entrer dans le répertoire build cd build # Créer une archive Java (JAR) à partir de tous les fichiers *.class. # Ajouter un MANIFEST.MF pointant vers HelloWorld comme classe de lancement. jar cfe MyDeliverable.jar HelloWorld *class -
Cela produit un fichier JAR :
MyDeliverable.jar-
Contenu :
-
Avec le contenu du fichier
MANIFEST.MF:
-
-
Le fichier JAR peut être exécuté directement à l’aide de la JVM :
java -jar MyDeliverable.jar
JAR pour d’autres usages
- Les fichiers JAR ne sont pas nécessairement des applications autonomes.
- Comme le format JAR n’est qu’un conteneur zip, vous pouvez aussi y regrouper du code sous forme de bibliothèque. Cela
signifie :
- Le JAR n’est pas un programme, mais un ensemble de fonctionnalités utiles ou d’interfaces.
- Le JAR n’a pas besoin de spécifier une classe de lancement dans son manifeste, puisqu’il n’est pas destiné à être exécuté.
- Le JAR peut aussi contenir, de façon optionnelle, de la documentation et les fichiers source, afin que les utilisateurs puissent comprendre son fonctionnement interne.
De façon générale :
- Lorsque vous construisez une version livrable pour un client, seul le comportement compte. Vous ajoutez uniquement le bytecode et un manifeste précisant la classe de lancement.
- Lorsque vous construisez une bibliothèque pour d’autres développeurs, le comportement et le fonctionnement interne comptent. Vous ajoutez aussi le code source et la documentation.
Récapitulatif
Les fichiers JAR ne sont que des conteneurs. Ce qu’on y inclut dépend du public cible.
Publications logicielles avec Maven
On pourrait soutenir que sélectionner manuellement des fichiers et les regrouper dans un JAR est quelque peu peu pratique.
Idéalement :
- Créer une nouvelle version est rapide et pratique
- Créer une nouvelle version est fiable
C’est l’une des motivations clés des systèmes de build : passer rapidement, facilement et de manière fiable des sources du projet à quelque chose qui peut être livré au client.
Dans ce qui suit, nous allons examiner comment le processus de build est réalisé à l’aide de l’outil de build « Maven ».
Structure d’un projet Maven
Avant de commencer, nous devons respecter quelques contraintes imposées par Maven :
- Les projets Maven imposent une structure interne spécifique, légèrement différente de celle des projets Java standards.
- Heureusement, nous n’avons pas besoin de créer manuellement la structure initiale du projet, mais pouvons utiliser Maven pour initialiser nos projets :
mvn archetype:generate \
-DgroupId=ca.uqam.info \
-DartifactId=MavenHelloWorld \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false
Note: Certains systèmes (Windows) ne peuvent pas gérer les commandes sur plusieurs lignes. Supprimez les et placez tout sur une seule ligne.
Décomposons la commande ci-dessus :
archetypesignifie « nous voulons utiliser un modèle de projet »- Il existe différents archetypes pour différents usages. Par exemple, pour une application web ou un backend
serveur, nous aurions utilisé un
archetypeArtifactIddifférent.
- Il existe différents archetypes pour différents usages. Par exemple, pour une application web ou un backend
serveur, nous aurions utilisé un
- Comme pour toute dépendance dont vous pourriez avoir besoin, votre propre logiciel doit avoir un identifiant unique.
D’autres développeurs pourraient en effet utiliser votre logiciel comme bibliothèque !
groupIdreprésente une chaîne spécifique à une 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 utilisonsca.uqam.infoartifactIddésigne le logiciel que vous construisez. Il doit être un nom descriptif, indiquant ce que fait votre logiciel.
Une fois la commande exécutée, la structure de dossiers et de fichiers suivante sera créée :
MavenHelloWorld/
├── pom.xml
└── src
├── main
│ └── java
│ └── ca
│ └── uqam
│ └── info
│ └── App.java
└── test
└── java
└── ca
└── uqam
└── info
└── AppTest.java
12 répertoires, 3 fichiers
Pour l’instant, nous ne nous intéressons qu’au pom.xml et au fichier de classe initial App.java. Nous traiterons des tests dans une conférence ultérieure.
Classe App initiale
Le fichier pom initial n’est qu’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!");
}
}
Package structures
Notice how the initial groupId argument has affected to project's package naming and internal folder structure ?
Initial pom file
The initial pom file looks, as created by the as follows:
<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 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. Nous avons donc besoin d’un bloc de dépendances.
Quelque chose de particulier dans le bloc de dépendances ?
Le bloc de dépendances junit comporte en réalité 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 compiler 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 de scope test supplémentaire.
Compilation avec Maven
Utilisons Maven pour compiler le projet, c’est-à-dire créer le bytecode Java. La commande correspondante est
mvn package.
-
La première fois que vous exécutez
mvn package, nous verrons comment Maven télécharge junit.- Il y aura quelques messages de journalisation :
text ... 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 : ```text target/ ├── MavenHelloWorld-1.0-SNAPSHOT.jar ├── classes │ └── ca │ └── uqam │ └── info │ └── App.class ...
21 répertoires, 10 fichiers ```
- Il y aura quelques messages de journalisation :
-
Parmi d’autres, c’est exactement le même résultat que nous aurions pu créer manuellement, en utilisant le compilateur Java :
- Un fichier jar
- Les fichiers de classes de notre code source
package produit un build
Bien que la configuration initiale ait été un peu fastidieuse, un projet n’a besoin d’être configuré qu’une seule fois. À partir de là, nous pouvons produire facilement de nouveaux builds (JAR) avec la commande Maven correspondante mvn clean package.
Exécution des artefacts Maven
L’exécution des artefacts générés est presque identique à celle des 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 nous trouver à la racine de la structure des packages pour appeler notre programme :
- Exécution du programme
App.classdepuis un mauvais emplacement : - Exécution du programme
App.classdepuis la racine de la structure des packages :
Fichiers Jar
L’exécution du fichier jar n’est pas possible sans spécifier la classe principale, car par défaut le manifest ne
contient pas de référence à la classe de lancement.
- Tentative d’exécution du fichier jar sans arguments :
$ cd target; java -jar MavenHelloWorld-1.0-SNAPSHOT.jar
no main manifest attribute, in MavenHelloWorld-1.0-SNAPSHOT.jar
- Lorsque nous inspectons le fichier MANIFEST interne du jar, nous constatons qu’aucun lanceur n’est spécifié :
- Exécution du fichier jar en spécifiant la classe principale via l’argument classpath :
Note : Maven offre bien sûr un moyen d’intégrer un MANIFEST fonctionnel dans le fichier jar produit. Nous y reviendrons plus tard.
Un build propre
Le répertoire target accumule tous les artefacts jamais construits. Si vous modifiez votre code ou le pom.xml et que vous reconstruisez, de nouveaux fichiers peuvent être ajoutés et il peut être difficile de distinguer les anciens des nouveaux fichiers.
Une bonne astuce est d’utiliser toujours l’argument clean avant de compiler, ce qui efface entièrement le répertoire target : Construisez votre projet systématiquement avec **mvn clean package**
Dépendances
La plupart du temps, vous ne voulez pas tout programmer depuis le début ( voir leçon précédente sur le développement orienté vers la réutilisation)
Exemple JSON
- Nous allons maintenant examiner comment la compilation et l'exécution changent lorsque des bibliothèques supplémentaires sont impliquées.
-
Imaginez que nous voulons sérialiser (créer une représentation en chaîne de caractères lisible par machine) d'un objet Java :
-
Un objet étudiant, créé avec
new Student(34, "Maximilian", "Schiedermeier"), devrait être sérialisé en :
Création manuelle de chaîne
-
Bien sûr, je pourrais construire manuellement une chaîne JSON :
// Créer un étudiant Student myStudent = new Student(34, "Maximilian", "Schiedermeier"); // Exporter l'étudiant String jsonString = "{\n" + "\t\"age\": " + myStudent.getAge() + ",\n\t\"firstName\": \"" + myStudent.getFirstName() + "\", \n\t\"lastName\": \"" + myStudent.getLastName() + "\"\n}"; System.out.println(jsonString); -
Mais que faire si je dois exporter un autre objet ? Que se passe-t-il si la structure de l'objet change ?
Utiliser une bibliothèque
-
Il serait beaucoup plus facile de réutiliser la bibliothèque Google GSON existante :
-
Cependant, nous utilisons maintenant du code qui n'est pas le nôtre, et le compilateur, ainsi que le JDK, doivent connaître cette dépendance.
- Télécharger le fichier JAR de la bibliothèque Gson :
- Cette fois, nous compilons avec l'argument
-cp(classpath), indiquant au compilateur qu'il y a des classes supplémentaires à prendre en compte.javac -cp gson-2.11.0.jar *java - De même, lors de l'exécution du bytecode compilé, la JVM doit connaître la bibliothèque GSON :
java -cp gson-2.11.0.jar:. MainWithGson
Que pourrait-il mal se passer ?
En réutilisant la bibliothèque Google GSON, nous avons créé une "dépendance". Sans cette bibliothèque à portée de main, notre code ne peut ni être compilé, ni être exécuté.
Gestion des dépendances
La gestion des dépendances vise à éliminer tous les problèmes mentionnés précédemment en spécifiant plutôt quelles dépendances existent (et où les obtenir), plutôt que de gérer manuellement les fichiers JAR.
Essentiellement, les ingrédients pour tout 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 les vulnérabilités de sécurité.
- Aucun dommage n'est causé si vous perdez un JAR de bibliothèque, vous pouvez facilement le récupérer à partir du dépôt.
Maven
Maven est un système de construction 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, au grand jamais
Ne jamais, au grand 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, modifiez le pom.xml, mais ne faites jamais glisser et déposer un fichier JAR dans votre projet, ni modifier le classpath.
Dépôts
Le dépôt local :
- Maven maintient également un dépôt local sur votre ordinateur, dans 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 sur Internet à chaque fois.
- Mode hors ligne : Vous n'êtes peut-être 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 pas accessibles publiquement.
- Vos propres bibliothèques que vous ne souhaitez pas télécharger.
- Quiconque peut créer son propre dépôt.
- Un dépôt en ligne se compose simplement de quelques fichiers accessibles via un serveur web HTTP.
- Cependant, par défaut, Maven ne connaît pas les dépôts tiers. Si vous souhaitez que Maven recherche dans votre
propre dépôt, vous devez modifier le fichier
pom.xmlet indiquer l'emplacement de votre dépôt tiers.
L'algorithme de résolution des dépendances de Maven
Pour construire un projet, Maven essaie de satisfaire toutes les dépendances avec les artefacts correspondants (les fichiers JAR et certaines métadonnées). Pour satisfaire une dépendance, Maven :
- Vérifie d'abord le dépôt local
.m2pour un fichier mis en cache. - Si le fichier n'est pas mis en cache, il vérifie si des dépôts tiers sont définis. (Généralement, aucun n'est défini.)
- Contacte les serveurs du dépôt officiel de Maven 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 mises en cache. Il prendra le chemin le plus élevé.
Compilation vs exécution
Par défaut, Maven inclut les dépendances uniquement au moment de la compilation, c’est-à-dire que nous ne pouvons pas exécuter le JAR produit sans fournir manuellement toutes les dépendances via les arguments de classpath.
Dans une conférence ultérieure, nous apprendrons comment configurer Maven pour produire un JAR autonome, qui peut être utilisé tel quel.
Le problème avec les JARs
Les JARs sont un moyen simple de partager des fonctionnalités, mais à mesure que les projets se développent, plusieurs problèmes tendent à persister :
- Plus vous avez de dépendances, plus vous transportez de JARs avec vous.
- Où stocker les JARs ? Dans le dépôt ? Que faire si vous avez besoin du même JAR dans plusieurs projets, devez-vous les stocker deux fois ?
- Chaque fois qu'un nouveau développeur rejoint le projet, vous devez lui transmettre tous les JARs et lui faire étendre manuellement son classpath.
- Compiler votre projet devient quelque peu 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. Il est probable qu'il ait négligé d'installer un JAR, ou a installé la mauvaise version. Comment savoir lequel c'est ?
- Un JAR est un instantané, c'est une version fixe.
- Que faire si une vulnérabilité de sécurité a été trouvée dans un JAR que vous avez téléchargé. Comment le sauriez-vous ?
- Vous avez perdu un JAR dont vous avez besoin pour construire votre projet, où le retrouver ? Quelle version était-elle déjà compatible avec votre projet ?
Une véritable histoire d'horreur
Dans un précédent laboratoire de recherche, nous avions un logiciel particulièrement difficile à utiliser.
Avant qu'un développeur puisse même écrire une seule ligne de code, il devait passer au moins 30 minutes à 1 heure à configurer manuellement le projet.
Le projet avait même des JARs dont personne ne savait exactement d'où ils venaient, s'ils étaient encore nécessaires, ou ce qu'ils apportaient exactement.
Il y avait des rumeurs selon lesquelles un stagiaire, qui avait travaillé il y a environ 3 ans, avait créé les JARs.
Mais le stagiaire était parti depuis longtemps et personne n'avait ses coordonnées. En même temps, ce étaient des artefacts logiciels volumineux qui alourdaient notre exécutable.
D'innombrables heures de développement ont été gaspillées à cause d'une mauvaise gestion des dépendances.
Plugins Maven
En plus de télécharger et de mettre en cache les dépendances pour une utilisation dans le classpath local, Maven a également un second objectif : Modifier le pipeline de construction.
- Par défaut, tout ce qui se passe lors de
mvn clean packageest la compilation standard des fichiers sources (en utilisant toutes les bibliothèques spécifiées pour le processus). - Mais la plupart du temps, vous souhaitez faire plus, par exemple produire une documentation lisible par l'homme, exécuter des tests ou créer un artefact de construction avec toutes les dépendances incluses.
- Le comportement de Maven concernant le pipeline de construction peut être modifié avec des
plugins.
Un plugin est un court extrait (ou parfois pas si court) dans une section dédiée plugins du pom.xml. Vous pouvez
avoir autant de plugins que vous le souhaitez dans le pom.xml :
<project>
<build>
<plugins>
<!-- First plugin details -->
<plugin>
...
</plugin>
<!-- Second plugin details -->
<plugin>
...
</plugin>
...
</plugins>
</build>
</project>
- Chaque plugin a une emplacement par défaut dans le pipeline de construction, car la plupart des tâches n'ont de sens qu'à un moment donné du processus.
- Exemple : construire un JAR avec toutes les dépendances à l'intérieur devrait se faire à la fin, après que toutes les classes aient été compilées, que tous les tests aient réussi, etc.
Nous examinerons comment les plugins fonctionnent plus en détail, ainsi que la compréhension par Maven des points de variation des plugins dans le processus de construction dans une future leçon. Pour l'instant, nous allons voir quelques exemples de plugins utiles.
Exec
Le plugin exec vous permet de spécifier une classe principale pour votre code, qui devrait être appelée par défaut
lorsque le code est exécuté.
- C'est ce qui se rapproche le plus du fameux triangle vert ("
▶ ") - Tout ce que vous devez faire est d'indiquer la classe principale à appeler lors de l'exécution :
<!-- 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 facilement exécuter votre programme 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 l'IDE (aussi appelée lorsque le triangle vert est cliqué) pour simplement appeler le plugin exec de Maven !
Plugin Maven Jar
Le plugin JAR de Maven vous permet d'ajouter des informations supplémentaires lorsque votre programme est empaqueté dans un JAR.
- Précédemment, nous avons vu qu'un JAR produit par Maven ne peut pas être lancé, sans indiquer explicitement la classe principale.
- Le
maven-jar-pluginvous permet de fournir une information par défaut, sur laquelle classe principale doit être listée dans le manifest du JAR.
<!-- specify main class for JAR manifest-->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>ca.uqam.info.MainWithGson</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>MainWithGson</finalName>
<appendAssemblyId>true</appendAssemblyId>
</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 l'homme. Le plugin JavaDoc vous permet d'automatiser cette étape, en tant que composant standard du processus de construction.
- Activer le plugin JavaDoc est également une bonne pratique, car vous pouvez directement voir s'il y a des problèmes dans votre style de code, chaque fois que vous compilez votre code.
- Idéalement, le plugin est configuré pour échouer en cas d'avertissements, afin qu'aucun développeur ne soit tenté de
travailler avec ou de produire du code non documenté.
- "Je documenterai cela plus tard", se transforme facilement en "Je ne documenterai jamais cela."
<!-- 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>
Utilisez une bibliothèque de snippets
La plupart des développeurs ne créent pas manuellement leur pom.xml ligne par ligne, mais l'assemblent à partir de blocs préparés. Utilisez une bibliothèque de snippets, par exemple https://m5c.github.io/MavenSnippetLibrary/ pour créer rapidement un pipeline de construction fonctionnel.
Gestion des dépendances au-delà de Java
- La gestion des dépendances n'est pas un concept spécifique à Java.
- Presque chaque langage dispose d'outils pour assurer une gestion adéquate des dépendances, mais quel que soit le
langage, les deux composants principaux restent :
- Un fichier local pour spécifier les dépendances
- Un dépôt central pour obtenir des artefacts
- Un cache local des dépendances installées
- Python :
- Les dépendances du projet sont spécifiées dans un fichier
requirements.txt - Tout comme les artefacts Java, toutes les dépendances listent un identifiant unique et une version, par exemple :
- Les dépendances du projet sont spécifiées dans un fichier
- L'installateur de paquets de Python,
pip, peut consommer un fichierrequirements.txtpour télécharger des artefacts :pip install -r requirements.txt - Python n'a pas de concept de dépôt local partagé, mais offre des environnements virtuels.
- Les environnements virtuels sont des caches locaux de dépendances.
- Ils remplacent l'interpréteur Python global et permettent l'installation de dépendances spécifiques au projet.
Littérature
Inspiration et lectures supplémentaires pour les esprits curieux :