Bundler

Menetapkan dependensi pada Gemfile dan menjalankan proyek dengan bundle exec.

Daftar isi

Proyek yang nyata hampir selalu menggunakan lebih dari satu gem, dan setiap gem menuntut rentang versi yang berbeda-beda. Bundler menyelesaikan hal itu melalui satu berkas Gemfile yang memuat daftar dependensi proyek, perintah bundle install yang memasangnya, berkas Gemfile.lock yang mengunci versi terpilih, serta perintah bundle exec yang menjalankan program pada konteks dependensi terkunci. Keempatnya dipraktikkan pada proyek kecil dengan satu gem bernama colored.

Gemfile sebagai daftar dependensi#

Gemfile adalah berkas yang ditulis dengan sintaks Ruby dan berisi daftar dependensi sebuah proyek beserta sumber gemnya. Baris source mendeklarasikan katalog tempat gem diunduh, sedangkan setiap deklarasi gem menyebut nama gem dan rentang versinya. Proyek pada topik ini hanya menggunakan colored, sehingga Gemfile-nya pendek, sementara proyek yang besar dapat memuat belasan deklarasi gem yang saling terkait.

text Gemfile
source "https://rubygems.org"

gem "colored", "~> 1.2"

Gemfile diletakkan pada akar direktori proyek dan dibaca oleh seluruh perintah bundle. Rentang versi ~> 1.2 menyatakan tuntutan yang longgar, dan maknanya dibahas lebih rinci pada bagian akhir topik ini.

Memasang dependensi dengan bundle install#

Perintah bundle install membaca Gemfile, menyelesaikan seluruh rentang versinya, lalu memastikan setiap gem yang dipilih tersedia pada direktori gem.

bash
# memasang seluruh dependensi yang tercantum pada Gemfile
bundle install
text Keluaran
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Bundle complete! 1 Gemfile dependency, 2 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

Baris pertama keluaran menunjukkan metadata gem dikumpulkan dari rubygems.org untuk mencari versi yang memenuhi Gemfile, sedangkan baris Resolving dependencies menandakan tahap pemilihan versi sedang berlangsung. Tidak ada baris Installing pada keluaran ini karena colored 1.2 sudah ada di direktori gem, sehingga Bundler cukup menggunakan yang tersedia. Apabila gemnya belum pernah terpasang, keluaran yang sama menambahkan baris Installing yang memasangnya terlebih dahulu. Baris terakhir menyarankan perintah bundle info untuk menelusuri lokasi sebuah gem.

Gemfile.lock sebagai kunci versi#

Setelah resolusi selesai, Bundler menuliskan hasilnya pada berkas Gemfile.lock di direktori proyek. Gemfile menyatakan rentang versi yang dituntut, sedangkan Gemfile.lock mencatat versi persis yang terpilih, sehingga perintah berikutnya tidak perlu menyelesaikan ulang apa pun. Berkas ini juga mencatat sumber gem, platform, dan versi Bundler yang membuatnya.

text Cuplikan isi Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    colored (1.2)

DEPENDENCIES
  colored (~> 1.2)

BUNDLED WITH
   2.6.9

Isi Gemfile.lock di atas memperlihatkan perbedaan peran kedua berkas. Bagian DEPENDENCIES menyalin rentang dari Gemfile, sedangkan bagian specs mencatat versi persis colored (1.2) yang terpilih. Karena versinya terkunci, anggota tim yang lain maupun lingkungan deploy (tempat program dijalankan bagi pengguna) memperoleh versi gem yang sama persis selama Gemfile.lock ikut dibagikan bersama kode proyek.

bash
# menampilkan gem yang termuat pada bundle
bundle list
text Keluaran
Gems included by the bundle:
  * colored (1.2)
Use `bundle info` to print more detailed information about a gem

Perintah bundle list membaca Gemfile.lock, bukan Gemfile, sehingga daftar yang tampil adalah isi bundle yang terkunci. Perubahan rentang versi pada Gemfile baru berdampak setelah bundle install dijalankan ulang dan berkas kuncinya diperbarui.

Menjalankan program dengan bundle exec#

Perintah bundle exec menjalankan sebuah program setelah menyiapkan konteks gem sesuai Gemfile.lock. Program proyek berikut memuat colored dan melaporkan versi yang aktif. Program ini dijalankan dua kali, yaitu langsung dengan ruby dan melalui bundle exec, untuk membandingkan keluaran keduanya.

