Accueil Nos publications Blog Hey les gars, y’a du Gradle à côté !

Hey les gars, y’a du Gradle à côté !


Il y a quelques temps, j’assistais à une présentation au ToursJUG de Gradle et Maven3. En réalité, le but était de confronter les deux outils pour finir sur : “on est tous copains, on a chacun nos défauts” qui permettait de mettre tout le monde d’accord.
Depuis, j’ai migré sur Maven 3 puisque cela consistait presque uniquement à télécharger l’archive et mettre à jour la variable d’environnement M2_HOME.
J’ai également assisté à l’impuissance de plusieurs équipes travaillant sur un même projet multi-modules qui mettait leur environnement de travail à genoux à chaque compilation. (” – Hey les gars, mon Eclipse tente une compilation, on va prendre un café ?! – Ha non, j’en peux plus du café, je suis trop sur les nerfs !”) Ce magnifique projet utilisait Maven comme une bonne partie des projets Java que l’on rencontre actuellement chez nos clients. Peut-être que la fièvre de la modularisation facilitée par l’utilisation de notre cher Maven a duré un peu trop longtemps.
J’ai un peu de temps, et je me dis que c’est peut-être le moment d’ajouter un outil dans ma caisse d’artisan.

C’est quoi ce Gradle ?

Gradle est présenté comme étant un outil de construction logicielle (“build tool” comme ils disent aux states). Il se différencie donc légèrement de Maven de ce point de vue puisque Maven se définit comme étant un outil de gestion de projet logiciel (“software project management and comprehension tool”), c’est à dire qu’il est aussi un outil de construction logicielle, mais pas seulement.

On se concentre donc sur la construction logicielle.

Il fut un temps lointain où je développais des bricoles en C. Il était possible à l’époque (c’est toujours le cas d’ailleurs) d’utiliser l’honorable ‘make’ pour lancer la construction du projet décrite dans le bon vieux Makefile. Ce Makefile définissait des cibles qui consistaient à faire quelque chose à un moment. Ce moment était défini par une relation de dépendance entre cibles : “je m’exécute après mes dépendances”. Cela marchait pas mal, mais ça devenait vite un peu laborieux lorsque les projets devenaient volumineux. On faisait alors appel à une surcouche (autotools, scons…) qui offrait un langage de plus haut niveau pour décrire le projet.

Java est ensuite arrivé et dans son sillage Ant. Son classique build.xml pourrait être considéré comme le Makefile de Java. On peut vraiment tout faire avec un descripteur Ant. Tout et n’importe quoi. On peut aussi se perdre dans des actions qui n’ont rien à voir avec la construction logicielle (déconseillé tout de même par les créateurs de Ant).

Peu de temps après, Maven est arrivé et la révolution s’est enclenchée. Fini de mettre les mains dans le cambouis de la construction logicielle, on se focalise sur le projet ! C’est beau.

D’autres sont apparus depuis dans le monde Java reprenant cette idée qu’il est préférable de se focaliser sur le projet que sur des tâches annexes telles que la construction logicielle :

Et c’est donc ce dernier qui va nous intéresser pour le moment. Il propose un langage de construction logicielle qui se base sur la technologie Ant. Il offre un gestionnaire de dépendances grâce à Ivy et il vous sera donc possible d’utiliser tous les dépôts Maven existants !
Pour rappel, Ivy est un projet Apache développé initialement par un Français dont le but était d’ajouter la gestion des dépendances à un build Ant. Le descripteur de dépendance permet une grande flexibilité et tout type de dépôt est utilisable.

Il faut mentionner également que la liste des langages supportés officiellement par Gradle est limitée :

  • Java
  • Groovy
  • Scala
  • Antlr

Si vous souhaitez utiliser un langage un peu plus exotique, il vous faudra attendre un peu ou bien trouver un plugin Gradle vous permettant de compiler votre projet. Sinon, rien ne vous empêche d’utiliser les fonctionnalités de ant pour invoquer le compilateur pour votre langage favori.

