Ekstensi modul memungkinkan pengguna memperluas sistem modul dengan membaca data input dari modul di seluruh grafik dependensi, melakukan logika yang diperlukan untuk me-resolve dependensi, dan terakhir 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. Di antara hal lainnya, hal 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
, sama seperti aturan repo. Parameter ini tidak dipanggil secara langsung; tetapi, setiap modul menentukan bagian data yang disebut tag untuk dibaca 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 di 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
modul
rules_jvm_external
:
bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
Tindakan ini akan mengikat nilai yang ditampilkan use_extension
ke variabel, yang memungkinkan pengguna menggunakan sintaksis titik untuk menentukan tag bagi ekstensi. Tag harus mengikuti skema yang ditentukan oleh class tag terkait 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 perintah use_repo
untuk memasukkan repo
yang dihasilkan 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" menjanjikan untuk menghasilkan repo yang disebut maven
. Dengan deklarasi di atas, ekstensi menyelesaikan label seperti @maven//:org_junit_junit
dengan benar agar mengarah ke repo yang dihasilkan oleh ekstensi "maven".
Definisi ekstensi
Anda dapat menentukan ekstensi modul mirip dengan aturan repo, menggunakan
fungsi module_extension
. Namun,
meskipun aturan repo memiliki sejumlah atribut, ekstensi modul memiliki
tag_class
, 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 implementasi ekstensi modul mirip dengan fungsi aturan repo, kecuali bahwa fungsi tersebut mendapatkan objek module_ctx
, yang memberikan akses ke semua modul yang menggunakan ekstensi dan semua tag yang relevan.
Fungsi implementasi kemudian memanggil aturan repo untuk membuat repo.
# @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 memberinya identitas baru
dan jika kedua versi ekstensi digunakan dalam grafik modul transitif,
kedua versi tersebut 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 dari satu file .bzl
tunggal.
Nama dan visibilitas repositori
Repos yang dibuat oleh ekstensi memiliki nama kanonis dalam bentuk module_repo_canonical_name~extension_name~repo_name
. Untuk ekstensi yang dihosting di
modul root, bagian module_repo_canonical_name
diganti dengan string _main
. Perhatikan bahwa format nama kanonis bukanlah
API yang harus Anda andalkan — format ini dapat berubah sewaktu-waktu.
Kebijakan penamaan ini berarti setiap ekstensi memiliki "namespace repo"-nya sendiri; dua
ekstensi yang berbeda dapat menentukan repo dengan nama yang sama tanpa 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 repo yang dihasilkan oleh ekstensi modul, ada beberapa aturan visibilitas repo:
- Repositori modul Bazel dapat melihat semua repositori yang dimasukkan dalam file
MODULE.bazel
melaluibazel_dep
danuse_repo
. - Repositori yang dihasilkan oleh ekstensi modul dapat melihat semua repositori yang terlihat oleh modul yang menghosting ekstensi, ditambah semua repositori lain yang dihasilkan oleh ekstensi modul yang sama (menggunakan nama yang ditentukan dalam aturan repo dipanggil sebagai nama jelasnya).
- Hal ini dapat menyebabkan konflik. Jika repo modul dapat melihat repo dengan
nama yang terlihat
foo
, dan ekstensi menghasilkan repo dengan nama yang ditentukanfoo
, maka untuk semua repo yang dihasilkan oleh ekstensi tersebut,foo
merujuk ke yang pertama.
- Hal ini dapat menyebabkan konflik. Jika repo modul dapat melihat repo dengan
nama yang terlihat
- Demikian pula, dalam fungsi implementasi ekstensi modul, repo yang dibuat oleh ekstensi dapat saling merujuk dengan 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 digabungkan dalam panggilan ke
Label
untuk memastikan bahwa label tersebut merujuk ke repo yang terlihat oleh modul, bukan repo yang dihasilkan ekstensi dengan nama yang sama.
- Jika terjadi konflik dengan repositori yang terlihat oleh modul, label
yang diteruskan ke atribut aturan repositori dapat digabungkan dalam panggilan ke
Mengganti dan memasukkan repositori 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 vendor
# 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: Menambahkan patch pada dependensi Go agar bergantung pada @zlib
, bukan sistem zlib
# 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 agar langsung digunakan, mudah dikelola, dan beradaptasi dengan baik terhadap perubahan dari waktu ke waktu.
Tempatkan setiap ekstensi di file terpisah
Jika ekstensi berada di dalam file yang berbeda, ekstensi akan memungkinkan satu ekstensi memuat repositori yang dihasilkan oleh ekstensi lain. Meskipun Anda tidak menggunakan fungsi ini, sebaiknya tempatkan dalam file terpisah jika Anda memerlukannya nanti. Hal ini karena identitas ekstensi didasarkan pada file-nya, sehingga memindahkan ekstensi ke file lain nantinya akan mengubah API publik Anda dan merupakan perubahan yang tidak kompatibel dengan versi sebelumnya bagi pengguna.
Menentukan kemampuan reproduksi
Jika ekstensi Anda selalu menentukan repositori yang sama dengan input yang sama
(tag ekstensi, file yang dibaca, dll.) dan khususnya tidak mengandalkan
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.
Menentukan 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 bahwa Bazel menyadari
perlunya evaluasi ulang jika ada perubahan pada salah satunya.
Karena ketergantungan pada host seperti ini mempersulit pertahanan entri lockfile untuk ekstensi ini, pertimbangkan untuk menandai ekstensi yang dapat direproduksi jika memungkinkan.
Hanya modul root yang akan memengaruhi nama repositori secara langsung
Ingat bahwa saat ekstensi membuat repositori, repositori tersebut dibuat dalam
namespace ekstensi. Ini berarti tabrakan dapat terjadi jika modul yang berbeda menggunakan ekstensi yang sama dan pada akhirnya membuat repositori dengan nama yang sama. Argumen ini sering kali berwujud 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 error akan terjadi.
Untuk menghindari hal ini, hapus kemampuan untuk menetapkan nama repositori secara langsung, atau hanya izinkan modul root untuk melakukannya. Anda dapat mengizinkan modul root memiliki kemampuan ini karena tidak ada yang akan bergantung padanya, sehingga tidak perlu khawatir modul lain akan membuat nama yang bertentangan.