Depuis que j’ai changé de job il y a un an et demi, je fais beaucoup de "catch-up" (rattrapage) technologique, cf article précédent.

Depuis septembre, j’ai aussi repris un autre projet (qui est encore à l’état de prototype) dont je ne connais ni les technos, ni la génèse ; il n’est pas documenté, et les docs de conception sont de type "fil rouge" (ie très succintes).

Bref tout est à faire, mais la base techno est déjà posée et ne changera pas (il y a de bonnes raisons pour ça, qui sont hors sujet ici). Pas grave, ça me dérange pas, je maîtris le "saut dans la piscine en mode éponge" !

Aujourd’hui, je vous parlerai de mes premiers pas avec Groovy. Et donc avec Gradle. En utilisant IDEA.

Prenez une boisson chaude, et des petits gateaux, et allons y relax…​

Groovy, kezako ?

De quoi on parle :

  • Groovy est un langage de programmation basé sur Java

  • Java est un langage de programmation orienté objet

  • Java (et ses dérivés, dont Groovy) est compilés en bytecode

  • ce bytecode s’exécute sur une machine virtuelle multiplateforme

Ok ? Mais alors, pourquoi Groovy ?

  • Groovy vise à simplifier et à "dé-rigidifier" Java

  • Groovy évite le "boilerplate" qui n’apporte rien et qui ne sert qu’à faire "que ça marche"

  • Groovy permet de faire des "scripts" java (ie) et de faire du "typage optionnel"

Première illustration :

// HelloWorld.java
public class HelloWorld {
	public static void main(String[] args) {
		System.out.println("Hello, World");
	}
}
// HelloWorld.groovy
println "Hello, World"

Je sais pas vous, mais moi il y en a clairement un que je préfère…​

Trois choses sont à noter dans cet exemple :

  • les ; en fin de ligne sont optionnels

  • on a un script "principal" et non pas une méthode class/méthode "main", ce qui élimine du code "boilerplate" qui n’apporte rien. En interne, l’outil va générer une classe et va prendre le code du script et tout coller dans une fonction main générée à la volée. Bref, c’est équivalent, mais plus simple.

  • on a pas besoin de mettre le chemin de package des trucs les plus souvent utilisés, un certain nombre d’import sont faits automatiquement

  • la fonction println opère par défaut sur stdout, sans qu’on ait besoin de préciser qu’on println sur la sortie standard System.out

  • pour les commandes de "premier niveau", les parenthèses sont facultatives, ie on aurait pu écrire println("toto") mais on peut écrire println "toto". De la même manière, on écrirait f a, b au lieu d’écrire f(a, b).

Bref, l’objectif c’est de faire pareil, en plus simple, pour se concentrer sur le fond plutôt que la forme.

Tout groovy est fait sur ce principe, deux autres exemples :

  • les types sont optionnels : def a = new ArrayList<String>() plutôt que ArrayList<String> a = new ArrayList<String>() donc on évite la répétition

  • les nulls sont gérés de manière plus sécuritaire : class A { String s; }; A a=null; b=a?.s on aura b==null plutôt qu’une NullPointerException, sans avoir à tester avant d’accéder !

Pour finir, un script groovy ça s’exécute de la manière suivante :

# soit avec une compilation à la volée :
groovy monscript.groovy options
# soit on le compile en bytecode java
groovyc a.groovy
# puis on exécute le bytecode java en incluant les librairies
java -jar /usr/share/groovy2/embeddable/groovy-all-2.4.5.jar -cp . a

Il a des tonnes d’autres trucs que je suis loin de maîtriser, mais la documentation du langage est très bien faite et agréable à lire, il faut juste prendre le temps de l’ingurgiter et de la digérer.

Gradle, kezako ?

De quoi on parle :

  • Gradle est ce un "task-runner", écrit en Groovy

  • un task-runner, c’est un machin qui fait des boulots

  • les boulots peuvent être liés entre eux (A dépend de B), ou pas

  • un boulot n’est à refaire que quand c’est nécessaire (on bosse pas pour rien)

  • un task-runner s’appuie sur un script qui lui dit ce qu’il doit faire

  • le script dit comment faire chaque tâche

  • dans le cas de Gradle, le script est écrit en Groovy

Quand on parle de tâches, on peut déjà penser aux tâches suivantes :

  • compiler du code

  • exécuter une application avec des paramètres par défaut

  • générer de la documentation

  • produire un fichier 'zip' de l’application pour distribution

  • pousser un site web vers un serveur

  • lancer une campagne de tests automatisés

  • poster un tweet

  • faire le café

