โมดูล Bazel

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

โมดูลต้องมีไฟล์ MODULE.bazel ที่รูทที่เก็บ ไฟล์นี้เป็นไฟล์ Manifest ของโมดูล โดยประกาศชื่อ เวอร์ชัน รายการทรัพยากร 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

Bazel เริ่มด้วยการอ่านไฟล์ MODULE.bazel ของโมดูลรูท แล้วส่งคำขอไฟล์ MODULE.bazel ของทรัพยากร Dependency จากรีจิสทรีของ Bazel ซ้ำๆ จนกว่าจะพบกราฟทรัพยากร Dependency ทั้งหมด

จากนั้น Bazel จะเลือกแต่ละโมดูล 1 เวอร์ชันเพื่อนำมาใช้โดยค่าเริ่มต้น Bazel แสดงแต่ละโมดูลด้วยที่เก็บ และปรึกษาสำนักทะเบียนอีกครั้งเพื่อดูวิธีกำหนดที่เก็บแต่ละห้อง

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

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

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

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

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

การเลือกเวอร์ชัน

ลองพิจารณาปัญหาเกี่ยวกับ Diamond 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 จะสร้างกระบวนการเลือกเวอร์ชันที่มีความแม่นยำสูงและทำซ้ำได้

เวอร์ชันแย้ง

รีจิสทรีสามารถประกาศว่าเวอร์ชันหนึ่งๆ ถูกยกเลิกได้หากควรหลีกเลี่ยงเวอร์ชันนั้น (เช่น เพราะมีช่องโหว่ด้านความปลอดภัย) Bazel แสดงข้อผิดพลาด เมื่อเลือกโมดูลที่แยกออกมา หากต้องการแก้ไขข้อผิดพลาดนี้ ให้อัปเกรดเป็นเวอร์ชันใหม่ที่ไม่ได้เพิกถอน หรือใช้ Flag --allow_yanked_versions เพื่ออนุญาตเวอร์ชันที่เพิกถอนอย่างชัดเจน

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

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

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

ลบล้าง

ระบุการลบล้างในไฟล์ MODULE.bazel เพื่อเปลี่ยนลักษณะการทำงานของความละเอียดโมดูล Bazel มีเพียงการลบล้างของโมดูลรูทเท่านั้นที่จะมีผล หากใช้โมดูลทรัพยากร Dependency ระบบจะไม่สนใจการลบล้าง

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

การลบล้างเวอร์ชันเดียว

single_version_override ใช้เพื่อวัตถุประสงค์หลายอย่าง ดังนี้

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

แอตทริบิวต์เหล่านี้เป็นตัวเลือกทั้งหมด และสามารถนำไปผสมและจับคู่ได้

การลบล้างแบบหลายเวอร์ชัน

คุณสามารถระบุ multiple_version_override เพื่ออนุญาตให้โมดูลเดียวกันหลายเวอร์ชันอยู่ร่วมกันได้ในกราฟความสัมพันธ์ที่แก้ไขแล้ว

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

ตัวอย่างเช่น หากมีเวอร์ชัน 1.1, 1.3, 1.5, 1.7 และ 2.0 ในกราฟความเกี่ยวข้องก่อนการแก้ไข และเวอร์ชันหลักคือระดับความเข้ากันได้ ให้ทำดังนี้

  • การลบล้างหลายเวอร์ชันที่อนุญาต 1.3, 1.7 และ 2.0 จะส่งผลให้1.1ได้รับการอัปเกรดเป็น 1.3, 1.5 ได้รับการอัปเกรดเป็น 1.7 และเวอร์ชันอื่นๆ ยังคงเหมือนเดิม
  • การลบล้างหลายเวอร์ชันที่อนุญาต 1.5 และ 2.0 ทําให้เกิดข้อผิดพลาด เนื่องจาก 1.7 ไม่มีเวอร์ชันที่สูงกว่าซึ่งมีระดับความเข้ากันได้เดียวกันให้อัปเกรด
  • การลบล้างหลายเวอร์ชันที่อนุญาต 1.9 และ 2.0 จะทำให้เกิดข้อผิดพลาด เนื่องจาก 1.9 ไม่อยู่ในกราฟความเกี่ยวข้องก่อนการแก้ไข

