Script de build Gradle en Java
Anatomie d'un script de build Gradle pour Java — plugins, dépendances, tâches et bases du DSL.
Un script de build Gradle décrit comment compiler, tester et packager un projet — non pas sous la forme d'un document XML rigide, mais sous forme de code. Gradle lit un fichier build.gradle (DSL Groovy) ou build.gradle.kts (DSL Kotlin), transforme les déclarations qu'il contient en un graphe de tâches, et n'exécute que les tâches dont votre commande a besoin, dans le bon ordre. Là où Maven vous offre un cycle de vie fixe, Gradle vous en offre un programmable : appliquer un plugin, déclarer une dépendance et définir une tâche sont des instructions ordinaires dans un vrai langage.
Cette page suppose que vous savez déjà ce qu'est Gradle à un niveau général — sinon, commencez par l'introduction à Gradle. Ici, nous décomposons un vrai build.gradle bloc par bloc : plugins, dépôts, dépendances et tâches.
L'anatomie de build.gradle
Un script de build Java minimal comporte quatre blocs : quels plugins appliquer, où télécharger les dépendances, quelles sont ces dépendances, et un peu de configuration. Voici un exemple complet et idiomatique en DSL Kotlin :
plugins {
java
application
}
group = "com.example"
version = "1.0.0"
repositories {
mavenCentral()
}
dependencies {
implementation("com.google.guava:guava:32.1.3-jre")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
application {
mainClass = "com.example.App"
}
tasks.test {
useJUnitPlatform()
}Le plugin java à lui seul vous offre gratuitement compileJava, test, jar et une dizaine d'autres tâches. Le plugin application ajoute run et installDist. La ligne tasks.test { useJUnitPlatform() } relie la tâche test à l'exécution des tests JUnit 5 — sans elle, Gradle utilise par défaut l'ancien moteur JUnit 4 et n'exécute rien silencieusement. Tout le reste est la configuration de ce que font ces tâches.
Plugins, dépôts et cycle de vie du build
Presque rien n'est intégré dans Gradle — les fonctionnalités arrivent sous forme de plugins. Le plugin java est le fondement du travail sur la JVM ; les autres s'y superposent :
| Plugin | Ce qu'il ajoute |
|---|---|
java | compileJava, test, jar, les source sets, les configurations de dependencies |
application | run et une distribution packagée avec des scripts de démarrage |
java-library | La distinction api vs implementation pour les bibliothèques |
org.springframework.boot | bootJar, bootRun pour les applications Spring Boot |
jacoco | Rapport de couverture de code intégré à test |
repositories { } indique à Gradle où télécharger les dépendances — mavenCentral() est le choix habituel. Sans dépôt, aucune dépendance externe ne peut être résolue.
Déclarer les dépendances et leurs portées
Les dépendances sont déclarées avec une configuration qui contrôle leur emplacement sur le classpath. Choisir la bonne configuration garde votre classpath de compilation propre et vos builds rapides :
dependencies {
// On the compile and runtime classpath, but NOT exposed to consumers
implementation("org.apache.commons:commons-lang3:3.14.0")
// Part of this library's public API — leaks to consumers (java-library only)
api("com.google.guava:guava:32.1.3-jre")
// Needed to compile, but provided at runtime by the environment
compileOnly("org.projectlombok:lombok:1.18.30")
// Only on the test classpath
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
// Only at runtime (e.g. a JDBC driver loaded by name)
runtimeOnly("org.postgresql:postgresql:42.7.1")
}Le format des coordonnées est group:name:version — les mêmes coordonnées que Maven utilise dans son pom.xml. Lorsque deux dépendances font référence au même module à des versions différentes, la stratégie par défaut de Gradle place la version la plus haute, et une seule, sur le classpath — l'exemple exécutable ci-dessous modélise exactement ce comportement.
Les tâches : l'unité de travail
Chaque action effectuée par Gradle est une tâche, et les tâches déclarent des dépendances envers d'autres tâches. Exécuter gradle build ne fait pas qu'une seule chose ; cela parcourt un graphe et exécute chaque prérequis une seule fois. Vous pouvez également définir les vôtres :
tasks.register("printVersion") {
group = "help"
description = "Prints the project version."
doLast {
println("Project version is $version")
}
}
// Make the jar task wait for our custom task
tasks.named("jar") {
dependsOn("printVersion")
}Deux autres caractéristiques rendent Gradle rapide. Premièrement, il est incrémental : une tâche dont les entrées et sorties sont inchangées est signalée UP-TO-DATE et ignorée. Deuxièmement, le Gradle Wrapper (./gradlew, appuyé par gradle/wrapper/gradle-wrapper.properties) fixe une version de Gradle par projet, de sorte que chaque développeur et machine CI build avec la même chaîne d'outils — vous n'avez jamais à installer Gradle globalement.
Un exemple concret : un build, modélisé en Java pur
Gradle lui-même n'est pas disponible sur ce runner, donc le programme ci-dessous modélise les trois idées qui font fonctionner un script de build — le graphe de tâches et son ordre d'exécution, le saut incrémental des tâches à jour, et la résolution des conflits de version de dépendances — en n'utilisant rien d'autre que le JDK. C'est le modèle mental que gradle build exécute réellement.
Ce qu'il faut retenir de l'exécution :
- La liste des tâches pour
gradle buildest calculée par un tri topologique, et non écrite à la main.compileJavaetprocessResourcesprécèdentclasses, qui précèdejarettest, qui précèdentbuild— exactement l'ordre qu'affiche Gradle avec le préfixe:taskNamepour chaque tâche, car une tâche ne peut s'exécuter qu'après que tout ce dont elledependsOnest terminé. - Un losange dans le graphe exécute un prérequis partagé une seule fois, pas deux.
jarettestdépendent tous deux declasses, pourtantclassesn'apparaît qu'une seule fois dans l'ordre — le setdoneest ce qui empêche Gradle de recompiler le même code pour chaque tâche en aval. - Le deuxième run illustre le comportement incrémental de Gradle :
compileJava,processResourcesetclassessontUP-TO-DATEet ignorés, de sorte que seulement3tâches s'exécutent réellement. C'est pourquoi un projet inchangé se rebuild en quelques millisecondes — Gradle compare les entrées et sorties des tâches et n'effectue aucun travail qu'il peut éviter. - La résolution des dépendances réduit un conflit de version à un seul gagnant :
slf4j-apiest demandé à la fois en2.0.9(direct) et en1.7.36(transitif via guava), mais le classpath résolu ne le liste qu'une seule fois en2.0.9. La stratégie par défaut de Gradle est la version la plus haute gagne, de sorte qu'un seul jar cohérent se retrouve sur le classpath au lieu de deux copies en conflit. - La dernière ligne indique la version Gradle
8.7comme si elle était lue depuisgradle-wrapper.properties. Dans un vrai projet, le wrapper stocke cette version dans le contrôle de source, de sorte que./gradlew buildutilise le même Gradle pour tout le monde — le build est reproductible quelle que soit l'installation sur la machine.