Un task-runner, c’est un truc qui automatise une chaine de tâches répétitives.

Si vous avez déjà programmé un jour dans votre vie, vous avez sûrement du voir l’un ou l’autre des trucs suivants :

  • build.bat

  • make avec un Makefile manuel ou généré par ./configure

  • grunt et gulp pour du javascript

  • ant, maven et`gradle` pour du java

  • Robo pour PHP

  • et plein d’autres …​

Ben voilà, ces trucs là, c’est sous une forme ou l’autre, des task-runners, qui utilisent un script d’une forme ou d’une autre pour définir leurs tâches et qui exécutent ensuite les tâches demandées, en plus de celles qui sont nécessaires

Gradle, quel intérêt ?

On peut se poser la question. La réponse ? La simplification, comme pour Groovy ! On parle ici de simplifier au minimum l'écriture du script de définition des tâches. Pour le reste, ça fonctionne comme les autres.

L’idée derrière Gradle, part du constat que :

  • un script Makefile (exemple) décrit explicitement toutes les actions attendues

  • un script Ant (exemple) décrit explicitement toutes les actions attendues

  • un script Maven (exemple) décrit explicitement toutes les actions attendues

En comparaison avec les scripts donnés en exemple ci-dessus, un script Gradle se contente de décrire ce qui change de la norme.

Par exemple, un script Gradle pour un programme Groovy peut se résumer à :

apply plugin: 'groovy'
repositories {
	mavenCentral()
}
dependencies {
	compile 'group:name:version'
}

Ce qui est succinct, vous en conviendrez !

Tout ça parce qu’on n’exprime dans le script que ce qui dévie de la convention (Gradle), au lieu de répéter des choses qui doivent de toute façon suivre la convention (Ant, Maven, make, …​)

Gradle, structure en plugins

La force de Gradle réside dans le fait que ces fameuses conventions que l’on a pas besoin de spécifier, résident dans des plugins, qu’il suffit d’appliquer au script.

Un plugin, c’est ni plus ni moins constitué :

  • d’une "liste de tâches" automatiquement importées au chargement du plugin

  • d’éléments de configuration, avec des valeurs par défaut

  • de convention sur l’organisation, reflétées dans les actions et la config

  • de dépendance sur d’autres plugins éventuels

Avoir des conventions, qui sont implicites (mais documentées !) plutôt qu’explicite permet d’avoir par exemple :

  • un script gradle "vide" qui dispose déjà de tâches standard (init, tasks, wrapper …​) sans qu’on ait besoin de les définir !

  • on configure simplement les dépendances de code utilisées (les librairies via le paramètre dependencies) et où il ira les chercher (via le paramètre repositories)

  • le plugin pour un langage 'X' définit l’arborescence par défaut à suivre pour l’emplacement des fichiers sources X (src/main/X), des tests X (src/test/X)

  • un plugin 'A' va appliquer automatiquement un plugin 'B' parce que sa fonctionnalité est utile/nécessaire à l’utilisateur du plugin 'A'

Tout ça en ayant toujours à l’idée, que tout ce qui respecte les conventions prises n’a pas besoin d’être spécifié dans le script du task-runner, ce qui économise du temps, des problèmes et des emmerdes au développeur.

Gradle, étape 1 : installation native

Gradle gère les dépendances. Gradle est donc capable d’importer tout ce qui est nécessaire à son fonctionnement.

Pour installer gradle, prenez votre gestionnaire de package habituel (apt-get pour Debian, rpm pour CentOS, sdkman, etc) et installez le package "gradle"

Une fois que vous pouvez taper la commande suivante avec succès, vous êtes bon :

gradle --version

