จัดการทรัพยากร Dependency ภายนอกด้วย Bzlmod

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

ใน Bazel 5.0 นั้น Bzlmod ไม่ได้เปิดอยู่โดยค่าเริ่มต้น คุณต้องระบุแฟล็ก --experimental_enable_bzlmod เพื่อให้รายการต่อไปนี้มีผล ตามที่ชื่อ Flag แนะนำ ปัจจุบันฟีเจอร์นี้อยู่ในขั้นทดสอบ API และลักษณะการทำงานอาจมีการเปลี่ยนแปลงจนกว่าจะมีการเปิดตัวฟีเจอร์อย่างเป็นทางการ

หากต้องการย้ายข้อมูลโปรเจ็กต์ไปยัง Bzlmod โปรดทำตามคำแนะนำในการย้ายข้อมูล Bzlmod คุณยังดูตัวอย่างการใช้ Bzlmod ได้ในที่เก็บตัวอย่าง

โมดูล Bazel

ระบบทรัพยากร Dependency ภายนอกแบบเก่าที่ใช้ WORKSPACE จะมีศูนย์กลางอยู่ที่ที่เก็บ (หรือที่เก็บ) ซึ่งสร้างผ่านกฎที่เก็บ (หรือกฎที่เก็บ) แม้ว่าที่เก็บยังคงเป็นแนวคิดที่สำคัญในระบบใหม่ โมดูลก็เป็นหน่วยหลักของการพึ่งพากัน

โมดูลคือโปรเจ็กต์ Bazel ที่มีได้หลายเวอร์ชัน โดยแต่ละเวอร์ชันจะเผยแพร่ข้อมูลเมตาเกี่ยวกับโมดูลอื่นๆ ที่โมดูลนี้อ้างอิงอยู่ ซึ่งแตกต่างจากแนวคิดที่คุ้นเคยในระบบการจัดการทรัพยากร Dependency อื่นๆ เช่น อาร์ติแฟกต์ Maven, แพ็กเกจ npm, ลังสินค้า, โมดูล Go เป็นต้น

โมดูลเพียงระบุทรัพยากร Dependency โดยใช้คู่ name และ version แทน URL ที่เจาะจงใน WORKSPACE ระบบจะค้นหาทรัพยากร Dependency ในรีจิสทรีของ Bazel โดยค่าเริ่มต้นคือรีจิสทรีของ Bazel Central จากนั้นแต่ละโมดูลจะกลายเป็นที่เก็บในพื้นที่ทํางาน

MODULE.bazel

ทุกเวอร์ชันของโมดูลจะมีไฟล์ MODULE.bazel ที่ประกาศทรัพยากร Dependency และข้อมูลเมตาอื่นๆ ตัวอย่างเบื้องต้นมีดังนี้

module(
    name = "my-module",
    version = "1.0",
)

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

ไฟล์ MODULE.bazel ควรอยู่ที่รากของไดเรกทอรีพื้นที่ทํางาน (ถัดจากไฟล์ WORKSPACE) สิ่งที่ต่างจากการใช้ไฟล์ WORKSPACE คือคุณไม่จำเป็นต้องระบุทรัพยากร Dependency แบบสกรรมกริยา แต่ควรระบุเฉพาะทรัพยากร Dependency โดยตรง แทน ระบบจะประมวลผลไฟล์ MODULE.bazel ของทรัพยากร Dependency เพื่อค้นหาทรัพยากร Dependency ชั่วคราวโดยอัตโนมัติ

