สร้างโปรแกรมด้วย Bazel

รายงานปัญหา ดูแหล่งที่มา

หน้านี้จะพูดถึงวิธีสร้างโปรแกรมด้วย Bazel, ไวยากรณ์คำสั่งบิลด์ และไวยากรณ์รูปแบบเป้าหมาย

คู่มือเริ่มใช้งานฉบับย่อ

หากต้องการเรียกใช้ Bazel ให้ไปที่ไดเรกทอรีพื้นที่ทำงานพื้นฐานหรือไดเรกทอรีย่อย และพิมพ์ bazel โปรดดูสร้างหากคุณต้องสร้างพื้นที่ทำงานใหม่

bazel help
                             [Bazel release bazel version]
Usage: bazel command options ...

คำสั่งที่ใช้ได้

  • analyze-profile: วิเคราะห์ข้อมูลโปรไฟล์บิลด์
  • aquery: ดำเนินการค้นหาในกราฟการดำเนินการหลังการวิเคราะห์
  • build: สร้างเป้าหมายที่ระบุ
  • canonicalize-flags: กำหนดแฟล็ก Bazel
  • clean: นำไฟล์เอาต์พุตออกและเลือกหยุดเซิร์ฟเวอร์
  • cquery: ดำเนินการค้นหากราฟทรัพยากร Dependency แบบหลังการวิเคราะห์
  • dump: ดัมพ์สถานะภายในของกระบวนการของเซิร์ฟเวอร์ Bazel
  • help: พิมพ์ความช่วยเหลือสำหรับคำสั่งหรือดัชนี
  • info: แสดงข้อมูลรันไทม์เกี่ยวกับเซิร์ฟเวอร์ Bazel
  • fetch: ดึงข้อมูลทรัพยากร Dependency ภายนอกทั้งหมดของเป้าหมาย
  • mobile-install: ติดตั้งแอปในอุปกรณ์เคลื่อนที่
  • query: ดำเนินการค้นหากราฟการอ้างอิง
  • run: เรียกใช้เป้าหมายที่ระบุ
  • shutdown: หยุดเซิร์ฟเวอร์ Bazel
  • test: สร้างและเรียกใช้เป้าหมายการทดสอบที่ระบุ
  • version: พิมพ์ข้อมูลเวอร์ชันสำหรับ Bazel

การขอความช่วยเหลือ

  • bazel help command: ความช่วยเหลือเกี่ยวกับการพิมพ์และตัวเลือกสำหรับ command
  • bazel helpstartup_options: ตัวเลือกสำหรับ JVM ที่โฮสต์ Bazel
  • bazel helptarget-syntax: อธิบายไวยากรณ์สำหรับการระบุเป้าหมาย
  • bazel help info-keys: แสดงรายการคีย์ที่คำสั่งข้อมูลใช้

เครื่องมือ bazel ทำหน้าที่หลายอย่างที่เรียกว่าคำสั่ง ตัวกรองที่ใช้บ่อยที่สุดคือ bazel build และ bazel test คุณสามารถเรียกดูข้อความช่วยเหลือออนไลน์ โดยใช้ bazel help

การสร้างเป้าหมาย 1 รายการ

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

หากต้องการสร้างโปรแกรมด้วย Bazel ให้พิมพ์ bazel build ตามด้วยเป้าหมายที่ต้องการสร้าง

bazel build //foo

หลังจากออกคำสั่งเพื่อสร้าง //foo แล้ว คุณจะเห็นเอาต์พุตในลักษณะนี้

INFO: Analyzed target //foo:foo (14 packages loaded, 48 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 9.905s, Critical Path: 3.25s
INFO: Build completed successfully, 6 total actions

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

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

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

หากคุณพิมพ์คำสั่งเดิมอีกครั้ง บิลด์จะเสร็จสิ้นเร็วขึ้นมาก

bazel build //foo
INFO: Analyzed target //foo:foo (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //foo:foo up-to-date:
  bazel-bin/foo/foo
