Grape adalah sistem manajemen dependensi bawaan Groovy, dan anotasi @Grab adalah cara memakainya. Sebuah utilitas dapat dikirim sebagai satu berkas .groovy karena pustaka pihak ketiga yang dibutuhkannya dideklarasikan langsung di dalam berkas itu, lalu diunduh dan dipasang secara otomatis saat skrip pertama kali dijalankan. Topik ini membahas bentuk anotasi @Grab, repositori tambahan dengan @GrabResolver, penyiapan driver JDBC, alat baris perintah grape, cache unduhannya, serta dua kegagalan yang paling sering ditemui ketika mekanisme ini dipraktikkan.
Grape sebagai sistem dependensi bawaan#
Grape, kependekan dari Groovy Adaptable Packaging Engine, adalah pengelola dependensi JAR yang tertanam di dalam Groovy. Dependensi dideklarasikan lewat anotasi @Grab, dan Grape mengunduh jar pustaka tersebut beserta seluruh dependensi transitifnya dari repositori seperti Maven Central saat skrip dikompilasi. Bila ada bagian unduhan yang tidak dibutuhkan, anotasi @GrabExclude dapat menyaringnya secara sebagian. Anotasi ini diproses lebih dulu sebelum resolusi kelas lain, sehingga import terhadap pustaka yang dianotasi @Grab dapat diselesaikan pada tahap kompilasi.
Contoh berikut memakai pustaka commons-lang3, kumpulan utilitas teks dari Apache Commons, untuk membersihkan catatan laporan piket kelas. Metode normalizeSpace merapikan spasi dan baris baru, countMatches menghitung kata dari jumlah spasinya, dan abbreviate memenggal teks menjadi maksimal dua puluh karakter.
// bersih-laporan.groovy: membersihkan teks laporan dengan StringUtils
@Grab('org.apache.commons:commons-lang3:3.14.0')
import org.apache.commons.lang3.StringUtils
def mentah = ' Laporan harian piket\n\nkelas 11-IPA \t'
def bersih = StringUtils.normalizeSpace(mentah)
println "Bersih : [$bersih]"
println "Kata : ${StringUtils.countMatches(bersih, ' ') + 1}"
println "Ringkas: ${StringUtils.abbreviate(bersih, 20)}"Bersih : [Laporan harian piket kelas 11-IPA]
Kata : 5
Ringkas: Laporan harian pi...Keluaran menunjukkan tiga pemanggilan tersebut secara berurutan. Spasi berlebih, baris baru, dan tab hilang setelah normalizeSpace, penghitung kata menghasilkan lima, dan abbreviate menambah tanda titik tiga pada akhir teks yang dipenggal. Pustaka commons-lang3 sendiri hanya diunduh sekali pada eksekusi pertama, sedangkan eksekusi berikutnya membaca jar langsung dari cache Grape.
Bentuk anotasi dan pemilihan versi#
Anotasi @Grab memiliki dua bentuk. Bentuk singkat menuliskan koordinat pustaka sebagai satu String dengan pola grup:artefak:versi, sedangkan bentuk penuh memakai parameter bernama group, module, dan version. Hasil kedua bentuk tersebut identik, sehingga pilihannya hanya soal kebiasaan membaca. Lebih dari satu @Grab boleh bertumpuk pada target yang sama tanpa anotasi pembungkus @Grapes. Pada versi Groovy lama, anotasi pembungkus tersebut masih wajib. Target yang dianjurkan adalah baris import, tetapi anotasi ini juga sah ditempelkan pada kelas, metode, hingga variabel lokal.
Nilai versi boleh berupa versi literal seperti 3.14.0 atau rentang versi Ivy seperti [2.2.1,) yang berarti 2.2.1 atau versi yang lebih baru. Batas atas juga didukung, misalnya [2.2.1,2.3.0) untuk mengunci rentang antara dua versi. Selain itu ada tanda bintang '*' yang berarti versi terbaru yang tersedia saat resolusi berlangsung.
Skrip berikut memakai kedua bentuk sekaligus, yaitu bentuk singkat untuk commons-lang3 dan bentuk penuh untuk commons-math3 yang menyediakan kelas statistiknya. Contoh yang dihitung berupa ringkasan nilai ujian lima peserta.
// bentuk singkat dan bentuk penuh pada skrip yang sama
@Grab('org.apache.commons:commons-lang3:3.14.0')
@Grab(group='org.apache.commons', module='commons-math3', version='3.6.1')
import org.apache.commons.lang3.StringUtils
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics
def nilai = [85, 78, 91, 70, 88] as double[]
def stat = new DescriptiveStatistics(nilai)
println StringUtils.center('Rekap nilai ujian', 24)
println "Rata-rata : ${stat.mean}"
println "Median : ${stat.getPercentile(50)}"
println "Tertinggi : ${stat.max}" Rekap nilai ujian
Rata-rata : 82.4
Median : 85.0
Tertinggi : 91.0Kedua anotasi terpasang pada skrip yang sama dan seluruh kelas pustakanya langsung dipakai. Kelas DescriptiveStatistics dari commons-math3 menerima deret nilai lima peserta lalu menghitung ringkasannya, sedangkan StringUtils.center menempatkan judul di tengah kolom dua puluh empat karakter. Rata-rata 82.4, median 85.0, dan nilai tertinggi 91.0 semuanya berasal dari data yang sama dan mudah dihitung ulang.
Menambah repositori dengan @GrabResolver#
Tidak semua pustaka berada di Maven Central, misalnya pustaka internal organisasi atau pustaka yang diterbitkan dari GitHub melalui JitPack. Anotasi @GrabResolver menambahkan repositori ke rantai resolusi Grape dengan parameter name sebagai nama bebas dan root sebagai alamat repositorinya. Anotasi ini diletakkan sebelum @Grab yang membutuhkan repositori tersebut, dan tanpa anotasi ini resolusi hanya mencari pada repositori bawaan.
Agar dapat dijalankan apa adanya, contoh berikut menunjuk root ke Maven Central juga, sehingga resolusinya berhasil dengan pustaka yang sudah dikenal. Pada pemakaian nyata, nilai root diganti alamat repositori yang menyimpan pustaka tujuan.
@GrabResolver(name='pusat-maven', root='https://repo1.maven.org/maven2/')
@Grab('org.apache.commons:commons-lang3:3.14.0')
import org.apache.commons.lang3.StringUtils
println StringUtils.swapCase('Groovy')
println "Kata: ${StringUtils.countMatches('grape dan grab', ' ') + 1}"gROOVY
Kata: 3Metode swapCase membalikkan kapitalisasi setiap hurufnya sehingga Groovy menjadi gROOVY, sedangkan baris kedua menghitung tiga kata dari frasa grape dan grab. Seluruh pemanggilan itu hanya berhasil karena artefak commons-lang3 ditemukan pada repositori yang terdaftar di rantai resolusi. Pustaka yang sama memang tersedia di Maven Central, sehingga contoh ini berjalan tanpa memerlukan repositori baru yang sungguhan.
Driver JDBC dengan @GrabConfig#
Grape menyediakan anotasi @GrabConfig untuk menyetel perilaku unduhannya per skrip, dan salah satu pengaturannya adalah systemClassLoader=true yang menempelkan dependensi ke system class loader. Konfigurasi ini disarankan dokumentasi Grape untuk dependensi driver JDBC karena cara pemuatan driver JDBC melibatkan system class loader tersebut. Contoh berikut memakai basis data H2 dalam mode in-memory lewat URL jdbc:h2:mem:laporan beserta parameter DB_CLOSE_DELAY=-1, dengan pengguna sa dan kata sandi kosong. Kelas Sql yang dipakai di sini berasal dari paket groovy.sql, pustaka standar Groovy di atas JDBC.
Skrip berikut membuat tabel nilai, menyisipkan tiga baris, lalu membacanya kembali urut dari skor tertinggi. Pemeriksaan koneksi diletakkan di awal agar keberhasilan pemasangan driver JDBC terlihat langsung pada baris keluaran pertama.
// koneksi-h2.groovy: driver JDBC melalui @Grab dan @GrabConfig
@Grab('com.h2database:h2:2.2.224')
@GrabConfig(systemClassLoader=true)
import groovy.sql.Sql
def sql = Sql.newInstance('jdbc:h2:mem:laporan;DB_CLOSE_DELAY=-1', 'sa', '', 'org.h2.Driver')
println "Terhubung: ${sql.connection.isValid(0)}"
sql.execute 'CREATE TABLE nilai (nama VARCHAR(20), skor INT)'
sql.executeInsert "INSERT INTO nilai (nama, skor) VALUES ('Ali', 85), ('Budi', 78), ('Citra', 91)"
sql.eachRow('SELECT nama, skor FROM nilai ORDER BY skor DESC') { baris ->
println "${baris.nama} : ${baris.skor}"
}
println "Jumlah: ${sql.rows('SELECT nama FROM nilai').size()} peserta"
sql.close()Terhubung: true
Citra : 91
Ali : 85
Budi : 78
Jumlah: 3 pesertaMetode newInstance menerima URL, pengguna, kata sandi, dan nama kelas driver, sedangkan connection.isValid(0) memeriksa koneksi yang baru dibuka. Tiga baris data milik Ali, Budi, dan Citra masuk lewat executeInsert lalu terbaca kembali urut dari skor 91. Pemanggilan close pada akhir skrip menutup koneksi dan melepaskan sumber dayanya.
Alat grape dan cache unduhan#
Grape menyertakan alat baris perintah bernama grape untuk memeriksa dan mengelola cache tanpa menulis skrip. Perintah grape install memasang pustaka ke cache, grape list menampilkan modul yang sudah terpasang, grape resolve mencetak lokasi jar pustaka beserta dependensi transitifnya, dan grape uninstall menghapus satu versi pustaka dari cache. Perintah install tanpa versi memilih versi terbaru yang tersedia.
# tidak-dijalankan
# memasang pustaka ke cache tanpa menulis skrip
grape install org.apache.commons commons-lang3 3.14.0
# menampilkan modul yang sudah terpasang pada cache
grape list
# menampilkan lokasi jar beserta dependensi transitifnya
grape resolve org.apache.commons commons-lang3 3.14.0
# menghapus satu versi pustaka dari cache
grape uninstall org.apache.commons commons-lang3 3.14.0Mesin GrapeIvy yang menjadi pilihan bawaan Groovy menyimpan hasil unduhan pada ~/.groovy/grapes, sedangkan Groovy 5 juga menyediakan mesin alternatif GrapeMaven dengan cache ~/.groovy/grapesM2. Jalur penyimpanan dapat dipindahkan lewat properti sistem grape.root bila direktori home tidak cocok untuk cache. Perintah grape list di awal bagian ini membaca isi cache yang sama.
Kesalahan umum#
Dua kegagalan berikut terjadi pada tahap kompilasi, bukan saat skrip berjalan, karena anotasi Grape diproses oleh kompilator lebih dulu. Agar pesannya ringkas, kedua contoh mengevaluasi skrip yang bermasalah melalui GroovyShell, menangkap MultipleCompilationErrorsException, lalu mengekstrak bagian inti pesannya dengan ekspresi reguler (regular expression).
Kegagalan pertama adalah koordinat yang salah, misalnya versi 9.9 yang tidak pernah diterbitkan. Pesan errornya berupa Error grabbing Grapes beserta rincian dependensi yang tidak teratasi, dan kegagalan ini muncul sebelum satu baris kode pun dieksekusi.
import org.codehaus.groovy.control.MultipleCompilationErrorsException
def skrip = '''
@Grab('org.apache.commons:commons-math3:9.9')
import java.math.BigDecimal
println BigDecimal.ONE
'''
try {
new GroovyShell().evaluate(skrip)
} catch (MultipleCompilationErrorsException e) {
println "Gagal saat kompilasi: ${e.getClass().simpleName}"
def m = (e.message =~ /unresolved dependency: [^\]]+/)
if (m) println "Penyebab: ${m[0]}"
}Gagal saat kompilasi: MultipleCompilationErrorsException
Penyebab: unresolved dependency: org.apache.commons#commons-math3;9.9: not foundBagian unresolved dependency pada pesan menyebut koordinat yang gagal ditemukan di seluruh repositori yang terdaftar. Memperbaiki nomor versi atau nama artefaknya menyelesaikan kasus ini, dan versi yang benar untuk commons-math3 persis seperti yang dipakai contoh statistik sebelumnya, yaitu 3.6.1.
Kegagalan kedua adalah import yang ditulis tanpa anotasi @Grab pendampingnya. Karena tidak ada dependensi yang dipasang, kompilator tidak menemukan kelas pustakanya dan kompilasi berhenti dengan pesan unable to resolve class.
import org.codehaus.groovy.control.MultipleCompilationErrorsException
def skrip = '''
import org.apache.commons.lang3.StringUtils
println StringUtils.center('Grape', 7)
'''
try {
new GroovyShell().evaluate(skrip)
} catch (MultipleCompilationErrorsException e) {
println "Gagal saat kompilasi: ${e.getClass().simpleName}"
def m = (e.message =~ /unable to resolve class [^\n]+/)
if (m) println "Penyebab: ${m[0]}"
}Gagal saat kompilasi: MultipleCompilationErrorsException
Penyebab: unable to resolve class org.apache.commons.lang3.StringUtilsPesan tersebut menunjuk langsung ke kelas yang tidak dapat diselesaikan, sehingga penyebabnya mudah dilacak. Kebiasaan yang mencegah keduanya adalah menempatkan @Grab dan import berdampingan sejak baris pertama skrip, agar setiap pustaka yang diimpor selalu memiliki anotasi pendampingnya.
Ringkasan#
- Anotasi @Grab mendeklarasikan dependensi langsung di dalam skrip, dan Grape mengunduh jar beserta dependensi transitifnya dari Maven Central atau repositori @GrabResolver saat skrip pertama kali dikompilasi.
- Bentuk singkat
grup:artefak:versidan bentuk penuhgroup/module/versionmenghasilkan resolusi yang sama, beberapa anotasi boleh bertumpuk, dan versi eksplisit menjaga keluaran skrip tetap stabil. @GrabConfig(systemClassLoader=true)menempelkan dependensi driver JDBC ke system class loader, sedangkan alat grape mengelola cache~/.groovy/grapesmelalui perintah install, list, resolve, dan uninstall.- Koordinat yang salah dan import tanpa @Grab keduanya gagal saat kompilasi dengan
MultipleCompilationErrorsException.
Referensi#
- Dependency management with Grape — Apache Groovy — dokumentasi resmi Grape: bentuk anotasi, @GrabResolver, @GrabConfig, alat grape, dan rentang versi.
- Version matchers — Apache Ivy
— notasi pencocokan versi Ivy termasuk rentang
[1.0,2.0)yang dipakai Grape. - Apache Commons Lang — Apache Commons — dokumentasi pustaka StringUtils yang dipakai pada contoh pembersih teks di topik ini.