Bzlmod adalah namakode sistem dependensi eksternal baru yang diperkenalkan di Bazel 5.0. Metode ini diperkenalkan untuk mengatasi beberapa masalah di sistem lama yang tidak dapat diperbaiki secara bertahap; lihat bagian Pernyataan Masalah pada dokumen desain asli untuk mengetahui detail selengkapnya.
Di Bazel 5.0, Bzlmod tidak diaktifkan secara default; flag
--experimental_enable_bzlmod
harus ditentukan agar hal berikut dapat
diterapkan. Seperti yang ditunjukkan oleh nama flag, fitur ini saat ini masih eksperimental;
API dan perilaku dapat berubah hingga fitur resmi diluncurkan.
Modul Bazel
Sistem dependensi eksternal berbasis WORKSPACE
yang lama berpusat di
repositori (atau repos), yang dibuat melalui aturan repositori (atau aturan repositori).
Meskipun repo masih menjadi 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. Ini adalah analog dengan konsep umum di sistem pengelolaan dependensi lainnya: artefak Maven, paket npm, peti Kargo, modul Go, dll.
Modul hanya menentukan dependensinya menggunakan pasangan name
dan version
,
bukan URL tertentu dalam WORKSPACE
. Kemudian, dependensi tersebut akan dicari
di registry Bazel; secara default,
Bazel Central Registry. Di ruang kerja Anda, setiap
modul kemudian diubah menjadi repo.
sederhana.bazel
Setiap versi setiap modul memiliki file MODULE.bazel
yang mendeklarasikan
dependensi dan metadata lainnya. Berikut adalah contoh dasar:
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
). Berbeda dengan file WORKSPACE
, Anda tidak perlu
menentukan dependensi transitif; Sebagai gantinya, sebaiknya hanya tentukan
dependensi langsung, 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; 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 (bukan oleh modul yang digunakan sebagai dependensi) untuk menyesuaikan perilaku dependensi langsung atau transitif tertentu:
- Perintah yang terkait dengan ekstensi modul:
Format versi
Bazel memiliki beragam ekosistem dan project menggunakan berbagai skema pembuatan versi. Yang
paling populer sejauh ini adalah SemVer, tetapi ada
juga project penting yang menggunakan skema berbeda seperti
Abseil, yang
versinya berbasis tanggal, misalnya 20210324.2
).
Karena alasan ini, Bzlmod menggunakan versi spesifikasi SemVer yang lebih longgar, khususnya
yang memungkinkan sejumlah urutan digit pada bagian "rilis"
versi (bukan tepat 3 seperti yang ditentukan oleh SemVer: MAJOR.MINOR.PATCH
).
Selain itu, semantik peningkatan versi utama, minor, dan patch tidak
diterapkan. (Namun, lihat tingkat kompatibilitas untuk mengetahui detail
tentang cara kami menunjukkan kompatibilitas mundur.) Bagian lain dari spesifikasi SemVer, seperti
tanda hubung yang menunjukkan versi prarilis, tidak diubah.
Resolusi versi
Masalah dependensi diamond adalah makanan pokok 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 menjawab pertanyaan ini, Bzlmod menggunakan algoritme Pemilihan Versi Minimal (MVS) yang diperkenalkan dalam sistem modul Go. MVS mengasumsikan bahwa semua versi baru dari modul kompatibel dengan versi lama, dan dengan demikian memilih versi tertinggi yang ditentukan oleh dependensi apa pun (contoh D 1.1). 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 pilihan versi adalah high-fidelity dan dapat direproduksi.
Resolusi versi dilakukan secara lokal di perangkat Anda, bukan di registry.
Tingkat kompatibilitas
Perlu diketahui bahwa asumsi MVS tentang kompatibilitas mundur dapat dilakukan karena hanya memperlakukan versi lama yang tidak kompatibel dengan modul sebagai modul terpisah. Dalam hal SemVer, artinya A 1.x dan A 2.x dianggap sebagai modul berbeda, dan dapat beroperasi berdampingan dalam grafik dependensi yang telah diselesaikan. Hal ini, pada akhirnya, dimungkinkan oleh fakta bahwa versi utama dienkode dalam jalur paket di Go, sehingga tidak ada konflik waktu kompilasi atau waktu penautan.
Di Bazel, kami tidak memiliki jaminan tersebut. Oleh karena itu, kita memerlukan cara untuk menunjukkan nomor "versi
utama" untuk mendeteksi versi yang tidak kompatibel dengan versi sebelumnya. Angka ini
disebut tingkat kompatibilitas, dan ditentukan oleh setiap versi modul dalam
perintah module()
. Dengan informasi ini, kita dapat menampilkan error
saat mendeteksi bahwa versi modul yang sama dengan level kompatibilitas
yang berbeda ada di 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, @io_bazel_skylib
dan @bazel_skylib
berarti
Bazel skylib), atau
nama repositori 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 memanfaatkan mekanisme pemetaan repositori di sistem baru. Berikut adalah dua konsep penting:
Nama repositori kanonis: Nama repositori unik secara global untuk setiap repositori. Ini akan menjadi nama direktori tempat penyimpanan berada.
Teks ini dibuat sebagai berikut (Peringatan: format nama kanonis bukanlah API yang harus Anda andalkan, tetapi dapat berubah kapan saja):- Untuk repositori modul Bazel:
module_name.version
(Contoh.@bazel_skylib.1.0.3
) - Repositori ekstensi modul:
module_name.version.extension_name.repo_name
(Contoh.@rules_cc.0.0.1.cc_configure.local_config_cc
)
- Untuk repositori modul Bazel:
Nama repositori lokal: Nama repositori yang akan digunakan dalam file
BUILD
dan.bzl
dalam repo. Dependensi yang sama dapat memiliki nama lokal yang berbeda untuk repositori yang berbeda.
Menentukannya sebagai berikut:
Setiap repositori memiliki kamus pemetaan repositori dari dependensi langsungnya, yang merupakan peta dari nama repositori lokal 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 lokal dapat ditemukan dengan menguraikan file MODULE.bazel
,
sehingga konflik dapat dengan mudah ditangkap dan diselesaikan tanpa memengaruhi
dependensi lain.
dependensi ketat
Format spesifikasi dependensi baru memungkinkan kami melakukan pemeriksaan yang lebih ketat. Secara khusus, sekarang kita memberlakukan bahwa modul hanya dapat menggunakan repo yang dibuat dari dependensi langsungnya. Hal ini membantu mencegah kerusakan yang tidak disengaja dan sulit di-debug saat sesuatu dalam grafik dependensi transitif berubah.
Dependensi ketat diterapkan berdasarkan pemetaan repositori. Pada dasarnya, pemetaan repositori untuk setiap repo berisi semua dependensi langsung, dan 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 yang menyediakan ekstensi, serta semua repositori lain yang dibuat 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 index registry, yang merupakan direktori lokal atau server HTTP statis dengan format tertentu. Di masa mendatang, kami berencana menambahkan dukungan untuk registry modul tunggal, yang merupakan git repo 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, pengelolanya, file MODULE.bazel
setiap versi, dan cara mengambil sumber setiap modul
versi. Secara khusus, file tidak perlu menampilkan arsip sumber itu sendiri.
Registry indeks harus mengikuti format berikut:
/bazel_registry.json
: File JSON yang berisi metadata untuk registry. Saat ini, repositori ini hanya memiliki satu kunci,mirrors
, yang menentukan daftar pencerminan yang akan digunakan untuk arsip sumber./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 dapat ditemukan di registry ini.yanked_versions
: Daftar versi yanked modul ini. Saat ini, tindakan ini belum dilakukan. Namun, di masa mendatang, versi yang dilewati akan dilewati atau menyebabkan error.
/modules/$MODULE/$VERSION
: Direktori yang berisi file berikut:MODULE.bazel
: FileMODULE.bazel
dari versi modul ini.source.json
: File JSON yang berisi informasi tentang cara mengambil sumber versi modul ini, dengan kolom berikut:url
: URL arsip sumber.integrity
: Checksum Subresource Integrity dalam arsip.strip_prefix
: Awalan direktori yang akan dihapus saat mengekstrak arsip sumber.patches
: Daftar string, yang masing-masing memberi nama file patch untuk diterapkan ke arsip yang diekstrak. File patch berada di bagian direktori/modules/$MODULE/$VERSION/patches
.patch_strip
: Sama seperti argumen--strip
patch Unix.
patches/
: Direktori opsional yang berisi file patch.
Registry Pusat Bazel
Bazel Central Registry (BCR) adalah registry indeks yang terletak di
registry.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 Central Registry Bazel.
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
antarmodul dalam BCR Domain.
Memilih registry
Flag Bazel yang dapat diulang --registry
dapat digunakan untuk menentukan
daftar registry yang akan diminta oleh modul, sehingga Anda dapat menyiapkan project untuk mengambil
dependensi dari registry pihak ketiga atau internal. Registry sebelumnya
diutamakan. 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 menyelesaikan
dependensi, dan terakhir membuat repo dengan memanggil aturan repo. Keduanya mirip
dengan makro WORKSPACE
saat ini, tetapi lebih cocok
dalam dunia modul dan dependensi transitif.
Ekstensi modul ditentukan dalam file .bzl
, seperti aturan repo atau makro WORKSPACE
. Keduanya tidak dipanggil secara langsung; sebaliknya, setiap modul dapat menentukan bagian data yang disebut tag untuk dibaca oleh ekstensi. Kemudian, setelah resolusi versi
modul selesai, ekstensi modul akan dijalankan. Setiap ekstensi dijalankan sekali setelah resolusi modul (masih sebelum build benar-benar terjadi), dan akan membaca semua tag yang ada 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 ditetapkan untuk ekstensi "maven", dan beberapa ditetapkan untuk "kargo". Saat grafik dependensi ini diselesaikan (misalnya, mungkin B 1.2
sebenarnya memiliki bazel_dep
di D 1.3
, tetapi diupgrade menjadi D 1.4
karena C
), ekstensi "maven" dijalankan, dan membaca semua
tag maven.*
, menggunakan informasi di dalamnya untuk memutuskan repositori mana yang akan dibuat.
Demikian pula untuk ekstensi "kargo".
Penggunaan ekstensi
Ekstensi dihosting di modul Bazel sendiri, jadi untuk menggunakan ekstensi di
modul, Anda harus menambahkan bazel_dep
di modul tersebut terlebih dahulu, lalu memanggil
use_extension
fungsi bawaan
untuk memasukkannya ke dalam cakupan. Perhatikan contoh berikut, cuplikan dari
file MODULE.bazel
untuk menggunakan ekstensi hipotetis "maven" yang didefinisikan 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 kemudian dapat menggunakan sintaks titik untuk menentukan tag. Perhatikan bahwa tag harus mengikuti skema yang ditentukan oleh class tag yang terkait (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 digunakan 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 merupakan bagian dari API-nya, sehingga dari tag yang
Anda tentukan, Anda harus mengetahui bahwa ekstensi "maven" akan menghasilkan
repo yang disebut "org_junit_junit", dan ekstensi 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 seperti aturan repo, menggunakan
fungsi module_extension
.
Keduanya memiliki fungsi implementasi; Meskipun aturan repo memiliki sejumlah
atribut, ekstensi modul memiliki sejumlah
tag_class
es, yang masing-masing memiliki
sejumlah atribut. Class tag menentukan skema untuk tag yang digunakan oleh ekstensi ini. Melanjutkan contoh ekstensi hipotesis "maven" 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
, hanya saja
objek tersebut mendapatkan objek module_ctx
, yang memberikan
akses ke grafik dependensi dan semua tag terkait. Kemudian, fungsi
implementasi harus memanggil aturan repo untuk menghasilkan 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 melalui semua modul dalam grafik dependensi
(ctx.modules
), yang masing-masing merupakan
objek bazel_module
yang tags
-nya menampilkan
semua tag maven.*
pada modul. Kemudian kami memanggil utilitas CLI
Coursier untuk menghubungi Maven dan menjalankan resolusi. Terakhir, kita menggunakan hasil penyelesaian untuk membuat sejumlah repositori, menggunakan aturan hipotesis maven_single_jar
repositori.