ส่วนขยายของโมดูล

รายงานปัญหา ดูแหล่งที่มา Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

ส่วนขยายโมดูลช่วยให้ผู้ใช้ขยายระบบโมดูลได้โดยการอ่านข้อมูลอินพุต จากโมดูลในกราฟการอ้างอิง การใช้ตรรกะที่จำเป็นเพื่อแก้ไข การอ้างอิง และสุดท้ายคือการสร้างที่เก็บโดยการเรียกใช้กฎ repo ส่วนขยายเหล่านี้มีความสามารถคล้ายกับกฎของที่เก็บ ซึ่งช่วยให้ส่วนขยายสามารถดำเนินการ I/O ของไฟล์ ส่งคำขอเครือข่าย และอื่นๆ ซึ่งช่วยให้ Bazel โต้ตอบกับระบบการจัดการแพ็กเกจอื่นๆ ได้ ในขณะเดียวกันก็ยังคงใช้กราฟการอ้างอิงที่สร้างขึ้นจากโมดูล Bazel

คุณกำหนดส่วนขยายของโมดูลในไฟล์ .bzl ได้เช่นเดียวกับกฎของที่เก็บ โดยจะ ไม่ได้เรียกใช้โดยตรง แต่ละโมดูลจะระบุชิ้นส่วนของข้อมูลที่เรียกว่าแท็ก เพื่อให้ส่วนขยายอ่าน Bazel จะเรียกใช้การแก้ปัญหาโมดูลก่อนประเมินส่วนขยาย ส่วนขยายจะอ่านแท็กทั้งหมดที่อยู่ในกราฟการอ้างอิงทั้งหมด

การใช้งานส่วนขยาย

ส่วนขยายจะโฮสต์อยู่ในโมดูล Bazel เอง หากต้องการใช้ส่วนขยายในโมดูล ให้เพิ่ม bazel_dep ในโมดูลที่โฮสต์ส่วนขยายก่อน แล้วเรียกใช้ฟังก์ชันในตัว use_extension เพื่อนำส่วนขยายเข้าสู่ขอบเขต พิจารณาตัวอย่างต่อไปนี้ ซึ่งเป็นข้อมูลโค้ดจากไฟล์ MODULE.bazel เพื่อใช้ส่วนขยาย "maven" ที่กำหนดไว้ในโมดูล rules_jvm_external

bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

ซึ่งจะเชื่อมค่าที่ส่งคืนของ use_extension กับตัวแปร ทำให้ผู้ใช้ใช้ไวยากรณ์แบบจุดเพื่อระบุแท็กสำหรับส่วนขยายได้ แท็กต้องเป็นไปตาม รูปแบบที่กำหนดโดยคลาสแท็กที่เกี่ยวข้องซึ่งระบุไว้ใน คำจำกัดความของส่วนขยาย ตัวอย่างการระบุแท็ก maven.install และ 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"])

ใช้คำสั่ง use_repo เพื่อนำที่เก็บข้อมูล ที่ส่วนขยายสร้างขึ้นมาไว้ในขอบเขตของโมดูลปัจจุบัน

use_repo(maven, "maven")

ที่เก็บที่ส่วนขยายสร้างขึ้นเป็นส่วนหนึ่งของ API ของส่วนขยาย ในตัวอย่างนี้ ส่วนขยายโมดูล "maven" สัญญาว่าจะสร้างที่เก็บที่ชื่อ maven เมื่อใช้การประกาศข้างต้น ส่วนขยายจะแก้ไขป้ายกำกับอย่างถูกต้อง เช่น @maven//:org_junit_junit เพื่อชี้ไปยังที่เก็บที่สร้างโดยส่วนขยาย "maven"

คำจำกัดความของส่วนขยาย

คุณกำหนดส่วนขยายโมดูลได้ในลักษณะเดียวกับกฎของที่เก็บ โดยใช้ฟังก์ชัน module_extension อย่างไรก็ตาม แม้ว่ากฎของที่เก็บจะมีแอตทริบิวต์หลายรายการ แต่ส่วนขยายโมดูล จะมี tag_classes ซึ่งแต่ละรายการ จะมีแอตทริบิวต์หลายรายการ คลาสแท็กจะกำหนดสคีมาสำหรับแท็กที่ส่วนขยายนี้ใช้ เช่น ส่วนขยาย "maven" ด้านบนอาจกำหนดได้ดังนี้

# @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},
)

การประกาศเหล่านี้แสดงให้เห็นว่าแท็ก maven.install และ maven.artifact สามารถ ระบุได้โดยใช้สคีมาแอตทริบิวต์ที่ระบุ

ฟังก์ชันการใช้งานของส่วนขยายโมดูลจะคล้ายกับของกฎของที่เก็บ ยกเว้นว่าส่วนขยายจะได้รับออบเจ็กต์ module_ctx ซึ่งให้สิทธิ์เข้าถึงโมดูลทั้งหมดที่ใช้ส่วนขยายและแท็กที่เกี่ยวข้องทั้งหมด จากนั้นฟังก์ชันการติดตั้งใช้งานจะเรียกใช้กฎของที่เก็บเพื่อสร้างที่เก็บ

