Alat Build

Mengelola proyek dan dependensi dengan Maven: POM, lifecycle, dan repository.

Daftar isi

Proyek Java yang nyata jarang berdiri dari satu berkas tunggal. Ada puluhan kelas, ada library pihak ketiga yang harus tersedia pada versi yang tepat, dan ada langkah berulang seperti kompilasi, pengujian, sampai pengemasan. Topik ini membahas Maven, alat build yang mengurus semuanya dari satu berkas konfigurasi. Setelah membacanya, struktur POM, rangkaian lifecycle, dan cara repository menyediakan dependensi dapat dibaca tanpa menebak-nebak maknanya.

Mengapa proyek butuh alat build#

Kompilasi manual dengan javac memadai untuk program lima berkas, tetapi cepat kehabisan tenaga di luar itu. Setiap library tambahan berarti berkas .jar yang harus diunduh, ditempatkan, dan dirangkai sebagai classpath, dan langkah itu diulang di komputer setiap anggota tim. Alat build mengambil alih pekerjaan berulang tersebut dengan membaca deskripsi proyek, lalu menjalankan kompilasi, pengujian, dan pengemasan dengan urutan serta lingkungan yang selalu sama.

Dua alat build paling banyak dipakai di ekosistem Java adalah Maven dan Gradle. Maven mendeskripsikan proyek dalam berkas XML bernama pom.xml, sedangkan Gradle memakai skrip berbasis Kotlin atau Groovy. Keduanya menyelesaikan pekerjaan yang sama, dan pemilihan alat biasanya mengikuti kebiasaan tim. Topik ini memakai Maven sebagai contoh karena konfigurasinya deklaratif dan bentuknya paling mudah dibaca orang yang baru bertemu alat build.

Alat build juga menjadi titik sambung untuk kebiasaan lain yang akan dipakai sepanjang karier. Pengujian otomatis dijalankan lewat alat build, kualitas kode diperiksa oleh plugin yang terpasang padanya, dan hampir semua library Java didistribusikan dalam format yang dipahami alat build.

Anatomi POM#

POM (Project Object Model) adalah deskripsi proyek dalam berkas pom.xml yang menjadi satu-satunya titik konfigurasi Maven. Di dalamnya terdapat identitas proyek, versi, daftar dependensi, dan pengaturan proses build. Identitas proyek menyusun tiga bagian yang disebut coordinate, yaitu groupId untuk organisasi pemilik, artifactId untuk nama artefak, dan version untuk versi terkini.

xml Contoh: pom.xml minimal untuk proyek kalkulator nilai ujian
<!-- tidak-dijalankan -->
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>id.tutorial</groupId>
    <artifactId>kalkulator-nilai</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Bagian properties menampung nilai yang dipakai ulang, seperti versi Java yang ditargetkan dan encoding sumber. Bagian dependencies mencantumkan setiap library yang dirujuk proyek beserta versinya. Elemen scope mengatur kapan dependensi ikut dipakai, dan nilai test berarti library tersebut hanya tersedia untuk kode pengujian. POM di atas adalah titik awal yang wajar untuk proyek yang akan diuji dengan JUnit pada topik Pengujian Unit.

Koordinat proyek juga menentukan tempat artefaknya berlabuh di repository. Artefak id.tutorial:kalkulator-nilai:1.0.0 akan tersimpan di bawah jalur id/tutorial/kalkulator-nilai/1.0.0/ pada repository yang menampungnya, sehingga identitas dan lokasinya konsisten di seluruh ekosistem. Kesamaan aturan inilah yang membuat groupId sebaiknya mengikuti nama domain organisasi secara terbalik, karena kebiasaan itu menjaga keunikan koordinat antarproyek dari tim yang berbeda.

Lifecycle: rangkaian fase bawaan#

