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

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

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

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

โมดูล Bazel

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

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

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

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) คุณไม่จำเป็นต้องระบุทรัพยากร Dependency แบบส่งต่อ เหมือนกับไฟล์ WORKSPACE แต่ควรรระบุเฉพาะทรัพยากร Dependency โดยตรง และระบบจะประมวลผลไฟล์ MODULE.bazel ของทรัพยากร Dependency เพื่อค้นหาทรัพยากร Dependency แบบส่งต่อโดยอัตโนมัติ

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

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

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

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

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

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

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

การแก้ปัญหาเวอร์ชัน

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

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

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

ระบบจะแก้ปัญหาเวอร์ชันในเครื่องของคุณ ไม่ใช่โดยรีจิสทรี

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

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

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

ชื่อที่เก็บ

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

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

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

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

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

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

ทรัพยากร Dependency ที่เข้มงวด

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

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

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

รีจิสทรี

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

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

รีจิสทรีดัชนีเป็นไดเรกทอรีในเครื่องหรือเซิร์ฟเวอร์ 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 ที่มีข้อมูลเกี่ยวกับวิธีดึงข้อมูลซอร์สโค้ดของโมดูลเวอร์ชันนี้
      • ประเภทเริ่มต้นคือ "archive" ที่มีช่องต่อไปนี้
        • url: URL ของไฟล์เก็บถาวรของซอร์สโค้ด
        • integrity: Subresource Integrity checksum ของไฟล์เก็บถาวร
        • strip_prefix: คำนำหน้าไดเรกทอรีที่จะนำออกเมื่อแยกไฟล์เก็บถาวรของซอร์สโค้ด
        • patches: รายการสตริง ซึ่งแต่ละรายการจะตั้งชื่อไฟล์แพตช์ที่จะใช้กับไฟล์เก็บถาวรที่แยกออกมา ไฟล์แพตช์จะอยู่ในไดเรกทอรี /modules/$MODULE/$VERSION/patches
        • patch_strip: เหมือนกับอาร์กิวเมนต์ --strip ของแพตช์ Unix
      • คุณสามารถเปลี่ยนประเภทเพื่อใช้เส้นทางในเครื่องที่มีช่องต่อไปนี้ได้
        • type: local_path
        • path: เส้นทางในเครื่องไปยัง repo ซึ่งคำนวณได้ดังนี้
          • หากเส้นทางเป็นเส้นทางสัมบูรณ์ ระบบจะใช้เส้นทางนั้น
          • หากเส้นทางเป็นเส้นทางสัมพัทธ์และ module_base_path เป็นเส้นทางสัมบูรณ์ ระบบจะแก้ปัญหาเส้นทางเป็น <module_base_path>/<path>
          • หากทั้งเส้นทางและ module_base_path เป็นเส้นทางสัมพัทธ์ ระบบจะแก้ปัญหาเส้นทางเป็น <registry_path>/<module_base_path>/<path> รีจิสทรีต้องโฮสต์ในเครื่องและใช้โดย --registry=file://<registry_path> มิฉะนั้น Bazel จะแสดงข้อผิดพลาด
    • patches/: ไดเรกทอรีที่ไม่บังคับซึ่งมีไฟล์แพตช์ โดยจะใช้ก็ต่อเมื่อ source.json มีประเภท "archive"

รีจิสทรีกลางของ Bazel

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

ชุมชน Bazel เป็นผู้ดูแล BCR และยินดีรับผู้มีส่วนร่วมส่งคำขอ Pull โปรดดู นโยบายและขั้นตอนของรีจิสทรีกลางของ Bazel

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

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

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

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

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

ส่วนขยายโมดูลจะกำหนดไว้ในไฟล์ .bzl เช่นเดียวกับกฎของ repo หรือมาโคร 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) ]

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

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

ส่วนขยายโฮสต์อยู่ในโมดูล 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")

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

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

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

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

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

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

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