# @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)

ข้อมูลประจำตัวของส่วนขยาย

ส่วนขยายโมดูลจะระบุด้วยชื่อและไฟล์ .bzl ที่ปรากฏ ในการเรียกใช้ use_extension ในตัวอย่างต่อไปนี้ ระบบจะระบุส่วนขยาย maven โดยใช้ไฟล์ .bzl @rules_jvm_external//:extension.bzl และชื่อ maven

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

การส่งออกส่วนขยายจากไฟล์ .bzl อื่นอีกครั้งจะทำให้ส่วนขยายมีข้อมูลประจำตัวใหม่ และหากใช้ส่วนขยายทั้ง 2 เวอร์ชันในกราฟโมดูลแบบทรานซิทีฟ ระบบจะประเมินส่วนขยายทั้ง 2 แยกกันและจะเห็นเฉพาะแท็กที่เชื่อมโยง กับข้อมูลประจำตัวนั้นๆ

ในฐานะผู้เขียนส่วนขยาย คุณควรตรวจสอบว่าผู้ใช้จะใช้ส่วนขยายโมดูลจากไฟล์ .bzl เพียงไฟล์เดียวเท่านั้น

ชื่อที่เก็บและระดับการเข้าถึง

ที่เก็บที่ส่วนขยายสร้างขึ้นจะมีชื่อที่แน่นอนในรูปแบบ module_repo_canonical_name+extension_name+repo_name โปรดทราบว่ารูปแบบชื่อที่แน่นอน ไม่ใช่ API ที่คุณควรใช้ เนื่องจากอาจมีการเปลี่ยนแปลงได้ทุกเมื่อ

นโยบายการตั้งชื่อนี้หมายความว่าส่วนขยายแต่ละรายการจะมี "เนมสเปซของที่เก็บ" เป็นของตัวเอง ส่วนขยาย 2 รายการที่แตกต่างกันสามารถกำหนดที่เก็บที่มีชื่อเดียวกันได้โดยไม่ต้องเสี่ยงต่อการเกิดข้อขัดแย้ง นอกจากนี้ยังหมายความว่า repository_ctx.nameจะรายงานชื่อ Canonical ของที่เก็บ ซึ่งไม่เหมือนกับชื่อที่ระบุในการเรียกกฎที่เก็บ

เมื่อพิจารณาถึงที่เก็บที่สร้างโดยส่วนขยายโมดูลแล้ว จะมีกฎการมองเห็นที่เก็บหลายข้อดังนี้

  • ที่เก็บโมดูล Bazel จะเห็นที่เก็บทั้งหมดที่ระบุไว้ในไฟล์ MODULE.bazel ผ่าน bazel_dep และ use_repo
  • ที่เก็บที่สร้างโดยส่วนขยายโมดูลจะเห็นที่เก็บทั้งหมดที่โมดูลซึ่งโฮสต์ส่วนขยายมองเห็น รวมถึงที่เก็บอื่นๆ ทั้งหมดที่สร้างโดยส่วนขยายโมดูลเดียวกัน (โดยใช้ชื่อที่ระบุในการเรียกกฎที่เก็บเป็นชื่อที่ปรากฏ)
    • ซึ่งอาจทำให้เกิดความขัดแย้ง หากที่เก็บโมดูลมองเห็นที่เก็บที่มีชื่อที่ชัดเจนว่า foo และส่วนขยายสร้างที่เก็บที่มีชื่อที่ระบุว่า foo สำหรับที่เก็บทั้งหมดที่ส่วนขยายนั้นสร้างขึ้น foo จะอ้างอิงถึงที่เก็บแรก
  • ในทำนองเดียวกัน ในฟังก์ชันการติดตั้งใช้งานของส่วนขยายโมดูล ที่เก็บที่สร้างขึ้น โดยส่วนขยายจะอ้างอิงถึงกันได้ตามชื่อที่ปรากฏใน แอตทริบิวต์ ไม่ว่าที่เก็บจะสร้างขึ้นตามลำดับใดก็ตาม
    • ในกรณีที่เกิดความขัดแย้งกับที่เก็บที่โมดูลมองเห็นได้ คุณสามารถห่อป้ายกำกับ ที่ส่งไปยังแอตทริบิวต์กฎที่เก็บไว้ในการเรียกใช้ Label เพื่อให้แน่ใจว่าป้ายกำกับเหล่านั้นอ้างอิงถึง ที่เก็บที่โมดูลมองเห็นได้แทนที่เก็บที่สร้างโดยส่วนขยาย ซึ่งมีชื่อเดียวกัน

การแทนที่และการแทรกที่เก็บส่วนขยายของโมดูล

โมดูลรูทสามารถใช้ override_repo และ inject_repo เพื่อลบล้างหรือแทรก ที่เก็บส่วนขยายของโมดูล