INFO: Elapsed time: 0.144s, Critical Path: 0.00s
INFO: Build completed successfully, 1 total action

นี่คือnull build เพราะไม่มีอะไรเปลี่ยนแปลง จึงไม่มีแพ็กเกจให้โหลดซ้ำ และไม่มีขั้นตอนของบิลด์ให้ดำเนินการ หากมีการเปลี่ยนแปลงบางอย่างใน "foo" หรือทรัพยากร Dependency ทาง Bazel จะดำเนินการสร้างบางอย่างอีกครั้ง หรือสร้างบิลด์ที่เพิ่มขึ้น

การสร้างเป้าหมายหลายเป้าหมาย

Bazel สามารถระบุเป้าหมายที่จะสร้างได้หลายวิธี สถานการณ์เหล่านี้เรียกรวมกันว่ารูปแบบเป้าหมาย ไวยากรณ์นี้จะใช้ในคำสั่งอย่างเช่น build, test หรือ query

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

รูปแบบเป้าหมายทั้งหมดที่เริ่มต้นด้วย // ได้รับการแก้ไขซึ่งสัมพันธ์กับพื้นที่ทำงานปัจจุบัน

//foo/bar:wiz เฉพาะเป้าหมายเดียว //foo/bar:wiz
//foo/bar เทียบเท่ากับ //foo/bar:bar
//foo/bar:all เป้าหมายกฎทั้งหมดในแพ็กเกจ foo/bar
//foo/... กฎทั้งหมดจะกำหนดเป้าหมายแพ็กเกจทั้งหมดภายใต้ไดเรกทอรี foo
//foo/...:all กฎทั้งหมดจะกำหนดเป้าหมายแพ็กเกจทั้งหมดภายใต้ไดเรกทอรี foo
//foo/...:* เป้าหมายทั้งหมด (กฎและไฟล์) ในทุกแพ็กเกจภายใต้ไดเรกทอรี foo
//foo/...:all-targets เป้าหมายทั้งหมด (กฎและไฟล์) ในทุกแพ็กเกจภายใต้ไดเรกทอรี foo
//... เป้าหมายทั้งหมดในแพ็กเกจในพื้นที่ทำงาน โดยไม่รวมเป้าหมายจากที่เก็บภายนอก
//:all เป้าหมายทั้งหมดในแพ็กเกจระดับบนสุด หากมีไฟล์ "BUILD" ที่รูทของพื้นที่ทำงาน

รูปแบบเป้าหมายที่ไม่ได้ขึ้นต้นด้วย // จะได้รับการแก้ไขโดยสัมพันธ์กับไดเรกทอรีที่ใช้งานอยู่ปัจจุบัน ตัวอย่างเหล่านี้สมมติว่าเป็นไดเรกทอรีที่ใช้งานได้ของ foo

:foo เทียบเท่ากับ //foo:foo
bar:wiz เทียบเท่ากับ //foo/bar:wiz
bar/wiz เทียบเท่ากับสิ่งต่อไปนี้
  • //foo/bar/wiz:wiz หาก foo/bar/wiz เป็นแพ็กเกจ
  • //foo/bar:wiz หาก foo/bar เป็นแพ็กเกจ
  • //foo:bar/wiz เมื่อสั่งซื้อนอกเหนือจากมูลค่าที่กำหนด
bar:all เทียบเท่ากับ //foo/bar:all
:all เทียบเท่ากับ //foo:all
...:all เทียบเท่ากับ //foo/...:all
... เทียบเท่ากับ //foo/...:all
bar/...:all เทียบเท่ากับ //foo/bar/...:all

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

นอกจากนี้ Bazel จะไม่ทำตามลิงก์สัญลักษณ์เมื่อประเมินรูปแบบเป้าหมายที่เกิดซ้ำในไดเรกทอรีที่มีไฟล์ชื่อดังนี้ DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN

