Maven mendeskripsikan build dalam XML deklaratif yang seragam; Gradle mengambil jalan lain: build adalah program. Build script Gradle ditulis dalam bahasa Kotlin (atau Groovy) dan alat build menyusunnya menjadi kumpulan task yang dihubungkan relasi ketergantungan membentuk graf. Gradle, yang lahir tahun 2008, kini menjadi build tool bawaan di ekosistem Android dan pilihan populer untuk aplikasi JVM modern — dikenal lewat daemon yang menjaga proses build tetap hangat serta incremental build yang hanya mengulang pekerjaan yang benar-benar berubah. Pada chapter ini kita akan mengenal anatomi build script Kotlin, relasi antar task, dan prinsip DAG yang menjadi inti model eksekusi Gradle.
Task sebagai Unit Kerja #
Kalau Maven mengenal fase dan goal, Gradle hanya mengenal satu konsep: task. Satu task adalah satu unit kerja — compileJava mengompilasi, test menguji, jar mengemas. Tidak ada urutan fase yang tersembunyi; urutan eksekusi murni lahir dari relasi dependsOn antar task yang dinyatakan di build script. Task juga membawa metadata input dan output (berkas sumber, classpath, direktori hasil) yang dipakai Gradle untuk memutuskan apakah sebuah task perlu diulang.
Perintah-perintah berikut adalah keseharian bekerja dengan Gradle:
gradle build
gradle test
gradle tasks
gradle cleanPerintah gradle build memanggil task agregat bernama build yang bergantung pada assemble dan check — padanan kasar dari mvn package yang berjalan sampai pengemasan sekaligus pengujian. gradle tasks menampilkan daftar task yang tersedia beserta kelompoknya.
Directed Acyclic Graph dari Task #
Sebelum satu baris pekerjaan dijalankan, Gradle menyusun Directed Acyclic Graph (DAG): node berupa task, panah berupa relasi “harus selesai lebih dulu”. Dari task yang kita panggil, Gradle menelusuri mundur seluruh prasyarat, memangkas yang duplikat, lalu mengeksekusi dalam urutan topologis. Program berikut meniru penyusunan urutan tersebut untuk task build:
import java.util.ArrayList;
import java.util.List;
public class GrafTugas {
// Task dan daftar task yang menjadi prasyaratnya (meniru plugin java).
private static final String[] TASK = {
"compileJava", "processResources", "classes", "compileTestJava",
"test", "jar", "assemble", "check", "build"
};
private static final String[][] BERGANTUNG_PADA = {
{}, // compileJava
{}, // processResources
{"compileJava", "processResources"}, // classes
{"classes"}, // compileTestJava
{"compileTestJava"}, // test
{"classes"}, // jar
{"jar"}, // assemble
{"test"}, // check
{"assemble", "check"} // build
};
private static final List<String> urutan = new ArrayList<>();
public static void main(String[] args) {
susun("build");
System.out.println("urutan eksekusi task 'build':");
for (int i = 0; i < urutan.size(); i++) {
System.out.printf("%d. %s%n", i + 1, urutan.get(i));
}
System.out.println();
System.out.println("total task terjadwal: " + urutan.size());
}
/** DFS: sebuah task hanya dijadwalkan setelah seluruh prasyaratnya. */
private static void susun(String nama) {
if (urutan.contains(nama)) {
return;
}
for (String prasyarat : BERGANTUNG_PADA[indeks(nama)]) {
susun(prasyarat);
}
urutan.add(nama);
}
private static int indeks(String nama) {
for (int i = 0; i < TASK.length; i++) {
if (TASK[i].equals(nama)) {
return i;
}
}
throw new IllegalArgumentException("task tidak dikenal: " + nama);
}
}Output yang Dihasilkan:
urutan eksekusi task 'build':
1. compileJava
2. processResources
3. classes
4. jar
5. assemble
6. compileTestJava
7. test
8. check
9. build
total task terjadwal: 9Mari kita bedah pemetaan graf ini ke Gradle sungguhan:
- Array
TASKdanBERGANTUNG_PADA: padanan registry task Gradle — setiap task terdaftar dengan nama dan daftardependsOn; susunan data inilah yang pluginjavabangun otomatis saat diterapkan. - Method
susun: DFS pascapesanan (post-order) — task ditambahkan keurutansetelah seluruh prasyaratnya selesai dikunjungi; Gradle melakukan penelusuran serupa saat menyusun DAG. - Baris
if (urutan.contains(nama)): pengaman terhadap kunjungan ganda —classesmenjadi prasyaratjarsekaliguscompileTestJava, namun cukup dijadwalkan sekali; DAG memang memotong duplikasi. - Urutan hasil
1-9:compileJavadanprocessResourcesberjalan paling awal karena tidak berprasyarat;buildselalu paling akhir; memanggilgradle testakan memotong daftar ini hingga nomor 7. - Kata acyclic: relasi yang melingkar (A menunggu B, B menunggu A) membuat penjadwalan mustahil — Gradle menolak build script semacam itu saat konfigurasi.
Build Script Kotlin DSL #
Seluruh definisi di atas ditulis dalam berkas build.gradle.kts. Cuplikan berikut mendaftarkan dua task kustom dengan relasi ketergantungan:
tasks.register("tampilkanProfil") {
group = "dokumentasi"
description = "Menampilkan profil proyek"
doLast {
println("Proyek: aplikasi-gudang, versi 1.0.0")
}
}
tasks.register("sapaPengembang") {
dependsOn("tampilkanProfil")
doLast {
println("Halo, pengembang aplikasi-gudang!")
}
}Bedah singkatnya: tasks.register("nama") mendaftarkan task ke container proyek; blok di dalamnya adalah konfigurasi task (kolom group dan description muncul di daftar gradle tasks); dependsOn("tampilkanProfil") menambahkan panah pada DAG sehingga gradle sapaPengembang menjalankan kedua task berurutan; blok doLast berisi aksi yang baru dieksekusi pada fase eksekusi. Karena ini kode Kotlin sungguhan, kita dapat memakai loop, kondisional, dan function — kebebasan yang tidak dimiliki XML Maven.
settings.gradle.kts dan Struktur Multi-Proyek #
Sebelum membaca build script, Gradle membaca settings.gradle.kts — berkas yang menentukan siapa saja peserta build:
rootProject.name = "aplikasi-gudang"
include("app")
include("lib-util")Baris rootProject.name menamai proyek akar, sedangkan setiap include mendaftarkan subproyek yang masing-masing memiliki build.gradle.kts sendiri. Struktur ini memungkinkan satu repositori memuat banyak modul — skenario multi-project build — dengan task lintas modul dikenali nama penuhnya seperti gradle :app:test. Daftar include inilah yang diisi Gradle pada tahap initialization yang akan kita bahas di chapter Gradle Build Lifecycle.
Incremental Build dan Status Up-to-Date #
Setiap task menyimpan jejak input dan output dari eksekusi sebelumnya. Saat dijalankan ulang, Gradle membandingkan input dan output terkini dengan jejak tersebut; bila keduanya identik, task ditandai UP-TO-DATE dan dilewati tanpa menjalankan aksinya. Ubah satu berkas .java, maka hanya compileJava (dan task yang bergantung padanya) yang berjalan kembali — processResources yang inputnya tak berubah tetap dilewati. Mekanisme ini, dipadukan dengan daemon proses yang bertahan hidup antar perintah, menjadikan iterasi build Gradle terasa cepat pada proyek besar.
Model task dan DAG adalah fondasi Gradle — namun kapan tepatnya build script dibaca, DAG disusun, dan task dieksekusi? Chapter berikutnya membedah tiga tahap hidup sebuah build Gradle.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
GrafTugas.java
Unduh berkasimport java.util.ArrayList;
import java.util.List;
public class GrafTugas {
// Task dan daftar task yang menjadi prasyaratnya (meniru plugin java).
private static final String[] TASK = {
"compileJava", "processResources", "classes", "compileTestJava",
"test", "jar", "assemble", "check", "build"
};
private static final String[][] BERGANTUNG_PADA = {
{}, // compileJava
{}, // processResources
{"compileJava", "processResources"}, // classes
{"classes"}, // compileTestJava
{"compileTestJava"}, // test
{"classes"}, // jar
{"jar"}, // assemble
{"test"}, // check
{"assemble", "check"} // build
};
private static final List<String> urutan = new ArrayList<>();
public static void main(String[] args) {
susun("build");
System.out.println("urutan eksekusi task 'build':");
for (int i = 0; i < urutan.size(); i++) {
System.out.printf("%d. %s%n", i + 1, urutan.get(i));
}
System.out.println();
System.out.println("total task terjadwal: " + urutan.size());
}
/** DFS: sebuah task hanya dijadwalkan setelah seluruh prasyaratnya. */
private static void susun(String nama) {
if (urutan.contains(nama)) {
return;
}
for (String prasyarat : BERGANTUNG_PADA[indeks(nama)]) {
susun(prasyarat);
}
urutan.add(nama);
}
private static int indeks(String nama) {
for (int i = 0; i < TASK.length; i++) {
if (TASK[i].equals(nama)) {
return i;
}
}
throw new IllegalArgumentException("task tidak dikenal: " + nama);
}
}