Ekstensi modul

Laporkan masalah Lihat sumber Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Ekstensi modul memungkinkan pengguna memperluas sistem modul dengan membaca data input dari modul di seluruh grafik dependensi, menjalankan logika yang diperlukan untuk me-resolve dependensi, dan akhirnya membuat repo 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, seperti aturan repo. Fungsi ini tidak dipanggil secara langsung; melainkan, 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 dalam modul, tambahkan bazel_dep terlebih dahulu di 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 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 perintah use_repo untuk memasukkan repo yang dihasilkan oleh ekstensi ke dalam cakupan modul saat ini.

use_repo(maven, "maven")

Repos yang dibuat oleh ekstensi adalah bagian dari API-nya. Dalam contoh ini, ekstensi modul "maven" berjanji untuk membuat repo bernama maven. Dengan deklarasi di atas, ekstensi me-resolve label dengan benar seperti @maven//:org_junit_junit untuk 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 dengan 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.

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 repo yang diperkenalkan dalam file MODULE.bazel-nya melalui bazel_dep dan use_repo.
  • Repo yang dihasilkan oleh ekstensi modul dapat melihat semua repo yang terlihat oleh modul yang menghosting ekstensi, plus semua repo lain yang dihasilkan 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 menghasilkan repo dengan nama yang ditentukan foo, maka untuk semua repo yang dihasilkan oleh ekstensi tersebut, foo merujuk ke yang pertama.
  • 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 dibuat oleh ekstensi dengan nama yang sama.

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 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: Melakukan patch pada 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 agar ekstensi tersebut mudah digunakan, dapat dikelola, 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 dihasilkan oleh ekstensi lain. Meskipun Anda tidak menggunakan fungsi ini, sebaiknya masukkan fungsi tersebut ke 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 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 mengenali kebutuhan untuk evaluasi ulang jika ada perubahan pada salah satunya.

Karena jenis dependensi pada host ini mempersulit pengelolaan entri file kunci 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. Artinya, konflik dapat terjadi jika modul yang berbeda menggunakan ekstensi yang sama dan akhirnya membuat repositori dengan nama yang sama. Hal ini sering kali muncul 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 membuat nama yang bertentangan.