ไฟล์ MODULE.bazel คล้ายกับไฟล์ BUILD เนื่องจากไม่รองรับขั้นตอนการควบคุมรูปแบบใดๆ และยังห้ามคำสั่ง load อีกด้วย การรองรับไฟล์คำสั่ง MODULE.bazel มีดังนี้

  • module เพื่อระบุข้อมูลเมตาเกี่ยวกับโมดูลปัจจุบัน รวมถึงชื่อ เวอร์ชัน และอื่นๆ
  • bazel_dep เพื่อระบุการขึ้นต่อกันโดยตรง ในโมดูล Bazel อื่นๆ
  • การลบล้าง ซึ่งใช้ได้เฉพาะในโมดูลรูท (กล่าวคือ ไม่ใช่โดยโมดูลที่ใช้เป็นทรัพยากร Dependency) เพื่อปรับแต่งลักษณะการทำงานของทรัพยากร Dependency โดยตรงหรือแบบสกรรมกริยาบางรายการ ดังนี้
  • คำสั่งที่เกี่ยวข้องกับส่วนขยายโมดูล

รูปแบบเวอร์ชัน

Bazel มีระบบนิเวศที่หลากหลายและโครงการต่างๆ ใช้รูปแบบการกำหนดเวอร์ชันที่หลากหลาย แพลตฟอร์มที่ได้รับความนิยมมากที่สุดคือ SemVer แต่ก็ยังมีโปรเจ็กต์ที่โดดเด่นที่ใช้แผนการที่แตกต่างกัน เช่น Abseil ซึ่งมีเวอร์ชันเป็นวันที่ เช่น 20210324.2)

ด้วยเหตุนี้ Bzlmod จึงนำข้อกำหนดของ SemVer เวอร์ชันที่ผ่อนคลายมากขึ้นมาใช้ โดยความแตกต่างมีดังนี้

  • SemVer กำหนดว่าส่วน "เผยแพร่" ของเวอร์ชันต้องประกอบด้วย 3 กลุ่ม ได้แก่ MAJOR.MINOR.PATCH ใน Bazel ข้อกำหนดนี้จะเป็นแบบหลวมๆ เพื่อให้แสดงกี่กลุ่มก็ได้
  • ใน SemVer แต่ละส่วนในส่วน "เผยแพร่" ต้องเป็นตัวเลขเท่านั้น ใน Bazel ให้ปล่อยไว้เพื่อให้รองรับตัวอักษรด้วย และความหมายเปรียบเทียบจะตรงกับ "ตัวระบุ" ในส่วน "รุ่นทดลอง"
  • นอกจากนี้ จะไม่มีการบังคับใช้อรรถศาสตร์ของการเพิ่มเวอร์ชันหลัก เวอร์ชันย่อย และเวอร์ชันแพตช์ (อย่างไรก็ตาม ดูระดับความเข้ากันได้สำหรับรายละเอียดเกี่ยวกับวิธีที่เราใช้แสดงความเข้ากันได้แบบย้อนหลัง)

เวอร์ชัน SemVer ที่ถูกต้องคือเวอร์ชันโมดูล Bazel ที่ถูกต้อง นอกจากนี้ 2 เวอร์ชัน SemVer คือ a และ b จะเปรียบเทียบ a < b ระหว่างการคงไว้ชั่วคราวเดียวกันเมื่อเปรียบเทียบกับเวอร์ชันโมดูล Bazel

ความละเอียดของเวอร์ชัน

ปัญหาการพึ่งพาไดมอนด์เป็นส่วนสำคัญในพื้นที่การจัดการทรัพยากร Dependency ที่มีเวอร์ชัน สมมติว่าคุณมีกราฟทรัพยากร Dependency ต่อไปนี้

       A 1.0
      /     \
   B 1.0    C 1.1
     |        |
   D 1.0    D 1.1

ควรใช้ D เวอร์ชันใด ในการตอบคำถามนี้ Bzlmod ใช้อัลกอริทึมการเลือกเวอร์ชันต่ำสุด (MVS) ที่ใช้ในระบบโมดูล Go MVS จะถือว่าเวอร์ชันใหม่ทั้งหมดของโมดูลสามารถเข้ากันได้แบบย้อนหลัง จึงค่อยเลือกเวอร์ชันสูงสุดที่ระบุโดยการอ้างอิงเวอร์ชันใดๆ (D 1.1 ในตัวอย่างของเรา) วิธีนี้เรียกว่า "มินิมอล" เพราะ D 1.1 ในที่นี้เป็นเวอร์ชันมินิมอลที่อาจเป็นไปตามข้อกำหนด แม้ว่าเราจะไม่ได้เลือก D 1.2 ขึ้นไปก็ตาม ซึ่งมีประโยชน์เพิ่มเติมเนื่องจากการเลือกเวอร์ชันมีความแม่นยำสูงและทำซ้ำได้

