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

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

คุณสามารถกำหนดส่วนขยายโมดูลในไฟล์ .bzl ได้เช่นเดียวกับกฎของ repo โดยจะไม่มีการเรียกใช้โดยตรง แต่โมดูลแต่ละโมดูลจะระบุข้อมูลบางส่วนที่เรียกว่า แท็กเพื่อให้ส่วนขยายอ่าน 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 เพื่อนำ repo ที่ส่วนขยายสร้างขึ้นมาไว้ในขอบเขตของโมดูลปัจจุบัน

use_repo(maven, "maven")

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

การกำหนดส่วนขยาย

คุณสามารถกำหนดส่วนขยายโมดูลได้ในลักษณะเดียวกับ กฎของ repo, โดยใช้ฟังก์ชัน module_extension อย่างไรก็ตาม แม้ว่ากฎของ repo จะมีแอตทริบิวต์หลายรายการ แต่ส่วนขยายโมดูล จะมี 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 ได้โดยใช้สคีมาแอตทริบิวต์ที่ระบุ

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

ข้อมูลระบุตัวตนของส่วนขยาย

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

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

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

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

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

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

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

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

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

การลบล้างและการแทรก repo ส่วนขยายโมดูล

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

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

# 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 สาธารณะและเป็นการเปลี่ยนแปลงที่เข้ากันไม่ได้กับเวอร์ชันก่อนหน้าสำหรับผู้ใช้

ระบุความสามารถในการทำซ้ำและใช้ข้อมูล

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

หากส่วนขยายของคุณขึ้นอยู่กับข้อมูลที่เปลี่ยนแปลงไม่ได้ซึ่งได้มาจากภายนอก การบิลด์ ซึ่งส่วนใหญ่มาจากเครือข่าย แต่คุณไม่มี Checksum เพื่อป้องกันการดาวน์โหลด ให้พิจารณาใช้พารามิเตอร์ facts ของ extension_metadata เพื่อ บันทึกข้อมูลดังกล่าวอย่างถาวรและทำให้ส่วนขยายของคุณทำซ้ำได้ facts คาดว่าจะเป็นพจนานุกรมที่มีคีย์สตริงและ ค่า Starlark ที่คล้ายกับ JSON ซึ่งจะคงอยู่เสมอในไฟล์ Lock และ พร้อมสำหรับการประเมินส่วนขยายในอนาคตผ่าน facts ช่องของ module_ctx

facts จะไม่ถูกลบล้างแม้ว่าโค้ดของส่วนขยายโมดูลจะเปลี่ยนไป ดังนั้นคุณควรเตรียมพร้อมที่จะจัดการกรณีที่โครงสร้างของ facts เปลี่ยนไป นอกจากนี้ Bazel ยังถือว่าพจนานุกรม facts 2 รายการที่แตกต่างกันซึ่งเกิดจากการประเมินส่วนขยายเดียวกัน 2 ครั้งสามารถผสานกันได้แบบตื้น (เช่น ราวกับว่าใช้โอเปอเรเตอร์ | กับพจนานุกรม 2 รายการ) module_ctx.facts ไม่รองรับการแจกแจงรายการ แต่รองรับการค้นหาด้วยคีย์เท่านั้น

ตัวอย่างการใช้ facts คือการบันทึกการแมปจากหมายเลขเวอร์ชันของ SDK บางรายการไปยังออบเจ็กต์ที่มี URL การดาวน์โหลดและ Checksum ของเวอร์ชันนั้น เมื่อมีการประเมินส่วนขยายเป็นครั้งแรก ส่วนขยายจะดึงข้อมูลการแมปนี้จากเครือข่าย แต่ในการประเมินครั้งต่อๆ ไป ส่วนขยายจะใช้การแมปจาก facts เพื่อหลีกเลี่ยงคำขอเครือข่าย

ระบุการขึ้นต่อกันกับระบบปฏิบัติการและสถาปัตยกรรม

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

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

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

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

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

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