Maven menjalankan seluruh pekerjaan build sebagai rangkaian fase yang berurutan, dan rangkaian ini disebut lifecycle. Terdapat tiga lifecycle bawaan, yaitu default untuk membangun proyek, clean untuk menghapus hasil build lama, dan site untuk membuat dokumentasi proyek. Fase pada lifecycle default yang paling sering ditemui adalah validate, compile, test, package, verify, install, dan deploy.

validatecompiletestpackageverifyinstalldeploylifecycle default MavenPerintah mvn package menjalankan validate, compile, test, lalu package.
Rantai fase pada lifecycle default Maven

Aturan pentingnya satu kalimat saja: pemanggilan satu fase selalu menjalankan seluruh fase sebelumnya. Perintah mvn package mengompilasi kode, menjalankan pengujian, lalu mengemas hasilnya, tanpa perlu memanggil mvn compile terlebih dahulu. Tabel berikut merangkum fungsi tiap fase yang paling sering dipakai.

Pendekatan fase ini berbeda dari sekadar menghafal rangkaian perintah kompilasi. Karena urutannya tetap, hasil build di komputer satu dibaca dengan asumsi yang sama di komputer lain, dan tak ada langkah yang terlewat hanya karena dilupakan. Kalau kebutuhannya hanya membersihkan, mvn clean cukup dipanggil sendirian, sedangkan kebutuhan yang lazim adalah menggabungkan dua lifecycle lewat mvn clean package seperti pada contoh terminal di bawah.

FaseFungsi
validateMemeriksa apakah proyek dan POM-nya dalam keadaan sah
compileMengompilasi kode sumber menjadi bytecode
testMenjalankan pengujian unit dengan kerangka uji yang terpasang
packageMengemas hasil build menjadi jar atau format lain
verifyMenjalankan pemeriksaan kualitas tambahan atas hasil kemasan
installMenempatkan artefak ke repository lokal komputer
deployMengunggah artefak ke repository bersama untuk dipakai tim

Repository dan penyelesaian dependensi#

Semua library yang dirujuk POM diambil dari repository, yaitu gudang artefak yang terorganisir menurut koordinatnya. Maven membaca pom.xml, mencari artefak yang diminta di repository lokal pada folder ~/.m2/repository, dan mengunduhnya dari repository jarak jauh seperti Maven Central bila belum ada. Dependensi yang diunduh bisa membawa dependensinya sendiri, dan rantai tersebut disebut dependensi transitif. Maven menyelesaikan seluruh rantai itu secara otomatis sehingga developer cukup mencantumkan dependensi paling atas saja.

Mekanisme penyelesaian pohon dependensi dapat ditiru dengan program kecil berikut agar prosesnya terlihat.

java PohonDependensi.java
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

public class PohonDependensi {

    // peta artefak ke dependensi langsungnya, meniru isi sebuah POM
    static final Map<String, String[]> POM = new LinkedHashMap<>();

    static {
        POM.put("app-kalkulator", new String[] {"util-format:1.0.0", "util-matematika:2.1.0"});
        POM.put("util-format:1.0.0", new String[] {"util-matematika:2.1.0"});
        POM.put("util-matematika:2.1.0", new String[] {});
    }

    static void telusuri(String artefak, String indentasi, Set<String> terkumpul) {
        System.out.println(indentasi + artefak);
        terkumpul.add(artefak);
        for (String dependensi : POM.getOrDefault(artefak, new String[0])) {
            telusuri(dependensi, indentasi + "|  ", terkumpul);
        }
    }

    public static void main(String[] args) {
        Set<String> unik = new LinkedHashSet<>();
        System.out.println("pohon dependensi app-kalkulator:");
        telusuri("app-kalkulator", "  ", unik);
        System.out.println("artefak unik yang harus tersedia: " + unik.size());
    }
}
text Keluaran
pohon dependensi app-kalkulator:
  app-kalkulator
  |  util-format:1.0.0
  |  |  util-matematika:2.1.0
  |  util-matematika:2.1.0
artefak unik yang harus tersedia: 3