Les deux notions principales nécessaires à la compréhension d’un build Gradle se limitent à:

  • project : un projet n’est pas nécessairement quelque chose à construire, cela peut être quelque chose à faire. Il est composé d’un ensemble de tâches (task).
  • task : Contrairement à Ant, Gradle ne fait pas la distinction entre une tâche et une cible : une tâche est une cible ! Cela rend la compréhension globale plus simple.  Une tâche doit être comprise comme une action atomique du build.

Installation de Gradle

Je me rends sur le site de gradle pour télécharger la dernière version disponible. En l’occurrence, il s’agit de la version  1.0-milestone-6. Les binaires suffiront. Je jette un œil à la documentation officielle (rien de mieux pour commencer) et il semblerait que l’installation ne soit pas plus compliquée qu’une installation Maven :

  1. Je dézippe l’archive dans le répertoire qui héberge l’ensemble des outils de ce type. Pour limiter les surprises de mauvais goût, on va éviter d’utiliser un chemin qui contiendrait un espace (quelle idée !).
  2. Je définis une variable d’environnement GRADLE_HOME qui pointe sur mon répertoire que je viens de dézipper
  3. J’ajoute GRADLE_HOME/bin à mon PATH pour que je puisse utiliser la commande ‘gradle’ dans ma console préférée
  4. Je définis une variable d’environnement JAVA_HOME, si elle n’existe pas, qui pointe sur un JDK 1.5 minimum (c’est la documentation Gradle qui dit ça, on lui fait confiance). On nous dit que Gradle utilisera le premier JDK qu’il trouve. Définir la variable JAVA_HOME est un moyen de savoir exactement quel JDK il utilisera et c’est pas plus mal.

Bon, il me semble que tout y est. Je lance une console et je tappe “gradle -v“. L’outil me répond fièrement :

------------------------------------------------------------
 Gradle 1.0-milestone-6
 ------------------------------------------------------------
Gradle build time: jeudi 17 novembre 2011 05 h 54 UTC
 Groovy: 1.8.4
 Ant: Apache Ant(TM) version 1.8.2 compiled on December 20 2010
 Ivy: 2.2.0
 JVM: 1.6.0_26 (Sun Microsystems Inc. 20.1-b02)
 OS: Windows XP 5.1 x86

C’est bon, on peut respirer, Gradle est installé.(“OS: Windows XP 5.1 x86” : aucune moquerie ne sera tolérée, on fait avec les outils à disposition).

Un projet “cradle” en guise d’illustration

Le projet et les sources

Bon, c’est le moment où on se lance sur un projet pour lequel Gradle sera utilisé comme outil de construction. On appellera le projet Cradle. Il utilisera deux langages de la jvm : Java et Groovy.

Ce projet se composera de 3 modules:

  • domain (Groovy)
  • dataaccess (Groovy)
  • services (Java)

Les sources du projet sont disponibles sur github : https://github.com/soatexpert/cradle . Il est important de noter que ce projet existe uniquement dans le but d’illustrer l’utilisation de Gradle. Il serait donc inutile de chercher une quelconque utilité dans les fonctionnalités implémentées dans les quelques lignes qui composent le projet cradle.

La définition des projets qui composent le build

On commence donc par ouvrir notre éditeur préféré et on retranscrit les quelques lignes précédentes dans le langage Gradle.

  1. Création du répertoire hébergeant le projet
  2. Edition du fichier settings.gradle qui listera l’ensemble des modules que le projet contient
  3. Edition du fichier build.gradle pour donner les informations globales sur chaque module du projet

Le fichier settings.gradle

Ce fichier est lu par le build Gradle dans sa phase d’initialisation. Il permet entre autre de définir les modules qui composent le projet à l’aide de la directive ‘include’



include 'domain','dataaccess','services'

Note: Le fichier settings.gradle est optionnel pour un projet simple.

On peut maintenant demander à gradle la liste des projets contenus (de son point de vue) dans “cradle” avec la commande “gradle projects“. Il nous répond :

$ pwd && gradle projects
 /cygdrive/c/MiguelBasire/workspace/cradle
 :projects
------------------------------------------------------------
 Root project
 ------------------------------------------------------------
Root project 'cradle'
 +--- Project ':dataaccess'
 +--- Project ':domain'
 \--- Project ':services'

On note qu’il n’y a pas de notion de technologies utilisées ni de dépendances entre projets.