foo/... คือไวลด์การ์ดเหนือแพ็กเกจ ซึ่งระบุแพ็กเกจทั้งหมดซ้อนกันใต้ไดเรกทอรี foo (สำหรับรูททั้งหมดของเส้นทางแพ็กเกจ) :all คือไวลด์การ์ดสำหรับเป้าหมายที่ตรงกันกับกฎทั้งหมดภายในแพ็กเกจ ค่า 2 รายการนี้อาจใช้ด้วยกันได้ เช่น ใน foo/...:all และเมื่อมีการใช้ไวลด์การ์ดทั้ง 2 แบบ ชื่อย่ออาจย่อเป็น foo/...

นอกจากนี้ :* (หรือ :all-targets) คือไวลด์การ์ดที่ตรงกับทุกเป้าหมายในแพ็กเกจที่ตรงกัน ซึ่งรวมถึงไฟล์ที่ปกติแล้วไม่ได้สร้างขึ้นด้วยกฎใดๆ เช่น ไฟล์ _deploy.jar ที่เชื่อมโยงกับกฎ java_binary

ซึ่งหมายความว่า :* แสดงถึงชุดย่อยของ :all แม้อาจสับสน แต่รูปแบบคำสั่งนี้อนุญาตให้ใช้ไวลด์การ์ด :all ที่คุ้นเคยกับบิลด์ทั่วไป ซึ่งไม่ต้องการให้สร้างเป้าหมายอย่าง _deploy.jar

นอกจากนี้ Bazel ยังใช้เครื่องหมายทับแทนเครื่องหมายทวิภาคที่ไวยากรณ์ของป้ายกำกับกำหนดได้ ซึ่งมักจะสะดวกเมื่อใช้การขยายชื่อไฟล์ Bash เช่น foo/bar/wiz เทียบเท่ากับ //foo/bar:wiz (หากมีแพ็กเกจ foo/bar) หรือ //foo:bar/wiz (หากมีแพ็กเกจ foo)

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

bazel build foo/... bar/...

หมายถึง "สร้างเป้าหมายทั้งหมดที่ต่ำกว่า foo และ เป้าหมายทั้งหมดที่ต่ำกว่า bar" ขณะที่

bazel build -- foo/... -foo/bar/...

หมายถึง "สร้างเป้าหมายทั้งหมดที่อยู่ใต้ foo ยกเว้นเป้าหมายที่อยู่ภายใต้ foo/bar" (จำเป็นต้องใช้อาร์กิวเมนต์ -- เพื่อป้องกันไม่ให้อาร์กิวเมนต์ที่ตามมาที่ขึ้นต้นด้วย - ได้รับการตีความเป็นตัวเลือกเพิ่มเติม)

แต่สิ่งสำคัญที่ควรทราบคือ การลบเป้าหมายด้วยวิธีนี้ไม่ได้รับประกันว่าจะไม่มีการสร้างเป้าหมาย เนื่องจากอาจเป็นทรัพยากร Dependency ของเป้าหมายที่ไม่ได้หักออก ตัวอย่างเช่น หากมีเป้าหมาย //foo:all-apis ที่รายการอื่นๆ พึ่งพา //foo/bar:api รายการหลังจะสร้างขึ้นเป็นส่วนหนึ่งของการสร้างเป้าหมายแรก

เป้าหมายที่มี tags = ["manual"] จะไม่รวมอยู่ในรูปแบบเป้าหมายไวลด์การ์ด (..., :*, :all ฯลฯ) เมื่อระบุในคำสั่งอย่างเช่น bazel build และ bazel test (แต่จะรวมอยู่ในรูปแบบเป้าหมายไวลด์การ์ดเชิงลบ กล่าวคือจะมีการหักออก) คุณควรระบุเป้าหมายการทดสอบดังกล่าวด้วยรูปแบบเป้าหมายที่ชัดเจนในบรรทัดคำสั่งหากต้องการให้ Bazel สร้าง/ทดสอบเป้าหมายดังกล่าว ในทางตรงกันข้าม bazel query จะไม่ทำการกรองโดยอัตโนมัติ (ซึ่งจะไม่เป็นไปตามวัตถุประสงค์ของ bazel query)

