Bzlmod adalah nama kode sistem dependensi eksternal baru yang diperkenalkan di Bazel 5.0. Fitur ini diperkenalkan untuk mengatasi beberapa poin masalah sistem lama yang tidak dapat diperbaiki secara bertahap; lihat bagian Pernyataan Masalah dalam dokumen desain asli untuk mengetahui detail selengkapnya.
Pada Bazel 5.0, Bzlmod tidak diaktifkan secara default; tanda
--experimental_enable_bzlmod
harus ditentukan agar hal berikut dapat
diterapkan. Seperti yang disarankan oleh nama tanda, fitur ini masih bersifat eksperimental;
API dan perilaku dapat berubah hingga fitur ini diluncurkan secara resmi.
Untuk memigrasikan project ke Bzlmod, ikuti Panduan Migrasi Bzlmod. Anda juga dapat menemukan contoh penggunaan Bzlmod di repositori examples.
Modul Bazel
Sistem dependensi eksternal berbasis WORKSPACE
lama berpusat pada
repositori (atau repo), yang dibuat melalui aturan repositori (atau aturan repo).
Meskipun repositori masih merupakan konsep penting dalam sistem baru, modul adalah
unit inti dependensi.
Pada dasarnya, modul adalah project Bazel yang dapat memiliki beberapa versi, yang masing-masing memublikasikan metadata tentang modul lain yang menjadi dependensinya. Hal ini analog dengan konsep yang sudah dikenal dalam sistem pengelolaan dependensi lainnya: artefak Maven, paket npm, crate Cargo, modul Go, dll.
Modul hanya menentukan dependensinya menggunakan pasangan name
dan version
,
bukan URL tertentu di WORKSPACE
. Kemudian, dependensi akan dicari di
registry Bazel; secara default,
Bazel Central Registry. Di ruang kerja Anda, setiap
modul kemudian akan diubah menjadi repo.
MODULE.bazel
Setiap versi setiap modul memiliki file MODULE.bazel
yang mendeklarasikan
dependensi dan metadata lainnya. Berikut adalah contoh dasarnya:
module(
name = "my-module",
version = "1.0",
)
bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")
File MODULE.bazel
harus berada di root direktori ruang kerja (di samping file WORKSPACE
). Tidak seperti file WORKSPACE
, Anda tidak perlu
menentukan dependensi transitif. Sebagai gantinya, Anda hanya boleh menentukan
dependensi direct, dan file MODULE.bazel
dependensi Anda
akan diproses untuk menemukan dependensi transitif secara otomatis.
File MODULE.bazel
mirip dengan file BUILD
karena tidak mendukung
bentuk alur kontrol apa pun; file ini juga melarang pernyataan load
. Dukungan file
MODULE.bazel
perintah adalah:
module
, untuk menentukan metadata tentang modul saat ini, termasuk nama, versi, dan sebagainya;bazel_dep
, untuk menentukan dependensi langsung pada modul Bazel lainnya;- Penggantian, yang hanya dapat digunakan oleh modul root (yaitu, bukan oleh modul yang sedang digunakan sebagai dependensi) untuk menyesuaikan perilaku dependensi langsung atau transitif tertentu:
- Perintah yang terkait dengan ekstensi modul:
Format versi
Bazel memiliki ekosistem yang beragam dan project menggunakan berbagai skema pembuatan versi. Yang
paling populer sejauh ini adalah SemVer, tetapi ada
juga project terkenal yang menggunakan skema yang berbeda seperti
Abseil, yang
versinya berbasis tanggal, misalnya 20210324.2
).
Karena alasan ini, Bzlmod menggunakan versi spesifikasi SemVer yang lebih santai. Perbedaannya meliputi:
- SemVer menetapkan bahwa bagian "rilis" dari versi harus terdiri dari 3 segmen:
MAJOR.MINOR.PATCH
. Pada Bazel, persyaratan ini dilonggarkan sehingga sejumlah segmen diizinkan. - Di SemVer, setiap segmen di bagian "rilis" hanya boleh berupa angka. Di Bazel, hal ini dilonggarkan untuk mengizinkan huruf juga, dan semantik perbandingan cocok dengan "ID" di bagian "pra-rilis".
- Selain itu, semantik peningkatan versi utama, minor, dan patch tidak diterapkan. (Namun, lihat tingkat kompatibilitas untuk mengetahui detail tentang cara kami menunjukkan kompatibilitas mundur.)
Semua versi SemVer yang valid adalah versi modul Bazel yang valid. Selain itu, dua
versi SemVer a
dan b
membandingkan a < b
jika hal yang sama berlaku saat
dibandingkan sebagai versi modul Bazel.
Resolusi versi
Masalah dependensi berlian adalah hal yang biasa di ruang pengelolaan dependensi berversi. Misalkan Anda memiliki grafik dependensi berikut:
A 1.0
/ \
B 1.0 C 1.1
| |
D 1.0 D 1.1
Versi D mana yang harus digunakan? Untuk menyelesaikan pertanyaan ini, Bzlmod menggunakan algoritma Minimal Version Selection (MVS) yang diperkenalkan dalam sistem modul Go. MVS mengasumsikan bahwa semua versi baru modul kompatibel dengan versi lama, sehingga hanya memilih versi tertinggi yang ditentukan oleh dependensi mana pun (D 1.1 dalam contoh kami). Versi ini disebut "minimal" karena D 1.1 di sini adalah versi minimal yang dapat memenuhi persyaratan kita; meskipun D 1.2 atau yang lebih baru ada, kita tidak akan memilihnya. Hal ini memiliki manfaat tambahan yaitu pilihan versi berakurasi tinggi dan dapat direproduksi.
Resolusi versi dilakukan secara lokal di komputer Anda, bukan oleh registry.
Tingkat kompatibilitas
Perhatikan bahwa asumsi MVS tentang kompatibilitas mundur dapat diterima karena hanya memperlakukan versi modul yang tidak kompatibel dengan versi sebelumnya sebagai modul terpisah. Dalam hal SemVer, artinya A 1.x dan A 2.x dianggap sebagai modul yang berbeda, dan dapat berdampingan dalam grafik dependensi yang di-resolve. Hal ini, pada gilirannya, memungkinkan karena fakta bahwa versi utama dienkode dalam jalur paket di Go, sehingga tidak ada konflik waktu kompilasi atau waktu penautan.
Di Bazel, kita tidak memiliki jaminan tersebut. Oleh karena itu, kita memerlukan cara untuk menunjukkan nomor "versi
utama" guna mendeteksi versi yang tidak kompatibel dengan versi sebelumnya. Angka ini
disebut tingkat kompatibilitas, dan ditentukan oleh setiap versi modul dalam
direktif module()
-nya. Dengan informasi ini, kita dapat menampilkan error
saat mendeteksi bahwa versi modul yang sama dengan tingkat kompatibilitas
yang berbeda ada dalam grafik dependensi yang di-resolve.
Nama repositori
Di Bazel, setiap dependensi eksternal memiliki nama repositori. Terkadang, dependensi
yang sama dapat digunakan melalui nama repositori yang berbeda (misalnya, @io_bazel_skylib
dan @bazel_skylib
berarti
Bazel skylib), atau nama repositori
yang sama dapat digunakan untuk dependensi yang berbeda dalam project yang berbeda.
Di Bzlmod, repositori dapat dibuat oleh modul Bazel dan ekstensi modul. Untuk mengatasi konflik nama repositori, kami menggunakan mekanisme pemetaan repositori di sistem baru. Berikut adalah dua konsep penting:
Nama repositori kanonis: Nama repositori yang unik secara global untuk setiap repositori. Nama ini akan menjadi nama direktori tempat repositori berada.
Format ini dibuat sebagai berikut (Peringatan: format nama kanonis bukan API yang harus Anda andalkan, format ini dapat berubah sewaktu-waktu):- Untuk repo modul Bazel:
module_name~version
(Contoh.@bazel_skylib~1.0.3
) - Untuk repo ekstensi modul:
module_name~version~extension_name~repo_name
(Contoh.@rules_cc~0.0.1~cc_configure~local_config_cc
)
- Untuk repo modul Bazel:
Nama repositori yang terlihat: Nama repositori yang akan digunakan dalam file
BUILD
dan.bzl
dalam repo. Dependensi yang sama dapat memiliki nama yang tampak berbeda di repo yang berbeda.
Nilainya ditentukan sebagai berikut:
Setiap repositori memiliki kamus pemetaan repositori dari dependensi langsungnya,
yang merupakan peta dari nama repositori yang jelas ke nama repositori kanonis.
Kami menggunakan pemetaan repositori untuk me-resolve nama repositori saat membuat label. Perlu diperhatikan bahwa, tidak ada konflik nama repositori kanonis, dan
penggunaan nama repositori yang jelas dapat ditemukan dengan mengurai file MODULE.bazel
, sehingga konflik dapat dengan mudah ditemukan dan diselesaikan tanpa memengaruhi
dependensi lainnya.
Dependensi ketat
Format spesifikasi dependensi baru memungkinkan kita melakukan pemeriksaan yang lebih ketat. Secara khusus, kami sekarang menerapkan bahwa modul hanya dapat menggunakan repo yang dibuat dari dependensi langsungnya. Hal ini membantu mencegah kerusakan yang tidak disengaja dan sulit di-debug saat ada perubahan pada grafik dependensi transitif.
Dependensi ketat diterapkan berdasarkan pemetaan repositori. Pada dasarnya, pemetaan repositori untuk setiap repo berisi semua dependensi langsung-nya, repositori lain tidak terlihat. Dependensi yang terlihat untuk setiap repositori ditentukan sebagai berikut:
- Repositori modul Bazel dapat melihat semua repositori yang diperkenalkan dalam file
MODULE.bazel
melaluibazel_dep
danuse_repo
. - Repositori ekstensi modul dapat melihat semua dependensi modul yang terlihat dan menyediakan ekstensi, serta semua repositori lain yang dihasilkan oleh ekstensi modul yang sama.
Registry
Bzlmod menemukan dependensi dengan meminta informasinya dari registri Bazel. Registry Bazel hanyalah database modul Bazel. Satu-satunya bentuk registry yang didukung adalah registry indeks, yang merupakan direktori lokal atau server HTTP statis yang mengikuti format tertentu. Di masa mendatang, kami berencana menambahkan dukungan untuk registry modul tunggal, yang merupakan repositori git yang berisi sumber dan histori project.
Registry indeks
Registry indeks adalah direktori lokal atau server HTTP statis yang berisi
informasi tentang daftar modul, termasuk halaman beranda, pengelola, file MODULE.bazel
dari setiap versi, dan cara mengambil sumber dari setiap
versi. Secara khusus, server ini tidak perlu menayangkan arsip sumber itu sendiri.
Registry indeks harus mengikuti format di bawah ini:
/bazel_registry.json
: File JSON yang berisi metadata untuk registry seperti:mirrors
, yang menentukan daftar duplikat yang akan digunakan untuk arsip sumber.module_base_path
, yang menentukan jalur dasar untuk modul dengan jenislocal_repository
dalam filesource.json
.
/modules
: Direktori yang berisi subdirektori untuk setiap modul dalam registry ini./modules/$MODULE
: Direktori yang berisi subdirektori untuk setiap versi modul ini, serta file berikut:metadata.json
: File JSON yang berisi informasi tentang modul, dengan kolom berikut:homepage
: URL halaman beranda project.maintainers
: Daftar objek JSON, yang masing-masing sesuai dengan informasi pengelola modul di registry. Perhatikan bahwa ini tidak selalu sama dengan penulis project.versions
: Daftar semua versi modul ini yang dapat ditemukan di registry ini.yanked_versions
: Daftar versi yang ditarik dari modul ini. Hal ini saat ini tidak berfungsi, tetapi pada masa mendatang, versi yang ditarik akan dilewatkan atau menghasilkan error.
/modules/$MODULE/$VERSION
: Direktori yang berisi file berikut:MODULE.bazel
: FileMODULE.bazel
versi modul ini.source.json
: File JSON yang berisi informasi tentang cara mengambil sumber versi modul ini.- Jenis defaultnya adalah "arsip" dengan kolom berikut:
url
: URL arsip sumber.integrity
: Checksum Integrity Subresource dari arsip.strip_prefix
: Awalan direktori yang akan dihapus saat mengekstrak arsip sumber.patches
: Daftar string, yang masing-masing menamai file patch untuk diterapkan ke arsip yang diekstrak. File patch terletak di direktori/modules/$MODULE/$VERSION/patches
.patch_strip
: Sama seperti argumen--strip
patch Unix.
- Jenis ini dapat diubah untuk menggunakan jalur lokal dengan kolom berikut:
type
:local_path
path
: Jalur lokal ke repo, yang dihitung sebagai berikut:- Jika jalur adalah jalur absolut, jalur tersebut akan digunakan apa adanya.
- Jika jalur adalah jalur relatif dan
module_base_path
adalah jalur absolut, jalur akan di-resolve ke<module_base_path>/<path>
- Jika jalur dan
module_base_path
merupakan jalur relatif, jalur di-resolve menjadi<registry_path>/<module_base_path>/<path>
. Registry harus dihosting secara lokal dan digunakan oleh--registry=file://<registry_path>
. Jika tidak, Bazel akan menampilkan error.
- Jenis defaultnya adalah "arsip" dengan kolom berikut:
patches/
: Direktori opsional yang berisi file patch, hanya digunakan jikasource.json
memiliki jenis "archive".
Bazel Central Registry
Bazel Central Registry (BCR) adalah registry indeks yang terletak di
bcr.bazel.build. Kontennya
didukung oleh repo GitHub
bazelbuild/bazel-central-registry
.
BCR dikelola oleh komunitas Bazel; kontributor dapat mengirimkan permintaan pull. Lihat Kebijakan dan Prosedur Bazel Central Registry.
Selain mengikuti format registry indeks normal, BCR memerlukan
file presubmit.yml
untuk setiap versi modul
(/modules/$MODULE/$VERSION/presubmit.yml
). File ini menentukan beberapa target
build dan pengujian penting yang dapat digunakan untuk memeriksa validitas versi modul
ini, dan digunakan oleh pipeline CI BCR untuk memastikan interoperabilitas
antar-modul dalam BCR.
Memilih registry
Flag Bazel --registry
yang dapat diulang dapat digunakan untuk menentukan daftar
registri tempat modul diminta, sehingga Anda dapat menyiapkan project untuk mengambil
dependensi dari registry internal atau pihak ketiga. Registry sebelumnya lebih
diprioritaskan. Untuk memudahkan, Anda dapat menempatkan daftar flag --registry
dalam file .bazelrc
project Anda.
Ekstensi Modul
Ekstensi modul memungkinkan Anda memperluas sistem modul dengan membaca data input
dari modul di seluruh grafik dependensi, menjalankan logika yang diperlukan untuk me-resolve
dependensi, dan akhirnya membuat repo dengan memanggil aturan repo. Fungsinya mirip dengan makro WORKSPACE
saat ini, tetapi lebih cocok untuk modul dan dependensi transitif.
Ekstensi modul ditentukan dalam file .bzl
, seperti aturan repo atau
makro WORKSPACE
. Fungsi ini tidak dipanggil secara langsung; setiap modul dapat
menentukan potongan data yang disebut tag untuk dibaca oleh ekstensi. Kemudian, setelah resolusi versi modul selesai, ekstensi modul akan dijalankan. Setiap ekstensi dijalankan
satu kali setelah resolusi modul (masih sebelum build benar-benar terjadi), dan
dapat membaca semua tag yang dimilikinya di seluruh grafik dependensi.
[ A 1.1 ]
[ * maven.dep(X 2.1) ]
[ * maven.pom(...) ]
/ \
bazel_dep / \ bazel_dep
/ \
[ B 1.2 ] [ C 1.0 ]
[ * maven.dep(X 1.2) ] [ * maven.dep(X 2.1) ]
[ * maven.dep(Y 1.3) ] [ * cargo.dep(P 1.1) ]
\ /
bazel_dep \ / bazel_dep
\ /
[ D 1.4 ]
[ * maven.dep(Z 1.4) ]
[ * cargo.dep(Q 1.1) ]
Dalam contoh grafik dependensi di atas, A 1.1
dan B 1.2
, dll. adalah modul Bazel;
Anda dapat menganggap setiap modul sebagai file MODULE.bazel
. Setiap modul dapat menentukan beberapa tag untuk ekstensi modul; di sini beberapa ditentukan untuk ekstensi "maven", dan beberapa ditentukan untuk "cargo". Saat grafik dependensi ini selesai (misalnya, mungkin B 1.2
sebenarnya memiliki bazel_dep
di D 1.3
, tetapi diupgrade ke
D 1.4
karena C
), ekstensi "maven" akan dijalankan, dan dapat membaca semua
tag maven.*
, menggunakan informasi di dalamnya untuk menentukan repo mana yang akan dibuat.
Demikian pula untuk ekstensi "cargo".
Penggunaan ekstensi
Ekstensi dihosting di modul Bazel itu sendiri, jadi untuk menggunakan ekstensi di
modul, Anda harus menambahkan bazel_dep
terlebih dahulu di modul tersebut, lalu memanggil
fungsi bawaan use_extension
untuk memasukkannya ke dalam cakupan. Pertimbangkan contoh berikut, cuplikan dari
file MODULE.bazel
untuk menggunakan ekstensi "maven" hipotetis yang ditentukan dalam
modul rules_jvm_external
:
bazel_dep(name = "rules_jvm_external", version = "1.0")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Setelah memasukkan ekstensi ke dalam cakupan, Anda dapat menggunakan sintaksis titik untuk menentukan tag untuk ekstensi tersebut. Perhatikan bahwa tag harus mengikuti skema yang ditentukan oleh
class tag yang sesuai (lihat definisi ekstensi
di bawah). Berikut adalah contoh yang menentukan beberapa tag maven.dep
dan maven.pom
.
maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")
Jika ekstensi menghasilkan repositori yang ingin Anda gunakan dalam modul, gunakan
perintah use_repo
untuk mendeklarasikannya. Hal ini untuk memenuhi kondisi dependensi yang ketat dan menghindari konflik nama repo lokal.
use_repo(
maven,
"org_junit_junit",
guava="com_google_guava_guava",
)
Repositori yang dihasilkan oleh ekstensi adalah bagian dari API-nya. Jadi, dari tag yang Anda tentukan, Anda harus mengetahui bahwa ekstensi "maven" akan menghasilkan repositori yang disebut "org_junit_junit", dan yang disebut "com_google_guava_guava". Dengan
use_repo
, Anda dapat mengganti namanya secara opsional dalam cakupan modul, seperti
"guava" di sini.
Definisi ekstensi
Ekstensi modul ditentukan mirip dengan aturan repo, menggunakan
fungsi module_extension
.
Keduanya memiliki fungsi implementasi; tetapi meskipun aturan repo memiliki sejumlah
atribut, ekstensi modul memiliki sejumlah
tag_class
, yang masing-masing memiliki
sejumlah atribut. Class tag menentukan skema untuk tag yang digunakan oleh ekstensi ini. Melanjutkan contoh ekstensi "maven" hipotetis di atas:
# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
implementation=_maven_impl,
tag_classes={"dep": maven_dep, "pom": maven_pom},
)
Deklarasi ini memperjelas bahwa tag maven.dep
dan maven.pom
dapat
ditentukan, menggunakan skema atribut yang ditentukan di atas.
Fungsi implementasi ini mirip dengan makro WORKSPACE
, kecuali bahwa
fungsi tersebut mendapatkan objek module_ctx
, yang memberikan
akses ke grafik dependensi dan semua tag terkait. Fungsi
implementasi kemudian harus memanggil aturan repo untuk membuat repo:
# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
coords = []
for mod in ctx.modules:
coords += [dep.coord for dep in mod.tags.dep]
output = ctx.execute(["coursier", "resolve", coords]) # hypothetical call
repo_attrs = process_coursier(output)
[maven_single_jar(**attrs) for attrs in repo_attrs]
Pada contoh di atas, kita membahas semua modul dalam grafik dependensi
(ctx.modules
), yang masing-masing adalah
objek bazel_module
yang kolom tags
-nya
mengekspos semua tag maven.*
pada modul. Kemudian kita memanggil utilitas CLI
Coursier untuk menghubungi Maven dan melakukan resolusi. Terakhir, kita menggunakan hasil
resolusi untuk membuat sejumlah repo, menggunakan aturan repo
maven_single_jar
hipotetis.