เอกสารนี้เป็นคำอธิบายเกี่ยวกับฐานของโค้ดและโครงสร้างของ Bazel มีไว้สำหรับผู้ที่ยินดีให้ความช่วยเหลือแก่ Bazel ไม่ใช่ผู้ใช้ปลายทาง
เกริ่นนำ
ฐานของโค้ดของ Bazel นั้นมีขนาดใหญ่ (โค้ดการผลิตประมาณ 350KLOC และโค้ดทดสอบประมาณ 260 KLOC) และไม่มีใครคุ้นชินกับภาพรวมทั้งหมด ทุกคนรู้จักหุบเขาลึกของพวกเขาเป็นอย่างดี แต่น้อยคนที่จะรู้ว่ามีสิ่งใดอยู่เหนือเนินเขาในทุกทิศทาง
เอกสารฉบับนี้จะพยายามแสดงภาพรวมของฐานของโค้ดเพื่อให้เริ่มต้นใช้งานได้ง่ายขึ้น เพื่อให้ผู้คนที่อยู่ในระหว่างการเดินทางไม่ต้องค้นพบตัวเองในป่าอันมืดมิดและเส้นทางที่ตรงไปตรงมา
ซอร์สโค้ดสาธารณะของ 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
) ตัวเลือกแบบแรกเรียกว่า "ตัวเลือกการเริ่มต้น" ซึ่งมีผลกับกระบวนการของเซิร์ฟเวอร์ทั้งหมด ในขณะที่ประเภทหลังคือ "ตัวเลือกคำสั่ง" จะมีผลกับคำสั่งเดียวเท่านั้น
อินสแตนซ์ของเซิร์ฟเวอร์แต่ละรายการมีโครงสร้างต้นทาง ("พื้นที่ทำงาน") 1 รายการ และโดยปกติพื้นที่ทำงานแต่ละรายการจะมีอินสแตนซ์เซิร์ฟเวอร์ที่ใช้งานอยู่ 1 รายการ ซึ่งสามารถหลีกเลี่ยงได้โดยระบุฐานเอาต์พุตที่กำหนดเอง (ดูข้อมูลเพิ่มเติมในส่วน "การออกแบบไดเรกทอรี")
Bazel แจกจ่ายเป็นไฟล์ปฏิบัติการของ ELF ไฟล์เดียวและเป็นไฟล์ .zip ที่ถูกต้องด้วย
เมื่อคุณพิมพ์ bazel
ไฟล์สั่งการของ ELF ข้างต้นซึ่งนำมาใช้ใน C++ ("ไคลเอ็นต์") จะได้รับการควบคุม เครื่องมือจะตั้งค่ากระบวนการของเซิร์ฟเวอร์ที่เหมาะสมโดยใช้ขั้นตอนต่อไปนี้
- ตรวจสอบว่าได้แยกข้อมูลตัวเองออกมาแล้วหรือไม่ แต่ถ้าไม่ ก็ก็จะเป็นเช่นนั้น ซึ่งเป็นที่มาของการติดตั้งใช้งานเซิร์ฟเวอร์
- ตรวจสอบว่ามีอินสแตนซ์เซิร์ฟเวอร์ที่ใช้งานอยู่ที่ใช้งานได้หรือไม่ อินสแตนซ์กำลังทำงานอยู่ มีตัวเลือกการเริ่มต้นที่ถูกต้องและใช้ไดเรกทอรีพื้นที่ทำงานที่ถูกต้อง ซึ่งจะค้นหาเซิร์ฟเวอร์ที่ทำงานอยู่โดยดูที่ไดเรกทอรี
$OUTPUT_BASE/server
ซึ่งมีไฟล์ล็อกที่มีพอร์ตที่เซิร์ฟเวอร์กำลังฟังอยู่ - หากจำเป็น ให้หยุดกระบวนการของเซิร์ฟเวอร์เก่า
- เริ่มต้นกระบวนการของเซิร์ฟเวอร์ใหม่หากจำเป็น
หลังจากกระบวนการของเซิร์ฟเวอร์ที่เหมาะสมพร้อมแล้ว ระบบจะส่งคำสั่งที่จำเป็นไปยังเซิร์ฟเวอร์ผ่านอินเทอร์เฟซ gRPC จากนั้นมีการส่งเอาต์พุตของ Bazel กลับไปที่เทอร์มินัล เรียกใช้พร้อมกันได้เพียงคำสั่งเดียวเท่านั้น ซึ่งทำได้โดยใช้กลไกการล็อกที่ซับซ้อนด้วยส่วนต่างๆ ใน C++ และส่วนต่างๆ ใน Java มีโครงสร้างพื้นฐานบางอย่างสำหรับการเรียกใช้คำสั่งหลายรายการพร้อมกัน เนื่องจากการเรียกใช้ bazel version
ควบคู่กับคำสั่งอื่นไม่ได้นั้นค่อนข้างน่าอาย ตัวบล็อกหลักคือวงจรของ BlazeModule
และบางสถานะใน BlazeRuntime
เมื่อสิ้นสุดคำสั่ง เซิร์ฟเวอร์ Bazel จะส่งโค้ดสำหรับออกที่ไคลเอ็นต์ควรส่งกลับมา ริ้วรอยที่น่าสนใจคือการใช้งาน bazel run
หน้าที่ของคำสั่งนี้คือการเรียกใช้สิ่งที่ Bazel เพิ่งสร้าง แต่ทำจากกระบวนการของเซิร์ฟเวอร์ไม่ได้ เนื่องจากไม่มีเทอร์มินัล ดังนั้นจึงเป็นการบอกไคลเอ็นต์ว่าควร ujexec() ไบนารีใด และควรใช้อาร์กิวเมนต์ใด
เมื่อมีคนกด Ctrl-C ไคลเอ็นต์จะเปลี่ยนเป็นการเรียกใช้ "ยกเลิก" บนการเชื่อมต่อ gRPC ซึ่งจะพยายามสิ้นสุดคำสั่งนี้โดยเร็วที่สุด หลังจากกด Ctrl-C ครั้งที่ 3 แล้ว ไคลเอ็นต์จะส่ง SIGKILL ไปยังเซิร์ฟเวอร์แทน
ซอร์สโค้ดของไคลเอ็นต์อยู่ภายใต้ src/main/cpp
และโปรโตคอลที่ใช้สื่อสารกับเซิร์ฟเวอร์จะอยู่ใน src/main/protobuf/command_server.proto
จุดแรกเข้าหลักของเซิร์ฟเวอร์คือ BlazeRuntime.main()
และการเรียก gRPC จากไคลเอ็นต์จะได้รับการจัดการโดย GrpcServerImpl.run()
เลย์เอาต์ไดเรกทอรี
Bazel สร้างชุดไดเรกทอรีที่ค่อนข้างซับซ้อนในระหว่างการสร้าง ดูคำอธิบายแบบเต็มได้ในเลย์เอาต์ไดเรกทอรีเอาต์พุต
"พื้นที่ทำงาน" คือโครงสร้างต้นทางที่ Bazel ทำงานอยู่ ซึ่งมักจะตรงกับรายการ ที่คุณชำระเงินจากการควบคุมแหล่งที่มา
Bazel ใส่ข้อมูลทั้งหมดไว้ในส่วน "รูทผู้ใช้เอาต์พุต" ซึ่งโดยปกติจะเป็น $HOME/.cache/bazel/_bazel_${USER}
แต่ลบล้างได้โดยใช้ตัวเลือกการเริ่มต้นใช้งาน --output_user_root
"ฐานผู้ใช้งาน" คือตำแหน่งที่ดึงข้อมูล Bazel ไป ซึ่งจะเกิดขึ้นโดยอัตโนมัติและ Bazel แต่ละเวอร์ชันจะได้รับไดเรกทอรีย่อยตาม checksum ภายใต้ฐานการติดตั้ง โดยค่าเริ่มต้นจะอยู่ที่ $OUTPUT_USER_ROOT/install
และสามารถเปลี่ยนได้โดยใช้ตัวเลือกบรรทัดคำสั่ง --install_base
"ฐานเอาต์พุต" คือตำแหน่งที่อินสแตนซ์ Bazel ที่ต่อเชื่อมกับพื้นที่ทำงานที่ระบุ ฐานเอาต์พุตแต่ละฐานจะมีอินสแตนซ์เซิร์ฟเวอร์ Bazel สูงสุด 1 อินสแตนซ์ที่ทำงานในแต่ละครั้ง เวลามักจะเป็นเวลา $OUTPUT_USER_ROOT/<checksum of the path
to the workspace>
คุณสามารถเปลี่ยนแปลงได้โดยใช้ตัวเลือกการเริ่มต้นใช้งาน --output_base
ซึ่งมีประโยชน์ในการหลบเลี่ยงข้อจำกัดที่มีเพียงอินสแตนซ์ของ Bazel เพียง 1 รายการเท่านั้นที่ทำงานในพื้นที่ทำงานได้ในช่วงเวลาหนึ่งๆ
ไดเรกทอรีเอาต์พุตจะประกอบด้วยข้อมูลต่อไปนี้
- ที่เก็บภายนอกที่ดึงข้อมูลแล้วที่
$OUTPUT_BASE/external
- รูทผู้บริหาร ซึ่งเป็นไดเรกทอรีที่มีลิงก์สัญลักษณ์ไปยังซอร์สโค้ดทั้งหมดสำหรับบิลด์ปัจจุบัน ตั้งอยู่ที่
$OUTPUT_BASE/execroot
ระหว่างการสร้าง ไดเรกทอรีการทำงานคือ$EXECROOT/<name of main repository>
เราวางแผนที่จะเปลี่ยนเป็น$EXECROOT
แม้ว่าจะเป็นแผนระยะยาวเพราะเป็นการเปลี่ยนแปลงที่เข้ากันไม่ได้อย่างมาก - ไฟล์ที่สร้างขึ้นระหว่างบิลด์
ขั้นตอนการเรียกใช้คำสั่ง
เมื่อเซิร์ฟเวอร์ Bazel ได้รับการควบคุมและได้รับข้อมูลเกี่ยวกับคำสั่งที่จำเป็นต้องเรียกใช้ เหตุการณ์ต่อไปนี้จะเกิดขึ้นตามลำดับ
BlazeCommandDispatcher
ได้รับแจ้งเกี่ยวกับคำขอใหม่ โดยจะกำหนดว่าคำสั่งนี้จำเป็นต้องมีพื้นที่ทำงานหรือไม่ (เกือบทุกคำสั่งยกเว้นคำสั่งที่ไม่ได้เกี่ยวข้องกับซอร์สโค้ด เช่น เวอร์ชันหรือความช่วยเหลือ) และกำหนดว่าคำสั่งอื่นทำงานอยู่หรือไม่พบคำสั่งที่ถูกต้อง คำสั่งแต่ละรายการต้องใช้อินเทอร์เฟซ
BlazeCommand
และต้องมีคำอธิบายประกอบ@Command
(ค่อนข้างเป็นแบบแผน การมีคำอธิบายข้อมูลเมตาทั้งหมดที่จำเป็นต้องใช้คำสั่งในBlazeCommand
)ตัวเลือกบรรทัดคำสั่งจะได้รับการแยกวิเคราะห์ แต่ละคำสั่งมีตัวเลือกบรรทัดคำสั่งที่แตกต่างกัน ดังที่อธิบายไว้ในคำอธิบายประกอบ
@Command
มีการสร้างบัสกิจกรรม รถบัสของเหตุการณ์เป็นสตรีมของเหตุการณ์ที่เกิดขึ้นระหว่างการสร้าง โดยบางส่วนมีการส่งออกไปภายนอก Bazel ภายใต้ aegis ของโปรโตคอลเหตุการณ์การสร้างเพื่อบอกให้โลกรู้ว่างานสร้างมีอย่างไร
คำสั่งจะเป็นการควบคุม คำสั่งที่น่าสนใจที่สุดคือคำสั่งที่เรียกใช้บิวด์ เช่น บิลด์ ทดสอบ เรียกใช้ การครอบคลุม และอื่นๆ โดย
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 มักจะเป็นประเภท Java แบบง่าย (สตริง จำนวนเต็ม บูลีน ป้ายกำกับ ฯลฯ) อย่างไรก็ตาม เรารองรับตัวเลือกประเภทที่ซับซ้อนมากกว่าด้วย ในกรณีนี้ งานการแปลงจากสตริงบรรทัดคำสั่งเป็นประเภทข้อมูลจะเป็นการใช้งาน com.google.devtools.common.options.Converter
แผนผังแหล่งที่มาตามที่เห็นโดย Bazel
บาเซลทำธุรกิจด้านการสร้างซอฟต์แวร์ ซึ่งเกิดขึ้นจากการอ่านและตีความซอร์สโค้ด จำนวนซอร์สโค้ดทั้งหมดที่ Bazel ทำงานบน เรียกว่า "พื้นที่ทำงาน" และมีโครงสร้างในที่เก็บ แพ็กเกจ และกฎ
ที่เก็บ
"ที่เก็บ" คือผังซอร์สที่นักพัฒนาซอฟต์แวร์ทำงานอยู่ ซึ่งมักเป็นตัวแทนของโปรเจ็กต์เดียว Blaze บรรพบุรุษของ Bazel ทำงานบนเขา Monorepo กล่าวคือ เป็นซอร์สทรีเดียวที่มีซอร์สโค้ดทั้งหมดที่ใช้ในการสร้างบิลด์ ในทางตรงกันข้าม Bazel รองรับโปรเจ็กต์ที่มีซอร์สโค้ดครอบคลุมที่เก็บหลายแห่ง ที่เก็บที่ Bazel เรียกใช้จะเรียกว่า "ที่เก็บหลัก" ส่วนที่เก็บอื่นๆ จะเรียกว่า "ที่เก็บภายนอก"
ที่เก็บมีการทำเครื่องหมายโดยไฟล์ชื่อ WORKSPACE
(หรือ WORKSPACE.bazel
) ในไดเรกทอรีราก ไฟล์นี้มีข้อมูลที่เป็น "ส่วนกลาง" สำหรับทั้งบิลด์ เช่น ชุดที่เก็บภายนอกที่ใช้ได้ โดยจะทำงานเหมือนไฟล์ Starlark ปกติ ซึ่งหมายความว่าสามารถ load()
ไฟล์ Starlark อื่นๆ ได้
ซึ่งโดยทั่วไปจะใช้เพื่อดึงที่เก็บซึ่งต้องการโดยที่เก็บที่มีการอ้างอิงอย่างชัดเจน (เราเรียกวิธีนี้ว่า "รูปแบบ deps.bzl
")
โค้ดของที่เก็บภายนอกลิงก์กันหรือดาวน์โหลดภายใต้ $OUTPUT_BASE/external
เมื่อเรียกใช้บิลด์ โครงสร้างซอร์สทั้งหมดต้องต่อเข้าด้วยกัน ซึ่งดำเนินการโดย SymlinkForest ซึ่งเชื่อมโยงแพ็กเกจทั้งหมดในที่เก็บหลักกับ $EXECROOT
และที่เก็บภายนอกทั้งหมดกับ $EXECROOT/external
หรือ $EXECROOT/..
(แน่นอนว่ากรณีแรกทำให้มีแพ็กเกจที่ชื่อว่า external
ในที่เก็บหลักไม่ได้และนั่นคือเหตุผลที่เราย้ายข้อมูลออกจากที่เก็บหลัก)
กล่องพัสดุ
ที่เก็บทั้งหมดประกอบด้วยแพ็กเกจ คอลเล็กชันของไฟล์ที่เกี่ยวข้อง และข้อกำหนดของทรัพยากร Dependency ซึ่งระบุโดยไฟล์ที่ชื่อว่า BUILD
หรือ BUILD.bazel
หากมีทั้ง 2 ไฟล์ Bazel จะเลือก BUILD.bazel
เหตุผลที่ยังคงยอมรับไฟล์ BUILD
อยู่คือ Blaze ซึ่งเป็นบรรพบุรุษของ Bazel ที่ใช้ชื่อไฟล์นี้ แต่กลับกลายเป็นส่วนของเส้นทางที่ใช้กันโดยทั่วไป โดยเฉพาะใน Windows ซึ่งชื่อไฟล์จะไม่คำนึงถึงตัวพิมพ์เล็กหรือใหญ่
แพ็กเกจไม่ได้เชื่อมโยงกัน: การเปลี่ยนแปลงไฟล์ BUILD
ของแพ็กเกจจะไม่ทำให้แพ็กเกจอื่นๆ เปลี่ยนแปลง การเพิ่มหรือการนําไฟล์ BUILD
ออก
_can _change แพ็กเกจอื่นๆ เนื่องจากลูกบอลแบบวนซ้ำจะหยุดที่ขอบเขตของแพ็กเกจ
และด้วยเหตุนี้การมีไฟล์ BUILD
จึงหยุดการเกิดซ้ำ
การประเมินไฟล์ BUILD
เรียกว่า "การโหลดแพ็กเกจ" คีย์นี้จะนำไปใช้ในคลาส PackageFactory
ทำงานโดยเรียกล่าม Starlark และต้องใช้ความรู้เกี่ยวกับชุดคลาสกฎที่มี ผลของการโหลดแพ็กเกจจะเป็นออบเจ็กต์ Package
ซึ่งส่วนมากจะเป็นแผนที่จากสตริง (ชื่อเป้าหมาย) ไปยังตัวเป้าหมาย
ความซับซ้อนจำนวนมากระหว่างการโหลดแพ็กเกจคือ Bazel ไม่ได้กำหนดให้ไฟล์ต้นฉบับทุกไฟล์อยู่ในรายการอย่างชัดเจน แต่เรียกใช้ globs แทนได้ (เช่น glob(["**/*.java"])
) โดยรองรับ Glomb ที่เกิดซ้ำที่ลงไปยังไดเรกทอรีย่อย (แต่ไม่อยู่ในแพ็กเกจย่อย) ซึ่งต่างจาก Shell ซึ่งจำเป็นต้องเข้าถึงระบบไฟล์ และเนื่องจากการทำเช่นนี้อาจทำได้ช้า เราจึงใช้เทคนิคทุกชนิดเพื่อทำให้ระบบทำงานพร้อมกันและมีประสิทธิภาพมากที่สุด
Globbing มีการนำไปใช้กับคลาสต่อไปนี้
LegacyGlobber
นักบินอวกาศที่ไม่รู้จักจาก Skyframe แบบรวดเร็วและแสนสุขSkyframeHybridGlobber
ซึ่งเป็นเวอร์ชันที่ใช้ Skyframe และเปลี่ยนกลับไปใช้ globber เดิมเพื่อหลีกเลี่ยง "การรีสตาร์ท Skyframe" (คำอธิบายด้านล่าง)
คลาส Package
เองก็มีสมาชิกบางรายที่ใช้แยกวิเคราะห์ไฟล์ WORKSPACE โดยเฉพาะซึ่งไม่เหมาะสำหรับแพ็กเกจจริง ข้อบกพร่องในการออกแบบเนื่องจากออบเจ็กต์ที่อธิบายแพ็กเกจปกติไม่ควรมีช่องที่อธิบายสิ่งอื่น ซึ่งรวมถึงการใช้งานดังต่อไปนี้
- การแมปที่เก็บ
- Toolchain ที่ลงทะเบียนแล้ว
- แพลตฟอร์มการดำเนินการที่ลงทะเบียน
ตามหลักแล้ว ควรแยกการแยกวิเคราะห์ไฟล์ WORKSPACE ออกจากการแยกวิเคราะห์แพ็กเกจปกติมากกว่า เพื่อให้ Package
ไม่ต้องตอบสนองความต้องการของทั้ง 2 ไฟล์ งานนี้ทำได้ยากเพราะทั้ง 2 อย่างนี้เชื่อมโยงกันอย่างใกล้ชิด
ป้ายกำกับ เป้าหมาย และกฎ
แพ็กเกจประกอบด้วยเป้าหมาย ซึ่งมีประเภทต่อไปนี้
- ไฟล์: สิ่งต่างๆ ที่เป็นอินพุตหรือเอาต์พุตของบิลด์ ในพาร์แลนซ์เบเซล เราเรียกสิ่งนี้ว่าอาร์ติแฟกต์ (กล่าวถึงที่อื่น) ไม่ใช่ทุกไฟล์ที่สร้างขึ้นระหว่างบิลด์จะเป็นเป้าหมาย แต่เอาต์พุตของ Bayel จะไม่มีป้ายกำกับที่เกี่ยวข้องเป็นเรื่องปกติ
- กฎ: กฎเหล่านี้อธิบายขั้นตอนในการรับผลลัพธ์จากข้อมูล โดยทั่วไปแล้ว ภาษาเหล่านี้จะเชื่อมโยงกับภาษาโปรแกรม (เช่น
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 ระหว่างคลาสของกฎกับเป้าหมายของประเภทดังกล่าว
สกายเฟรม
เฟรมเวิร์กการประเมินเบื้องหลัง Bazel เรียกว่า Skyframe โมเดลในที่นี้คือ ทุกสิ่งที่ต้องสร้างระหว่างการสร้างจะได้รับการจัดระเบียบเป็นกราฟแบบวงกลมที่มีทิศทางตรง โดยมีขอบที่ชี้จากส่วนของข้อมูลใดๆ ไปยังทรัพยากร Dependency กล่าวคือ ข้อมูลชิ้นอื่นๆ ที่จำเป็นต้องรู้ในการสร้างข้อมูลนั้น
โหนดในกราฟเรียกว่า SkyValue
และมีชื่อเรียกว่า SkyKey
วัตถุทั้งสองแบบนี้จะเปลี่ยนแปลงไม่ได้อย่างมาก ควรเข้าถึงได้เฉพาะออบเจ็กต์ที่เปลี่ยนแปลงไม่ได้เท่านั้น ค่าความไม่สม่ำเสมอนี้จะคงไว้ชั่วคราวเสมอและในกรณีที่ไม่มี (เช่น สำหรับคลาสตัวเลือกเดี่ยว BuildOptions
ซึ่งเป็นสมาชิกของ BuildConfigurationValue
และ SkyKey
) เราพยายามอย่างยิ่งที่จะไม่เปลี่ยนแปลงหรือเปลี่ยนแปลงการดำเนินการในรูปแบบที่ไม่สามารถสังเกตได้จากภายนอกเท่านั้น
ด้วยเหตุนี้ ทุกสิ่งที่คํานวณภายใน Skyframe (เช่น เป้าหมายที่กําหนดค่าไว้) จะต้องเปลี่ยนแปลงไม่ได้เช่นกัน
วิธีที่สะดวกที่สุดในการดูกราฟ Skyframe คือการเรียกใช้ bazel dump
--skyframe=detailed
ซึ่งจะถ่ายโอนกราฟ 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()
ซึ่งจะเกิดผลข้างเคียงจากการลงทะเบียนทรัพยากร Dependency เหล่านั้นลงในกราฟภายในของ Skyframe เพื่อให้ Skyframe ทราบว่าต้องประเมินฟังก์ชันอีกครั้งเมื่อความพึ่งพิงใดๆ ของ Skyframe มีการเปลี่ยนแปลง กล่าวคือ การแคชและการประมวลผลที่เพิ่มขึ้นของ Skyframe จะทำงานที่ระดับความละเอียดของ SkyFunction
และ SkyValue
เมื่อ SkyFunction
ขอทรัพยากร Dependency ที่ไม่พร้อมใช้งาน getValue()
จะแสดงผลเป็น Null จากนั้นฟังก์ชันควรเปลี่ยนการควบคุมกลับไปยัง Skyframe
โดยการส่งคืนค่า Null ในภายหลัง Skyframe จะประเมินทรัพยากร Dependency ที่ใช้งานไม่ได้ จากนั้นรีสตาร์ทฟังก์ชันใหม่ตั้งแต่ต้น ซึ่งจะทำได้เฉพาะกรณีที่การเรียกใช้ getValue()
เป็นผลลัพธ์ที่ไม่ใช่ Null เท่านั้น
ผลที่ตามมาคือการคำนวณใดๆ ที่ทำภายใน SkyFunction
ก่อนการรีสตาร์ทจะต้องทำซ้ำ แต่ไม่รวมงานที่ทำเพื่อประเมินทรัพยากร Dependency SkyValues
ซึ่งแคชเอาไว้ ดังนั้น เราจึงมักแก้ปัญหานี้โดย
- การประกาศทรัพยากร Dependency เป็นชุด (โดยใช้
getValuesAndExceptions()
) เพื่อจำกัดจำนวนการรีสตาร์ท - การแยก
SkyValue
เป็นชิ้นส่วนต่างๆ ที่คำนวณโดยSkyFunction
ต่างๆ เพื่อให้สามารถคำนวณและแคชแยกกันได้ ควรดำเนินการอย่างมีกลยุทธ์ เนื่องจากจะช่วยเพิ่มการใช้หน่วยความจำ - สถานะการจัดเก็บระหว่างการรีสตาร์ท ไม่ว่าจะใช้
SkyFunction.Environment.getState()
หรือเก็บแคชแบบคงที่เฉพาะกิจ "ไว้เบื้องหลัง Skyframe"
โดยพื้นฐานแล้ว เราต้องใช้วิธีแก้ปัญหาเบื้องต้นเหล่านี้เพราะเรามีโหนด Skyframe ที่กำลังทำงานเป็นประจำกว่าหลายแสนรายการ และ Java ไม่รองรับเทรดขนาดเล็ก
สตาร์ลาร์ก
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
ที่แสดงถึงออบเจ็กต์ดังกล่าว - การวิเคราะห์เป้าหมายที่กำหนดค่าไว้ นั่นคือการใช้กฎเพื่อสร้างกราฟการดำเนินการ
เป้าหมายที่กำหนดค่าแต่ละรายการในการปิดทางอ้อมของเป้าหมายที่กำหนดค่าซึ่งส่งคำขอในบรรทัดคำสั่งจะต้องได้รับการวิเคราะห์จากล่างขึ้นบน กล่าวคือ โหนด Leaf ก่อน จากนั้นจึงไปถึงโหนดในบรรทัดคำสั่ง อินพุตสำหรับการวิเคราะห์ เป้าหมายที่กำหนดค่าเดียว ได้แก่
- การกำหนดค่า ("วิธีการ" สร้างกฎนั้น เช่น แพลตฟอร์มเป้าหมายและสิ่งอื่นๆ อย่างตัวเลือกบรรทัดคำสั่งที่ผู้ใช้ต้องการส่งผ่านไปยังคอมไพเลอร์ C++)
- ทรัพยากร Dependency โดยตรง ผู้ให้บริการข้อมูลทางอ้อมของพวกเขา จะสามารถใช้ได้ในกฎที่จะวิเคราะห์ ไลบรารีเหล่านี้เรียกว่าเนื่องจากมี "ภาพรวม" ของข้อมูลในการปิดชั่วคราวของเป้าหมายที่กำหนดค่าไว้ เช่น ไฟล์ .jar ทั้งหมดในคลาสพาธหรือไฟล์ .o ทั้งหมดที่ต้องลิงก์กับไบนารี C++)
- ตัวเป้าหมายเอง นี่คือผลลัพธ์ของการโหลดแพ็กเกจที่มีเป้าหมายอยู่ สำหรับกฎ แอตทริบิวต์ดังกล่าวจะรวมถึงแอตทริบิวต์ต่างๆ ซึ่งมักจะเป็นสิ่งสำคัญ
- การใช้เป้าหมายที่กำหนดค่าไว้ สำหรับกฎ พารามิเตอร์นี้อาจอยู่ใน Starlark หรือใน Java เป้าหมายทั้งหมดที่ไม่มีการกำหนดค่าด้วยกฎจะได้รับการนำมาใช้งานใน Java
ผลลัพธ์ที่ได้จากการวิเคราะห์เป้าหมายที่กำหนดค่าไว้คือ
- ผู้ให้บริการข้อมูลทางอ้อมที่กำหนดค่าเป้าหมายที่อ้างอิงอยู่สามารถเข้าถึง
- อาร์ติแฟกต์ที่เครื่องมือนี้สร้างขึ้นได้และการดำเนินการต่างๆ ที่สร้างอาร์ติแฟกต์เหล่านั้น
API ที่เสนอให้กับกฎ Java คือ RuleContext
ซึ่งเทียบเท่ากับอาร์กิวเมนต์ ctx
ของกฎ Starlark API ของ API นั้นมีประสิทธิภาพมากกว่า แต่ขณะเดียวกัน ก็ทำ Bad ThingsTM ได้ง่ายกว่า เช่น เขียนโค้ดที่มีเวลาหรือความซับซ้อนของพื้นที่เป็นเลขยกกำลังสอง (หรือแย่กว่า) ทำให้เซิร์ฟเวอร์ Bazel ขัดข้องด้วยข้อยกเว้น Java หรือละเมิดค่าไม่เปลี่ยนแปลง (เช่น ด้วยการแก้ไขอินสแตนซ์ Options
โดยไม่ตั้งใจ หรือโดยการทำให้เป้าหมายที่กำหนดค่าไว้เปลี่ยนแปลงไม่ได้)
อัลกอริทึมที่กำหนดทรัพยากร Dependency โดยตรงของชีวิตเป้าหมายที่กำหนดค่าไว้ใน DependencyResolver.dependentNodeMap()
การกำหนดค่า
การกำหนดค่าคือ "วิธีการ" ในการสร้างเป้าหมายสำหรับแพลตฟอร์ม ตัวเลือกบรรทัดคำสั่ง ฯลฯ
สามารถสร้างเป้าหมายเดียวกันสำหรับการกำหนดค่าหลายรายการในเวอร์ชันเดียวกันได้ ซึ่งมีประโยชน์ เช่น เมื่อมีการใช้โค้ดเดียวกันสำหรับเครื่องมือที่ทำงานระหว่างบิลด์และโค้ดเป้าหมาย และเรากำลังทำการคอมไพล์แบบข้ามระบบ หรือเมื่อกำลังสร้างแอป Android ที่มีน้ำหนักมาก (แอปที่มีโค้ดแบบเนทีฟสำหรับสถาปัตยกรรม CPU หลายแบบ)
โดยหลักการแล้ว การกําหนดค่าคืออินสแตนซ์ BuildOptions
แต่ในทางปฏิบัติแล้ว BuildOptions
จะรวมอยู่ใน BuildConfiguration
ซึ่งมีฟังก์ชันอื่นๆ อีกมากมาย โดยจะเผยแพร่จากด้านบนของกราฟ
การอ้างอิงถึงด้านล่าง หากมีการเปลี่ยนแปลง ก็ต้องวิเคราะห์บิลด์อีกครั้ง
ซึ่งส่งผลให้เกิดความผิดปกติ เช่น ต้องวิเคราะห์บิลด์ทั้งหมดอีกครั้ง เช่น จำนวนการเปลี่ยนแปลงของการเรียกใช้การทดสอบที่ขอ แม้ว่าจะส่งผลต่อเป้าหมายทดสอบเท่านั้น (เรามีแผนที่จะ "ตัด" การกำหนดค่าซึ่งยังไม่ใช่กรณีเช่นนี้ แต่ยังไม่พร้อมใช้งาน)
เมื่อการติดตั้งใช้งานกฎต้องการส่วนหนึ่งของการกำหนดค่า ก็จะต้องประกาศกฎนั้นในคำจำกัดความโดยใช้ RuleClass.Builder.requiresConfigurationFragments()
วิธีนี้มีไว้เพื่อหลีกเลี่ยงข้อผิดพลาด (เช่น กฎ Python ที่ใช้ส่วนย่อยของ Java) และเพื่อช่วยในการตัดการกำหนดค่า เช่นในกรณีที่ตัวเลือก Python เปลี่ยนไป ก็ไม่จำเป็นต้องวิเคราะห์เป้าหมาย C++ ใหม่
การกำหนดค่ากฎไม่จำเป็นต้องเหมือนกับการกำหนดค่ากฎ "หลัก" กระบวนการเปลี่ยนการกำหนดค่าใน Edge Dependency เรียกว่า "การเปลี่ยนการกำหนดค่า" ปัญหานี้อาจเกิดขึ้นได้ใน 2 ที่ ดังนี้
- อยู่ใน Dependency Edge การเปลี่ยนเหล่านี้ระบุอยู่ใน
Attribute.Builder.cfg()
และเป็นฟังก์ชันจากRule
(ที่จะเกิดการเปลี่ยน) และBuildOptions
(การกำหนดค่าเดิม) เป็นBuildOptions
(การกำหนดค่าเอาต์พุต) อย่างน้อย 1 รายการ - ที่ Edge ขาเข้าไปยังเป้าหมายที่กำหนดค่าไว้ โดยระบุใน
RuleClass.Builder.cfg()
ชั้นเรียนที่เกี่ยวข้องคือ TransitionFactory
และ ConfigurationTransition
การเปลี่ยนการกำหนดค่าจะใช้ดังตัวอย่างต่อไปนี้
- เพื่อประกาศว่ามีการใช้ทรัพยากร Dependency เฉพาะระหว่างบิลด์ และควรสร้างทรัพยากร Dependency ในสถาปัตยกรรมการดำเนินการ
- เพื่อประกาศว่าคุณต้องสร้างทรัพยากร Dependency ที่เจาะจงสำหรับสถาปัตยกรรมหลายอย่าง (เช่น สำหรับโค้ดแบบเนทีฟใน APK ของ Android แบบไขมัน)
หากการเปลี่ยนการกำหนดค่าทำให้เกิดการกำหนดค่าหลายรายการ กรณีนี้จะเรียกว่าการเปลี่ยนรุ่นแยกกัน
คุณสามารถเปลี่ยนการกำหนดค่าใน Starlark ได้ด้วย (ดูเอกสารประกอบ ที่นี่)
ผู้ให้บริการข้อมูลทางอ้อม
ผู้ให้บริการข้อมูลทางอ้อมคือวิธี (และทาง _only _) สำหรับเป้าหมายที่มีการกำหนดค่าในการบอกสิ่งต่างๆ เกี่ยวกับเป้าหมายอื่นๆ ที่กำหนดค่าไว้ซึ่งอาศัยข้อมูลนั้น เหตุผลที่ทำให้คำว่า "สกรรมกริยา" มีชื่อก็คือคำนี้มักจะเป็นการควบรวมบางส่วนของการปิดแบบสกรรมกริยาของเป้าหมายที่กำหนดค่าไว้
โดยทั่วไปจะมีการติดต่อแบบ 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
ซึ่งเป็นแนวคิดที่คลุมเครือเกี่ยวกับ "ชุดของไฟล์ที่กฎนี้นำเสนอ" ซึ่งเป็นไฟล์ที่สร้างขึ้นเมื่อเป้าหมายที่กำหนดค่าอยู่ในบรรทัดคำสั่งหรือใน src ของ Genrule- ไฟล์รัน ข้อมูลทั่วไป และข้อมูล
- กลุ่มเอาต์พุต ซึ่งก็คือ "ไฟล์ชุดอื่นๆ" ที่กฎสร้างขึ้นได้ ซึ่งเข้าถึงได้โดยใช้แอตทริบิวต์ export_group ของกฎกลุ่มไฟล์ใน BUILD และใช้ผู้ให้บริการ
OutputGroupInfo
ใน Java
Runfiles
ไบนารีบางรายการต้องใช้ไฟล์ข้อมูลจึงจะทำงานได้ ตัวอย่างที่เห็นได้ชัดเจนคือการทดสอบที่ต้องใช้ไฟล์อินพุต โดยมีแนวคิดของ "runfiles" ใน Bazel "แผนผังrunfiles" เป็นแผนผังไดเรกทอรีของไฟล์ข้อมูลสำหรับไบนารีหนึ่งๆ โดยสร้างขึ้นในระบบไฟล์เป็นแผนผังสัญลักษณ์ โดยมีลิงก์สัญลักษณ์แต่ละลิงก์ชี้ไปยังไฟล์ในแหล่งที่มาของต้นไม้เอาต์พุต
ชุดของ Runfile จะแสดงเป็นอินสแตนซ์ Runfiles
โดยแนวคิดจะเป็นแผนที่จากเส้นทางของไฟล์ในโครงสร้าง Runfiles ไปยังอินสแตนซ์ Artifact
ที่แสดงถึงไฟล์นั้น มีความซับซ้อนมากกว่า Map
รายการเดียวเล็กน้อยเนื่องด้วยเหตุผล 2 ข้อ ดังนี้
- ส่วนใหญ่แล้ว เส้นทาง Runfiles ของไฟล์จะเหมือนกับเส้นทางไฟล์ปฏิบัติการ เราใช้ข้อมูลนี้เพื่อประหยัด RAM บางส่วน
- มีรายการเดิมหลายประเภทในแผนผัง Runfiles ซึ่งต้องแสดง
Runfiles จะได้รับการรวบรวมโดยใช้ RunfilesProvider
: อินสแตนซ์ของคลาสนี้เป็นตัวแทนของ Runfile ที่กำหนดค่าเป้าหมาย (เช่น ไลบรารี) และความจำเป็นการปิดแบบสกรรม และรวบรวมมาเหมือนชุดที่ฝัง (อันที่จริงแล้ว ชุดเหล่านี้ใช้โดยชุดที่ซ้อนกันภายใต้หน้าปก) โดยแยก Runfile ของทรัพยากร Dependency แต่ละรายการ เพิ่มไฟล์ของรันไทม์แต่ละไฟล์ขึ้นมา จากนั้นส่งชุดผลลัพธ์ในรูปแบบกราฟทรัพยากร Dependency อินสแตนซ์ RunfilesProvider
มีอินสแตนซ์ Runfiles
2 รายการ อินสแตนซ์หนึ่งใช้สำหรับกรณีที่กฎอ้างอิงผ่านแอตทริบิวต์ "data" และอินสแตนซ์หนึ่งสำหรับการอ้างอิงขาเข้าประเภทอื่นๆ ทั้งนี้เพราะบางครั้งเป้าหมายจะแสดง Runfile ที่ต่างกันเมื่อต้องใช้ผ่านแอตทริบิวต์ข้อมูล ซึ่งเป็นลักษณะการทำงานที่ไม่พึงประสงค์ซึ่งเรายังไม่ได้นำออก
Runfiles ของไบนารีจะแสดงเป็นอินสแตนซ์ของ RunfilesSupport
ซึ่งแตกต่างจาก Runfiles
เนื่องจาก RunfilesSupport
มีความสามารถในการสร้างได้จริง (ต่างจาก Runfiles
ที่เป็นแค่การแมป) ซึ่งต้องมีคอมโพเนนต์เพิ่มเติมต่อไปนี้
- ไฟล์ Manifest ของ Runfiles อินพุต นี่คือคำอธิบายแบบต่อเนื่องของโครงสร้าง Runfiles โดยใช้เป็นพร็อกซีสำหรับเนื้อหาของโครงสร้าง Runfiles และ Bazel จะถือว่าโครงสร้าง Runfiles มีการเปลี่ยนแปลงก็ต่อเมื่อเนื้อหาของไฟล์ Manifest มีการเปลี่ยนแปลงเท่านั้น
- ไฟล์ Manifest ของ Runfiles เอาต์พุต ไลบรารีรันไทม์ที่ใช้จัดการโครงสร้าง Runfiles โดยเฉพาะใน Windows ซึ่งบางครั้งไม่รองรับลิงก์สัญลักษณ์
- คนกลางของ Runfiles เพื่อที่จะมีแผนผัง Runfiles อยู่ เราจะต้องสร้างแผนผัง Symlink และอาร์ติแฟกต์ที่ลิงก์สัญลักษณ์ชี้ไป ในการลดจำนวนขอบของทรัพยากร Dependency จะใช้คนกลางของ Runfiles เพื่อแสดงถึงองค์ประกอบเหล่านี้ทั้งหมด
- อาร์กิวเมนต์บรรทัดคำสั่งสำหรับการเรียกใช้ไบนารีที่มีอ็อบเจ็กต์ Runfiles
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 ใดลงในกราฟการอ้างอิงAspect
แสดงข้อมูลทั้งหมดที่จำเป็นต่อการคำนวณลักษณะที่ขยายกราฟ Dependency ลง ซึ่งประกอบด้วยคลาสด้านต่างๆ คำจำกัดความ และพารามิเตอร์RuleAspect
เป็นฟังก์ชันที่กำหนดว่ากฎเฉพาะหนึ่งๆ ควรเผยแพร่ในด้านใด ซึ่งเป็นฟังก์ชันRule
->Aspect
ข้อมูลแทรกที่ค่อนข้างคาดไม่ถึงคือแง่มุมต่างๆ อาจแนบไปกับแง่มุมอื่นๆ ได้ ตัวอย่างเช่น แง่มุมที่รวบรวมคลาสพาธสำหรับ Java IDE อาจต้องการทราบเกี่ยวกับไฟล์ .jar ทั้งหมดในคลาสพาธ แต่บางไฟล์เป็นบัฟเฟอร์โปรโตคอล ในกรณีนี้ ลักษณะ IDE จะต้องแนบกับคู่ (กฎ proto_library
+ องค์ประกอบ Java Proto)
ระบบจะบันทึกความซับซ้อนของด้านต่างๆ ในแง่มุมต่างๆ ไว้ในคลาส AspectCollection
แพลตฟอร์มและเครือเครื่องมือ
Bazel รองรับบิลด์หลายแพลตฟอร์ม กล่าวคือ สร้างบิลด์ที่อาจมีสถาปัตยกรรมหลายรายการที่การทำงานของบิลด์ทำงาน และสถาปัตยกรรมหลายรายการสำหรับสร้างโค้ด สถาปัตยกรรมเหล่านี้เรียกว่าแพลตฟอร์มในโครงสร้างของ Bazel (ดูเอกสารฉบับเต็มที่นี่)
คำอธิบายแพลตฟอร์มจะอธิบายด้วยการแมปคีย์-ค่าจากการตั้งค่าข้อจำกัด (เช่น แนวคิดของ "สถาปัตยกรรม CPU") ไปจนถึงจำกัดค่า (เช่น CPU บางประเภท เช่น x86_64) เรามี "พจนานุกรม" ของการตั้งค่าข้อจำกัดและค่าที่ใช้กันโดยทั่วไปในที่เก็บ @platforms
แนวคิดของ toolchain มาจากข้อเท็จจริงที่ว่า 1 อาจต้องใช้คอมไพเลอร์ที่แตกต่างกันขึ้นอยู่กับแพลตฟอร์มที่บิลด์ใช้งานและแพลตฟอร์มใดเป้าหมายหนึ่ง เช่น เชนเครื่องมือ C++ หนึ่งๆ อาจทำงานบนระบบปฏิบัติการที่เฉพาะเจาะจงและกำหนดเป้าหมายระบบปฏิบัติการอื่นๆ ได้ Bazel ต้องกำหนดคอมไพเลอร์ C++ ที่ใช้ตามการเรียกใช้ชุดและแพลตฟอร์มเป้าหมาย (ดูเอกสารประกอบสำหรับ Toolchain ที่นี่)
ในการดำเนินการดังกล่าว เชนเครื่องมือจะมีการใส่คำอธิบายประกอบด้วยชุดการดำเนินการและข้อจำกัดของแพลตฟอร์มเป้าหมายที่รองรับ ในการทำเช่นนี้ นิยามของ เชนเครื่องมือจะแบ่งออกเป็น 2 ส่วน ดังนี้
- กฎ
toolchain()
ที่อธิบายชุดการดำเนินการและข้อจำกัดของ Toolchain รองรับ รวมทั้งบอกประเภท (เช่น C++ หรือ Java) ของ Toolchain (เช่น C++ หรือ Java) ของ Toolchain (ข้อหลังจะแสดงด้วยกฎtoolchain_type()
) - กฎเฉพาะภาษาที่อธิบายเครื่องมือเชนจริง (เช่น
cc_toolchain()
)
การมีวิธีนี้เกิดขึ้นเนื่องจากเราจำเป็นต้องทราบข้อจำกัดสำหรับทุกๆ Toolchain เพื่อทำให้การแก้ไข Toolchain และกฎ *_toolchain()
ที่เจาะจงภาษามีข้อมูลมากกว่านั้นมาก จึงใช้เวลาในการโหลดนานขึ้น
แพลตฟอร์มการดำเนินการระบุด้วยวิธีใดวิธีหนึ่งต่อไปนี้
- ในไฟล์ WORKSPACE โดยใช้ฟังก์ชัน
register_execution_platforms()
- ในบรรทัดคำสั่งโดยใช้ตัวเลือกบรรทัดคำสั่ง --extra_execution_platforms
ระบบจะคำนวณชุดแพลตฟอร์มการดำเนินการที่ใช้ได้ใน
RegisteredExecutionPlatformsFunction
แพลตฟอร์มเป้าหมายสำหรับเป้าหมายที่กำหนดค่าจะกำหนดโดย
PlatformOptions.computeTargetPlatform()
นี่เป็นรายชื่อแพลตฟอร์ม เพราะสุดท้ายแล้วเราต้องการรองรับแพลตฟอร์มเป้าหมายหลายแพลตฟอร์ม แต่ยังไม่ได้ใช้งาน
ชุดของเชนเครื่องมือที่จะใช้สำหรับเป้าหมายที่กําหนดค่าไว้กําหนดโดย ToolchainResolutionFunction
เป็นฟังก์ชันของ
- ชุดของเชนเครื่องมือที่ลงทะเบียนไว้ (ในไฟล์ WORKSPACE และการกำหนดค่า)
- การดำเนินการที่ต้องการและแพลตฟอร์มเป้าหมาย (ในการกำหนดค่า)
- ชุดของประเภทเชนเครื่องมือที่เป้าหมายที่กำหนดค่าไว้ (ใน
UnloadedToolchainContextKey)
) กำหนด - ชุดข้อจำกัดแพลตฟอร์มการดำเนินการของเป้าหมายที่กำหนดค่าไว้ (แอตทริบิวต์
exec_compatible_with
) และการกำหนดค่า (--experimental_add_exec_constraints_to_targets
) ในUnloadedToolchainContextKey
ผลลัพธ์คือ UnloadedToolchainContext
ซึ่งเป็นแผนที่จากประเภท Toolchain (แสดงเป็นอินสแตนซ์ ToolchainTypeInfo
) ไปยังป้ายกำกับของ Toolchain ที่เลือก เครื่องมือนี้เรียกว่า "ยกเลิกการโหลด" เนื่องจากไม่มี Toolchains มีแต่ป้ายกำกับเท่านั้น
จากนั้นระบบจะโหลดเชนเครื่องมือโดยใช้ ResolvedToolchainContext.load()
และใช้โดยการติดตั้งใช้งานเป้าหมายที่กำหนดค่าไว้ที่ขอ
เรามีระบบเดิมที่อาศัยการกำหนดค่า "โฮสต์" รายการเดียว และการกำหนดค่าเป้าหมายจะแสดงด้วยแฟล็กการกำหนดค่าต่างๆ เช่น --cpu
เรากำลังทยอยเปลี่ยนไปใช้ระบบข้างต้น เราได้ใช้การแมปแพลตฟอร์มเพื่อแปลค่าระหว่างแฟล็กเดิมและข้อจำกัดแพลตฟอร์มรูปแบบใหม่ เพื่อจัดการกรณีที่ผู้ใช้ต้องใช้การกำหนดค่าเดิม
รหัสของพวกเขาอยู่ในภาษา PlatformMappingFunction
และใช้ "ภาษาน้อย" ที่ไม่ใช่ของ Starlark
ข้อจำกัด
บางครั้งมีคนต้องการระบุเป้าหมายที่ใช้ได้กับหลายแพลตฟอร์ม Bazel มีกลไกหลายอย่างที่ทำให้บรรลุเป้าหมายนี้ได้ (น่าเสียดาย)
- ข้อจำกัดเฉพาะกฎ
environment_group()
/environment()
- ข้อจำกัดของแพลตฟอร์ม
ข้อจำกัดเฉพาะกฎมักใช้ภายใน Google สำหรับกฎของ Java ข้อจำกัดเหล่านี้กำลังจะเลิกใช้งานและไม่พร้อมใช้งานใน Bazel แต่ซอร์สโค้ดอาจมีการอ้างอิงถึงข้อจำกัดนั้น แอตทริบิวต์ที่ควบคุมส่วนนี้เรียกว่า
constraints=
Environment_group() และ รวมทั้งสภาพแวดล้อม()
กฎเหล่านี้เป็นกลไกแบบเดิมและไม่ได้ใช้กันอย่างแพร่หลาย
กฎสำหรับบิวด์ทั้งหมดสามารถประกาศได้ว่าสามารถสร้าง "สภาพแวดล้อม" แบบใดได้ โดย "สภาพแวดล้อม" เป็นอินสแตนซ์ของกฎ 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 รองรับการดำเนินการนี้โดยกลไกที่เรียกว่า visibility: คุณประกาศได้ว่าเป้าหมายหนึ่งๆ จะใช้ได้โดยใช้แอตทริบิวต์ visibility เท่านั้น แอตทริบิวต์นี้มีความพิเศษเล็กน้อยเนื่องจากแม้ว่าจะมีรายการป้ายกำกับ แต่ป้ายกำกับเหล่านี้อาจเข้ารหัสรูปแบบเหนือชื่อแพ็กเกจแทนที่จะเป็นตัวชี้ไปยังเป้าหมายที่เจาะจง (ใช่ นี่เป็นข้อบกพร่องในการออกแบบ)
โดยจะนำไปใช้ในตำแหน่งต่อไปนี้
- อินเทอร์เฟซ
RuleVisibility
แสดงการประกาศระดับการเข้าถึง โดยอาจเป็นได้ทั้งค่าคงที่ (สาธารณะทั้งหมดหรือส่วนตัวโดยสมบูรณ์) หรือรายการป้ายกำกับก็ได้ - ป้ายกำกับอาจหมายถึงกลุ่มแพ็กเกจ (รายการแพ็กเกจที่กำหนดไว้ล่วงหน้า) ไปจนถึงแพ็กเกจโดยตรง (
//pkg:__pkg__
) หรือแผนผังย่อยของแพ็กเกจ (//pkg:__subpackages__
) ซึ่งแตกต่างจากไวยากรณ์บรรทัดคำสั่งซึ่งใช้//pkg:*
หรือ//pkg/...
- มีการใช้กลุ่มแพ็กเกจเป็นเป้าหมาย (
PackageGroup
) และเป้าหมายที่กำหนดค่า (PackageGroupConfiguredTarget
) ของตนเอง เราอาจแทนที่กลุ่มเหล่านี้ด้วยกฎง่ายๆ ได้หากต้องการ ระบบนำตรรกะนี้ไปใช้ด้วยความช่วยเหลือจากPackageSpecification
ซึ่งสอดคล้องกับรูปแบบเดียว เช่น//pkg/...
,PackageGroupContents
ซึ่งตรงกับแอตทริบิวต์packages
ของpackage_group
รายการเดียว และPackageSpecificationProvider
ซึ่งรวมกันกว่าpackage_group
และสับเปลี่ยนincludes
ของมัน - การแปลงจากรายการป้ายกำกับระดับการเข้าถึงเป็นทรัพยากร Dependency นั้นดำเนินการใน
DependencyResolver.visitTargetVisibility
และอีกตำแหน่งอื่นๆ อีก 2-3 แห่ง - การตรวจสอบจริงจะเสร็จสิ้นใน
CommonPrerequisiteValidator.validateDirectPrerequisiteVisibility()
ชุดที่ซ้อนกัน
บ่อยครั้งที่เป้าหมายที่กำหนดค่าจะรวบรวมชุดไฟล์จากทรัพยากร Dependency เพิ่มชุดไฟล์ของตัวเอง และรวมชุดไฟล์แบบรวมไว้ในผู้ให้บริการข้อมูลทางอ้อมเพื่อให้เป้าหมายที่กำหนดค่าที่อ้างอิงอยู่ทำงานแบบเดียวกัน ตัวอย่าง
- ไฟล์ส่วนหัว C++ ที่ใช้สำหรับบิวด์
- ไฟล์ออบเจ็กต์ที่แสดงถึงการปิดสกรรมของ
cc_library
- ชุดของไฟล์ .jar ที่ต้องอยู่บนคลาสพาธเพื่อให้กฎ Java คอมไพล์หรือเรียกใช้
- ชุดของไฟล์ Python ในการปิดทางอ้อมของกฎ Python
หากเราทำแบบไร้เดียงสาโดยใช้ List
หรือ Set
ก็น่าจะมีการใช้หน่วยความจำแบบกำลังสอง นั่นคือถ้ามีกฎห่วงโซ่ N และแต่ละกฎเพิ่มไฟล์ เราจะมีสมาชิกคอลเล็กชัน 1+2+...+N คน
เพื่อแก้ไขปัญหานี้ เราจึงมีแนวคิดของ NestedSet
ซึ่งเป็นโครงสร้างข้อมูลที่ประกอบด้วยอินสแตนซ์ NestedSet
อื่นๆ และสมาชิกบางส่วนของตัวเอง จึงสร้างกราฟแบบวนซ้ำที่มีทิศทางของชุด ซึ่งจะเปลี่ยนแปลงไม่ได้และสมาชิกก็สามารถทำซ้ำได้ เรากำหนดลำดับการทำซ้ำหลายรายการ (NestedSet.Order
) ได้แก่ การสั่งล่วงหน้า หลังการสั่งซื้อ การจำแนกประเภท (โหนดจะอยู่หลังระดับบนเสมอ) และ "ไม่สนใจ แต่ควรเหมือนกันทุกครั้ง"
โครงสร้างข้อมูลเดียวกันเรียกว่า depset
ใน Starlark
อาร์ติแฟกต์และการดำเนินการ
บิลด์จริงประกอบด้วยชุดคำสั่งที่ต้องเรียกใช้เพื่อสร้างเอาต์พุตที่ผู้ใช้ต้องการ คำสั่งจะแสดงเป็นอินสแตนซ์ของคลาส Action
และจะแสดงเป็นอินสแตนซ์ของคลาส Artifact
กราฟเหล่านี้จะมีการจัดเรียงเป็นกราฟแบบวงกลมที่มี 2 ส่วน เรียกว่า "กราฟการดำเนินการ"
อาร์ติแฟกต์มี 2 ประเภท ได้แก่ อาร์ติแฟกต์ต้นทาง (อาร์ติแฟกต์ที่ใช้ได้ก่อนที่ Bazel จะเริ่มดำเนินการ) และอาร์ติแฟกต์ที่ดึงมา (ที่ต้องสร้าง) อาร์ติแฟกต์ที่สร้างขึ้นอาจมีได้หลายประเภท ดังนี้
- **อาร์ติแฟกต์ปกติ **ระบบจะตรวจสอบความเป็นปัจจุบันด้วยการคำนวณ Checksum ที่มี mtime เป็นทางลัด เราจะไม่ตรวจสอบไฟล์หากเวลา ไม่มีการเปลี่ยนแปลง
- อาร์ติแฟกต์ Sym Link ที่ยังไม่ได้แก้ไข ระบบจะตรวจสอบการอัปเดตเหล่านี้เป็นข้อมูลล่าสุดโดยเรียก readlink() อาร์ติแฟกต์เหล่านี้อาจจะเชื่อมโยงเป็น Symlink ที่ต่างจากอาร์ติแฟกต์ทั่วไป มักใช้ในกรณีที่ไฟล์บางไฟล์มีไฟล์ที่ต้องการเก็บถาวร
- อาร์ติแฟกต์ต้นไม้ ไฟล์เหล่านี้ไม่ใช่ไฟล์เดี่ยว แต่เป็นต้นไม้ไดเรกทอรี ระบบจะตรวจหาข้อมูลล่าสุดโดยตรวจสอบชุดไฟล์ในนั้นและเนื้อหาของไฟล์ โดยจะแสดงเป็น
TreeArtifact
- อาร์ติแฟกต์ข้อมูลเมตาอย่างต่อเนื่อง การเปลี่ยนแปลงอาร์ติแฟกต์เหล่านี้จะไม่ทริกเกอร์การสร้างใหม่ โค้ดนี้ใช้สำหรับข้อมูลสแตมป์บิลด์โดยเฉพาะ เราไม่ต้องการ สร้างใหม่เพียงเพราะเวลาปัจจุบันเปลี่ยนไป
ไม่มีเหตุผลพื้นฐานว่าเหตุใดอาร์ติแฟกต์ต้นฉบับจึงเป็นสิ่งประดิษฐ์แบบต้นไม้หรืออาร์ติแฟกต์ Symlink ที่ยังไม่ได้แก้ไข เพียงแต่เรายังไม่ได้ติดตั้งใช้งานดังกล่าว (อย่างไรก็ตาม เราควร การอ้างอิงไดเรกทอรีแหล่งที่มาในไฟล์ BUILD
เป็นหนึ่งในปัญหาความไม่ถูกต้องที่ทราบกันมายาวนานของ Bazel เรามีการติดตั้งใช้งานประเภทที่พร็อพเพอร์ตี้ BAZEL_TRACK_SOURCE_DIRECTORIES=1
JVM เปิดใช้งานอยู่)
Artifact
ที่มีชื่อเสียงคือคนกลาง ตัวแปรดังกล่าวจะบ่งชี้โดยอินสแตนซ์ Artifact
ที่เป็นเอาต์พุตของ MiddlemanAction
พวกเขาจะคุ้นเคยกับ
กรณีพิเศษบางอย่าง เช่น
- การรวมคนกลางใช้ในการจัดกลุ่มอาร์ติแฟกต์เข้าด้วยกัน ดังนั้นถ้ามีการทำงานจำนวนมากที่ใช้อินพุตชุดเดียวกัน เราจะไม่มีขอบอ้างอิง N*M จะมีเพียง N+M เท่านั้น (แทนที่ด้วยชุดที่ซ้อนกัน)
- การกำหนดเวลาคนกลางของทรัพยากร Dependency ช่วยให้เกิดการดำเนินการก่อนการดำเนินการอื่น
ส่วนใหญ่จะใช้สำหรับการวิเคราะห์โค้ด แต่ใช้สำหรับการคอมไพล์ C++ ด้วย (ดูคำอธิบายที่
CcCompilationContext.createMiddleman()
) - ตัวกลางของ Runfiles ใช้สำหรับตรวจสอบว่ามีโครงสร้าง Runfiles แบบต้นไม้ โดยที่โครงสร้างนี้ไม่จำเป็นต้องขึ้นอยู่กับไฟล์ Manifest ของเอาต์พุตและอาร์ติแฟกต์เดี่ยวทั้งหมดที่อ้างอิงโดยโครงสร้างของ Runfiles
เราเข้าใจการดำเนินการที่ดีที่สุดว่าเป็นคำสั่งที่ต้องเรียกใช้ สภาพแวดล้อมที่จำเป็นต้องใช้ และชุดเอาต์พุตที่สร้างขึ้น ต่อไปนี้คือองค์ประกอบหลักของ คำอธิบายการกระทำ
- บรรทัดคำสั่งที่ต้องเรียกใช้
- อาร์ติแฟกต์อินพุตที่ต้องใช้
- ตัวแปรสภาพแวดล้อมที่ต้องตั้งค่า
- คำอธิบายประกอบที่อธิบายสภาพแวดล้อม (เช่น แพลตฟอร์ม) ที่จำเป็นต้องใช้ใน \
นอกจากนี้ยังมีกรณีพิเศษอื่นๆ อีกเล็กน้อย เช่น การเขียนไฟล์ที่ Bazel เป็นที่รู้จัก ซึ่งเป็นคลาสย่อยของ AbstractAction
การดำเนินการส่วนใหญ่เป็น SpawnAction
หรือ StarlarkAction
(เช่นเดียวกัน ไม่ควรแยกกันเป็นคลาส) แม้ว่า Java และ C++ จะมีประเภทการดำเนินการของตนเอง (JavaCompileAction
, CppCompileAction
และ CppLinkAction
)
ท้ายที่สุดแล้ว เราต้องการย้ายทุกอย่างไปที่ SpawnAction
ซึ่ง JavaCompileAction
ใกล้เคียงกันมาก แต่ C++ เป็นกรณีพิเศษเล็กน้อยเนื่องจากการแยกวิเคราะห์ไฟล์ .d และรวมการสแกนด้วย
กราฟการดำเนินการส่วนมากจะ "ฝัง" ลงในกราฟ Skyframe โดยในเชิงแนวคิดแล้ว การดำเนินการของการดำเนินการจะแสดงเป็นการเรียกใช้ ActionExecutionFunction
มีคำอธิบายการแมปจาก Edge Dependency ของกราฟการดำเนินการไปยัง Edge Dependency ของ Skyframe แล้วใน ActionExecutionFunction.getInputDeps()
และ Artifact.key()
และจะมีการเพิ่มประสิทธิภาพเล็กน้อยเพื่อไม่ให้มีจำนวนขอบของ Skyframe ต่ำ ดังนี้
- อาร์ติแฟกต์ที่ได้รับไม่มี
SkyValue
เป็นของตัวเอง แต่จะใช้Artifact.getGeneratingActionKey()
เพื่อค้นหาคีย์ของการดำเนินการที่สร้างคีย์ดังกล่าวขึ้นมาแทน - ชุดที่ซ้อนกันจะมีคีย์ Skyframe ของตัวเอง
การดำเนินการที่แชร์
การดำเนินการบางอย่างสร้างขึ้นจากเป้าหมายที่กําหนดค่าไว้หลายรายการ กฎ Starlark จะจํากัดมากกว่า เนื่องจากได้รับอนุญาตให้ใส่การดําเนินการที่ได้ลงในไดเรกทอรีที่ระบุโดยการกําหนดค่าและแพ็กเกจเท่านั้น (แต่ถึงอย่างนั้นกฎในแพ็กเกจเดียวกันก็อาจขัดแย้งได้) แต่กฎที่นำมาใช้ใน Java สามารถวางอาร์ติแฟกต์ที่ดึงมาไว้ที่ใดก็ได้
การทำเช่นนี้ถือว่าเป็นคุณลักษณะที่ไม่ถูกต้อง แต่การลบออกนั้นทำได้ยากจริงๆ เนื่องจากจะช่วยลดเวลาในการดำเนินการกับไฟล์ได้อย่างมาก เช่น เมื่อไฟล์ต้นฉบับต้องได้รับการประมวลผลด้วยวิธีใดก็ตาม และไฟล์นั้นอ้างอิงด้วยกฎหลายข้อ (แฮนด์เวฟ-แฮนด์เวฟ) ต้องใช้ RAM บางส่วน: แต่ละอินสแตนซ์ของการดำเนินการที่แชร์จะต้องจัดเก็บในหน่วยความจำแยกต่างหาก
หากมีการดำเนินการ 2 อย่างที่สร้างไฟล์เอาต์พุตเดียวกัน การดำเนินการทั้ง 2 อย่างจะต้องเหมือนกันทุกประการ ซึ่งได้แก่ มีอินพุตเหมือนกัน เอาต์พุตเดียวกัน และเรียกใช้บรรทัดคำสั่งเดียวกัน ระบบใช้ความสัมพันธ์ที่เทียบเท่านี้ใน Actions.canBeShared()
และได้รับการยืนยันระหว่างขั้นตอนการวิเคราะห์และการดำเนินการโดยดูที่การดำเนินการทุกรายการ
มีการติดตั้งใช้งานใน SkyframeActionExecutor.findAndStoreArtifactConflicts()
และเป็นหนึ่งในไม่กี่แห่งใน Bazel ที่ต้องมีมุมมอง "ทั่วโลก" ของบิลด์
ระยะดำเนินการ
ซึ่งเป็นเวลาที่ Bazel เริ่มเรียกใช้การดำเนินการบิลด์ เช่น คำสั่งที่สร้างเอาต์พุต
สิ่งแรกที่ Bazel ทำหลังจากช่วงการวิเคราะห์คือการระบุว่าต้องสร้างอาร์ติแฟกต์ใดบ้าง ตรรกะของการดำเนินการนี้มีการเข้ารหัสใน TopLevelArtifactHelper
กล่าวคือเป็น filesToBuild
ของเป้าหมายที่กำหนดค่าในบรรทัดคำสั่งและเนื้อหาของกลุ่มเอาต์พุตพิเศษเพื่อวัตถุประสงค์ที่ชัดเจนในการแสดง "หากเป้าหมายนี้อยู่ในบรรทัดคำสั่ง ให้สร้างอาร์ติแฟกต์เหล่านี้"
ขั้นตอนถัดไปคือการสร้างรูทการดำเนินการ เนื่องจาก Bazel มีตัวเลือกในการอ่านแพ็กเกจแหล่งที่มาจากตำแหน่งต่างๆ ในระบบไฟล์ (--package_path
) ระบบไฟล์จึงต้องมอบการทำงานที่ดำเนินการในเครื่องพร้อมด้วยแผนผังแหล่งที่มาที่สมบูรณ์ คลาส SymlinkForest
จะจัดการโดยการบันทึกเป้าหมายทั้งหมดที่ใช้ในขั้นตอนการวิเคราะห์ และสร้างโครงสร้างไดเรกทอรี 1 รายการที่เชื่อมโยงแพ็กเกจทุกรายการกับเป้าหมายที่ใช้จากตำแหน่งจริง อีกวิธีหนึ่งคือการส่งเส้นทางที่ถูกต้องไปยังคำสั่ง (โดยพิจารณา --package_path
)
ซึ่งไม่เป็นที่ต้องการเนื่องจาก
- เครื่องมือนี้จะเปลี่ยนบรรทัดคำสั่งการดำเนินการเมื่อมีการย้ายแพ็กเกจจากรายการเส้นทางแพ็กเกจไปยังรายการอื่น (เหตุการณ์ที่เกิดขึ้นบ่อย)
- ซึ่งทำให้เกิดบรรทัดคำสั่งที่แตกต่างกันหากการดำเนินการนั้นทำจากระยะไกล เมื่อเทียบกับการทำงานภายในเครื่อง
- ซึ่งต้องมีการเปลี่ยนรูปแบบบรรทัดคำสั่งเฉพาะสำหรับเครื่องมือที่ใช้งาน (พิจารณาความแตกต่างระหว่างเส้นทาง เช่น classpaths ของ Java และ C++ include path)
- การเปลี่ยนบรรทัดคำสั่งของการดำเนินการจะทำให้รายการแคชการดำเนินการใช้ไม่ได้
--package_path
เลิกใช้งานอย่างช้าๆ และต่อเนื่อง
จากนั้น Bazel จะเริ่มข้ามผ่านกราฟการดำเนินการ (กราฟแบบ 2 ส่วนที่มีทิศทางตรงซึ่งประกอบไปด้วยการดำเนินการต่างๆ รวมถึงอาร์ติแฟกต์อินพุตและเอาต์พุต) รวมถึงการดำเนินการที่ทำงานอยู่
การดำเนินการของแต่ละการดำเนินการจะแสดงด้วยอินสแตนซ์ของคลาส SkyValue
ActionExecutionValue
เนื่องจากการดำเนินการมีค่าใช้จ่ายสูง เราจึงมีการแคช 2-3 เลเยอร์ซึ่งสามารถชนหลัง Skyframe ดังนี้
ActionExecutionFunction.stateMap
มีข้อมูลที่ทำให้ Skyframe เริ่มต้นใหม่ ของActionExecutionFunction
โดยใช้ราคาถูก- แคชการดำเนินการในเครื่องมีข้อมูลเกี่ยวกับสถานะของระบบไฟล์
- ระบบการดำเนินการระยะไกลมักจะมีแคชของตัวเองด้วย
แคชการดำเนินการในเครื่อง
แคชนี้เป็นอีกเลเยอร์หนึ่งที่อยู่เบื้องหลัง Skyframe แม้ว่าจะมีการเรียกใช้อีกครั้งใน Skyframe แต่ก็ยังคงเป็น Hit ในแคชการดำเนินการในเครื่อง โดยแสดงถึงสถานะของระบบไฟล์ในเครื่องและมีการเรียงอันดับไปยังดิสก์ ซึ่งหมายความว่าเมื่อเริ่มเซิร์ฟเวอร์ Bazel ใหม่ ผู้ใช้รายหนึ่งจะได้รับ Hit แคชการดำเนินการในเครื่องแม้ว่ากราฟ Skyframe จะว่างเปล่า
แคชนี้มีการตรวจสอบ Hit โดยใช้เมธอด
ActionCacheChecker.getTokenIfNeedToExecute()
ต่างจากชื่อของเครื่องมือนี้ตรงที่เป็นแผนที่ซึ่งเริ่มจากเส้นทางของอาร์ติแฟกต์ที่ดึงมาไปจนถึงการดำเนินการที่ปล่อยวัตถุนั้นออกมา การทำงานมีคำอธิบายดังนี้
- ชุดของไฟล์อินพุตและเอาต์พุต รวมถึงการตรวจสอบข้อผิดพลาด
- "คีย์การดำเนินการ" ซึ่งมักจะเป็นบรรทัดคำสั่งที่ทำงาน แต่โดยทั่วไปคือทุกอย่างที่ผลรวมตรวจสอบของไฟล์อินพุตไม่ได้บันทึกไว้ (เช่น สำหรับ
FileWriteAction
จะเป็นผลรวมตรวจสอบของข้อมูลที่เขียน)
นอกจากนี้ยังมี "แคชการดำเนินการจากด้านบน" รุ่นทดลองขั้นสูงที่ยังอยู่ระหว่างการพัฒนา ซึ่งใช้แฮชแบบสับเปลี่ยนเพื่อหลีกเลี่ยงการไปที่แคชหลายครั้ง
การค้นพบอินพุตและการตัดอินพุต
การดำเนินการบางอย่างอาจซับซ้อนกว่ามีเพียงชุดอินพุต การเปลี่ยนแปลงชุดอินพุตของการดำเนินการมี 2 รูปแบบ ดังนี้
- การดำเนินการอาจค้นพบอินพุตใหม่ก่อนที่จะดำเนินการ หรือตัดสินใจว่าอินพุตบางอย่างไม่จำเป็นจริงๆ ตัวอย่าง Canonical คือ C++ ซึ่งควรคาดเดาอย่างมีหลักการว่าไฟล์ส่วนหัวที่ไฟล์ C++ ใช้จากการปิดแบบสกรรมกริยา ซึ่งในปัจจุบันควรจะใช้การคาดเดาอย่างมีหลักการว่าไฟล์ส่วนหัวใดในไฟล์ C++ ที่ใช้จากการปิดแบบสกรรม เพื่อที่เราไม่ต้องสนใจส่งไฟล์ทุกไฟล์ไปยังโปรแกรมสั่งการระยะไกล ดังนั้นเราจึงมีตัวเลือกที่จะไม่ลงทะเบียนไฟล์ส่วนหัวทุกไฟล์เป็น "อินพุต" แต่สแกนไฟล์ต้นทางสำหรับคำสั่งที่รวมไว้แบบโอนถ่ายเท่านั้น" เพื่อทำเครื่องหมายไฟล์ส่วนหัวที่นำมาใช้เป็นคำสั่ง
#include
- การดำเนินการอาจทราบว่าไม่มีการใช้ไฟล์บางไฟล์ในระหว่างการดำเนินการ ใน C++ จะเรียกสิ่งนี้ว่า "ไฟล์ .d" คอมไพเลอร์จะบอกว่ามีการใช้ไฟล์ส่วนหัวใดหลังจากเกิดเหตุ และเพื่อหลีกเลี่ยงความน่าอายที่ Bazel จะได้ประโยชน์นี้มากขึ้น ซึ่งให้การประมาณได้ดีกว่าเครื่องสแกนแบบรวม เพราะต้องใช้คอมไพเลอร์
โดยนำการเปลี่ยนแปลงเหล่านี้ไปใช้โดยใช้เมธอดในการดำเนินการ ดังนี้
- เรียกว่า
Action.discoverInputs()
ซึ่งควรแสดงชุดอาร์ติแฟกต์ที่ฝังอยู่ซึ่งพิจารณาแล้วว่าจำเป็น โดยต้องเป็นอาร์ติแฟกต์ต้นทางเพื่อไม่ให้มี Edge Dependency ในกราฟการดำเนินการที่ไม่มีค่าเทียบเท่าในกราฟเป้าหมายที่กำหนดค่าไว้ - การทำงานนี้จะดำเนินการโดยเรียกใช้
Action.execute()
- ในตอนท้ายของ
Action.execute()
การดำเนินการจะเรียกใช้Action.updateInputs()
เพื่อบอก Bazel ได้ว่าไม่จำเป็นต้องป้อนข้อมูลทั้งหมด ซึ่งอาจส่งผลให้เกิดบิลด์ส่วนเพิ่มที่ไม่ถูกต้องหากมีการรายงานว่าอินพุตที่ใช้แล้วไม่ได้ใช้
เมื่อแคชการดำเนินการส่ง Hit กลับมาในอินสแตนซ์ Action ใหม่ (เช่น สร้างขึ้นหลังจากรีสตาร์ทเซิร์ฟเวอร์) Bazel จะเรียกตัวเอง updateInputs()
เพื่อให้ชุดอินพุตแสดงผลลัพธ์ของการค้นหาอินพุตและตัดส่วนที่เสร็จสิ้นก่อนหน้านี้
การดำเนินการของ Starlark อาจใช้ประโยชน์จากพื้นที่ดังกล่าวเพื่อประกาศอินพุตบางส่วนเป็น "ไม่ได้ใช้งาน" โดยใช้อาร์กิวเมนต์ unused_inputs_list=
ของ ctx.actions.run()
วิธีต่างๆ ในการเรียกใช้การดำเนินการ: กลยุทธ์/ActionContexts
การดำเนินการบางอย่างเรียกใช้ได้หลายวิธี เช่น บรรทัดคำสั่งอาจทำงานภายในเครื่อง ภายในเครื่อง แต่ใช้ในแซนด์บ็อกซ์ประเภทต่างๆ หรือจากระยะไกลได้ แนวคิดที่รวมสิ่งนี้เข้าด้วยกันนี้เรียกว่า ActionContext
(หรือ Strategy
เนื่องจากเราประสบความสำเร็จได้เพียงครึ่งทางพร้อมกับการเปลี่ยนชื่อ...)
วงจรชีวิตของบริบทการดำเนินการมีดังนี้
- เมื่อระยะการดำเนินการเริ่มต้น ระบบจะถามอินสแตนซ์
BlazeModule
อินสแตนซ์การดำเนินการที่มี เหตุการณ์นี้จะเกิดขึ้นในเครื่องมือสร้างของExecutionTool
ประเภทบริบทการดำเนินการจะระบุโดยอินสแตนซ์Class
ของ Java ที่อ้างถึงอินเทอร์เฟซย่อยของActionContext
และอินเทอร์เฟซที่ต้องมีบริบทการดำเนินการ - เลือกบริบทการดำเนินการที่เหมาะสมจากรายการที่มีอยู่ แล้วส่งต่อไปยัง
ActionExecutionContext
และBlazeExecutor
- การดำเนินการขอบริบทโดยใช้
ActionExecutionContext.getContext()
และBlazeExecutor.getStrategy()
(จริงๆ แล้วควรมีเพียงวิธีเดียวที่จะทำได้...)
กลยุทธ์ต่างๆ มีอิสระที่จะเรียกกลยุทธ์อื่นๆ มาทำงานของตน เช่น นำมาใช้ในกลยุทธ์แบบไดนามิกที่เริ่มต้นการดำเนินการทั้งในเครื่องและจากระยะไกล จากนั้นจะใช้การดำเนินการอย่างใดอย่างหนึ่งที่เสร็จสิ้นก่อน
กลยุทธ์ที่โดดเด่นหนึ่งคือกลยุทธ์ที่นำกระบวนการสำหรับผู้ปฏิบัติงานอย่างต่อเนื่อง
(WorkerSpawnStrategy
) แนวคิดคือเครื่องมือบางอย่างมีระยะเวลาเริ่มต้นใช้งานที่ยาวนาน ดังนั้นจึงควรนำกลับมาใช้ใหม่ระหว่างการทำงานต่างๆ แทนที่จะต้องเริ่มใหม่ทั้งหมดสำหรับทุกๆ การดำเนินการ (แสดงถึงปัญหาความถูกต้องที่อาจเกิดขึ้น เนื่องจาก Bazel เชื่อคำมั่นสัญญาในกระบวนการของผู้ปฏิบัติงานว่าไม่มีสถานะที่สังเกตได้ระหว่างคำขอแต่ละรายการ)
หากเครื่องมือมีการเปลี่ยนแปลง จำเป็นต้องรีสตาร์ทกระบวนการของผู้ปฏิบัติงาน การกำหนดว่าจะให้ผู้ปฏิบัติงานนำผู้ปฏิบัติงานไปใช้ซ้ำได้หรือไม่นั้นโดยการคำนวณ Checksum สำหรับเครื่องมือที่ใช้โดยใช้ WorkerFilesHash
ซึ่งต้องอาศัยการทราบว่าอินพุตของการดำเนินการใดเป็นส่วนของเครื่องมือและอินพุตใดเป็นอินพุต ซึ่งกำหนดโดยผู้สร้างการดำเนินการ: Spawn.getToolFiles()
และการเรียกใช้ไฟล์ของ Spawn
นับเป็นส่วนหนึ่งของเครื่องมือ
ข้อมูลเพิ่มเติมเกี่ยวกับกลยุทธ์ (หรือบริบทการดำเนินการ)
- ดูข้อมูลเกี่ยวกับกลยุทธ์ต่างๆ สำหรับการดำเนินการที่ทำงานอยู่ได้ที่นี่
- ข้อมูลเกี่ยวกับกลยุทธ์แบบไดนามิก ซึ่งเราดำเนินการทั้งในเครื่องและระยะไกลเพื่อดูการดำเนินการขั้นสุดท้ายที่นี่
- ดูข้อมูลเกี่ยวกับความซับซ้อนของการดำเนินการภายในเครื่องได้ที่นี่
ผู้จัดการทรัพยากรภายใน
Bazel สามารถเรียกใช้การดำเนินการหลายรายการพร้อมกันได้ จำนวนการดำเนินการในเครื่องที่ควรเรียกใช้พร้อมกันจะแตกต่างไปตามการดำเนินการคือ ยิ่งต้องใช้ทรัพยากรมากเท่าไร อินสแตนซ์ก็ควรทำงานพร้อมกันน้อยลงเพื่อหลีกเลี่ยงไม่ให้เครื่องของผู้ใช้ทำงานหนักเกินไป
มีการนำไปใช้ในคลาส ResourceManager
โดยการดำเนินการแต่ละรายการต้องมีคำอธิบายประกอบด้วยค่าประมาณของทรัพยากรในเครื่องที่จำเป็นต้องใช้ในรูปแบบของอินสแตนซ์ ResourceSet
(CPU และ RAM) จากนั้นเมื่อบริบทการดำเนินการทำบางสิ่งที่ต้องใช้ทรัพยากรในเครื่อง บริบทเหล่านั้นจะเรียกใช้ ResourceManager.acquireResources()
และจะถูกบล็อกจนกว่าจะมีทรัพยากรที่จำเป็นพร้อมใช้งาน
ดูคำอธิบายโดยละเอียดเกี่ยวกับการจัดการทรัพยากรในพื้นที่ได้ที่นี่
โครงสร้างของไดเรกทอรีเอาต์พุต
การดำเนินการแต่ละอย่างต้องมีตำแหน่งแยกต่างหากในไดเรกทอรีเอาต์พุต ซึ่งจะมีการวางเอาต์พุตไว้ โดยปกติแล้ว ตำแหน่งของอาร์ติแฟกต์ที่ดึงมาจะเป็นดังนี้
$EXECROOT/bazel-out/<configuration>/bin/<package>/<artifact name>
ชื่อของไดเรกทอรีที่เชื่อมโยงกับการกำหนดค่าเฉพาะหนึ่งๆ ได้รับการกำหนดอย่างไร พร็อพเพอร์ตี้ที่ต้องการมี 2 อย่างที่ขัดแย้งกัน ดังนี้
- หากการกำหนดค่า 2 รายการเกิดขึ้นได้ในเวอร์ชันเดียวกัน ทั้งสองควรมีไดเรกทอรีที่ต่างกันเพื่อให้ทั้งสองเวอร์ชันมีการดำเนินการเดียวกันได้ ไม่เช่นนั้น หากการกำหนดค่าทั้ง 2 รายการมีความเห็นไม่ตรงกัน เช่น บรรทัดคำสั่งของการดำเนินการที่สร้างไฟล์เอาต์พุตเดียวกัน Bazel จะไม่รู้ว่าควรเลือกการดำเนินการใด ("ความขัดแย้งของการดำเนินการ")
- หากการกำหนดค่า 2 รายการแสดงถึงสิ่งเดียวกัน "คร่าวๆ" ก็ควรจะมีชื่อเดียวกัน เพื่อให้การดำเนินการในรายการหนึ่งสามารถใช้ซ้ำสำหรับอีกรายการได้หากบรรทัดคำสั่งตรงกัน เช่น การเปลี่ยนแปลงตัวเลือกบรรทัดคำสั่งกับคอมไพเลอร์ Java ไม่ควรส่งผลให้มีการเรียกใช้คอมไพล์ C++ ซ้ำ
จนถึงตอนนี้ เรายังไม่ได้คิดวิธีหลักในการแก้ปัญหานี้ ซึ่งมีความคล้ายคลึงกับปัญหาการตัดการกำหนดค่า คุณสามารถดูการสนทนาเพิ่มเติมเกี่ยวกับตัวเลือกต่างๆ ได้ที่นี่ ประเด็นปัญหาหลักๆ คือกฎของ Starlark (ที่ผู้เขียนมักจะไม่คุ้นเคยกับ Bazel มาก่อน) และแง่มุมต่างๆ ซึ่งจะเพิ่มมิติให้กับพื้นที่ของสิ่งต่างๆ ที่สามารถสร้างไฟล์เอาต์พุต "เดียวกัน" ได้
แนวทางปัจจุบันคือกลุ่มเส้นทางสำหรับการกำหนดค่าคือ <CPU>-<compilation mode>
โดยมีการเพิ่มคำต่อท้ายที่หลากหลายเพื่อให้การเปลี่ยนการกำหนดค่าที่ใช้ใน Java ไม่ส่งผลให้เกิดความขัดแย้งของการดำเนินการ นอกจากนี้ จะมีการเพิ่มการตรวจสอบผลรวมการเปลี่ยนการกำหนดค่า Starlark เพื่อให้ผู้ใช้ไม่ทำให้เกิดความขัดแย้งของการดำเนินการ อะไรๆ ก็ไม่ได้สมบูรณ์แบบ ซึ่งดำเนินการได้ใน OutputDirectories.buildMnemonic()
และอาศัย Fragment การกำหนดค่าแต่ละรายการที่เพิ่มส่วนของตัวเองลงในชื่อของไดเรกทอรีเอาต์พุต
การทดสอบ
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 และ stderr ไม่ได้แยกกันtest.outputs
ซึ่งเป็น "ไดเรกทอรีเอาต์พุตที่ไม่ได้ประกาศ" ใช้โดยการทดสอบที่ต้องการส่งออกไฟล์นอกเหนือจากสิ่งที่พิมพ์ไปยังเทอร์มินัล
แต่ในระหว่างดำเนินการทดสอบ จะไม่สามารถเกิดขึ้นระหว่างการสร้างเป้าหมายปกติได้ 2 อย่าง ได้แก่ การดำเนินการทดสอบพิเศษและการสตรีมเอาต์พุต
การทดสอบบางรายการต้องดำเนินการในโหมดพิเศษ เช่น ไม่ได้ควบคู่กับการทดสอบอื่นๆ ซึ่งทำได้โดยการเพิ่ม tags=["exclusive"]
ลงในกฎการทดสอบหรือเรียกใช้การทดสอบด้วย --test_strategy=exclusive
การทดสอบพิเศษแต่ละรายการจะเรียกใช้โดยการเรียกใช้ Skyframe แยกกัน ซึ่งจะขอให้ดำเนินการทดสอบหลังบิลด์ "หลัก" ซึ่งนำมาใช้ใน SkyframeExecutor.runExclusiveTest()
ผู้ใช้สามารถขอให้สตรีมเอาต์พุตของการทดสอบเพื่อให้ผู้ใช้ได้รับข้อมูลเกี่ยวกับความคืบหน้าของการทดสอบที่ใช้เวลานาน ซึ่งต่างจากการดำเนินการปกติที่เอาต์พุตเทอร์มินัลจะถูกทิ้งเมื่อการดำเนินการสิ้นสุดลง ข้อมูลนี้ระบุโดยตัวเลือกบรรทัดคำสั่ง --test_output=streamed
และบอกเป็นนัยถึงการดำเนินการทดสอบพิเศษเพื่อไม่ให้เอาต์พุตของการทดสอบที่แตกต่างกันกระจัดกระจาย
ซึ่งดำเนินการในคลาส StreamedTestOutput
ที่มีชื่อเหมาะสมและทำงานด้วยการสำรวจการเปลี่ยนแปลงไปยังไฟล์ test.log
ของการทดสอบที่เป็นปัญหาและถ่ายโอนไบต์ใหม่ไปยังเทอร์มินัลที่มีกฎของ Bazel
ผลการทดสอบที่ดำเนินการแล้วจะแสดงบนรถบัสของกิจกรรมโดยการสังเกตเหตุการณ์ต่างๆ (เช่น TestAttempt
, TestResult
หรือ TestingCompleteEvent
)
โดยการทดสอบเหล่านี้จะถูกถ่ายโอนไปยังโปรโตคอล Build Event และถูกส่งไปยังคอนโซลภายใน AggregatingTestListener
การรวบรวมการครอบคลุม
รายงานการครอบคลุมจะรายงานโดยการทดสอบในรูปแบบ LCOV ในไฟล์
bazel-testlogs/$PACKAGE/$TARGET/coverage.dat
ในการรวบรวมการครอบคลุม การดำเนินการทดสอบแต่ละครั้งจะรวมไว้ในสคริปต์ที่เรียกว่า collect_coverage.sh
สคริปต์นี้จะตั้งค่าสภาพแวดล้อมของการทดสอบเพื่อเปิดใช้คอลเล็กชันการครอบคลุมและระบุว่ารันไทม์การครอบคลุมจะเขียนไฟล์การครอบคลุมไว้ที่ใด จากนั้นทำการทดสอบ การทดสอบอาจเรียกใช้กระบวนการย่อยหลายกระบวนการและประกอบด้วยส่วนต่างๆ ที่เขียนขึ้นในภาษาโปรแกรมต่างๆ หลายภาษา (โดยมีรันไทม์ของคอลเล็กชันที่ครอบคลุมแยกต่างหาก) สคริปต์ Wrapper มีหน้าที่แปลงไฟล์ผลลัพธ์เป็นรูปแบบ LCOV หากจำเป็น และผสานรวมเป็นไฟล์เดียว
การแทรกของ collect_coverage.sh
ดำเนินการโดยกลยุทธ์การทดสอบและกำหนดให้ collect_coverage.sh
เป็นอินพุตของการทดสอบ การดำเนินการนี้เกิดขึ้นโดยแอตทริบิวต์โดยนัย :coverage_support
ซึ่งได้รับการแก้ไขเป็นค่าแฟล็กการกำหนดค่า --coverage_support
(ดู TestConfiguration.TestOptions.coverageSupport
)
บางภาษามีการวัดคุมแบบออฟไลน์ ซึ่งหมายความว่ามีการเพิ่มเครื่องมือการครอบคลุมในเวลาคอมไพล์ (เช่น C++) และภาษาอื่นๆ ที่ใช้ทางออนไลน์ ซึ่งหมายความว่าระบบจะเพิ่มการวัดคุมการครอบคลุมในเวลาดำเนินการ
แนวคิดหลักอีกอย่างหนึ่งคือความครอบคลุมของเกณฑ์พื้นฐาน นี่คือการครอบคลุมของไลบรารี ไบนารี
หรือการทดสอบหากไม่มีการเรียกใช้โค้ด ปัญหาที่จะแก้ไขได้คือหากคุณต้องการคำนวณการครอบคลุมการทดสอบสำหรับไบนารี จะไม่เพียงพอสำหรับการรวมความครอบคลุมของการทดสอบทั้งหมดเนื่องจากอาจมีโค้ดในไบนารีที่ไม่ได้ลิงก์กับการทดสอบใดๆ ดังนั้น สิ่งที่เราทำคือการปล่อยไฟล์การครอบคลุมของไบนารีทุกไฟล์ ซึ่งมีเฉพาะไฟล์ที่เรารวบรวมให้โดยไม่มีบรรทัดที่ครอบคลุม ไฟล์การครอบคลุมของเกณฑ์พื้นฐานสำหรับเป้าหมายอยู่ที่
bazel-testlogs/$PACKAGE/$TARGET/baseline_coverage.dat
นอกจากนี้ ยังสร้างขึ้นสำหรับไบนารีและไลบรารีนอกเหนือจากการทดสอบ หากคุณส่งแฟล็ก --nobuild_tests_only
ไปยัง Bazel
ไม่ครอบคลุมเกณฑ์พื้นฐานในขณะนี้
เราติดตามไฟล์ 2 กลุ่มสำหรับการรวบรวมการครอบคลุมสำหรับแต่ละกฎ ได้แก่ ชุดไฟล์ที่มีการวัดคุมและชุดไฟล์ข้อมูลเมตาการใช้เครื่องมือ
ชุดของไฟล์ที่มีเครื่องมือเป็นเพียงชุดของไฟล์ที่จะใช้ สำหรับรันไทม์ของบริการออนไลน์ สามารถนำโค้ดนี้ไปใช้ในขณะรันไทม์เพื่อตัดสินใจว่าจะส่งไฟล์ใด นอกจากนี้ยังใช้เพื่อสร้างการครอบคลุมของเกณฑ์พื้นฐานด้วย
ชุดของไฟล์ข้อมูลเมตาการใช้เครื่องมือคือชุดไฟล์พิเศษที่การทดสอบต้องใช้ในการสร้างไฟล์ LCOV ที่ Bazel ต้องใช้ ในทางปฏิบัติ ไฟล์จะประกอบด้วยไฟล์ที่ระบุรันไทม์ เช่น gcc จะปล่อยไฟล์ .gcno ระหว่างการคอมไพล์ ข้อมูลนี้จะถูกเพิ่มลงในชุดอินพุตของการดำเนินการทดสอบหากเปิดใช้โหมดการครอบคลุม
ไม่ว่าจะมีการรวบรวมการครอบคลุมใน BuildConfiguration
หรือไม่ สิ่งนี้มีประโยชน์เพราะเป็นวิธีง่ายๆ ในการเปลี่ยนการดำเนินการทดสอบและกราฟการดำเนินการโดยอาศัยบิตนี้ แต่นั่นก็หมายความว่าหากมีการพลิกบิตนี้ จะต้องมีการวิเคราะห์เป้าหมายทั้งหมดอีกครั้ง (บางภาษา เช่น C++ ต้องใช้ตัวเลือกคอมไพเลอร์ที่แตกต่างกันในการปล่อยโค้ดที่สามารถรวบรวมการครอบคลุม
ซึ่งจะช่วยลดปัญหานี้ได้หลังจากนั้น ต้องมีการวิเคราะห์อีกครั้ง)
ไฟล์สนับสนุนการครอบคลุมจะขึ้นอยู่กับป้ายกำกับแบบพึ่งพิงโดยนัยเพื่อให้นโยบายคำขอลบล้างได้ ซึ่งทำให้ไฟล์สนับสนุนของ Bazel เวอร์ชันต่างๆ แตกต่างกัน ทางที่ดีที่สุดคือเราควรนำความแตกต่างเหล่านี้ออก และเราได้มาตรฐานอย่างใดอย่างหนึ่ง
นอกจากนี้ เรายังสร้าง "รายงานการครอบคลุม" ซึ่งรวมการครอบคลุมที่รวบรวมสำหรับการทดสอบทุกรายการในการเรียกใช้ Bazel เข้าด้วยกัน ซึ่งจัดการโดย CoverageReportActionFactory
และเรียกจาก BuildView.createResult()
และเข้าถึงเครื่องมือที่ต้องการได้จากการดูแอตทริบิวต์ :coverage_report_generator
ของการทดสอบแรกที่ดำเนินการ
เครื่องมือ Query
Bazel มีภาษาเล็กน้อยในการถามถึงสิ่งต่างๆ เกี่ยวกับกราฟ ประเภทการสืบค้นที่มีให้มีดังนี้
- ใช้
bazel query
ในการตรวจสอบกราฟเป้าหมาย - ใช้
bazel cquery
เพื่อตรวจสอบกราฟเป้าหมายที่กำหนดค่าไว้ - ใช้
bazel aquery
เพื่อตรวจสอบกราฟการดำเนินการ
โดยแต่ละประเภทจะใช้คลาสย่อย AbstractBlazeQueryEnvironment
ใช้ฟังก์ชันการค้นหาเพิ่มเติมได้ด้วยคลาสย่อย QueryFunction
หากต้องการอนุญาตให้สตรีมผลการค้นหา แทนที่จะรวบรวมผลลัพธ์ไปยังโครงสร้างข้อมูลบางอย่าง ระบบจะส่ง query2.engine.Callback
ไปยัง QueryFunction
ซึ่งเรียกใช้ผลการค้นหาที่ต้องการแสดงผล
ผลลัพธ์ของการค้นหาอาจมีการส่งออกได้หลายวิธี เช่น ป้ายกำกับ, ป้ายกำกับและคลาสกฎ, XML, protobuf และอื่นๆ ซึ่งใช้งานเป็นคลาสย่อยของ OutputFormatter
ข้อกำหนดย่อยของรูปแบบเอาต์พุตการค้นหาบางรูปแบบ (แต่ Pro จริงๆ นะ) คือ Bazel ต้องปล่อยข้อมูล _all _ทั้งหมดที่การโหลดแพ็กเกจมีให้ เพื่อให้ A สามารถแยกผลลัพธ์และพิจารณาว่าเป้าหมายหนึ่งๆ มีการเปลี่ยนแปลงหรือไม่ เพราะเหตุนี้ ค่าแอตทริบิวต์ต้องอยู่ต่อเนื่องกัน ทำให้มีประเภทแอตทริบิวต์เพียงไม่กี่ประเภทที่ไม่มีแอตทริบิวต์ที่มีค่า 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 ออกแบบมาให้ใช้แบบ Monorepo (ต้นไม้แหล่งเดียวที่มีทุกอย่างที่ต้องใช้ในการสร้าง) Bazel ก็อาศัยอยู่ในโลกที่สิ่งนี้ไม่จริงเสมอไป "ที่เก็บภายนอก" เป็นนามธรรมที่ใช้ในการเชื่อมโลกทั้ง 2 อย่างนี้ เพราะเป็นโค้ดที่จำเป็นสำหรับบิลด์ แต่ไม่ได้อยู่ในผังแหล่งที่มาหลัก
ไฟล์ WORKSPACE
ชุดของที่เก็บภายนอกจะกำหนดโดยการแยกวิเคราะห์ไฟล์ WORKSPACE เช่น การประกาศในลักษณะนี้
local_repository(name="foo", path="/foo/bar")
ผลลัพธ์ในที่เก็บที่เรียกว่า @foo
พร้อมใช้งาน ปัญหานี้อาจมีความซับซ้อนเนื่องจากผู้ใช้สามารถกำหนดกฎที่เก็บใหม่ในไฟล์ Starlark ซึ่งสามารถนำไปใช้เพื่อโหลดโค้ด Starlark ใหม่ ซึ่งสามารถใช้เพื่อกำหนดกฎที่เก็บใหม่ และอื่นๆ...
ในการจัดการกรณีนี้ การแยกวิเคราะห์ไฟล์ WORKSPACE (ใน WorkspaceFileFunction
) จะแบ่งออกเป็นส่วนๆ ตามคำสั่ง load()
ดัชนีกลุ่มจะระบุด้วย WorkspaceFileKey.getIndex()
และคำนวณ WorkspaceFileFunction
จนกว่าดัชนี X จะหมายถึงการประเมินจนถึงคำสั่ง load()
ที่ X
กำลังดึงข้อมูลที่เก็บ
ก่อนที่ Bazel จะเข้าถึงโค้ดของที่เก็บได้ คุณต้องfetched ซึ่งทำให้ Bazel สร้างไดเรกทอรีภายใต้ $OUTPUT_BASE/external/<repository name>
การดึงข้อมูลที่เก็บมีขั้นตอนดังนี้
PackageLookupFunction
ทราบว่าตนเองต้องมีที่เก็บและสร้างRepositoryName
เป็นSkyKey
ซึ่งเรียกใช้RepositoryLoaderFunction
RepositoryLoaderFunction
ส่งต่อคำขอไปยังRepositoryDelegatorFunction
ด้วยเหตุผลที่ไม่ชัดเจน (โค้ดระบุว่าจะหลีกเลี่ยงการดาวน์โหลดสิ่งต่างๆ ซ้ำในกรณีที่ Skyframe รีสตาร์ท แต่นั่นไม่ใช่เหตุผลที่ยากมาก)RepositoryDelegatorFunction
จะค้นหากฎที่เก็บที่ระบบจะขอให้ดึงข้อมูลโดยทำซ้ำส่วนของไฟล์ WORKSPACE จนกว่าจะพบที่เก็บที่ขอ- พบ
RepositoryFunction
ที่เหมาะสมซึ่งใช้การดึงข้อมูลที่เก็บ โดยอาจเป็นการใช้งาน Starlark ของที่เก็บดังกล่าวหรือแมปแบบฮาร์ดโค้ดสำหรับที่เก็บที่ใช้งานใน Java
การแคชหลายเลเยอร์เพราะการดึงข้อมูลที่เก็บอาจมีราคาแพงมาก เช่น
- มีแคชสำหรับไฟล์ที่ดาวน์โหลดซึ่งกำหนดโดยการตรวจสอบข้อผิดพลาด (
RepositoryCache
) การดำเนินการนี้กำหนดให้ไฟล์ตรวจสอบข้อผิดพลาดต้องแสดงอยู่ในไฟล์ WORKSPACE แต่วิธีนี้ก็จะมีผลดีเพื่อให้เกิดความแตกต่าง ระบบจะแชร์อินสแตนซ์นี้โดยอินสแตนซ์ของเซิร์ฟเวอร์ Bazel ทั้งหมดบนเวิร์กสเตชันเดียวกัน ไม่ว่าอินสแตนซ์นั้นกำลังทำงานอยู่ในพื้นที่ทำงานหรือฐานเอาต์พุตใดก็ตาม - มีการเขียน "ไฟล์เครื่องหมาย" สำหรับที่เก็บแต่ละรายการใน
$OUTPUT_BASE/external
ที่มี checksum ของกฎที่ใช้เพื่อดึงข้อมูล ถ้าเซิร์ฟเวอร์ Bazel รีสตาร์ท แต่ checksum ไม่เปลี่ยนแปลง จะไม่มีการดึงข้อมูลนั้นอีกครั้ง ซึ่งนำไปใช้ในRepositoryDelegatorFunction.DigestWriter
- ตัวเลือกบรรทัดคำสั่ง
--distdir
จะกำหนดแคชอื่นที่ใช้ค้นหาอาร์ติแฟกต์ที่จะดาวน์โหลด วิธีนี้เป็นประโยชน์ในการตั้งค่าขององค์กร ซึ่ง Bazel ไม่ควรดึงข้อมูลแบบสุ่มจากอินเทอร์เน็ต ซึ่งดำเนินการโดยDownloadManager
เมื่อดาวน์โหลดที่เก็บแล้ว ระบบจะถือว่าอาร์ติแฟกต์ในนั้นเป็นอาร์ติแฟกต์ต้นทาง ซึ่งทำให้เกิดปัญหาเนื่องจากโดยปกติแล้ว Bazel จะตรวจสอบเวอร์ชันล่าสุดของอาร์ติแฟกต์ต้นทางโดยการเรียกใช้ stat() ในอาร์ติแฟกต์ และอาร์ติแฟกต์เหล่านี้ไม่ถูกต้องเช่นกันเมื่อคำจำกัดความของที่เก็บมีการเปลี่ยนแปลง ดังนั้น FileStateValue
สำหรับอาร์ติแฟกต์ในที่เก็บภายนอกจึงต้องขึ้นอยู่กับที่เก็บภายนอก ซึ่งจัดการโดย ExternalFilesHelper
ไดเรกทอรีที่มีการจัดการ
บางครั้งที่เก็บภายนอกต้องแก้ไขไฟล์ภายใต้รูทของพื้นที่ทำงาน (เช่น ตัวจัดการแพ็กเกจที่เก็บแพ็กเกจที่ดาวน์โหลดในไดเรกทอรีย่อยของแผนผังต้นทาง) ซึ่งไม่สอดคล้องกับสมมติฐานที่ Bazel กำหนดให้ไฟล์ต้นฉบับได้รับการแก้ไขโดยผู้ใช้เท่านั้น ไม่ใช่ตัวมันเอง และอนุญาตให้แพ็กเกจอ้างอิงทุกไดเรกทอรีภายใต้รูทของพื้นที่ทำงาน เพื่อให้ที่เก็บภายนอกแบบนี้ใช้งานได้ บาเซล ทำ 2 อย่างคือ
- อนุญาตให้ผู้ใช้ระบุไดเรกทอรีย่อยของพื้นที่ทำงาน Bazel ที่ไม่ได้รับอนุญาตให้เข้าถึง โดยจะแสดงไว้ในไฟล์ชื่อ
.bazelignore
และมีการใช้ฟังก์ชันดังกล่าวในBlacklistedPackagePrefixesFunction
- เราเข้ารหัสการแมปจากไดเรกทอรีย่อยของพื้นที่ทำงานไปยังที่เก็บภายนอกซึ่งมีการจัดการใน
ManagedDirectoriesKnowledge
และจัดการFileStateValue
ที่อ้างถึงไดเรกทอรีดังกล่าวในลักษณะเดียวกับที่เก็บภายนอกทั่วไป
การแมปที่เก็บ
ซึ่งเกิดขึ้นได้เพราะที่เก็บหลายแหล่งต้องการอ้างอิงที่เก็บเดียวกัน แต่ใช้คนละเวอร์ชัน (นี่คืออินสแตนซ์ของ "ปัญหาการพึ่งพาไดมอนด์") ตัวอย่างเช่น ถ้าไบนารี 2 แบบในที่เก็บที่แยกต่างหากในบิลด์ต้องการขึ้นอยู่กับ Guava ทั้ง 2 กลุ่มจะอ้างอิง Guava ด้วยป้ายกำกับที่เริ่มตั้งแต่ @guava//
และคาดว่าจะหมายถึงเวอร์ชันที่แตกต่างกัน
ดังนั้น Bazel จะอนุญาตให้ 1 แมปป้ายกำกับที่เก็บภายนอกอีกครั้งเพื่อให้สตริง @guava//
อ้างถึงที่เก็บ Guava 1 รายการ (เช่น @guava1//
) ในที่เก็บของไบนารีหนึ่งและที่เก็บ Guava อีกที่เก็บหนึ่ง (เช่น @guava2//
)
หรืออาจใช้เพื่อjoinเพชรได้ด้วย หากที่เก็บขึ้นอยู่กับ @guava1//
และอีกที่เก็บหนึ่งใช้ @guava2//
การแมปที่เก็บจะอนุญาตให้ที่เก็บ 1 รายการแมปที่เก็บทั้งสองอีกครั้งเพื่อใช้ที่เก็บ @guava//
ตามรูปแบบบัญญัติ
การจับคู่ระบุในไฟล์ WORKSPACE เป็นแอตทริบิวต์ repo_mapping
ของการกำหนดที่เก็บแต่ละรายการ จากนั้นภาพดังกล่าวจะปรากฏใน Skyframe ในฐานะสมาชิกของ WorkspaceFileValue
โดยมีการต่อเข้ากับองค์ประกอบต่อไปนี้
Package.Builder.repositoryMapping
ซึ่งใช้เปลี่ยนรูปแบบแอตทริบิวต์ที่มีค่าป้ายกำกับของกฎในแพ็กเกจโดยRuleClass.populateRuleAttributeValues()
Package.repositoryMapping
ซึ่งใช้ในขั้นตอนการวิเคราะห์ (สำหรับแก้ปัญหาสิ่งต่างๆ เช่น$(location)
ซึ่งไม่ได้แยกวิเคราะห์ในขั้นตอนการโหลด)BzlLoadFunction
สำหรับการแก้ไขป้ายกำกับในคำสั่งload()
บิตของ JNI
เซิร์ฟเวอร์ของ Bazel ส่วนใหญ่_เขียนด้วย Java ยกเว้นส่วนที่ Java ไม่สามารถทำได้ด้วยตัวเองหรือทำได้เองเมื่อนำมาใช้งาน โดยส่วนใหญ่แล้วจะจำกัดเฉพาะการโต้ตอบกับระบบไฟล์ การควบคุมกระบวนการ และข้อมูลอื่นๆ อีกมากมาย
โค้ด C++ จะอยู่ภายใต้ src/main/native และคลาส Java ที่มีเมธอดแบบเนทีฟ ได้แก่
NativePosixFiles
และNativePosixFileSystem
ProcessUtils
WindowsFileOperations
และWindowsFileProcesses
com.google.devtools.build.lib.platform
เอาต์พุตของคอนโซล
การปล่อยเอาต์พุตคอนโซลดูเหมือนจะไม่ใช่เรื่องง่าย แต่การมาบรรจบกับการเรียกใช้หลายกระบวนการ (บางครั้งทำจากระยะไกล) การแคชแบบละเอียด ความต้องการที่จะมีเอาต์พุตเทอร์มินัลที่ดีและมีสีสัน และการมีเซิร์ฟเวอร์ที่ทำงานเป็นเวลานานก็ทำให้การทำงานกลายเป็นเรื่องไม่สำคัญ
หลังจากที่เรียก RPC มาจากไคลเอ็นต์ ระบบจะสร้างอินสแตนซ์ RpcOutputStream
2 รายการ (สำหรับ Stdout และ Stderr) ซึ่งจะส่งต่อข้อมูลที่พิมพ์ไปยังไคลเอ็นต์ จากนั้นจะนำไปรวมไว้ในคู่ OutErr
(an (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 ทาง ได้แก่
- รถบัสสำหรับกิจกรรม
- สตรีมเหตุการณ์ได้เชื่อมโยงเข้ามาผ่าน "ผู้รายงาน"
การเชื่อมต่อโดยตรงเพียงอย่างเดียวที่เครื่องจักรปฏิบัติการด้วยคำสั่ง (เช่น ส่วนอื่นๆ ของ Bazel) ได้เชื่อมต่อกับสตรีม RPC ไปยังไคลเอ็นต์คือผ่าน Reporter.getOutErr()
ซึ่งทำให้สามารถเข้าถึงสตรีมเหล่านี้ได้โดยตรง โดยจะใช้ก็ต่อเมื่อคำสั่งจำเป็นต้องถ่ายโอนข้อมูลไบนารีที่เป็นไปได้จำนวนมาก (เช่น bazel query
)
กำลังทำโปรไฟล์ Bazel
Bazel เร็ว Bazel ก็ทำงานช้าเช่นกัน เพราะงานสร้างมีแนวโน้มที่จะเติบโตจนถึง
ขอบของสิ่งที่ทนได้ ด้วยเหตุนี้ Bazel จึงมีเครื่องมือสร้างโปรไฟล์ที่สามารถใช้สร้างโปรไฟล์งานสร้างและตัว Bazel ได้ ใช้งานในชั้นเรียนที่ชื่อ Profiler
ได้อย่างเหมาะสม ตัวเลือกนี้เปิดอยู่โดยค่าเริ่มต้นแม้ว่าจะบันทึกเฉพาะข้อมูลฉบับย่อเพื่อให้รับทราบค่าใช้จ่ายส่วนเกินได้ บรรทัดคำสั่ง --record_full_profiler_data
ทำให้บันทึกทุกอย่างเท่าที่ทำได้
โดยจะส่งโปรไฟล์ในรูปแบบเครื่องมือสร้างโปรไฟล์ของ Chrome ซึ่งดูดีที่สุดใน Chrome โมเดลข้อมูลเป็นของสแต็กงาน ผู้ใช้จะเริ่มต้นงานและสิ้นสุดงานได้ ซึ่งจะซ้อนกันไว้ภายในอย่างเป็นระเบียบ เทรด Java แต่ละรายการจะมี สแต็กงานของตัวเอง TODO: สิ่งนี้ทำงานร่วมกับการกระทำและสไตล์การส่งเนื้อหาแบบต่อเนื่องอย่างไร
เครื่องมือสร้างโปรไฟล์เริ่มต้นและหยุดใน BlazeRuntime.initProfiler()
และ BlazeRuntime.afterCommand()
ตามลำดับ และพยายามเผยแพร่ให้นานที่สุดเท่าที่จะเป็นไปได้เพื่อให้เราทำโปรไฟล์ทุกอย่างได้ หากต้องการเพิ่มข้อมูลในโปรไฟล์
ให้โทรหา Profiler.instance().profile()
และจะแสดง Closeable
โดยที่การปิดโปรไฟล์หมายถึงมีการสิ้นสุดงาน โดยจะทำงานได้ดีที่สุดกับคำสั่ง "ลองกับทรัพยากร"
นอกจากนี้ เรายังทําโปรไฟล์หน่วยความจำเบื้องต้นใน MemoryProfiler
ด้วย นอกจากนี้ยังเปิดใช้อยู่เสมอ
และส่วนใหญ่จะบันทึกขนาดฮีปสูงสุดและลักษณะการทำงาน GC
กำลังทดสอบ Bazel
บาเซลมีการทดสอบ 2 ประเภทหลักๆ ได้แก่ การทดสอบที่สังเกตบาเซลเป็น "กล่องดำ" และการทดสอบที่ทำเฉพาะช่วงการวิเคราะห์เท่านั้น เราเรียกการทดสอบแบบผสานรวมว่า "การทดสอบการผสานรวม" และ "การทดสอบ 1 หน่วย" แบบหลัง แม้ว่าการทดสอบเหล่านี้จะดูเหมือนการทดสอบการผสานรวมซึ่งผสานรวมน้อยกว่า นอกจากนี้เรายังมีการทดสอบหน่วยจริง ที่จำเป็นอีกด้วย
การทดสอบการผสานรวมมี 2 ประเภท ได้แก่
- องค์กรนำไปใช้โดยใช้เฟรมเวิร์กการทดสอบ Bash อันซับซ้อนภายใต้
src/test/shell
- เวอร์ชันที่ติดตั้งใช้งานใน Java ตัวแปรเหล่านี้ใช้งานเป็นคลาสย่อยของ
BuildIntegrationTestCase
BuildIntegrationTestCase
เป็นเฟรมเวิร์กการทดสอบการผสานรวมที่แนะนำเนื่องจากพร้อมรองรับสถานการณ์การทดสอบส่วนใหญ่ เนื่องจากเป็นเฟรมเวิร์ก Java จึงมีความสามารถในการแก้ไขข้อบกพร่องและการผสานรวมกับเครื่องมือการพัฒนาทั่วไปจำนวนมากได้อย่างราบรื่น ในที่เก็บ Bazel มีตัวอย่างคลาส BuildIntegrationTestCase
จำนวนมาก
ใช้งานการทดสอบการวิเคราะห์เป็นคลาสย่อยของ BuildViewTestCase
มีระบบไฟล์ Scratch ที่คุณใช้เขียนไฟล์ BUILD
ได้ ตลอดจนวิธีการต่างๆ ของผู้ช่วยเหลือสามารถขอเป้าหมายที่กำหนดค่า เปลี่ยนการกำหนดค่า และยืนยันสิ่งต่างๆ เกี่ยวกับผลการวิเคราะห์ได้