เอกสารนี้เป็นคำอธิบายโค้ดเบสและโครงสร้างของ Bazel แพ็กเกจนี้มีไว้สำหรับผู้ที่ต้องการมีส่วนร่วมใน Bazel ไม่ใช่สำหรับผู้ใช้ปลายทาง
บทนำ
โค้ดเบสของ Bazel มีขนาดใหญ่ (โค้ดสำหรับใช้งานจริงประมาณ 350,000 บรรทัดโค้ดและโค้ดทดสอบประมาณ 260,000 บรรทัดโค้ด) และไม่มีใครคุ้นเคยกับภาพรวมทั้งหมด ทุกคนรู้จักหุบเขาของตัวเองเป็นอย่างดี แต่มีเพียงไม่กี่คนที่รู้ว่ามีอะไรอยู่หลังเนินเขาทุกทิศทาง
เอกสารนี้จึงพยายามแสดงภาพรวมของฐานของโค้ดเพื่อให้ง่ายต่อการเริ่มทำงา
ซอร์สโค้ดเวอร์ชันสาธารณะของ Bazel อยู่ใน GitHub ที่ github.com/bazelbuild/bazel ข้อมูลนี้ไม่ใช่ "แหล่งข้อมูลที่เป็นความจริง" แต่มาจากต้นไม้แหล่งที่มาภายในของ Google ซึ่งมีฟังก์ชันการทำงานเพิ่มเติมที่ใช้งานไม่ได้นอก Google เป้าหมายระยะยาวของเราคือทำให้ GitHub เป็นแหล่งข้อมูลที่ถูกต้อง
เรายอมรับการมีส่วนร่วมผ่านกลไกการดึงคำขอ GitHub ปกติ และ Googler จะนำเข้าข้อมูลไปยังโครงสร้างซอร์สโค้ดภายในด้วยตนเอง จากนั้นจึงส่งออกกลับไปที่ GitHub อีกครั้ง
สถาปัตยกรรมไคลเอ็นต์/เซิร์ฟเวอร์
ส่วนใหญ่ของ Bazel จะอยู่ในกระบวนการเซิร์ฟเวอร์ที่อยู่ใน RAM ระหว่างการบิลด์ ซึ่งช่วยให้ Bazel คงสถานะระหว่างบิลด์ได้
และนี่คือเหตุผลที่บรรทัดคำสั่ง Bazel มีตัวเลือก 2 ประเภท ได้แก่ การเริ่มต้นและคำสั่ง ในบรรทัดคำสั่ง เช่น
bazel --host_jvm_args=-Xmx8G build -c opt //foo:bar
ตัวเลือกบางรายการ (--host_jvm_args=
) จะอยู่ก่อนชื่อคําสั่งที่จะเรียกใช้ และบางรายการจะอยู่หลัง (-c opt
) ตัวเลือกประเภทแรกเรียกว่า "ตัวเลือกการเริ่มต้น" และส่งผลต่อกระบวนการของเซิร์ฟเวอร์โดยรวม ส่วนตัวเลือกประเภทหลังหรือ "ตัวเลือกคําสั่ง" จะส่งผลต่อคําสั่งเดียวเท่านั้น
อินสแตนซ์เซิร์ฟเวอร์แต่ละรายการมีต้นไม้ต้นทางที่เชื่อมโยงเดียว ("พื้นที่ทำงาน") และพื้นที่ทำงานแต่ละแห่งมักจะมีอินสแตนซ์เซิร์ฟเวอร์ที่ใช้งานอยู่เพียงรายการเดียว ปัญหานี้สามารถหลีกเลี่ยงได้ด้วยการระบุฐานเอาต์พุตที่กำหนดเอง (ดูข้อมูลเพิ่มเติมได้ในส่วน "เลย์เอาต์ไดเรกทอรี")
Bazel ได้รับการแจกจ่ายเป็นไฟล์ปฏิบัติการ ELF ไฟล์เดียว ซึ่งเป็นไฟล์ .zip ที่ถูกต้องด้วย
เมื่อคุณพิมพ์ bazel
ไฟล์ปฏิบัติการ ELF ข้างต้นที่นำไปใช้ใน C++ ("ไคลเอ็นต์") จะควบคุมได้ โดยจะตั้งค่ากระบวนการเซิร์ฟเวอร์ที่เหมาะสมโดยใช้ขั้นตอนต่อไปนี้
- ตรวจสอบว่ามีการแยกตัวเองแล้วหรือไม่ หากไม่ ระบบจะดำเนินการดังกล่าว การติดตั้งใช้งานเซิร์ฟเวอร์จึงเกิดขึ้นจากจุดนี้
- ตรวจสอบว่ามีอินสแตนซ์เซิร์ฟเวอร์ที่ทำงานอยู่หรือไม่ โดยดูว่าเซิร์ฟเวอร์ทำงานอยู่ มีตัวเลือกการเริ่มต้นที่ถูกต้อง และใช้ไดเรกทอรีเวิร์กスペースที่ถูกต้อง โดยจะค้นหาเซิร์ฟเวอร์ที่ทำงานอยู่โดยดูที่ไดเรกทอรี
$OUTPUT_BASE/server
ซึ่งมีไฟล์ล็อกที่มีพอร์ตที่เซิร์ฟเวอร์กำลังรอรับการเชื่อมต่อ - หยุดกระบวนการเซิร์ฟเวอร์เดิมหากจำเป็น
- เริ่มกระบวนการเซิร์ฟเวอร์ใหม่ หากจำเป็น
หลังจากกระบวนการเซิร์ฟเวอร์ที่เหมาะสมพร้อมใช้งานแล้ว ระบบจะสื่อสารคำสั่งที่ต้องเรียกใช้กับเซิร์ฟเวอร์ผ่านอินเทอร์เฟซ gRPC จากนั้นระบบจะส่งออกของ Bazel กลับไปยังเทอร์มินัล เรียกใช้คำสั่งได้ครั้งละ 1 รายการเท่านั้น ซึ่งทำได้โดยใช้กลไกล็อกที่ซับซ้อนซึ่งมีส่วนต่างๆ ใน C++ และส่วนต่างๆ ใน Java มีโครงสร้างพื้นฐานบางอย่างสําหรับการเรียกใช้คําสั่งหลายรายการพร้อมกัน เนื่องจากการที่เรียกใช้ bazel version
ควบคู่ไปกับคําสั่งอื่นไม่ได้นั้นเป็นเรื่องที่น่าอาย ปัญหาหลักคือวงจรชีวิตของ BlazeModule
s
และสถานะบางอย่างใน BlazeRuntime
เมื่อสิ้นสุดคําสั่ง เซิร์ฟเวอร์ Bazel จะส่งรหัสออกที่ไคลเอ็นต์ควรแสดง ส่วนเล็กๆ ที่น่าสนใจคือการนำ bazel run
มาใช้ หน้าที่ของคำสั่งนี้คือการเรียกใช้สิ่งที่ Bazel เพิ่งสร้างขึ้น แต่ไม่สามารถทำได้จากกระบวนการของเซิร์ฟเวอร์เนื่องจากไม่มีเทอร์มินัล ดังนั้นจึงบอกไคลเอ็นต์ว่าควรujexec()
ไบนารีใดและด้วยอาร์กิวเมนต์ใดแทน
เมื่อกด Ctrl-C ไคลเอ็นต์จะแปลเป็นคําสั่งยกเลิกในการเชื่อมต่อ gRPC ซึ่งจะพยายามสิ้นสุดคําสั่งโดยเร็วที่สุด หลังจาก Ctrl-C ครั้งที่ 3 ไคลเอ็นต์จะส่ง SIGKILL ไปยังเซิร์ฟเวอร์แทน
โค้ดต้นทางของไคลเอ็นต์อยู่ใน src/main/cpp
และโปรโตคอลที่ใช้สื่อสารกับเซิร์ฟเวอร์อยู่ใน src/main/protobuf/command_server.proto
จุดแรกเข้าหลักของเซิร์ฟเวอร์คือ BlazeRuntime.main()
และ GrpcServerImpl.run()
จะเป็นผู้จัดการการเรียก gRPC จากไคลเอ็นต์
เลย์เอาต์ไดเรกทอรี
Bazel จะสร้างชุดไดเรกทอรีที่ค่อนข้างซับซ้อนระหว่างการบิลด์ ดูคำอธิบายฉบับเต็มได้ในเลย์เอาต์ไดเรกทอรีเอาต์พุต
"พื้นที่ทํางาน" คือสคีมาซอร์สโค้ดที่ Bazel ทำงานอยู่ โดยปกติแล้ว รายการดังกล่าวจะสอดคล้องกับสิ่งที่คุณตรวจสอบออกจากระบบควบคุมแหล่งที่มา
Bazel จะจัดเก็บข้อมูลทั้งหมดไว้ภายใต้ "รูทผู้ใช้เอาต์พุต" โดยปกติแล้วจะเป็น $HOME/.cache/bazel/_bazel_${USER}
แต่สามารถลบล้างได้โดยใช้ตัวเลือกการเริ่มต้น --output_user_root
"ฐานการติดตั้ง" คือตำแหน่งที่จะแตกไฟล์ Bazel ระบบจะดำเนินการนี้โดยอัตโนมัติ และ Bazel แต่ละเวอร์ชันจะได้รับไดเรกทอรีย่อยตามการตรวจสอบผลรวมภายใต้ฐานการติดตั้ง โดยค่าเริ่มต้นจะมีค่าเป็น $OUTPUT_USER_ROOT/install
และสามารถเปลี่ยนแปลงได้โดยใช้ตัวเลือกบรรทัดคำสั่ง --install_base
"ฐานเอาต์พุต" คือตำแหน่งที่อินสแตนซ์ Bazel แนบกับพื้นที่ทำงานที่เฉพาะเจาะจงเขียนถึง ฐานเอาต์พุตแต่ละฐานจะมีอินสแตนซ์เซิร์ฟเวอร์ Bazel ทำงานอยู่ได้สูงสุด 1 อินสแตนซ์ โดยทั่วไปอุณหภูมิจะอยู่ที่ $OUTPUT_USER_ROOT/<checksum of the path
to the workspace>
ซึ่งสามารถเปลี่ยนแปลงได้โดยใช้--output_base
ตัวเลือกการเริ่มต้นใช้งาน ซึ่งมีประโยชน์ในการหลีกเลี่ยงข้อจำกัดที่ว่ามีเพียงอินสแตนซ์ Bazel เดียวเท่านั้นที่ทำงานได้ในเวิร์กスペースใดก็ตามในเวลาหนึ่งๆ
ไดเรกทอรีเอาต์พุตจะมีสิ่งต่อไปนี้ด้วย
- ที่เก็บข้อมูลภายนอกที่ดึงข้อมูลได้ที่
$OUTPUT_BASE/external
- รูท exec ซึ่งเป็นไดเรกทอรีที่มีลิงก์สัญลักษณ์ไปยังซอร์สโค้ดทั้งหมดของบิลด์ปัจจุบัน ตั้งอยู่ที่
$OUTPUT_BASE/execroot
ในระหว่างการสร้าง ไดเรกทอรีที่ใช้งานอยู่คือ$EXECROOT/<name of main repository>
เราวางแผนที่จะเปลี่ยนเป็น$EXECROOT
แม้ว่าจะเป็นแพ็กเกจระยะยาวเนื่องจากเป็นการเปลี่ยนแปลงที่ใช้ร่วมกันไม่ได้ - ไฟล์ที่สร้างระหว่างการบิลด์
กระบวนการเรียกใช้คําสั่ง
เมื่อเซิร์ฟเวอร์ Bazel ได้รับการควบคุมและได้รับแจ้งเกี่ยวกับคำสั่งที่ต้องดำเนินการ เหตุการณ์ต่อไปนี้จะเกิดขึ้นตามลำดับ
BlazeCommandDispatcher
ได้รับแจ้งเกี่ยวกับคำขอใหม่ โดยจะตัดสินว่าคำสั่งต้องใช้พื้นที่ทำงานหรือไม่ (แทบจะทุกคำสั่ง ยกเว้นคำสั่งที่ไม่มีส่วนเกี่ยวข้องกับซอร์สโค้ด เช่น เวอร์ชันหรือความช่วยเหลือ) และมีคำสั่งอื่นทำงานอยู่หรือไม่พบคำสั่งที่ถูกต้อง แต่ละคําสั่งต้องใช้อินเทอร์เฟซ
BlazeCommand
และต้องมีคําอธิบายประกอบ@Command
(นี่ไม่ใช่รูปแบบที่แนะนำ วิธีที่ดีกว่าคือให้เมตาข้อมูลทั้งหมดที่คําสั่งต้องการอธิบายโดยเมธอดในBlazeCommand
)ระบบจะแยกวิเคราะห์ตัวเลือกบรรทัดคำสั่ง แต่ละคําสั่งมีตัวเลือกบรรทัดคําสั่งที่แตกต่างกัน ซึ่งอธิบายไว้ในคำอธิบายประกอบ
@Command
มีการสร้างรถโดยสารสำหรับกิจกรรม บัสเหตุการณ์คือสตรีมสําหรับเหตุการณ์ที่เกิดขึ้นระหว่างการสร้าง ระบบจะส่งออกข้อมูลบางส่วนเหล่านี้ไปยังภายนอก Bazel ภายใต้การอุปถัมภ์ของ Build Event Protocol เพื่อบอกให้โลกรู้ถึงสถานะการสร้าง
คำสั่งจะควบคุม คำสั่งที่น่าสนใจที่สุดคือคำสั่งที่ใช้เรียกใช้การสร้าง เช่น การสร้าง การทดสอบ การทำงาน ความครอบคลุม และอื่นๆ ฟังก์ชันการทำงานนี้ใช้
BuildTool
ระบบจะแยกวิเคราะห์ชุดรูปแบบเป้าหมายในบรรทัดคำสั่งและแก้ไขไวลด์การ์ด เช่น
//pkg:all
และ//pkg/...
การดำเนินการนี้ใช้ในAnalysisPhaseRunner.evaluateTargetPatterns()
และแปลงเป็นจริงใน Skyframe เป็นTargetPatternPhaseValue
ขั้นตอนการโหลด/การวิเคราะห์จะทำงานเพื่อสร้างกราฟการดำเนินการ (กราฟแบบวนซ้ำของคำสั่งที่ต้องเรียกใช้สำหรับบิลด์)
ระยะการดําเนินการทํางาน ซึ่งหมายความว่าระบบจะเรียกใช้การดำเนินการที่จำเป็นทั้งหมดเพื่อสร้างเป้าหมายระดับบนสุดที่ขอ
ตัวเลือกบรรทัดคำสั่ง
ตัวเลือกบรรทัดคำสั่งสำหรับการเรียกใช้ Bazel จะอธิบายไว้ในออบเจ็กต์ OptionsParsingResult
ซึ่งจะมีแผนที่จาก "คลาสตัวเลือก" ไปยังค่าของตัวเลือก "ตัวเลือกคลาส" คือคลาสย่อยของ OptionsBase
และจัดกลุ่มตัวเลือกบรรทัดคำสั่งเข้าด้วยกันซึ่งเกี่ยวข้องกัน เช่น
- ตัวเลือกที่เกี่ยวข้องกับภาษาโปรแกรม (
CppOptions
หรือJavaOptions
) ตัวเลือกเหล่านี้ควรเป็นคลาสย่อยของFragmentOptions
และสุดท้ายจะรวมอยู่ในออบเจ็กต์BuildOptions
- ตัวเลือกที่เกี่ยวข้องกับวิธีที่ Bazel ดำเนินการ (
ExecutionOptions
)
ตัวเลือกเหล่านี้ออกแบบมาเพื่อใช้ในเฟสการวิเคราะห์และ (ผ่าน RuleContext.getFragment()
ใน Java หรือ ctx.fragments
ใน Starlark)
ระบบจะอ่านข้อมูลบางอย่าง (เช่น จะทำการสแกนรวม C++ หรือไม่) ในเฟสการดำเนินการ แต่จะต้องทำการเดินท่ออย่างชัดเจนเสมอเนื่องจากBuildConfiguration
ไม่พร้อมใช้งานในตอนนั้น ดูข้อมูลเพิ่มเติมได้ที่ส่วน "การกําหนดค่า"
คำเตือน: เราชอบที่จะสมมติว่าอินสแตนซ์ OptionsBase
เป็นแบบคงที่และใช้อินสแตนซ์ดังกล่าว (เช่น เป็นส่วนหนึ่งของ SkyKeys
) แต่ความจริงแล้วไม่ใช่เช่นนั้น และการแก้ไขอินสแตนซ์เป็นวิธีที่ยอดเยี่ยมในการทำให้ Bazel ทำงานผิดพลาดในลักษณะที่ละเอียดอ่อนซึ่งแก้ไขได้ยาก แต่การทำให้ข้อมูลเหล่านี้เป็นแบบคงที่จริงๆ นั้นเป็นเรื่องยาก
(การแก้ไข FragmentOptions
ทันทีหลังจากการก่อสร้างก่อนที่ผู้อื่นจะได้มีการอ้างอิงไว้ และก่อนที่จะมีการเรียก equals()
หรือ hashCode()
นั้น สามารถทำได้)
Bazel ได้เรียนรู้เกี่ยวกับคลาสตัวเลือกด้วยวิธีต่อไปนี้
- บางรายการเชื่อมต่อกับ Bazel อย่างถาวร (
CommonCommandOptions
) - จากคำอธิบายประกอบ
@Command
ในคำสั่ง Bazel แต่ละรายการ - จาก
ConfiguredRuleClassProvider
(ตัวเลือกบรรทัดคำสั่งเหล่านี้เกี่ยวข้องกับภาษาโปรแกรมแต่ละภาษา) - กฎ Starlark ยังกำหนดตัวเลือกของตนเองได้ด้วย (ดูที่นี่)
ตัวเลือกแต่ละรายการ (ยกเว้นตัวเลือกที่ Starlark กำหนด) คือตัวแปรสมาชิกของFragmentOptions
ซับคลาสที่มีคำอธิบายประกอบ @Option
ซึ่งระบุชื่อและประเภทของตัวเลือกบรรทัดคำสั่งพร้อมกับข้อความความช่วยเหลือบางส่วน
ประเภท Java ของค่าของตัวเลือกบรรทัดคำสั่งมักเป็นค่าที่เรียบง่าย (เช่น สตริง จำนวนเต็ม บูลีน ป้ายกำกับ ฯลฯ) อย่างไรก็ตาม เรายังรองรับตัวเลือกประเภทที่ซับซ้อนขึ้นด้วย ในกรณีนี้ การแปลงจากสตริงบรรทัดคำสั่งเป็นประเภทข้อมูลจะเป็นไปตามการใช้งาน com.google.devtools.common.options.Converter
โครงสร้างซอร์สโค้ดตามที่ Bazel เห็น
Bazel อยู่ในธุรกิจการสร้างซอฟต์แวร์ ซึ่งเกิดขึ้นจากการอ่านและตีความซอร์สโค้ด ซอร์สโค้ดทั้งหมดที่ Bazel ทำงานด้วยเรียกว่า "เวิร์กสเปซ" และจัดโครงสร้างเป็นรีโพซิทอรี แพ็กเกจ และกฎ
ที่เก็บ
"ที่เก็บ" คือต้นไม้ซอร์สโค้ดที่นักพัฒนาซอฟต์แวร์ทํางาน ซึ่งมักจะแสดงถึงโปรเจ็กต์เดียว Blaze ซึ่งเป็นบรรพบุรุษของ Bazel ทำงานใน Monorepo ซึ่งก็คือสคีมาแหล่งที่มาเดียวที่มีซอร์สโค้ดทั้งหมดที่ใช้เพื่อเรียกใช้บิลด์ แต่ Bazel รองรับโปรเจ็กต์ที่มีซอร์สโค้ดอยู่ในที่เก็บหลายแห่ง ที่เก็บที่ใช้เรียกใช้ Bazel เรียกว่า "ที่เก็บหลัก" ส่วนที่เหลือเรียกว่า "ที่เก็บภายนอก"
ที่เก็บมีการทำเครื่องหมายโดยไฟล์ชื่อ WORKSPACE
(หรือ WORKSPACE.bazel
) ในไดเรกทอรีราก ไฟล์นี้มีข้อมูลที่ "ส่วนกลาง" สำหรับทั้งบิลด์ เช่น ชุดที่เก็บข้อมูลภายนอกที่ใช้ได้ ซึ่งทำงานเหมือนไฟล์ Starlark ทั่วไป ซึ่งหมายความว่าสามารถload()
ไฟล์ Starlark อื่นๆ ได้
วิธีนี้มักใช้เพื่อดึงข้อมูลในที่เก็บซึ่งที่เก็บซึ่งจำเป็นต้องมีการอ้างอิงอย่างชัดแจ้ง (เราเรียกว่า "รูปแบบ deps.bzl
")
โค้ดของที่เก็บภายนอกมีการลิงก์หรือดาวน์โหลดภายใต้ $OUTPUT_BASE/external
เมื่อเรียกใช้บิลด์ โครงสร้างต้นทางทั้งหมดจะต้องต่อกัน ซึ่งดำเนินการโดย SymlinkForest
ซึ่งจะลิงก์ทุกแพ็กเกจในที่เก็บหลักกับ $EXECROOT
และเชื่อมต่อที่เก็บภายนอกทั้งหมดไปยัง $EXECROOT/external
หรือ $EXECROOT/..
(ซึ่งอย่างเดิมทำให้ไม่มีแพ็กเกจชื่อ external
ในที่เก็บหลักอยู่แล้ว เราจึงย้ายข้อมูลออกจากที่เก็บดังกล่าว)
แพ็กเกจ
ที่เก็บข้อมูลทุกแห่งประกอบด้วยแพ็กเกจ คอลเล็กชันไฟล์ที่เกี่ยวข้อง และข้อกำหนดของข้อกำหนดเบื้องต้น ซึ่งระบุโดยไฟล์ชื่อ BUILD
หรือ BUILD.bazel
หากมีทั้ง 2 ไฟล์ Bazel จะเลือก BUILD.bazel
เหตุผลที่ยังคงยอมรับไฟล์ BUILD
อยู่เนื่องจาก Blaze ซึ่งเป็นบรรพบุรุษของ Bazel ใช้ชื่อไฟล์นี้ แต่กลับกลายเป็นส่วนของเส้นทางที่ใช้กันโดยทั่วไป โดยเฉพาะใน Windows ที่ระบบไม่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ในชื่อไฟล์
แพ็กเกจเป็นอิสระต่อกัน ดังนั้นการเปลี่ยนแปลงไฟล์ BUILD
ของแพ็กเกจจะไม่ทำให้แพ็กเกจอื่นๆ เปลี่ยนแปลง การเพิ่มหรือการนำไฟล์ BUILD
ออกอาจเปลี่ยนแปลงแพ็กเกจอื่นๆ ได้ เนื่องจากนิพจน์ทั่วไปแบบซ้ำซ้อนจะหยุดที่ขอบเขตของแพ็กเกจ และไฟล์ BUILD
จะหยุดการเรียกซ้ำ
การประเมินไฟล์ BUILD
เรียกว่า "การโหลดแพ็กเกจ" มีการนำมาใช้ในคลาส PackageFactory
โดยทํางานโดยการเรียกใช้โปรแกรมแปลภาษา Starlark และต้องใช้ความรู้เกี่ยวกับชุดคลาสกฎที่ใช้ได้ ผลลัพธ์ของการโหลดแพ็กเกจคือออบเจ็กต์ Package
โดยส่วนใหญ่จะเป็นแผนที่จากสตริง (ชื่อเป้าหมาย) ไปยังเป้าหมายนั้นๆ
ความซับซ้อนส่วนใหญ่ระหว่างการโหลดแพ็กเกจคือรูปแบบทั่วไป: Bazel ไม่ได้กำหนดให้ต้องระบุไฟล์ต้นฉบับทุกไฟล์อย่างชัดเจน แต่สามารถเรียกใช้รูปแบบทั่วไป (เช่น glob(["**/*.java"])
) แทน ต่างจากเชลล์ตรงที่รองรับรูปแบบทั่วไปแบบซ้ำซ้อนที่ไปยังไดเรกทอรีย่อย (แต่ไม่ใช่ไปยังแพ็กเกจย่อย) ซึ่งต้องใช้สิทธิ์เข้าถึงระบบไฟล์ และเนื่องจากการดำเนินการนี้อาจช้า เราจึงใช้เทคนิคต่างๆ มากมายเพื่อให้ทำงานไปพร้อมๆ กันและมีประสิทธิภาพมากที่สุด
การใช้ Globbing มีอยู่ในคลาสต่อไปนี้
LegacyGlobber
ผู้ใช้ Globber ที่รวดเร็วและไม่รู้เรื่อง SkyframeSkyframeHybridGlobber
ซึ่งเป็นเวอร์ชันที่ใช้ Skyframe และเปลี่ยนกลับไปใช้ Globber แบบเดิมเพื่อหลีกเลี่ยง "การรีสตาร์ท Skyframe" (อธิบายไว้ด้านล่าง)
คลาส Package
เองก็มีสมาชิกบางรายที่ใช้เฉพาะในการแยกวิเคราะห์ไฟล์ WORKSPACE ซึ่งไม่เหมาะสำหรับแพ็กเกจจริง ข้อบกพร่องนี้เกิดจากการออกแบบ เนื่องจากออบเจ็กต์ที่อธิบายแพ็กเกจปกติไม่ควรมีช่องที่อธิบายสิ่งอื่น ซึ่งได้แก่
- การแมปที่เก็บ
- เครื่องมือทางเทคนิคที่ลงทะเบียน
- แพลตฟอร์มการดำเนินการที่ลงทะเบียนไว้
ตามหลักการแล้ว การแยกการแยกวิเคราะห์ไฟล์ WORKSPACE ออกจากการแยกวิเคราะห์แพ็กเกจปกติจะดีกว่า เพื่อให้ Package
ไม่ต้องตอบสนองต่อความต้องการทั้ง 2 อย่าง แต่น่าเสียดายที่ทั้ง 2 อย่างนี้สอดประสานกันอย่างลึกซึ้ง
ป้ายกํากับ เป้าหมาย และกฎ
แพ็กเกจประกอบด้วยเป้าหมายซึ่งมีประเภทต่อไปนี้
- ไฟล์: สิ่งต่างๆ ที่ใช้เป็นอินพุตหรือเอาต์พุตของบิลด์ ในภาษาของ Bazel เราเรียกสิ่งเหล่านี้ว่า อาร์ติแฟกต์ (มีคำอธิบายไว้ที่อื่น) ไฟล์ที่สร้างขึ้นในระหว่างการบิลด์ไม่ใช่เป้าหมายทั้งหมด เป็นเรื่องปกติที่เอาต์พุตของ Bazel จะไม่มีป้ายกำกับที่เชื่อมโยง
- กฎ: อธิบายขั้นตอนในการดึงเอาเอาต์พุตจากอินพุต โดยทั่วไปแล้ว ตัวแปรเหล่านี้จะเชื่อมโยงกับภาษาโปรแกรม (เช่น
cc_library
,java_library
หรือpy_library
) แต่ก็มีบางรายการที่ไม่เกี่ยวข้องกับภาษา (เช่นgenrule
หรือfilegroup
) - กลุ่มแพ็กเกจ: อธิบายไว้ในส่วนระดับการเข้าถึง
ชื่อของเป้าหมายเรียกว่าป้ายกํากับ รูปแบบคำสั่งของป้ายกำกับคือ @repo//pac/kage:name
โดยที่ repo
คือชื่อที่เก็บของป้ายกำกับ pac/kage
คือไดเรกทอรีที่มีไฟล์ BUILD
อยู่ และ name
คือเส้นทางของไฟล์ (หากป้ายกำกับหมายถึงไฟล์ต้นทาง) สัมพันธ์กับไดเรกทอรีของแพ็กเกจ เมื่ออ้างอิงเป้าหมายในบรรทัดคำสั่ง คุณสามารถละเว้นส่วนต่างๆ ของป้ายกำกับได้ ดังนี้
- หากไม่ระบุที่เก็บ ระบบจะถือว่าป้ายกำกับอยู่ในที่เก็บหลัก
- หากไม่ใส่ส่วนแพ็กเกจ (เช่น
name
หรือ:name
) ระบบจะถือว่าป้ายกำกับอยู่ในแพ็กเกจของไดเรกทอรีทํางานปัจจุบัน (ไม่อนุญาตให้ใช้เส้นทางแบบสัมพัทธ์ที่มีการอ้างอิงระดับบน (..))
กฎประเภทหนึ่ง (เช่น "คลัง C++") เรียกว่า "คลาสกฎ" คุณสามารถใช้คลาสของกฎได้ใน Starlark (ฟังก์ชัน rule()
) หรือใน Java (ซึ่งจะเรียกว่า "กฎเนทีฟ" พิมพ์ RuleClass
) ในระยะยาว กฎเฉพาะภาษาทุกกฎจะใช้ใน Starlark แต่ตระกูลกฎเดิมบางตระกูล (เช่น Java หรือ C++) ยังคงอยู่ใน Java ในขณะนี้
คุณต้องนำเข้าคลาสกฎ Starlark ที่ส่วนต้นของไฟล์ BUILD
โดยใช้คำสั่ง load()
ส่วนคลาสกฎ Java นั้น Bazel จะรู้จัก "โดยกำเนิด" เนื่องจากมีการลงทะเบียนกับ ConfiguredRuleClassProvider
คลาสของกฎจะมีข้อมูลต่างๆ เช่น
- แอตทริบิวต์ (เช่น
srcs
,deps
) ได้แก่ ประเภท ค่าเริ่มต้น ข้อจำกัด ฯลฯ - การเปลี่ยนการกำหนดค่าและลักษณะที่แนบมากับแต่ละแอตทริบิวต์ (หากมี)
- การใช้กฎ
- ผู้ให้บริการข้อมูลแบบทรานซิทีฟที่กฎ "มักจะ" สร้าง
หมายเหตุเกี่ยวกับคําศัพท์: ในโค้ดเบส เรามักใช้คำว่า "กฎ" เพื่อหมายถึงเป้าหมายที่สร้างขึ้นโดยคลาสกฎ แต่ควรใช้ "กฎ" ใน Starlark และเอกสารที่แสดงต่อผู้ใช้เพื่ออ้างอิงคลาสกฎเท่านั้น โดยเป้าหมายเป็นเพียง "เป้าหมาย" นอกจากนี้ โปรดทราบว่าแม้ว่า RuleClass
จะมี "class" ในชื่อ แต่คลาสกฎและเป้าหมายประเภทนั้นไม่มีความสัมพันธ์แบบรับช่วงของ Java
Skyframe
เฟรมเวิร์กการประเมินที่อยู่เบื้องหลัง Bazel เรียกว่า Skyframe โมเดลของเครื่องมือนี้คือทุกอย่างที่ต้องสร้างในระหว่างการสร้างจะจัดระเบียบเป็นกราฟแบบมีทิศทางที่ไม่เป็นวงจร โดยมีขอบที่ชี้จากข้อมูลหนึ่งๆ ไปยังข้อมูลที่เกี่ยวข้อง ซึ่งก็คือข้อมูลอื่นๆ ที่ต้องทราบเพื่อสร้างข้อมูลนั้น
โหนดในกราฟจะเรียกว่า SkyValue
และชื่อของโหนดจะเรียกว่า SkyKey
ทั้ง 2 อย่างนี้จะเปลี่ยนแปลงไม่ได้ มีเพียงวัตถุที่เปลี่ยนแปลงไม่ได้เท่านั้นที่เข้าถึงได้ เงื่อนไขคงที่นี้มักจะเป็นจริงเสมอ และในกรณีที่ไม่เป็นเช่นนั้น (เช่น สำหรับคลาสตัวเลือกแต่ละคลาส BuildOptions
ซึ่งเป็นสมาชิกของ BuildConfigurationValue
และ SkyKey
ของ BuildConfigurationValue
) เราจะพยายามอย่างเต็มที่ที่จะไม่เปลี่ยนแปลงคลาสเหล่านั้น หรือจะเปลี่ยนแปลงก็ให้เปลี่ยนแปลงในลักษณะที่มองไม่เห็นจากภายนอกเท่านั้น
จากข้อมูลนี้ ทุกอย่างที่ประมวลผลภายใน Skyframe (เช่น เป้าหมายที่กําหนดค่าไว้) จะต้องเป็นแบบคงที่ด้วย
วิธีสังเกตกราฟ Skyframe ที่สะดวกที่สุดคือการเรียกใช้ bazel dump
--skyframe=deps
ซึ่งจะทิ้งกราฟ 1 SkyValue
ต่อบรรทัด วิธีนี้เหมาะสําหรับบิลด์ขนาดเล็ก เนื่องจากไฟล์อาจมีขนาดค่อนข้างใหญ่
Skyframe อยู่ในแพ็กเกจ com.google.devtools.build.skyframe
แพ็กเกจ com.google.devtools.build.lib.skyframe
ที่มีชื่อคล้ายกันมีการใช้งาน Bazel บน Skyframe ดูข้อมูลเพิ่มเติมเกี่ยวกับ Skyframe ได้ที่นี่
หากต้องการประเมิน SkyKey
หนึ่งๆ เป็น SkyValue
Skyframe จะเรียกใช้ SkyFunction
ที่สอดคล้องกับประเภทของคีย์ ในระหว่างการประเมิน ฟังก์ชันอาจขอทรัพยากร Dependency อื่นๆ จาก Skyframe โดยการเรียกใช้ SkyFunction.Environment.getValue()
แบบโอเวอร์โหลดต่างๆ ซึ่งจะส่งผลข้างเคียงในการลงทะเบียนการพึ่งพาเหล่านั้นในกราฟภายในของ Skyframe เพื่อให้ Skyframe ทราบว่าต้องประเมินฟังก์ชันอีกครั้งเมื่อการพึ่งพาใดๆ เปลี่ยนแปลง กล่าวคือ การแคชและการประมวลผลแบบเพิ่มทีละน้อยของ Skyframe จะทำงานในระดับรายละเอียดของ SkyFunction
และ SkyValue
เมื่อใดก็ตามที่ SkyFunction
ขอทรัพยากร Dependency ที่ไม่มี getValue()
จะแสดงผลเป็น Null จากนั้นฟังก์ชันควรส่งการควบคุมกลับไปที่ Skyframe โดยแสดงผลเป็น Null ในภายหลัง Skyframe จะประเมินข้อกำหนดที่ไม่พร้อมใช้งาน จากนั้นจึงเริ่มฟังก์ชันใหม่ตั้งแต่ต้น เฉพาะครั้งนี้เท่านั้นที่การเรียก getValue()
จะสำเร็จโดยมีผลลัพธ์ที่ไม่ใช่ค่าว่าง
ผลที่ตามมาคือการคำนวณใดๆ ที่ดำเนินการภายใน SkyFunction
ก่อนการรีสตาร์ทจะต้องทำซ้ำ แต่จะไม่รวมงานที่ทําเพื่อประเมิน SkyValues
ที่เป็น Dependency ซึ่งมีการแคชไว้ ดังนั้น เรามักจะแก้ปัญหานี้ด้วยวิธีต่อไปนี้
- การประกาศการพึ่งพาแบบเป็นกลุ่ม (โดยใช้
getValuesAndExceptions()
) เพื่อจำกัดจำนวนการรีสตาร์ท - การแบ่ง
SkyValue
ออกเป็นชิ้นส่วนต่างๆ ที่คํานวณโดยSkyFunction
ที่แตกต่างกันเพื่อให้คํานวณและแคชได้อย่างอิสระ ซึ่งเป็นสิ่งที่ควรทำอย่างมีกลยุทธ์ เนื่องจากมีโอกาสเพิ่มการใช้หน่วยความจำ - จัดเก็บสถานะระหว่างการรีสตาร์ท โดยใช้
SkyFunction.Environment.getState()
หรือเก็บแคชเฉพาะกิจแบบ "เบื้องหลัง Skyframe"
โดยพื้นฐานแล้ว เราต้องใช้วิธีแก้ปัญหาประเภทนี้เนื่องจากเรามีโหนด Skyframe ที่ใช้งานอยู่หลายแสนโหนดเป็นประจำ และ Java ไม่รองรับเธรดแบบเบา
Starlark
Starlark เป็นภาษาเฉพาะโดเมนที่ผู้ใช้ใช้เพื่อกําหนดค่าและขยายความสามารถของ Bazel ภาษานี้สร้างขึ้นเพื่อเป็นชุดย่อยของ Python ที่มีข้อจำกัดมาก มีประเภทน้อยกว่ามาก มีข้อจำกัดมากขึ้นเกี่ยวกับโฟลว์การควบคุม และที่สำคัญที่สุดคือรับประกันความคงที่แบบสมบูรณ์เพื่อเปิดใช้การอ่านพร้อมกัน ภาษานี้ไม่ใช่ภาษา Turing-complete ซึ่งทำให้ผู้ใช้บางราย (แต่ไม่ใช่ทั้งหมด) ไม่สามารถทำงานทั่วไปด้านการเขียนโปรแกรมในภาษานี้ได้
Starlark ติดตั้งใช้งานในแพ็กเกจ net.starlark.java
นอกจากนี้ยังมีการใช้งาน Go แบบอิสระที่นี่ด้วย ปัจจุบันการใช้งาน Java ที่ใช้ใน Bazel ยังเป็นโปรแกรมล่าม
Starlark ใช้ในบริบทต่างๆ ซึ่งรวมถึง
- ภาษา
BUILD
ในส่วนนี้จะมีการกำหนดกฎใหม่ โค้ด Starlark ที่ทำงานในบริบทนี้มีสิทธิ์เข้าถึงเฉพาะเนื้อหาของไฟล์BUILD
และไฟล์.bzl
ที่ตัวไฟล์โหลดไว้เท่านั้น - คำจำกัดความของกฎ วิธีนี้ใช้กำหนดกฎใหม่ (เช่น การสนับสนุนภาษาใหม่) โค้ด Starlark ที่เรียกใช้ในบริบทนี้มีสิทธิ์เข้าถึงการกำหนดค่าและข้อมูลที่ระบุโดยทรัพยากร Dependency โดยตรง (เราจะอธิบายเพิ่มเติมในภายหลัง)
- ไฟล์ WORKSPACE ในส่วนนี้จะมีการกำหนดที่เก็บข้อมูลภายนอก (โค้ดที่ไม่ได้อยู่ในซอร์สทรีหลัก)
- คำจำกัดความของกฎที่เก็บ ซึ่งเป็นจุดที่มีการกำหนดประเภทที่เก็บภายนอกใหม่ โค้ด Starlark ที่ทำงานในบริบทนี้จะเรียกใช้โค้ดที่กำหนดเองในเครื่องที่ Bazel ทำงานอยู่ และเข้าถึงภายนอกเวิร์กスペースได้
ภาษาที่ใช้ได้สำหรับไฟล์ BUILD
และ .bzl
จะแตกต่างกันเล็กน้อยเนื่องจากมีความหมายต่างกัน ดูรายการความแตกต่างได้ที่นี่
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Starlark ได้ที่นี่
ระยะการโหลด/การวิเคราะห์
ระยะการโหลด/การวิเคราะห์คือช่วงที่ Bazel กำหนดการดำเนินการที่จำเป็นในการสร้างกฎหนึ่งๆ หน่วยพื้นฐานของ คือ "เป้าหมายที่กำหนดค่า" ซึ่งเป็นคู่ (เป้าหมาย การกำหนดค่า) ที่ค่อนข้างสมเหตุสมผล
ขั้นตอนนี้เรียกว่า "ระยะการโหลด/การวิเคราะห์" เนื่องจากสามารถแบ่งออกเป็น 2 ส่วนที่แตกต่างกัน ซึ่งก่อนหน้านี้จะทำงานตามลําดับ แต่ตอนนี้สามารถซ้อนทับกันได้
- การโหลดแพ็กเกจ กล่าวคือ การเปลี่ยนไฟล์
BUILD
ไฟล์เป็นออบเจ็กต์Package
ที่แสดงแพ็กเกจดังกล่าว - การวิเคราะห์เป้าหมายที่กําหนดค่าไว้ ซึ่งก็คือการเรียกใช้การติดตั้งใช้งานกฎเพื่อสร้างกราฟการดําเนินการ
เป้าหมายที่กําหนดค่าแต่ละรายการใน Closure แบบโอนของเป้าหมายที่กําหนดค่าซึ่งขอในบรรทัดคําสั่งต้องได้รับการวิเคราะห์จากล่างขึ้นบน กล่าวคือ เริ่มจากโหนดใบก่อน แล้วจึงไปยังโหนดในบรรทัดคําสั่ง อินพุตสําหรับการวิเคราะห์เป้าหมายที่กําหนดค่าไว้รายการเดียวมีดังนี้
- การกําหนดค่า ("วิธี" สร้างกฎ เช่น แพลตฟอร์มเป้าหมาย รวมถึงสิ่งต่างๆ เช่น ตัวเลือกบรรทัดคำสั่งที่ผู้ใช้ต้องการให้ส่งไปยังคอมไพเลอร์ C++)
- Dependency โดยตรง ผู้ให้บริการข้อมูลแบบทรานซิทีฟของบุคคลที่สามพร้อมให้บริการแก่กฎที่วิเคราะห์ ไฟล์เหล่านี้เรียกว่า "รวม" เนื่องจากให้ "การรวม" ข้อมูลใน Closure แบบทรานซิทีฟของเป้าหมายที่กําหนดค่า เช่น ไฟล์ .jar ทั้งหมดใน Classpath หรือไฟล์ .o ทั้งหมดที่ต้องลิงก์กับไบนารี C++)
- เป้าหมายเอง นี่เป็นผลการโหลดแพ็กเกจที่มีเป้าหมายอยู่ สำหรับกฎ เกณฑ์นี้รวมถึงแอตทริบิวต์ของกฎต่างๆ ซึ่งมักเป็นสิ่งที่สำคัญ
- การใช้เป้าหมายที่กําหนดค่าไว้ สำหรับกฎ การกำหนดระดับนี้ อาจเป็น Starlark หรือ Java ระบบจะใช้เป้าหมายที่กําหนดค่าไว้ซึ่งไม่ใช่กฎทั้งหมดใน Java
เอาต์พุตของการวิเคราะห์เป้าหมายที่กําหนดค่าไว้มีดังนี้
- ผู้ให้บริการข้อมูลแบบทรานซิทีฟที่กําหนดค่าเป้าหมายซึ่งใช้ข้อมูลดังกล่าวจะเข้าถึงได้
- อาร์ติแฟกต์ที่สามารถสร้างและการดำเนินการที่ทำให้เกิดอาร์ติแฟกต์
API ที่เสนอให้กับกฎ Java คือ RuleContext
ซึ่งเทียบเท่ากับอาร์กิวเมนต์ ctx
ของกฎ Starlark API ของ Bazel มีประสิทธิภาพมากกว่า แต่ขณะเดียวกันก็ทําให้เกิด "สิ่งเลวร้าย" ได้ง่ายขึ้น เช่น การเขียนโค้ดที่มีความซับซ้อนด้านเวลาหรือพื้นที่ทำงานเป็น 2 เท่า (หรือแย่กว่านั้น) ทําให้เซิร์ฟเวอร์ Bazel ขัดข้องด้วยข้อยกเว้น Java หรือละเมิดค่าคงที่ (เช่น การแก้ไขอินสแตนซ์ Options
โดยไม่ได้ตั้งใจ หรือทําให้เป้าหมายที่กําหนดค่าไว้มีการเปลี่ยนแปลงได้)
อัลกอริทึมที่กำหนดทรัพยากร Dependency โดยตรงของเป้าหมายที่กำหนดค่าแล้วจะทำงานใน DependencyResolver.dependentNodeMap()
การกำหนดค่า
การกำหนดค่าคือ "วิธีการ" ของการสร้างเป้าหมาย สำหรับแพลตฟอร์มใด พร้อมตัวเลือกบรรทัดคำสั่งใด ฯลฯ
คุณสร้างเป้าหมายเดียวกันสําหรับการกําหนดค่าหลายรายการในบิลด์เดียวกันได้ การดำเนินการนี้มีประโยชน์ เช่น เมื่อใช้โค้ดเดียวกันกับเครื่องมือที่ทำงานระหว่างการสร้างและสำหรับโค้ดเป้าหมาย และเรากำลังคอมไพล์ข้าม หรือเมื่อเราสร้างแอป Android แบบรวม (แอปที่มีโค้ดเนทีฟสำหรับสถาปัตยกรรม CPU หลายแบบ)
การกำหนดค่าคืออินสแตนซ์ BuildOptions
ในทางแนวคิด อย่างไรก็ตาม ในทางปฏิบัติ BuildOptions
จะรวมอยู่ใน BuildConfiguration
ซึ่งให้ฟังก์ชันการทำงานเพิ่มเติม โดยจะกระจายจากด้านบนของกราฟความเกี่ยวข้องไปทางด้านล่าง หากมีการเปลี่ยนแปลง จะต้องวิเคราะห์บิลด์อีกครั้ง
ซึ่งส่งผลให้เกิดความผิดปกติ เช่น ต้องมีการวิเคราะห์บิลด์ทั้งหมดอีกครั้งหากมีการเปลี่ยนแปลงจำนวนการเรียกใช้การทดสอบที่ขอ แม้ว่าจะส่งผลต่อเป้าหมายการทดสอบเท่านั้น (เรามีแผนที่จะ "ตัด" การกําหนดค่าเพื่อไม่ให้เกิดกรณีเช่นนี้ แต่ยังไม่พร้อมใช้งาน)
เมื่อการติดตั้งใช้งานกฎต้องใช้การกําหนดค่าบางส่วน จะต้องประกาศการกําหนดค่านั้นในคําจํากัดความโดยใช้ RuleClass.Builder.requiresConfigurationFragments()
การดำเนินการนี้ทั้งเพื่อหลีกเลี่ยงข้อผิดพลาด (เช่น กฎ Python ที่ใช้ข้อมูลโค้ด Java) และเพื่ออำนวยความสะดวกในการตัดแต่งการกำหนดค่า เช่น หากตัวเลือก Python เปลี่ยนแปลง ก็ไม่จำเป็นต้องวิเคราะห์เป้าหมาย C++ อีกครั้ง
การกำหนดค่าของกฎไม่จำเป็นต้องเหมือนกับกฎ "หลัก" ของกฎดังกล่าว กระบวนการเปลี่ยนการกําหนดค่าในขอบความเกี่ยวข้องเรียกว่า "การเปลี่ยนการกําหนดค่า" ซึ่งอาจเกิดขึ้นได้ 2 แห่ง ดังนี้
- บนขอบทรัพยากร Dependency การเปลี่ยนเหล่านี้จะระบุไว้ใน
Attribute.Builder.cfg()
และจะเป็นฟังก์ชันจากRule
(ตำแหน่งที่เกิดการเปลี่ยน) และBuildOptions
(การกำหนดค่าเดิม) ไปยังBuildOptions
อย่างน้อย 1 รายการ (การกำหนดค่าเอาต์พุต) - ใน Edge ที่เข้ามายังเป้าหมายที่กําหนดค่าไว้ ซึ่งระบุไว้ใน
RuleClass.Builder.cfg()
ชั้นเรียนที่เกี่ยวข้องคือ TransitionFactory
และ ConfigurationTransition
การเปลี่ยนการกำหนดค่ามีการใช้งาน เช่น
- เพื่อประกาศว่าใช้ทรัพยากรบางอย่างในระหว่างการสร้าง และควรสร้างในสถาปัตยกรรมการดำเนินการ
- วิธีประกาศว่าต้องสร้างการพึ่งพาบางอย่างสำหรับสถาปัตยกรรมหลายแบบ (เช่น สําหรับโค้ดเนทีฟใน APK ของ Android แบบรวม)
หากการเปลี่ยนการกำหนดค่าส่งผลให้มีการกำหนดค่าหลายรายการ การเปลี่ยนดังกล่าวจะเรียกว่าการเปลี่ยนแบบแยก
นอกจากนี้ คุณยังใช้การเปลี่ยนการกำหนดค่าใน Starlark ได้ด้วย (เอกสารประกอบที่นี่)
ผู้ให้บริการข้อมูลทางอ้อม
ผู้ให้บริการข้อมูลแบบทรานซิทีฟเป็นวิธี (และวิธีเดียว) ที่เป้าหมายที่กําหนดค่าไว้จะบอกข้อมูลเกี่ยวกับเป้าหมายอื่นๆ ที่กําหนดค่าไว้ซึ่งต้องอาศัยข้อมูลดังกล่าว สาเหตุที่ชื่อมีคำว่า "Transitive" อยู่ด้วยคือโดยปกติแล้วการรวมประเภทนี้จะเป็นการนำ Closure แบบทรานซิทีฟของเป้าหมายที่กําหนดค่าไว้มารวมกัน
โดยทั่วไปจะมีการโต้ตอบแบบ 1:1 ระหว่างผู้ให้บริการข้อมูลแบบทรานซิทีฟของ Java กับผู้ให้บริการข้อมูล Starlark (ข้อยกเว้นคือ DefaultInfo
ซึ่งเป็นการรวม FileProvider
, FilesToRunProvider
และ RunfilesProvider
เนื่องจากคาดว่า API นั้นเป็น Starlark-ish มากกว่าการทับศัพท์ของ Java โดยตรง)
รหัสของผู้ใช้คือสิ่งใดสิ่งหนึ่งต่อไปนี้
- ออบเจ็กต์คลาส Java ตัวเลือกนี้ใช้ได้กับผู้ให้บริการที่เข้าถึงจาก Starlark ไม่ได้เท่านั้น ผู้ให้บริการเหล่านี้เป็นคลาสย่อยของ
TransitiveInfoProvider
- สตริง รูปแบบนี้เป็นรูปแบบเดิมที่เราไม่แนะนำให้ใช้เนื่องจากมีแนวโน้มที่จะเกิดการทับซ้อนของชื่อ ผู้ให้บริการข้อมูลแบบเปลี่ยนผ่านดังกล่าวเป็นคลาสย่อยโดยตรงของ
build.lib.packages.Info
- สัญลักษณ์ผู้ให้บริการ ซึ่งสร้างได้จาก Starlark โดยใช้ฟังก์ชัน
provider()
และเป็นวิธีที่แนะนำในการสร้างผู้ให้บริการใหม่ สัญลักษณ์จะแสดงโดยอินสแตนซ์Provider.Key
ใน Java
ผู้ให้บริการรายใหม่ที่ใช้งานใน Java ควรใช้งานโดยใช้ BuiltinProvider
NativeProvider
เลิกใช้งานแล้ว (เรายังไม่มีเวลานำออก) และ
TransitiveInfoProvider
ไม่สามารถเข้าถึงคลาสย่อยจาก Starlark ได้
เป้าหมายที่กําหนดค่า
ระบบจะใช้เป้าหมายที่กําหนดค่าเป็น RuleConfiguredTargetFactory
แต่ละคลาสกฎที่ติดตั้งใช้งานใน Java จะมีคลาสย่อย เป้าหมายที่กำหนดค่า Starlark
สร้างขึ้นผ่าน StarlarkRuleConfiguredTargetUtil.buildRule()
โรงงานเป้าหมายที่กำหนดค่าไว้ควรใช้ RuleConfiguredTargetBuilder
เพื่อสร้างผลลัพธ์ ซึ่งประกอบด้วยสิ่งต่อไปนี้
filesToBuild
ของบุคคลนั้นๆ ซึ่งเป็นแนวคิดที่คลุมเครือเกี่ยวกับ "ชุดไฟล์ที่กฎนี้แสดงถึง" ไฟล์เหล่านี้คือไฟล์ที่ถูกสร้างขึ้นเมื่อเป้าหมายที่กำหนดค่าไว้อยู่ในบรรทัดคำสั่งหรือใน srcs ของ Genrule- ไฟล์รันไทม์ ไฟล์ปกติ และไฟล์ข้อมูล
- กลุ่มเอาต์พุต นี่คือ "ไฟล์ชุดอื่นๆ" ที่กฎสร้างได้ โดยเข้าถึงได้โดยใช้แอตทริบิวต์ output_group ของกฎ filegroup ใน BUILD และใช้ผู้ให้บริการ
OutputGroupInfo
ใน Java
การเรียกใช้ไฟล์
ไฟล์ไบนารีบางไฟล์ต้องใช้ไฟล์ข้อมูลจึงจะทำงานได้ ตัวอย่างที่เห็นได้ชัดคือทดสอบที่ต้องใช้ไฟล์อินพุต ซึ่งใน Bazel จะใช้แนวคิด "runfiles" "ต้นไม้ runfiles" คือต้นไม้ไดเรกทอรีของไฟล์ข้อมูลสําหรับไบนารีหนึ่งๆ ระบบจะสร้างไฟล์นี้ในระบบไฟล์เป็นต้นไม้ลิงก์ที่มีลิงก์แต่ละรายการซึ่งชี้ไปยังไฟล์ในแหล่งที่มาของต้นไม้เอาต์พุต
ชุดไฟล์รันไทม์จะแสดงเป็นอินสแตนซ์ Runfiles
โดยแนวคิดแล้ว ไฟล์นี้เป็นการแมปจากเส้นทางของไฟล์ในโครงสร้าง runfiles ไปยังอินสแตนซ์ Artifact
ที่แสดงไฟล์นั้น การดำเนินการนี้มีความซับซ้อนกว่า Map
เดี่ยวเล็กน้อยเนื่องจากเหตุผล 2 ข้อต่อไปนี้
- ส่วนใหญ่แล้ว เส้นทาง runfiles ของไฟล์จะเหมือนกับ execpath ของไฟล์ เราใช้วิธีนี้เพื่อประหยัด RAM
- มีรายการเดิมหลายประเภทในแผนผัง Runfile ซึ่งต้องแสดงด้วย
ระบบรวบรวมไฟล์รันไฟล์โดยใช้ RunfilesProvider
: อินสแตนซ์ของคลาสนี้แสดงแทนไฟล์การเรียกใช้ของเป้าหมายที่กำหนดค่าไว้ (เช่น ไลบรารี) และความจำเป็นการปิดแบบทรานซิทีฟ โดยจะรวบรวมไฟล์เหล่านั้นเหมือนกับชุดที่ซ้อนกัน (อันที่จริงแล้ว ไฟล์ดังกล่าวติดตั้งโดยใช้ชุดที่ซ้อนกันใต้หน้าปก): แต่ละการรวมเป้าหมายกับรันไฟล์ของทรัพยากร Dependency จะเพิ่มไฟล์ของตนเองบางส่วน จากนั้นส่งการตั้งค่าที่ได้ไปยังกราฟทรัพยากร Dependency อินสแตนซ์ RunfilesProvider
มีอินสแตนซ์ Runfiles
2 รายการ โดยอินสแตนซ์หนึ่งเป็นอินสแตนซ์ที่ขึ้นอยู่กับกฎผ่านแอตทริบิวต์ "data" และอีกอินสแตนซ์หนึ่งสำหรับทรัพยากร Dependency ที่เข้ามาใหม่ทุกประเภท เนื่องจากบางครั้งเป้าหมายอาจนำเสนอไฟล์การเรียกใช้ที่แตกต่างกันเมื่อพึ่งพาผ่านแอตทริบิวต์ข้อมูลมากกว่าแบบอื่น นี่เป็นลักษณะการทำงานเดิมที่ไม่พึงประสงค์ซึ่งเรายังไม่ได้นำออก
ไฟล์รันไทม์ของไบนารีจะแสดงเป็นอินสแตนซ์ของ RunfilesSupport
ซึ่งแตกต่างจาก Runfiles
เนื่องจาก RunfilesSupport
มีความสามารถในการสร้างขึ้นจริง (ต่างจาก Runfiles
ที่เป็นเพียงแค่การแมป) ซึ่งจำเป็นต้องใช้คอมโพเนนต์เพิ่มเติมต่อไปนี้
- ไฟล์ Manifest ของไฟล์รันไทม์อินพุต นี่คือคำอธิบายที่แปลงเป็นอนุกรมของต้นไม้ runfiles ไฟล์นี้ใช้เป็นพร็อกซีสําหรับเนื้อหาของต้นไม้ runfiles และ Bazel จะถือว่าต้นไม้ runfiles มีการเปลี่ยนแปลงก็ต่อเมื่อเนื้อหาของไฟล์ Manifest มีการเปลี่ยนแปลงเท่านั้น
- ไฟล์ Manifest ของไฟล์รันไทม์เอาต์พุต มีการใช้งานโดยไลบรารีรันไทม์ที่จัดการโครงสร้างของรันไฟล์ โดยเฉพาะใน Windows ซึ่งบางครั้งก็ไม่รองรับลิงก์สัญลักษณ์
- คนกลางของรันไฟล์ ในการสร้างต้นไม้การเรียกใช้ไฟล์อยู่นั้น ต้องสร้างทรี Symlink และอาร์ติแฟกต์ที่ลิงก์สัญลักษณ์ชี้ไป ในการลดจำนวนเอดจ์ของทรัพยากร Dependency จะใช้สื่อกลางของ Runfile เพื่อแทนค่าเหล่านี้ได้
- อาร์กิวเมนต์บรรทัดคำสั่งสําหรับการเรียกใช้ไบนารีที่ออบเจ็กต์
RunfilesSupport
แสดงถึง
ลักษณะ
ส่วนต่างๆ เป็นวิธี "เผยแพร่การคํานวณไปตามกราฟทรัพยากร Dependency" เราได้อธิบายไว้ที่นี่สำหรับผู้ใช้ Bazel ตัวอย่างที่สร้างแรงจูงใจที่ดีคือบัฟเฟอร์โปรโตคอล กฎ proto_library
ไม่ควรรู้เกี่ยวกับภาษาใดภาษาหนึ่ง แต่การสร้างการใช้ข้อความบัฟเฟอร์โปรโตคอล ("หน่วยพื้นฐาน" ของบัฟเฟอร์โปรโตคอล) ในภาษาโปรแกรมใดก็ตามควรเชื่อมโยงกับกฎ proto_library
เพื่อที่ว่าหากเป้าหมาย 2 รายการในภาษาเดียวกันต้องพึ่งพาบัฟเฟอร์โปรโตคอลเดียวกัน จะมีการสร้างเพียงครั้งเดียวเท่านั้น
เช่นเดียวกับเป้าหมายที่กําหนดค่าไว้ เป้าหมายเหล่านี้จะแสดงใน Skyframe เป็น SkyValue
และวิธีการสร้างจะคล้ายกับการสร้างเป้าหมายที่กําหนดค่าไว้มาก กล่าวคือ มีคลาสโรงงานชื่อ ConfiguredAspectFactory
ที่มีสิทธิ์เข้าถึง RuleContext
แต่ต่างจากโรงงานเป้าหมายที่กําหนดค่าไว้ตรงที่เป้าหมายเหล่านี้จะทราบเกี่ยวกับเป้าหมายที่กําหนดค่าไว้ซึ่งเชื่อมโยงอยู่และผู้ให้บริการของเป้าหมายนั้นด้วย
ชุดแง่มุมที่เผยแพร่ไปตามกราฟความเกี่ยวข้องจะระบุสำหรับแอตทริบิวต์แต่ละรายการโดยใช้ฟังก์ชัน Attribute.Builder.aspects()
มีชั้นเรียนที่มีชื่อและสร้างความสับสน
ที่เข้าร่วมในกระบวนการนี้ 2-3 ชั้นเรียน ได้แก่
AspectClass
คือการใช้งานแง่มุม อาจเป็นใน Java (ในกรณีนี้จะเป็นคลาสย่อย) หรือใน Starlark (ในกรณีนี้จะเป็นอินสแตนซ์ของStarlarkAspectClass
) ซึ่งคล้ายกับRuleConfiguredTargetFactory
AspectDefinition
คือคําจํากัดความของลักษณะ ซึ่งรวมถึงผู้ให้บริการที่จําเป็น ผู้ให้บริการที่มีให้ และมีการอ้างอิงถึงการใช้งาน เช่น อินสแตนซ์AspectClass
ที่เหมาะสม ซึ่งคล้ายกับRuleClass
AspectParameters
เป็นวิธีกำหนดพารามิเตอร์ของลักษณะที่เผยแพร่ลงในกราฟทรัพยากร Dependency ปัจจุบันเป็นแผนที่สตริงกับสตริง ตัวอย่างที่ดีของประโยชน์คือบัฟเฟอร์โปรโตคอล ถ้าภาษาหนึ่งมี API หลาย API ควรเผยแพร่ข้อมูลของ API ที่บัฟเฟอร์โปรโตคอลให้ลงไปในกราฟทรัพยากร DependencyAspect
แสดงข้อมูลทั้งหมดที่จําเป็นสําหรับคํานวณแง่มุมที่ส่งต่อไปยังกราฟความเกี่ยวข้อง ซึ่งประกอบด้วยคลาสการแสดงผล คำจำกัดความ และพารามิเตอร์RuleAspect
คือฟังก์ชันที่กําหนดว่ากฎหนึ่งๆ ควรเผยแพร่แง่มุมใด นั่นคือฟังก์ชันRule
->Aspect
ความซับซ้อนที่คาดไม่ถึงคือแง่มุมหนึ่งสามารถแนบไปกับแง่มุมอื่นได้ เช่น แง่มุมที่รวบรวม classpath สําหรับ Java IDE อาจต้องการทราบเกี่ยวกับไฟล์ .jar ทั้งหมดใน classpath แต่ไฟล์บางไฟล์เป็นบัฟเฟอร์โปรโตคอล ในกรณีนี้ แง่มุม IDE จะต้องการแนบไปกับคู่ (proto_library
rule + Java proto aspect)
ระบบจะบันทึกความซับซ้อนขององค์ประกอบในองค์ประกอบไว้ในคลาส
AspectCollection
แพลตฟอร์มและ Toolchain
Bazel รองรับการสร้างแบบหลายแพลตฟอร์ม ซึ่งก็คือการสร้างที่อาจมีสถาปัตยกรรมหลายแบบที่ใช้เรียกใช้การดำเนินการสร้าง และสถาปัตยกรรมหลายแบบที่ใช้สร้างโค้ด สถาปัตยกรรมเหล่านี้เรียกว่าแพลตฟอร์มในโครงสร้างของ Bazel (ดูเอกสารประกอบฉบับเต็มที่นี่)
แพลตฟอร์มจะอธิบายด้วยการแมปคีย์-ค่าจากการตั้งค่าข้อจำกัด (เช่น แนวคิด "สถาปัตยกรรม CPU") ไปยังค่าข้อจำกัด (เช่น CPU บางรุ่น เช่น x86_64) เรามี "พจนานุกรม" ของการตั้งค่าข้อจำกัดและค่าจำกัดที่ใช้บ่อยที่สุดในที่เก็บ @platforms
แนวคิดของ toolchain มาจากข้อเท็จจริงที่ว่าผู้สร้างอาจต้องใช้คอมไพเลอร์ที่ต่างกันไปตามแพลตฟอร์มที่บิลด์ทำงานอยู่และกำหนดเป้าหมายแพลตฟอร์ม ตัวอย่างเช่น เครื่องมือเชน C++ รายการหนึ่งอาจทำงานบนระบบปฏิบัติการที่เฉพาะเจาะจงและสามารถกำหนดเป้าหมายระบบปฏิบัติการอื่นๆ ได้ Bazel ต้องกำหนดคอมไพเลอร์ C++ ที่ใช้ตามการเรียกใช้ที่กำหนดและแพลตฟอร์มเป้าหมาย (ดูเอกสารประกอบสำหรับชุดเครื่องมือที่นี่)
โดย Toolchain จะมีคำอธิบายประกอบพร้อมชุดข้อจำกัดแพลตฟอร์มการดำเนินการและเป้าหมายที่เครื่องมือดังกล่าวรองรับ ด้วยเหตุนี้ คําจํากัดความของเครื่องมือทางเทคนิคจึงแบ่งออกเป็น 2 ส่วน ดังนี้
- กฎ
toolchain()
ที่อธิบายชุดข้อจำกัดด้านการดำเนินการและเป้าหมายที่เครื่องมือทางเทคนิครองรับ และบอกประเภท (เช่น C++ หรือ Java) ของเครื่องมือทางเทคนิค (กฎหลังแสดงโดยกฎtoolchain_type()
) - กฎเฉพาะภาษาที่อธิบาย Toolchain จริง (เช่น
cc_toolchain()
)
เนื่องจากเราจําเป็นต้องรู้ข้อจํากัดของ Toolchain ทุกตัวเพื่อทำการแปลง Toolchain และกฎ *_toolchain()
ที่เจาะจงภาษามีข้อมูลมากกว่านั้นมาก ดังนั้นให้ใช้เวลาโหลดนานขึ้น
แพลตฟอร์มการเรียกใช้จะระบุด้วยวิธีใดวิธีหนึ่งต่อไปนี้
- ในไฟล์ WORKSPACE โดยใช้ฟังก์ชัน
register_execution_platforms()
- ในบรรทัดคำสั่งโดยใช้ตัวเลือกบรรทัดคำสั่ง --extra_execution_platforms
ระบบจะคํานวณชุดแพลตฟอร์มการเรียกใช้ที่ใช้ได้ในส่วน
RegisteredExecutionPlatformsFunction
แพลตฟอร์มเป้าหมายสําหรับเป้าหมายที่กําหนดค่าไว้จะกําหนดโดย
PlatformOptions.computeTargetPlatform()
รายการแพลตฟอร์มนี้เพราะเราต้องการรองรับแพลตฟอร์มเป้าหมายหลายแพลตฟอร์มในอนาคต แต่ยังไม่ได้ใช้งาน
ชุดเครื่องมือที่จะใช้กับเป้าหมายที่กําหนดค่าไว้จะกําหนดโดย
ToolchainResolutionFunction
ซึ่งขึ้นอยู่กับปัจจัยต่อไปนี้
- ชุดเครื่องมือทางเทคนิคที่ลงทะเบียน (ในไฟล์ WORKSPACE และการกำหนดค่า)
- แพลตฟอร์มการดำเนินการและแพลตฟอร์มเป้าหมายที่ต้องการ (ในการกําหนดค่า)
- ชุดประเภท Toolchain ที่เป้าหมายที่กำหนดค่าไว้ต้องใช้ (ใน
UnloadedToolchainContextKey)
- ชุดข้อจำกัดของแพลตฟอร์มการเรียกใช้ของเป้าหมายที่กําหนดค่าไว้ (แอตทริบิวต์
exec_compatible_with
) และการกําหนดค่า (--experimental_add_exec_constraints_to_targets
) ในUnloadedToolchainContextKey
ผลลัพธ์ที่ได้คือ UnloadedToolchainContext
ซึ่งโดยพื้นฐานแล้วคือการแมปจากประเภทเครื่องมือ (แสดงเป็นอินสแตนซ์ ToolchainTypeInfo
) กับป้ายกำกับของเครื่องมือที่เลือก ไฟล์นี้เรียกว่า "ไม่ได้โหลด" เนื่องจากไม่มีเครื่องมือทางเทคนิคเอง แต่มีเฉพาะป้ายกำกับของเครื่องมือทางเทคนิคเท่านั้น
จากนั้นระบบจะโหลดเครื่องมือทางเทคนิคโดยใช้ ResolvedToolchainContext.load()
และนำไปใช้งานโดยการติดตั้งใช้งานเป้าหมายที่กำหนดค่าไว้ซึ่งขอเครื่องมือทางเทคนิคเหล่านั้น
นอกจากนี้ เรายังมีระบบเดิมที่อาศัยการกําหนดค่า "โฮสต์" รายการเดียวและการกําหนดค่าเป้าหมายที่แสดงโดย Flag การกําหนดค่าต่างๆ เช่น --cpu
เราจะค่อยๆ เปลี่ยนไปใช้ระบบข้างต้น เราได้ติดตั้งใช้งานการแมปแพลตฟอร์มเพื่อแปลงค่า Flag แบบเดิมกับข้อจำกัดของแพลตฟอร์มรูปแบบใหม่เพื่อจัดการกรณีที่ผู้ใช้ใช้ค่าการกำหนดค่าเดิม
โค้ดอยู่ใน PlatformMappingFunction
และใช้ "ภาษา" ที่ไม่ใช้ Starlark
ข้อจำกัด
บางครั้งคุณอาจต้องการกำหนดเป้าหมายให้ใช้ได้กับแพลตฟอร์มเพียงไม่กี่แพลตฟอร์ม Bazel มีกลไกมากมายในการบรรลุเป้าหมายนี้ (น่าเสียดาย)
- ข้อจำกัดเฉพาะกฎ
environment_group()
/environment()
- ข้อจำกัดของแพลตฟอร์ม
ข้อจำกัดเฉพาะกฎส่วนใหญ่ใช้ใน Google สำหรับกฎ Java ซึ่งกำลังจะเลิกใช้งานและไม่มีให้ใช้งานใน Bazel แต่ซอร์สโค้ดอาจมีการอ้างอิงถึงข้อจำกัดดังกล่าว แอตทริบิวต์ที่ควบคุมกระบวนการนี้เรียกว่า
constraints=
environment_group() และ environment()
กฎเหล่านี้เป็นกลไกเดิมและไม่ได้ใช้กันอย่างแพร่หลาย
กฎการสร้างทั้งหมดสามารถประกาศ "สภาพแวดล้อม" ที่สามารถสร้างได้ โดยที่ "สภาพแวดล้อม" คืออินสแตนซ์ของกฎ environment()
คุณสามารถระบุสภาพแวดล้อมที่รองรับสำหรับกฎได้หลายวิธี ดังนี้
- ผ่านแอตทริบิวต์
restricted_to=
รูปแบบนี้เป็นรูปแบบที่ตรงที่สุดของข้อกําหนด ซึ่งจะประกาศชุดสภาพแวดล้อมที่แน่นอนที่กฎรองรับสําหรับกลุ่มนี้ - ผ่านแอตทริบิวต์
compatible_with=
คำสั่งนี้จะประกาศสภาพแวดล้อมที่กฎรองรับนอกเหนือจากสภาพแวดล้อม "มาตรฐาน" ที่รองรับโดยค่าเริ่มต้น - ผ่านแอตทริบิวต์ระดับแพ็กเกจ
default_restricted_to=
และdefault_compatible_with=
- ผ่านข้อกําหนดเริ่มต้นในกฎ
environment_group()
สภาพแวดล้อมทุกสภาพแวดล้อมจะอยู่ในกลุ่มของคู่แข่งที่เกี่ยวข้องตามธีม (เช่น "สถาปัตยกรรม CPU" "เวอร์ชัน JDK" หรือ "ระบบปฏิบัติการบนอุปกรณ์เคลื่อนที่") การกําหนดกลุ่มสภาพแวดล้อมจะระบุสภาพแวดล้อมที่ควรรองรับโดย "ค่าเริ่มต้น" หากไม่ได้ระบุไว้เป็นอย่างอื่นโดยแอตทริบิวต์restricted_to=
/environment()
กฎที่ไม่มีแอตทริบิวต์ดังกล่าวจะรับค่าเริ่มต้นทั้งหมด - ผ่านค่าเริ่มต้นของคลาสกฎ ซึ่งจะลบล้างค่าเริ่มต้นส่วนกลางสำหรับอินสแตนซ์ทั้งหมดของคลาสกฎที่ระบุ ตัวอย่างเช่น วิธีนี้จะช่วยให้กฎ
*_test
ทั้งหมดทดสอบได้โดยที่แต่ละอินสแตนซ์ไม่ต้องประกาศความสามารถนี้อย่างชัดแจ้ง
environment()
ใช้งานเป็นกฎทั่วไป ส่วน environment_group()
มีทั้งเป็นคลาสย่อยของ Target
แต่ไม่ใช่ Rule
(EnvironmentGroup
) และเป็นฟังก์ชันที่ใช้ได้โดยค่าเริ่มต้นจาก Starlark (StarlarkLibrary.environmentGroup()
) ซึ่งจะสร้างเป้าหมายที่มีชื่อเดียวกันในท้ายที่สุด ทั้งนี้เพื่อหลีกเลี่ยงการขึ้นต่อกันแบบวนซ้ำที่จะเกิดขึ้นเนื่องจากแต่ละสภาพแวดล้อมต้องประกาศกลุ่มสภาพแวดล้อมที่กลุ่มสภาพแวดล้อมนั้นเป็นสมาชิกอยู่ และแต่ละกลุ่มสภาพแวดล้อมต้องประกาศสภาพแวดล้อมเริ่มต้น
คุณจำกัดบิลด์ให้ใช้ได้กับบางสภาพแวดล้อมได้โดยใช้ตัวเลือกบรรทัดคำสั่ง --target_environment
การใช้งานการตรวจสอบข้อจำกัดมีอยู่ใน RuleContextConstraintSemantics
และ TopLevelConstraintSemantics
ข้อจำกัดของแพลตฟอร์ม
ปัจจุบันวิธี "อย่างเป็นทางการ" ในการอธิบายแพลตฟอร์มที่เป้าหมายเข้ากันได้คือการใช้ข้อจำกัดเดียวกับที่ใช้อธิบายเครื่องมือและแพลตฟอร์ม อยู่ระหว่างตรวจสอบในคำขอดึงข้อมูล #10945
ระดับการแชร์
หากคุณทํางานกับโค้ดเบสขนาดใหญ่ที่มีนักพัฒนาซอฟต์แวร์จํานวนมาก (เช่น ที่ Google) คุณควรระมัดระวังไม่ให้ผู้อื่นใช้โค้ดของคุณโดยพลการ มิเช่นนั้น ผู้คนจะพึ่งพาลักษณะการทำงานที่คุณคิดว่าเป็นรายละเอียดการใช้งานตามกฎของ Hyrum
Bazel รองรับการทำงานนี้โดยกลไกที่เรียกว่าระดับการเข้าถึง ซึ่งประกาศได้ว่าเป้าหมายที่เฉพาะเจาะจงจะอ้างอิงได้โดยใช้แอตทริบิวต์ระดับการเข้าถึงเท่านั้น แอตทริบิวต์นี้มีความพิเศษเล็กน้อยเนื่องจากแม้ว่าจะมีรายการป้ายกำกับ แต่ป้ายกำกับเหล่านี้อาจเข้ารหัสรูปแบบเหนือชื่อแพ็กเกจแทนที่จะเป็นเคอร์เซอร์ไปยังเป้าหมายที่เฉพาะเจาะจง (ใช่ นี่ถือเป็นข้อบกพร่องในการออกแบบ)
ซึ่งติดตั้งใช้งานในตำแหน่งต่อไปนี้
- อินเทอร์เฟซ
RuleVisibility
แสดงประกาศการแสดงผล โดยอาจเป็นค่าคงที่ (สาธารณะทั้งหมดหรือส่วนตัวทั้งหมด) หรือรายการป้ายกำกับก็ได้ - ป้ายกำกับอาจหมายถึงกลุ่มแพ็กเกจ (รายการแพ็กเกจที่กำหนดไว้ล่วงหน้า) แพ็กเกจโดยตรง (
//pkg:__pkg__
) หรือแพ็กเกจย่อย (//pkg:__subpackages__
) ซึ่งแตกต่างจากไวยากรณ์บรรทัดคำสั่งที่ใช้//pkg:*
หรือ//pkg/...
- กลุ่มแพ็กเกจจะใช้เป็นเป้าหมายของตัวเอง (
PackageGroup
) และเป้าหมายที่กำหนดค่าไว้ (PackageGroupConfiguredTarget
) เราอาจแทนที่กลุ่มแพ็กเกจเหล่านี้ด้วยกฎง่ายๆ ได้หากต้องการ ระบบจะใช้ตรรกะเหล่านี้โดยได้รับความช่วยเหลือจากPackageSpecification
ซึ่งสอดคล้องกับรูปแบบเดียว เช่น//pkg/...
,PackageGroupContents
ซึ่งสอดคล้องกับแอตทริบิวต์packages
ของpackage_group
รายการเดียว และPackageSpecificationProvider
ซึ่งรวบรวมข้อมูลในpackage_group
และincludes
แบบเปลี่ยนผ่าน - การเปลี่ยนจากรายการป้ายกำกับระดับการเข้าถึงเป็นรายการทรัพยากรที่เกี่ยวข้องจะดำเนินการใน
DependencyResolver.visitTargetVisibility
และที่อื่นๆ อีกเล็กน้อย - การตรวจสอบจริงจะดำเนินการใน
CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility()
ชุดที่ซ้อนกัน
บ่อยครั้งที่เป้าหมายที่กำหนดค่าไว้จะรวมชุดไฟล์จากทรัพยากร Dependency แล้วเพิ่มไฟล์ขึ้นมาเอง และรวมชุดดังกล่าวไว้ในผู้ให้บริการข้อมูลแบบทรานซิทีฟ เพื่อให้เป้าหมายที่กำหนดค่าที่อาศัยการอ้างอิงดังกล่าวทำแบบเดียวกันได้ ตัวอย่าง
- ไฟล์ส่วนหัว C++ ที่ใช้สำหรับบิลด์
- ไฟล์ออบเจ็กต์ที่แสดงการปิดเชิงการเปลี่ยนรูปแบบของ
cc_library
- ชุดของไฟล์ .jar ที่ต้องอยู่ในคลาสพาธเพื่อให้กฎ Java คอมไพล์หรือเรียกใช้
- ชุดไฟล์ Python ใน Closure แบบทรานซิทีฟของกฎ Python
หากทําด้วยวิธีที่ไม่ซับซ้อนโดยใช้ List
หรือ Set
ผลลัพธ์ที่ได้คือการใช้งานหน่วยความจําแบบสี่เหลี่ยมจัตุรัส กล่าวคือ หากมีกฎ N รายการและแต่ละกฎเพิ่มไฟล์ 1 ไฟล์ เราก็จะมีสมาชิกคอลเล็กชัน 1+2+...+N
ในการแก้ปัญหานี้ เราจึงคิดวิธี NestedSet
ซึ่งเป็นโครงสร้างข้อมูลที่ประกอบด้วยอินสแตนซ์ NestedSet
อื่นๆ และสมาชิกบางส่วนของตนเอง จึงเป็นกราฟชุดแบบมีทิศทางแบบไม่มีวงวน รายการเหล่านี้เป็นแบบคงที่และสามารถวนซ้ำสมาชิกได้ เรากำหนดลำดับการทำซ้ำหลายลำดับ (NestedSet.Order
) ได้แก่ สั่งล่วงหน้า ลำดับตามหลัง โทโพโลยี (โหนดจะอยู่หลังบรรพบุรุษเสมอ) และ "ไม่สนใจ แต่ควรจะเหมือนกันทุกครั้ง"
โครงสร้างข้อมูลเดียวกันนี้เรียกว่า depset
ใน Starlark
อาร์ติแฟกต์และการดำเนินการ
บิลด์จริงประกอบด้วยชุดคำสั่งที่ต้องเรียกใช้เพื่อสร้างเอาต์พุตที่ผู้ใช้ต้องการ คำสั่งจะแสดงเป็นอินสแตนซ์ของคลาส Action
และไฟล์จะแสดงเป็นอินสแตนซ์ของคลาส Artifact
โดยจัดเรียงเป็นกราฟแบบ 2 กลุ่มที่มีทิศทางและไม่วนซ้ำ ซึ่งเรียกว่า "กราฟการดำเนินการ"
ออบเจ็กต์โค้ดมี 2 ประเภท ได้แก่ ออบเจ็กต์โค้ดต้นทาง (ออบเจ็กต์โค้ดที่มีให้ใช้งานก่อน Bazel เริ่มดำเนินการ) และออบเจ็กต์โค้ดที่ดึงข้อมูล (ออบเจ็กต์โค้ดที่ต้องสร้าง) อาร์ติแฟกต์ที่ดึงข้อมูลมาอาจมีได้หลายประเภท ดังนี้
- **อาร์ติแฟกต์ปกติ **ระบบจะตรวจสอบความทันสมัยของไฟล์เหล่านี้โดยคํานวณการตรวจสอบผลรวมโดยใช้ mtime เป็นทางลัด เราจะไม่ตรวจสอบผลรวมของไฟล์หาก ctime ของไฟล์ไม่เปลี่ยนแปลง
- อาร์ติแฟกต์ลิงก์สัญลักษณ์ที่ยังไม่ได้รับการแก้ไข ซึ่งมีการตรวจสอบความทันสมัยโดยเรียกใช้ readlink() ซึ่งต่างจากอาร์ติแฟกต์ทั่วไปตรงที่อาจทำให้เกิดลิงก์สัญลักษณ์ที่ทำให้อันตราย มักใช้ในกรณีที่จะแพ็กไฟล์บางไฟล์ลงในไฟล์เก็บถาวร
- อาร์ติแฟกต์ต้นไม้ ไฟล์เหล่านี้ไม่ใช่ไฟล์เดี่ยว แต่เป็นโครงสร้างไดเรกทอรี ระบบจะตรวจสอบความเป็นปัจจุบันโดยตรวจสอบชุดของไฟล์และเนื้อหาในไฟล์ โดยจะแสดงเป็น
TreeArtifact
- อาร์ติแฟกต์ข้อมูลเมตาแบบคงที่ การเปลี่ยนแปลงอาร์ติแฟกต์เหล่านี้จะไม่ทริกเกอร์การสร้างใหม่ ข้อมูลนี้ใช้สำหรับข้อมูลการประทับเวลาของบิลด์เท่านั้น เราไม่ต้องการสร้างใหม่เพียงเพราะเวลาปัจจุบันมีการเปลี่ยนแปลง
ไม่มีเหตุผลพื้นฐานใดที่ทำให้อาร์ติแฟกต์ต้นฉบับไม่สามารถเป็นอาร์ติแฟกต์ต้นไม้หรืออาร์ติแฟกต์ลิงก์สัญลักษณ์ที่ยังไม่ได้รับการแก้ไข เพียงแต่เรายังไม่ได้ใช้งาน (แต่ควรใช้งาน การอ้างอิงไดเรกทอรีต้นฉบับในไฟล์ BUILD
เป็นหนึ่งในปัญหาความไม่ถูกต้องที่ทราบกันมานานไม่กี่ข้อเกี่ยวกับ Bazel เราใช้งานที่ได้ผลอยู่บ้างซึ่งเปิดใช้โดยพร็อพเพอร์ตี้ BAZEL_TRACK_SOURCE_DIRECTORIES=1
JVM)
Artifact
ประเภทหนึ่งที่น่าสนใจคือสื่อกลาง โดยจะมีเครื่องหมายเป็น Artifact
อินสแตนซ์ที่เป็นเอาต์พุตของ MiddlemanAction
ซึ่งใช้เพื่อดำเนินการกับบางสิ่งในลักษณะพิเศษ ดังนี้
- สื่อกลางการรวบรวมข้อมูลใช้เพื่อจัดกลุ่มรายการต่างๆ เข้าด้วยกัน การดำเนินการจำนวนมากใช้ชุดอินพุตขนาดใหญ่ชุดเดียวกัน เราจึงไม่มีขอบความเกี่ยวข้อง N*M แต่มีเพียง N+M (มีการแทนที่ด้วยชุดที่ฝังอยู่)
- การกำหนดเวลาตัวกลางของความเกี่ยวข้องช่วยให้มั่นใจได้ว่าการดำเนินการหนึ่งจะทำงานก่อนการดำเนินการอื่น
โดยส่วนใหญ่จะใช้สำหรับการตรวจสอบโค้ด แต่จะใช้สำหรับการคอมไพล์ C++ ได้ด้วย (ดูคำอธิบายที่
CcCompilationContext.createMiddleman()
) - คนกลางของ Runfile ใช้เพื่อให้แน่ใจว่ามีโครงสร้าง Runfile เพื่อที่จะได้ไม่ต้องพึ่งไฟล์ Manifest ของเอาต์พุตและอาร์ติแฟกต์ทั้งหมดที่โครงสร้าง Runfiles อ้างอิง
การดำเนินการคือคําสั่งที่ต้องเรียกใช้ สภาพแวดล้อมที่จําเป็น และชุดเอาต์พุตที่สร้างขึ้น องค์ประกอบหลักของคำอธิบายการดำเนินการมีดังนี้
- บรรทัดคำสั่งที่ต้องเรียกใช้
- อาร์ติแฟกต์อินพุตที่จําเป็น
- ตัวแปรสภาพแวดล้อมที่ต้องตั้งค่า
- คําอธิบายประกอบที่อธิบายถึงสภาพแวดล้อม (เช่น แพลตฟอร์ม) ที่จําเป็นต้องใช้งาน \
นอกจากนี้ยังมีกรณีพิเศษอื่นๆ อีก 2-3 กรณี เช่น การเขียนไฟล์ที่ Bazel รู้จักเนื้อหา เป็นคลาสย่อยของ AbstractAction
การดำเนินการส่วนใหญ่คือ SpawnAction
หรือ StarlarkAction
(เหมือนกัน ไม่ควรแยกเป็นคลาสต่างๆ) แม้ว่า Java และ C++ จะมีประเภทการดำเนินการเป็นของตัวเอง (JavaCompileAction
, CppCompileAction
และ CppLinkAction
)
ท้ายที่สุดแล้ว เราต้องการย้ายทุกอย่างไปที่ SpawnAction
เนื่องจาก JavaCompileAction
นั้นใกล้เคียงกันมาก แต่ C++ เป็นกรณีพิเศษเล็กน้อยเนื่องจากมีการแยกวิเคราะห์ไฟล์ .d และรวมการสแกน
กราฟการดำเนินการส่วนใหญ่จะ "ฝัง" อยู่ในกราฟ Skyframe โดยแนวคิดคือการดำเนินการของการดำเนินการจะแสดงเป็นคําเรียกใช้ ActionExecutionFunction
การแมปจากขอบทรัพยากร Dependency ของกราฟการดำเนินการไปยังขอบทรัพยากร Dependency ของ Skyframe อธิบายไว้ใน ActionExecutionFunction.getInputDeps()
และ Artifact.key()
และมีการเพิ่มประสิทธิภาพบางอย่างเพื่อรักษาจำนวนขอบ Skyframe ให้ต่ำ
- อาร์ติแฟกต์ที่ดึงข้อมูลมาจะไม่มี
SkyValue
เป็นของตัวเอง แต่จะใช้Artifact.getGeneratingActionKey()
เพื่อค้นหาคีย์สําหรับการดําเนินการที่สร้างรายการนั้นแทน - ชุดที่ซ้อนกันจะมีคีย์ Skyframe ของตัวเอง
การดำเนินการที่แชร์
การดำเนินการบางอย่างสร้างขึ้นโดยเป้าหมายที่กำหนดค่าไว้หลายรายการ กฎ Starlark จะจํากัดมากกว่าเนื่องจากได้รับอนุญาตให้วางการดำเนินการที่ดึงข้อมูลไว้ในไดเรกทอรีที่กําหนดโดยการกำหนดค่าและแพ็กเกจเท่านั้น (แต่ถึงอย่างนั้น กฎในแพ็กเกจเดียวกันก็อาจขัดแย้งกันได้) แต่กฎที่ติดตั้งใช้งานใน Java จะวางอาร์ติแฟกต์ที่ดึงข้อมูลไว้ที่ใดก็ได้
การดำเนินการนี้ถือว่าไม่ถูกต้อง แต่การกำจัดการดำเนินการนี้ออกนั้นทำได้ยากมากเนื่องจากช่วยประหยัดเวลาในการดำเนินการได้อย่างมาก เช่น เมื่อต้องประมวลผลไฟล์ต้นทางด้วยวิธีใดวิธีหนึ่งและไฟล์ดังกล่าวมีการอ้างอิงโดยกฎหลายข้อ (handwave-handwave) ซึ่งจะกินพื้นที่ RAM บางส่วน เนื่องจากต้องจัดเก็บอินสแตนซ์ของการดำเนินการที่แชร์แต่ละรายการไว้ในหน่วยความจำแยกกัน
หากการดำเนินการ 2 รายการสร้างไฟล์เอาต์พุตเดียวกัน การดำเนินการดังกล่าวต้องเหมือนกันทุกประการ กล่าวคือ มีอินพุตเดียวกัน เอาต์พุตเดียวกัน และเรียกใช้บรรทัดคำสั่งเดียวกัน ความสัมพันธ์ที่เทียบเท่านี้ใช้ใน Actions.canBeShared()
และได้รับการยืนยันระหว่างระยะการวิเคราะห์และระยะดำเนินการโดยดูที่การดำเนินการแต่ละรายการ
การดำเนินการนี้มีการติดตั้งใช้งานใน SkyframeActionExecutor.findAndStoreArtifactConflicts()
และเป็นหนึ่งในไม่กี่แห่งใน Bazel ที่จำเป็นต้องมีมุมมอง "ทั่วโลก" ของบิลด์
ระยะการดําเนินการ
ขั้นตอนนี้เป็นเวลาที่ Bazel เริ่มเรียกใช้การดำเนินการสร้างจริง เช่น คำสั่งที่ผลิตเอาต์พุต
สิ่งแรกที่ Bazel ทำหลังจากช่วงการวิเคราะห์คือการกำหนดสิ่งที่จำเป็นต้องสร้างอาร์ติแฟกต์ ตรรกะสำหรับการดำเนินการนี้ได้รับการเข้ารหัสใน TopLevelArtifactHelper
โดยคร่าวๆ ก็คือ filesToBuild
ของเป้าหมายที่กําหนดค่าไว้ในบรรทัดคําสั่งและเนื้อหาของกลุ่มเอาต์พุตพิเศษเพื่อวัตถุประสงค์ที่ชัดเจนในการระบุว่า "หากเป้าหมายนี้อยู่ในบรรทัดคําสั่ง ให้สร้างอาร์ติแฟกต์เหล่านี้"
ขั้นตอนถัดไปคือการสร้างรูทการดำเนินการ เนื่องจาก Bazel มีตัวเลือกในการอ่านแพ็กเกจแหล่งที่มาจากตำแหน่งต่างๆ ในระบบไฟล์ (--package_path
) จึงต้องมอบการดำเนินการที่ดำเนินการในเครื่องพร้อมทรีแหล่งที่มาแบบเต็ม การดำเนินการนี้จัดการโดยคลาส SymlinkForest
และทำงานโดยการบันทึกเป้าหมายทั้งหมดที่ใช้ในระยะการวิเคราะห์ และสร้างต้นไม้ไดเรกทอรีเดียวซึ่งลิงก์สัญลักษณ์แพ็กเกจทั้งหมดที่มีเป้าหมายที่ใช้จากตำแหน่งจริง อีกทางเลือกหนึ่งคือการส่งผ่านเส้นทางที่ถูกต้องไปยังคำสั่ง (โดยนำ --package_path
มาพิจารณา)
ซึ่งเป็นสิ่งที่ไม่พึงประสงค์เนื่องจากเหตุผลต่อไปนี้
- โดยเปลี่ยนบรรทัดคำสั่งการดำเนินการเมื่อมีการย้ายแพ็กเกจจากรายการเส้นทางแพ็กเกจไปยังรายการอื่น (เคยเกิดขึ้นบ่อย)
- ซึ่งจะส่งผลให้บรรทัดคำสั่งแตกต่างกันหากการดําเนินการทํางานจากระยะไกลหรือทํางานในเครื่อง
- จำเป็นต้องเปลี่ยนรูปแบบบรรทัดคำสั่งเฉพาะในเครื่องมือที่ใช้งานอยู่ (พิจารณาความแตกต่างระหว่างเส้นทาง เช่น Java classpaths และ C++ รวม)
- การเปลี่ยนบรรทัดคำสั่งของการดำเนินการจะทำให้รายการแคชการดำเนินการนั้นใช้งานไม่ได้
--package_path
กำลังเลิกใช้งานอย่างช้าๆ
จากนั้น Bazel เริ่มข้ามผ่านกราฟการกระทำ (กราฟสองพาร์ท กราฟทิศทางที่ประกอบด้วยการกระทำ และอาร์ติแฟกต์อินพุตและเอาต์พุต) และการเรียกใช้การกระทำ
การดำเนินการแต่ละรายการจะแสดงโดยอินสแตนซ์ของSkyValue
คลาส ActionExecutionValue
เนื่องจากการเรียกใช้การดำเนินการมีค่าใช้จ่ายสูง เรามีการแคช 2-3 เลเยอร์ที่อาจถูกโจมตีหลัง Skyframe ดังนี้
ActionExecutionFunction.stateMap
มีข้อมูลที่ทำให้การรีสตาร์ท Skyframe ของActionExecutionFunction
ราคาถูก- แคชการดำเนินการภายในมีข้อมูลเกี่ยวกับสถานะของระบบไฟล์
- ระบบการดําเนินการจากระยะไกลมักจะมีแคชของตนเองด้วย
แคชการกระทําเกี่ยวกับสถานที่
แคชนี้เป็นอีกเลเยอร์หนึ่งที่อยู่หลัง Skyframe แม้ว่าจะมีการดำเนินการซ้ำใน Skyframe แล้ว แคชก็ยังคงสามารถถูกโจมตีในแคชการกระทำในเครื่องได้ ซึ่งแสดงสถานะของระบบไฟล์ในเครื่องและได้รับการจัดรูปแบบเป็นอนุกรมไปยังดิสก์ ซึ่งหมายความว่าเมื่อเริ่มต้นเซิร์ฟเวอร์ Bazel ใหม่ คุณจะได้รับ Hit ของแคชการดำเนินการในเครื่องแม้ว่ากราฟ Skyframe จะว่างเปล่าก็ตาม
ระบบจะตรวจสอบแคชนี้เพื่อหา Hit โดยใช้เมธอด ActionCacheChecker.getTokenIfNeedToExecute()
ซึ่งตรงข้ามกับชื่อที่เรียก เพราะเป็นแผนที่จากเส้นทางของอาร์ติแฟกต์ที่ดึงข้อมูลมายังการดำเนินการที่ทำให้เกิดอาร์ติแฟกต์ การดําเนินการมีคำอธิบายดังนี้
- ชุดไฟล์อินพุตและเอาต์พุต รวมถึงการตรวจสอบผลรวม
- "คีย์การดำเนินการ" ซึ่งมักจะเป็นบรรทัดคำสั่งที่ดำเนินการ แต่โดยทั่วไปจะแสดงถึงทุกอย่างที่ไม่ได้บันทึกโดยการตรวจสอบผลรวมของไฟล์อินพุต (เช่น สำหรับ
FileWriteAction
จะเป็นการตรวจสอบผลรวมของข้อมูลที่เขียน)
นอกจากนี้ยังมี "แคชการดำเนินการจากบนลงล่าง" เวอร์ชันทดลองขั้นสูงที่อยู่ระหว่างการพัฒนา ซึ่งใช้แฮชแบบทรานซิทีฟเพื่อหลีกเลี่ยงการไปที่แคชหลายครั้ง
การค้นพบอินพุตและการกรองอินพุต
การดำเนินการบางอย่างมีความซับซ้อนมากกว่าการมีชุดอินพุต การเปลี่ยนแปลงชุดอินพุตของการดําเนินการมีอยู่ 2 รูปแบบ ดังนี้
- การดำเนินการอาจค้นพบอินพุตใหม่ก่อนดำเนินการ หรือตัดสินใจว่าอินพุตบางอย่างไม่จำเป็น ตัวอย่างมาตรฐานคือ C++ ซึ่งควรที่จะคาดเดาอย่างมีข้อมูลเกี่ยวกับไฟล์ส่วนหัวที่ไฟล์ C++ ใช้จาก Closure แบบทรานซิทีฟ เพื่อที่เราจะได้ไม่ต้องส่งไฟล์ทุกไฟล์ไปยังผู้ดำเนินการระยะไกล ดังนั้นเราจึงมีตัวเลือกที่จะไม่ลงทะเบียนไฟล์ส่วนหัวทุกไฟล์เป็น "อินพุต" แต่สแกนไฟล์ต้นฉบับเพื่อหาส่วนหัวที่รวมอยู่แบบทรานซิทีฟ และทําเครื่องหมายไฟล์ส่วนหัวเหล่านั้นเป็นอินพุตที่กล่าวถึงในคำสั่ง
#include
เท่านั้น (เราประเมินค่าสูงเกินจริงเพื่อที่จะไม่ต้องใช้โปรแกรมเตรียม C แบบสมบูรณ์) ปัจจุบันตัวเลือกนี้ได้รับการตั้งค่าเป็น "เท็จ" ใน Bazel และใช้ใน Google เท่านั้น - การดําเนินการอาจพบว่าไม่ได้ใช้ไฟล์บางไฟล์ในระหว่างการดําเนินการ ใน C++ สิ่งนี้เรียกว่า "ไฟล์ .d": คอมไพเลอร์จะบอกไฟล์ส่วนหัวที่ใช้หลังจากสร้างไปแล้ว และ Bazel ใช้ประโยชน์จากข้อเท็จจริงนี้เพื่อหลีกเลี่ยงความอับอายที่การปรับปรุงแย่กว่า Make ซึ่งจะประมาณได้ดีกว่าเครื่องมือสแกนรวม เนื่องจากใช้คอมไพเลอร์
การดำเนินการเหล่านี้จะใช้เมธอดในการดำเนินการ
Action.discoverInputs()
เรียก ซึ่งควรแสดงผลชุดอาร์ติแฟกต์ที่ฝังอยู่ซึ่งระบบพิจารณาว่าจําเป็น รายการเหล่านี้ต้องเป็นอาร์ติแฟกต์ต้นทางเพื่อไม่ให้มีขอบ Dependency ในกราฟการดำเนินการที่ไม่มีรายการที่เทียบเท่าในกราฟเป้าหมายที่กําหนดค่าไว้- การดำเนินการจะดำเนินการโดยเรียกใช้
Action.execute()
- เมื่อสิ้นสุด
Action.execute()
การดำเนินการจะเรียกAction.updateInputs()
เพื่อบอก Bazel ว่าไม่จำเป็นต้องใช้อินพุตทั้งหมด ซึ่งอาจส่งผลให้มีการบิลด์ที่เพิ่มขึ้นไม่ถูกต้องหากมีการรายงานว่าอินพุตที่ใช้แล้วไม่ได้ใช้งาน
เมื่อแคชการดำเนินการแสดงผลลัพธ์ในอินสแตนซ์การดำเนินการใหม่ (เช่น สร้างขึ้นหลังจากรีสตาร์ทเซิร์ฟเวอร์) Bazel จะเรียกใช้ updateInputs()
เองเพื่อให้ชุดอินพุตแสดงผลลัพธ์ของการค้นพบและการตัดอินพุตที่ดำเนินการไปก่อนหน้านี้
การดำเนินการของ Starlark จะใช้ประโยชน์จากเครื่องมือเพื่อประกาศอินพุตบางรายการว่าไม่ได้ใช้งานโดยใช้อาร์กิวเมนต์ unused_inputs_list=
ของ ctx.actions.run()
วิธีต่างๆ ในการดำเนินการ: Strategies/ActionContexts
การดำเนินการบางอย่างสามารถเรียกใช้ได้หลายวิธี เช่น บรรทัดคำสั่งสามารถดำเนินการได้แบบในเครื่อง ดำเนินการในเครื่องแต่ในแซนด์บ็อกซ์ประเภทต่างๆ หรือดำเนินการจากระยะไกล แนวคิดที่รวมสิ่งนี้เรียกว่า ActionContext
(หรือ Strategy
เนื่องจากเรา
ประสบความสำเร็จได้เพียงครึ่งทางด้วยการเปลี่ยนชื่อ...)
วงจรชีวิตของบริบทการกระทํามีดังนี้
- เมื่อเริ่มระยะการดําเนินการ ระบบจะถามอินสแตนซ์
BlazeModule
ว่ามีบริบทการดําเนินการใดบ้าง ซึ่งเกิดขึ้นในคอนสตรัคเตอร์ของExecutionTool
ประเภทบริบทการดำเนินการจะระบุโดยอินสแตนซ์ JavaClass
ที่อ้างถึงอินเทอร์เฟซย่อยของActionContext
และอินเทอร์เฟซที่บริบทการดำเนินการต้องใช้ - ระบบจะเลือกบริบทการดำเนินการที่เหมาะสมจากรายการที่พร้อมใช้งาน และส่งต่อไปยัง
ActionExecutionContext
และBlazeExecutor
- การดำเนินการจะขอบริบทโดยใช้
ActionExecutionContext.getContext()
และBlazeExecutor.getStrategy()
(จริงๆ แล้วควรมีวิธีเดียวเท่านั้น)
กลยุทธ์มีอิสระในการเรียกกลยุทธ์อื่นมาทำงานแทน ตัวอย่างเช่น ในกลยุทธ์แบบไดนามิกที่เริ่มต้นการดำเนินการทั้งภายในและจากระยะไกล จากนั้นจึงจะใช้ขึ้นอยู่กับว่าเหตุการณ์ใดเสร็จสิ้นก่อน
กลยุทธ์ที่น่าสังเกตอย่างหนึ่งคือกลยุทธ์ที่ใช้กระบวนการทำงานแบบถาวร (WorkerSpawnStrategy
) แนวคิดคือเครื่องมือบางรายการมีเวลาเริ่มต้นนาน จึงควรนํากลับมาใช้ใหม่ระหว่างการดําเนินการแทนที่จะเริ่มต้นใหม่สําหรับการดําเนินการแต่ละรายการ (นี่อาจเป็นปัญหาด้านความถูกต้องได้ เนื่องจาก Bazel อาศัยความน่าเชื่อถือของกระบวนการทํางานที่จะไม่มีการเก็บสถานะที่สังเกตได้ระหว่างคําขอแต่ละรายการ)
หากเครื่องมือมีการเปลี่ยนแปลง จะต้องรีสตาร์ทกระบวนการทำงาน การกำหนดผู้ปฏิบัติงานซ้ำว่าจะใช้ซ้ำได้หรือไม่นั้นโดยการคำนวณผลรวมตรวจสอบของเครื่องมือที่ใช้โดยใช้ WorkerFilesHash
ค่านี้ขึ้นอยู่กับการทราบว่าอินพุตใดของการดำเนินการเป็นส่วนหนึ่งของเครื่องมือและอินพุตใดแสดงถึงอินพุต ซึ่งจะกำหนดโดยผู้สร้างการดำเนินการ: Spawn.getToolFiles()
และการเรียกใช้ไฟล์ของ Spawn
จะนับเป็นส่วนหนึ่งของเครื่องมือ
ข้อมูลเพิ่มเติมเกี่ยวกับกลยุทธ์ (หรือบริบทการดำเนินการ)
- ดูข้อมูลเกี่ยวกับกลยุทธ์ต่างๆ ในการเรียกใช้การดำเนินการได้ที่นี่
- ข้อมูลเกี่ยวกับกลยุทธ์แบบไดนามิก ซึ่งเราเรียกใช้การดำเนินการทั้งจากเครื่องและจากระยะไกลเพื่อดูว่าการดำเนินการใดเสร็จสิ้นก่อนมีที่นี่
- ดูข้อมูลเกี่ยวกับความซับซ้อนของการดำเนินการในเครื่องได้ที่นี่
เครื่องมือจัดการทรัพยากรในเครื่อง
Bazel สามารถเรียกใช้การดำเนินการหลายรายการพร้อมกัน จํานวนการดําเนินการในเครื่องที่ควรทํางานพร้อมกันจะแตกต่างกันไปในแต่ละการดําเนินการ ยิ่งการดําเนินการต้องใช้ทรัพยากรมากเท่าใด ก็ควรมีอินสแตนซ์ที่ทํางานพร้อมกันน้อยลงเพื่อไม่ให้เครื่องทำงานหนักเกินไป
การดำเนินการนี้ใช้ในคลาส ResourceManager
: แต่ละการดำเนินการต้องมีการกำกับเนื้อหาโดยประมาณของทรัพยากรในเครื่องที่จําเป็นในรูปแบบอินสแตนซ์ ResourceSet
(CPU และ RAM) จากนั้นเมื่อบริบทการดำเนินการทําบางอย่างที่ต้องใช้ทรัพยากรในเครื่อง ระบบจะเรียกใช้ ResourceManager.acquireResources()
และระบบจะบล็อกจนกว่าจะมีทรัพยากรที่จําเป็น
ดูคำอธิบายการจัดการทรัพยากรในเครื่องโดยละเอียดได้ที่นี่
โครงสร้างของไดเรกทอรีเอาต์พุต
การดำเนินการแต่ละรายการต้องมีตำแหน่งแยกต่างหากในไดเรกทอรีเอาต์พุตที่จะวางเอาต์พุต โดยปกติแล้วตำแหน่งของอาร์ติแฟกต์ที่ดึงข้อมูลจะมีดังนี้
$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>
ระบบกำหนดชื่อไดเรกทอรีที่เชื่อมโยงกับการกำหนดค่าหนึ่งๆ อย่างไร มีพร็อพเพอร์ตี้ที่ต้องการที่ขัดแย้งกัน 2 รายการ ดังนี้
- หากการกําหนดค่า 2 รายการเกิดขึ้นในบิลด์เดียวกัน ก็ควรมีไดเรกทอรีต่างกันเพื่อให้ทั้ง 2 รายการมีการดำเนินการเดียวกันในเวอร์ชันของตัวเอง มิเช่นนั้นหากการกําหนดค่า 2 รายการขัดแย้งกัน เช่น บรรทัดคําสั่งของการดำเนินการที่สร้างไฟล์เอาต์พุตเดียวกัน Bazel จะไม่ทราบว่าควรเลือกการดำเนินการใด ("การดำเนินการขัดแย้งกัน")
- หากการกำหนดค่า 2 รายการกล่าวถึงสิ่งเดียวกันอย่าง "คล้ายคลึง" การกำหนดค่าดังกล่าวควรมีชื่อเดียวกันเพื่อให้การดำเนินการที่ดำเนินการในรายการหนึ่งสามารถใช้ซ้ำสำหรับอีกรายการหนึ่งได้ในกรณีที่บรรทัดคำสั่งตรงกัน เช่น การเปลี่ยนแปลงตัวเลือกบรรทัดคำสั่งในคอมไพเลอร์ Java ไม่ควรทำให้การดำเนินการคอมไพล์ C++ ถูกเรียกใช้ซ้ำ
จนถึงตอนนี้ เรายังไม่พบวิธีแก้ปัญหานี้อย่างเป็นระบบ ซึ่งคล้ายกับปัญหาการลดการกำหนดค่า ดูการพูดคุยเรื่องตัวเลือกต่างๆ เพิ่มเติมได้ที่นี่ ส่วนที่เป็นปัญหาหลักๆ คือกฎของ Starlark (ซึ่งผู้เขียนมักไม่ค่อยคุ้นเคยกับ Bazel) และแง่มุมต่างๆ ซึ่งช่วยเพิ่มมิติของสิ่งที่สามารถสร้างไฟล์เอาต์พุต "เดียวกัน" ได้
แนวทางปัจจุบันคือส่วนของเส้นทางสำหรับการกําหนดค่าคือ <CPU>-<compilation mode>
ที่มีการเพิ่มส่วนต่อท้ายต่างๆ เพื่อให้การเปลี่ยนการกำหนดค่าที่ติดตั้งใช้งานใน Java จะไม่ทําให้การดำเนินการขัดแย้งกัน นอกจากนี้ยังมีการเพิ่ม Checksum ของชุดการเปลี่ยนการกำหนดค่า Starlark เพื่อไม่ให้ผู้ใช้สร้างความขัดแย้งของการดำเนินการ เรียกได้ว่าสมบูรณ์แบบจริงๆ ซึ่งจะนำไปใช้ใน OutputDirectories.buildMnemonic()
และใช้ส่วนย่อยการกำหนดค่าแต่ละรายการที่เพิ่มส่วนของตัวเองไปยังชื่อของไดเรกทอรีเอาต์พุต
การทดสอบ
Bazel รองรับการทดสอบที่ทำงานอยู่อย่างหลากหลาย โดยรองรับการดำเนินการต่อไปนี้
- เรียกใช้การทดสอบจากระยะไกล (หากมีแบ็กเอนด์การดำเนินการระยะไกล)
- การทดสอบหลายครั้งพร้อมกัน (สำหรับการลดจำนวนการทดสอบหรือรวบรวมข้อมูลเกี่ยวกับเวลา)
- การแยกการทดสอบ (การแยกเฟรมทดสอบในการทดสอบเดียวกันไปยังหลายกระบวนการเพื่อเพิ่มความเร็ว)
- ทำการทดสอบที่ไม่น่าเชื่อถืออีกครั้ง
- จัดกลุ่มการทดสอบเป็นชุดทดสอบ
การทดสอบเป็นเป้าหมายที่กำหนดค่าไว้ตามปกติซึ่งมี TestProvider ซึ่งจะอธิบายวิธีดำเนินการทดสอบ ดังนี้
- อาร์ติแฟกต์ที่การบิลด์ส่งผลให้มีการเรียกใช้การทดสอบ นี่คือไฟล์ "สถานะแคช" ที่มีข้อความ
TestResultData
แบบอนุกรม - จำนวนครั้งที่ทำการทดสอบ
- จํานวนชาร์ดที่ควรแบ่งการทดสอบ
- พารามิเตอร์บางอย่างเกี่ยวกับวิธีเรียกใช้การทดสอบ (เช่น การหมดเวลาทดสอบ)
การกำหนดการทดสอบที่จะเรียกใช้
การกำหนดการทดสอบที่จะทํางานเป็นกระบวนการที่ซับซ้อน
ขั้นแรก ในระหว่างการแยกวิเคราะห์รูปแบบเป้าหมาย ระบบจะขยายชุดทดสอบแบบซ้ำ ใช้การขยายใน TestsForTargetPatternFunction
แล้ว สิ่งที่น่าประหลาดใจคือหากชุดทดสอบไม่ได้ประกาศการทดสอบใดๆ เลย จะเป็นการอ้างอิงการทดสอบทั้งหมดในแพ็กเกจ ซึ่งติดตั้งใช้งานใน Package.beforeBuild()
โดยเพิ่มแอตทริบิวต์โดยนัยชื่อ $implicit_tests
ลงในกฎชุดทดสอบ
จากนั้นระบบจะกรองการทดสอบตามขนาด แท็ก การหมดเวลา และภาษาตามตัวเลือกบรรทัดคำสั่ง การดำเนินการนี้ใช้ใน TestFilter
และเรียกใช้จาก TargetPatternPhaseFunction.determineTests()
ระหว่างการแยกวิเคราะห์เป้าหมาย และใส่ผลลัพธ์ไว้ใน TargetPatternPhaseValue.getTestsToRunLabels()
สาเหตุที่แอตทริบิวต์ของกฎกรองกำหนดค่าไม่ได้ก็คือเกิดขึ้นก่อนช่วงการวิเคราะห์ การกำหนดค่าจึงไม่พร้อมใช้งาน
จากนั้นระบบจะประมวลผลเพิ่มเติมใน BuildView.createResult()
: ระบบจะกรองเป้าหมายที่การวิเคราะห์ไม่สําเร็จออก และแยกการทดสอบออกเป็นการทดสอบแบบไม่รวมและแบบรวม จากนั้นระบบจะใส่ข้อมูลลงใน AnalysisResult
ซึ่งเป็นวิธีที่ ExecutionTool
รู้ว่าจะเรียกใช้การทดสอบใด
tests()
เป็นตัวดำเนินการการค้นหา (ติดตั้งใช้งานใน TestsFunction
) เพื่อระบุการทดสอบที่จะเรียกใช้เมื่อระบุเป้าหมายที่เฉพาะเจาะจงในบรรทัดคำสั่ง ซึ่งจะช่วยให้กระบวนการที่ซับซ้อนนี้มีความโปร่งใสมากขึ้น เราต้องขออภัยที่ต้องใช้การติดตั้งใช้งานอีกครั้ง จึงอาจแตกต่างจากข้างต้นในหลายๆ ด้าน
การทดสอบที่ทำงานอยู่
วิธีเรียกใช้การทดสอบคือขออาร์ติแฟกต์สถานะแคช ซึ่งจะทําให้ TestRunnerAction
ทำงาน ซึ่งสุดท้ายแล้วก็จะเรียก TestActionContext
ที่เลือกโดยตัวเลือกบรรทัดคำสั่ง --test_strategy
ซึ่งจะทําการทดสอบในลักษณะที่ขอ
การทดสอบจะดำเนินการตามโปรโตคอลที่ละเอียดซึ่งใช้ตัวแปรสภาพแวดล้อมเพื่อบอกให้การทดสอบทราบถึงสิ่งที่คาดหวังจากตัวแปรนั้น ดูคำอธิบายโดยละเอียดเกี่ยวกับสิ่งที่ Bazel คาดหวังจากการทดสอบและสิ่งที่การทดสอบคาดหวังจาก Bazel ได้ที่นี่ ในกรณีที่ง่ายที่สุด รหัสออก 0 หมายถึงความสําเร็จ ส่วนรหัสอื่นๆ หมายถึงความล้มเหลว
นอกจากไฟล์สถานะแคชแล้ว กระบวนการทดสอบแต่ละรายการจะสร้างไฟล์อื่นๆ อีกหลายไฟล์ โดยจะใส่ไว้ใน "ไดเรกทอรีบันทึกการทดสอบ" ซึ่งเป็นไดเรกทอรีย่อยที่ชื่อ testlogs
ของไดเรกทอรีเอาต์พุตของการกำหนดค่าเป้าหมาย ดังนี้
test.xml
ซึ่งเป็นไฟล์ XML สไตล์ JUnit ที่แสดงรายละเอียดของกรณีทดสอบแต่ละรายการในแชร์ทดสอบtest.log
เอาต์พุตคอนโซลของการทดสอบ โดยไม่มีการแยก stdout และ stderrtest.outputs
"ไดเรกทอรีเอาต์พุตที่ไม่ได้ประกาศ" ซึ่งใช้โดยทดสอบที่ต้องการแสดงผลไฟล์นอกเหนือจากที่พิมพ์ไปยังเทอร์มินัล
การดำเนินการทดสอบมี 2 อย่างที่จะเกิดขึ้นไม่ได้ในระหว่างการสร้างเป้าหมายปกติ ได้แก่ การดำเนินการทดสอบแบบพิเศษและสตรีมมิงเอาต์พุต
การทดสอบบางรายการต้องดำเนินการในโหมดพิเศษ เช่น ดำเนินการควบคู่กับการทดสอบอื่นๆ ซึ่งทำได้โดยการเพิ่ม tags=["exclusive"]
ลงในกฎทดสอบหรือเรียกใช้การทดสอบด้วย --test_strategy=exclusive
การทดสอบพิเศษแต่ละรายการจะเรียกใช้โดยการเรียกใช้ Skyframe แยกต่างหากที่ขอการดำเนินการทดสอบหลังจากบิลด์ "หลัก" การดำเนินการนี้ใช้ใน
SkyframeExecutor.runExclusiveTest()
ซึ่งแตกต่างจากการดำเนินการปกติที่ระบบจะแสดงผลลัพธ์ของเทอร์มินัลเมื่อการดำเนินการเสร็จสิ้น ผู้ใช้สามารถขอให้สตรีมเอาต์พุตของการทดสอบเพื่อให้ทราบความคืบหน้าของการทดสอบที่ทำงานเป็นเวลานาน ซึ่งระบุโดยตัวเลือกบรรทัดคำสั่ง --test_output=streamed
และบอกเป็นนัยถึงการดำเนินการทดสอบพิเศษเพื่อไม่ให้เอาต์พุตของการทดสอบต่างๆ กระจัดกระจาย
ซึ่งจะนำไปใช้ในคลาส StreamedTestOutput
ที่ได้รับการตั้งชื่ออย่างเหมาะสมและดำเนินการโดยการโพลการเปลี่ยนแปลงไฟล์ test.log
ของการทดสอบดังกล่าวและทิ้งไบต์ใหม่ไปยังเทอร์มินัลที่ Bazel ใช้กฎ
ผลลัพธ์ของการทดสอบที่ดำเนินการจะแสดงในบัสเหตุการณ์โดยสังเกตเหตุการณ์ต่างๆ (เช่น TestAttempt
, TestResult
หรือ TestingCompleteEvent
) ระบบจะส่งออกข้อมูลไปยัง Build Event Protocol และส่งไปยังคอนโซลโดย AggregatingTestListener
คอลเล็กชันความครอบคลุม
การทดสอบจะรายงานการครอบคลุมในรูปแบบ LCOV ในไฟล์
bazel-testlogs/$PACKAGE/$TARGET/coverage.dat
หากต้องการรวบรวมการครอบคลุม ระบบจะรวมการเรียกใช้การทดสอบแต่ละรายการไว้ในสคริปต์ที่ชื่อ
collect_coverage.sh
สคริปต์นี้จะตั้งค่าสภาพแวดล้อมการทดสอบเพื่อเปิดใช้การเก็บรวบรวมการครอบคลุม และกำหนดตำแหน่งที่รันไทม์การครอบคลุมจะเขียนไฟล์การครอบคลุม จากนั้นระบบจะทำการทดสอบ การทดสอบอาจเรียกใช้กระบวนการย่อยหลายรายการและประกอบด้วยส่วนต่างๆ ที่เขียนด้วยภาษาโปรแกรมหลายภาษา (ที่มีรันไทม์การเก็บรวบรวมการครอบคลุมแยกกัน) สคริปต์รัปเปอร์มีหน้าที่แปลงไฟล์ที่ได้เป็นรูปแบบ LCOV หากจําเป็น และผสานไฟล์เหล่านั้นเข้าด้วยกันเป็นไฟล์เดียว
การแทรกแซงของ collect_coverage.sh
ดำเนินการโดยกลยุทธ์การทดสอบและกำหนดให้ collect_coverage.sh
อยู่ในข้อมูลที่ป้อนของการทดสอบ รายการนี้มาพร้อมกับแอตทริบิวต์แบบไม่เจาะจงปลายทาง :coverage_support
ซึ่งได้รับการแก้ไขเป็นค่าของแฟล็กการกำหนดค่า --coverage_support
(ดู TestConfiguration.TestOptions.coverageSupport
)
บางภาษามีการวัดคุมแบบออฟไลน์ ซึ่งหมายความว่าจะมีการเพิ่มการวัดคุมการครอบคลุมในเวลาคอมไพล์ (เช่น C++) ส่วนบางภาษาจะทำการวัดคุมออนไลน์ ซึ่งหมายความว่าจะเพิ่มการวัดคุมการครอบคลุมขณะดำเนินการ
แนวคิดหลักอีกประการหนึ่งคือความครอบคลุมของเส้นฐาน นี่คือการครอบคลุมของไลบรารี ไฟล์ไบนารี หรือทดสอบว่าไม่มีโค้ดใดทำงานอยู่ ปัญหาที่เครื่องมือนี้ช่วยแก้ไขได้คือ หากต้องการคํานวณการครอบคลุมของการทดสอบสําหรับไบนารี การผสานการครอบคลุมของการทดสอบทั้งหมดนั้นไม่เพียงพอ เนื่องจากอาจมีโค้ดในไบนารีที่ไม่ได้ลิงก์กับการทดสอบใดๆ ดังนั้นสิ่งที่เราทําคือสร้างไฟล์การครอบคลุมสําหรับไบนารีทุกไฟล์ซึ่งมีเฉพาะไฟล์ที่เรารวบรวมการครอบคลุมโดยไม่มีบรรทัดที่มีการครอบคลุม ไฟล์พื้นฐานที่ครอบคลุมของเป้าหมายคือ
bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat
นอกจากนี้ ระบบจะสร้างไฟล์ดังกล่าวสำหรับไบนารีและไลบรารีนอกเหนือจากการทดสอบด้วยหากคุณส่งผ่าน Flag --nobuild_tests_only
ไปยัง Bazel
ขณะนี้ความครอบคลุมของข้อมูลพื้นฐานใช้งานไม่ได้
เราติดตามไฟล์ 2 กลุ่มสําหรับการเก็บรวบรวมการครอบคลุมสําหรับแต่ละกฎ ได้แก่ ชุดไฟล์ที่มีเครื่องมือวัดและชุดไฟล์ข้อมูลเมตาของเครื่องมือวัด
ชุดไฟล์ที่มีเครื่องมือวัดผลเป็นเพียงชุดไฟล์ที่มีเครื่องมือวัดผล สำหรับรันไทม์การครอบคลุมออนไลน์ สามารถใช้การตั้งค่านี้ขณะรันไทม์เพื่อเลือกไฟล์ที่จะใช้วัด และยังใช้เพื่อติดตั้งใช้งานความครอบคลุมพื้นฐานด้วย
ชุดไฟล์ข้อมูลเมตาของการวัดคุมคือชุดไฟล์พิเศษที่การทดสอบต้องใช้ในการสร้างไฟล์ LCOV ที่ Bazel ต้องการ ในทางปฏิบัติ ไฟล์เหล่านี้ประกอบด้วยไฟล์เฉพาะรันไทม์ เช่น gcc จะสร้างไฟล์ .gcno ในระหว่างการคอมไพล์ ซึ่งจะเพิ่มลงในชุดอินพุตของการดำเนินการทดสอบหากเปิดใช้โหมดการครอบคลุม
ระบบจะจัดเก็บข้อมูลว่ามีการรวบรวมการครอบคลุมหรือไม่ไว้ใน
BuildConfiguration
ซึ่งมีประโยชน์เนื่องจากเป็นวิธีที่ง่ายในการเปลี่ยนการทดสอบการดำเนินการและกราฟการดำเนินการโดยขึ้นอยู่กับบิตนี้ แต่ขณะเดียวกันก็หมายความว่าหากพลิกบิตนี้ จะต้องวิเคราะห์เป้าหมายทั้งหมดอีกครั้ง (บางภาษา เช่น C++ ต้องใช้ตัวเลือกคอมไพเลอร์ที่แตกต่างกันเพื่อแสดงผลโค้ดที่รวบรวมการครอบคลุมได้ ซึ่งช่วยบรรเทาปัญหานี้ได้บ้าง เนื่องจากจะต้องวิเคราะห์อีกครั้งอยู่ดี)
ไฟล์สนับสนุนการครอบคลุมจะขึ้นอยู่กับป้ายกำกับในการพึ่งพาโดยนัยเพื่อให้นโยบายการเรียกใช้ลบล้างไฟล์เหล่านั้นได้ ซึ่งช่วยให้ไฟล์เหล่านี้แตกต่างกันไปในแต่ละเวอร์ชันของ Bazel โดยหลักการแล้ว เราควรนำความแตกต่างเหล่านี้ออกและกำหนดมาตรฐานให้เหลือเพียงรูปแบบเดียว
นอกจากนี้ เรายังสร้าง "รายงานการครอบคลุม" ซึ่งผสานการครอบคลุมที่รวบรวมสําหรับการทดสอบทุกรายการในการเรียกใช้ Bazel การดำเนินการนี้จัดการโดย CoverageReportActionFactory
และเรียกใช้จาก BuildView.createResult()
และเข้าถึงเครื่องมือที่จำเป็นโดยดูที่แอตทริบิวต์ :coverage_report_generator
ของการทดสอบแรกที่ดำเนินการ
เครื่องมือการค้นหา
Bazel ใช้ภาษาง่ายๆ ในการถามเรื่องต่างๆ เกี่ยวกับกราฟต่างๆ เรามีคำค้นหาประเภทต่อไปนี้
bazel query
ใช้เพื่อตรวจสอบกราฟเป้าหมายbazel cquery
จะใช้ในการตรวจสอบกราฟเป้าหมายที่กำหนดค่าไว้bazel aquery
ใช้เพื่อตรวจสอบกราฟการดำเนินการ
แต่ละรายการเหล่านี้ติดตั้งใช้งานโดยการแยกคลาสย่อยของ AbstractBlazeQueryEnvironment
คุณสามารถเพิ่มฟังก์ชันการค้นหาเพิ่มเติมได้โดยการสร้างคลาสย่อยของ QueryFunction
ระบบจะส่ง query2.engine.Callback
ไปยัง QueryFunction
เพื่อเรียกใช้ผลลัพธ์ที่ต้องการแสดงแทนการเก็บรวบรวมผลลัพธ์ไปยังโครงสร้างข้อมูลบางอย่าง เพื่อให้สตรีมผลการค้นหาได้
ผลลัพธ์ของการค้นหาสามารถแสดงผลได้หลายวิธี เช่น ป้ายกํากับ คลาสป้ายกํากับและกฎ XML, protobuf และอื่นๆ ซึ่งติดตั้งใช้งานเป็นคลาสย่อยของ
OutputFormatter
ข้อกำหนดเล็กๆ น้อยๆ ของรูปแบบเอาต์พุตการค้นหาบางรูปแบบ (proto) คือ Bazel จำเป็นต้องแสดงข้อมูลทั้งหมดที่การโหลดแพ็กเกจให้เพื่อให้ผู้ใช้สามารถเปรียบเทียบเอาต์พุตและพิจารณาว่าเป้าหมายหนึ่งๆ มีการเปลี่ยนแปลงหรือไม่ ด้วยเหตุนี้ ค่าแอตทริบิวต์จึงต้องจัดเก็บเป็นอนุกรมได้ จึงมีแอตทริบิวต์ประเภทต่างๆ เพียงไม่กี่ประเภทที่ไม่มีแอตทริบิวต์ที่มีค่า Starlark ซับซ้อน วิธีแก้ปัญหาทั่วไปคือการใช้ป้ายกํากับและแนบข้อมูลที่ซับซ้อนไปกับกฎที่มีป้ายกํากับนั้น วิธีแก้ปัญหานี้ไม่ค่อยน่าพอใจนัก และเรายินดีอย่างยิ่งที่จะยกเลิกข้อกำหนดนี้
ระบบโมดูล
คุณขยาย Bazel ได้โดยการเพิ่มโมดูล แต่ละโมดูลต้องเป็นคลาสย่อยของ BlazeModule
(ชื่อนี้มาจากประวัติของ Bazel เมื่อก่อนเรียกว่า Blaze) และรับข้อมูลเกี่ยวกับเหตุการณ์ต่างๆ ระหว่างการเรียกใช้คำสั่ง
โดยส่วนใหญ่จะใช้เพื่อใช้ฟังก์ชันต่างๆ ที่ "ไม่ใช่หลัก" ซึ่งมีเพียง Bazel บางเวอร์ชัน (เช่น เวอร์ชันที่เราใช้ที่ Google) ที่ต้องการ
- อินเทอร์เฟซกับระบบการดําเนินการระยะไกล
- คำสั่งใหม่
ชุดจุดขยายที่ BlazeModule
เสนอค่อนข้างไม่เป็นระเบียบ อย่าใช้เป็นตัวอย่างของหลักการออกแบบที่ดี
บัสเหตุการณ์
วิธีที่หลักที่ BlazeModules สื่อสารกับส่วนที่เหลือของ Bazel คือผ่านบัสเหตุการณ์ (EventBus
) ระบบจะสร้างอินสแตนซ์ใหม่สําหรับบิลด์แต่ละรายการ ส่วนต่างๆ ของ Bazel สามารถโพสต์เหตุการณ์ไปยังบัสดังกล่าว และโมดูลสามารถลงทะเบียนผู้ฟังสําหรับเหตุการณ์ที่สนใจ ตัวอย่างเช่น สิ่งต่อไปนี้จะแสดงเป็นเหตุการณ์
- กำหนดรายการเป้าหมายการสร้างที่จะสร้างแล้ว
(
TargetParsingCompleteEvent
) - กําหนดการกําหนดค่าระดับบนสุดแล้ว
(
BuildConfigurationEvent
) - สร้างเป้าหมายสำเร็จหรือไม่ (
TargetCompleteEvent
) - มีการทดสอบ (
TestAttempt
,TestSummary
)
เหตุการณ์บางอย่างเหล่านี้แสดงอยู่นอก Bazel ในโปรโตคอลเหตุการณ์การสร้าง (เป็น BuildEvent
) ซึ่งไม่เพียงช่วยให้ BlazeModule
เท่านั้น แต่ยังช่วยให้สิ่งต่างๆ ที่อยู่นอกกระบวนการ Bazel สังเกตการสร้างได้ โดยสามารถเข้าถึงในรูปแบบไฟล์ที่มีข้อความโปรโตคอล หรือ Bazel จะเชื่อมต่อกับเซิร์ฟเวอร์ (เรียกว่าบริการเหตุการณ์การสร้าง) เพื่อสตรีมเหตุการณ์ก็ได้
ซึ่งติดตั้งใช้งานในแพ็กเกจ Java build.lib.buildeventservice
และ
build.lib.buildeventstream
ที่เก็บข้อมูลภายนอก
ในขณะที่ Bazel ได้รับการออกแบบมาเพื่อใช้ในโมโนเรโป (ต้นไม้แหล่งเดียวที่มีทุกอย่างที่จำเป็นต่อการสร้าง) แต่ Bazel อาศัยอยู่ในโลกที่โลกนี้ไม่เป็นความจริงเสมอไป "ที่เก็บข้อมูลภายนอก" เป็นการแยกความคิดที่ใช้เพื่อเชื่อมโลกทั้ง 2 โลกเข้าด้วยกัน โดยที่เก็บข้อมูลภายนอกแสดงถึงโค้ดที่จําเป็นสําหรับบิลด์แต่ไม่ได้อยู่ในสคีมาซอร์สหลัก
ไฟล์ WORKSPACE
ชุดที่เก็บข้อมูลภายนอกจะกำหนดโดยการแยกวิเคราะห์ไฟล์ WORKSPACE เช่น การประกาศแบบนี้
local_repository(name="foo", path="/foo/bar")
ผลลัพธ์ในที่เก็บชื่อ @foo
พร้อมใช้งาน สิ่งที่ทำให้การดำเนินการนี้ซับซ้อนคือผู้ใช้สามารถกำหนดกฎใหม่ของที่เก็บข้อมูลในไฟล์ Starlark ซึ่งจะใช้โหลดโค้ด Starlark ใหม่ได้ ซึ่งจะใช้กำหนดกฎใหม่ของที่เก็บข้อมูลได้ และอื่นๆ
ในการรองรับกรณีนี้ การแยกวิเคราะห์ไฟล์ WORKSPACE (ใน WorkspaceFileFunction
) จะแบ่งออกเป็นกลุ่มๆ โดยคั่นด้วยคำสั่ง load()
ดัชนีของข้อมูลโค้ดจะระบุด้วย WorkspaceFileKey.getIndex()
และการคํานวณ WorkspaceFileFunction
จนกว่าดัชนี X หมายถึงการประเมินจนกว่าจะถึงคำสั่ง load()
ลำดับที่ X
กําลังดึงข้อมูลที่เก็บ
คุณต้องดึงข้อมูลโค้ดของที่เก็บก่อนจึงจะพร้อมใช้งานสำหรับ Bazel ซึ่งจะทำให้ Bazel สร้างไดเรกทอรีภายใต้
$OUTPUT_BASE/external/<repository name>
การดึงข้อมูลพื้นที่เก็บข้อมูลจะทําตามขั้นตอนต่อไปนี้
PackageLookupFunction
ตระหนักว่าต้องมีที่เก็บข้อมูลและสร้างRepositoryName
เป็นSkyKey
ซึ่งเรียกใช้RepositoryLoaderFunction
RepositoryLoaderFunction
ส่งต่อคำขอไปยังRepositoryDelegatorFunction
ด้วยเหตุผลที่ไม่ชัดเจน (โค้ดระบุว่าเพื่อหลีกเลี่ยงการดาวน์โหลดซ้ำในกรณีที่ Skyframe รีสตาร์ท แต่เหตุผลนี้ไม่ชัดเจนมากนัก)RepositoryDelegatorFunction
จะค้นหากฎที่เก็บซึ่งได้รับคําขอให้ดึงข้อมูลโดยวนผ่านข้อมูลส่วนต่างๆ ของไฟล์ WORKSPACE จนกว่าจะพบที่เก็บที่ขอ- พบ
RepositoryFunction
ที่เหมาะสมซึ่งใช้การดึงข้อมูลที่เก็บ โดยอาจเป็นการใช้งานที่เก็บของ Starlark หรือแผนที่แบบฮาร์ดโค้ดสำหรับที่เก็บที่มีการใช้งานใน Java
การแคชมีหลายระดับเนื่องจากการดึงข้อมูลพื้นที่เก็บข้อมูลอาจใช้ทรัพยากรมาก
- มีแคชสำหรับไฟล์ที่ดาวน์โหลดไว้ซึ่งใช้การตรวจสอบข้อผิดพลาด (
RepositoryCache
) เป็นคีย์ ซึ่งการตรวจสอบข้อผิดพลาดต้องอยู่ในไฟล์ WORKSPACE แต่วิธีนี้ช่วยรักษาความสมบูรณ์ของข้อมูลได้ อินสแตนซ์เซิร์ฟเวอร์ Bazel ทุกอินสแตนซ์ในเวิร์กสเตชันเดียวกันจะแชร์ข้อมูลนี้ ไม่ว่าจะทำงานในเวิร์กสเปซหรือฐานเอาต์พุตใดก็ตาม - ระบบจะเขียน "ไฟล์เครื่องหมาย" สำหรับที่เก็บข้อมูลแต่ละแห่งในส่วน
$OUTPUT_BASE/external
ซึ่งมีการตรวจสอบผลรวมของกฎที่ใช้ดึงข้อมูล หากเซิร์ฟเวอร์ Bazel รีสตาร์ทแต่การตรวจสอบผลรวมไม่เปลี่ยนแปลง ระบบจะไม่ดึงข้อมูลอีกครั้ง การดำเนินการนี้ใช้ในRepositoryDelegatorFunction.DigestWriter
- ตัวเลือกบรรทัดคำสั่ง
--distdir
จะกำหนดแคชอื่นที่ใช้ค้นหาอาร์ติแฟกต์ที่จะดาวน์โหลด วิธีนี้เป็นประโยชน์ในการตั้งค่าระดับองค์กร ซึ่ง Bazel ไม่ควรดึงข้อมูลแบบสุ่มจากอินเทอร์เน็ตDownloadManager
เป็นผู้ติดตั้งใช้งาน
เมื่อดาวน์โหลดที่เก็บแล้ว ระบบจะถือว่าอาร์ติแฟกต์ที่อยู่ในที่เก็บนั้นเป็นอาร์ติแฟกต์ต้นทาง ปัญหานี้ทำให้เกิดปัญหาเนื่องจากโดยปกติแล้ว Bazel จะตรวจสอบเวอร์ชันล่าสุดของอาร์ติแฟกต์ต้นทางด้วยการเรียกใช้ stat() และอาร์ติแฟกต์เหล่านี้จะถูกทำให้ไม่ถูกต้องเช่นกันเมื่อคำจำกัดความของที่เก็บมีการเปลี่ยนแปลง ดังนั้น FileStateValue
สำหรับอาร์ติแฟกต์ในที่เก็บภายนอกจึงจำเป็นต้องใช้ที่เก็บภายนอก ซึ่งจัดการโดย ExternalFilesHelper
ไดเรกทอรีที่มีการจัดการ
ในบางครั้ง รีโพซิทอรีภายนอกจำเป็นต้องแก้ไขไฟล์ที่อยู่ภายใต้รูทของเวิร์กスペース (เช่น เครื่องมือจัดการแพ็กเกจที่จัดเก็บแพ็กเกจที่ดาวน์โหลดไว้ในไดเรกทอรีย่อยของต้นไม้ซอร์สโค้ด) วิธีนี้ไม่เหมาะกับการสันนิษฐานว่า Bazel ทำให้ไฟล์ต้นฉบับได้รับการแก้ไขโดยผู้ใช้เท่านั้น ไม่ใช่โดยตัวมันเอง และช่วยให้แพ็กเกจอ้างอิงทุกไดเรกทอรีภายใต้รูทของพื้นที่ทำงานได้ Bazel ทำ 2 สิ่งต่อไปนี้เพื่อให้ที่เก็บภายนอกแบบนี้ใช้งานได้
- อนุญาตให้ผู้ใช้ระบุไดเรกทอรีย่อยของพื้นที่ทำงาน Bazel สามารถเข้าถึงได้ โดยจะแสดงอยู่ในไฟล์ชื่อ
.bazelignore
และใช้งานฟังก์ชันการทำงานในBlacklistedPackagePrefixesFunction
- เราจะเข้ารหัสการแมปจากไดเรกทอรีย่อยของเวิร์กスペースไปยังที่เก็บข้อมูลภายนอกที่จัดการ
ManagedDirectoriesKnowledge
และจัดการFileStateValue
ที่อ้างอิงถึงไดเรกทอรีย่อยดังกล่าวในลักษณะเดียวกับที่เก็บข้อมูลภายนอกทั่วไป
การแมปที่เก็บ
กรณีที่รีโพซิทอรีหลายแห่งต้องการใช้รีโพซิทอรีเดียวกัน แต่ใช้เวอร์ชันต่างกัน (นี่คืออินสแตนซ์ของ "ปัญหาการพึ่งพาแบบเพชร") ตัวอย่างเช่น หากไบนารี 2 รายการในที่เก็บข้อมูลแยกกันในบิลด์ต้องการใช้ Guava ก็อาจมีการอ้างอิง Guava ด้วยป้ายกำกับที่ขึ้นต้นด้วย @guava//
ทั้ง 2 รายการ และคาดว่าจะเป็นเวอร์ชันที่แตกต่างกัน
ดังนั้น Bazel จึงอนุญาตให้รายการหนึ่งแมปป้ายกำกับที่เก็บภายนอกอีกครั้งเพื่อให้สตริง @guava//
อ้างถึงที่เก็บ Guava หนึ่ง (เช่น @guava1//
) ในที่เก็บของไบนารีหนึ่งและที่เก็บ Guava อีกรายการหนึ่ง (เช่น @guava2//
) กับที่เก็บของอีกแห่งได้
หรือจะใช้เพื่อต่อเพชรก็ได้ หากที่เก็บข้อมูลหนึ่งขึ้นอยู่กับ @guava1//
และอีกที่เก็บข้อมูลหนึ่งขึ้นอยู่กับ @guava2//
การแมปที่เก็บข้อมูลจะช่วยให้คุณแมปที่เก็บข้อมูลทั้ง 2 แห่งอีกครั้งเพื่อใช้ที่เก็บข้อมูล @guava//
ที่เป็น Canonical ได้
มีการระบุการแมปไว้ในไฟล์ WORKSPACE เป็นแอตทริบิวต์ repo_mapping
ของการกำหนดที่เก็บแต่ละรายการ จากนั้นจะปรากฏใน Skyframe ในฐานะสมาชิกของ WorkspaceFileValue
ซึ่งเชื่อมต่อกับสิ่งต่อไปนี้
Package.Builder.repositoryMapping
ซึ่งใช้เปลี่ยนรูปแบบแอตทริบิวต์ที่มีค่าป้ายกำกับของกฎในแพ็กเกจโดยRuleClass.populateRuleAttributeValues()
Package.repositoryMapping
ที่ใช้ในขั้นตอนการวิเคราะห์ (สำหรับการแก้ไขสิ่งต่างๆ เช่น$(location)
ซึ่งไม่ได้แยกวิเคราะห์ในขั้นตอนการโหลด)BzlLoadFunction
สําหรับการแก้ไขป้ายกํากับในคำสั่ง load()
บิต JNI
เซิร์ฟเวอร์ของ Bazel นั้น_ ส่วนใหญ่เป็น _write ใน Java ข้อยกเว้นคือส่วนที่ Java ทำเองไม่ได้หรือทำเองไม่ได้เมื่อเราติดตั้งใช้งาน ซึ่งส่วนใหญ่แล้วจะจำกัดไว้เพียงแค่การโต้ตอบกับระบบไฟล์ การควบคุมกระบวนการ และสิ่งอื่นๆ ในระดับต่ำ
โค้ด C++ จะอยู่ภายใต้ src/main/native และคลาส Java ที่มีเมธอดแบบเนทีฟดังนี้
NativePosixFiles
และNativePosixFileSystem
ProcessUtils
WindowsFileOperations
และWindowsFileProcesses
com.google.devtools.build.lib.platform
เอาต์พุตคอนโซล
การปล่อยเอาต์พุตของคอนโซลดูเหมือนจะเป็นเรื่องง่าย แต่ผลจากการทำงานที่หลายกระบวนการ (บางครั้งทำงานจากระยะไกล) การแคชแบบละเอียด ความต้องการให้มีเอาต์พุตเทอร์มินัลที่มีสีสันและมีสีสัน และการมีเซิร์ฟเวอร์ที่ทำงานเป็นเวลานานทำให้เป็นงานที่ไม่สำคัญ
ทันทีที่มีการเรียก RPC จากไคลเอ็นต์ ระบบจะสร้างRpcOutputStream
อินสแตนซ์ 2 รายการ (สำหรับ stdout และ stderr) ซึ่งส่งต่อข้อมูลที่พิมพ์ไปยังไคลเอ็นต์ จากนั้นระบบจะรวมข้อมูลเหล่านี้ไว้ใน OutErr
(คู่ (stdout, stderr)) ทุกอย่างที่ต้องพิมพ์ในคอนโซลจะต้องผ่านสตรีมเหล่านี้ จากนั้นระบบจะส่งสตรีมเหล่านี้ให้
BlazeCommandDispatcher.execExclusively()
โดยค่าเริ่มต้น เอาต์พุตจะพิมพ์โดยมีลำดับการยกเว้น ANSI เมื่อไม่เป็นที่ต้องการ (--color=no
) จะถูก AnsiStrippingOutputStream
ตัดออก นอกจากนี้ ระบบจะเปลี่ยนเส้นทาง System.out
และ System.err
ไปยังสตรีมเอาต์พุตเหล่านี้ด้วย
การดำเนินการนี้เพื่อให้สามารถพิมพ์ข้อมูลการแก้ไขข้อบกพร่องโดยใช้ System.err.println()
และยังคงแสดงผลลัพธ์ในเทอร์มินัลของไคลเอ็นต์ (ซึ่งแตกต่างจากเซิร์ฟเวอร์) ระบบจะตรวจสอบว่าหากกระบวนการสร้างเอาต์พุตไบนารี (เช่น bazel query --output=proto
) ระบบจะไม่ทำการแปลง stdout
ข้อความสั้นๆ (ข้อผิดพลาด คำเตือน และอื่นๆ) จะแสดงผ่านEventHandler
อินเทอร์เฟซ ที่สำคัญคือข้อความเหล่านี้แตกต่างจากสิ่งที่โพสต์ลงใน EventBus
(ทำให้เกิดความสับสน) Event
แต่ละรายการมี EventKind
(ข้อผิดพลาด คำเตือน ข้อมูล และอื่นๆ อีก 2-3 รายการ) และอาจมี Location
(ตำแหน่งในซอร์สโค้ดที่ทำให้เหตุการณ์เกิดขึ้น)
การติดตั้งใช้งาน EventHandler
บางรายการจะจัดเก็บเหตุการณ์ที่ได้รับ ซึ่งจะใช้เพื่อเล่นข้อมูลซ้ำไปยัง UI ที่เกิดจากการดำเนินการที่แคชไว้หลายประเภท เช่น คำเตือนที่เกิดจากเป้าหมายที่กําหนดค่าไว้ซึ่งแคชไว้
EventHandler
บางรายการยังอนุญาตให้โพสต์เหตุการณ์ที่ท้ายที่สุดจะปรากฏในบัสเหตุการณ์ด้วย (Event
ปกติจะไม่ปรากฏในบัสเหตุการณ์) เวอร์ชันเหล่านี้คือการใช้งาน ExtendedEventHandler
และการใช้งานหลักคือการเล่นเหตุการณ์ EventBus
ที่แคชไว้ซ้ำ เหตุการณ์ EventBus
ทั้งหมดเหล่านี้ใช้ Postable
แต่ไม่ได้หมายความว่าทุกอย่างที่โพสต์ไปยัง EventBus
ต้องใช้อินเทอร์เฟซนี้ เฉพาะเหตุการณ์ที่ ExtendedEventHandler
แคชไว้เท่านั้น (ซึ่งเป็นสิ่งที่ควรทำและส่วนใหญ่ก็ใช้ แต่ก็ไม่ได้บังคับ)
ผลลัพธ์ของเทอร์มินัลจะส่วนใหญ่แสดงผ่าน UiEventHandler
ซึ่งมีหน้าที่รับผิดชอบการจัดรูปแบบผลลัพธ์และรายงานความคืบหน้าที่ดูดีทั้งหมดที่ Bazel ดำเนินการ โดยจะมีอินพุต 2 รายการ ดังนี้
- บัสเหตุการณ์
- สตรีมเหตุการณ์ที่ส่งผ่านเข้ามาผ่าน Reporter
การเชื่อมต่อโดยตรงเพียงอย่างเดียวที่กลไกการดําเนินการตามคําสั่ง (เช่น Bazel ที่เหลือ) มีกับสตรีม RPC ไปยังไคลเอ็นต์คือผ่าน Reporter.getOutErr()
ซึ่งช่วยให้เข้าถึงสตรีมเหล่านี้ได้โดยตรง โดยจะใช้ได้ก็ต่อเมื่อคำสั่งต้องถ่ายโอนข้อมูลไบนารีจำนวนมาก (เช่น bazel query
)
การทำโปรไฟล์ Bazel
Bazel ทำงานเร็ว Bazel ยังทำงานช้าด้วย เนื่องจากบิลด์มีแนวโน้มที่จะเติบโตจนเกือบถึงขีดจำกัดที่รับได้ ด้วยเหตุนี้ Bazel จึงมีเครื่องมือวิเคราะห์ประสิทธิภาพที่สามารถใช้เพื่อวิเคราะห์ประสิทธิภาพของบิลด์และตัว Bazel เอง ติดตั้งใช้งานในคลาสที่มีชื่อว่า Profiler
ฟีเจอร์นี้จะเปิดอยู่โดยค่าเริ่มต้น แม้ว่าจะบันทึกเฉพาะข้อมูลแบบย่อเพื่อให้มีค่าใช้จ่ายเพิ่มเติมที่ยอมรับได้ แต่บรรทัดคำสั่ง--record_full_profiler_data
จะทำให้บันทึกทุกอย่างที่ทำได้
โดยจะปล่อยโปรไฟล์ในรูปแบบเครื่องมือสร้างโปรไฟล์ของ Chrome ซึ่งเหมาะกับการดูใน Chrome รูปแบบข้อมูลของเครื่องมือนี้คือกองงาน ซึ่งผู้ใช้สามารถเริ่มและสิ้นสุดงานได้ และงานต่างๆ ควรซ้อนกันอยู่อย่างเป็นระเบียบ แต่ละเธรด Java จะมีกองงานของตัวเอง TODO: How does this work with actions and continuation-passing style?
ระบบจะเริ่มและหยุดเครื่องมือวิเคราะห์ใน BlazeRuntime.initProfiler()
และ BlazeRuntime.afterCommand()
ตามลำดับ และจะพยายามทำงานเป็นเวลานานที่สุดเพื่อให้เราวิเคราะห์ทุกอย่างได้ หากต้องการเพิ่มบางสิ่งลงในโปรไฟล์
ให้โทรหา Profiler.instance().profile()
โดยจะแสดงผล Closeable
ซึ่งการปิดท้ายนั้นแสดงถึงจุดสิ้นสุดของงาน วิธีใช้ที่ดีที่สุดคือใช้กับคำสั่ง try-with-resources
นอกจากนี้ เรายังทำโปรไฟล์หน่วยความจำขั้นพื้นฐานใน MemoryProfiler
ด้วย นอกจากนี้ เครื่องมือนี้ยังเปิดอยู่เสมอ และส่วนใหญ่จะบันทึกขนาดฮีปสูงสุดและลักษณะการทำงานของ GC
ทดสอบ Bazel
บาเซลมีการทดสอบหลักๆ 2 ประเภท ได้แก่ การทดสอบที่สังเกต Bazel เป็น "กล่องดำ" และการทดสอบที่ดำเนินการเฉพาะช่วงการวิเคราะห์ เราเรียกการทดสอบแบบแรกว่า "การทดสอบการผสานรวม" และเรียกการทดสอบแบบหลังว่า "การทดสอบหน่วย" แม้ว่าการทดสอบเหล่านี้จะคล้ายกับการทดสอบการผสานรวมที่ผสานรวมน้อยลง และเรายังมีการทดสอบ 1 หน่วยที่ต้องทำจริงๆ ด้วย
ของการทดสอบการผสานรวมมี 2 ประเภท ได้แก่
- ติดตั้งใช้งานโดยใช้เฟรมเวิร์กการทดสอบ Bash ที่ละเอียดมากภายใต้
src/test/shell
- รายการที่ติดตั้งใช้งานใน Java ซึ่งติดตั้งใช้งานเป็นคลาสย่อยของ
BuildIntegrationTestCase
BuildIntegrationTestCase
เป็นเฟรมเวิร์กการทดสอบการผสานรวมที่แนะนำให้ใช้ เนื่องจากเตรียมพร้อมสำหรับสถานการณ์การทดสอบส่วนใหญ่ เนื่องจากเป็นเฟรมเวิร์ก Java จึงมีความสามารถในการแก้ไขข้อบกพร่องและการผสานรวมที่ราบรื่นกับเครื่องมือการพัฒนาทั่วไปจำนวนมาก มีตัวอย่างคลาส BuildIntegrationTestCase
มากมายในที่เก็บ Bazel
การทดสอบการวิเคราะห์จะติดตั้งใช้งานเป็นคลาสย่อยของ BuildViewTestCase
มีระบบไฟล์สำหรับใช้ชั่วคราวที่คุณสามารถใช้เขียนไฟล์ BUILD
จากนั้นเมธอดตัวช่วยต่างๆ จะขอเป้าหมายที่กำหนดค่าไว้ เปลี่ยนการกำหนดค่า และยืนยันสิ่งต่างๆ เกี่ยวกับผลลัพธ์ของการวิเคราะห์ได้