Setiap pemanggilan ./gradlew di proyek Kotlin melewati tiga fase berurutan: initialization, configuration, dan execution. Pemahaman batas antar fase ini menentukan apakah kita mampu menulis build script yang benar (mis. tidak menjalankan kode mahal saat configuration) dan membaca log build dengan tepat. Pada chapter ini kita akan menelusuri ketiga fase, perbedaan doFirst dan doLast, serta cara kerja up-to-date checking yang membuat build inkremental.

Fase Initialization #

Fase pertama dimulai sebelum task apapun dijalankan. Gradle membaca settings.gradle.kts di direktori tempat gradlew dipanggil. File ini menentukan:

  • Nama root project (rootProject.name)
  • Daftar subproyek (include(":app", ":core"))
  • Repositori plugin (pluginManagement)

Setelah fase ini selesai, Gradle memiliki object model Project lengkap untuk setiap modul yang akan dibangun.

Fase Configuration #

Fase kedua mengevaluasi build.gradle.kts setiap proyek secara berurutan dari root ke subproyek. Setiap blok dalam build script dievaluasi:

  • plugins { ... } menerapkan plugin
  • repositories { ... } menambah repositori
  • dependencies { ... } menambah dependensi
  • tasks.register(...) menambah task ke task graph
  • tasks.named("...") { ... } mengonfigurasi task yang sudah ada

Fase Execution #

Fase ketiga menjalankan task yang diminta beserta dependensinya. Gradle memilih task yang perlu dijalankan dari DAG yang dibangun di fase sebelumnya, lalu menjalankan task satu per satu sesuai urutan topologis.

kotlin Demonstrasi eksekusi
tasks.register("langkah1") {
    doLast { println("Langkah 1 selesai") }
}

tasks.register("langkah2") {
    dependsOn("langkah1")
    doLast { println("Langkah 2 selesai (setelah langkah 1)") }
}

tasks.register("langkah3") {
    dependsOn("langkah2")
    doLast { println("Langkah 3 selesai") }
}

Saat menjalankan ./gradlew langkah3, urutan eksekusi adalah langkah1 → langkah2 → langkah3. Gradle menyelesaikan dependensi terlebih dahulu, lalu task utama.

Demonstrasi Lifecycle #

Kode berikut mencantumkan ketiga fase beserta urutan eksekusi task saat ./gradlew build dijalankan.

kotlin
fun main() {
    println("=== Lifecycle Build Gradle ===")
    println()
    println("Tahap 1: Initialization")
    println("  - settings.gradle.kts dievaluasi")
    println("  - Project objects dibuat (root + subproyek dari include())")
    println()
    println("Tahap 2: Configuration")
    println("  - build.gradle.kts tiap proyek dievaluasi dari atas ke bawah")
    println("  - Plugin diterapkan, task diregistrasi")
    println("  - Dependency graph antar task dibangun (DAG)")
    println("  - Tidak ada aksi task yang berjalan, hanya konfigurasi")
    println()
    println("Tahap 3: Execution")
    println("  - Hanya task yang diminta + dependensinya dijalankan")
    println("  - Urutan eksekusi sesuai DAG (topological sort)")
    println("  - Up-to-date checking: task dengan input/output tidak berubah dilewati")
    println()
    println("Contoh urutan eksekusi ./gradlew build:")
    println("  :compileKotlin -> :compileJava -> :processResources -> :classes")
    println("  -> :jar -> :assemble -> :compileTestKotlin -> :test -> :check -> :build")
}

Output yang Dihasilkan:

text
=== Lifecycle Build Gradle ===

Tahap 1: Initialization
  - settings.gradle.kts dievaluasi
  - Project objects dibuat (root + subproyek dari include())

Tahap 2: Configuration
  - build.gradle.kts tiap proyek dievaluasi dari atas ke bawah
  - Plugin diterapkan, task diregistrasi
  - Dependency graph antar task dibangun (DAG)
  - Tidak ada aksi task yang berjalan, hanya konfigurasi

Tahap 3: Execution
  - Hanya task yang diminta + dependensinya dijalankan
  - Urutan eksekusi sesuai DAG (topological sort)
  - Up-to-date checking: task dengan input/output tidak berubah dilewati

Contoh urutan eksekusi ./gradlew build:
  :compileKotlin -> :compileJava -> :processResources -> :classes
  -> :jar -> :assemble -> :compileTestKotlin -> :test -> :check -> :build

Mari kita telaah kode di atas:

  1. Initialization: Gradle selalu membaca settings.gradle.kts terlebih dahulu. Tanpa file ini, Gradle memperlakukan direktori sebagai proyek tunggal tanpa nama.
  2. Configuration: setiap build.gradle.kts dijalankan sebagai kode Kotlin. Lambda yang diberikan ke tasks.register dijalankan di sini untuk membuat objek task. Lambda doLast hanya disimpan sebagai referensi untuk nanti.
  3. Execution: task yang diminta (build) beserta semua dependensi transitifnya dijalankan. Urutan eksekusi mengikuti topological sort DAG — task tanpa dependensi yang belum dijalankan akan diproses lebih dulu.

Up-to-Date Checking #

Untuk task yang memiliki deklarasi inputs dan outputs, Gradle mencatat hash dari input dan output saat task berjalan. Saat dijalankan ulang, bila hash input dan output tidak berubah, task dilewati dengan label UP-TO-DATE:

kotlin Task dengan up-to-date checking
val generateFile by tasks.registering {
    val output = layout.buildDirectory.file("generated/info.txt")
    outputs.file(output)   // deklarasi output untuk cache
    inputs.property("version", project.version)  // deklarasi input

    doLast {
        output.get().asFile.writeText("Build ${project.version}")
        println("File generated")
    }
}

Eksekusi pertama mencetak File generated. Eksekusi kedua (tanpa perubahan input/output) mencetak Task :generateFile UP-TO-DATE.

Configuration Cache #

Sejak Gradle 7.4, configuration cache dapat menyimpan hasil configuration phase ke file. Build berikutnya melewati fase ini sepenuhnya bila build.gradle.kts tidak berubah:

bash
# Mengaktifkan configuration cache eksperimen
./gradlew build --configuration-cache

# Memeriksa compatibility (laporan di build/reports/configuration-cache)
./gradlew build --configuration-cache

doFirst vs doLast — Lebih Detail #

Sebuah task memiliki tiga phase internal:

  1. Pre-actions (ditambahkan via doFirst)
  2. Task action default (mis. copy() pada Copy task)
  3. Post-actions (ditambahkan via doLast)

doFirst cocok untuk setup yang harus berjalan sebelum aksi utama (mis. membersihkan direktori output lama). doLast cocok untuk teardown atau tindakan pasca-eksekusi (mis. mencetak ringkasan, meng-upload artefak).

Referensi Lanjutan #

  • Gradle — panduan resmi Kotlin + Gradle sebagai konteks lifecycle build Kotlin
  • Gradle User Guide — buku panduan Gradle yang menjelaskan init, configuration, dan execution phase

Penutup #

Pemahaman lifecycle Gradle memisahkan dua jebakan umum: logika yang berjalan terlalu awal (saat configuration) dan task yang seharusnya up-to-date tetapi selalu dijalankan ulang. Pada chapter berikutnya kita akan membahas version catalog, fitur Gradle modern yang memusatkan deklarasi versi dependensi.