กำลังดึงข้อมูลทรัพยากร Dependency ภายนอก

โดยค่าเริ่มต้น Bazel จะดาวน์โหลดและเชื่อมโยงทรัพยากร Dependency ภายนอกระหว่างการสร้าง อย่างไรก็ตาม วิธีนี้อาจไม่เป็นที่ต้องการ เนื่องจากคุณต้องการทราบเมื่อมีการเพิ่มทรัพยากร Dependency ภายนอกใหม่ หรือเพราะคุณต้องการ "ดึงข้อมูลทรัพยากร Dependency "ล่วงหน้า" (เช่น ก่อนเที่ยวบินที่คุณจะออฟไลน์) หากต้องการป้องกันไม่ให้มีการเพิ่มทรัพยากร Dependency ใหม่ระหว่างบิลด์ ให้ระบุแฟล็ก --fetch=false โปรดทราบว่าแฟล็กนี้มีผลเฉพาะกับกฎที่เก็บที่ไม่ได้ชี้ไปยังไดเรกทอรีในระบบไฟล์ในเครื่องเท่านั้น เช่น การเปลี่ยนแปลงใน local_repository, new_local_repository รวมถึงกฎที่เก็บ Android SDK และ NDK จะมีผลเสมอโดยไม่คำนึงถึงค่า --fetch

หากคุณไม่อนุญาตให้ดึงข้อมูลระหว่างบิลด์และ Bazel พบทรัพยากร Dependency ใหม่ภายนอก บิลด์ของคุณจะไม่สำเร็จ

คุณดึงข้อมูลทรัพยากร Dependency ด้วยตนเองได้โดยการเรียกใช้ bazel fetch หากไม่อนุญาตในระหว่างการสร้างการดึงข้อมูล คุณจะต้องเรียกใช้ bazel fetch

  • ก่อนที่คุณจะสร้างเป็นครั้งแรก
  • หลังจากเพิ่มทรัพยากร Dependency ภายนอกใหม่แล้ว

เมื่อเรียกใช้แล้ว คุณไม่จำเป็นต้องเรียกใช้อีกจนกว่าไฟล์ WORKSPACE จะมีการเปลี่ยนแปลง

fetch ใช้รายการเป้าหมายเพื่อดึงข้อมูลทรัพยากร Dependency ตัวอย่างเช่น การดำเนินการนี้จะดึงข้อมูลทรัพยากร Dependency ที่จำเป็นในการสร้าง //foo:bar และ //bar:baz:

bazel fetch //foo:bar //bar:baz

หากต้องการดึงข้อมูลทรัพยากร Dependency ภายนอกทั้งหมดสำหรับพื้นที่ทำงาน ให้เรียกใช้คำสั่งต่อไปนี้

bazel fetch //...

เมื่อใช้ Bazel 7.1 ขึ้นไป หากเปิดใช้ Bzlmod คุณจะดึงข้อมูลทรัพยากร Dependency ภายนอกทั้งหมดได้ด้วยการเรียกใช้

bazel fetch

คุณไม่จำเป็นต้องเรียกใช้การดึงข้อมูลแบบ Bazel เลย หากมีเครื่องมือทั้งหมดที่ใช้อยู่ (ตั้งแต่ Jar ไลบรารีไปจนถึง JDK เอง) ภายในรูทของ Workspace อย่างไรก็ตาม หากคุณกำลังใช้สิ่งใดๆ นอกไดเรกทอรีพื้นที่ทำงาน Bazel จะเรียกใช้ bazel fetch โดยอัตโนมัติก่อนเรียกใช้ bazel build

แคชที่เก็บ

