Premiers pas avec Gradle, Groovy et IDEA
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 standardSystem.out
-
pour les commandes de "premier niveau", les parenthèses sont facultatives, ie on aurait pu écrire
println("toto")
mais on peut écrireprintln "toto"
. De la même manière, on écriraitf a, b
au lieu d’écriref(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 queArrayList<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 aurab==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 unMakefile
manuel ou généré par./configure
-
grunt
etgulp
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 :
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ètrerepositories
) -
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
etclean
, etjavadoc
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 documentationgroovydoc
-
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 javaMain.class
-
un fichier
.jar
utilisant le nom du projet (cfsettings.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
etdistTar
) 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