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

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

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

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

โมดูล Bazel

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ใน 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 การขึ้นต่อกันเดียวกันอาจมีชื่อที่ปรากฏต่างกันใน repo ต่างๆ
    ชื่อนี้กำหนดขึ้นดังนี้

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

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

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

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

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

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

รีจิสทรี

Bzlmod ค้นพบการขึ้นต่อกันโดยการขอข้อมูลจาก รีจิสทรี 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 ของไฟล์เก็บถาวร
        • 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 และยินดีรับผู้มีส่วนร่วมส่งคำขอรับการผสาน โปรดดู นโยบายและขั้นตอนของรีจิสทรีกลางของ Bazel

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

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

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

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

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

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

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

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

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

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