Ketika kita menekan Enter pada perintah gradle build, Gradle tidak langsung mengompilasi apa pun. Sebelum task pertama berjalan, Gradle menempuh dua tahap persiapan: mengenali peserta build, lalu mengevaluasi seluruh build script menjadi DAG task. Keseluruhan perjalanan terbagi dalam tiga tahap resmi — initialization, configuration, dan execution — dan memahami batas antar tahap adalah kunci menulis build script yang cepat serta bekerja sesuai dugaan. Pada chapter ini kita akan menelusuri ketiga tahap tersebut, membahas konfigurasi lazy yang menjaga tahap konfigurasi tetap ringan, dan mengenal build cache yang memperpendek tahap eksekusi.

Tiga Tahap Build #

Tahap initialization berjalan paling awal: Gradle membaca settings.gradle.kts, menentukan proyek mana saja yang ikut build, dan membuat satu objek Project untuk masing-masing. Tahap configuration mengevaluasi build.gradle.kts setiap proyek — kode Kotlin di dalamnya benar-benar dieksekusi untuk mendaftarkan task, plugin, dan dependency, lalu Gradle menyusun DAG untuk task yang kita minta. Tahap execution barulah menjalankan aksi task dalam urutan graf: mengompilasi, menguji, mengemas.

Program berikut memetakan alur ketiga tahap untuk perintah gradle :app:build pada build multi-proyek:

java TahapBuild.java
public class TahapBuild {

    // Peserta build hasil membaca settings.gradle.kts.
    private static final String[] PROYEK = {"aplikasi-gudang (root)", "app", "lib-util"};

    // Task yang terdaftar di subproyek 'app' (meniru plugin java).
    private static final String[] TASK_APP = {
        "compileJava", "processResources", "classes", "jar", "assemble",
        "compileTestJava", "test", "check", "build"
    };

    public static void main(String[] args) {
        System.out.println("$ gradle :app:build");
        System.out.println();
        System.out.println("[initialization]");
        System.out.println("  membaca settings.gradle.kts");
        System.out.println("  peserta build: " + String.join(", ", PROYEK));
        System.out.println();
        System.out.println("[configuration]");
        System.out.println("  mengevaluasi build.gradle.kts setiap proyek");
        System.out.println("  task terdaftar di 'app': " + TASK_APP.length + " task");
        System.out.println("  DAG untuk ':app:build' tersusun");
        System.out.println();
        System.out.println("[execution]");
        for (int i = 0; i < TASK_APP.length; i++) {
            System.out.printf("  %d. :app:%s%n", i + 1, TASK_APP[i]);
        }
    }
}

Output yang Dihasilkan:

text
$ gradle :app:build

[initialization]
  membaca settings.gradle.kts
  peserta build: aplikasi-gudang (root), app, lib-util

[configuration]
  mengevaluasi build.gradle.kts setiap proyek
  task terdaftar di 'app': 9 task
  DAG untuk ':app:build' tersusun

[execution]
  1. :app:compileJava
  2. :app:processResources
  3. :app:classes
  4. :app:jar
  5. :app:assemble
  6. :app:compileTestJava
  7. :app:test
  8. :app:check
  9. :app:build

Tiga pengamatan dari simulasi di atas:

  1. Blok [initialization]: daftar peserta build lahir dari settings.gradle.kts — hanya berkas inilah yang dibaca pada tahap pertama; build script belum tersentuh.
  2. Blok [configuration]: seluruh build.gradle.kts dievaluasi meski kita hanya meminta task milik app — konfigurasi lib-util tetap dibaca karena task app dapat bergantung padanya.
  3. Blok [execution]: task berjalan dalam urutan prasyarat — compileJava sebelum classes, classes sebelum jar. Di antara cabang yang tidak saling bergantung (cabang assemble dan cabang check), urutan interleaving tidak dijamin tetap; yang pasti hanya relasi prasyarat.

Configuration Phase dan Lazy Configuration #

Tahap konfigurasi berjalan pada setiap pemanggilan Gradle — termasuk gradle tasks, gradle clean, bahkan build yang akhirnya tidak melakukan apa-apa. Konsekuensinya: kode berat di dalam blok konfigurasi membuat setiap perintah terasa lambat, seberat apa pun task yang diminta.

kotlin Konfigurasi eager versus lazy
// EAGER: blok langsung dievaluasi saat configuration phase
tasks.create("cetakEager") {
    println("dievaluasi setiap build, apa pun task yang diminta")
}

// LAZY: dievaluasi hanya jika task benar-benar dijalankan
tasks.register("cetakLazy") {
    doLast {
        println("hanya berjalan saat task ini dieksekusi")
    }
}

Perbedaannya letak letaknya println: pada varian eager, kode di tingkat task berjalan saat konfigurasi; pada varian lazy, kode di dalam doLast baru berjalan saat eksekusi. API modern Gradle membangun budaya lazy ini lebih jauh lewat tipe Provider dan Property — nilai seperti versi Java atau nama berkas output dibungkus sehingga penyelesaiannya tertunda sampai benar-benar dibutuhkan, memungkinkan Gradle melewatkan pekerjaan yang hasilnya tidak dipakai.

Build Cache dan Eksekusi Ulang #

Tahap eksekusi punya dua lapis penghematan. Lapis pertama adalah incremental build dari chapter sebelumnya: task dengan input dan output yang tak berubah ditandai UP-TO-DATE. Lapis kedua adalah build cache: hasil eksekusi task diarsipkan berdasarkan hash input-nya, sehingga output dapat diambil kembali bahkan setelah direktori build dihapus bersih atau dari mesin lain lewat remote cache.

bash
gradle clean build --build-cache

Dengan flag --build-cache, urutan clean lalu build tidak mengompilasi ulang semuanya — task-task yang cache-nya cocok cukup menyalin kembali output dari arsip cache. Tim besar mengkonfigurasi remote cache bersama di CI sehingga developer lokal menerima hasil kompilasi yang sama tanpa menunggu mesin masing-masing menyelesaikannya.

Tiga tahap lifecycle ini menjelaskan bagaimana Gradle menyiapkan dan menjalankan pekerjaan. Pertanyaan berikutnya menyangkut apa yang masuk classpath pada tahap eksekusi: bagaimana dependency dideklarasikan lewat konfigurasi seperti implementation dan api, dan bagaimana version catalog merapikan seluruhnya. Chapter berikutnya membahasnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

TahapBuild.java

Unduh berkas
java
public class TahapBuild {

    // Peserta build hasil membaca settings.gradle.kts.
    private static final String[] PROYEK = {"aplikasi-gudang (root)", "app", "lib-util"};

    // Task yang terdaftar di subproyek 'app' (meniru plugin java).
    private static final String[] TASK_APP = {
        "compileJava", "processResources", "classes", "jar", "assemble",
        "compileTestJava", "test", "check", "build"
    };

    public static void main(String[] args) {
        System.out.println("$ gradle :app:build");
        System.out.println();
        System.out.println("[initialization]");
        System.out.println("  membaca settings.gradle.kts");
        System.out.println("  peserta build: " + String.join(", ", PROYEK));
        System.out.println();
        System.out.println("[configuration]");
        System.out.println("  mengevaluasi build.gradle.kts setiap proyek");
        System.out.println("  task terdaftar di 'app': " + TASK_APP.length + " task");
        System.out.println("  DAG untuk ':app:build' tersusun");
        System.out.println();
        System.out.println("[execution]");
        for (int i = 0; i < TASK_APP.length; i++) {
            System.out.printf("  %d. :app:%s%n", i + 1, TASK_APP[i]);
        }
    }
}