ไฟล์ Bazel Lock

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

การสร้าง Lockfile

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

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

การใช้ Lockfile

คุณสามารถควบคุม Lockfile ได้โดยใช้แฟล็ก --lockfile_mode เพื่อ ปรับแต่งลักษณะการทำงานของ Bazel เมื่อสถานะโปรเจ็กต์แตกต่างจาก Lockfile โหมดที่ใช้ได้มีดังนี้

  • update (ค่าเริ่มต้น): ใช้ข้อมูลที่มีอยู่ใน Lockfile เพื่อข้ามการดาวน์โหลดไฟล์รีจิสทรีที่รู้จัก และหลีกเลี่ยงการประเมินส่วนขยายซ้ำซึ่งผลลัพธ์ยังคงเป็นปัจจุบัน หากไม่มีข้อมูล ระบบจะเพิ่มข้อมูลลงใน Lockfile ในโหมดนี้ Bazel จะหลีกเลี่ยงการรีเฟรชข้อมูลที่เปลี่ยนแปลงได้ เช่น เวอร์ชันที่ยกเลิกแล้ว สำหรับการขึ้นต่อกันที่ไม่มีการเปลี่ยนแปลง
  • refresh: เหมือนกับ update แต่ระบบจะรีเฟรชข้อมูลที่เปลี่ยนแปลงได้ทุกครั้งเมื่อเปลี่ยนไปใช้โหมดนี้ และรีเฟรชทุกๆ ชั่วโมงโดยประมาณขณะอยู่ในโหมดนี้
  • error: เหมือนกับ update แต่หากไม่มีข้อมูลหรือข้อมูลล้าสมัย Bazel จะล้มเหลวและแสดงข้อผิดพลาด โหมดนี้จะไม่เปลี่ยนแปลง Lockfile หรือส่งคำขอเครือข่ายระหว่างการแก้ปัญหา ส่วนขยายโมดูลที่ทำเครื่องหมายตัวเองเป็น reproducible อาจยังคงส่งคำขอเครือข่าย แต่คาดว่าจะให้ผลลัพธ์เดียวกันเสมอ
  • off: ระบบจะไม่ตรวจสอบหรืออัปเดต Lockfile

ประโยชน์ของ Lockfile

Lockfile มีประโยชน์หลายประการและสามารถใช้ได้หลายวิธี ดังนี้

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

  • การแก้ปัญหาแบบเพิ่มทีละส่วนอย่างรวดเร็ว Lockfile ช่วยให้ Bazel หลีกเลี่ยงการดาวน์โหลดไฟล์รีจิสทรีที่ใช้ไปแล้วในบิลด์ก่อนหน้า ซึ่งจะช่วยเพิ่มประสิทธิภาพของบิลด์ได้อย่างมาก โดยเฉพาะอย่างยิ่งในสถานการณ์ที่การแก้ปัญหาอาจใช้เวลานาน

  • ความเสถียรและการลดความเสี่ยง Lockfile ช่วยรักษาความเสถียรด้วยการป้องกันการอัปเดตที่ไม่คาดคิดหรือการเปลี่ยนแปลงที่ทำให้เกิดข้อผิดพลาดในไลบรารีภายนอก การล็อกการขึ้นต่อกันไว้ที่เวอร์ชันที่เฉพาะเจาะจงจะช่วยลดความเสี่ยงในการเกิดข้อบกพร่องเนื่องจากการอัปเดตที่ไม่เข้ากันหรือยังไม่ได้ทดสอบ

Lockfile ที่ซ่อนอยู่

นอกจากนี้ Bazel ยังมี Lockfile อีกไฟล์ที่ "$(bazel info output_base)"/MODULE.bazel.lock โดยไม่ได้ระบุรูปแบบและเนื้อหาของ Lockfile นี้อย่างชัดเจน และใช้เพื่อเพิ่มประสิทธิภาพเท่านั้น แม้ว่าจะลบพร้อมกับเอาต์พุตเบสผ่าน bazel clean --expunge ได้ แต่การดำเนินการดังกล่าวถือเป็นข้อบกพร่องใน Bazel เองหรือส่วนขยายโมดูล