ความละเอียดของเวอร์ชันจะดำเนินการภายในเครื่อง ไม่ใช่จากรีจิสทรี

ระดับความเข้ากันได้

โปรดทราบว่าสมมติฐานของ MVS เกี่ยวกับความเข้ากันได้แบบย้อนหลังนั้นเป็นไปได้ เนื่องจากจะมองว่าโมดูลเวอร์ชันที่เข้ากันไม่ได้แบบย้อนหลังเป็นโมดูลแยกต่างหาก ในแง่ของ SemVer นั่นหมายถึง A 1.x และ A 2.x ถือเป็นโมดูลที่แตกต่างกัน และสามารถอยู่ร่วมกันได้ในกราฟการอ้างอิงที่แก้ไขแล้ว ในทางกลับกัน กรณีนี้เป็นไปได้เพราะมีการเข้ารหัสเวอร์ชันหลักในเส้นทางแพ็กเกจใน Go จึงไม่มีความขัดแย้งเวลาคอมไพล์หรือเวลาในการลิงก์

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

ชื่อที่เก็บ

ใน Bazel ทรัพยากร Dependency ภายนอกทั้งหมดจะมีชื่อที่เก็บ บางครั้งอาจมีการใช้ทรัพยากร Dependency เดียวกันผ่านชื่อที่เก็บที่แตกต่างกัน (เช่น ทั้ง @io_bazel_skylib และ @bazel_skylib หมายถึง Bazel skylib) หรืออาจใช้ชื่อที่เก็บเดียวกันสําหรับทรัพยากร Dependency ที่แตกต่างกันในโปรเจ็กต์ต่างๆ

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

  • ชื่อที่เก็บ Canonical: ชื่อที่เก็บที่ไม่ซ้ำกันทั่วโลกสำหรับที่เก็บแต่ละรายการ นี่จะเป็นชื่อไดเรกทอรีที่มีที่เก็บ
    โครงสร้างมีดังนี้ (คำเตือน: รูปแบบชื่อ Canonical ไม่ใช่ API ที่คุณควรใช้ แต่อาจมีการเปลี่ยนแปลงได้ทุกเมื่อ)

    • สำหรับที่เก็บโมดูล Bazel: module_name~version
      (ตัวอย่าง @bazel_skylib~1.0.3)
    • สำหรับที่เก็บส่วนขยายโมดูล: module_name~version~extension_name~repo_name
      (ตัวอย่าง @rules_cc~0.0.1~cc_configure~local_config_cc)
  • ชื่อที่เก็บที่ปรากฏ: ชื่อที่เก็บที่จะใช้ในไฟล์ BUILD และ .bzl ภายในที่เก็บ ทรัพยากร Dependency เดียวกันอาจมีชื่อที่ชัดเจนในที่เก็บที่แตกต่างกัน
    มีการกำหนดดังต่อไปนี้

    • สำหรับที่เก็บโมดูล Bazel ให้ใช้ module_name โดยค่าเริ่มต้น หรือชื่อที่แอตทริบิวต์ repo_name ระบุใน bazel_dep
    • สำหรับที่เก็บส่วนขยายโมดูล: ชื่อที่เก็บที่แนะนำผ่าน use_repo

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

Dep ที่แน่นอน

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

