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

รายงานปัญหา ดูแหล่งที่มา /3} /4} {3/4} {3/4} {3/4} {3/4} /4.

หน้านี้จะพูดถึงวิธีสร้างโปรแกรมด้วย 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: ดำเนินการค้นหากราฟทรัพยากร Dependency
  • 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

การสร้างเป้าหมายเดียว

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

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

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

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

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

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

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

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

bazel fetch //...

คุณไม่จำเป็นต้องเรียกใช้การดึงข้อมูลแบบ Bazel เลยหากมีเครื่องมือทั้งหมดที่ใช้อยู่ (ตั้งแต่ JD ไลบรารีไปจนถึง JDK) ภายใต้รูทของพื้นที่ทำงาน อย่างไรก็ตาม หากคุณใช้งานนอกไดเรกทอรีพื้นที่ทำงาน 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 โดยนัยเหล่านี้มี Toolchain และกฎที่อาจไม่จำเป็นสำหรับทุกคน เช่น เครื่องมือ Android จะเลิกรวมกลุ่มและดึงข้อมูลเมื่อสร้างโปรเจ็กต์ Android เท่านั้น

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

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

หากต้องการสร้างทรัพยากร Dependency เหล่านี้นอกสภาพแวดล้อมที่มีการเชื่อมต่ออากาศ ก่อนอื่นให้ตรวจสอบโครงสร้างแหล่งที่มาของ 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 ในสภาพแวดล้อมที่มีการเชื่อมต่ออากาศ ให้ส่งธง --distdir ที่ชี้ไปยังไดเรกทอรี เพื่อความสะดวก ให้คุณเพิ่มเป็นรายการ .bazelrc ได้ ดังนี้

build --distdir=path/to/directory

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

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

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

โดยทั่วไปแล้วจะมีไลบรารีจำนวนมากที่เป็นข้อกำหนดเบื้องต้นของทั้งเป้าหมายบิลด์ (//foo:bin) ที่ขอและเครื่องมือดำเนินการอย่างน้อย 1 รายการ เช่น ไลบรารีพื้นฐานบางรายการ ไลบรารีดังกล่าวต้องสร้างขึ้น 2 ครั้ง ครั้งแรกสำหรับการกำหนดค่าการดำเนินการ และอีกครั้งสำหรับการกำหนดค่าเป้าหมาย Bazel ดูแลให้มีการสร้างตัวแปรทั้งสองแบบและแยกไฟล์ที่ดึงมาไว้แยกกันเพื่อหลีกเลี่ยงการรบกวน โดยทั่วไปเป้าหมายดังกล่าวจะสามารถสร้างพร้อมกัน เนื่องจากเป้าหมายทั้งสองจะแยกจากกัน หากคุณเห็นข้อความความคืบหน้าที่บ่งชี้ว่ามีการสร้างเป้าหมายที่กำหนด 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 ไม่เรียกใช้คอมไพเลอร์อีกครั้ง และจำเป็นต้องทิ้งเอาต์พุตที่ไม่ถูกต้องของบิลด์ก่อนหน้าโดยใช้ make clean ด้วยตนเอง

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

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

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

สร้างความสอดคล้องและค่อยๆ เพิ่มขึ้น

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

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

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

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

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

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

การดำเนินการที่แซนด์บ็อกซ์

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

Bazel จะพิมพ์คำเตือนหากระบบของคุณไม่สนับสนุนแซนด์บ็อกซ์ เพื่อแจ้งเตือนคุณว่าเวอร์ชันต่างๆ ไม่ได้รับประกันว่าบิลด์จะเป็นแบบไม่ซับซ้อนและอาจส่งผลต่อระบบโฮสต์ในรูปแบบที่ไม่รู้จัก หากต้องการปิดคำเตือนนี้ คุณสามารถส่ง Flag --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 ไม่สำเร็จ ในกรณีนี้ ให้ลองปิดใช้งานแซนด์บ็อกซ์สำหรับ Genrule ที่มี --strategy=Genrule=standalone และกฎอื่นๆ ที่มี --spawn_strategy=standalone นอกจากนี้ โปรดรายงานข้อบกพร่องในเครื่องมือติดตามปัญหา และระบุว่าคุณใช้ Linux Distribution ใดอยู่เพื่อให้เราตรวจสอบและแก้ไขข้อบกพร่องในรุ่นต่อๆ ไปได้

ขั้นของบิลด์

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

ระยะการโหลด

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

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

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

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

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

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

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

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

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

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