Vous avez une version gradle qui est "ce qu’elle est" (là sur ma version Ubuntu Mate 16.04 LTS, j’ai le résultat suivant :

$ gradle --version
------------------------------------------------------------
Gradle 2.10
------------------------------------------------------------
Build time:   2016-01-26 15:17:49 UTC
Build number: none
Revision:     UNKNOWN
Groovy:       2.4.5
Ant:          Apache Ant(TM) version 1.9.6 compiled on July 8 2015
JVM:          1.8.0_111 (Oracle Corporation 25.111-b14)
OS:           Linux 4.4.0-47-generic amd64

Ici, d’une part, j’ai gradle en version 2.10. Mais je vois aussi que j’ai un groovy en version 2.4.5…​ WTF ? Ben oui, gradle est écrit en groovy, donc il lui faut un groovy fonctionnel, et la version que ma distribution a installé est celle-là (on peut confirmer par un roovy --version)

Gradle, étape 2 : le wrapper, et les versions choisies pour votre projet

Vous avez un nouveau projet tout beau. Vous voulez utiliser les dernières versions stables. Comme pour plein d’autres langages, on arrive à un point qui génère généralement des galères : installer d’autres versions que celles dont on dispose.

Je passe sur la problématique (vu qu’on ne la rencontrera pas), mais résumons par les faits avérés suivants :

  • on peut utiliser une autre version de gradle pour le projet que celle qui est installée nativement

  • on peut utiliser une autre version de groovy pour le projet que celle qui est installée nativement

  • gradle se chargera de récupérer les versions nécessaires

  • gradle utilisera automatiquement les versions demandées

  • on pourrait même au final désinstaller les versions natives !

Comment on fait cette magie ? En générant un wrapper.

Un wrapper (enrobeur en français) ça fait ça :

  • truc prend un bidule, et s’emballe autour

  • truc reçoit un machin

  • truc adapte machin à bidule

  • truc transmet le machin adapté à bidule

  • bidule bidouille

  • bidulle donne son résultat à truc

  • truc dés-adapte le résultat

  • truc donne le résultat adapté à qui lui avait fourni

En résumé, un wrapper fait l’interface et masque ce qu’il contient

Le wrapper de Gradle fait exactement ça :

  • il prend les commandes qu’on lui donne

  • il prend les informations configurées (ie les versions requises)

  • il récupère les trucs nécessaires (si pas déjà récupérées)

  • il transmet les commandes aux outils dans la version demandées

  • il redonne le résultat

Et ça permet donc d’utiliser n’importe quelle version de Gradle pour le projet, sans avoir à installer, ni gérer quoi que ce soit sur la machine.

Ça permet aussi, en le distribuant avec le projet, de permettre à tous ceux qui veulent participer à notre projet, d’utiliser automatiquement et implicitement les versions prévues, comme ça tout le monde aura exactement le même comportement.

Pour notre projet, on va donc générer un wrapper, et l’ajouter au code source pour qu’il soit distribué avec.

Pour générer un wrapper, il suffit de passer la commande suivante :

gradle wrapper --gradle-version 3.1

Cette commande va travailler, et générer des fichiers dans le répertoire.

Le premier lot de fichier est le suivant :

./.gradle
./.gradle/2.10
./.gradle/2.10/taskArtifacts
./.gradle/2.10/taskArtifacts/cache.properties
./.gradle/2.10/taskArtifacts/cache.properties.lock
./.gradle/2.10/taskArtifacts/fileHashes.bin
./.gradle/2.10/taskArtifacts/fileSnapshots.bin
./.gradle/2.10/taskArtifacts/outputFileStates.bin
./.gradle/2.10/taskArtifacts/taskArtifacts.bin

Ce répertoire .gradle et son contenu contient les fichiers de travail locaux, en fonction des versions qui les ont lancées. Par exemple, on a lancé la création du wrapper avec le gradle local (en version 2.10) on a donc des fichiers qui ont été créé dans le répertoire 2.10.

L’important à retenir sur le répertoire de travail .gradle (avec un . devant) est qu’il ne sert à rien de le mettre dans le gestionnaire de code, et qu’on s’en contrefout si on l’efface (il sera recréé). Bref, on l’ignore !

Le deuxième lot de fichiers est le suivant :

./gradlew
./gradlew.bat
./gradle
./gradle/wrapper
./gradle/wrapper/gradle-wrapper.jar
./gradle/wrapper/gradle-wrapper.properties

Ceci constitue le "wrapper" en tant que tel. Ce sont les fichiers réellement utiles du wrapper (les deux scripts à la racine, et le contenu du répertoire). Ajoutez les à votre gestionnaire de code.

Vous me direz, "ouais, ok, et maintenant" ?

Et bien maintenant, partout où on devrait/voudrait taper la commande gradle, on tapera plutôt une commande ./gradlew de manière à utiliser la version choisie par le projet, plutôt que la version installée par votre distribution.

On lance ce wrapper pour voir la version :

$ ./gradlew --version
Downloading https://services.gradle.org/distributions/gradle-3.1-bin.zip
.....................................
.....................................
.....................................
.....................................
...
Unzipping /home/nipil/.gradle/wrapper/dists/gradle-3.1-bin/37qejo6a26ua35lyn7h1u9v2n/gradle-3.1-bin.zip to /home/nipil/.gradle/wrapper/dists/gradle-3.1-bin/37qejo6a26ua35lyn7h1u9v2n
Set executable permissions for: /home/nipil/.gradle/wrapper/dists/gradle-3.1-bin/37qejo6a26ua35lyn7h1u9v2n/gradle-3.1/bin/gradle
Starting a Gradle Daemon (subsequent builds will be faster)
:help
------------------------------------------------------------
Gradle 3.1
------------------------------------------------------------
Build time:   2016-09-19 10:53:53 UTC
Revision:     13f38ba699afd86d7cdc4ed8fd7dd3960c0b1f97
Groovy:       2.4.7
Ant:          Apache Ant(TM) version 1.9.6 compiled on June 29 2015
JVM:          1.8.0_111 (Oracle Corporation 25.111-b14)
OS:           Linux 4.4.0-47-generic amd64

On voit alors les choses suivantes :

  • on a simplement appelé le wrapper

  • il a téléchargé la version de gradle demandée (ie la version 3.1)

  • la version récupérée est dans sa variante "bin" (on verra ça plus tard)

  • il a installé la version hors du dossier du projet (ie dans $HOME/.gradle)

  • le gradle 3.1 récupéré inclus une version 2.4.7

  • la version affichée est 3.1, le wrapper utilise bien la version demandée au lieu du gradle natif (2.10)

Et pour finir, on retrouve dans le dossier de travail les fichiers de travail de la version 3.1 en plus des fichiers de travail de la 2.10:

./.gradle/3.1
./.gradle/3.1/taskArtifacts
./.gradle/3.1/taskArtifacts/cache.properties
./.gradle/3.1/taskArtifacts/cache.properties.lock
./.gradle/3.1/taskArtifacts/fileHashes.bin
./.gradle/3.1/taskArtifacts/fileSnapshots.bin
./.gradle/3.1/taskArtifacts/taskArtifacts.bin

Tout fonctionne correctement.

Si je résume les points importants :

  • notre projet utilise gradle 3.1 aussi longtemps qu’on utilise le wrapper

  • les gens utilisent le wrapper, récupérent et utilisent la bonne version

Personne n’aura "besoin" d’avoir gradle installé nativement.

Elle est pas belle la vie ?

Et quand on voudra changer de version gradle pour le projet, il suffit de regénérer le wrapper :

gradle wrapper --gradle-version 3.2

Puis d’inclure les fichiers regénérés dans le gestionnaire de code.

Pour les plus affamés, vous pouvez allez lire la documentation de la tâche wrapper

Groovy, étape 1 : dépendances et installation

De la même manière qu’on peut choisir la version de gradle utilisée par le projet, on peut choisir la version de groovy utilisée par le projet.

Les fichiers utilisés par gradle

On va commencer par générer un script de build "par défaut" via la tâche "init" de gradle :

./gradlew init

Cette tâche a généré deux fichiers :

./build.gradle
./settings.gradle

Le fichier build.gradle

  • sera chargé par défaut par gradle à chaque appel du wrapper

  • il contiendra toutes les tâches et le paramétrage gradle du projet

  • pour l’instant, tout est commenté, il est virtuellement vide.

Le fichier settings.gradle

  • contient le nom du projet via le paramètre rootProject.name

  • le nom par défaut est "le nom du répertoire du projet"

  • vous pouvez bien sûr le modifier

Ajoutez ces deux fichiers à votre gestionnaire de code source.

Dépôts et les dépendances du fichier build.gradle

On va maintenant gérer les dépôts et les dépendances.

Mais tout d’abord, quelques faits/rappels ou choses nouvelles :

  • java dispose d’un dépôt de librairies appelé "maven central"

  • on peut y récupérer tout ce qu’on souhaite

  • groovy est basé sur java

  • groovy peut être publié sur "maven central"

  • on récupèrera logiquement groovy depuis "maven central" :-)

  • on a vu qu’on peut utiliser la version qu’on veut de gradle

  • on utilisera logiquement la version qu’on veut de groovy

