Bzlmod adalah nama kode sistem dependensi eksternal baru yang diperkenalkan di Bazel 5.0. Sistem ini diperkenalkan untuk mengatasi beberapa masalah pada sistem lama yang tidak dapat diperbaiki secara bertahap; lihat bagian Pernyataan Masalah dalam dokumen desain asli untuk mengetahui detail selengkapnya.
Di Bazel 5.0, Bzlmod tidak diaktifkan secara default; flag --experimental_enable_bzlmod harus ditentukan agar perubahan berikut diterapkan. Seperti yang ditunjukkan oleh nama flag, fitur ini saat ini masih dalam tahap eksperimental;
API dan perilaku dapat berubah hingga fitur ini diluncurkan secara resmi.
Untuk memigrasikan project Anda ke Bzlmod, ikuti Panduan Migrasi Bzlmod. Anda juga dapat menemukan contoh penggunaan Bzlmod di repositori contoh.
Modul Bazel
Sistem dependensi eksternal berbasis WORKSPACE lama berpusat pada repositori (atau repo), yang dibuat melalui aturan repositori (atau aturan repo).
Meskipun repo masih merupakan konsep penting dalam sistem baru, modul adalah unit inti dependensi.
Modul pada dasarnya 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. Dependensi kemudian dicari di
registry Bazel; secara default, Bazel Central Registry. Di ruang kerja Anda, setiap modul kemudian akan diubah menjadi repo.
MODULE.bazel
Setiap versi dari setiap modul memiliki file MODULE.bazel yang mendeklarasikan dependensi dan metadata lainnya. Berikut 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 perlu menentukan dependensi langsung, dan file MODULE.bazel dari 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. Direktif yang didukung file MODULE.bazel 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 digunakan sebagai dependensi) untuk menyesuaikan perilaku dependensi langsung atau transitif tertentu:
- Direktif terkait ekstensi modul:
Format versi
Bazel memiliki ekosistem yang beragam dan project menggunakan berbagai skema pembuatan versi. Yang
paling populer adalah SemVer, tetapi ada
juga project terkemuka yang menggunakan skema berbeda seperti
Abseil, yang
versinya berbasis tanggal, misalnya 20210324.2).
Oleh karena itu, Bzlmod mengadopsi versi spesifikasi SemVer yang lebih fleksibel. Perbedaannya meliputi:
- SemVer menetapkan bahwa bagian "rilis" dari versi harus terdiri dari 3 segmen:
MAJOR.MINOR.PATCH. Di Bazel, persyaratan ini dilonggarkan sehingga jumlah segmen apa pun diizinkan. - Di SemVer, setiap segmen di bagian "rilis" hanya boleh berupa digit. Di Bazel, persyaratan ini dilonggarkan sehingga huruf juga diizinkan, dan semantik perbandingan cocok dengan "pengenal" di bagian "prerilis".
- Selain itu, semantik peningkatan versi utama, versi minor, dan versi patch tidak diterapkan. (Namun, lihat tingkat kompatibilitas untuk detail tentang cara kami menunjukkan kompatibilitas mundur.)
Setiap versi SemVer yang valid adalah versi modul Bazel yang valid. Selain itu, dua
versi SemVer a dan b dibandingkan a < b jika hal yang sama berlaku saat dibandingkan sebagai versi modul Bazel.
Penyelesaian versi
Masalah dependensi berlian adalah hal yang umum dalam ruang pengelolaan dependensi versi. 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 menjawab pertanyaan ini, Bzlmod menggunakan Minimal Version Selection (MVS) algoritma yang diperkenalkan dalam sistem modul Go. MVS mengasumsikan bahwa semua versi baru modul kompatibel mundur, sehingga hanya memilih versi tertinggi yang ditentukan oleh dependensi apa pun (D 1.1 dalam contoh kami). Versi ini disebut "minimal" karena D 1.1 di sini adalah versi minimal yang dapat memenuhi persyaratan kami; meskipun D 1.2 atau yang lebih baru ada, kami tidak memilihnya. Hal ini memiliki manfaat tambahan bahwa pemilihan versi bersifat fidelity tinggi dan dapat direproduksi.
Penyelesaian versi dilakukan secara lokal di komputer Anda, bukan oleh registry.
Tingkat kompatibilitas
Perhatikan bahwa asumsi MVS tentang kompatibilitas mundur dapat dilakukan karena MVS hanya memperlakukan versi modul yang tidak kompatibel mundur sebagai modul terpisah. Dalam hal SemVer, hal ini berarti A 1.x dan A 2.x dianggap sebagai modul yang berbeda, dan dapat ada bersama dalam grafik dependensi yang diselesaikan. Hal ini dimungkinkan karena versi utama dienkode dalam jalur paket di Go, sehingga tidak ada konflik waktu kompilasi atau waktu penautan.
Di Bazel, kami tidak memiliki jaminan seperti itu. Oleh karena itu, kita memerlukan cara untuk menunjukkan nomor "versi utama" guna mendeteksi versi yang tidak kompatibel mundur. Nomor ini
disebut tingkat kompatibilitas, dan ditentukan oleh setiap versi modul dalam
direktif module(). Dengan informasi ini, kita dapat menampilkan error saat mendeteksi bahwa versi modul yang sama dengan tingkat kompatibilitas yang berbeda ada dalam grafik dependensi yang diselesaikan.
Nama repositori
Di Bazel, setiap dependensi eksternal memiliki nama repositori. Terkadang, dependensi yang sama
dapat digunakan melalui nama repositori yang berbeda (misalnya, keduanya
@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 dua konsep penting:
Nama repositori kanonis: Nama repositori unik global untuk setiap repositori. Nama ini akan menjadi nama direktori tempat repositori berada.
Nama ini dibuat sebagai berikut (Peringatan: format nama kanonis bukan API yang harus Anda andalkan, format ini dapat berubah kapan saja):- 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
BUILDdan.bzldalam repo. Dependensi yang sama dapat memiliki nama yang terlihat berbeda di repo yang berbeda.
Nama ini ditentukan sebagai berikut:
Setiap repositori memiliki kamus pemetaan repositori dari dependensi langsungnya, yang merupakan peta dari nama repositori yang terlihat ke nama repositori kanonis.
Kami menggunakan pemetaan repositori untuk menyelesaikan nama repositori saat membuat label. Perhatikan bahwa tidak ada konflik nama repositori kanonis, dan penggunaan nama repositori yang terlihat dapat ditemukan dengan mengurai file MODULE.bazel, sehingga konflik dapat dengan mudah ditangkap dan diselesaikan tanpa memengaruhi dependensi lainnya.
Dependensi ketat
Format spesifikasi dependensi baru memungkinkan kami 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 diimplementasikan berdasarkan pemetaan repositori. Pada dasarnya, pemetaan repositori untuk setiap repo berisi semua dependensi langsung, dan repositori lainnya tidak terlihat. Dependensi yang terlihat untuk setiap repositori ditentukan sebagai berikut:
- Repo modul Bazel dapat melihat semua repo yang diperkenalkan dalam file
MODULE.bazelmelaluibazel_depdanuse_repo. - Repo ekstensi modul dapat melihat semua dependensi yang terlihat dari modul yang menyediakan ekstensi, ditambah semua repo lain yang dibuat oleh ekstensi modul yang sama.
Registry
Bzlmod menemukan dependensi dengan meminta informasi dari registry 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 hanyalah repo 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 setiap versi, dan cara mengambil sumber setiap versi. Perlu diperhatikan bahwa registry indeks tidak perlu menayangkan arsip sumber itu sendiri.
Registry indeks harus mengikuti format di bawah:
/bazel_registry.json: File JSON yang berisi metadata untuk registry seperti:mirrors, yang menentukan daftar mirror yang akan digunakan untuk arsip sumber.module_base_path, yang menentukan jalur dasar untuk modul dengan jenislocal_repositorydalam filesource.json.
/modules: Direktori yang berisi subdirektori untuk setiap modul di 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 hal ini tidak selalu sama dengan penulis project.versions: Daftar semua versi modul ini yang akan ditemukan di registry ini.yanked_versions: Daftar versi modul ini yang ditarik. Saat ini, versi ini tidak beroperasi, tetapi di masa mendatang, versi yang ditarik akan dilewati atau menghasilkan error.
/modules/$MODULE/$VERSION: Direktori yang berisi file berikut:MODULE.bazel: FileMODULE.bazeldari versi modul ini.source.json: File JSON yang berisi informasi tentang cara mengambil sumber versi modul ini.- Jenis default-nya adalah "archive" dengan kolom berikut:
url: URL arsip sumber.integrity: Checksum Subresource Integrity dari arsip.strip_prefix: Awalan direktori yang akan dihapus saat mengekstrak arsip sumber.patches: Daftar string, yang masing-masing memberi nama file patch yang akan diterapkan ke arsip yang diekstrak. File patch berada di direktori/modules/$MODULE/$VERSION/patches.patch_strip: Sama dengan argumen--stripdari patch Unix.
- Jenisnya dapat diubah untuk menggunakan jalur lokal dengan kolom ini:
type:local_pathpath: Jalur lokal ke repo, yang dihitung sebagai berikut:- Jika jalur adalah jalur absolut, jalur akan digunakan sebagaimana adanya.
- Jika jalur adalah jalur relatif dan
module_base_pathadalah jalur absolut, jalur akan diselesaikan ke<module_base_path>/<path> - Jika jalur dan
module_base_pathadalah jalur relatif, jalur akan diselesaikan ke<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 default-nya adalah "archive" dengan kolom berikut:
patches/: Direktori opsional yang berisi file patch, hanya digunakan jikasource.jsonmemiliki jenis "archive".
Bazel Central Registry
Bazel Central Registry (BCR) adalah registry indeks yang terletak di
bcr.bazel.build. Isinya
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 di BCR.
Memilih registry
Flag Bazel --registry yang dapat diulang dapat digunakan untuk menentukan daftar registry tempat meminta modul, sehingga Anda dapat menyiapkan project untuk mengambil dependensi dari registry pihak ketiga atau internal. Registry sebelumnya akan diprioritaskan. Untuk memudahkan, Anda dapat menempatkan daftar flag --registry di file .bazelrc project Anda.
Ekstensi Modul
Ekstensi modul memungkinkan Anda memperluas sistem modul dengan membaca data input dari modul di seluruh grafik dependensi, melakukan logika yang diperlukan untuk menyelesaikan dependensi, dan akhirnya membuat repo dengan memanggil aturan repo. Fungsinya mirip dengan makro WORKSPACE saat ini, tetapi lebih cocok di dunia modul dan dependensi transitif.
Ekstensi modul ditentukan dalam file .bzl, seperti aturan repo atau makro WORKSPACE. Ekstensi modul tidak dipanggil secara langsung; melainkan, setiap modul dapat menentukan bagian data yang disebut tag untuk dibaca oleh ekstensi. Kemudian, setelah penyelesaian versi modul selesai, ekstensi modul akan dijalankan. Setiap ekstensi dijalankan satu kali setelah penyelesaian 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 masing-masing sebagai file MODULE.bazel. Setiap modul dapat menentukan beberapa tag untuk ekstensi modul; di sini beberapa tag ditentukan untuk ekstensi "maven", dan beberapa tag ditentukan untuk "cargo". Saat grafik dependensi ini diselesaikan (misalnya, mungkin B 1.2 sebenarnya memiliki bazel_dep di D 1.3, tetapi diupgrade ke D 1.4 karena C), ekstensi "maven" dijalankan, dan ekstensi tersebut dapat membaca semua tag maven.*, menggunakan informasi di dalamnya untuk memutuskan repo mana yang akan dibuat.
Hal yang sama berlaku untuk ekstensi "cargo".
Penggunaan ekstensi
Ekstensi dihosting di modul Bazel itu sendiri, jadi untuk menggunakan ekstensi di
modul Anda, Anda harus terlebih dahulu menambahkan bazel_dep di modul tersebut, lalu memanggil
fungsi bawaan use_extensionuntuk memasukkannya ke dalam cakupan. Perhatikan 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 repo yang ingin Anda gunakan di modul Anda, gunakan
use_repo direktif untuk mendeklarasikan
nya. Hal ini untuk memenuhi kondisi dependensi ketat dan menghindari konflik nama repo lokal.
use_repo(
maven,
"org_junit_junit",
guava="com_google_guava_guava",
)
Repo yang dibuat oleh ekstensi adalah bagian dari API-nya, sehingga dari tag yang Anda tentukan, Anda akan mengetahui bahwa ekstensi "maven" akan membuat repo bernama "org_junit_junit", dan satu repo bernama "com_google_guava_guava". Dengan use_repo, Anda dapat mengganti namanya secara opsional dalam cakupan modul Anda, seperti menjadi "guava" di sini.
Definisi ekstensi
Ekstensi modul ditentukan dengan cara yang sama seperti aturan repo, menggunakan fungsi
module_extension.
Keduanya memiliki fungsi implementasi; tetapi meskipun aturan repo memiliki sejumlah
atribut, ekstensi modul memiliki sejumlah
tag_classes, 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 menjelaskan bahwa tag maven.dep dan maven.pom dapat ditentukan, menggunakan skema atribut yang ditentukan di atas.
Fungsi implementasi mirip dengan makro WORKSPACE, kecuali bahwa fungsi ini
mendapatkan objek module_ctx, yang memberikan
akses ke grafik dependensi dan semua tag yang relevan. 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]
Dalam contoh di atas, kita membahas semua modul dalam grafik dependensi
(ctx.modules), yang masing-masing adalah objek
bazel_module yang kolom tags menampilkan semua tag maven.* pada modul. Kemudian, kita memanggil utilitas CLI Coursier untuk menghubungi Maven dan melakukan penyelesaian. Terakhir, kita menggunakan hasil penyelesaian untuk membuat sejumlah repo, menggunakan aturan repo maven_single_jar hipotetis.