Reproducible builds adalah prinsip bahwa input yang sama harus menghasilkan output yang identik secara byte-for-byte. Tanpa reproducibility, dua orang yang membangun kode sumber yang sama dapat menghasilkan artefak yang berbeda, menyulitkan audit keamanan dan kolaborasi. Inisiatif Reproducible Builds (https://reproducible-builds.org ) mendorong ekosistem alat untuk mendukung properti ini. Pada chapter ini kita akan membahas syarat reproducibility, pengaturan Gradle yang relevan, serta cara memverifikasi hasil build.
Mengapa Reproducible Builds Penting #
Tiga alasan utama:
- Audit keamanan: bila build reproducible, siapapun dapat memverifikasi bahwa artefak produksi benar-benar berasal dari kode sumber publik, tanpa pintu belakang yang disuntik saat build.
- Cache efektif: build cache hanya bekerja bila input yang sama menghasilkan output yang sama. Tanpa reproducibility, cache hit rate turun drastis.
- Kolaborasi: developer dapat membandingkan artefak lokal dengan artefak CI untuk memverifikasi kesetaraan. Bila berbeda, berarti ada masalah lingkungan.
Sumber Non-Determinisme #
Beberapa hal yang membuat build tidak reproducible:
| Sumber | Cara mengatasi |
|---|---|
| Timestamp file di JAR | isPreserveFileTimestamps = false |
| Urutan file di JAR (urutan filesystem) | isReproducibleFileOrder = true |
Dynamic version (+, latest.release) | Pin versi di version catalog |
Math.random() di kode build | Pakai java.util.Random(seed) |
System.currentTimeMillis() | Gantikan dengan waktu tetap dari env |
| Path absolut di bytecode | Pakai path relatif |
| Locale OS yang berbeda | Set LC_ALL=C di CI |
Pengaturan Gradle #
import org.gradle.api.tasks.bundling.AbstractArchiveTask
// Seluruh task yang menghasilkan archive (jar, zip, tar) menjadi reproducible
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=trueVariabel lingkungan SOURCE_DATE_EPOCH (timestamp Unix) dapat dipakai untuk menetapkan waktu tetap yang konsisten lintas build:
# Build dengan timestamp tetap (mis. tanggal commit terakhir)
SOURCE_DATE_EPOCH=1700000000 ./gradlew buildDemonstrasi Reproducible Builds #
Kode berikut mencantumkan konsep dan pengaturan reproducibility.
fun main() {
println("=== Reproducible Builds ===")
println()
println("Definisi: input yang sama -> output bytecode yang identik (byte-for-byte)")
println()
println("Syarat reproducible:")
println(" - Versi dependensi dipin (tidak ada dynamic version '+')")
println(" - Timestamp JAR diset ke waktu tetap (SOURCE_DATE_EPOCH)")
println(" - Build dilakukan di environment yang sama (JDK, OS)")
println(" - Tidak ada input non-deterministik (Math.random(), currentTimeMillis())")
println()
println("Pengaturan Gradle untuk reproducibility:")
println(" gradle.properties:")
println(" org.gradle.parallel=true")
println(" org.gradle.caching=true")
println(" org.gradle.configuration-cache=true")
println()
println(" build.gradle.kts:")
println(" tasks.withType<AbstractArchiveTask>() {")
println(" isPreserveFileTimestamps = false")
println(" isReproducibleFileOrder = true")
println(" }")
println()
println("Verifikasi:")
println(" diff <(unzip -l app.jar) <(unzip -l app.jar.bak)")
println(" reproducible-builds.org/tools/ — alat verifikasi")
}Output yang Dihasilkan:
=== Reproducible Builds ===
Definisi: input yang sama -> output bytecode yang identik (byte-for-byte)
Syarat reproducible:
- Versi dependensi dipin (tidak ada dynamic version '+')
- Timestamp JAR diset ke waktu tetap (SOURCE_DATE_EPOCH)
- Build dilakukan di environment yang sama (JDK, OS)
- Tidak ada input non-deterministik (Math.random(), currentTimeMillis())
Pengaturan Gradle untuk reproducibility:
gradle.properties:
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
build.gradle.kts:
tasks.withType<AbstractArchiveTask>() {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
Verifikasi:
diff <(unzip -l app.jar) <(unzip -l app.jar.bak)
reproducible-builds.org/tools/ — alat verifikasiMari kita telaah kode di atas:
- Definisi: input sama menghasilkan output identik byte-for-byte. Bukan hanya “membangun kode yang sama”, tetapi juga “menghasilkan JAR yang sama persis”.
- Empat syarat: versi dependensi dipin, timestamp tetap, environment sama, tanpa input non-deterministik. Kombinasi keempatnya diperlukan; bila satu syarat gagal, output akan berbeda.
- Pengaturan Gradle: dua baris
isPreserveFileTimestamps = falsedanisReproducibleFileOrder = trueadalah kunci utama. Tanpa ini, JAR Maven/Gradle biasanya menyimpan timestamp filesystem yang berbeda antar build. SOURCE_DATE_EPOCH: variabel standar yang dikenali banyak alat (Gradle, Maven, javac) untuk menetapkan timestamp tetap. Nilai biasanya diambil dari tanggal commit Git terakhir.
Verifikasi Reproducibility #
Setelah build, verifikasi dengan membangun dua kali dan membandingkan hash file:
# Build pertama
./gradlew clean build
sha256sum build/libs/app.jar > /tmp/hash1.txt
# Build kedua
./gradlew clean build
sha256sum build/libs/app.jar > /tmp/hash2.txt
# Bandingkan
diff /tmp/hash1.txt /tmp/hash2.txt
# Bila identik: build reproducibleAlat yang lebih canggih tersedia di https://reproducible-builds.org/tools/
, termasuk diffoscope yang menampilkan perbedaan struktur file secara visual.
Reproducible Builds untuk Maven #
Maven juga mendukung reproducibility sejak versi 3.8.1+. Pengaturan:
<properties>
<project.build.outputTimestamp>2024-01-01T00:00:00Z</project.build.outputTimestamp>
</properties>Properti outputTimestamp dipakai oleh maven-jar-plugin untuk menetapkan timestamp tetap di JAR.
Reproducible Builds Initiative #
Inisiatif global Reproducible Builds (https://reproducible-builds.org ) didirikan pada 2014 dan kini didukung oleh Debian, Fedora, F-Droid, dan banyak proyek lain. Tujuannya: setiap distribusi biner harus dapat dibangun ulang dari kode sumber dan diverifikasi identik.
Untuk proyek Kotlin, mengikuti praktik ini bermanfaat bahkan tanpa audit keamanan penuh: build yang reproducible berarti build cache bekerja optimal, dan perubahan artefak dapat dilacak dengan tepat ke commit tertentu.
Referensi Lanjutan #
- Gradle User Guide — buku panduan Gradle yang merinci opsi untuk build yang reproducible
- Gradle — rujukan Kotlin + Gradle untuk mengunci versi dan opsi compiler agar konsisten
Penutup #
Reproducible builds adalah praktik mutakhir untuk proyek yang menjaga kepercayaan publik. Bagi sebagian besar proyek internal, penerapan parsial (pin versi, set isReproducibleFileOrder) sudah memberi manfaat signifikan untuk build cache. Pada chapter terakhir section ini kita akan membahas rangkuman praktik terbaik build tools yang menggabungkan seluruh topik yang telah dibahas.