Le fichier build.gradle

On est à présent en mesure de spécifier les technologies employées par nos différents projets. Le fichier build.gradle est utilisé par Gradle lors de sa phase de configuration de build.

On commence donc par ajouter les plugins Gradle nécessaires à la construction de nos projets :



project(':domain') {
apply plugin: 'groovy'
}

project(':dataaccess') {
apply plugin: 'groovy'
}

project(':services') {
apply plugin: 'java'
}

La méthode project nous a permis d’instancier un objet pour chaque projet qui constitue notre build. Avant l’invocation de cette méthode, l’objet n’existe pas et il n’est donc pas possible de les utiliser avant. A l’étape d’initialisation du build Gradle, nos projets étaient référencés uniquement à l’aide de leur identifiant (exemple: ‘:services’). Il faudra donc faire attention à l’ordre des blocs de configuration dans le fichier build.gradle.

Les dépendances

Les dépendances entre sous-projets

Nous ajoutons un lien de dépendance entre nos projets. Les dépendances existent entre deux artefacts dans un contexte particulier. Ces contextes sont appelés par Gradle configuration de dépendances. Cette notion est similaire au dependency scope de Maven. Dans notre cas, nous souhaitons que les dépendances existent à l’étape de compilation de nos projets. Notre fichier build.gradle devient :



project(':domain') {
apply plugin: 'groovy'
}

project(':dataaccess') {
apply plugin: 'groovy'

dependencies {
compile project(':domain')
}
}

project(':services') {
apply plugin: 'java'
dependencies {
compile project(':dataaccess')
}
}

Il faut préciser que les configurations de dépendances par défaut sont définies dans les plugins (java, groovy…).  Puisque nous avons précisé plus haut que nos projets utilisent soit le plugin ‘java’ soit le plugin ‘groovy’, nous sommes en mesure d’utiliser la configuration “compile“‘.

Nous vérifions que à l’aide de la tâche dependencies sur le projet “services” que nos dépendances sont prises en compte:

$ gradle -q services:dep
------------------------------------------------------------
 Project :services
 ------------------------------------------------------------
archives - Configuration for archive artifacts.
 No dependencies
compile - Classpath for compiling the main sources.
 \--- cradle:dataaccess:unspecified [default]
     \--- cradle:domain:unspecified [default]
default - Configuration for default artifacts.
 \--- cradle:dataaccess:unspecified [default]
     \--- cradle:domain:unspecified [default]
runtime - Classpath for running the compiled main classes.
 \--- cradle:dataaccess:unspecified [default]
    \--- cradle:domain:unspecified [default]
testCompile - Classpath for compiling the test sources.
 \--- cradle:dataaccess:unspecified [default]
    \--- cradle:domain:unspecified [default]
testRuntime - Classpath for running the compiled test classes.
 \--- cradle:dataaccess:unspecified [default]
    \--- cradle:domain:unspecified [default]

Ca a l’air de fonctionner correctement. Quelles sont les informations retournées ? Toutes les configurations de dépendances disponibles pour le projet “services” sont affichées. Un bref descriptif apparaît à côté du nom ainsi que les artefacts qui le composent. Ainsi, nous retrouvons la configuration compile qui est composée du sous projet “dataaccess” mais aussi indirectement du sous projet “domain”. Gradle gère donc les dépendances transitives.

