W3docs

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 :

PluginCe qu'il ajoute
javacompileJava, test, jar, les source sets, les configurations de dependencies
applicationrun et une distribution packagée avec des scripts de démarrage
java-libraryLa distinction api vs implementation pour les bibliothèques
org.springframework.bootbootJar, bootRun pour les applications Spring Boot
jacocoRapport 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.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • La liste des tâches pour gradle build est calculée par un tri topologique, et non écrite à la main. compileJava et processResources précèdent classes, qui précède jar et test, qui précèdent build — exactement l'ordre qu'affiche Gradle avec le préfixe :taskName pour chaque tâche, car une tâche ne peut s'exécuter qu'après que tout ce dont elle dependsOn est terminé.
  • Un losange dans le graphe exécute un prérequis partagé une seule fois, pas deux. jar et test dépendent tous deux de classes, pourtant classes n'apparaît qu'une seule fois dans l'ordre — le set done est 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, processResources et classes sont UP-TO-DATE et ignorés, de sorte que seulement 3 tâ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-api est demandé à la fois en 2.0.9 (direct) et en 1.7.36 (transitif via guava), mais le classpath résolu ne le liste qu'une seule fois en 2.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.7 comme si elle était lue depuis gradle-wrapper.properties. Dans un vrai projet, le wrapper stocke cette version dans le contrôle de source, de sorte que ./gradlew build utilise le même Gradle pour tout le monde — le build est reproductible quelle que soit l'installation sur la machine.

Pratique

Pratique
Un projet Java Gradle déclare 'org.slf4j:slf4j-api:2.0.9' directement, tandis qu'une dépendance transitive tire 'org.slf4j:slf4j-api:1.7.36'. Avec la stratégie de résolution par défaut de Gradle, qu'est-ce qui se retrouve sur le classpath ?
Un projet Java Gradle déclare 'org.slf4j:slf4j-api:2.0.9' directement, tandis qu'une dépendance transitive tire 'org.slf4j:slf4j-api:1.7.36'. Avec la stratégie de résolution par défaut de Gradle, qu'est-ce qui se retrouve sur le classpath ?
Was this page helpful?