ไฟล์ Bazel Lock

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

การสร้าง Lockfile

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

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

การใช้ 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 นี้อย่างชัดเจน และใช้เพื่อเพิ่มประสิทธิภาพเท่านั้น แม้ว่าจะลบ 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 เองเป็นทรัพยากร Dependency ของ บิลด์ 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