Script de construcción Gradle en Java
Anatomía de un script de construcción Gradle para Java: plugins, dependencias, tareas y conceptos básicos del DSL.
Un script de construcción Gradle describe cómo compilar, probar y empaquetar un proyecto — no como un documento XML rígido, sino como código. Gradle lee un archivo build.gradle (DSL de Groovy) o build.gradle.kts (DSL de Kotlin), convierte las declaraciones en un grafo de tareas y ejecuta solo las tareas que tu comando necesita, en el orden correcto. Donde Maven te ofrece un ciclo de vida fijo, Gradle te ofrece uno programable: aplicar un plugin, declarar una dependencia y definir una tarea son sentencias ordinarias en un lenguaje real.
Esta página asume que ya sabes qué es Gradle a un nivel general — si no es así, comienza con la introducción a Gradle. Aquí desmontamos un build.gradle real bloque a bloque: plugins, repositorios, dependencias y tareas.
La anatomía de build.gradle
Un script de construcción Java mínimo tiene cuatro bloques: qué plugins aplicar, de dónde obtener las dependencias, cuáles son esas dependencias y algo de configuración. Aquí hay uno completo e idiomático en DSL de 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()
}El plugin java solo ya te da compileJava, test, jar y una docena de tareas más de forma gratuita. El plugin application añade run e installDist. La línea tasks.test { useJUnitPlatform() } conecta la tarea test para ejecutar pruebas de JUnit 5 — sin ella, Gradle usa de forma predeterminada el motor legado de JUnit 4 y no ejecuta nada en silencio. Todo lo demás es configuración de lo que hacen esas tareas.
Plugins, repositorios y el ciclo de vida de construcción
Casi nada en Gradle está integrado — las capacidades llegan como plugins. El plugin java es la base para el trabajo en JVM; otros se añaden encima:
| Plugin | Lo que añade |
|---|---|
java | compileJava, test, jar, conjuntos de fuentes, las configuraciones de dependencies |
application | run y una distribución empaquetada con scripts de inicio |
java-library | La distinción api vs implementation para bibliotecas |
org.springframework.boot | bootJar, bootRun para aplicaciones Spring Boot |
jacoco | Informes de cobertura de código conectados a test |
repositories { } le dice a Gradle de dónde descargar las dependencias — mavenCentral() es la elección habitual. Sin un repositorio, ninguna dependencia externa puede resolverse.
Declarar dependencias y sus alcances
Las dependencias se declaran con una configuración que controla dónde aparecen en el classpath. Elegir la correcta mantiene tu classpath de compilación limpio y tus construcciones rápidas:
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")
}El formato de coordenadas es group:name:version — las mismas coordenadas que Maven usa en su pom.xml. Cuando dos dependencias traen el mismo módulo en diferentes versiones, la estrategia predeterminada de Gradle coloca una sola versión, la más alta, en el classpath — el ejemplo ejecutable a continuación modela exactamente esto.
Tareas: la unidad de trabajo
Cada acción que realiza Gradle es una tarea, y las tareas declaran dependencias de otras tareas. Ejecutar gradle build no hace una sola cosa; recorre un grafo y ejecuta cada requisito previo una vez. También puedes definir las tuyas propias:
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")
}Dos hechos más hacen a Gradle rápido. Primero, es incremental: una tarea cuyos entradas y salidas no han cambiado se informa como UP-TO-DATE y se omite. Segundo, el Gradle Wrapper (./gradlew, respaldado por gradle/wrapper/gradle-wrapper.properties) fija una versión de Gradle por proyecto, de modo que cada desarrollador y máquina de CI construye con la misma cadena de herramientas — nunca instalas Gradle globalmente.
Un ejemplo práctico: una construcción, modelada en Java puro
Gradle en sí no está disponible en este ejecutor, así que el programa a continuación modela las tres ideas que hacen funcionar un script de construcción — el grafo de tareas y su orden de ejecución, el omitido incremental de tareas actualizadas, y la resolución de conflictos de versión de dependencias — usando únicamente el JDK. Es el modelo mental que gradle build ejecuta en realidad.
Lo que se debe extraer de la ejecución:
- La lista de tareas para
gradle buildse calcula mediante una ordenación topológica, no se escribe a mano.compileJavayprocessResourcesvienen antes queclasses, que viene antes quejarytest, que vienen antes quebuild— exactamente el orden que Gradle imprime con cada prefijo:taskName, porque una tarea solo puede ejecutarse después de que todo aquello de lo quedependsOnhaya terminado. - Un diamante en el grafo ejecuta un requisito previo compartido una vez, no dos. Tanto
jarcomotestdependen declasses, y sin embargoclassesaparece una sola vez en el orden — el conjuntodonees lo que impide a Gradle recompilar el mismo código para cada tarea descendente. - La segunda ejecución muestra el comportamiento incremental de Gradle:
compileJava,processResourcesyclassesestánUP-TO-DATEy se omiten, por lo que solo3tareas se ejecutan realmente. Por eso un proyecto sin cambios se reconstruye en milisegundos — Gradle compara las entradas y salidas de las tareas y no hace ningún trabajo que pueda evitar. - La resolución de dependencias colapsa un conflicto de versión a un único ganador:
slf4j-apise solicita tanto en2.0.9(directa) como en1.7.36(transitiva a través de guava), pero el classpath resuelto lo lista una vez a2.0.9. La estrategia predeterminada de Gradle es la versión más alta gana, por lo que un único jar consistente llega al classpath en lugar de dos copias en conflicto. - La línea final nombra la versión de Gradle
8.7como si se leyera degradle-wrapper.properties. En un proyecto real, el wrapper almacena esa versión en el control de versiones, de modo que./gradlew buildusa el mismo Gradle para todos — la construcción es reproducible independientemente de lo que esté (o no) instalado en la máquina.