ruby salam.rb
# salam.rb: program proyek yang dijalankan pada konteks bundle
require "colored"

versi = Gem.loaded_specs["colored"].version
puts "Gem colored versi #{versi} tersedia di dalam bundle"
text Keluaran
Gem colored versi 1.2 tersedia di dalam bundle
bash
# menjalankan program pada konteks dependensi terkunci
bundle exec ruby salam.rb
text Keluaran
Gem colored versi 1.2 tersedia di dalam bundle

Kedua cara menjalankan di atas menghasilkan keluaran yang sama pada lingkungan ini, tetapi jaminannya berbeda. Tanpa bundle exec, program menggunakan load path bawaan, yaitu daftar direktori tempat Ruby mencari pustaka. Versi gem yang dimuat mengikuti direktori gem global dan tidak dijamin sama dengan Gemfile.lock. Dengan bundle exec, load path disusun dari berkas kunci sehingga require selalu menemukan versi yang sama pada komputer mana pun. Karena itu bundle exec menjadi cara baku menjalankan program Ruby pada proyek yang dependensinya dikelola Bundler.

Operator ~> dan versi semantik#

Nomor versi gem mengikuti semantic versioning, yaitu konvensi penomoran tiga tingkat mayor.minor.patch. Versi mayor naik ketika terdapat perubahan yang tidak kompatibel, versi minor naik untuk fitur baru yang tetap kompatibel, dan versi patch naik untuk perbaikan tanpa perubahan perilaku. Operator ~> menyatakan rentang yang hanya boleh naik pada satu tingkat terakhir.

text Padanan rentang operator ~>
gem "colored", "~> 1.2"    # 1.2 atau lebih baru, tetapi di bawah 2.0
gem "colored", "~> 1.2.3"  # 1.2.3 atau lebih baru, tetapi di bawah 1.3.0

Kedua bentuk di atas berbeda dalam tingkat keketatannya. Bentuk ~> 1.2 mengunci versi mayor sehingga pembaruan minor masih diterima, sedangkan bentuk ~> 1.2.3 mengunci mayor dan minor sehingga hanya perbaikan patch yang diterima. Aturan praktisnya adalah memilih rentang seluas yang masih diyakini kompatibel, dan rentang dua tingkat seperti ~> 1.2 lazim digunakan untuk gem kecil yang stabil.

Kesalahan umum#

Kesalahan pertama adalah menjalankan perintah bundle pada direktori yang tidak memuat Gemfile. Bundler membaca Gemfile dari direktori saat ini, sehingga perintahnya berhenti dengan pesan error Could not locate Gemfile dan tidak ada proses yang berjalan. Perbaikannya memindahkan direktori kerja ke akar proyek terlebih dahulu sebelum mengulang perintahnya.

Kesalahan kedua adalah menambahkan deklarasi gem baru pada Gemfile tanpa menjalankan bundle install. Pada keadaan itu bundle exec berhenti dengan pesan error yang meminta bundle install dijalankan, karena Gemfile.lock belum mencakup gem yang baru. Jalankan bundle install setiap kali Gemfile berubah, lalu periksa perubahan versi yang tercatat pada Gemfile.lock.

Kesalahan ketiga adalah menyunting Gemfile.lock secara langsung demi mengganti satu nomor versi. Bundler menghitung ulang isi berkas kunci tersebut sehingga suntingan hilang atau berkas dinyatakan tidak cocok dengan Gemfile. Perbaikannya sesuai aturan pada bagian kunci versi, yaitu mengubah rentang pada Gemfile dan menjalankan ulang bundle install.

Ringkasan#

  • Gemfile memuat daftar dependensi proyek dengan baris source dan deklarasi gem “nama”, “~> versi”.
  • bundle install menyelesaikan seluruh rentang versi, sedangkan Gemfile.lock mengunci versi persis hasil resolusinya.
  • bundle exec menjalankan program dengan load path sesuai Gemfile.lock sehingga versi gem yang dimuat selalu sama.
  • Operator ~> menyatakan rentang versi semantik, misalnya ~> 1.2 berarti di bawah 2.0 dan ~> 1.2.3 berarti di bawah 1.3.0.

Referensi#