Bazel พยายามหลีกเลี่ยงการดึงข้อมูลไฟล์เดียวกันหลายครั้ง แม้ว่าจะจำเป็นต้องใช้ไฟล์เดียวกันในพื้นที่ทำงานอื่น หรือในกรณีที่คำจำกัดความของที่เก็บภายนอกมีการเปลี่ยนแปลง แต่ยังคงต้องดาวน์โหลดไฟล์เดียวกัน วิธีการคือ Bazel จะแคชไฟล์ทั้งหมดที่ดาวน์โหลดมาในแคชที่เก็บ ซึ่งโดยค่าเริ่มต้นจะอยู่ที่ ~/.cache/bazel/_bazel_$USER/cache/repos/v1/ เปลี่ยนสถานที่ได้โดยตัวเลือก --repository_cache โดยจะมีการแชร์แคชระหว่างพื้นที่ทำงานทั้งหมดและเวอร์ชันที่ติดตั้งของ Bazel รายการจะนำมาจากแคชหาก Bazel รู้อย่างแน่ชัดว่ามีสำเนาของไฟล์ที่ถูกต้อง กล่าวคือเมื่อคำขอดาวน์โหลดมีผลรวม SHA256 ของไฟล์ที่ระบุและไฟล์ที่มีแฮชนั้นอยู่ในแคช ดังนั้นการระบุแฮชสำหรับไฟล์ภายนอกแต่ละไฟล์ ไม่เพียงแต่เป็นแนวคิดที่ดีในแง่ของความปลอดภัยเท่านั้น แต่ยังช่วยหลีกเลี่ยงการดาวน์โหลดที่ไม่จำเป็นด้วย

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

ไดเรกทอรีไฟล์การกระจาย

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

เมื่อใช้ตัวเลือก --distdir=/path/to-directory คุณจะระบุไดเรกทอรีแบบอ่านอย่างเดียวเพิ่มเติมเพื่อค้นหาไฟล์แทนการดึงได้ ไฟล์จะมาจากไดเรกทอรีดังกล่าวหากชื่อไฟล์เท่ากับชื่อพื้นฐานของ URL และแฮชของไฟล์ยังเท่ากับแฮชที่ระบุไว้ในคําขอดาวน์โหลดด้วย ซึ่งจะได้ผลก็ต่อเมื่อมีการระบุแฮชของไฟล์ในการประกาศ WORKSPACE

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

วิ่ง Bazel ในสภาพแวดล้อมที่ไม่มีการกั้นอากาศ

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

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

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

หากต้องการสร้างทรัพยากร Dependency เหล่านี้นอกสภาพแวดล้อมที่มี Airgapped ให้ตรวจสอบแผนผังซอร์สของ Bazel ในเวอร์ชันที่ถูกต้องก่อน

git clone https://github.com/bazelbuild/bazel "$BAZEL_DIR"
cd "$BAZEL_DIR"
git checkout "$BAZEL_VERSION"

จากนั้นสร้าง Tarball ที่มีทรัพยากร Dependency ของรันไทม์โดยนัยสำหรับเวอร์ชัน Bazel ดังกล่าว

bazel build @additional_distfiles//:archives.tar

ส่งออก tarball นี้ไปยังไดเรกทอรีที่สามารถคัดลอกไปยังสภาพแวดล้อมที่ปิดกั้นได้ โปรดสังเกตแฟล็ก --strip-components เนื่องจาก --distdir อาจเข้ากันได้ค่อนข้างน้อยกับระดับการซ้อนไดเรกทอรีดังนี้

tar xvf bazel-bin/external/additional_distfiles/archives.tar \
  -C "$NEW_DIRECTORY" --strip-components=3

สุดท้าย เมื่อคุณใช้ Bazel ในสภาพแวดล้อมที่มี Airgapped ให้ส่งแฟล็ก --distdir ที่ชี้ไปยังไดเรกทอรี เพื่อความสะดวก คุณเพิ่มเป็น .bazelrc:

build --distdir=path/to/directory

สร้างการกำหนดค่าและการคอมไพล์ข้าม

