Previously, in the decouverte series
Dans l’article précédent, je vous ai présenté les objectifs de cette série d’articles découverte. Dans cet article on va s’intéresser à un outil en particulier : Gradle.
L’objectif de cet article est de vous donner les bases d’un outil dont on va régulièrement se servir dans les prochains articles et qu’en plus j’apprécie tout particulièrement.
Alors, Gradle, Qu’est-ce que c’est ?
Gradle est, ce qu’on appelle un « Outil de build». C’est à dire, que cet outil va pouvoir gérer un grand nombre d’actions sur votre projet, telles que :
Compiler les sources
Exécuter les tests
Packager votre projet (création d’une archive JAR dans notre cas)
Exécuter (ou lancer) l’application
Gérer les technologies utilisées dans le projet sous forme de dépendances
Et beaucoup d’autres fonctionnalités qu’on découvrira petit à petit
Le principal concurrent, et je pense le plus utilisé, est Apache Maven. Gradle fait globalement le même métier que Maven, mais, à mon avis, beaucoup mieux. On aura l’occasion de revenir dessus par la suite.
Je ne m’attarde pas sur l’installation, je vous conseille de suivre la documentation officielle de Gradle qui se trouve ici. Cette page décrit très bien les différentes manières de l’’installer.
Tout comme Maven, Gradle se configure dans un fichier. Le fichier build.gradlequi est l’équivalent du pom.xmlde Maven. La grande différence est que pour Maven ce fichier s’écrit XML, alors que le fichier de configuration de Gradle s’écrit dans le langage de programmation Groovy. Rassurez vous, pas besoin d’installer Groovy sur votre machine :). L’utilisation d’un langage de programmation à la place du XML va apporter une certaine souplesse à notre projet.
Bien, Commençons 😃.
Dans le dossier où vous désirez créer votre projet, à la racine créez un fichier build.gradle.
2 Écrivez ceci dans le fichier :
apply plugin : 'java'
3. Lancez dans ce dossier la commande suivante : gradle build
Vous devriez obtenir ce résultat:
Bon, on est d’accord, le projet est quasiment vide. Dans ce cas, on ne fait pas grand-chose.
Rajoutons un peu de code. Mais avant il faut savoir où il nous faut rajouter ce code. Pour cela, il est nécessaire de comprendre la structure d’un projet Gradle.
Il y a 4 dossiers principaux dans lesquels on va travailler :
src/main/java: pour nos fichiers java
src/main/resources: pour tous nos fichiers autre que java
src/test/java : pour nos tests
src/test/resources : pour tous nos fichiers nécessaires pour nos tests.
Noter également le dossier build/. Ce fichier correspond au dossier target/avec Maven. Ce dossier contiendra tout ce qui va être généré par le build, ( bytecode, jar, résultats des tests, etc.).
On va, pour l’exemple, créer une classe Guerrierqui se contentera d’avoir un nom. Dans une méthode main(), on affichera le nom de ce guerrier.
Dans le dossier src/main/java :
Créez un package fr.slickteam.decouverte.gradle
2. Dans ce package, créez un fichier Guerrier.java
3. Insérez ce contenu dans ce fichier.
4. Créez un fichier Main.java
Bien sûr, on peut facilement exécuter ce bout de code dans un IDE. Mais il est plus intéressant pour la suite d’apprendre à l’exécuter avec Gradle.
Pour exécuter notre bout de code, on va utiliser un plugin. Bon d’accord, je l’avoue, c’est une bonne excuse pour vous apprendre la notion de plugin avec Gradle.
Tout comme Maven, Gradle possède un grand nombre de plugin qui permettent d’enrichir les fonctionnalités de Gradle.
Dans notre cas, le plugin qui nous intéresse s’appelle : application. La documentation de ce plugin ce trouve ici.
Dans le fichier build.gradlepour utiliser le plugin, ajoutez la ligne suivante :
apply plugin: 'application'
Ce plugin va rajouter une nouvelle tâche d’exécution appelée run. Vous pouvez voir la liste des tâches disponibles avec la commande gradle tasks
On peut ensuite tenter de lancer la commande : gradle run
On constate que le build échoue. En effet, on n’a pas renseigné l’emplacement de notre méthode main(). Le plugin ne connaît donc pas le point d’entrée de l’application à exécuter. Ajoutons cette information :
3. Rajoutez dans le fichier :
mainClassName = "fr.slickteam.decouverte.gradle.Main"
4. Relancez la commande: gradle runapply plugin: ‘java’
apply plugin: ‘application’
mainClassName = “fr.slickteam.decouverte.gradle.Main”
Cette fois, c’est bon . Notre valeureux guerrier Childerik s’affiche bien 😃.
Au final notre fichier build.gradleressemble à ceci :
apply plugin: 'java'apply plugin: 'application'mainClassName = "fr.slickteam.decouverte.gradle.Main"
Notez, pour trouver une liste des plugins existants pour Gradle avec leur documentation, il existe ce site avec un moteur de recherche : https://plugins.gradle.org/
Ici encore le test est une excuse pour découvrir une fonctionnalité importante de Gradle, la gestion de dépendances. En effet pour tester, il nous faut utiliser une technologie de tests. Il en existe de nombreuses. Contentons-nous de la plus commune : Junit4.
Gradle gère les dépendances à notre place. Mais que veux dire « gérer à notre place » ?
Voici un flow manuel typique pour utiliser une dépendance telle que junit :
1. Il faut trouver le jar correspondant sur internet, par exemple sur le site de junit.
2) Puis l’ajouter au classpath. Alors il existe plusieurs façon de le faire. Avec un IDE ou bien ( à la main ) avec la commande javac -classpath.
Ce sont des opérations longues, et pas vraiment intéressantes. Avec Junit on est dans un cas simple car le JAR de junit se suffit à lui même. Mais, en général, ce n’est pas le cas. Par exemple quand on utilise le framework Spring, le moindre JAR nécessite une dizaine de jar en dépendances. Et ces dépendances peuvent elles-même avoir des dépendances. C’est même généralement le cas.
Voici un exemple d’un projet spring-boot pour créer une API Rest minimale. C’est à dire qu’il s’agit du minium de dépendances nécessaire pour faire un simple « Hello world » avec une API Rest en utilisant Spring-boot. Imaginez-vous télécharger toutes ces dépendances manuellement. La joie n’est-ce pas ? 😝
Mais, ce n’est pas tout, Il faut également faire attention aux versions. Regardez cette partie du graphe de dépendances.
Observez les numéros de versions.
On y trouve la dépendance spring-boot-starter-logging version 1.5.7.RELEASE qui dépend de logback-classic version 1.1.11. Et rien ne garantit que spring-boot-starter-logging fonctionnerait avec une version différente de logback-classic. Il est même fortement déconseillé de tenter. Donc, si vous tentez de gérer les dépendances manuellement, il vous faut récupérer pour chacune des dépendances la bonne version nécessaire. Et il n’est pas toujours simple de connaître cette version. Mais il y a encore pire, regardez maintenant la ligne :
org.slf4j:slf4j-api:1.7.22 -> 1.7.25
Que signifie 1.7.22 -> 1.7.25. Cela signifie qu’il y a un conflit. En effet, regardez un peu plus bas, on peut observer cette ligne apparaître à plusieurs reprises :
org.slf4j:slf4j-api:1.7.25
Cela signifie que votre projet a besoin de logback-classic à la fois en version 1.7.22 et 1.7.25. Mais ceci est impossible. Il faut dans ce cas faire un choix.
En résumé, résoudre un arbre de dépendances à la main devient très rapidement une tâche qu’on pourrait qualifier d’« herculéenne» 😅. Bon courage.
Globalement, comment Gradle gère les dépendances ? On pourrait dire qu’il le fait de la même manière que nous :
téléchargement de l’archive.
ajout de l’archive au classpath.
Mais comment trouves-t-il les dépendances ? Pour junit, va-t-il sur le site de junit ? Heureusement non. Il existe des dépôts de jar sur le net. Les plus utilisés sont :
maven-central l’historique, et utilisé par maven.
Jcenter : Plus récent.
Voici, le flux global de gestion des dépendances :
Gradle crée un dépôt local sur votre machine
Gradle vérifie si la dépendance demandée est présente dans le dépôt
Si elle est présente, elle est ajoutée au classpath du projet
Si elle n’est pas présente, Gradle va la chercher dans les dépôts distants connus, l’ajoute au dépôt local puis au classpath
On vérifie si cette dépendance nécessite également des dépendances. Si oui, on repart en étape 2.
Et pour les conflits alors ?
Ne vous inquiétez pas, Gradle gère les conflits. Même si le comportement par défaut n’est pas parfait. Le graphe de dépendances présenté dans la partie précédente fût généré par Gradle. On peut l’obtenir avec la commande :
gradle dependencies
Le conflit : org.slf4j:slf4j-api:1.7.22 -> 1.7.25 fut géré automatiquement par Gradle. Le comportement par défaut est de choisir la version la plus récente. Bien sûr, ce comportement est configurable et il est même conseillé de le configurer. Chaque conflit pouvant causer des problèmes, je vous conseille de lire cette page pour en apprendre d’avantage.
Pour ajouter une dépendance, Gradle doit connaître un ou plusieurs dépôt distant. Notre projet actuel n’en connaît aucun. Les dépôt s’ajoutent dans une ‘balise’ repositories (En réalité, ils’agit d'une fonction Groovy).
Ajoutez donc la balise repositorie dans votre fichier build.gradle.
repositories {}
2. Ajoutez maintenant le repository maven-central.
repositories { mavenCentral()}
Maintenant il nous faut ajouter la dépendance junit4. Mais avant, il nous faut savoir comment une dépendance est identifiée. Les dépendances se placent dans une balise dependencies.
Une dépendance (qu’on appelle plus généralement artifact) est identifiée de manière unique par trois identifiants :
le groupe
le nom
la version
Il nous faut donc trouver ces informations pour junit. Nous utilisons maven-central. Allons donc sur ce site et recherchons avec le moteur de recherche junit : https://search.maven.org/
Choisissons celui avec le group : junit. Pourquoi celui-là en particulier me diriez vous ? Eh bien, parce que je le connais. Ici, il n’y a pas d’autre choix que de se renseigner sur la technologie désirée.
Et pour finir, en bas à gauche de la page, on peut trouver la ligne à ajouter dans Gradle :
Voilà, c’est tout ? Hum, pas tout à fait. Le mot clé compile à une signification importante. En effet il faut encore répondre à une question.
Pourquoi ai-je besoin de cette technologie ?
Ici la réponse est :
j’en ai besoin pour :
la compilation des tests.
l’exécution des tests
Mais j’en ai pas besoin pour
la compilation des sources
l’exécution de mon application
le packaging de mon application
Pour cela Gradle nous fournit des scopes pour ces dépendances. Il en existe plusieurs. Pour le moment, limitons nous aux deux principaux :
compile
testCompile
Ici, on est intéressé par le scope testCompile
4. Ajoutez junit en scope testCompile dans la balise dependancies
dependencies { testCompile "junit:junit:4.12"}
Pour simplifier, pour le moment, quand on utilise une techno uniquement pour écrire des tests, utilisez le scope testCompile, sinon, utilisez le scope compile. Ceci devrait suffire pour le moment, on affinera ces notions plus tard.
Le fichier complet devrait donc ressembler à ceci.
apply plugin: 'java'apply plugin: 'application'mainClassName = "fr.slickteam.decouverte.gradle.Main"repositories { mavenCentral()}dependencies { testCompile "junit:junit:4.12"}
Pour vérifier que tout se passe bien, lancez la commande : gradle build
Tout ceci, est bien sympa, mais on a toujours pas de tests. Donc notre build ne fait toujours pas grand chose… Ajoutons un petit test Junit simple pour vérifier le comportement.
6. Dans le dossier src/test/java, créez à nouveau un package fr.slickteam.decouverte.gradle
Dans ce package créez un fichier GuerrierTest.java et y ajouter le contenu suivant :
Relancez la commande gradle build et observer le résultat.
On observe aisément que le build est un succès. Mais qu’en est-il du test ? A-t-il été bien exécuté ?
Pour cela, regardez dans le dossier build/reports/tests/ et ouvrez le fichier index.html dans un navigateur.
Tentons maintenant de faire échouer le test. Faites en sorte que le teste échoue et relancez la commande Gradle, puis vérifiez le résultat des pages web.
Vous pouvez trouver les sources sur mon github ici:
Dans le prochain article on va enfin commencer notre projet Guild-of-Geek. On verra notamment comment :
Créer un nouveau projet spring-boot.
Mettre en place une API Rest.
Slickteam