Comment on fait tout ça ? On va le voir maintenant, ouvrez le fichier build.gradle et videz le (ou modifiez le contenu).

On fait du gradle, qui est une sorte de java. On va donc avoir besoin du plugin 'java'. Ajoutez la ligne suivante dans le fichier :

apply plugin: 'java'

Cette commande :

  • charge le plugin java de gradle

  • donne accès au paramètre repositories

  • donne accès au paramètre dependencies

  • ajoute des tâches, notamment build et clean, et javadoc

On va ensuite ajouter un dépôt de code aux repositories :

repositories {
	mavenCentral()
}

Ici on indique mavenCentral() qui n’est pas une chaine de caractère, parce que gradle se charge de mettre la bonne valeur pour MavenCentral, parce que tout le monde l’utilise, autant que ça soit fait au plus simple.

Vous me direz, pourquoi il ne l’inclus pas tout automatiquement ? Parce qu’en interne entreprise vous pourriez avec un dépôt local, copie de MavenCentral, pour plus de performance et pour épargner les accès internert de votre boîte, qui sont sûrement déjà bien chargés !

Ensuite on déclarera les dépendances nécessaires (ie les librairies et compagnie) pour notre projet.

dependencies {
	compile 'mygroup:myname:myversion'
	compile group: 'mygroup', name: 'myname', version: 'myversion'
	testCompile 'mygroup:myname:myversion'
	testCompile group: 'mygroup', name: 'myname', version: 'myversion'
}