อินพุตทั้งหมดที่ระบุลักษณะการทํางานและผลลัพธ์ของบิลด์หนึ่งๆ แบ่งออกได้เป็น 2 หมวดหมู่ที่แตกต่างกัน ประเภทแรกคือข้อมูลภายในที่จัดเก็บในไฟล์ BUILD ของโปรเจ็กต์ ได้แก่ กฎบิลด์ ค่าของแอตทริบิวต์ และชุดทรัพยากร Dependency แบบเปลี่ยนทั้งหมด ประเภทที่ 2 คือข้อมูลภายนอกหรือข้อมูลสิ่งแวดล้อมที่ผู้ใช้หรือเครื่องมือบิลด์เป็นผู้ระบุ ตัวเลือกสถาปัตยกรรมเป้าหมาย การคอมไพล์และการลิงก์ และตัวเลือกการกำหนดค่า Toolchain อื่นๆ เราเรียกชุดข้อมูลด้านสิ่งแวดล้อมทั้งหมดว่าการกำหนดค่า

ในบิลด์หนึ่งๆ อาจมีการกำหนดค่ามากกว่า 1 รายการ ลองพิจารณาการคอมไพล์แบบข้าม ซึ่งคุณสร้างไฟล์ปฏิบัติการ //foo:bin สำหรับสถาปัตยกรรม 64 บิต แต่เวิร์กสเตชันของคุณเป็นเครื่องแบบ 32 บิต แน่นอนว่าบิลด์จะต้องสร้าง //foo:bin โดยใช้เชนเครื่องมือที่สามารถสร้างไฟล์สั่งการแบบ 64 บิตได้ แต่ระบบบิลด์จะต้องสร้างเครื่องมือต่างๆ ที่ใช้ระหว่างการสร้างตัวมันเองด้วย เช่น เครื่องมือที่สร้างขึ้นจากต้นทางแล้วนำไปใช้ในภายหลัง เช่น การสร้างด้วยกฎเกณฑ์ และจะต้องสร้างเพื่อให้ทำงานในเวิร์กสเตชันของคุณ เราจึงระบุการกำหนดค่าได้ 2 รายการ ได้แก่ การกำหนดค่าการดำเนินการที่ใช้สำหรับการสร้างเครื่องมือที่ทำงานระหว่างบิลด์ และการกำหนดค่าเป้าหมาย (หรือการกำหนดค่าคำขอ แต่เราใช้คำว่า "การกำหนดค่าเป้าหมาย" บ่อยกว่าการกำหนดค่าเป้าหมาย แม้คำนั้นจะมีหลายความหมายอยู่แล้ว) ซึ่งใช้ในการสร้างไบนารีที่คุณขอในท้ายที่สุด

