Chapter penutup section ini merangkum praktik terbaik yang mempengaruhi keseharian tim pengembang Kotlin. Topik-topik yang telah dibahas di chapter-chapter sebelumnya — kotlinc, Gradle, dependencies, version catalog, Maven — semuanya menyatu dalam keputusan praktis: bagaimana menulis build.gradle.kts yang sehat, bagaimana mengonfigurasi CI, dan bagaimana menjaga build tetap cepat seiring proyek tumbuh. Pada chapter ini kita akan membahas lima praktik yang paling berdampak, beserta alasan dan konsekuensi masing-masing.
1. Pin Versi di Version Catalog #
Praktik pertama: kumpulkan seluruh deklarasi versi di gradle/libs.versions.toml. Hindari menulis string versi langsung di build.gradle.kts. Manfaatnya:
- Versi konsisten lintas modul: bila satu modul memakai
kotlinx-coroutines-core:1.8.1, modul lain tidak akan secara tidak sengaja memakai1.7.3. - Update satu tempat: cukup ubah satu baris di
[versions]untuk upgrade seluruh modul. - IDE autocomplete: typed accessor
libs.kotlinx.coroutines.corememberi saran sambil mengetik.
[versions]
kotlin = "2.0.21"
coroutines = "1.8.1"
[libraries]
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }2. Pakai Gradle Wrapper #
Wrapper memastikan versi Gradle yang dipakai developer dan CI identik. Selalu commit file wrapper ke repository:
gradlew
gradlew.bat
gradle/wrapper/gradle-wrapper.jar
gradle/wrapper/gradle-wrapper.propertiesUpgrade wrapper dengan perintah:
./gradlew wrapper --gradle-version 8.103. Build Bersih di CI #
CI harus menghasilkan build yang deterministik. Beberapa praktik:
- Selalu mulai dari container Docker yang sama, dengan versi JDK dan Gradle yang dipin.
- Jalankan
./gradlew clean build(atau./gradlew build --no-daemon --no-build-cache) untuk memastikan build dari nol berhasil. - Set
SOURCE_DATE_EPOCHagar timestamp file di JAR deterministik. - Untuk verifikasi tambahan, bandingkan hash JAR dengan build sebelumnya.
jobs:
build:
runs-on: ubuntu-22.04
container: eclipse-temurin:21-jdk
steps:
- uses: actions/checkout@v4
- name: Build
run: ./gradlew clean build
env:
SOURCE_DATE_EPOCH: 17000000004. Cache Dependensi #
Build cache memotong waktu build secara signifikan. Tiga lapisan cache yang harus diaktifkan:
org.gradle.parallel=true # build multi-modul paralel
org.gradle.caching=true # build cache lokal
org.gradle.configuration-cache=true # cache hasil configuration phase
org.gradle.jvmargs=-Xmx2g -XX:+UseZGC # heap daemon cukup besarUntuk CI, cache direktori ~/.gradle/caches/ antar run:
- uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', 'gradle/libs.versions.toml') }}5. Hindari Dynamic Version #
Dynamic version (+, latest.release, latest.snapshot) membuat build tidak reproducible. Setiap eksekusi dapat menghasilkan versi dependensi yang berbeda. Beberapa alasan mengapa ini berbahaya:
- Build tidak reproducible: dua build pada hari berbeda dapat menghasilkan JAR berbeda.
- Risiko break mendadak: versi baru library dengan breaking change menyebabkan build gagal tanpa perubahan kode dari kita.
- Sulit untuk audit: tidak jelas versi mana yang sebenarnya dipakai saat produksi.
// HINDARI:
implementation("com.example:lib:1.+")
implementation("com.example:lib:latest.release")
// GANTI DENGAN:
implementation(libs.example.lib) // dari version catalogDemonstrasi Best Practices #
Kode berikut mencantumkan lima praktik utama sebagai ringkasan.
fun main() {
println("=== Best Practices Build Tools ===")
println()
println("1. Pin versi di version catalog")
println(" - Hindari dynamic version: implementation(\"lib:1.+\")")
println(" - Pakai libs.versions.toml untuk semua versi")
println()
println("2. Pakai Gradle Wrapper")
println(" - Commit gradlew, gradlew.bat, gradle/wrapper/* ke repository")
println(" - Versi Gradle konsisten di semua developer dan CI")
println()
println("3. Build bersih di CI")
println(" - Tidak ada state lokal (sebelum build: ./gradlew clean)")
println(" - Gunakan container deterministik (image Docker versi pin)")
println(" - Set SOURCE_DATE_EPOCH untuk reproducibility")
println()
println("4. Cache dependensi")
println(" - Aktifkan Gradle cache: gradle.properties -> org.gradle.caching=true")
println(" - CI: cache direktori ~/.gradle/caches/")
println(" - Build cache remote: Gradle Enterprise / Develocity")
println()
println("5. Hindari dynamic version (+)")
println(" - '+' / 'latest.release' membuat build tidak reproducible")
println(" - Risiko: versi baru library break compatibility tanpa peringatan")
println(" - Pakai dependabot / renovate untuk update versi otomatis")
}Output yang Dihasilkan:
=== Best Practices Build Tools ===
1. Pin versi di version catalog
- Hindari dynamic version: implementation("lib:1.+")
- Pakai libs.versions.toml untuk semua versi
2. Pakai Gradle Wrapper
- Commit gradlew, gradlew.bat, gradle/wrapper/* ke repository
- Versi Gradle konsisten di semua developer dan CI
3. Build bersih di CI
- Tidak ada state lokal (sebelum build: ./gradlew clean)
- Gunakan container deterministik (image Docker versi pin)
- Set SOURCE_DATE_EPOCH untuk reproducibility
4. Cache dependensi
- Aktifkan Gradle cache: gradle.properties -> org.gradle.caching=true
- CI: cache direktori ~/.gradle/caches/
- Build cache remote: Gradle Enterprise / Develocity
5. Hindari dynamic version (+)
- '+' / 'latest.release' membuat build tidak reproducible
- Risiko: versi baru library break compatibility tanpa peringatan
- Pakai dependabot / renovate untuk update versi otomatisMari kita telaas kode di atas:
- Lima praktik: dipilih karena berdampak paling besar pada kualitas build. Praktik lain (pemilihan configuration, structure project, reproducibility) telah dibahas di chapter tersendiri.
- Dynamic version sebagai sumber utama masalah: bahaya utama adalah reproducibility dan auditabilitas. Dependabot memberi manfaat update otomatis tanpa risiko dynamic version.
- Build cache: tiga lapisan (local cache, configuration cache, daemon) bekerja sama. Aktifkan ketiganya untuk hasil optimal.
Praktik Tambahan #
Beberapa praktik yang juga penting tetapi tidak masuk lima besar:
- Modularisasi: pisahkan kode ke modul-modul kecil agar build inkremental efektif.
- Deteksi unused dependencies: jalankan
./gradlew dependenciesdandependencyInsightsecara berkala untuk membersihkan. - Lock files (Gradle 7+):
./gradlew dependencies --write-verification-metadata sha-256untuk verifikasi integritas dependensi. - SBOM (Software Bill of Materials): gunakan plugin seperti
org.cyclonedx.bomuntuk menghasilkan daftar dependensi lengkap untuk audit keamanan.
Referensi Lanjutan #
- Gradle — panduan resmi Kotlin + Gradle sebagai dasar praktik terbaik build Kotlin
- Kotlin with Maven — panduan resmi Kotlin + Maven untuk praktik terbaik proyek berbasis Maven
- Coding conventions — panduan konvensi penulisan Kotlin yang juga relevan untuk tata nama build script
Rangkuman Section #
Section Build Tools dan Dependency Management berakhir di sini. Kita telah menelusuri kotlinc sebagai compiler dasar, Gradle sebagai build tool modern, struktur proyek, deklarasi dependensi, lifecycle build, version catalog, resolusi konflik, Maven sebagai alternatif, build variants, dan reproducible builds. Pemahaman utuh atas topik ini menjadi modal untuk membaca dan menulis build script yang sehat. Section berikutnya akan berfokus pada topik yang sama pentingnya untuk pengembangan profesional: debugging dan kualitas kode.