ใช้ Dep ที่เข้มงวดตามการแมปที่เก็บ โดยพื้นฐานแล้ว การแมปที่เก็บสำหรับแต่ละที่เก็บจะมีทรัพยากร Dependency โดยตรงทั้งหมด ในขณะที่ที่เก็บอื่นๆ จะมองไม่เห็น ทรัพยากร Dependency ที่มองเห็นได้ของที่เก็บแต่ละแห่งจะกำหนดดังนี้

  • ที่เก็บโมดูล Bazel จะดูที่เก็บทั้งหมดที่แนะนำในไฟล์ MODULE.bazel ได้ ผ่าน bazel_dep และ use_repo
  • ที่เก็บส่วนขยายโมดูลจะดูทรัพยากร Dependency ที่มองเห็นได้ทั้งหมดของโมดูลซึ่งมีส่วนขยายดังกล่าว รวมถึงที่เก็บอื่นๆ ทั้งหมดที่สร้างจากส่วนขยายโมดูลเดียวกัน

รีจิสทรี

Bzlmod พบทรัพยากร Dependency ด้วยการขอข้อมูลจากรีจิสทรีของ Bazel รีจิสทรีของ Bazel เป็นเพียงฐานข้อมูลของโมดูล Bazel รีจิสทรีรูปแบบเดียวที่รองรับคือรีจิสทรีดัชนีซึ่งเป็นไดเรกทอรีภายในหรือเซิร์ฟเวอร์ HTTP แบบคงที่ตามรูปแบบที่เฉพาะเจาะจง ในอนาคต เราวางแผนที่จะเพิ่มการรองรับรีจิสทรีโมดูลเดียว ซึ่งเป็นเพียง git repos ที่มีแหล่งที่มาและประวัติของโปรเจ็กต์

รีจิสทรีดัชนี

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

รีจิสทรีดัชนีต้องมีรูปแบบดังนี้

  • /bazel_registry.json: ไฟล์ JSON ที่มีข้อมูลเมตาสำหรับรีจิสทรี เช่น
    • mirrors การระบุรายการมิเรอร์ที่จะใช้สำหรับที่เก็บถาวรต้นทาง
    • module_base_path การระบุเส้นทางฐานสำหรับโมดูลที่มีประเภท local_repository ในไฟล์ source.json
  • /modules: ไดเรกทอรีที่มีไดเรกทอรีย่อยสำหรับแต่ละโมดูลในรีจิสทรีนี้
  • /modules/$MODULE: ไดเรกทอรีที่มีไดเรกทอรีย่อยสำหรับแต่ละเวอร์ชันของโมดูลนี้ รวมถึงไฟล์ต่อไปนี้
    • metadata.json: ไฟล์ JSON ที่มีข้อมูลเกี่ยวกับโมดูล โดยมีช่องต่อไปนี้
      • homepage: URL หน้าแรกของโปรเจ็กต์
      • maintainers: รายการออบเจ็กต์ JSON โดยแต่ละรายการจะตรงกับข้อมูลของผู้บำรุงรักษาของโมดูลในรีจิสทรี โปรดทราบว่าข้อมูลนี้ไม่จำเป็นต้องเหมือนกับผู้เขียนของโปรเจ็กต์
      • versions: รายการเวอร์ชันของโมดูลนี้จะพบในรีจิสทรีนี้
      • yanked_versions: รายการเวอร์ชันที่ถูกดึงของโมดูลนี้ การดำเนินการนี้ไม่ใช่การดำเนินการในขณะนี้ แต่ในอนาคต เวอร์ชันที่ถูกดึงออกจะถูกข้ามหรือทำให้เกิดข้อผิดพลาด
  • /modules/$MODULE/$VERSION: ไดเรกทอรีที่มีไฟล์ต่อไปนี้
    • MODULE.bazel: ไฟล์ MODULE.bazel ของเวอร์ชันโมดูลนี้
    • source.json: ไฟล์ JSON ที่มีข้อมูลเกี่ยวกับวิธีดึงข้อมูลแหล่งที่มาของโมดูลเวอร์ชันนี้
      • ประเภทเริ่มต้นคือ "เก็บถาวร" โดยมีช่องต่อไปนี้
        • url: URL ของที่เก็บถาวรต้นทาง
        • integrity: การตรวจสอบข้อผิดพลาด Subresource Integrity ของที่เก็บถาวร
        • strip_prefix: คำนำหน้าไดเรกทอรีที่จะตัดเมื่อดึงข้อมูลที่เก็บถาวรต้นทาง
        • patches: รายการสตริง แต่ละสตริงตั้งชื่อไฟล์แพตช์เพื่อใช้กับที่เก็บถาวรที่ดึงข้อมูล ไฟล์แพตช์จะอยู่ภายใต้ไดเรกทอรี /modules/$MODULE/$VERSION/patches
        • patch_strip: เหมือนกับอาร์กิวเมนต์ --strip ของแพตช์ Unix
      • เปลี่ยนประเภทเพื่อใช้เส้นทางภายในกับช่องเหล่านี้ได้
        • type: local_path
        • path: เส้นทางภายในไปยังที่เก็บซึ่งคำนวณดังนี้
          • หากเส้นทางเป็นเส้นทางสัมบูรณ์ ระบบจะใช้เส้นทางตามนั้น
          • หากเส้นทางเป็นเส้นทางแบบสัมพัทธ์และ module_base_path เป็นเส้นทางสัมบูรณ์ ระบบจะจับคู่เส้นทางเป็น <module_base_path>/<path>
          • หากเส้นทางและ module_base_path เป็นเส้นทางสัมพัทธ์ทั้งคู่ ระบบจะกำหนดเส้นทางเป็น <registry_path>/<module_base_path>/<path> รีจิสทรีต้องโฮสต์อยู่ภายในเครื่องและใช้โดย --registry=file://<registry_path> มิฉะนั้น Bazel จะแสดงข้อผิดพลาด
    • patches/: ไดเรกทอรีที่ไม่บังคับซึ่งมีไฟล์แพตช์ จะใช้เมื่อ source.json มีประเภท "เก็บถาวร" เท่านั้น

