12 juin 2014

Gradle

Gradle permet d'automatiser la construction, le test, la publication, le déploiement d'applications.

Gradle a la gestion des dépendances et les conventions de Maven avec en plus la flexibilité de Ant.

Les fichiers de builds sont écrits via le langage Groovy DSL qui est un Domain Specific Language basé sur Groovy. Le langage Groovy a été choisi car Java ne suffisait pas pour décrire un fichier de build de projet et Groovy est un langage qui est plus proche syntaxiquement de Java que d'autres langages comme Python ou Ruby.

Gradle supporte les types de projets suivants : Java, Groovy, OSGI, Web, Scala et Android.

Il supporte nativement les projets multi-modules.

Fichiers Gradle

Gradle propose deux types de fichiers à créer à la racine du projet :
  • build.gradle : fichier de build du projet
    • contient les tâches à exécuter
  • settings.gradle : fichier de configuration du projet
    • contient la définition de valeurs de propriétés et la dépendance vers d'autres sous-projets

Cycle de vie du build dans Gradle

Voici les phases du cycle de vie dans Gradle :

  • Initialisation : Loading
    • Gradle détermine quels sont les projets en dépendance du projet actuel à traiter durant le build
    • Il exécute les fichiers settings.gradle des projets à construire durant cette phase
  • Configuration : Configuring
    • Gradle exécute les fichiers build.gradle de tous ou d'une partie des projets : 
      • il charge ainsi la configuration et la liste des tâches des projets à construire
  • Exécution : Building
    • En fonction du nom de la tâche fournie dans la commande de lancement, Gradle lance l'exécution d'une partie des tâches qui avaient été chargées lors de la phase de configuration

build.gradle : Tâches de construction

Le fichier build.gradle contient les tâches pour construire, tester et déployer le projet.

Tâche

Une tâche est déclarée de la manière suivante :
task hello << {
    println 'Hello world!'
}
Nous pouvons lancer cette tâche via la commande suivante :
> gradle hello
:hello
Hello world!

BUILD SUCCESSFUL

Total time: 3.927 secs

Dépendance de tâches

Nous pouvons indiquer qu'une tâche s'exécute après une autre tâche de la manière suivante :

  • task1 dépend de la tâche task2

task task1 << {
    println 'Hello '
}
task task2(dependsOn: 'task1') << {
    println 'World !'
}
ce qui donne :
> gradle task2
:task1
Hello
:task2
World !

BUILD SUCCESSFUL

Total time: 2.535 secs

settings.gradle : Configuration

Ce fichier contient la configuration du build.

Il peut y avoir des fichiers settings.gradle spécifiques avec un nom différents pour adapter le build à chaque environnement ou contexte.

Ce fichier contient des lignes de commandes et des définitions de variables :
rootProject.name = 'main'
println 'The project name is : ' + rootProject.name
En créant ce fichier settings.gradle dans le même répertoire que le fichier build.gradle précédent, nous obtenons le résultat suivant :
> gradle task2
The project name is : main
:task1
Hello
:task2
World !

BUILD SUCCESSFUL

Total time: 4.477 secs
La ligne The project name is : main indique que le code du println du fichier settings.gradle a été exécuté.

Numéro de version

La version de l'application est définie dans le fichier build.gradle :
version = '1.0'

Dépendances entre projets

Les dépendances d'un projet avec d'autres projets est décrit dans le fichier settings.gradle du projet.

Les mots-clés include et includeFlat définissent les dépendances du projet actuel avec d'autres projets qui doivent être construits avant lui :
  • Dans le cas où le projet lié est dans un répertoire enfant du projet actuel :
    include <sous-répertoire>
  • Dans le cas où le projet lié est dans un répertoire voisin du projet actuel, c'est à dire situé au même niveau que le répertoire du projet actuel :
    includeFlat <répertoire voisin>
Exemple, pour l'arborescence suivante :
bookstore
 |-- bookstore-front
 |    |-- bookstore-web
 |    |-- bookstore-rest
 |-- bookstore-common
Pour le projet bookstore-front, nous aurons les inclusions suivantes dans son fichier settings.gradle :
includeFlat bookstore-common
include bookstore-web, bookstore-rest
Ainsi, bookstore-web inclut bookstore-common et les deux sous-projets bookstore-web et bookstore-rest.

Redéfinition de la configuration du projet lié

Il est possible depuis le fichier de configuration settings.gradle du projet actuel de modifier des propriétés définies dans le fichier de configuration settings.gradle du projet en dépendance.

Définir un comportement selon le projet inclu

Il est possible de définir dans le fichier build.gradle des tâches pour les projets en dépendance :
  • Définir des tâches pour le projet actuel et les projets en dépendance :
    allprojects { 
        ...tasks... 
    }
  • Définir des tâches pour tous les projets en dépendance :
    subprojects {
        ...tasks...
    }
  • Redéfinir la tâche d'un des projets liés :
    project(':project_name').task << {
        ...code...
    }

Projet Java

Nous allons voir la configuration Gradle pour un projet Java.

Plugin

Nous ajoutons dans le fichier build.gradle le support du type de projet Java à Gradle en utilisant le plugin java :
apply plugin: 'java'

Sources du projet

Nous avons à déclarer les sources du projet qui seront à compiler via les sourceSets.

Le plugin apporte par défaut deux types de sourceSets :
  • main : code de l'application qui sera inclu dans le packaging final : jar, war, etc.
  • test : code pour tester le code de l'application qui sera exécuté par JUnit ou TestNG

Organisation du projet

