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

7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

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

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

โมดูล Bazel

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ชื่อที่เก็บ

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

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

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

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

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

ไลบรารีที่ต้องติดตั้ง

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

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

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

รีจิสทรี

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

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

รีจิสทรีกลางของ Bazel (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 จะทำงานร่วมกันได้

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

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

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

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

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

          [ 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 เพื่อประกาศ การดำเนินการนี้เพื่อให้เป็นไปตามเงื่อนไขของการตรวจสอบที่เข้มงวดและหลีกเลี่ยงข้อขัดแย้งเกี่ยวกับชื่อของที่เก็บในพื้นที่

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

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

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

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