Le group est généralement un nom de du producteur du package (nom de domaine inversé), name est le nom du package à importer, et version la version demandée.

Chaque dépendance peut être nécessaire pour compiler le programme (mot clé compile) ou n’être nécessaire que pour compiler les tests (mot clé testCompile) mais ne sont pas nécessaire pour le programme en lui même.

Les deux premières lignes compile sont équivalentes, et les deux lignes testCompile aussi. Il s’agit juste de deux syntaxes possible, choisissez l’une ou l’autre syntaxe.

Dans notre cas, on se contentera de mentionner la dépendance vers la version de groovy qu’on souhaite utiliser …​ qui est logiquement nécessaire pour compiler des programmes groovy.

On aura le fichier suivant :

apply plugin: 'java'
repositories {
	mavenCentral()
}
dependencies {
	compile 'org.codehaus.groovy:groovy-all:2.3.1'
}

Là j’ai mis la version 2.3.1 de groovy, pour bien montrer :

  • qu’on peut choisir la version qu’on veut !

  • qu’elle peut être différente de la version installée nativement (2.4.5, voir début de l’article)

  • qu’elle peut être différente de la version apportée par la version gradle utilisée (groovy 2.4.7 apportée par gradle 3.1)

On verifiera que les dépendances sont bien importées :

$ ./gradlew dependencies
:dependencies
------------------------------------------------------------
Root project
------------------------------------------------------------
archives - Configuration for archive artifacts.
No dependencies
compile - Dependencies for source set 'main'.
Download https://repo1.maven.org/maven2/org/codehaus/groovy/groovy-all/2.3.1/groovy-all-2.3.1.pom
\--- org.codehaus.groovy:groovy-all:2.3.1
compileClasspath - Compile classpath for source set 'main'.
\--- org.codehaus.groovy:groovy-all:2.3.1
compileOnly - Compile dependencies for source set 'main'.
\--- org.codehaus.groovy:groovy-all:2.3.1
default - Configuration for default artifacts.
\--- org.codehaus.groovy:groovy-all:2.3.1
runtime - Runtime dependencies for source set 'main'.
\--- org.codehaus.groovy:groovy-all:2.3.1
testCompile - Dependencies for source set 'test'.
\--- org.codehaus.groovy:groovy-all:2.3.1
testCompileClasspath - Compile classpath for source set 'test'.
\--- org.codehaus.groovy:groovy-all:2.3.1
testCompileOnly - Compile dependencies for source set 'test'.
\--- org.codehaus.groovy:groovy-all:2.3.1
testRuntime - Runtime dependencies for source set 'test'.
\--- org.codehaus.groovy:groovy-all:2.3.1
BUILD SUCCESSFUL
Total time: 1.787 secs

On voit qu’il a téléchargé la version 2.3.1 demandée de Groovy !

Il l’a d’ailleurs à nouveau installé en dehors du répertoire du projet, dans un sous répertoire de $HOME/.gradle (vous pouvez vérifier par vous même :-)

On a donc maintenant un environnement gradle qui permet d’importer des librairies issues de MavenCentral, dont groovy.

Plugin Groovy