นอกจากนี้ ผู้ใช้ยังลบล้างรีจิสทรีโดยใช้แอตทริบิวต์ registry ได้ ซึ่งคล้ายกับการลบล้างเวอร์ชันเดียว

การลบล้างที่ไม่ใช่รีจิสทรี

การลบล้างที่ไม่ใช่รีจิสทรีจะนำโมดูลออกจากการแก้ไขเวอร์ชันโดยสมบูรณ์ Bazel จะไม่ขอไฟล์ MODULE.bazel เหล่านี้จากรีจิสทรี แต่จะขอจากรีโปโดยตรง

Bazel รองรับการลบล้างที่ไม่ใช่รีจิสทรีต่อไปนี้

กำหนดที่เก็บที่ไม่ได้แสดงถึงโมดูล Bazel

เมื่อใช้ bazel_dep คุณจะกำหนดที่เก็บที่แสดงถึงโมดูล Bazel อื่นๆ ได้ บางครั้งอาจต้องกำหนดที่เก็บที่ไม่ได้เป็นตัวแทนของโมดูล Bazel เช่น โมดูลที่มีไฟล์ JSON ธรรมดาซึ่งจะอ่านเป็นข้อมูล

ในกรณีนี้ คุณใช้คำสั่ง use_repo_rule เพื่อกำหนดที่เก็บโดยตรงได้ด้วยการเรียกใช้กฎที่เก็บ เฉพาะโมดูลที่กําหนดค่าไว้เท่านั้นที่จะมองเห็นที่เก็บข้อมูลนี้

ขั้นสูง วิธีนี้ใช้กลไกเดียวกันกับส่วนขยายโมดูล ซึ่งช่วยให้คุณกำหนดข้อความได้อย่างยืดหยุ่นมากขึ้น

ชื่อที่เก็บและข้อกำหนดที่เข้มงวด

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

ชื่อ Canonical ของที่เก็บสำรองโมดูลจะเป็น module_name~version (เช่น bazel_skylib~1.0.3) หรือ module_name~ (เช่น bazel_features~) ขึ้นอยู่กับว่าในกราฟทรัพยากร Dependency มีหลายเวอร์ชันหรือไม่ (ดู multiple_version_override) โปรดทราบว่ารูปแบบชื่อ Canonical ไม่ใช่ API ที่ควรต้องใช้และอาจเปลี่ยนแปลงได้ทุกเมื่อ แทนที่จะฮาร์ดโค้ดชื่อ Canonical ให้ใช้วิธีที่รองรับเพื่อรับชื่อจาก Bazel โดยตรง ดังนี้ * ในไฟล์ "BUILD และ .bzl" ให้ใช้ Label.repo_name บนอินสแตนซ์ Label ที่สร้างขึ้นจากสตริงป้ายกำกับจากชื่อที่ปรากฏของที่เก็บ เช่น Label("@bazel_skylib").repo_name * เมื่อค้นหาไฟล์รันไทม์ ให้ใช้ $(rlocationpath ...) หรือไลบรารีไฟล์รันไทม์รายการใดรายการหนึ่งใน @bazel_tools//tools/{bash,cpp,java}/runfiles หรือสำหรับชุดกฎ rules_foo ให้ใช้ @rules_foo//foo/runfiles * เมื่อโต้ตอบกับ Bazel จากเครื่องมือภายนอก เช่น IDE หรือเซิร์ฟเวอร์ภาษา ให้ใช้คำสั่ง bazel mod dump_repo_mapping เพื่อรับการแมปจากชื่อที่เห็นได้ชัดไปจนถึงชื่อ Canonical สำหรับชุดที่เก็บหนึ่งๆ

ส่วนขยายโมดูลยังสามารถสร้างที่เก็บเพิ่มเติม ในขอบเขตที่มองเห็นได้ของโมดูลได้อีกด้วย