โดยปกติแล้วจะมีไลบรารีจำนวนมากที่เป็นข้อกำหนดเบื้องต้นของทั้งเป้าหมายบิลด์ที่ขอ (//foo:bin) และเครื่องมือการดำเนินการอย่างน้อย 1 รายการ เช่น ไลบรารีพื้นฐานบางรายการ ไลบรารีดังกล่าวต้องสร้างขึ้น 2 ครั้ง โดยครั้งหนึ่งสำหรับการกำหนดค่าการดำเนินการ และ 1 ครั้งสำหรับการกำหนดค่าเป้าหมาย Bazel มีหน้าที่สร้างตัวแปรทั้ง 2 ตัวและแยกไฟล์ที่ดึงมาไว้แยกกันเพื่อหลีกเลี่ยงการรบกวน โดยปกติแล้วเป้าหมายดังกล่าวสามารถสร้างพร้อมกันเพราะเป็นอิสระจากกัน หากคุณเห็นข้อความความคืบหน้าซึ่งบ่งชี้ว่ามีการสร้างเป้าหมายที่กำหนด 2 ครั้ง ก็เป็นไปได้ว่านี่คือคำอธิบาย

โดยการกำหนดค่า exec ได้มาจากการกำหนดค่าเป้าหมาย ดังนี้

  • ใช้ Crosstool (--crosstool_top) เวอร์ชันเดียวกับที่ระบุไว้ในการกำหนดค่าคำขอ เว้นแต่จะระบุ --host_crosstool_top
  • ใช้ค่า --host_cpu สําหรับ --cpu (ค่าเริ่มต้น: k8)
  • ใช้ค่าของตัวเลือกเหล่านี้เหมือนกับที่ระบุไว้ในการกำหนดค่าคำขอ ได้แก่ --compiler, --use_ijars และหากมีการใช้ --host_crosstool_top ระบบจะใช้ค่า --host_cpu เพื่อค้นหา default_toolchain ใน Crosstool (ไม่สนใจ --compiler) สำหรับการกำหนดค่าการดำเนินการ
  • ใช้ค่า --host_javabase สําหรับ --javabase
  • ใช้ค่า --host_java_toolchain สําหรับ --java_toolchain
  • ใช้บิลด์ที่เพิ่มประสิทธิภาพสำหรับโค้ด C++ (-c opt)
  • สร้างข้อมูลการแก้ไขข้อบกพร่อง (--copt=-g0)
  • ตัดข้อมูลการแก้ไขข้อบกพร่องจากไฟล์สั่งการและไลบรารีที่ใช้ร่วมกัน (--strip=always)
  • วางไฟล์ที่ได้ทั้งหมดไว้ในตำแหน่งพิเศษ ซึ่งต่างจากไฟล์ที่ได้ใช้โดยการกำหนดค่าคำขอที่เป็นไปได้ใดๆ
  • ระงับการประทับไบนารีที่มีข้อมูลบิลด์ (ดูตัวเลือก --embed_*)
  • ค่าอื่นๆ ทั้งหมดจะยังคงเป็นค่าเริ่มต้น

มีเหตุผลหลายประการที่ควรเลือกการกำหนดค่าการดำเนินการที่ต่างจากการกำหนดค่าคำขอ สิ่งสำคัญที่สุดคือ

ประการแรก การใช้ไบนารีที่เพิ่มประสิทธิภาพและตัดให้สั้นลง จะช่วยลดเวลาที่ใช้ในการลิงก์และเรียกใช้เครื่องมือ พื้นที่ในดิสก์ที่เครื่องมือใช้ และระยะเวลา I/O ของเครือข่ายในบิลด์แบบกระจาย

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

แก้ไขการสร้างใหม่ที่เพิ่มขึ้นทีละน้อย

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

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

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

นอกจากนี้ Make มีประสิทธิภาพไม่ดีต่อการสิ้นสุดกระบวนการย่อยที่ไม่สำเร็จหลังจากที่กระบวนการย่อยนั้นเริ่มเขียนลงในไฟล์เอาต์พุต แม้ว่าการดำเนินการปัจจุบันของ "สร้าง" จะไม่สำเร็จ แต่การเรียกใช้ "สร้าง" ครั้งต่อๆ ไปจะถือว่าไฟล์เอาต์พุตที่ตัดให้สั้นลงนั้นถูกต้อง (เนื่องจากเป็นไฟล์ใหม่กว่าอินพุต) และระบบจะไม่สร้างใหม่อีก ในทำนองเดียวกัน หากกระบวนการ "สร้าง" หยุดทำงาน ก็อาจเกิดเหตุการณ์ที่คล้ายกันนี้ได้

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

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

สร้างความสอดคล้องและเพิ่มบิลด์

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

ความไม่สอดคล้องอีกประเภทหนึ่งที่ร้ายแรงได้ คือ ความไม่สอดคล้องกันอย่างมาก หากบิลด์อยู่ในสถานะที่ไม่สอดคล้องกันอย่างคงที่ การเรียกใช้เครื่องมือสร้างบิลด์ที่ประสบความสำเร็จซ้ำๆ จะไม่คืนค่าความสอดคล้องกัน หมายความว่าบิลด์ "ค้าง" และเอาต์พุตยังคงไม่ถูกต้อง สถานะที่ไม่สอดคล้องกันแบบคงที่เป็นเหตุผลหลักที่ผู้ใช้ของ Make (และเครื่องมือบิลด์อื่นๆ) ประเภท make clean การค้นพบว่าเครื่องมือสร้างล้มเหลวในลักษณะนี้ (แล้วกู้คืนจากเครื่องมือสร้าง) อาจใช้เวลามากและน่าหงุดหงิดมาก

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

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

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

หากคุณตรวจพบสถานะที่ไม่สอดคล้องกันอย่างคงที่กับ Bazel โปรดรายงานข้อบกพร่อง

การดำเนินการในแซนด์บ็อกซ์

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

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

ในบางแพลตฟอร์ม เช่น โหนดคลัสเตอร์ Google Kubernetes Engine หรือ Debian ระบบจะปิดใช้งานเนมสเปซของผู้ใช้โดยค่าเริ่มต้นเนื่องจากข้อกังวลด้านความปลอดภัย ซึ่งตรวจสอบได้โดยดูที่ไฟล์ /proc/sys/kernel/unprivileged_userns_clone หากมีไฟล์อยู่และมี 0 แล้ว เนมสเปซของผู้ใช้จะสามารถเปิดใช้งานด้วย sudo sysctl kernel.unprivileged_userns_clone=1 ได้

ในบางกรณี แซนด์บ็อกซ์ Bazel จะดำเนินการกฎไม่ได้เนื่องจากการตั้งค่าระบบ โดยทั่วไปลักษณะปัญหาคือการแสดงข้อความคล้ายกับ namespace-sandbox.c:633: execvp(argv[0], argv): No such file or directory ไม่สำเร็จ ในกรณีดังกล่าว ให้ลองปิดใช้งานแซนด์บ็อกซ์สำหรับ genrules ด้วย --strategy=Genrule=standalone และสำหรับกฎอื่นๆ ที่มี --spawn_strategy=standalone นอกจากนี้ โปรดรายงานข้อบกพร่องในเครื่องติดตามปัญหาและระบุการกระจาย Linux ที่คุณใช้เพื่อให้เราตรวจสอบและทำการแก้ไขในรุ่นต่อๆ ไปได้

ขั้นตอนต่างๆ ของงานสร้าง

ใน Bazel บิลด์จะเกิดขึ้นใน 3 ระยะที่แตกต่างกัน ในฐานะผู้ใช้ การทำความเข้าใจความแตกต่างระหว่าง 2 ประเภทนี้จะให้ข้อมูลเชิงลึกเกี่ยวกับตัวเลือกที่ควบคุมบิลด์ (ดูด้านล่าง)

ช่วงการโหลด

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

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

ข้อผิดพลาดที่รายงานในระยะนี้ ได้แก่ ไม่พบแพ็กเกจ, ไม่พบเป้าหมาย, ข้อผิดพลาดด้านคำศัพท์และไวยากรณ์ในไฟล์ BUILD และข้อผิดพลาดในการประเมิน

ช่วงวิเคราะห์

ขั้นที่ 2 คือการวิเคราะห์จะเกี่ยวข้องกับการวิเคราะห์เชิงความหมายและการตรวจสอบกฎของบิลด์แต่ละกฎ การสร้างกราฟทรัพยากร Dependency ของบิลด์ และการพิจารณาว่าต้องทำอะไรในแต่ละขั้นตอนของบิลด์

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

ข้อผิดพลาดที่รายงานในขั้นตอนนี้ ได้แก่ ทรัพยากร Dependency ที่ไม่เหมาะสม อินพุตที่ไม่ถูกต้องไปยังกฎ และข้อความแสดงข้อผิดพลาดเฉพาะกฎทั้งหมด

ขั้นตอนการโหลดและการวิเคราะห์เป็นไปอย่างรวดเร็วเนื่องจาก Bazel หลีกเลี่ยง I/O ไฟล์ที่ไม่จำเป็นในขั้นตอนนี้ โดยจะอ่านเฉพาะไฟล์ BUILD เพื่อพิจารณางานที่ต้องทำให้เสร็จ ซึ่งเป็นการออกแบบที่จะทำให้ Bazel เป็นรากฐานที่ดีสำหรับเครื่องมือวิเคราะห์ เช่น คำสั่งการค้นหาของ Bazel ซึ่งนำไปใช้ในขั้นตอนการโหลด

ระยะดำเนินการ

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