Mais vous me direz, on a ajouté un plugin 'java' …​ alors qu’on veut faire du groovy.

Simple, on ajoute le plugin 'groovy' au script …​ aussi simple que ça !

apply plugin: 'groovy'

Mais la documentation indique que ce plugin importe automatiquement le plugin 'java'. On peut donc soit lister les deux plugins dans le script gradle, ou bien seulement lister le plugin 'groovy', pour plus de simplicité

Le plugin ajoute notamment les éléments suivants :

  • des tâches de compilation comme compileGroovy et de génération de documentation groovydoc

  • définit la structure par défaut du code source (les sourceSets)

A nouveau, on retrouve la notion de convention : si on respecte la convention, on a rien à spécifier. C’est seulement si la convention n’est pas respectée (ie qu’on met les fichiers ailleurs, alors il faut spécifier quelque chose)

En l’occurence, l’arborescence conventionnelle est la suivante :

src/main/java       Production Java source
src/main/resources  Production resources
src/main/groovy     Production Groovy sources
src/test/java       Test Java source
src/test/resources  Test resources
src/test/groovy     Test groovy sources

On créé alors les répertoires listés ci-dessus :

mkdir -p src/{main,test}/{java,resources,groovy}

A partir de là, dès qu’on met un fichier .groovy au bon endroit, il sera pris en compte automatiquement au titre des tâches compileGroovy du plugin, qui est déclenchée par la tâche standard build de gradle.

C’est ce que nous allons faire ensuite !

Sinon, vous pouvez allez lire la documentation du plugin Groovy

Groovy, étape 2 : script, compilation et lancement

Nous venons de voir la convention qui définit l’arborescence de fichiers.

Les fichiers .groovy devront placés dans le répertoire src/main/groovy (ou un de ses sous répertoires, au regard de norme de nommage des packages)

Prenons un fichier script que nous appellerons Main.groovy

// fichier src/main/groovy/Main.groovy
println "Hello world, this is groovy version ${GroovySystem.version}"

On lance la compilation (on nettoie juste )

./gradlew build

On trouvera dans le répertoire build (encore une convention) les fichiers suivants :

build/
build/classes
build/classes/main
build/classes/main/Main.class
build/libs
build/libs/article.jar
build/tmp
build/tmp/jar
build/tmp/jar/MANIFEST.MF
build/tmp/compileGroovy
build/tmp/compileGroovy/groovy-java-stubs

Parmi ces éléments, on notera :

  • le fichier groovy Main.groovy compilé en bytecode java Main.class

  • un fichier .jar utilisant le nom du projet (cf settings.gradle)

  • ce fichier jar contient tous les .class de notre projet

Le reste, je ne sais pas encore trop à quoi ça sert mais pour l’instant on s’en fiche un peu :-)

Lançons notre programme !

Packaging, installation et lancement de l’application

Pour lancer le programme, le plus simple est de passer par un plugin gradle, qui va packager notre application et permettre son lancement d’une manière simple et robuste.

Ajouter le plugin Gradle 'application' (doc) au fichier build.gradle:

apply plugin: 'application'

Ce plugin fournit les éléments suivants :

  • le paramètre mainClassName qui définit la classe principale (le nom du script, dans le cas d’un script groovy)

  • la tâches run qui exécute le programme sans paramètres

Le plus important ici est ce paramètre mainClassName, il s’agira

  • soit de la classe qui contient la méthode "static main"

  • soit du nom du script groovy "principal"

Attention, dans les deux cas, le nom de la classe doit être le nom complet en incluant le package !

Exemple 1

// fichier src/main/groovy/Main.groovy
// pas de package
println "Hello world"
// fichier build.gradle
// le chemin est relatif au nom de package
// le script n'appartient à aucun package
// le script est compilé dans build/classes/main
// le chemin contient alors juste le nom du script
mainClassName="Main"

Exemple 2

// fichier src/main/groovy/a/b/c/Main.groovy
package a.b.c
println "Hello world"
// fichier build.gradle
// le chemin est relatif au nom de package
// le script appartient au package a.b.c
// le script est compilé dans build/classes/main/a/b/c
// le chemin contient le nom du script avec son **package**
mainClassName="a.b.c.Main"

Et c’est pareil si on utilise des classes plutôt que des scripts.

On peut alors lancer l’application (sans paramètres) via gradle :

