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