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

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

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

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

โมดูล Bazel

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

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

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

MODULE.bazel

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

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

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

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

ชื่อที่เก็บ

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

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

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

รีจิสทรี

Bzlmod จะค้นหาการขึ้นต่อกันโดยการขอข้อมูลจากรีจิสทรีของ Bazel รีจิสทรี Bazel เป็นเพียงฐานข้อมูลของโมดูล Bazel รูปแบบรีจิสทรีเดียวที่รองรับคือรีจิสทรีดัชนี ซึ่งเป็นไดเรกทอรีในเครื่องหรือเซิร์ฟเวอร์ HTTP แบบคงที่ที่ใช้รูปแบบที่เฉพาะเจาะจง ในอนาคต เราวางแผนที่จะเพิ่มการรองรับรีจิสทรีแบบโมดูลเดียว ซึ่งเป็นเพียง ที่เก็บข้อมูล 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: ผลรวมตรวจสอบ ความสมบูรณ์ของทรัพยากรย่อย ของไฟล์เก็บถาวร
        • 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 มีประเภท "archive"

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

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

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

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

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

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

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

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

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

          [ 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" และแท็กบางรายการจะระบุไว้สำหรับ "cargo" เมื่อกราฟการขึ้นต่อกันนี้เสร็จสมบูรณ์ (เช่น B 1.2 อาจมี bazel_dep ใน D 1.3 แต่ได้รับการอัปเกรดเป็น D 1.4 เนื่องจาก C) ระบบจะเรียกใช้ส่วนขยาย "maven" และอ่านแท็ก maven.* ทั้งหมด โดยใช้ข้อมูลในนั้นเพื่อตัดสินใจว่าจะสร้างที่เก็บใด เช่นเดียวกับส่วนขยาย "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")

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

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

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

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

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

# @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 สมมติ