Tareas de Gradle: integradas, ./gradlew y tareas personalizadas
Cada build de Gradle es un grafo de tareas: aprende a ejecutarlas y a escribir las tuyas.
En Gradle, una *tarea* (task) es la unidad atómica de trabajo: compilar fuentes, copiar archivos, ejecutar pruebas o empaquetar un artefacto. Un build nunca es un único paso monolÃtico; es un conjunto de tareas que Gradle conecta y ejecuta en el orden correcto. Al aplicar un plugin heredas sus tareas gratis. Los plugins de Kotlin JVM y `application`, por ejemplo, aportan las tareas de ciclo de vida que usarás a diario: `build` (ensambla y verifica todo), `test` (compila y ejecuta las pruebas), `run` (lanza tu función `main`) y `clean` (borra el directorio `build/` para empezar de cero).
Las tareas se invocan mediante el *Gradle Wrapper*, un pequeño script versionado en cada proyecto: `./gradlew` en macOS y Linux, `gradlew.bat` en Windows. El wrapper fija una versión exacta de Gradle (declarada en `gradle/wrapper/gradle-wrapper.properties`) y la descarga la primera vez, de modo que cada compañero y cada máquina de CI compilen con el mismo toolchain; nunca uses un `gradle` instalado globalmente para el trabajo del proyecto. Los comandos tÃpicos son `./gradlew build`, `./gradlew test`, `./gradlew run` y `./gradlew clean`. Puedes encadenarlos (`./gradlew clean build`) y pasar flags como `--info` para más detalle o `-x test` para *excluir* una tarea.
Por dentro, Gradle construye un *grafo de tareas*: un grafo dirigido acÃclico (DAG) donde las aristas son dependencias entre tareas. Para ello trabaja en dos fases: una fase de configuración, en la que evalúa tu script y determina qué tareas existen y cómo se relacionan, y una fase de ejecución, en la que realmente corre las tareas seleccionadas en orden de dependencia. Como el grafo es un DAG, Gradle puede saltarse trabajo ya actualizado (builds incrementales) y ejecutar tareas independientes en paralelo. Ejecutar `build` no hace todo por sà mismo: *depende de* `assemble` y `check`, que a su vez dependen de `compileKotlin`, `test` y más; Gradle resuelve toda esa cadena automáticamente.
Para añadir tu propio trabajo, registra una tarea con la API perezosa `tasks.register(...)` en `build.gradle.kts`. Prefiere `register` sobre el antiguo `tasks.create` (que es eager), porque las tareas registradas solo se configuran si realmente forman parte del build solicitado, lo que mantiene rápida la configuración. Dentro del bloque de configuración describes la tarea; el trabajo real va en una acción `doLast { }`, que se ejecuta en la fase de ejecución. Un ejemplo mÃnimo imprime un mensaje: `tasks.register("hello") { doLast { println("Hello from Gradle!") } }`. Ejecútala con `./gradlew hello`.
El orden se expresa con `dependsOn`. Declarar `tasks.named("build") { dependsOn("hello") }` (o `finalizedBy` para pasos de limpieza) inserta tu tarea en el grafo para que se ejecute cuando lo haga la tarea dependiente. Mantén dependencias con sentido: apúntalas a la tarea que produce la entrada que tu tarea necesita y deja que Gradle calcule el orden en vez de codificar secuencias a mano. Un patrón común es agrupar y documentar tus tareas con `group = "build"` y `description = "..."` para que aparezcan bien en `./gradlew tasks`.
Dos hábitos modernos completan el tema. Primero, centraliza las versiones de dependencias y plugins en `gradle/libs.versions.toml` — el *catálogo de versiones* — y referéncialas con accesos tipados como `libs.kotlinx.coroutines.core`; este es el enfoque idiomático de Gradle 8 para proyectos Kotlin 2.x. Segundo, recuerda la diferencia entre una tarea de *ciclo de vida* (un agregador vacÃo como `build` que solo existe para depender de otras) y una tarea de *acción* (con trabajo real en `doLast`/`doFirst`). Dominar las tareas, el wrapper, el DAG y `dependsOn` te da todo lo necesario para leer, ejecutar y extender cualquier build de Gradle.
// build.gradle.kts — plugins give you build/test/run/clean for freeplugins {kotlin("jvm") version "2.1.0"application // adds the `run` task that launches your main()}application {mainClass.set("com.example.AppKt") // <file>.kt -> <File>Kt}dependencies {implementation(libs.kotlinx.coroutines.core) // from the version catalogtestImplementation(kotlin("test"))}
Applying the kotlin("jvm") and application plugins contributes the lifecycle tasks.
// build.gradle.kts — a custom action task wired into the task graphtasks.register("hello") {group = "build" // shows under "Build tasks" in ./gradlew tasksdescription = "Prints a greeting"doLast { println("Hello from Gradle!") } // runs in the execution phase}// Make `build` depend on it: ./gradlew build now also runs `hello`tasks.named("build") { dependsOn("hello") }
register is lazy; doLast holds the work; dependsOn adds the edge to the DAG.
# gradle/libs.versions.toml — central version catalog (Gradle 8, Kotlin 2.x)[versions]kotlin = "2.1.0"coroutines = "1.9.0"[libraries]kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }[plugins]kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
Type-safe accessors: libs.kotlinx.coroutines.core and libs.plugins.kotlin.jvm.
🧠Comprueba tu comprensión
0/1 · 0/1 answered1. What does declaring tasks.named("build") { dependsOn("hello") } accomplish?