ตัวอย่าง: การแทนที่ rules_java's java_tools ด้วยสำเนาที่จัดจำหน่าย

# 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")

ตัวอย่าง: แพตช์การขึ้นต่อกันของ Go เพื่อให้ขึ้นต่อกับ @zlib แทนที่จะเป็น 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"],
 )

แนวทางปฏิบัติแนะนำ

ส่วนนี้อธิบายแนวทางปฏิบัติแนะนำในการเขียนส่วนขยายเพื่อให้ใช้งานได้ง่าย บำรุงรักษาได้ และปรับให้เข้ากับการเปลี่ยนแปลงเมื่อเวลาผ่านไปได้ดี

ใส่ส่วนขยายแต่ละรายการในไฟล์แยกกัน

เมื่อส่วนขยายอยู่ในไฟล์ที่แตกต่างกัน ส่วนขยายหนึ่งจะโหลดที่เก็บที่สร้างโดยส่วนขยายอื่นได้ แม้ว่าคุณจะไม่ได้ใช้ฟังก์ชันนี้ แต่ก็ควรแยกไฟล์ไว้ในกรณีที่จำเป็นต้องใช้ในภายหลัง เนื่องจากข้อมูลระบุตัวตนของส่วนขยายอิงตามไฟล์ของส่วนขยาย ดังนั้นการย้าย ส่วนขยายไปยังไฟล์อื่นในภายหลังจะเปลี่ยน API สาธารณะและเป็นการเปลี่ยนแปลงที่ เข้ากันไม่ได้แบบย้อนหลังสำหรับผู้ใช้

ระบุความสามารถในการทำซ้ำ

หากส่วนขยายกำหนดที่เก็บข้อมูลเดียวกันเสมอเมื่อได้รับอินพุตเดียวกัน (แท็กส่วนขยาย ไฟล์ที่อ่าน ฯลฯ) และโดยเฉพาะอย่างยิ่งไม่ได้อิงตาม การดาวน์โหลดใดๆ ที่ไม่ได้ป้องกันด้วย ผลรวมตรวจสอบ ให้พิจารณาแสดงผล extension_metadata พร้อมด้วย reproducible = True ซึ่งจะช่วยให้ Bazel ข้ามส่วนขยายนี้เมื่อเขียนไปยัง ไฟล์ล็อก

ระบุระบบปฏิบัติการและสถาปัตยกรรม

หากส่วนขยายของคุณขึ้นอยู่กับระบบปฏิบัติการหรือประเภทสถาปัตยกรรมของระบบ โปรดระบุในคำจำกัดความของส่วนขยายโดยใช้แอตทริบิวต์บูลีน os_dependent และ arch_dependent ซึ่งจะช่วยให้ Bazel ทราบว่าต้องประเมินซ้ำหากมีการเปลี่ยนแปลงในไฟล์ใดไฟล์หนึ่ง

เนื่องจากการพึ่งพาโฮสต์ในลักษณะนี้ทำให้การดูแลรายการในไฟล์ Lock สำหรับส่วนขยายนี้ยากขึ้น โปรดทำเครื่องหมายส่วนขยายว่าทำซ้ำได้หากเป็นไปได้

มีเพียงโมดูลรูทเท่านั้นที่ควรส่งผลต่อชื่อที่เก็บโดยตรง

โปรดทราบว่าเมื่อส่วนขยายสร้างที่เก็บ ระบบจะสร้างที่เก็บภายใน เนมสเปซของส่วนขยาย ซึ่งหมายความว่าการชนกันอาจเกิดขึ้นหากโมดูลต่างๆ ใช้ส่วนขยายเดียวกันและลงท้ายด้วยการสร้างที่เก็บที่มีชื่อเดียวกัน ซึ่งมักจะแสดงเป็น tag_class ของส่วนขยายโมดูลที่มีอาร์กิวเมนต์ name ซึ่งส่งเป็นค่า name ของกฎที่เก็บ

เช่น สมมติว่าโมดูลรูท A ขึ้นอยู่กับโมดูล B ทั้ง 2 โมดูล ขึ้นอยู่กับโมดูล mylang หากทั้ง A และ B เรียกใช้ mylang.toolchain(name="foo") ทั้ง 2 จะพยายามสร้างที่เก็บชื่อ foo ภายในโมดูล mylang และจะเกิดข้อผิดพลาดขึ้น

หากต้องการหลีกเลี่ยงปัญหานี้ ให้นำความสามารถในการตั้งชื่อที่เก็บโดยตรงออก หรืออนุญาตให้เฉพาะโมดูลรูททำได้เท่านั้น คุณอนุญาตให้โมดูลรูทมีความสามารถนี้ได้ เนื่องจากไม่มีสิ่งใดขึ้นอยู่กับโมดูลรูท จึงไม่ต้องกังวลว่า โมดูลอื่นจะสร้างชื่อที่ขัดแย้งกัน