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

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

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

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

โมดูล Bazel

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

โดยพื้นฐานแล้ว โมดูลคือโปรเจ็กต์ Bazel ที่มีได้หลายเวอร์ชัน ซึ่งแต่ละเวอร์ชัน จะเผยแพร่ข้อมูลเมตาเกี่ยวกับโมดูลอื่นๆ ที่ขึ้นอยู่กับโมดูลนั้น ซึ่งคล้ายกับแนวคิดที่คุ้นเคยในระบบการจัดการทรัพยากร Dependency อื่นๆ เช่น Artifact ของ Maven, Package ของ npm, Crate ของ Cargo, Module ของ 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 ดังนี้

       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 จึงไม่มีความขัดแย้งในเวลาคอมไพล์หรือเวลาลิงก์

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

ชื่อที่เก็บ

ใน 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 ภายในที่เก็บ การขึ้นต่อกันเดียวกันอาจมีชื่อที่ปรากฏแตกต่างกันในที่เก็บต่างๆ
    โดยมีวิธีพิจารณาดังนี้

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

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

การขึ้นต่อกันอย่างเข้มงวด

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

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

  • ที่เก็บโมดูล Bazel จะเห็นที่เก็บทั้งหมดที่ระบุไว้ในไฟล์ MODULE.bazel ผ่าน bazel_dep และ use_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 ที่มีข้อมูลเกี่ยวกับวิธีดึงข้อมูล แหล่งที่มาของโมดูลเวอร์ชันนี้
      • ประเภทเริ่มต้นคือ "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

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

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

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

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

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

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