$ ./gradlew run
:compileJava UP-TO-DATE
:compileGroovy
:processResources UP-TO-DATE
:classes
:run
Hello world, this is groovy version 2.3.1
BUILD SUCCESSFUL
Total time: 2.02 secs

On constate :

  • qu’on peut lancer notre application

  • qu’on a bien la version de groovy qu’on a demandé à utiliser

C’est pas génial tout ça ?!

Pour finir, le plugin 'application' applique automatiquement le plugin 'distribution' (doc) qui fournit les éléments suivants :

  • générer une archive (distZip et distTar) pour distribution)

  • installDist qui installe localement l’application

On utilisera la tâche installDist pour installer notre programme, ainsi que toutes ses dépendances, dans un répertoire local.

La tâche génère même un script de lancement qui configure tout bien pour que "tout fonctionne" : il inclus tous les jars, les ajoute au classpath, et lance la classe spécifiée par mainClassName.

Remarque : en l’installant localement, il est plus simple de lui passer des paramètres en ligne de commande

Pour la lancer, on commence par demander l’installation :

$ ./gradlew installDist
:compileJava UP-TO-DATE
:compileGroovy UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar
:startScripts
:installDist
BUILD SUCCESSFUL
Total time: 0.851 secs

Puis on la lance à la main (on peut y passer des paramètres)

$ build/install/article/bin/article
Hello world, this is groovy version 2.3.1

Comme tout à l’heure avec run, on constate :

  • qu’on peut lancer notre application

  • qu’on a bien la version de groovy qu’on a demandé à utiliser

Whouhouuuu ça y est on est montés sur la première marche, on a tout ce qu’il faut côté gradle et environnement, pour se lancer dans la programmation de notre application !

Si vous avez suivi jusqu’ici, merci pour votre attention, et amusez vous bien pour la suite.

Cependant, je vais continuer avec quelques élements qui sont (à mon humble avis) tout aussi indispensables que le reste, mais qui peuvent rester facultatifs.

Aller plus loin !

Voici quelques points qui permettront d’aller plus loin, en ajoutant des éléments supplémentaires (version, test, logging) qui sont généralement nécessaires à chaque application

Versionning de l’application (optionnel)

Le versionning de notre application est possible grâce au paramètre version du fichier build.gradle :

version = "1.2.3"

Une fois configuré, il sera utilisé pour les tâches de packaging (zip, tar, jar). Mais dans tous les cas, il est facultatif.

Logging

Dans un programme, généralement on log des trucs. Que ça soit du debug, que ça soit de l’info, un programme ça log, surtout si ça tourne pendant longtemps.

Comment logger ? Il existe des dizaines de framework, mais Slf4j+Logback semblent être sur le devant de la scène.

Commen ça se présente :

  • une interface API commune (Slf4j) qui définit l’interface

  • une implémentation réelle du logger (Logback) qui fait le taf

  • l’intégration facile du logger à notre code (annotation groovy Slf4j)

Pour les utiliser, c’est super simple :

  • on va ajouter les dépendances Slf4j et Logback à notre build.gradle

  • on va importer l’annotation groovy @Slf4j dans nos fichiers

  • on applique l’annotation à toutes les classes où on veut logger

Simple non ? On y va !

On édite d’abord la section dependencies du fichier build.gradle :

dependencies {
	...
	compile 'org.slf4j:slf4j-api:1.7.21' // used by logging
	compile 'ch.qos.logback:logback-classic:1.1.7' // used by logging
	compile 'ch.qos.logback:logback-core:1.1.7' // used by logging
	...
}

Pour rappel, on a retrouvé les groupes, les noms, et les versions pour construire la ligne de dépendance via les infos consultables sur le dépôt MavenCentral

Ensuite, dans notre script principal :

  • on définit une classe "bidon"

  • on définit la classe comme "loggueuse" à l’aide de l’annotation

  • l’annotation injectera un logger dans la classe

  • on utilise le logger pour logguer un message d’info

Ça donne ça :

// fichier src/main/groovy/Main.groovy
import groovy.util.logging.Slf4j
@Slf4j
class Toto {
  Toto() {
    log.info "ceci est un message d'info loggué"
  }
}
def t = new Toto()
println "Hello world, ${t}"

Ici, le simple fait d’avoir mis l’annotation, va avoir pour effet :

  • un logger dont le nom est celui de la classe va être créé

  • il sera injecté dans la classe en tant que membre de classe nommé log

  • chaque message qu’on lui envoie sera formaté "proprement" et affiché