Keluarannya memperlihatkan dua hal yang juga dikerjakan Maven. Pohon ditelusuri sampai rantai paling dalam, sehingga dependensi transitif util-matematika ditemukan lewat dua jalur berbeda, dan kumpulan artefak uniknya hanya berisi tiga entri. Ketika dua jalur meminta versi berbeda untuk artefak yang sama, Maven memilih dependensi yang terdekat ke akar pohon. Konflik semacam ini adalah salah satu sumber bug versi yang paling umum. Perintah mvn dependency:tree menampilkan pohon yang sama untuk proyek sungguhan.

Maven di terminal#

Proyek Maven mengikuti tata letak bawaan yang dipahami semua penggunanya, yaitu kode sumber pada src/main/java, kode pengujian pada src/test/java, dan hasil build pada target. Dengan tata letak yang seragam, pindah dari satu proyek ke proyek lain tidak menuntut pemahaman ulang strukturnya. Perintah-perintah berikut adalah pekerjaan harian yang paling sering diketik.

bash
# tidak-dijalankan
# membangun dari awal sampai berkas .jar siap pakai di folder target
mvn clean package

# menjalankan pengujian unit saja
mvn test

# menampilkan pohon dependensi seperti pada demo program kecil di atas
mvn dependency:tree

Kata clean di depan perintah pertama memanggil lifecycle clean untuk menghapus folder target sebelum build dimulai, sehingga hasil lama tidak bercampur dengan hasil baru. Hasil akhirnya berupa berkas jar di target yang siap dijalankan atau didistribusikan. Saat proses berjalan, Maven mencetak fase yang sedang dieksekusi beserta hasil pengujian, dan pesan BUILD SUCCESS di baris akhir menandakan seluruh fase tuntas tanpa kegagalan.

Kebiasaan harian yang sehat adalah menjalankan mvn test sebelum kode diserahkan ke repository bersama, karena perintah itu memastikan seluruh pengujian dari topik Pengujian Unit benar-benar lulus di lingkungan build. Bendera -DskipTests memang tersedia untuk mempercepat build yang hanya mencoba kompilasi, tetapi kebiasaan melewati pengujian pada build utama mempertaruhkan jaminan yang justru menjadi alasan memakai alat build.

Kesalahan umum#

  • Mengubah kode tanpa memperbarui version proyek di POM. Artefak lama dan baru menjadi sulit dibedakan di repository, terutama saat tim memakai hasil install. Naikkan versi setiap kali hasil yang baru hendak dibagikan.
  • Melupakan scope pada dependensi pengujian. Library uji yang tanpa scope test ikut terkemas ke artefak produksi dan membesarkannya tanpa manfaat. Cantumkan <scope>test</scope> untuk semua dependensi uji.
  • Mengira mvn clean merapikan seluruh proyek. Perintah itu hanya menghapus folder target, sehingga masalah dari konfigurasi luar atau IDE tetap perlu ditangani sendiri.
  • Menambah dependensi lewat fitur IDE tanpa memeriksa POM. Entri yang dibuat otomatis sering memakai versi pilihan IDE, bukan versi yang disepakati tim. Periksa hasilnya di pom.xml dan rapikan ke versi yang benar.
  • Menyimpan berkas jar hasil unduhan manual ke dalam proyek. Praktik lama ini memutus rantai dependensi Maven dan membuat versi tak terlacak. Cantumkan semuanya sebagai dependensi POM agar repository yang menguruskannya.

Ringkasan#

  • Maven membaca satu berkas pom.xml yang memuat identitas proyek, dependensi, dan pengaturan build, lalu menjalankan seluruh pekerjaan berulang atas nama developer.
  • Lifecycle default terdiri atas fase berurutan, dan pemanggilan satu fase selalu menjalankan fase-fase di depannya.
  • Dependensi diambil dari repository lokal dan jarak jauh, termasuk rantai dependensi transitif yang diselesaikan otomatis.
  • Tata letak folder src/main/java, src/test/java, dan target bersifat seragam sehingga proyek Maven mudah dibaca lintas tim.

Referensi#