สำนักทะเบียน Bazel Central

Bazel Central Registry (BCR) คือรีจิสทรีดัชนีอยู่ที่ bcr.bazel.build เนื้อหาที่เก็บได้รับการสนับสนุน โดยที่เก็บของ GitHub bazelbuild/bazel-central-registry

BCR ได้รับการดูแลรักษาโดยชุมชน Bazel และยินดีให้ผู้ร่วมให้ข้อมูลส่งคำขอแบบพุลได้ โปรดดูนโยบายและขั้นตอนของ Bazel Central Registry

นอกเหนือจากการปฏิบัติตามรูปแบบรีจิสทรีดัชนีปกติแล้ว BCR จำเป็นต้องมีไฟล์ presubmit.yml สำหรับแต่ละเวอร์ชันของโมดูล (/modules/$MODULE/$VERSION/presubmit.yml) ไฟล์นี้จะระบุเป้าหมายการสร้างและทดสอบที่จำเป็น 2-3 เป้าหมายที่สามารถใช้เพื่อตรวจสอบความถูกต้องของเวอร์ชันโมดูลนี้ได้ และใช้โดยไปป์ไลน์ CI ของ BCR เพื่อรับประกันความสามารถในการทำงานร่วมกันระหว่างโมดูลใน BCR

การเลือกรีจิสทรี

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

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

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

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

          [ A 1.1                ]
          [   * maven.dep(X 2.1) ]
          [   * maven.pom(...)   ]
              /              \
   bazel_dep /                \ bazel_dep
            /                  \
[ B 1.2                ]     [ C 1.0                ]
[   * maven.dep(X 1.2) ]     [   * maven.dep(X 2.1) ]
[   * maven.dep(Y 1.3) ]     [   * cargo.dep(P 1.1) ]
            \                  /
   bazel_dep \                / bazel_dep
              \              /
          [ D 1.4                ]
          [   * maven.dep(Z 1.4) ]
          [   * cargo.dep(Q 1.1) ]

