Série découverte, épisode 01 : Gradle

Jeremie Guidoux
19/03/2018

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.

Gradle ,Qu’est-ce que c’est ?

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.

Installation

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.

Mon premier projet Java avec Gradle

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 😃.

Initialisation

  1. 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.

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.).

Ajoutons un peu de code

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 :

  1. 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.

Exécuter mon projet

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.

  1. 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/

Testons notre application

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.

La complexité de la gestion des dépendances.

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.

Le système de gestion des dépendances.

Globalement, comment Gradle gère les dépendances ? On pourrait dire qu’il le fait de la même manière que nous :

  1. téléchargement de l’archive.

  2. 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 :

Voici, le flux global de gestion des dépendances :

  1. Gradle crée un dépôt local sur votre machine

  2. Gradle vérifie si la dépendance demandée est présente dans le dépôt

  3. Si elle est présente, elle est ajoutée au classpath du projet

  4. 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

  5. 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.

Ajouter une dépendance dans Gradle

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).

  1. 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:

In the next episode

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.

liste des épisodes.

Slickteam