Le code du projet est organisé par défaut de la manière suivante :
  • src/main/java : code Java de l'application
  • src/main/resources : fichiers de ressources de l'application
  • src/test/java : code Java des tests
  • src/test/resources : fichiers de ressources des tests
  • src/<sourceSet>/java : code Java du sourceSet
  • src/<sourceSet>/resources : fichiers de ressources du sourceSet
Il est possible de modifier cette organisation (voir documentation Gradle "Custom Java source layout").

Tâches

Le plugin Java de Gradle ajoute des tâches pour chaque étape du cycle de build d'un projet.

Voici le graphe de dépendances entre les tâches ajoutées par le plugin Java dans Gradle :
Voici la liste des tâches d'un projet Java :

  • Compilation des sources Java :
    • compileJava : compiler les fichiers Java
    • processResources : copier les fichiers du répertoire des ressources dans le répertoire des classes Java compilées
    • classes : tâche qui lance les deux tâches compileJava et processResources
  • Compilation des sources Java de test :
    • compileTestJava : compiler les fichiers Java de test
    • processTestResources : copier les fichiers du répertoire des ressources de test dans le répertoire des classes Java compilées de test
    • testClasses : tâche qui lance les deux tâches compileTestJava et processTestResources
  • Exécution des tests :
    • test : exécuter les tests
  • Construction des archives :
    • jar : construit le fichier JAR à partir du répertoire des classes Java compilées
    • javadoc : construit le fichier JAR contenant la Javadoc des classes Java du projet
    • uploadArchives : déploie le fichier JAR du projet et ceux de ses dépendances dans le référentiel
    • assemble : assemble toutes les archives du projet
    • check : tâches de vérification de la construction du projet
  • Tâche principale de construction du projet :
    • build : tâche de construction principale du projet qui lance tout le cycle de construction du projet, y compris les tests
  • Nettoyage du projet :
    • clean : nettoie le projet en supprimant le répertoire de construction du projet

Ainsi la commande suivante lance tout le cycle de build d'un projet :
gradle build

Dépendances

Gradle est compatible avec les repositories Maven en déclarant dans le fichier build.gradle :
repositories {
    mavenCentral()
};
Nous pouvons récupérer une dépendance depuis ce repository :
dependencies {
    testCompile 'junit:junit:4.11'
}
Ceci indique que la dépendance junit en version 4.11 est récupérée pour la phase testCompile de compilation des tests.

dependencies permet d'indiquer les dépendances du projet et a la syntaxe suivante :
dependencies {
    <task> '<groupId>:<artifactId>:<version>'
    <task> groupId: '<groupId>', name: '<artifactId>', version: '<version>'
    <task>(
        ['groupId>:<artifactId>:<version>'],
        [groupId: '<groupId>', name: '<artifactId>', version: '<version>']
    )
}
, avec :
  • <task> : une des tâches Gradle du plugin java

Dépendances vers un projet lié ou un module

La dépendance vers un projet lié ou un sous-module est défini dans le fichier settings.gradle via les includeFlat et include.

Au niveau Java, pour inclure le Jar du projet lié ou celui d'un sous-module, il faut ajouter celui-ci via la balise dependencies.
dependencies {
    <task> project(':<nom du projet>')
}
, avec :
  • <nom du projet> : le nom du projet ou du sous-module défini dans le fichier settings.gradle
Contrairement à Maven, nous spécifions uniquement le nom du projet sans avoir besoin d'indiquer le groupId, le artifactId et le numéro de version. Gradle le détermine automatiquement ce qui rend plus simple la configuration des dépendances et des versions entre les projets liés.

Gradle va alors lancer le cycle de build de ce projet et récupérer le JAR de ce projet pour effectuer la compilation du projet courant.

Syntaxe des chemins vers les projets liés

Dans Gradle, le séparateur utilisé pour définir les chemins vers les projets liés est ":" à la place de "/".
# projet racine
:

# projet1 situé dans le répertoire projet1 du projet racine
:projet1    

# projet12 situé dans le répertoire projet2 du répertoire projet1 du projet racine
:projet1:projet12

Projet War

Le plugin war de Gradle permet de générer le fichier war.

Ce plugin est à déclarer dans le fichier build.gradle :
apply plugin: 'war'
La commande gradle build produit maintenant un fichier war dans le répertoire build/libs et dont le nom contient le numéro de version indiqué dans le fichier build.gradle

Exemple : projet multi-module

Nous avons le projet bookstore qui contient deux modules common et front :
bookstore
|-- settings.gradle
|-- build.gradle
|-- common
|   |-- settings.gradle
|   |-- build.gradle
|   |-- src/main/java
|   |-- src/main/resources
|   |-- src/test/java
|   |-- src/test/resources
|-- web
|   |-- settings.gradle
|   |-- build.gradle
|   |-- src/main/java
|   |-- src/main/resources
|   |-- src/test/java
|   |-- src/test/resources
, avec les contenus suivants :
# bookstore/settings.gradle
include "common", "web"

# bookstore/build.gradle
<vide>
# bookstore/common/settings.gradle
<vide>

# bookstore/common/build.gradle
apply plugin: 'java'

version = "1.0"

repositories {
    mavenCentral()
};
dependencies {
    testCompile 'junit:junit:4.11'
}
# bookstore/web/settings.gradle
includeFlat 'common'

# bookstore/web/build.gradle
apply plugin: 'java'
apply plugin: 'war'

version = "2.0"

allprojects {
    apply plugin: 'java'

    repositories {
        mavenCentral()
    };
    dependencies {
        testCompile 'junit:junit:4.11'
    }
}

dependencies {
    compile project(':common')
}
Avec cette configuration, nous pouvons lancer la commande gradle build sur les différents projets : projet common, web et racine.