ในตัวอย่างกราฟการอ้างอิงด้านบน A 1.1 และ B 1.2 ฯลฯ เป็นโมดูล Bazel คุณอาจคิดว่าแต่ละโมดูลเป็นไฟล์ MODULE.bazel แต่ละโมดูลจะระบุแท็กสำหรับส่วนขยายโมดูลได้ โดยบางส่วนจะระบุแท็กสำหรับส่วนขยาย "maven" และบางแท็กระบุสำหรับ "สินค้า" เมื่อสรุปกราฟการอ้างอิงนี้ (เช่น B 1.2 อาจมี bazel_dep จริงๆ ใน D 1.3 แต่ได้รับการอัปเกรดเป็น D 1.4 เนื่องจาก C) ระบบจะเรียกใช้ส่วนขยาย "maven" และอ่านแท็ก maven.* ทั้งหมด โดยใช้ข้อมูลนั้นในการตัดสินใจว่าจะสร้างที่เก็บใด ในลักษณะเดียวกันสำหรับส่วนขยาย "สินค้า"

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

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

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

หลังจากนำส่วนขยายเข้ามาในขอบเขตแล้ว คุณสามารถใช้ไวยากรณ์เพื่อระบุแท็กสำหรับส่วนขยายได้ โปรดทราบว่าแท็กต้องเป็นไปตามสคีมาที่กำหนดโดยคลาสแท็กที่เกี่ยวข้อง (ดูคำจำกัดความของส่วนขยายด้านล่าง) ต่อไปนี้เป็นตัวอย่างที่ระบุแท็ก maven.dep และ maven.pom บางรายการ

maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")

หากส่วนขยายสร้างที่เก็บที่ต้องการใช้ในโมดูล ให้ใช้คำสั่ง use_repo เพื่อประกาศ วิธีนี้ทำให้เป็นไปตามเงื่อนไข Dep ที่เข้มงวดและหลีกเลี่ยงข้อขัดแย้งของชื่อที่เก็บในเครื่อง

use_repo(
    maven,
    "org_junit_junit",
    guava="com_google_guava_guava",
)

ที่เก็บซึ่งส่วนขยายสร้างขึ้นเป็นส่วนหนึ่งของ API ของส่วนขยาย ดังนั้นจากแท็กที่คุณระบุ คุณควรทราบว่าส่วนขยาย "maven" กำลังจะสร้างที่เก็บที่เรียกว่า "com_google_guava_guava" ด้วย use_repo คุณสามารถเลือกที่จะเปลี่ยนชื่อในขอบเขตของโมดูล เช่น เป็น "guava" ที่นี่

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

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

# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
    implementation=_maven_impl,
    tag_classes={"dep": maven_dep, "pom": maven_pom},
)

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

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

# @rules_jvm_external//:extensions.bzl
load("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
  coords = []
  for mod in ctx.modules:
    coords += [dep.coord for dep in mod.tags.dep]
  output = ctx.execute(["coursier", "resolve", coords])  # hypothetical call
  repo_attrs = process_coursier(output)
  [maven_single_jar(**attrs) for attrs in repo_attrs]

ในตัวอย่างข้างต้น เราจะดูโมดูลทั้งหมดในกราฟการอ้างอิง (ctx.modules) ซึ่งแต่ละโมดูลเป็นออบเจ็กต์ bazel_module ที่ช่อง tags จะแสดงแท็ก maven.* ทั้งหมดในโมดูล จากนั้นเราเรียกใช้ยูทิลิตี CLI Coursier เพื่อติดต่อ Maven และดำเนินการแก้ปัญหา สุดท้าย เราจะใช้ผลลัพธ์การแก้ปัญหาเพื่อสร้างที่เก็บจำนวนหนึ่งโดยใช้กฎที่เก็บ maven_single_jar สมมติ