ส่วนขยายโมดูลช่วยให้ผู้ใช้ขยายระบบโมดูลได้โดยการอ่านข้อมูลอินพุตจากโมดูลต่างๆ ในกราฟการขึ้นต่อกัน ดำเนินการตรรกะที่จำเป็นเพื่อแก้ไขการขึ้นต่อกัน และสุดท้ายคือสร้าง 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
เพื่อนำที่เก็บที่ส่วนขยายสร้างขึ้นมาไว้ในขอบเขตของโมดูลปัจจุบัน
use_repo(maven, "maven")
ที่เก็บที่สร้างโดยส่วนขยายเป็นส่วนหนึ่งของ API ของส่วนขยาย ในตัวอย่างนี้ ส่วนขยายโมดูล "maven" สัญญาว่าจะสร้างที่เก็บที่ชื่อ maven
เมื่อใช้การประกาศข้างต้น ส่วนขยายจะแก้ไขป้ายกำกับอย่างถูกต้อง เช่น
@maven//:org_junit_junit
เพื่อชี้ไปยังที่เก็บที่สร้างโดยส่วนขยาย "maven"
คำจำกัดความของการขยาย
คุณกำหนดส่วนขยายของโมดูลได้ในลักษณะเดียวกับกฎของ repo
โดยใช้ฟังก์ชัน module_extension
อย่างไรก็ตาม แม้ว่ากฎของที่เก็บจะมีแอตทริบิวต์หลายรายการ แต่ส่วนขยายโมดูล
จะมี tag_class
es ซึ่งแต่ละรายการ
มีแอตทริบิวต์หลายรายการ คลาสแท็กจะกำหนดสคีมาสำหรับแท็กที่ส่วนขยายนี้ใช้ เช่น ส่วนขยาย "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
ซึ่งให้สิทธิ์เข้าถึงโมดูลทั้งหมดที่ใช้ส่วนขยายและแท็กที่เกี่ยวข้องทั้งหมด
จากนั้นฟังก์ชันการติดตั้งใช้งานจะเรียกใช้กฎของที่เก็บเพื่อสร้างที่เก็บ
# @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
ของที่เก็บ ซึ่งไม่เหมือนกับชื่อที่ระบุในการเรียกกฎที่เก็บ
เมื่อพิจารณาที่เก็บข้อมูลที่สร้างโดยส่วนขยายโมดูลแล้ว จะมีกฎการมองเห็นที่เก็บข้อมูลหลายข้อดังนี้
- Repo โมดูล Bazel จะเห็น repo ทั้งหมดที่ระบุไว้ในไฟล์
MODULE.bazel
ผ่านbazel_dep
และuse_repo
- Repo ที่สร้างโดยส่วนขยายโมดูลจะเห็น Repo ทั้งหมดที่โมดูลซึ่งโฮสต์ส่วนขยายมองเห็น รวมถึง Repo อื่นๆ ทั้งหมดที่สร้างโดยส่วนขยายโมดูลเดียวกัน (โดยใช้ชื่อที่ระบุในการเรียกกฎ 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 ข้ามส่วนขยายนี้เมื่อเขียนไปยัง
MODULE.bazel
ไฟล์ล็อก ซึ่งจะช่วยให้ไฟล์ล็อกมีขนาดเล็กและลด
โอกาสที่จะเกิดความขัดแย้งในการผสาน โปรดทราบว่า Bazel ยังคงแคชผลลัพธ์ของส่วนขยายที่ทำซ้ำได้ในลักษณะที่คงอยู่เมื่อรีสตาร์ทเซิร์ฟเวอร์ ดังนั้นแม้แต่ส่วนขยายที่ทำงานเป็นเวลานานก็สามารถทำเครื่องหมายว่าทำซ้ำได้โดยไม่มีผลกระทบต่อประสิทธิภาพ
หากส่วนขยายของคุณต้องอาศัยข้อมูลที่เปลี่ยนแปลงไม่ได้ซึ่งได้มาจากภายนอก
บิลด์ ซึ่งส่วนใหญ่มาจากเครือข่าย แต่คุณไม่มีผลรวมตรวจสอบ
เพื่อป้องกันการดาวน์โหลด ให้ลองใช้พารามิเตอร์ facts
ของ
extension_metadata
เพื่อ
บันทึกข้อมูลดังกล่าวอย่างถาวร และทำให้ส่วนขยายของคุณสามารถ
ทำซ้ำได้ facts
คาดว่าจะเป็นพจนานุกรมที่มีคีย์สตริงและค่า Starlark ที่มีลักษณะคล้าย JSON ที่กำหนดเอง ซึ่งจะคงอยู่ในไฟล์ล็อกเสมอและพร้อมใช้งานสำหรับการประเมินส่วนขยายในอนาคตผ่านฟิลด์ facts
ของ module_ctx
facts
จะยังคงใช้ได้แม้ว่าโค้ดของส่วนขยายโมดูลจะเปลี่ยนไป
ดังนั้นโปรดเตรียมพร้อมรับมือในกรณีที่โครงสร้างของ facts
เปลี่ยนไป
นอกจากนี้ Bazel ยังถือว่า facts
dict 2 รายการที่แตกต่างกันซึ่งเกิดจากการประเมินส่วนขยายเดียวกัน 2 ครั้งที่แตกต่างกันสามารถผสานรวมกันได้แบบตื้น (กล่าวคือ เหมือนกับการใช้ตัวดำเนินการ |
กับ dict 2 รายการ) ซึ่งส่วนหนึ่งเป็นผลมาจากmodule_ctx.facts
ที่ไม่รองรับการแจงนับรายการ แต่รองรับเฉพาะการค้นหาตามคีย์
ตัวอย่างการใช้ facts
คือการบันทึกการแมปจากหมายเลขเวอร์ชันของ
SDK บางรายการไปยังออบเจ็กต์ที่มี URL ดาวน์โหลดและผลรวมตรวจสอบของ
เวอร์ชันนั้น เมื่อมีการประเมินส่วนขยายเป็นครั้งแรก ส่วนขยายจะดึงข้อมูลการแมปนี้จากเครือข่ายได้ แต่ในการประเมินครั้งต่อๆ ไป ส่วนขยายจะใช้การแมปจาก facts
เพื่อหลีกเลี่ยงคำขอเครือข่ายได้
ระบุการขึ้นอยู่กับระบบปฏิบัติการและสถาปัตยกรรม
หากส่วนขยายของคุณขึ้นอยู่กับระบบปฏิบัติการหรือประเภทสถาปัตยกรรมของระบบ
โปรดระบุในคำจำกัดความของส่วนขยายโดยใช้แอตทริบิวต์บูลีน os_dependent
และ arch_dependent
ซึ่งจะช่วยให้ Bazel ทราบว่าต้องประเมินซ้ำหากมีการเปลี่ยนแปลงในไฟล์ใดไฟล์หนึ่ง
เนื่องจากการพึ่งพาโฮสต์ในลักษณะนี้ทำให้การดูแลรายการในไฟล์ Lock สำหรับส่วนขยายนี้ยากขึ้น โปรดพิจารณาทำเครื่องหมายส่วนขยายว่าทำซ้ำได้หากเป็นไปได้
มีเพียงโมดูลรูทเท่านั้นที่ควรมีผลต่อชื่อที่เก็บโดยตรง
โปรดทราบว่าเมื่อส่วนขยายสร้างที่เก็บ ระบบจะสร้างที่เก็บภายใน
เนมสเปซของส่วนขยาย ซึ่งหมายความว่าการชนกันอาจเกิดขึ้นได้หากโมดูลต่างๆ ใช้ส่วนขยายเดียวกันและลงท้ายด้วยการสร้างที่เก็บที่มีชื่อเดียวกัน ซึ่งมักจะปรากฏในรูปแบบที่ส่วนขยายโมดูลมี tag_class
ที่มีอาร์กิวเมนต์ name
ซึ่งส่งผ่านเป็นค่า name
ของกฎที่เก็บ
เช่น สมมติว่าโมดูลรูท A
ขึ้นอยู่กับโมดูล B
ทั้ง 2 โมดูล
ขึ้นอยู่กับโมดูล mylang
หากทั้ง A
และ B
เรียกใช้
mylang.toolchain(name="foo")
ทั้ง 2 จะพยายามสร้างที่เก็บชื่อ
foo
ภายในโมดูล mylang
และจะเกิดข้อผิดพลาดขึ้น
หากต้องการหลีกเลี่ยงปัญหานี้ ให้นำความสามารถในการตั้งชื่อที่เก็บโดยตรงออก หรืออนุญาตให้เฉพาะโมดูลรูททำได้ ทั้งนี้ คุณอนุญาตให้โมดูลรูทมีความสามารถนี้ได้ เนื่องจากไม่มีสิ่งใดขึ้นอยู่กับโมดูลรูท จึงไม่ต้องกังวลว่า โมดูลอื่นจะสร้างชื่อที่ขัดแย้งกัน