Ekstensi modul memungkinkan pengguna memperluas sistem modul dengan membaca data input dari modul di seluruh grafik dependensi, melakukan logika yang diperlukan untuk mengatasi dependensi, dan akhirnya membuat repositori dengan memanggil aturan repo. Ekstensi ini memiliki kemampuan yang mirip dengan aturan repo, yang memungkinkannya melakukan I/O file, mengirim permintaan jaringan, dan sebagainya. Selain hal lainnya, ekstensi ini memungkinkan Bazel berinteraksi dengan sistem pengelolaan paket lain sekaligus mematuhi grafik dependensi yang dibuat dari modul Bazel.
Anda dapat menentukan ekstensi modul dalam file .bzl, seperti aturan repo. Ekstensi ini tidak dipanggil secara langsung; melainkan, setiap modul menentukan bagian data yang disebut tag untuk dibaca oleh ekstensi. Bazel menjalankan resolusi modul sebelum mengevaluasi ekstensi apa pun. Ekstensi membaca semua tag yang dimilikinya di seluruh grafik dependensi.
Penggunaan ekstensi
Ekstensi dihosting di modul Bazel itu sendiri. Untuk menggunakan ekstensi dalam
modul, pertama-tama tambahkan bazel_dep pada modul yang menghosting ekstensi, lalu
panggil fungsi bawaan use_extension
untuk memasukkannya ke dalam cakupan. Pertimbangkan contoh berikut — cuplikan dari file
MODULE.bazel untuk menggunakan ekstensi "maven" yang ditentukan dalam
rules_jvm_external
modul:
bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Hal ini mengikat nilai yang ditampilkan use_extension ke variabel, yang memungkinkan pengguna menggunakan sintaksis titik untuk menentukan tag untuk ekstensi. Tag harus mengikuti
skema yang ditentukan oleh class tag yang sesuai yang ditentukan dalam
definisi ekstensi. Untuk contoh yang menentukan beberapa tag maven.install dan maven.artifact:
maven.install(artifacts = ["org.junit:junit:4.13.2"])
maven.artifact(group = "com.google.guava",
artifact = "guava",
version = "27.0-jre",
exclusions = ["com.google.j2objc:j2objc-annotations"])
Gunakan direktif use_repo untuk memasukkan repositori
yang dibuat oleh ekstensi ke dalam cakupan modul saat ini.
use_repo(maven, "maven")
Repositori yang dibuat oleh ekstensi adalah bagian dari API-nya. Dalam contoh ini, ekstensi modul "maven" berjanji untuk membuat repositori bernama maven. Dengan deklarasi di atas, ekstensi akan menyelesaikan label dengan benar seperti @maven//:org_junit_junit untuk mengarah ke repositori yang dibuat oleh ekstensi "maven".
Definisi ekstensi
Anda dapat menentukan ekstensi modul dengan cara yang sama seperti aturan repo, menggunakan the
module_extension fungsi. Namun,
meskipun aturan repo memiliki sejumlah atribut, ekstensi modul memiliki
tag_classes, yang masing-masing memiliki sejumlah
atribut. Class tag menentukan skema untuk tag yang digunakan oleh ekstensi ini. Misalnya, ekstensi "maven" di atas dapat ditentukan seperti ini:
# @rules_jvm_external//:extensions.bzl
_install = tag_class(attrs = {"artifacts": attr.string_list(), ...})
_artifact = tag_class(attrs = {"group": attr.string(), "artifact": attr.string(), ...})
maven = module_extension(
implementation = _maven_impl,
tag_classes = {"install": _install, "artifact": _artifact},
)
Deklarasi ini menunjukkan bahwa tag maven.install dan maven.artifact dapat ditentukan menggunakan skema atribut yang ditentukan.
Fungsi penerapan ekstensi modul mirip dengan fungsi aturan repo
rules, kecuali bahwa fungsi tersebut mendapatkan objek module_ctx,
yang memberikan akses ke semua modul menggunakan ekstensi dan semua tag yang relevan.
Fungsi penerapan kemudian memanggil aturan repo untuk membuat repositori.
# @rules_jvm_external//:extensions.bzl
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") # a repo rule
def _maven_impl(ctx):
# This is a fake implementation for demonstration purposes only
# collect artifacts from across the dependency graph
artifacts = []
for mod in ctx.modules:
for install in mod.tags.install:
artifacts += install.artifacts
artifacts += [_to_artifact(artifact) for artifact in mod.tags.artifact]
# call out to the coursier CLI tool to resolve dependencies
output = ctx.execute(["coursier", "resolve", artifacts])
repo_attrs = _process_coursier_output(output)
# call repo rules to generate repos
for attrs in repo_attrs:
http_file(**attrs)
_generate_hub_repo(name = "maven", repo_attrs)
Identitas ekstensi
Ekstensi modul diidentifikasi berdasarkan nama dan file .bzl yang muncul dalam panggilan ke use_extension. Dalam contoh berikut, ekstensi maven
diidentifikasi oleh file .bzl @rules_jvm_external//:extension.bzl dan nama
maven:
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Mengekspor ulang ekstensi dari file .bzl yang berbeda akan memberikan identitas baru dan jika kedua versi ekstensi digunakan dalam grafik modul transitif, keduanya akan dievaluasi secara terpisah dan hanya akan melihat tag yang terkait dengan identitas tertentu tersebut.
Sebagai penulis ekstensi, Anda harus memastikan bahwa pengguna hanya akan menggunakan ekstensi modul Anda dari satu file .bzl.
Nama dan visibilitas repositori
Repositori yang dibuat oleh ekstensi memiliki nama kanonis dalam bentuk module_repo_canonical_name+extension_name+repo_name. Perhatikan bahwa format nama kanonis bukanlah API yang harus Anda andalkan — format ini dapat berubah kapan saja.
Kebijakan penamaan ini berarti bahwa setiap ekstensi memiliki "namespace repo" sendiri; dua ekstensi yang berbeda dapat menentukan repo dengan nama yang sama tanpa menimbulkan risiko konflik. Hal ini juga berarti bahwa repository_ctx.name melaporkan nama kanonis repo, yang tidak sama dengan nama yang ditentukan dalam panggilan aturan repo.
Dengan mempertimbangkan repositori yang dibuat oleh ekstensi modul, ada beberapa aturan visibilitas repo:
- Repo modul Bazel dapat melihat semua repo yang diperkenalkan dalam file
MODULE.bazelnya melaluibazel_depdanuse_repo. - Repo yang dibuat oleh ekstensi modul dapat melihat semua repo yang terlihat oleh modul yang menghosting ekstensi, ditambah semua repo lain yang dibuat oleh ekstensi modul yang sama (menggunakan nama yang ditentukan dalam panggilan aturan repo sebagai nama yang terlihat).
- Hal ini dapat menyebabkan konflik. Jika repo modul dapat melihat repo dengan nama yang terlihat
foo, dan ekstensi membuat repo dengan nama yang ditentukanfoo, maka untuk semua repo yang dibuat oleh ekstensi tersebut,foomerujuk ke repo sebelumnya.
- Hal ini dapat menyebabkan konflik. Jika repo modul dapat melihat repo dengan nama yang terlihat
- Demikian pula, dalam fungsi penerapan ekstensi modul, repo yang dibuat oleh ekstensi dapat merujuk satu sama lain berdasarkan nama yang terlihat dalam atribut, terlepas dari urutan pembuatannya.
- Jika terjadi konflik dengan repositori yang terlihat oleh modul, label
yang diteruskan ke atribut aturan repositori dapat dienkapsulasi dalam panggilan ke
Labeluntuk memastikan bahwa label tersebut merujuk ke repo yang terlihat oleh modul, bukan repo yang dibuat oleh ekstensi dengan nama yang sama.
- Jika terjadi konflik dengan repositori yang terlihat oleh modul, label
yang diteruskan ke atribut aturan repositori dapat dienkapsulasi dalam panggilan ke
Mengganti dan memasukkan repo ekstensi modul
Modul root dapat menggunakan
override_repo dan
inject_repo untuk mengganti atau memasukkan
repo ekstensi modul.
Contoh: Mengganti java_tools rules_java dengan salinan yang dijual
# MODULE.bazel
local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")
local_repository(
name = "my_java_tools",
path = "vendor/java_tools",
)
bazel_dep(name = "rules_java", version = "7.11.1")
java_toolchains = use_extension("@rules_java//java:extension.bzl", "toolchains")
override_repo(java_toolchains, remote_java_tools = "my_java_tools")
Contoh: Mem-patch dependensi Go agar bergantung pada @zlib, bukan zlib sistem
# MODULE.bazel
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "zlib", version = "1.3.1.bcr.3")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_deps.module_override(
patches = [
"//patches:my_module_zlib.patch",
],
path = "example.com/my_module",
)
use_repo(go_deps, ...)
inject_repo(go_deps, "zlib")
# patches/my_module_zlib.patch
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,6 +1,6 @@
go_binary(
name = "my_module",
importpath = "example.com/my_module",
srcs = ["my_module.go"],
- copts = ["-lz"],
+ cdeps = ["@zlib"],
)
Praktik terbaik
Bagian ini menjelaskan praktik terbaik saat menulis ekstensi sehingga mudah digunakan, dapat dipertahankan, dan beradaptasi dengan baik terhadap perubahan dari waktu ke waktu.
Menempatkan setiap ekstensi dalam file terpisah
Jika ekstensi berada dalam file yang berbeda, satu ekstensi dapat memuat repositori yang dibuat oleh ekstensi lain. Meskipun Anda tidak menggunakan fungsi ini, sebaiknya tempatkan ekstensi dalam file terpisah jika Anda memerlukannya nanti. Hal ini karena identitas ekstensi didasarkan pada filenya, sehingga memindahkan ekstensi ke file lain nanti akan mengubah API publik Anda dan merupakan perubahan yang tidak kompatibel dengan versi sebelumnya bagi pengguna Anda.
Menentukan kemampuan reproduksi dan menggunakan fakta
Jika ekstensi Anda selalu menentukan repositori yang sama dengan input yang sama
(tag ekstensi, file yang dibacanya, dll.) dan khususnya tidak bergantung pada
apa pun download yang tidak dilindungi oleh
checksum, pertimbangkan untuk menampilkan
extension_metadata dengan
reproducible = True. Hal ini memungkinkan Bazel melewati ekstensi ini saat menulis ke file kunci MODULE.bazel, yang membantu menjaga ukuran file kunci tetap kecil dan mengurangi kemungkinan konflik gabungan. Perhatikan bahwa Bazel masih menyimpan dalam cache hasil ekstensi yang dapat direproduksi dengan cara yang tetap ada di seluruh proses memulai ulang server, sehingga ekstensi yang berjalan lama pun dapat ditandai sebagai dapat direproduksi tanpa penalti performa.
Jika ekstensi Anda bergantung pada data yang efektif dan tidak dapat diubah yang diperoleh dari luar
build, yang paling umum dari jaringan, tetapi Anda tidak memiliki checksum
yang tersedia untuk melindungi download, pertimbangkan untuk menggunakan parameter facts dari
extension_metadata untuk
mencatat data tersebut secara persisten dan dengan demikian memungkinkan ekstensi Anda menjadi
dapat direproduksi. facts diharapkan berupa kamus dengan kunci string dan
nilai Starlark seperti JSON arbitrer yang selalu dipertahankan dalam file kunci dan
tersedia untuk evaluasi ekstensi di masa mendatang melalui kolom
facts dari module_ctx.
facts tidak dibatalkan meskipun kode ekstensi modul Anda berubah, jadi bersiaplah untuk menangani kasus saat struktur facts berubah.
Bazel juga mengasumsikan bahwa dua dict facts yang berbeda yang dihasilkan oleh dua evaluasi ekstensi yang sama dapat digabungkan secara dangkal (yaitu, seolah-olah menggunakan operator | pada dua dict). Hal ini sebagian diterapkan oleh module_ctx.facts yang tidak mendukung enumerasi entri, hanya pencarian berdasarkan kunci.
Contoh penggunaan facts adalah untuk mencatat pemetaan dari nomor versi beberapa SDK ke objek yang berisi URL download dan checksum versi tersebut. Saat pertama kali dievaluasi, ekstensi dapat mengambil pemetaan ini dari jaringan, tetapi pada evaluasi berikutnya, ekstensi dapat menggunakan pemetaan dari facts untuk menghindari permintaan jaringan.
Menentukan dependensi pada sistem operasi dan arsitektur
Jika ekstensi Anda bergantung pada sistem operasi atau jenis arsitekturnya, pastikan untuk menunjukkannya dalam definisi ekstensi menggunakan atribut boolean os_dependent dan arch_dependent. Hal ini memastikan Bazel mengenali kebutuhan untuk evaluasi ulang jika ada perubahan pada salah satunya.
Karena jenis dependensi pada host ini mempersulit pemeliharaan entri file kunci untuk ekstensi ini, pertimbangkan untuk menandai ekstensi yang dapat direproduksi jika memungkinkan.
Hanya modul root yang boleh memengaruhi nama repositori secara langsung
Ingatlah bahwa saat ekstensi membuat repositori, repositori tersebut dibuat dalam namespace ekstensi. Artinya, konflik dapat terjadi jika modul yang berbeda menggunakan ekstensi yang sama dan akhirnya membuat repositori dengan nama yang sama. Hal ini sering kali terwujud sebagai tag_class ekstensi modul yang memiliki argumen name yang diteruskan sebagai nilai name aturan repositori.
Misalnya, modul root, A, bergantung pada modul B. Kedua modul bergantung pada modul mylang. Jika A dan B memanggil
mylang.toolchain(name="foo"), keduanya akan mencoba membuat repositori bernama
foo dalam modul mylang dan akan terjadi error.
Untuk menghindarinya, hapus kemampuan untuk menetapkan nama repositori secara langsung, atau hanya izinkan modul root melakukannya. Tidak masalah untuk mengizinkan modul root memiliki kemampuan ini karena tidak ada yang akan bergantung padanya, sehingga modul root tidak perlu khawatir tentang modul lain yang membuat nama yang bertentangan.