Bzlmod adalah nama kode sistem dependensi eksternal baru yang diperkenalkan di Bazel 5.0. Fitur 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; tanda
--experimental_enable_bzlmod harus ditentukan agar perubahan berikut diterapkan. Seperti yang ditunjukkan oleh nama flag, fitur ini saat ini masih eksperimental;
API dan perilaku dapat berubah hingga fitur diluncurkan secara resmi.
Untuk memigrasikan project Anda 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 di sekitar
repositori (atau repositori), yang dibuat melalui aturan repositori (atau aturan repositori).
Meskipun repo masih menjadi konsep penting dalam sistem baru, modul adalah unit dependensi inti.
Modul pada dasarnya adalah project Bazel yang dapat memiliki beberapa versi, yang masing-masing memublikasikan metadata tentang modul lain yang menjadi dependensinya. Hal ini mirip 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 dicari di
registry Bazel; secara default,
Bazel Central Registry. Di ruang kerja Anda, setiap modul kemudian diubah menjadi repo.
MODULE.bazel
Setiap versi setiap modul memiliki file MODULE.bazel yang menyatakan
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; selain itu, file ini 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:
- Petunjuk 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 terkenal yang menggunakan skema berbeda seperti Abseil, yang versinya berbasis tanggal, misalnya 20210324.2).
Oleh karena itu, Bzlmod mengadopsi versi spesifikasi SemVer yang lebih longgar. 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. - Dalam SemVer, setiap segmen di bagian "rilis" hanya boleh berupa digit. Di Bazel, hal ini dilonggarkan untuk mengizinkan huruf juga, dan semantik perbandingan cocok dengan "identifier" di bagian "pra-rilis".
- Selain itu, semantik peningkatan versi utama, minor, dan patch tidak diterapkan. (Namun, lihat tingkat kompatibilitas untuk 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 dibandingkan a < b jika sama saat dibandingkan
sebagai versi modul Bazel.
Resolusi versi
Masalah dependensi berlian adalah hal yang umum dalam ruang pengelolaan dependensi yang diberi 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 algoritma Pemilihan Versi Minimal (MVS) yang diperkenalkan dalam sistem modul Go. MVS mengasumsikan bahwa semua versi baru modul kompatibel dengan versi sebelumnya, sehingga hanya memilih versi tertinggi yang ditentukan oleh dependensi (D 1.1 dalam contoh kita). Versi ini disebut "minimal" karena D 1.1 di sini adalah versi minimal yang dapat memenuhi persyaratan kami; meskipun ada D 1.2 atau yang lebih baru, kami tidak memilihnya. Hal ini memiliki manfaat tambahan bahwa pemilihan versi bersifat fidelitas 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 dilakukan karena MVS hanya memperlakukan versi modul yang tidak kompatibel dengan versi sebelumnya sebagai modul terpisah. Dari segi SemVer, hal ini berarti A 1.x dan A 2.x dianggap sebagai modul yang berbeda, dan dapat berdampingan dalam grafik dependensi yang diselesaikan. Hal ini, pada gilirannya, dimungkinkan oleh 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 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()-nya. Dengan informasi ini, kita dapat memunculkan error
saat mendeteksi bahwa versi modul yang sama dengan tingkat kompatibilitas
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, @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 menerapkan mekanisme pemetaan repositori di sistem baru. Berikut dua konsep penting:
Nama repositori kanonis: Nama repositori yang unik secara global untuk setiap repositori. Ini akan menjadi nama direktori tempat repositori berada.
Nama kanonis dibuat sebagai berikut (Peringatan: format nama kanonis bukanlah API yang harus Anda andalkan, format ini dapat berubah kapan saja):- Untuk repositori modul Bazel:
module_name~version
(Contoh.@bazel_skylib~1.0.3) - Untuk 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 yang terlihat: Nama repositori yang akan digunakan dalam file
BUILDdan.bzldalam repositori. Dependensi yang sama dapat memiliki nama yang tampak berbeda di repo yang berbeda.
Ditentukan sebagai berikut:
Setiap repositori memiliki kamus pemetaan repositori untuk 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 mem-parsing file MODULE.bazel, sehingga konflik dapat dengan mudah ditemukan dan diselesaikan tanpa memengaruhi dependensi lainnya.
Dependensi ketat
Format spesifikasi dependensi baru memungkinkan kami melakukan pemeriksaan yang lebih ketat. Secara khusus, kami kini mewajibkan agar 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.
Deps ketat diimplementasikan berdasarkan pemetaan repositori. Pada dasarnya, pemetaan repositori untuk setiap repositori berisi semua dependensi langsung, dan repositori lain 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 modul yang terlihat yang menyediakan ekstensi, serta semua repo 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 registri yang didukung adalah index registry, yang merupakan direktori lokal atau server HTTP statis yang mengikuti format tertentu. Pada masa mendatang, kami berencana menambahkan dukungan untuk registri modul tunggal, yang hanyalah 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 setiap versi, dan cara mengambil sumber setiap
versi. Khususnya, tidak perlu menayangkan arsip sumber itu sendiri.
Pendaftaran 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 dalam registri 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 dalam registry. Perhatikan bahwa hal ini tidak harus sama dengan penulis project.versions: Daftar semua versi modul ini yang akan ditemukan di registri ini.yanked_versions: Daftar versi modul ini yang dibatalkan. Saat ini tidak ada operasi yang dilakukan, tetapi pada masa mendatang, versi yang dibatalkan 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 defaultnya adalah "archive" dengan kolom berikut:
url: URL arsip sumber.integrity: Checksum Subresource Integrity arsip.strip_prefix: Awalan direktori yang akan dihapus saat mengekstrak arsip sumber.patches: Daftar string, yang masing-masing menamai file patch yang akan diterapkan ke arsip yang diekstrak. File patch terletak di bawah direktori/modules/$MODULE/$VERSION/patches.patch_strip: Sama dengan argumen--stripdari patch Unix.
- Jenis dapat diubah untuk menggunakan jalur lokal dengan kolom berikut:
type:local_pathpath: Jalur lokal ke repo, dihitung sebagai berikut:- Jika jalur adalah jalur absolut, akan digunakan apa adanya.
- Jika jalur adalah jalur relatif dan
module_base_pathadalah jalur absolut, jalur di-resolve 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 defaultnya 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. Kontennya didukung oleh repositori GitHub bazelbuild/bazel-central-registry.
BCR dikelola oleh komunitas Bazel; kontributor dipersilakan untuk mengirimkan permintaan pull. Lihat Kebijakan dan Prosedur Registry Pusat 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 antar-modul di BCR.
Memilih registry
Flag Bazel yang dapat diulang --registry dapat digunakan untuk menentukan daftar
registry untuk meminta modul, sehingga Anda dapat menyiapkan project untuk mengambil
dependensi dari registry pihak ketiga atau internal. Pendaftaran sebelumnya
lebih diutamakan. Untuk mempermudah, Anda dapat menempatkan daftar tanda --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 repositori dengan memanggil aturan repositori. Fungsinya serupa 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. API ini tidak dipanggil secara langsung; melainkan, setiap modul dapat menentukan bagian data yang disebut tag agar dibaca oleh ekstensi. Kemudian, setelah penyelesaian versi modul selesai, ekstensi modul akan dijalankan. Setiap ekstensi dijalankan
sekali setelah penyelesaian modul (tetap sebelum build benar-benar terjadi), dan
dapat membaca semua tag yang menjadi miliknya 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". Setelah 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" dijalankan, dan ekstensi tersebut dapat membaca semua tag maven.*, menggunakan informasi di dalamnya untuk memutuskan repositori 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, 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 membawa ekstensi ke dalam cakupan, Anda dapat menggunakan sintaksis titik untuk menentukan tag untuknya. 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 membuat repositori yang ingin Anda gunakan dalam modul, gunakan
direktif use_repo untuk mendeklarasikannya. Hal ini dilakukan untuk memenuhi kondisi deps 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 akan tahu bahwa ekstensi "maven" akan menghasilkan repositori bernama "org_junit_junit", dan satu lagi bernama "com_google_guava_guava". Dengan
use_repo, Anda dapat mengganti namanya secara opsional dalam cakupan modul, seperti menjadi
"jambu" di sini.
Definisi ekstensi
Ekstensi modul ditentukan dengan cara yang sama seperti aturan repo, menggunakan fungsi
module_extension.
Keduanya memiliki fungsi penerapan; 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 memperjelas bahwa tag maven.dep dan maven.pom dapat ditentukan, menggunakan skema atribut yang ditentukan di atas.
Fungsi penerapan mirip dengan makro WORKSPACE, kecuali bahwa fungsi tersebut 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 akan memeriksa semua modul dalam grafik dependensi (ctx.modules), yang masing-masing merupakan 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 repositori, menggunakan aturan repositori maven_single_jar
hipotetis.