Rappel : toujours dans une logique de simplification, le mot clé this semble optionnel en groovy lorsqu’on souhaite accéder aux membres d’une classe depuis celle-ci, quand ça n’est pas ambigü :

Donc d’après ci-dessous, on écrit plutôt le premier que le deuxième

// en groovy
log.info "message"
// en java
this.log.info("message");

Quand on lance l’application avec le logger, ça donne ça :

$ ./gradlew run
:compileJava UP-TO-DATE
:compileGroovy UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
19:29:07.301 [main] INFO Toto - ceci est un message d'info loggué
Hello world, Toto@7276c8cd
BUILD SUCCESSFUL
Total time: 1.169 secs

On voit qu’il y a bien un message de loggué, et que les informations de contexte (timestamp, thread, class, loglevel) ont été ajouté au message en plus du corps de celui-ci.

Utilisation d’IDEA

IDEA est une interface de développement intégrée (comme Eclipse, Visual Studio, etc) qui est directement compatible Groovy et Gradle, et qui est légère et rapide.

On l’installe :

  • télécharger IDEA d’Intellij

  • décompresser dans son $HOME

  • lancer avec bin/idea.sh

IDEA peut interagir avec nos projets Gradle/Groovy de deux manières :

  • en utilisant les fichiers "idea" générés par gradle

  • en important le fichier build.gradle

Dans les faits, on utilisera les deux :

  • on "ouvrira" le projet en utilisant les fichiers idea générés par gradle

  • on "importera" le projet en utilisant le fichier build.gradle

Tout d’abord, on va générer les fichiers de projets pour IDEA. On commence par ajouter le plugin 'idea' au fichier build.gradle :

apply plugin: 'idea'

Ensuite on génèrera les fichiers concernés :

$ ./gradlew idea
:ideaModule
Download https://repo1.maven.org/maven2/org/codehaus/groovy/groovy-all/2.3.1/groovy-all-2.3.1-sources.jar
Download https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.21/slf4j-api-1.7.21-sources.jar
Download https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7-sources.jar
Download https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.1.7/logback-core-1.1.7-sources.jar
:ideaProject
:ideaWorkspace
:idea
BUILD SUCCESSFUL
Total time: 3.313 secs

Il télécharge le nécessaire et créé trois fichiers.

Remarque : on voit qu’il a téléchargé la variante '-all' de groovy, telle qu’on la définie dans les dépendances. Il existe deux variantes '-bin' et '-all', et la variante '-all' est plus utile dans le cas où on utilise un IDE, car elle permet d’avoir l’autocomplétion complète.

Il utilise le nom du projet (cf settings.gradle) pour nommer les fichiers :

./article.iml
./article.ipr
./article.iws

On ajoutera ces fichiers au fichier .gitignore du dépôt de code.

Pourquoi ? Pour éviter qu’ils ne soient transmis à d’autres utilisateurs : ces fichiers peuvent contenir des données et paramères locaux à chaque personne. Et dans tous les cas, les autres utilisateurs peuvent les générer par gradle.

Ensuite on passe dans IDEA.

Dans la fenêtre de bienvenue, on clique sur le bouton "ouvrir" (j’ai bien dit ouvrir) puis on va chercher le répertoire où se trouve les trois fichiers idea (iml, ipr et iws).

On laisse idea charger notre projet, analyser tout ce qu’il faut.

On constate une première fenêtre pop-up, qui nous dit un truc du genre :

Unlinked gradle Project ?
Import Gradle project, this will also enable Gradle Tool Window...

On s’empressera de cliquer le lien ! On choisira :

  • Use auto-import

  • Create directories for empty content roots automatically

  • Create separate module per source set

  • Use default gradle wrapper

  • on confirmera par OK

Si vous avez loupé le lien pour importer le projet gradle et que vous ne le retrouvez plus, allez dans File / New / Files from existing sources / Import project from external model / Gradle puis on fait la liste ci-dessus.

L’import du projet build.gradle déclenche l’activation de la fenêtre accessible via le menu View / Tool windows / Gradle.

On y retrouve :

  • toutes les tâches du projet

  • toutes les propriétés du projet

  • les sourceSets et les dépendances

  • la possibilité de lancer une tâche à la main

La documentation est disponible sur le site de l’éditeur.

Fichier ignore pour GIT/SVN/etc

Ces fichiers n’ont pas vocation à être intégrés au gestionnaire de code :

.gradle
.idea
*.iml
*.ipr
*.iws
build