Nous n’avons pas spécifié de version sur nos projets (ce qui explique le “unspecified” dans la sortie de la commande dependencies précédente. Nous décidons d’utiliser le même numéro de version pour l’ensemble de nos projets. Un nom de groupe pour le projet permettra d’associer logiquement nos sous projets. Cela se traduit de la manière suivante dans notre fichier build.gradle :



subprojects {

group = 'fr.soat.article.gradle'
version = '1.0.0-SNAPSHOT'
}

Nous vérifions que la version que nous venons de définir existe bien pour l’ensemble de nos projets avec par exemple la commande gradle services:dependencies:

$ gradle -q services:dep
------------------------------------------------------------
 Project :services
 ------------------------------------------------------------
[...]
compile - Classpath for compiling the main sources.
 \--- fr.soat.article.gradle:dataaccess:1.0.0-SNAPSHOT [default]
       \--- fr.soat.article.gradle:domain:1.0.0-SNAPSHOT [default]
[...]

Les informations ont bien été communiquées à l’ensemble de nos sous-projets. Le paragraphe suivant  devrait apporter quelques éclaircissements sur les quelques lignes qui viennent d’être ajoutées.

C’est quoi cette bouteille de lait ?

Petite précision pour les développeurs Java : les accolades dans les scripts gradle (et donc groovy) peuvent représenter une closure, c’est à dire un morceau de code exécutable. Nous devons donc lire



subprojects { apply plugin: 'java' }

comme une méthode subprojects() qui reçoit une closure. En l’occurrence, ce bloc sera exécuté pour chaque sous-projet déclaré dans properties.gradle . On aurait très bien pu écrire la ligne apply plugin: ‘java’ dans l’ensemble des sous-projets.

Les dépendances externes

Afin d’illustrer rapidement le mécanisme de rapatriement des dépendances externes, nous ajoutons une dépendance à JUnit pour l’ensemble de nos projets. Cette dépendance est bien évidemment inutile pour la compilation du projet et encore moins pour son exécution. Nous l’associerons donc à la configuration de dépendances ‘testCompile‘ appropriée et utilisée par le plugin ‘java’.



subprojects {
repositories {  mavenCentral()  }
}

project(':services''){
dependencies { testCompile group: 'junit', name: 'junit', version: '4.10'
}

Il n’est pas nécessaire d’ajouter la dépendance Junit à nos projets Groovy, puisque Groovy embarque JUnit !

La dépendance aurait pu s’écrire différemment :



project(':services''){

dependencies { testCompile 'junit:junit:4.10' }

Nous allons également ajouter une version explicite de groovy à nos projets. Pour ce faire, nous allons utiliser la méthode configure( projectName1, …) afin de limiter cette dépendance directe à nos projets groovy.



configure( subprojects.findAll { it.name != 'services'} ) {
apply plugin: 'groovy'
dependencies {
groovy group:'org.codehaus.groovy', name: 'groovy', version: '1.8.3'
}
}

Il y a à présent une redondance avec la déclaration du plugin ‘groovy’. Cela ne semble pas déranger gradle cependant.

Initialisation des projets

Un archetype ?

Maven nous propose les archetypes qui permettent notamment de créer la structure physique du projet. Il semblerait que Gradle soit dépourvu de cette fonctionnalité. Nous allons devoir trouver un autre moyen. Nous allons donc écrire notre première tâche gradle qui consistera à créer la structure physique de chaque sous-projet.

Des conventions sont utilisées par Gradle pour définir l’emplacement des sources et autres fichiers du projet. En l’occurrence, il s’agit des conventions Maven (pour les sources en tout cas). Les sources de notre projet Java “services” se trouveront donc dans src/main/java . Et les sources de nos projets Groovy se trouveront dans src/main/groovy . Nous nous apprêtons à écrire une tâche qui consiste à mimer le fonctionnement d’un archetype simple Maven.

Les plugins Java et Groovy utilisent chacun un objet “convention” qui contient des objets  SourceSet. Ces SourceSet définissent une structure. Nous allons extraire ces informations et créer les répertoires définis dans les SourceSets. Voici une façon d’implémenter notre besoin :



subprojects {

task initProject(description: 'Initialize project') << { task ->
task.project.sourceSets.all.groovy.srcDirs*.each {
println " Creating $it "
it.mkdirs()
}
}

}

Il faut comprendre les quelques lignes ci-dessus comme un morceau de code injecté dans l’ensemble des sous-projets connus. Une version de la méthode initProject existe donc à présent dans tous les sous-projets. Le bloc de configuration reçoit l’objet à configurer, c’est-à-dire la tâche. On extrait de cette tâche le projet auquel elle appartient. Dans ce projet, nous savons qu’un plugin apporte les SourceSets qui nous intéressent. Nous accédons au conteneur (sourceSets) SourceSetContainer qui nous permet d’obtenir tous les (all) SourceSet disponibles. Nous créons donc tous les répertoires associés aux SourceDirectorySet ‘groovy’ . Nos répertoires sont créés pour le projet “domain” à l’aide de la commande gradle domain:initProject ou bien plus court gradle domain:iP .

Deux lignes en sortie de la tâche initProject nous embêtent cependant :

The DomainObjectCollection.getAll() method is deprecated as DomainObjectCollection is now a Collection itself. Simply use the collection.
The DomainObjectCollection.findAll() method is deprecated as DomainObjectCollection is now a Collection itself. Use the matching(Spec) method.

Il semblerait que la tâche initProject fraichement extraite du net utilisait une version antérieure de Gradle. De plus, cette tâche ne fonctionnera pas sur le projet “services” puisque la property groovy n’existe pas sur le source set déclaré par le plugin ‘java’. Bon, c’est le moment, on retrousse nos manches et on ouvre un onglet sur le guide de référence du langage gradle : https://gradle.org/docs/current/dsl/index.html .
La première ligne du warning nous informe que l’accesseur getAll est déprécié et que le SourceSetContainer (une DomainObjetCollection) est devenue une spécialisation d’une simple collection. Cela veut dire que l’opération groovy  collect est disponible sur la property project.sourceSets ! Pour ceux qui n’ont aucune idée de ce qu’il s’agit, un petit tour sur cette page vous informera des beautés apportées aux collections Java par Groovy.
Notre précédente tâche devient plus simple et plus compréhensible :



task initProject(description: 'Initialize project') << { task ->
task.project.sourceSets.collect{ it.allSource.srcDirs }.flatten().each {
println "Creating $it"
it.mkdirs()
}
}

Cette tâche fonctionne pour l’ensemble de nos projets. Soulagé.

La configuration de l’IDE

La configuration pour les IDEs eclipse ou intellij se fait aisément à l’aide des plugins “eclipse” et “idea” respectivement. Nous tentons une configuration eclipse :



subprojects {
apply plugin: 'eclipse'
}

La tâche eclipse est disponible à présent pour l’ensemble des sous-projets. “gradle eclipse” permet à la racine du projet cradle d’initialiser la configuration pour la totalité des sous-projets. On est prêt c’est le grand moment tant attendu, on lance à la fois la structure de l’ensemble de nos projets et l’initialisation eclipse pour chacun d’entre eux.

$ gradle -q initProject eclipse
 Creating C:\MiguelBasire\workspace\cradle\dataaccess\src\main\resources
 Creating C:\MiguelBasire\workspace\cradle\dataaccess\src\main\java
 [...]
 Creating C:\MiguelBasire\workspace\cradle\services\src\test\resources
 Creating C:\MiguelBasire\workspace\cradle\services\src\test\java

La tâche eclipse est restée silencieuse à cause du paramètre “-q” (quiet). Nous consultons le contenu du projet “domain” :

$ find domain
 domain
 domain/.classpath
 domain/.project
 domain/.settings
 domain/.settings/org.eclipse.jdt.core.prefs
 domain/src
 domain/src/main
 domain/src/main/groovy
 domain/src/main/java
 domain/src/main/resources
 domain/src/test
 domain/src/test/groovy
 domain/src/test/java
 domain/src/test/resources

Tout est là il semblerait. Il nous reste à valider le bon fonctionnement avec l’IDE eclipse (version Indigo) en important les projets.

En lançant la tâche eclipse directement sans la tâche initProject, nous avons un fichier .classpath incomplet : les répertoires des sources ne sont pas ajoutés. De plus, si vous lancez deux fois la tâche eclipse sans passer un cleanEclipse intermédiaire, vous vous retrouverez avec des doublons. Il est clair que nous sommes en présence d’une tâche qui ne devrait pas être lancée sans les deux autres. C’est le moment d’introduire une notion de dépendances entre tâches. Voici une manière de réaliser ce besoin :



subprojects {

tasks.eclipse.dependsOn cleanEclipse
tasks.eclipseClasspath.dependsOn initProject

}

On voit ici que la relation de dépendance peut se définir en dehors de la tâche. De plus, il faut noter qu’on a utilisé une sous-tâche (eclipseClasspath) appelée par eclipse pour faire fonctionner correctement la création du fichier .classpath .

Passer à un autre IDE

Lorsque vous souhaiterez passer à un autre IDE, la manipulation sera très simple. Il vous suffit d’exécuter les étapes suivantes (passage à IntelliJ) :

  1. gradle cleanEclipse
  2. appliquer les plugins à l’ensemble de nos projets: apply plugin: ‘idea’
  3. gradle idea
  4. ouvrir IntelliJ
  5. coder !

Et dans la vraie vie, ça donne quoi ?

On va commencer à ajouter du code à notre fabuleux projet cradle afin de mettre en œuvre certaines fonctionnalités de Gradle comme :

  • Le lancement des tests
  • Modifier la configuration du build pour un projet spécifique
  • Déployer un artéfact vers un dépôt Maven
  • Réutiliser un build gradle par un projet Maven

Le lancement des tests en ligne de commande

Quelques comportements ont été ajoutés à nos objets du modèle. Ils sont donc accompagnés de quelques tests (peu importe qu’ils aient été codés avant ou après l’ajout du comportement). Le projet “domain” est écrit en Groovy.

Curieusement, bien que Groovy intègre Junit 3, il semblerait que Gradle n’ajoute pas le Junit de Gradle dans le classpath lors de l’exécution de la tâche test.
Il est donc possible de faire en ligne de commande la chose suivante :

$ pwd && groovy -cp src/main/groovy src/test/groovy/nursery/NurseryTest.groovy
 /cygdrive/c/miguelbasire/workspace/cradle/domain
 ...
 Time: 0,611
OK (3 tests)

Et lamentablement échouer avec la commande gradle  suivante :

$ pwd && gradle test
 /cygdrive/c/miguelbasire/workspace/cradle/domain
 :domain:compileJava UP-TO-DATE
 :domain:compileGroovy UP-TO-DATE
 :domain:processResources UP-TO-DATE
 :domain:classes UP-TO-DATE
 :domain:compileTestJava UP-TO-DATE
 :domain:compileTestGroovy
 [ant:groovyc] >>> a serious error occurred: junit/framework/TestCase
 [ant:groovyc] >>> stacktrace:
 [ant:groovyc] java.lang.NoClassDefFoundError: junit/framework/TestCase
[...]
FAILURE: Build failed with an exception.
* What went wrong:
 Execution failed for task ':domain:compileTestGroovy'.
 Cause: Forked groovyc returned error code: 1
* Try:
 Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 14.897 secs

C’est une véritable catastrophe. Est-ce qu’on aurait rien compris à Gradle ? Nous avons ajouté pourtant dans les dépendances de notre projet domain une version de groovy. On était en droit de penser que Gradle prendrait les dépendances de groovy par la même occasion. Que s’est-il passé ? La vérification des dépendances à l’étape de compilation des tests (gradle domain:dependencies) confirme bien qu’il n’y a aucune version de junit dans la configuration. Nous déplaçons donc la déclaration de Junit 4 afin que cette configuration soit injectée dans l’ensemble des projets Java et Groovy qui constituent notre build. Cela fonctionne correctement à présent :

$ pwd && gradle test
 /cygdrive/c/miguelbasire/workspace/cradle/domain
 :domain:compileJava UP-TO-DATE
 :domain:compileGroovy UP-TO-DATE
 :domain:processResources UP-TO-DATE
 :domain:classes UP-TO-DATE
 :domain:compileTestJava UP-TO-DATE
 :domain:compileTestGroovy
 :domain:processTestResources UP-TO-DATE
 :domain:testClasses
 :domain:test
BUILD SUCCESSFUL
Total time: 21.185 secs

On remarque que par défaut, Gradle n’est pas très bavard “BUILD SUCCESSFUL” signifie que la tâche test s’est déroulée sans encombre. Le fichier build/reports/tests/index.html contient le rapport des tests qui viennent d’être lancés.

En cas d’erreur, la tâche test est un peu plus bavarde :

$ pwd && gradle -q test
 /cygdrive/c/miguelbasire/workspace/cradle/domain
 Test nursery.NurseryTest FAILED
 5 tests completed, 1 failure
FAILURE: Build failed with an exception.
* What went wrong:
 Execution failed for task ':domain:test'.
 Cause: There were failing tests. See the report at C:\MiguelBasire\workspace\cradle\domain\build\reports\tests.
* Try:
 Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
 

Le rapport contiendra les informations nécessaires à la détection de l’anomalie. Cependant, une information dans la ligne de commande pourrait être intéressante. Il nous reste donc à modifier la tâche test pour qu’elle nous affiche le nécessaire pour une évaluation rapide de l’anomalie sans ouvrir son navigateur.


subprojects {
test {
afterTest { desc, result ->
if(result.resultType  == TestResult.ResultType.FAILURE){
logger.quiet("${result.resultType} : ${desc.name}(${desc.className}) -> ${result.exception.message}" )
}}}}

Notre sortie de ligne de commande devient :

$ pwd && gradle  test -q
 /cygdrive/c/miguelbasire/workspace/cradle/domain
 FAILURE : testMakeThemSleep_EmptyCradleShouldNotHaveToBeShaked(nursery.NurseryTest) -> You should not shake an empty cradle
 Test nursery.NurseryTest FAILED
 5 tests completed, 1 failure
FAILURE: Build failed with an exception.

Modifier la configuration du build pour un projet spécifique

Il vous arrivera très certainement de vouloir ajouter des dépendances uniquement pour un projet en particulier. En guise d’illustration,  nous souhaitons utiliser mockito pour les tests de notre projet “services” uniquement. Gradle offre la possibilité d’ajouter un descripteur de build dans un projet. Nous ajoutons donc le fichier services/build.gradle afin de spécifier une nouvelle dépendance.

$ cat services/build.gradle


dependencies {
testCompile 'org.mockito:mockito-core:1.9.0'
}

Il n’y a pas de notion de hiérarchie entre descripteur de build. Il faut comprendre que le build du projet “services” existe et qu’on injecte de la configuration à l’aide de cradle/services/build.gradle et cradle/build.gradle . Nous avons donc uniquement ajouté un peu de configuration à la construction du projet “services”. Dans notre cas, il s’agit d’un ajout de dépendance à la configuration testCompile. Ca aurait pu être un ajout ou la modification d’une tâche propre au projet.
Pour que votre IDE prenne en compte cette nouvelle dépendance, il vous faudra à nouveau générer le fichier qui décrit les dépendances. Nous restons sur notre eclipse et nous lançons donc gradle services:eclipse pour construire le nouveau fichier .classpth (Note: un simple gradle cleanEclipseClasspath eclipseClasspath aurait suffi). Une mise à jour du projet (refresh) suffira pour prendre en compte cette nouvelle dépendance dans votre instance d’eclipse.

Déployer un artéfact vers un dépôt Maven

Nous venons  de développer un projet. Il serait intéressant de le  publier. Il y a de forte chance que vous utilisiez un dépôt Maven. Nous allons donc nous intéresser au déploiement de notre “service cradle” vers un dépôt Maven.

Un plugin Maven pour Gradle permet de s’occuper du déploiement. Il permettra de réaliser exactement ce que vous faisiez pour déployer vos projets Maven. Ainsi, un gradle install déposera l’archive dans votre cache local avec un descripteur de projet pom.xml généré automatiquement.

Il serait intéressant également d’avoir une notion de release à savoir transformer une version SNAPSHOT en version livrable. La seule distinction qui existe entre ces deux versions se situe au niveau du nom de version de l’artéfact. Une tâche release permettra de marquer le build afin de la distinguer d’un build en cours de développement. Pour illustrer les fonctionnalités d’introspection de gradle, nous allons définir une tâche release qui se contentera uniquement de marqueur de build. Nous allons définir un comportement qui se basera ensuite sur la présence ou non de ce marqueur.



def version_number='1.0.0'

subprojects {
apply plugin: 'maven'

task release(description: 'fige une version avant son déploiement') {}

gradle.taskGraph.whenReady {taskGraph ->
if (taskGraph.hasTask(release)) {
version = version_number
} else {
version = version_number+"-SNAPSHOT"
}}

uploadArchives {
repositories {
mavenDeployer {
repository(url: "file://c:/integration-repo")
}}}}

Il est donc possible à présent de faire un gradle release install pour déployer notre projet dans le dépôt local. Il est également possible de déployer les artéfacts du projet où bon vous semble à l’aide de la tâche uploadArchives à laquelle nous avons transmis un mavenDeployer. Pour plus d’information sur cette fonctionnalité, il faut se rendre ici : https://gradle.org/docs/current/userguide/userguide_single.html#uploading_to_maven_repositories .

Il faut noter l’utilisation d”une variable globale version_number. Cette variable est accessible par l’ensemble des sous projets. Vous avez la possibilité de définir tout type d’objet de la même manière. Ces objets seront accessibles par vos sous projets.

Voici le résultat de la commande gradle release uploadArchives :

$ gradle -q release uploadArchives && find  c:/integration-repo -type f
 Uploading: fr/soat/article/gradle/dataaccess/1.0.0/dataaccess-1.0.0.jar to repository remote at file://c:/integration-repo
 Transferring 23K from remote
 Uploaded 23K
 Uploading: fr/soat/article/gradle/domain/1.0.0/domain-1.0.0.jar to repository remote at file://c:/integration-repo
 Transferring 29K from remote
 Uploaded 29K
 Uploading: fr/soat/article/gradle/services/1.0.0/services-1.0.0.jar to repository remote at file://c:/integration-repo
 Transferring 2K from remote
 Uploaded 2K
 c:/integration-repo/fr/soat/article/gradle/dataaccess/1.0.0/dataaccess-1.0.0.jar
 c:/integration-repo/fr/soat/article/gradle/dataaccess/1.0.0/dataaccess-1.0.0.jar.md5
 c:/integration-repo/fr/soat/article/gradle/dataaccess/1.0.0/dataaccess-1.0.0.jar.sha1
 c:/integration-repo/fr/soat/article/gradle/dataaccess/1.0.0/dataaccess-1.0.0.pom
 [...]
 c:/integration-repo/fr/soat/article/gradle/services/1.0.0/services-1.0.0.pom.md5
 c:/integration-repo/fr/soat/article/gradle/services/1.0.0/services-1.0.0.pom.sha1
 c:/integration-repo/fr/soat/article/gradle/services/maven-metadata.xml
 c:/integration-repo/fr/soat/article/gradle/services/maven-metadata.xml.md5
 c:/integration-repo/fr/soat/article/gradle/services/maven-metadata.xml.sha1

Réutiliser un build Gradle

Dans le paragraphe précédent, vous avez pu constater qu’il était possible de publier votre projet comme si il s’agissait d’un développement encadré par Maven. Si vous souhaitez donc réutiliser les artéfacts du projet, rien ne change par rapport à vos dépendances habituelles : déclaration du dépôt où sont stockés les dépendances, groupe, version…

Conclusion

De gros projets sont passés de Maven à Gradle. Hibernate par exemple nous explique quelles ont été leurs motivations dans l’article suivant : https://community.jboss.org/wiki/Gradlewhy . Cela ne signifie pas pour autant qu’il faut délaisser Maven et tous passer joyeusement à Gradle.

Pour assimiler le nécessaire (et surtout pas la totalité) de ce que Gradle propose pour configurer un projet multi-module, ça m’aura pris 2 jours. Les seules vraies difficultés se sont faites ressentir par mon manque d’expérience Groovy.
Durant cette initiation, j’ai pu apprécier la souplesse offerte par Gradle. Il est possible d’exprimer de plus d’une manière ce que l’on souhaite pour notre build.
De plus, utiliser une DSL et les API l’accompagnant semble plus naturel pour un développeur que l’écriture d’un xml très verbeux.

De nombreuses fonctionnalités Gradle  n’ont pas été utilisées dans les quelques lignes de cet article. Aucune tâche Ant n’a été appelée alors qu’elles sont directement accessibles par Gradle. La flexibilité de son gestionnaire de dépendance (Ivy)  n’a pas non plus été pleinement exploitée.
L’utilisation de Gradle dans un contexte d’intégration continue est bien entendue possible. Jenkins propose par exemple un plugin permettant d’intégrer Gradle.

L’outillage autour de Gradle semble être son défaut par rapport à Maven qui bénéficie d’éditeur de pom assez évolué à présent. Pour éditer un script build.gradle, un éditeur connaissant la syntaxe Groovy facilitera un peu la tâche.