เนื้อหาของ Lockfile

Lockfile มีข้อมูลที่จำเป็นทั้งหมดเพื่อระบุว่าสถานะโปรเจ็กต์มีการเปลี่ยนแปลงหรือไม่ นอกจากนี้ ยังมีผลลัพธ์ของการสร้างโปรเจ็กต์ในสถานะปัจจุบันด้วย Lockfile ประกอบด้วย 2 ส่วนหลัก ดังนี้

  1. แฮชของไฟล์ระยะไกลทั้งหมดที่เป็นอินพุตสำหรับการแก้ปัญหาโมดูล
  2. สำหรับส่วนขยายโมดูลแต่ละรายการ Lockfile จะมีอินพุตที่ส่งผลต่อส่วนขยายนั้น ซึ่งแสดงด้วย bzlTransitiveDigest, usagesDigest และฟิลด์อื่นๆ รวมถึง เอาต์พุตของการเรียกใช้ส่วนขยายนั้น ซึ่งเรียกว่า generatedRepoSpecs

ตัวอย่างต่อไปนี้แสดงโครงสร้างของ Lockfile พร้อมคำอธิบายสำหรับแต่ละส่วน

{
  "lockFileVersion": 10,
  "registryFileHashes": {
    "https://bcr.bazel.build/bazel_registry.json": "8a28e4af...5d5b3497",
    "https://bcr.bazel.build/modules/foo/1.0/MODULE.bazel": "7cd0312e...5c96ace2",
    "https://bcr.bazel.build/modules/foo/2.0/MODULE.bazel": "70390338... 9fc57589",
    "https://bcr.bazel.build/modules/foo/2.0/source.json": "7e3a9adf...170d94ad",
    "https://registry.mycorp.com/modules/foo/1.0/MODULE.bazel": "not found",
    ...
  },
  "selectedYankedVersions": {
    "foo@2.0": "Yanked for demo purposes"
  },
  "moduleExtensions": {
    "//:extension.bzl%lockfile_ext": {
      "general": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    },
    "//:extension.bzl%lockfile_ext2": {
      "os:macos": {
        "bzlTransitiveDigest": "oWDzxG/aLnyY6Ubrfy....+Jp6maQvEPxn0pBM=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      },
      "os:linux": {
        "bzlTransitiveDigest": "eWDzxG/aLsyY3Ubrto....+Jp4maQvEPxn0pLK=",
        "usagesDigest": "aLmqbvowmHkkBPve05y....yDNGN7oh7r3QIZTZs=",
        ...,
        "generatedRepoSpecs": {
          "hello": {
            "bzlFile": "@@//:extension.bzl",
            ...
          }
        }
      }
    }
  }
}

แฮชของไฟล์รีจิสทรี

ส่วน registryFileHashes มีแฮชของไฟล์ทั้งหมดจากรีจิสทรีระยะไกลที่เข้าถึงระหว่างการแก้ปัญหาโมดูล เนื่องจากอัลกอริทึมการแก้ปัญหาเป็นแบบกำหนดได้ทั้งหมดเมื่อได้รับอินพุตเดียวกัน และระบบจะแฮชอินพุตระยะไกลทั้งหมด การดำเนินการนี้จะช่วยให้มั่นใจได้ว่าผลลัพธ์การแก้ปัญหาจะทำซ้ำได้ทั้งหมด พร้อมทั้งหลีกเลี่ยงการทำซ้ำข้อมูลระยะไกลมากเกินไปใน Lockfile โปรดทราบว่าการดำเนินการนี้ยังกำหนดให้ต้องบันทึกเมื่อรีจิสทรีหนึ่งๆ ไม่มีโมดูลที่เฉพาะเจาะจง แต่รีจิสทรีที่มีลำดับความสำคัญต่ำกว่ามี (ดูรายการ "ไม่พบ" ในตัวอย่าง) คุณอัปเดตข้อมูลที่เปลี่ยนแปลงได้โดยธรรมชาติได้ผ่าน bazel mod deps --lockfile_mode=refresh

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

เวอร์ชันที่ยกเลิกแล้วที่เลือก

ส่วน selectedYankedVersions มีเวอร์ชันที่ยกเลิกแล้วของโมดูลที่การแก้ปัญหาโมดูลเลือก เนื่องจากโดยปกติแล้วการดำเนินการนี้จะส่งผลให้เกิดข้อผิดพลาดเมื่อพยายามสร้าง ดังนั้นส่วนนี้จึงไม่ว่างเปล่าก็ต่อเมื่อมีการอนุญาตเวอร์ชันที่ยกเลิกแล้วอย่างชัดเจนผ่าน --allow_yanked_versions หรือ BZLMOD_ALLOW_YANKED_VERSIONS

เราจำเป็นต้องใช้ฟิลด์นี้เนื่องจากข้อมูลเวอร์ชันที่ยกเลิกแล้วเปลี่ยนแปลงได้โดยธรรมชาติและจึงอ้างอิงด้วยแฮชไม่ได้ เมื่อเทียบกับไฟล์โมดูล คุณอัปเดตข้อมูลนี้ได้ผ่าน bazel mod deps --lockfile_mode=refresh

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

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

หากส่วนขยายไม่ขึ้นอยู่กับระบบปฏิบัติการหรือประเภทสถาปัตยกรรม ส่วนนี้จะมีรายการ "ทั่วไป" เพียงรายการเดียว ไม่เช่นนั้น ระบบจะรวมรายการหลายรายการซึ่งตั้งชื่อตามระบบปฏิบัติการ สถาปัตยกรรม หรือทั้ง 2 อย่าง โดยแต่ละรายการจะสอดคล้องกับผลลัพธ์ของการประเมินส่วนขยายในรายละเอียดเหล่านั้น

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

  1. bzlTransitiveDigest คือไดเจสต์ของการใช้งานส่วนขยายและไฟล์ .bzl ที่โหลดแบบทรานซิทีฟโดยส่วนขยายนั้น
  2. usagesDigest คือไดเจสต์ การใช้งาน ส่วนขยายในกราฟการขึ้นต่อกัน ซึ่งรวมถึงแท็กทั้งหมด
  3. ฟิลด์อื่นๆ ที่ไม่ได้ระบุซึ่งติดตามอินพุตอื่นๆ ไปยังส่วนขยาย เช่น เนื้อหาของไฟล์หรือไดเรกทอรีที่ส่วนขยายอ่านหรือตัวแปรสภาพแวดล้อมที่ส่วนขยายใช้
  4. generatedRepoSpecs จะเข้ารหัสที่เก็บที่ส่วนขยายสร้างขึ้นด้วยอินพุตปัจจุบัน
  5. ฟิลด์ moduleExtensionMetadata ที่ไม่บังคับจะมีข้อมูลเมตาที่ส่วนขยายให้ไว้ เช่น โมดูลรูทควรนำเข้าที่เก็บบางรายการที่ส่วนขยายสร้างขึ้นผ่าน use_repo หรือไม่ ข้อมูลนี้จะขับเคลื่อนคำสั่ง bazel mod tidy

ส่วนขยายโมดูลสามารถเลือกไม่ให้รวมอยู่ใน Lockfile ได้โดยตั้งค่าข้อมูลเมตาที่แสดงผลด้วย reproducible = True การดำเนินการนี้จะแสดงให้เห็นว่าส่วนขยายสัญญาว่าจะสร้างที่เก็บเดียวกันเสมอเมื่อได้รับอินพุตเดียวกัน

แนวทางปฏิบัติแนะนำ

โปรดพิจารณาแนวทางปฏิบัติแนะนำต่อไปนี้เพื่อใช้ประโยชน์จากฟีเจอร์ Lockfile ให้ได้มากที่สุด

  • อัปเดต Lockfile เป็นประจำเพื่อแสดงการเปลี่ยนแปลงในการขึ้นต่อกันหรือการกำหนดค่าของโปรเจ็กต์ การดำเนินการนี้จะช่วยให้บิลด์ในครั้งต่อๆ ไปอิงตามชุดการขึ้นต่อกันที่ถูกต้องและเป็นปัจจุบันที่สุด หากต้องการล็อกส่วนขยายทั้งหมดพร้อมกัน ให้เรียกใช้ bazel mod deps --lockfile_mode=update

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

  • ใช้ bazelisk เพื่อเรียกใช้ Bazel และรวมไฟล์ .bazelversion ไว้ในการควบคุมเวอร์ชันซึ่งระบุเวอร์ชัน Bazel ที่สอดคล้องกับ Lockfile เนื่องจาก Bazel เองเป็นการขึ้นต่อกันของ บิลด์ Lockfile จึงเป็นเวอร์ชัน Bazel ที่เฉพาะเจาะจง และจะ เปลี่ยนแปลงแม้แต่ระหว่าง รุ่น Bazel ที่เข้ากันได้แบบย้อนหลัง การใช้ bazelisk จะช่วยให้มั่นใจได้ว่านักพัฒนาแอปทุกคนใช้ Bazel เวอร์ชันที่ตรงกับ Lockfile

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

ความขัดแย้งในการผสาน

รูปแบบ Lockfile ได้รับการออกแบบมาเพื่อลดความขัดแย้งในการผสาน แต่ความขัดแย้งก็ยังเกิดขึ้นได้

การแก้ปัญหาอัตโนมัติ

Bazel มีไดรเวอร์การผสาน Git ที่กำหนดเอง เพื่อช่วยแก้ปัญหาความขัดแย้งเหล่านี้โดยอัตโนมัติ

ตั้งค่าไดรเวอร์โดยเพิ่มบรรทัดนี้ลงในไฟล์ .gitattributes ที่รูทของที่เก็บ Git

# A custom merge driver for the Bazel lockfile.
# https://bazel.build/external/lockfile#automatic-resolution
MODULE.bazel.lock merge=bazel-lockfile-merge

จากนั้นนักพัฒนาแอปแต่ละคนที่ต้องการใช้ไดรเวอร์จะต้องลงทะเบียนไดรเวอร์เพียงครั้งเดียวโดยทำตามขั้นตอนต่อไปนี้

  1. ติดตั้ง jq (1.5 ขึ้นไป)
  2. เรียกใช้คำสั่งต่อไปนี้
jq_script=$(curl https://raw.githubusercontent.com/bazelbuild/bazel/master/scripts/bazel-lockfile-merge.jq)
printf '%s\n' "${jq_script}" | less # to optionally inspect the jq script
git config --global merge.bazel-lockfile-merge.name   "Merge driver for the Bazel lockfile (MODULE.bazel.lock)"
git config --global merge.bazel-lockfile-merge.driver "jq -s '${jq_script}' -- %O %A %B > %A.jq_tmp && mv %A.jq_tmp %A"

การแก้ปัญหาด้วยตนเอง

คุณสามารถแก้ปัญหาความขัดแย้งในการผสานอย่างง่ายในฟิลด์ registryFileHashes และ selectedYankedVersions ได้อย่างปลอดภัยโดยเก็บรายการทั้งหมดจากทั้ง 2 ฝั่งของความขัดแย้งไว้

ส่วนความขัดแย้งในการผสานประเภทอื่นๆ ไม่ควรแก้ปัญหาด้วยตนเอง แต่ให้ทำดังนี้แทน

  1. กู้คืนสถานะก่อนหน้าของ Lockfile ผ่าน git reset MODULE.bazel.lock && git checkout MODULE.bazel.lock
  2. แก้ปัญหาความขัดแย้งในไฟล์ MODULE.bazel
  3. เรียกใช้ bazel mod deps เพื่ออัปเดต Lockfile