เอกสารนี้จะอธิบายฐานของโค้ดและโครงสร้างของ Bazel ทั้งนี้ มีไว้สำหรับผู้ที่ต้องการมีส่วนร่วมกับ Bazel ไม่ใช่สำหรับผู้ใช้ปลายทาง
บทนำ
ฐานของโค้ดของ Bazel มีขนาดใหญ่ (รหัสการผลิตประมาณ 350KLOC และการทดสอบ KLOC ประมาณ 260 รายการ และไม่มีใครคุ้นเคยกับภาพรวมทั้งหมด ทุกคนรู้ว่า หุบเขาหนึ่งๆ ได้เป็นอย่างดี แต่มีไม่กี่คนเท่านั้นที่รู้ว่ามีอะไรอยู่เหนือเนินเขาในทุกที่ เส้นทางการเรียนรู้
เพื่อให้ผู้คนที่อยู่ระหว่างการเดินทางไม่ต้องค้นหาตัวเอง ป่าที่มืดมิดด้วยเส้นทางที่เรียบง่ายนี้หายไป เอกสารนี้พยายาม แสดงภาพรวมของฐานของโค้ดเพื่อเริ่มต้นใช้งานได้ง่ายขึ้น กำลังดำเนินการอยู่
ซอร์สโค้ดของ 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++ (
"client") ได้รับการสนับสนุน ช่วยตั้งค่ากระบวนการของเซิร์ฟเวอร์ที่เหมาะสมโดยใช้
ขั้นตอนต่อไปนี้
- ตรวจสอบว่ามีการแยกตัวเองแล้วหรือไม่ แต่หากไม่เป็นเช่นนั้น ช่วงเวลานี้ คือที่มาของการติดตั้งใช้งานเซิร์ฟเวอร์
- ตรวจสอบว่ามีอินสแตนซ์เซิร์ฟเวอร์ที่ใช้งานอยู่ซึ่งทำงานอยู่หรือไม่
จะมีตัวเลือกเริ่มต้นที่เหมาะสมและใช้ไดเรกทอรีพื้นที่ทำงานที่เหมาะสม ทั้งนี้
ค้นหาเซิร์ฟเวอร์ที่ทำงานอยู่ โดยดูที่ไดเรกทอรี
$OUTPUT_BASE/server
ซึ่งมีไฟล์ล็อกกับพอร์ตที่เซิร์ฟเวอร์กำลังฟังอยู่ - ปิดกระบวนการของเซิร์ฟเวอร์เก่าหากจำเป็น
- หากจำเป็น ให้เริ่มกระบวนการของเซิร์ฟเวอร์ใหม่
หลังจากกระบวนการของเซิร์ฟเวอร์ที่เหมาะสมพร้อมแล้ว คำสั่งที่จำเป็นต้องเรียกใช้คือ
สื่อสารกับคอมพิวเตอร์ผ่านอินเทอร์เฟซ gRPC จากนั้นระบบจะ
ส่งเอาต์พุตของ Bazel กลับคืน
เครื่องชำระเงิน เรียกใช้คำสั่งได้ครั้งละ 1 รายการเท่านั้น นี่คือ
โดยใช้กลไกการล็อกที่ซับซ้อนด้วยชิ้นส่วนต่างๆ ใน C++ และชิ้นส่วนต่างๆ ใน
Java มีโครงสร้างพื้นฐานสำหรับเรียกใช้คำสั่งหลายรายการพร้อมกัน
เนื่องจากเรียกใช้ bazel version
พร้อมกันกับคำสั่งอื่นไม่ได้
ค่อนข้างน่าอาย ตัวบล็อกหลักคือวงจรชีวิตของ BlazeModule
และบางรัฐใน BlazeRuntime
เมื่อสิ้นสุดคำสั่ง เซิร์ฟเวอร์ Bazel จะส่งโค้ดสำหรับออกที่ไคลเอ็นต์
ควรกลับมา รอยย่นที่น่าสนใจคือการใช้ bazel run
:
งานของคำสั่งนี้คือเรียกใช้สิ่งที่ Bazel เพิ่งสร้างขึ้น แต่ดำเนินการดังกล่าวไม่ได้
จากกระบวนการของเซิร์ฟเวอร์เนื่องจากไม่มีเทอร์มินัล แทนที่เราจะบอก
ไคลเอ็นต์คือไบนารีใดที่ควร exec()
และมีอาร์กิวเมนต์ใด
เมื่อกด Ctrl-C ไคลเอ็นต์จะแปลเป็นปุ่ม "ยกเลิก" ใน gRPC ซึ่งจะพยายามสิ้นสุดคำสั่งโดยเร็วที่สุด หลังจาก Ctrl-C ที่สามให้ไคลเอ็นต์ส่ง 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 แนบกับ
Workspace เขียนด้วย ฐานเอาต์พุตแต่ละรายการมีอินสแตนซ์เซิร์ฟเวอร์ Bazel สูงสุด 1 รายการ
แสดงเมื่อใดก็ได้ โดยทั่วไปอุณหภูมิจะอยู่ที่ $OUTPUT_USER_ROOT/<checksum of the path
to the workspace>
คุณจะเปลี่ยนได้โดยใช้ตัวเลือกการเริ่มต้น --output_base
ซึ่งมีประโยชน์ในการหลบเลี่ยงข้อจำกัดที่
อินสแตนซ์ Bazel 1 รายการจะทํางานในพื้นที่ทำงานใดก็ได้ในเวลาหนึ่งๆ
ไดเรกทอรีเอาต์พุตจะมีสิ่งต่อไปนี้ด้วย
- ที่เก็บภายนอกที่ดึงข้อมูลที่
$OUTPUT_BASE/external
- รูทของ exec ซึ่งเป็นไดเรกทอรีที่มีลิงก์สัญลักษณ์ไปยังแหล่งที่มาทั้งหมด
สำหรับบิลด์ปัจจุบัน ซึ่งตั้งอยู่ที่
$OUTPUT_BASE/execroot
ระหว่าง บิลด์ ไดเรกทอรีที่ใช้งานได้คือ$EXECROOT/<name of main repository>
เราวางแผนที่จะเปลี่ยนค่านี้เป็น$EXECROOT
ในระยะยาว เพราะเป็นการเปลี่ยนแปลงที่เข้ากันไม่ได้ - ไฟล์ที่สร้างขึ้นระหว่างการสร้าง
กระบวนการเรียกใช้คำสั่ง
เมื่อเซิร์ฟเวอร์ Bazel ได้ควบคุมและได้รับแจ้งเกี่ยวกับคำสั่งที่จำเป็นแล้ว จะดำเนินการตามลำดับเหตุการณ์ต่อไปนี้
BlazeCommandDispatcher
ได้รับแจ้งเกี่ยวกับคำขอใหม่แล้ว ตัดสินใจเลือก คำสั่งต้องใช้พื้นที่ทำงานหรือไม่ (เกือบทุกคำสั่งยกเว้น สำหรับรหัสที่ไม่มีส่วนเกี่ยวข้องกับซอร์สโค้ด เช่น เวอร์ชันหรือ ความช่วยเหลือ) และดูว่ามีคำสั่งอื่นทำงานอยู่หรือไม่พบคำสั่งที่ถูกต้อง แต่ละคําสั่งต้องใช้อินเทอร์เฟซ
BlazeCommand
และต้องมีคำอธิบายประกอบ@Command
(นี่เป็น แพทเทิร์น: คงจะดีถ้าเราต้องใช้ข้อมูลเมตาทั้งหมดที่คำสั่งต้องการ อธิบายโดยใช้วิธีการในBlazeCommand
)ตัวเลือกบรรทัดคำสั่งจะได้รับการแยกวิเคราะห์ แต่ละคำสั่งมีบรรทัดคำสั่งแตกต่างกัน อื่นๆ ซึ่งอธิบายไว้ในคำอธิบายประกอบ
@Command
มีการสร้างรถโดยสารสำหรับกิจกรรม รถโดยสารสำหรับงานอีเวนต์จะเป็นช่องสำหรับจัดกิจกรรมที่เกิดขึ้น ระหว่างการสร้าง โดยบางส่วนจะส่งออกไปนอก Bazel ภายใต้ หลักของโปรโตคอลเหตุการณ์การสร้างเพื่อบอกคนทั่วโลกถึงวิธีการสร้าง ไป
โดยคำสั่งจะได้รับการควบคุม คำสั่งที่น่าสนใจที่สุดคือคำสั่งที่เรียกใช้ สร้าง: สร้าง ทดสอบ เรียกใช้ ครอบคลุม และอื่นๆ: ฟังก์ชันการทำงานนี้ ติดตั้งใช้งานโดย
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
ไม่พร้อมให้บริการในตอนนี้ สำหรับข้อมูลเพิ่มเติม โปรดดู
ส่วน "Configurations"
คำเตือน: เราเหมือนจะสมมติว่าอินสแตนซ์ OptionsBase
นั้นเปลี่ยนแปลงไม่ได้และ
ใช้วิธีการดังกล่าว (เช่น ส่วนหนึ่งของ SkyKeys
) โดยไม่เป็นเช่นนั้นและ
การดัดแปลงโมเดลเป็นวิธีที่ยอดเยี่ยมในการทำลายบาเซลด้วยวิธีที่แยบยลซึ่งทำได้ยาก
เพื่อแก้ไขข้อบกพร่อง น่าเสียดายที่การทำให้กลยุทธ์เหล่านี้เปลี่ยนแปลงไม่ได้นั้นต้องอาศัยความพยายามอย่างมาก
(การแก้ไข FragmentOptions
ทันทีหลังจากการก่อสร้างก่อนมีผู้ใดโดยเฉพาะ
จะมีโอกาสได้เก็บการอ้างอิงไว้ และก่อนวันที่ equals()
หรือ hashCode()
จะ
ก็ไม่มีปัญหา)
Bazel ได้เรียนรู้เกี่ยวกับคลาสตัวเลือกด้วยวิธีต่อไปนี้
- บางรุ่นต่อสายเข้ากับ Bazel (
CommonCommandOptions
) - จากคำอธิบายประกอบ
@Command
ในคำสั่ง Bazel แต่ละรายการ - จาก
ConfiguredRuleClassProvider
(ตัวเลือกเหล่านี้เกี่ยวข้องกับตัวเลือกบรรทัดคำสั่ง สำหรับภาษาโปรแกรมแต่ละภาษา) - กฎของ Starlark ยังกำหนดตัวเลือกของตัวเองด้วย (โปรดดู ที่นี่)
แต่ละตัวเลือก (ไม่รวมตัวเลือกที่กำหนดโดย Starlark) เป็นตัวแปรสมาชิกของ
คลาสย่อย FragmentOptions
ที่มีคำอธิบายประกอบ @Option
ซึ่งระบุ
ชื่อและประเภทของตัวเลือกบรรทัดคำสั่ง พร้อมกับข้อความช่วยเหลือบางอย่าง
ประเภท Java ของค่าของตัวเลือกบรรทัดคำสั่งมักจะเป็นแบบง่ายๆ
(เช่น สตริง จำนวนเต็ม บูลีน ป้ายกำกับ ฯลฯ) อย่างไรก็ตาม เรายังสนับสนุน
ประเภทที่ซับซ้อนขึ้น ในกรณีนี้ งานในการแปลงจาก
บรรทัดคำสั่งของประเภทข้อมูลเป็นประเภท
การติดตั้งใช้งาน
com.google.devtools.common.options.Converter
ต้นกำเนิดอย่างที่ Bazel เห็น
Bazel ทำธุรกิจการสร้างซอฟต์แวร์ ซึ่งเกิดขึ้นโดยการอ่านและ เป็นการแปลรหัสต้นฉบับ จำนวนซอร์สโค้ดทั้งหมดของ Bazel ที่ดำเนินการ มีชื่อว่า "พื้นที่ทำงาน" และมีโครงสร้างเป็นที่เก็บ แพ็กเกจ และ กฎ
ที่เก็บ
"ที่เก็บ" เป็นแผนผังแหล่งที่มาที่นักพัฒนาซอฟต์แวร์ทำงาน โดยปกติ แสดงโปรเจ็กต์เดียว บรรพบุรุษของ Bazel ชื่อ Blaze ซึ่งทำงานโดยใช้ระบบ Monorepo ซึ่งก็คือแผนผังแหล่งที่มาเดียวที่มีซอร์สโค้ดทั้งหมดที่ใช้ในการเรียกใช้บิลด์ ในทางกลับกัน Bazel สนับสนุนโปรเจ็กต์ที่มีซอร์สโค้ดครอบคลุมหลายรหัส ที่เก็บได้ ที่เก็บที่มีการเรียกใช้ Bazel เรียกว่า "main ที่เก็บอื่นๆ เรียกว่า "ที่เก็บภายนอก"
ที่เก็บมีการทำเครื่องหมายโดยไฟล์ขอบเขตที่เก็บ (MODULE.bazel
, REPO.bazel
หรือ
ในบริบทเดิม WORKSPACE
หรือ WORKSPACE.bazel
) ในไดเรกทอรีราก
ที่เก็บหลักคือแผนผังต้นทางที่คุณใช้เรียกใช้ Bazel ที่เก็บภายนอก
ได้รับการนิยามไว้หลากหลายวิธี ดูทรัพยากร Dependency ภายนอก
ภาพรวมสำหรับข้อมูลเพิ่มเติม
โค้ดของที่เก็บภายนอกมีการลิงก์หรือดาวน์โหลดภายใต้
$OUTPUT_BASE/external
ขณะเรียกใช้บิลด์ ต้นไม้ต้นทางทั้งหมดต้องนำมารวมกัน นี้
ดำเนินการโดย SymlinkForest
ซึ่งจะลิงก์ทุกแพ็กเกจในที่เก็บหลัก
ไปยัง $EXECROOT
และที่เก็บภายนอกทั้งหมดไปยัง $EXECROOT/external
หรือ
$EXECROOT/..
แพ็กเกจ
ที่เก็บทั้งหมดประกอบด้วยแพ็กเกจ คอลเล็กชันของไฟล์ที่เกี่ยวข้อง และ
ข้อกำหนดของทรัพยากร Dependency ข้อมูลเหล่านี้ระบุโดยไฟล์ชื่อ
BUILD
หรือ BUILD.bazel
หากมีทั้งคู่ Bazel จะใช้ BUILD.bazel
เหตุผล
ทำไมไฟล์ BUILD
ยังได้รับการยอมรับก็คือ Blaze ซึ่งเป็นบรรพบุรุษของ Bazel ใช้ไฟล์นี้
ชื่อไฟล์ แต่กลายเป็นกลุ่มเส้นทางที่ใช้กันโดยทั่วไป โดยเฉพาะอย่างยิ่ง
บน Windows ซึ่งชื่อไฟล์ไม่คำนึงถึงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่
แพ็กเกจไม่ได้เกี่ยวข้องกัน: การเปลี่ยนแปลงในไฟล์ BUILD
ของแพ็กเกจ
ไม่สามารถทำให้แพ็กเกจอื่นๆ เปลี่ยนแปลง การเพิ่มหรือนำไฟล์ BUILD
รายการออก
_can _change แพ็คเกจอื่น การเกิด glob เกิดขึ้นซ้ำๆ จะหยุดที่ขอบเขตของพัสดุ
ดังนั้นการมีไฟล์ BUILD
จะทำให้ระบบหยุดการเกิดซ้ำ
การประเมินไฟล์ BUILD
มีชื่อว่า "การโหลดแพ็กเกจ" ใช้แล้ว
ในชั้นเรียน PackageFactory
ทำงานโดยเรียกใช้ล่ามของ Starlark และ
ต้องใช้ความรู้เกี่ยวกับชุดคลาสของกฎที่มีอยู่ ผลลัพธ์ของแพ็กเกจ
การโหลดจะเป็นออบเจ็กต์ Package
ส่วนใหญ่เป็นแผนที่จากสตริง (ชื่อของ
เป้าหมาย) ไปยังตัวเป้าหมายเอง
ความซับซ้อนที่รวมมหาศาลในระหว่างการโหลดพัสดุก็คือ กลอบแซล: Bazel ไม่
กำหนดให้ไฟล์แหล่งที่มาทุกไฟล์ต้องมีการระบุไว้อย่างชัดเจน และสามารถเรียกใช้ glob ได้
(เช่น glob(["**/*.java"])
) วิธีนี้แตกต่างจาก Shell ตรงที่สนับสนุนการหมุนวนรอบที่
ลงไปในไดเรกทอรีย่อย (แต่ไม่ใช่แพ็กเกจย่อย) การดำเนินการนี้ต้องมีสิทธิ์เข้าถึง
ระบบไฟล์ และเนื่องจากอาจทำได้ช้า เราจึงใช้กลอุบายต่างๆ เพื่อ
ให้ทำงานไปพร้อมๆ กัน และมีประสิทธิภาพมากที่สุดเท่าที่จะทำได้
มีการใช้ Globbing ในชั้นเรียนต่อไปนี้
LegacyGlobber
ผู้เล่นดาวเคราะห์ Skyframe ที่ไม่รู้จักเร็วและร่าเริงSkyframeHybridGlobber
เวอร์ชันที่ใช้ Skyframe และเปลี่ยนกลับเป็น Globber แบบเดิมเพื่อหลีกเลี่ยงการ "Skyframe รีสตาร์ท" (อธิบายไว้ด้านล่าง)
คลาส Package
เองมีสมาชิกบางรายที่ใช้เฉพาะ
แยกวิเคราะห์ "ภายนอก" แพ็กเกจ (ที่เกี่ยวข้องกับทรัพยากร Dependency ภายนอก) และไม่ได้
แพ็กเกจที่ใช้ได้จริง นี่คือ
ข้อบกพร่องในการออกแบบเนื่องจากวัตถุที่อธิบายแพ็กเกจปกติไม่ควรมี
ที่อธิบายสิ่งอื่นๆ ซึ่งได้แก่
- การแมปที่เก็บ
- Toolchains ที่จดทะเบียน
- แพลตฟอร์มการดำเนินการที่ลงทะเบียนไว้
ตามหลักการแล้ว ควรใช้การแยกวิเคราะห์ "ภายนอก" มากกว่า พัสดุ
จากการแยกวิเคราะห์แพ็กเกจปกติเพื่อให้ Package
ไม่จำเป็นต้องให้บริการสำหรับ
ของทั้ง 2 อย่าง แต่น่าเสียดายที่การทำเช่นนี้เป็นเรื่องยาก เพราะทั้งสอง
เชื่อมโยงถึงกันได้อย่างลึกซึ้ง
ป้ายกำกับ เป้าหมาย และกฎ
แพ็กเกจประกอบด้วยเป้าหมายในประเภทต่อไปนี้
- ไฟล์: สิ่งต่างๆ ที่เป็นอินพุตหรือเอาต์พุตของบิลด์ ใน Bazel parlance เราเรียกว่าอาร์ติแฟกต์ (มีการพูดถึงที่อื่น) ไม่ใช่ทั้งหมด ไฟล์ที่สร้างขึ้นระหว่างการสร้างเป็นเป้าหมาย โดยทั่วไปสำหรับเอาต์พุต 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 ระหว่างคลาสกฎและเป้าหมาย
ของประเภทนั้นๆ
สกายเฟรม
กรอบการประเมินที่อยู่เบื้องหลัง Bazel เรียกว่า Skyframe โมเดลมีอยู่ว่า ทุกอย่างที่ต้องใช้ระหว่างการสร้างจะได้รับการจัดระเบียบให้อยู่ใน กราฟแบบวนซ้ำที่มีขอบชี้จากชิ้นข้อมูลใดก็ตามไปยังทรัพยากร Dependency ซึ่งก็คือข้อมูลชิ้นอื่นๆ ที่จำเป็นต้องทราบเพื่อที่จะสร้างขึ้นมา
โหนดในกราฟจะเรียกว่า SkyValue
และจะใช้ชื่อโหนดดังกล่าวว่า
SkyKey
วินาที ทั้ง 2 อย่างนี้จะเปลี่ยนแปลงไม่ได้ มีเพียงวัตถุที่เปลี่ยนแปลงไม่ได้เท่านั้นที่ควร
ที่เข้าถึงได้ ค่าคงที่นี้จะระงับไว้เกือบทุกครั้ง และในกรณีที่ไม่
(เช่น สำหรับคลาสตัวเลือกรายบุคคล BuildOptions
ซึ่งเป็นสมาชิกของ
BuildConfigurationValue
และSkyKey
) เราพยายามอย่างยิ่งที่จะไม่เปลี่ยนแปลง
หรือเปลี่ยนแปลงเฉพาะรูปแบบที่ไม่อาจสังเกตได้จากภายนอก
หลังจากนั้นทุกอย่างก็จะประมวลผลภายใน 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()
โดยมี
ผลข้างเคียงของการลงทะเบียนทรัพยากร Dependency เหล่านั้นลงในกราฟภายในของ Skyframe ดังนั้น
Skyframe จะรู้ว่าต้องประเมินฟังก์ชันนี้อีกครั้งเมื่อมีทรัพยากร Dependency ใดๆ
เปลี่ยน กล่าวคือ การแคชและการประมวลผลที่เพิ่มขึ้นของ Skyframe ทำงานที่
รายละเอียดของ SkyFunction
และ SkyValue
เมื่อใดก็ตามที่ SkyFunction
ขอทรัพยากร Dependency ที่ไม่พร้อมใช้งาน getValue()
จะแสดงผลเป็น Null ฟังก์ชันดังกล่าวควรตอบสนองการควบคุมกลับไปยัง Skyframe โดยทำตามขั้นตอนต่อไปนี้
ที่ส่งกลับค่า Null ในภายหลัง Skyframe จะประเมิน
ทรัพยากร Dependency ที่ไม่พร้อมใช้งาน จากนั้นรีสตาร์ทฟังก์ชันตั้งแต่ต้น - เฉพาะครั้งนี้
เวลาที่การเรียกใช้ getValue()
จะสำเร็จโดยมีผลลัพธ์ที่ไม่เป็น Null
นี่เป็นผลของการคำนวณทั้งหมดที่ดำเนินการภายใน SkyFunction
ก่อนรีสตาร์ท แต่ไม่รวมถึงงานที่ทำ
ประเมินทรัพยากร Dependency SkyValues
ซึ่งแคชไว้ ดังนั้น เราจึงมักจะทำงาน
เกี่ยวกับปัญหานี้โดย:
- การประกาศทรัพยากร Dependency เป็นกลุ่ม (โดยใช้
getValuesAndExceptions()
) เพื่อ จำกัดจำนวนการรีสตาร์ท - การแบ่ง
SkyValue
ออกเป็นชิ้นส่วนต่างๆ และคำนวณโดยค่าที่แตกต่างกันSkyFunction
เพื่อให้คำนวณและแคชได้อย่างอิสระ ช่วงเวลานี้ ควรทำอย่างมีกลยุทธ์ เนื่องจากจะช่วยเพิ่มหน่วยความจำ - สถานะการจัดเก็บระหว่างการรีสตาร์ท ไม่ว่าจะโดยใช้
SkyFunction.Environment.getState()
หรือเก็บแคชเฉพาะกิจ "เบื้องหลัง Skyframe" ด้วย SkyFunctions ที่ซับซ้อน การจัดการสถานะ ระหว่างการรีสตาร์ทอาจยุ่งยาก เปิดตัวStateMachine
สำหรับ แนวทางแบบโครงสร้างของการเกิดขึ้นพร้อมกันเชิงตรรกะ ซึ่งรวมถึงฮุกเพื่อระงับและ กลับไปใช้การคำนวณลำดับชั้นภายในSkyFunction
ต่อ ตัวอย่างDependencyResolver#computeDependencies
ใช้StateMachine
ที่มีgetState()
ในการประมวลผลชุดที่อาจมีขนาดใหญ่ ของทรัพยากร Dependency โดยตรงของเป้าหมายที่กำหนดค่า ซึ่งหากเป็นเช่นนั้น การรีสตาร์ทที่มีค่าใช้จ่ายสูง
โดยพื้นฐานแล้ว Bazel ต้องการวิธีแก้ไขปัญหาเฉพาะหน้าเหล่านี้
นับพันโหนด Skyframe บนเครื่องบินเป็นเรื่องปกติ และการรองรับของ Java
ชุดข้อความที่มีขนาดเล็กไม่ได้มีประสิทธิภาพดีกว่า
การใช้งาน StateMachine
ในปี 2023
สตาร์ลาร์ก
Starlark เป็นภาษาเฉพาะโดเมนที่ผู้คนใช้เพื่อกำหนดค่าและขยาย Bazel โดยถือว่าเป็นเซ็ตย่อยที่ถูกจำกัดของ Python ซึ่งมีประเภทน้อยกว่า มีข้อจำกัดมากขึ้นในการควบคุม และที่สำคัญที่สุดคือ การเปลี่ยนแปลงไม่ได้ รับประกันการเปิดอ่านพร้อมกัน ยังไม่เสร็จสมบูรณ์ของ Turing ไม่สนับสนุนให้ผู้ใช้บางส่วน (แต่ไม่ทั้งหมด) พยายาม งานเขียนโปรแกรมในภาษานั้นๆ
มีการใช้งาน Starlark ในแพ็กเกจ net.starlark.java
นอกจากนี้ยังมีการใช้ Go อย่างอิสระ
ที่นี่ ชวา
ที่ใช้ใน Bazel เป็นล่ามอยู่ในขณะนี้
Starlark มีการใช้ในบริบทต่างๆ ได้แก่
BUILD
ไฟล์ นี่คือที่ที่มีการกำหนดเป้าหมายบิลด์ใหม่ สตาร์ลาร์ก ที่โค้ดที่ทำงานในบริบทนี้มีสิทธิ์เข้าถึงเฉพาะเนื้อหาของBUILD
และไฟล์.bzl
ไฟล์- ไฟล์
MODULE.bazel
ในจุดนี้ ทรัพยากร Dependency ภายนอก กำหนดไว้ รหัส Starlark ที่เรียกใช้ในบริบทนี้มีการเข้าถึงที่จำกัดอย่างมาก กับคำสั่งที่กำหนดไว้ล่วงหน้า 2-3 คำสั่ง .bzl
ไฟล์ ตรงนี้คือที่ที่กฎการสร้าง กฎที่เก็บ และโมดูลใหม่ คือส่วนขยายที่กำหนดไว้ โค้ด Starlark ที่นี่สามารถกำหนดฟังก์ชันใหม่และโหลด จากไฟล์.bzl
อื่นๆ
ภาษาถิ่นที่ใช้กับไฟล์ BUILD
และ .bzl
มีความแตกต่างกันเล็กน้อย
เพราะแสดงออกถึงสิ่งต่างๆ แตกต่างกัน ดูรายการความแตกต่างได้
ที่นี่
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Starlark ได้ที่นี่
ระยะการโหลด/การวิเคราะห์
ขั้นตอนการโหลด/การวิเคราะห์คือจุดที่ Bazel พิจารณาว่าต้องดำเนินการใดบ้าง สร้างกฎหนึ่งๆ หน่วยพื้นฐานของหน่วยโฆษณาคือ "เป้าหมายที่กำหนดค่า" ซึ่งก็คือ คู่ (เป้าหมาย การกำหนดค่า) อย่างสมเหตุสมผล
ซึ่งเรียกว่า "ระยะการโหลด/การวิเคราะห์" เพราะสามารถแบ่งออกเป็น ส่วนต่างๆ ที่เคยเป็นแบบต่อเนื่องกัน แต่คราวนี้สามารถซ้อนทับกันได้เมื่อเวลาผ่านไป
- กำลังโหลดแพ็กเกจ คือการเปลี่ยนไฟล์
BUILD
รายการเป็นออบเจ็กต์Package
ที่แสดงถึงตน - การวิเคราะห์เป้าหมายที่กำหนดค่าแล้ว กล่าวคือใช้งานการติดตั้งใช้งาน กฎในการสร้างกราฟการดำเนินการ
เป้าหมายที่กำหนดค่าแต่ละรายการในการสับเปลี่ยนของเป้าหมายที่กำหนดค่าแล้ว ที่ขอในบรรทัดคำสั่งต้องมีการวิเคราะห์จากล่างขึ้นบน ซึ่งก็คือโหนดใบไม้ ก่อน แล้วจึงคลิกไปจนถึงรายการในบรรทัดคำสั่ง ข้อมูลสำหรับการวิเคราะห์ เป้าหมายที่กำหนดค่าแล้วเพียงรายการเดียว ได้แก่
- การกำหนดค่า ("วิธีการ" สร้างกฎ เช่น เป้าหมาย แต่ยังรวมถึงตัวเลือกบรรทัดคำสั่งที่ผู้ใช้ต้องการ ที่ส่งไปยังคอมไพเลอร์ C++)
- ทรัพยากร Dependency โดยตรง พบผู้ให้บริการข้อมูลทางอ้อม ลงในกฎที่วิเคราะห์ เราเรียกสิ่งนี้ว่าแบบนั้นเนื่องจากให้ "ภาพรวม" ในการปิดการรับส่งข้อมูลของการกำหนดค่า เช่น ไฟล์ .jar ทั้งหมดใน classpath หรือไฟล์ .o ทั้งหมดที่ ต้องลิงก์อยู่ในไบนารี C++)
- ตัวเป้าหมาย นี่เป็นผลมาจากการโหลดแพ็กเกจเป้าหมาย อยู่ใน สำหรับกฎ แอตทริบิวต์นี้จะรวมถึงแอตทริบิวต์ของกฎ ซึ่งมักจะเป็น เป็นสิ่งสำคัญ
- การใช้งานเป้าหมายที่กำหนดค่า สำหรับกฎ เกณฑ์นี้สามารถเป็น อยู่ใน Starlark หรือ Java มีการใช้เป้าหมายที่กำหนดค่าที่ไม่ใช่กฎทั้งหมด ใน Java
เอาต์พุตของการวิเคราะห์เป้าหมายที่กำหนดค่าไว้คือ
- ผู้ให้บริการข้อมูลทรานซิทีฟที่กำหนดค่าเป้าหมายซึ่งพึ่งพาข้อมูลดังกล่าวสามารถ เข้าถึง
- อาร์ติแฟกต์ที่ AI สร้างขึ้นได้และการดำเนินการที่สร้างอาร์ติแฟกต์เหล่านี้
API ที่เสนอให้กับกฎของ Java คือ RuleContext
ซึ่งเทียบเท่ากับ
อาร์กิวเมนต์ ctx
ของกฎ Starlark API ของ API มีประสิทธิภาพดีกว่า แต่ขณะเดียวกัน
จะช่วยให้ Bad ThingsTM ง่ายขึ้น เช่น การเขียนโค้ดที่มีเวลา หรือ
ความซับซ้อนของพื้นที่เป็นแบบกำลังสอง (หรือแย่กว่า) เพื่อทำให้เซิร์ฟเวอร์ Bazel เกิดขัดข้องโดยมี
ข้อยกเว้นของ Java หรือเพื่อละเมิดค่าคงที่ (เช่น ด้วยการแก้ไข
อินสแตนซ์ Options
หรือโดยการทำให้เป้าหมายที่กำหนดค่าเปลี่ยนแปลงได้)
อัลกอริทึมที่กำหนดทรัพยากร Dependency โดยตรงของเป้าหมายที่กำหนดค่า
อาศัยอยู่ใน DependencyResolver.dependentNodeMap()
การกำหนดค่า
การกำหนดค่าคือ ของการสร้างเป้าหมาย: สำหรับแพลตฟอร์มใด และ ตัวเลือกบรรทัดคำสั่ง ฯลฯ
เป้าหมายเดียวกันนั้นสร้างขึ้นสำหรับการกำหนดค่าหลายรายการในบิลด์เดียวกันได้ ช่วงเวลานี้ มีประโยชน์ ตัวอย่างเช่น เมื่อมีการใช้โค้ดเดียวกันสำหรับเครื่องมือที่ทำงานในระหว่าง บิลด์ และสำหรับโค้ดเป้าหมาย และเรากำลัง คอมไพล์แบบข้ามระบบ หรือเมื่อเรา สร้างแอป Android แบบอ้วน (แอปที่มีโค้ดแบบเนทีฟสำหรับ CPU หลายตัว สถาปัตยกรรม)
โดยหลักการแล้ว การกำหนดค่าคืออินสแตนซ์ BuildOptions
อย่างไรก็ตาม ใน
มี BuildOptions
รวมอยู่โดย BuildConfiguration
ที่ให้
ฟังก์ชันเล็กๆ น้อยๆ เพิ่มเติม โดยจะเริ่มจากด้านบนของ
กราฟการอ้างอิงที่ด้านล่าง หากมีการเปลี่ยนแปลง บิลด์จะต้อง
ได้รับการวิเคราะห์ซ้ำ
ซึ่งส่งผลให้เกิดความผิดปกติ เช่น ต้องวิเคราะห์งานสร้างใหม่ทั้งหมดหาก ตัวอย่างเช่น จำนวนการทดสอบที่ขอจะเปลี่ยนไป แม้ว่าจะมีเพียง ส่งผลต่อเป้าหมายทดสอบ (เรามีแผนที่จะ "ตัด" การกำหนดค่าเพื่อให้ ไม่ได้ แต่ยังไม่พร้อม)
เมื่อการใช้กฎต้องมีส่วนหนึ่งของการกำหนดค่า จะต้องประกาศ
ตามคำจำกัดความโดยใช้ RuleClass.Builder.requiresConfigurationFragments()
ที่ใช้เวลาเพียง 2 นาที เพื่อหลีกเลี่ยงข้อผิดพลาด (เช่น กฎ Python ที่ใช้ส่วน Java) และ
เพื่ออำนวยความสะดวกในการตัดการกำหนดค่า เช่น การเปลี่ยนตัวเลือก Python, C++
เป้าหมายจะไม่จำเป็นต้องมีการวิเคราะห์ใหม่
การกำหนดค่าของกฎไม่จำเป็นต้องเหมือนกับการกำหนดค่าของกฎสำหรับ "หลัก" กฎ กระบวนการเปลี่ยนการกำหนดค่าใน Dependency Edge เรียกว่า "configuration transition" (การเปลี่ยนการกำหนดค่า) ปัญหานี้อาจเกิดขึ้นจาก 2 ที่ ดังนี้
- อยู่ใน Dependency Edge การเปลี่ยนแปลงเหล่านี้มีการระบุไว้ใน
Attribute.Builder.cfg()
และเป็นฟังก์ชันจากRule
(โดยที่ฟังก์ชัน การเปลี่ยนแปลงเกิดขึ้น) และBuildOptions
(การกำหนดค่าเดิม) เป็น 1BuildOptions
หรือมากกว่า (การกำหนดค่าเอาต์พุต) - ใน Edge ขาเข้าไปยังเป้าหมายที่กำหนดค่าไว้ รายการเหล่านี้ระบุไว้ใน
RuleClass.Builder.cfg()
ชั้นเรียนที่เกี่ยวข้องคือ TransitionFactory
และ ConfigurationTransition
ระบบจะใช้การเปลี่ยนการกำหนดค่า เช่น
- เพื่อประกาศว่ามีการใช้ทรัพยากร Dependency ที่เฉพาะเจาะจงระหว่างบิลด์และ ดังนั้นควรสร้างขึ้นในสถาปัตยกรรมการดำเนินการ
- ในการประกาศว่าต้องสร้างทรัพยากร Dependency บางอย่างสำหรับ สถาปัตยกรรม (เช่น สำหรับโค้ดแบบเนทีฟใน Android APK ขนาดใหญ่)
ถ้าการเปลี่ยนการกำหนดค่าส่งผลให้เกิดการกำหนดค่าหลายรายการ จะถือเป็น การเปลี่ยนสไลด์
การเปลี่ยนการกำหนดค่าสามารถนำไปใช้ใน Starlark (เอกสารประกอบ ที่นี่)
ผู้ให้บริการข้อมูลทางอ้อม
ผู้ให้บริการข้อมูลทางอ้อมคือวิธี (และ _ทาง _เท่านั้น) สำหรับเป้าหมายที่กําหนดค่าไว้ เพื่อบอกสิ่งต่างๆ เกี่ยวกับเป้าหมายที่กำหนดค่าไว้อื่นๆ ที่ต้องใช้ เหตุผลที่ "สกรรมกริยา" คือชื่อที่สรุปแล้ว การปิดแบบทางอ้อมของเป้าหมายที่กำหนดค่าไว้
โดยทั่วไปจะมีการโต้ตอบแบบ 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 ของ
กฎกลุ่มไฟล์ใน BUILD และใช้ผู้ให้บริการ
OutputGroupInfo
ใน Java
ไฟล์เรียกใช้
ไบนารีบางรายการต้องใช้ไฟล์ข้อมูลเพื่อเรียกใช้ ตัวอย่างที่เห็นได้ชัดคือการทดสอบที่ต้อง ไฟล์อินพุต ลักษณะนี้แสดงอยู่ใน Bazel ตามแนวคิดของ "runfiles" ต "แผนผัง Runfiles" เป็นโครงสร้างไดเรกทอรีของไฟล์ข้อมูลสำหรับไบนารีหนึ่งๆ สร้างขึ้นในระบบไฟล์เป็นโครงสร้างลิงก์สัญลักษณ์ที่มีลิงก์สัญลักษณ์แยกกัน ซึ่งชี้ไปยังไฟล์ในซอร์สของเอาต์พุตต้นไม้
ชุดไฟล์รันจะแสดงเป็นอินสแตนซ์ Runfiles
ซึ่งมีแนวคิด
แมปจากเส้นทางของไฟล์ในโครงสร้าง Runfiles ไปยังอินสแตนซ์ Artifact
ที่
ก็แสดงถึงสิ่งนั้น จะซับซ้อนกว่า Map
เดียวสำหรับ 2 คนอยู่เล็กน้อย
เหตุผล:
- ส่วนใหญ่แล้ว เส้นทางรันไฟล์ของไฟล์จะเหมือนกับเส้นทางผู้ดำเนินการ เราใช้ข้อมูลนี้เพื่อประหยัด RAM บางส่วน
- มีรายการเดิมหลายประเภทในโครงสร้าง Runfile ซึ่งยังต้อง ที่จะเป็นตัวแทนได้
ระบบจะรวบรวมไฟล์ที่เรียกใช้โดยใช้ RunfilesProvider
ซึ่งเป็นอินสแตนซ์ของคลาสนี้
แสดงรันไฟล์ของเป้าหมายที่กำหนดค่าไว้ (เช่น ไลบรารี) และทรานซิทีฟ
ความต้องการในการปิดการขาย ซึ่งจะถูกรวบรวมไว้เหมือน
ชุดที่ซ้อนกันอยู่ (อันที่จริง
ถูกนำไปใช้โดยใช้ชุดที่ซ้อนกันใต้หน้าปก): แต่ละเป้าหมายรวมตัวรันไฟล์
ของทรัพยากร Dependency แล้วเพิ่มทรัพยากร Dependency ของตัวเองเข้าไป จากนั้นจึงส่งการตั้งค่าที่ได้
ในกราฟทรัพยากร Dependency อินสแตนซ์ RunfilesProvider
มี Runfiles
2 รายการ
อินสแตนซ์หนึ่งสำหรับกรณีที่กฎขึ้นอยู่กับผ่าน "ข้อมูล" และ
รายการหนึ่งสำหรับทรัพยากร Dependency ที่เข้ามาใหม่ทุกประเภท นั่นเป็นเพราะเป้าหมาย
บางครั้งจะแสดงไฟล์เรียกใช้ต่างๆ เมื่อต้องใช้ผ่านแอตทริบิวต์ข้อมูล
เมื่อเทียบกับกรณีอื่นๆ ซึ่งเป็นลักษณะการทำงานเดิมที่ไม่พึงประสงค์ที่เราไม่ได้แก้ไข
ยังนำออกอยู่
การเรียกใช้ไฟล์ไบนารีจะแสดงเป็นอินสแตนซ์ของ RunfilesSupport
ช่วงเวลานี้
แตกต่างจาก Runfiles
เนื่องจาก RunfilesSupport
มีความสามารถ
สร้างขึ้นจริง (ต่างจาก Runfiles
ที่เป็นเพียงการแมป) ช่วงเวลานี้
จำเป็นต้องมีคอมโพเนนต์เพิ่มเติมต่อไปนี้
- ไฟล์ Manifest ของการเรียกใช้ไฟล์อินพุต นี่คือคำอธิบายแบบต่อเนื่องของ โครงสร้างไฟล์รันไฟล์ ซึ่งใช้เป็นพร็อกซีสำหรับเนื้อหาของโครงสร้างไฟล์การเรียกใช้ และ Bazel สันนิษฐานว่าโครงสร้างไฟล์รันไฟล์จะเปลี่ยนแปลงก็ต่อเมื่อเนื้อหา ในไฟล์ Manifest
- ไฟล์ Manifest ของการเรียกใช้ไฟล์เอาต์พุต ซึ่งจะใช้โดยไลบรารีรันไทม์ที่ จัดการแผนผัง Runfiles โดยเฉพาะใน Windows ซึ่งบางครั้งก็ไม่รองรับ ลิงก์สัญลักษณ์
- คนกลางของรันไฟล์ เพื่อให้โครงสร้างการเรียกใช้ไฟล์มีอยู่แล้ว จะต้องมี เพื่อสร้างแผนผังลิงก์สัญลักษณ์และอาร์ติแฟกต์ที่ลิงก์สัญลักษณ์ชี้ไป อยู่ในคำสั่งซื้อ เพื่อลดจำนวนเอดจ์ของทรัพยากร Dependency อาจใช้ตัวกลางของการเรียกใช้ไฟล์ ที่ใช้เป็นตัวแทนทั้งหมดนี้
- อาร์กิวเมนต์บรรทัดคำสั่งสำหรับเรียกใช้ไบนารีที่มีการเรียกใช้ไฟล์
มีออบเจ็กต์
RunfilesSupport
รายการแทน
ลักษณะ
Aspects เป็นวิธีหนึ่งในการ "เผยแพร่การคํานวณลงในกราฟทรัพยากร Dependency" นั่นคือ
ที่อธิบายสำหรับผู้ใช้ Bazel
ที่นี่ ระดับพอดี
ตัวอย่างที่จูงใจคือบัฟเฟอร์โปรโตคอล: กฎ proto_library
ไม่ควรทราบ
เกี่ยวกับภาษาใดภาษาหนึ่งโดยเฉพาะ แต่การสร้างการใช้โปรโตคอล
บัฟเฟอร์ข้อความ ("หน่วยพื้นฐาน" ของบัฟเฟอร์โปรโตคอล) ในการเขียนโปรแกรม
ควรจับคู่ภาษากับกฎ proto_library
เพื่อที่ว่าถ้าเป้าหมาย 2 รายการอยู่ใน
ภาษาเดียวกันขึ้นอยู่กับบัฟเฟอร์โปรโตคอลเดียวกัน โดยจะสร้างขึ้นเพียงครั้งเดียว
เป้าหมายจะแสดงใน Skyframe เป็น SkyValue
เช่นเดียวกับเป้าหมายที่กำหนดค่าไว้
และวิธีสร้างเป้าหมายก็คล้ายกับการกำหนดเป้าหมายที่กำหนดค่าไว้
สร้าง: มีคลาสเริ่มต้นชื่อ ConfiguredAspectFactory
ซึ่งมี
เข้าถึง RuleContext
ได้ แต่วิธีนี้ก็แตกต่างจากโรงงานเป้าหมายที่กำหนดค่าไว้ด้วย
เกี่ยวกับเป้าหมายที่กำหนดค่าไว้ที่แนบกับผู้ให้บริการ
ชุดของลักษณะที่เผยแพร่ลงไปตามกราฟทรัพยากร Dependency จะระบุไว้สำหรับแต่ละรายการ
โดยใช้ฟังก์ชัน Attribute.Builder.aspects()
ตัวอย่างคือ
ชั้นเรียนที่มีชื่อสร้างความสับสนซึ่งมีส่วนร่วมในกระบวนการ:
AspectClass
คือการใช้งานในด้านต่างๆ สามารถแสดงใน Java (ในกรณีที่เป็นคลาสย่อย) หรือใน Starlark (ซึ่งในกรณีนี้คือStarlarkAspectClass
) ซึ่งคล้ายกับRuleConfiguredTargetFactory
AspectDefinition
คือคำจำกัดความของลักษณะนี้ จะมี ผู้ให้บริการที่ต้องการ ผู้ให้บริการที่ระบุ และมีการอ้างอิงไปยัง การนำไปใช้ เช่น อินสแตนซ์AspectClass
ที่เหมาะสม ตอนนี้ คล้ายกับRuleClass
AspectParameters
เป็นวิธีแบ่งลักษณะออกเป็นหลายๆ ด้านที่ค่อยๆ ขยายลงไป กราฟทรัพยากร Dependency ค่าปัจจุบันคือสตริงที่เชื่อมกับสตริง ตัวอย่างที่ดี ข้อดีคือบัฟเฟอร์โปรโตคอล ถ้าภาษาหนึ่งมี API หลายรายการ ข้อมูลว่าควรสร้างบัฟเฟอร์โปรโตคอล API ใด สามารถกระจายลงในกราฟทรัพยากร Dependency ได้Aspect
แสดงข้อมูลทั้งหมดที่จำเป็นสำหรับการคำนวณด้านที่ จะกระจายกราฟทรัพยากร Dependency ลงมา ซึ่งประกอบด้วยคลาสด้าน และพารามิเตอร์ของพารามิเตอร์RuleAspect
คือฟังก์ชันที่กําหนดลักษณะของกฎหนึ่งๆ ควรมีผล เป็นRule
->Aspect
ข้อมูลแทรกที่คาดไม่ถึงคือ แง่มุมต่างๆ อาจติดอยู่กับด้านอื่นๆ
ตัวอย่างเช่น ลักษณะที่รวบรวม classpath สำหรับ Java IDE
ต้องการทราบเกี่ยวกับไฟล์ .jar ทั้งหมดใน classpath แต่บางไฟล์
และบัฟเฟอร์โปรโตคอล ในกรณีนี้ ด้าน IDE จะต้องแนบกับ
คู่ (กฎ proto_library
กฎ + มุมมอง Java Pro)
ระบบจะบันทึกความซับซ้อนของแง่มุมต่างๆ ในชั้นเรียน
AspectCollection
แพลตฟอร์มและ Toolchain
Bazel รองรับบิลด์บนหลายแพลตฟอร์ม กล่าวคือ บิลด์อาจมี สถาปัตยกรรมหลายอย่างที่การกระทำของบิลด์ทำงาน และสถาปัตยกรรมหลายอย่าง สร้างโค้ดแบบใด สถาปัตยกรรมเหล่านี้เรียกว่าแพลตฟอร์มใน Bazel Parlance (เอกสารฉบับเต็ม) ที่นี่)
แพลตฟอร์มจะอธิบายโดยการแมปคีย์-ค่าจากการตั้งค่าข้อจํากัด (เช่น
แนวคิดของ "สถาปัตยกรรม CPU") เพื่อจำกัดค่า (เช่น CPU ตัวใดตัวหนึ่ง
เช่น x86_64) เรามี "พจนานุกรม" ของข้อจำกัดที่ใช้บ่อยที่สุด
การตั้งค่าและค่าต่างๆ ในที่เก็บ @platforms
แนวคิดของ toolchain มาจากข้อเท็จจริงที่ว่าโดยขึ้นอยู่กับแพลตฟอร์มใด บิลด์ทำงานอยู่และแพลตฟอร์มใดที่มีการกำหนดเป้าหมาย ผู้ใช้อาจจำเป็นต้องใช้ คอมไพเลอร์ที่ต่างกัน ตัวอย่างเช่น Toolchain ของ C++ หนึ่งๆ อาจทำงานบน ระบบปฏิบัติการที่เฉพาะเจาะจง และสามารถกำหนดเป้าหมายระบบปฏิบัติการอื่นๆ ได้ Bazel ต้องกำหนด C++ คอมไพเลอร์ที่ใช้ตามการดำเนินการเซ็ตและแพลตฟอร์มเป้าหมาย (เอกสารสำหรับ Toolchain ที่นี่)
ในการดำเนินการดังกล่าว Toolchains จะมีคำอธิบายประกอบด้วยชุดการดำเนินการและ ข้อจำกัดแพลตฟอร์มเป้าหมายที่รองรับ ในการดำเนินการดังกล่าว คำนิยามของ Toolchain แบ่งออกเป็น 2 ส่วนดังนี้
- กฎ
toolchain()
ที่อธิบายชุดการดำเนินการและเป้าหมาย จำกัดที่ Toolchain สนับสนุนและบอกชนิด (เช่น C++ หรือ Java) Toolchain นั้น (รายการหลังจะแสดงด้วยกฎtoolchain_type()
) - กฎเฉพาะภาษาที่อธิบาย Toolchain จริง (เช่น
cc_toolchain()
)
เนื่องจากเราจําเป็นต้องทราบข้อจํากัดสําหรับ
Toolchain เพื่อทำการแปลง Toolchain และเจาะจงภาษา
กฎ *_toolchain()
ข้อมีข้อมูลมากกว่านั้นมาก จึงใช้เวลามากกว่า
เวลาที่ใช้ในการโหลด
แพลตฟอร์มการดำเนินการจะระบุโดยใช้วิธีใดวิธีหนึ่งต่อไปนี้
- ในไฟล์ MODULE.bazel โดยใช้ฟังก์ชัน
register_execution_platforms()
- ในบรรทัดคำสั่งโดยใช้บรรทัดคำสั่ง --extra_execution_platforms ตัวเลือก
ชุดของแพลตฟอร์มการดำเนินการที่ใช้ได้จะถูกคำนวณใน
RegisteredExecutionPlatformsFunction
แพลตฟอร์มเป้าหมายสำหรับเป้าหมายที่กำหนดค่าจะกำหนดโดย
PlatformOptions.computeTargetPlatform()
โดยเป็นรายการแพลตฟอร์ม เพราะเรา
ท้ายที่สุดแล้วต้องรองรับแพลตฟอร์มเป้าหมายหลายแพลตฟอร์ม แต่ไม่มีการติดตั้งใช้งาน
ชุดของ Toolchain ที่จะใช้สำหรับเป้าหมายที่กำหนดค่าจะกำหนดโดย
ToolchainResolutionFunction
โดยเป็นฟังก์ชันของสิ่งต่อไปนี้
- ชุด Toolchain ที่จดทะเบียน (ในไฟล์ MODULE.bazel และ การกำหนดค่า)
- แพลตฟอร์มการดำเนินการและแพลตฟอร์มที่ต้องการ (ในการกำหนดค่า)
- ชุดของประเภท Toolchain ที่เป้าหมายที่กำหนดค่าไว้ต้องการ (ใน
UnloadedToolchainContextKey)
- ชุดข้อจำกัดแพลตฟอร์มการดำเนินการของเป้าหมายที่กำหนดค่า (แท็ก
exec_compatible_with
) และการกำหนดค่า (--experimental_add_exec_constraints_to_targets
) ในUnloadedToolchainContextKey
ผลการค้นหาจะเป็น UnloadedToolchainContext
ซึ่งโดยพื้นฐานแล้วเป็นแผนที่จาก
ประเภท Toolchain (แสดงเป็นอินสแตนซ์ ToolchainTypeInfo
) กับป้ายกำกับของ
Toolchain ที่เลือก ชื่อว่า "ยกเลิกการโหลด" เนื่องจากไม่มีองค์ประกอบ
เชนเครื่องมือด้วยตนเอง เฉพาะป้ายกำกับเท่านั้น
จากนั้นระบบจะโหลด Toolchain จริงๆ โดยใช้ ResolvedToolchainContext.load()
และใช้โดยการใช้งานเป้าหมายที่กำหนดค่าที่ขอ
นอกจากนี้เรายังมีระบบเดิมที่ต้องอาศัย "โฮสต์" เพียงรายเดียว
การกำหนดค่าและการกำหนดค่าเป้าหมายที่แสดงด้วย
แฟล็กการกำหนดค่า เช่น --cpu
เราจะค่อยๆ เปลี่ยนไปใช้การเปลี่ยนแปลงด้านบน
ระบบ ในการจัดการกรณีต่างๆ ที่ผู้ใช้ต้องอาศัยการกำหนดค่าเดิม
เราได้ปรับใช้
การแมปแพลตฟอร์ม
เพื่อแปลค่าระหว่าง Flag เดิมกับข้อจำกัดแพลตฟอร์มรูปแบบใหม่
โค้ดของพวกเขาอยู่ใน PlatformMappingFunction
และใช้แท็กที่ไม่ใช่ Starlark
ภาษา"
ข้อจำกัด
บางครั้งผู้ใช้รายหนึ่งต้องการกำหนดให้มีเป้าหมายที่เข้ากันได้กับเพียงไม่กี่คน ใหม่ Bazel มีกลไกมากมายในการบรรลุเป้าหมายนี้
- ข้อจำกัดเฉพาะกฎ
environment_group()
/environment()
- ข้อจำกัดของแพลตฟอร์ม
ข้อจำกัดเฉพาะกฎมักจะใช้ใน Google สำหรับกฎของ Java คือ
ไม่อยู่ได้ใน Bazel แต่ซอร์สโค้ดอาจ
อ้างอิงถึงได้ แอตทริบิวต์ที่ควบคุมสิ่งนี้เรียกว่า
constraints=
สภาพแวดล้อม_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
) และ a
ที่พร้อมใช้งานโดยค่าเริ่มต้นจาก Starlark
(StarlarkLibrary.environmentGroup()
) ซึ่งสุดท้ายแล้วจะกลายเป็นคำนาม
เป้าหมาย เพื่อหลีกเลี่ยงการพึ่งพาแบบวนซ้ำซึ่งอาจเกิดขึ้นเนื่องจาก
ต้องประกาศกลุ่มสภาพแวดล้อมสภาพแวดล้อมดังกล่าว และแต่ละ
กลุ่มสภาพแวดล้อมต้องประกาศสภาพแวดล้อมเริ่มต้น
บิลด์อาจถูกจำกัดไว้ในสภาพแวดล้อมบางอย่างด้วย
ตัวเลือกบรรทัดคำสั่ง --target_environment
การนำการตรวจสอบข้อจำกัดมาใช้
RuleContextConstraintSemantics
และ TopLevelConstraintSemantics
ข้อจำกัดของแพลตฟอร์ม
"อย่างเป็นทางการ" ในปัจจุบัน วิธีอธิบายแพลตฟอร์มที่เป้าหมายใช้งานร่วมกันได้ คือ การใช้ข้อจำกัดเดียวกันกับที่ใช้อธิบาย Toolchain และแพลตฟอร์ม อยู่ระหว่างตรวจสอบการดึงคำขอ #10945
ระดับการแชร์
หากคุณทำงานกับ Codebase ขนาดใหญ่กับนักพัฒนาซอฟต์แวร์จำนวนมาก (เช่นที่ Google) เพื่อป้องกันไม่ให้บุคคลอื่นตัดสิน โค้ด มิฉะนั้น ตามกฎของ Hyrum ผู้คนจะอาศัยพฤติกรรมที่คุณถือว่าเป็นการติดตั้งใช้งาน รายละเอียด
Bazel สนับสนุนการทำงานนี้โดยกลไกที่เรียกว่าระดับการเข้าถึง คุณสามารถประกาศว่า จะขึ้นอยู่กับการใช้ 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 ที่จำเป็นต้องอยู่ใน classpath สำหรับกฎ Java เพื่อ คอมไพล์หรือเรียกใช้
- ชุดของไฟล์ Python เมื่อปิดทรานซิทีฟของกฎ Python
หากเราทำเช่นนี้โดยใช้วิธีที่ไร้เดียงสา ตัวอย่างเช่น List
หรือ Set
เราจะมี
การใช้งานหน่วยความจำกำลังสอง: หากมีเชนของกฎ N กฎและกฎแต่ละข้อเพิ่ม
เราจะมีสมาชิกคอลเล็กชัน 1+2+...+N คน
ในการแก้ปัญหานี้ เราจึงได้คิดไอเดียเกี่ยวกับ
NestedSet
เป็นโครงสร้างข้อมูลที่ประกอบด้วยNestedSet
อื่นๆ
และสมาชิกบางส่วนของตัวเอง จึงสร้างกราฟแบบอินไซเคิลที่มีทิศทาง
จากชุด ซึ่งจะเปลี่ยนแปลงไม่ได้และสมาชิกสามารถทำซ้ำได้ เรานิยาม
ลำดับการทำซ้ำหลายรายการ (NestedSet.Order
): สั่งซื้อล่วงหน้า หลังสั่งซื้อ โทโพโลยี
(โหนดจะอยู่หลังระดับบนเสมอ) และ "ไม่สนใจ แต่โหนดควรเป็น
เท่ากันในแต่ละครั้ง"
โครงสร้างข้อมูลเดียวกันนี้เรียกว่า depset
ใน Starlark
อาร์ติแฟกต์และการดำเนินการ
บิลด์จริงประกอบด้วยชุดคำสั่งที่จำเป็นต้องเรียกใช้เพื่อสร้าง
เอาต์พุตที่ผู้ใช้ต้องการ คำสั่งจะแสดงเป็นอินสแตนซ์ของ
คลาส Action
และไฟล์จะแสดงเป็นอินสแตนซ์ของชั้นเรียน
Artifact
กราฟเหล่านี้ถูกจัดเรียงเป็นกราฟแบบ 2 ภาคแบบมีทิศทาง เรียกว่ากราฟ
"กราฟการดำเนินการ"
อาร์ติแฟกต์แบ่งออกเป็น 2 ประเภท ได้แก่ อาร์ติแฟกต์ต้นทาง (ประเภทที่มี ก่อนที่ Bazel จะเริ่มปฏิบัติการ) และอาร์ติแฟกต์ที่ดึงมา (สิ่งที่จำเป็นต้อง สร้าง) อาร์ติแฟกต์ที่ได้มาอาจมีได้หลายประเภท ดังนี้
- **อาร์ติแฟกต์ปกติ **ข้อมูลเหล่านี้ได้รับการตรวจสอบความล่าสุดโดยการคำนวณ ผลรวมตรวจสอบโดยมีเวลาเป็นทางลัด เราจะไม่ตรวจสอบความถูกต้องของไฟล์หาก เวลาไม่เปลี่ยนแปลง
- อาร์ติแฟกต์symlink ที่ยังไม่ได้รับการแก้ไข ซึ่งมีการตรวจสอบความทันสมัยโดย การเรียกใช้ Readlink() อาร์ติแฟกต์เหล่านี้อาจไม่ปลอดภัย ซึ่งต่างจากอาร์ติแฟกต์ทั่วไป ลิงก์สัญลักษณ์ มักใช้ในกรณีที่มีการแพคไฟล์บางไฟล์ลงใน การเก็บถาวรข้อมูลบางประเภท
- อาร์ติแฟกต์ของต้นไม้ ไฟล์เหล่านี้ไม่ใช่ไฟล์เดี่ยว แต่เป็นแผนผังไดเรกทอรี โฆษณาเหล่านี้
ตรวจสอบความทันสมัยด้วยการตรวจสอบชุดของไฟล์ในไฟล์และ
เนื้อหา โดยจะแสดงเป็น
TreeArtifact
- อาร์ติแฟกต์ข้อมูลเมตาแบบคงที่ การเปลี่ยนแปลงอาร์ติแฟกต์เหล่านี้จะไม่ทริกเกอร์ สร้างใหม่ ซึ่งจะใช้สําหรับข้อมูลแสตมป์ของบิลด์เท่านั้น เราไม่ต้องการ สร้างใหม่เพียงเพราะเวลาปัจจุบันเปลี่ยนไป
ซึ่งไม่มีเหตุผลพื้นฐานว่าทำไมอาร์ติแฟกต์ต้นทางจึงไม่สามารถเป็นอาร์ติแฟกต์แบบต้นไม้หรือ
อาร์ติแฟกต์ symlink ที่ยังไม่ได้แก้ไข เพียงแต่เรายังไม่ได้นำมันไปใช้ (เรา
อย่างไรก็ตาม การอ้างอิงไดเรกทอรีแหล่งที่มาในไฟล์ BUILD
เป็นหนึ่งใน
จำนวนปัญหาเรื่องความไม่ถูกต้องที่เกิดขึ้นมาเป็นเวลานานของ Bazel เรามี
ประเภทงานที่เปิดใช้งานโดย
BAZEL_TRACK_SOURCE_DIRECTORIES=1
พร็อพเพอร์ตี้ JVM)
Artifact
ลักษณะเด่นคือคนกลาง โดยจะระบุด้วย Artifact
อินสแตนซ์ที่เป็นเอาต์พุตของ MiddlemanAction
แอปเหล่านี้ใช้เพื่อ
คำนึงถึงกรณีพิเศษต่อไปนี้:
- ระบบใช้การรวมตัวกลางในการจัดกลุ่มอาร์ติแฟกต์เข้าด้วยกัน ทั้งนี้เพื่อให้ หากการทำงานหลายๆ อย่างใช้อินพุตขนาดใหญ่ชุดเดียวกัน เราจะไม่มี N*M ขอบของทรัพยากร Dependency เท่านั้น N+M (จะถูกแทนที่ด้วยชุดที่ซ้อนกัน)
- การกำหนดเวลาให้กับคนกลางของทรัพยากร Dependency จะช่วยให้มั่นใจได้ว่าการดำเนินการจะทำงานก่อนอีกรายการ
ส่วนใหญ่มักใช้สำหรับการวิเคราะห์โค้ด แต่ใช้สำหรับการรวบรวม C++ ด้วย (โปรดดู
CcCompilationContext.createMiddleman()
เพื่อดูคำอธิบาย) - คนกลางของ Runfiles ใช้เพื่อให้แน่ใจว่ามีแผนผัง Runfile เพื่อให้ ที่ไม่ต้องใช้แยกต่างหาก จะขึ้นอยู่กับไฟล์ Manifest ของเอาต์พุตและ อาร์ติแฟกต์เดี่ยวที่โครงสร้าง Runfiles อ้างอิง
การดำเนินการต่างๆ เป็นคำสั่งที่จำเป็นต้องเรียกใช้ สำหรับสภาพแวดล้อม และชุดเอาต์พุตที่สร้างขึ้นมา รายการต่อไปนี้คือข้อมูลหลัก ของคำอธิบายการดำเนินการ ได้แก่
- บรรทัดคำสั่งที่จำเป็นต้องเรียกใช้
- อาร์ติแฟกต์อินพุตที่จำเป็นต้องใช้
- ตัวแปรสภาพแวดล้อมที่ต้องตั้งค่า
- คำอธิบายประกอบที่อธิบายถึงสภาพแวดล้อม (เช่น แพลตฟอร์ม) ที่สภาพแวดล้อมนั้นต้องใช้ทำงาน \
นอกจากนี้ยังมีกรณีพิเศษอื่นๆ อีก เช่น การเขียนไฟล์ที่มีเนื้อหา
กับ Bazel เป็นคลาสย่อยของ AbstractAction
การดำเนินการส่วนใหญ่
SpawnAction
หรือ StarlarkAction
(ก็ไม่ควรเหมือนกัน
แยกคลาส) แม้ว่า Java และ C++ จะมีประเภทการดำเนินการของตนเอง
(JavaCompileAction
, CppCompileAction
และ CppLinkAction
)
ท้ายที่สุดแล้ว เราต้องการย้ายทุกอย่างไปยัง SpawnAction
JavaCompileAction
คือ
ค่อนข้างใกล้เคียง แต่ C++ เป็นกรณีพิเศษเล็กน้อยเนื่องจากการแยกวิเคราะห์ไฟล์ .d และ
รวมการสแกนด้วย
กราฟการกระทำส่วนใหญ่เป็นแบบ "ฝัง" ลงในกราฟ Skyframe ตามหลักการแล้ว
เรียกใช้การดำเนินการแสดงเป็นการเรียก
ActionExecutionFunction
การแมปจากขอบของทรัพยากร Dependency ของกราฟการดำเนินการไปยัง
ขอบของทรัพยากร Dependency ของ Skyframe มีอยู่ใน
ActionExecutionFunction.getInputDeps()
และ Artifact.key()
และมีอีก 2-3 รายการ
การเพิ่มประสิทธิภาพเพื่อรักษาขอบของ Skyframe ให้อยู่ในระดับต่ำ:
- อาร์ติแฟกต์ที่ได้มาไม่มี
SkyValue
เป็นของตัวเอง แต่Artifact.getGeneratingActionKey()
จะใช้ในการค้นหาคีย์ของ การทำงานที่สร้างขึ้น - ชุดที่ซ้อนกันจะมีคีย์ Skyframe ของตัวเอง
การดำเนินการที่แชร์
การกระทำบางอย่างสร้างขึ้นโดยเป้าหมายที่กำหนดค่าไว้หลายรายการ กฎของ Starlark ถูกจำกัดมากขึ้น เนื่องจากได้รับอนุญาตให้ใส่ การกระทำที่ได้รับมาใน ที่กำหนดโดยการกำหนดค่าและแพ็กเกจ (แต่ถึงอย่างนั้น กฎในแพ็กเกจเดียวกันสามารถขัดแย้งกัน) แต่กฎที่ใช้งานใน Java สามารถวาง ที่ได้รับรายการต่างๆ จากทุกที่
แม้จะเป็นฟีเจอร์ที่ไม่ถูกต้อง แต่การกำจัดเรื่องนี้เป็นเรื่องยากมาก เพราะช่วยประหยัดเวลาได้มาก เช่น เมื่อ ไฟล์ต้นฉบับต้องได้รับการประมวลผลในทางใดทางหนึ่ง และไฟล์ดังกล่าวมีการอ้างอิงโดย กฎหลายข้อ (คลื่นมือด้วยมือ) ซึ่งมาพร้อมกับค่าใช้จ่ายของ RAM บางส่วน ซึ่งมีดังนี้ ของการทำงานที่แชร์ต้องจัดเก็บในหน่วยความจำแยกต่างหาก
หากการดำเนินการ 2 รายการสร้างไฟล์เอาต์พุตเดียวกัน จะต้องเหมือนกันทุกประการ ดังนี้
มีอินพุตเดียวกัน เอาต์พุตเดียวกัน และเรียกใช้บรรทัดคำสั่งเดียวกัน ช่วงเวลานี้
มีการใช้ความสัมพันธ์เทียบเท่าใน Actions.canBeShared()
และเป็น
ยืนยันระหว่างขั้นตอนการวิเคราะห์และขั้นตอนการดำเนินการโดยดูที่การดำเนินการทั้งหมด
วิธีนี้ใช้ในSkyframeActionExecutor.findAndStoreArtifactConflicts()
และเป็นหนึ่งในไม่กี่แห่งใน Bazel ที่ต้องมี มุมมองของ
งานสร้าง
ระยะการดำเนินการ
นี่คือเวลาที่ Bazel เริ่มการทำงานของบิลด์ เช่น คำสั่งที่ ให้ผลลัพธ์ออกมา
สิ่งแรกที่ Bazel ทำหลังจากช่วงการวิเคราะห์คือ การพิจารณาว่า
ต้องสร้างอาร์ติแฟกต์ ตรรกะของกรณีนี้จะเข้ารหัสในรูปแบบ
TopLevelArtifactHelper
; กล่าวโดยคร่าวๆ ก็คือ filesToBuild
ของ
เป้าหมายที่กำหนดค่าไว้ในบรรทัดคำสั่งและเนื้อหาของเอาต์พุตพิเศษ
กลุ่มเพื่อวัตถุประสงค์ที่ชัดเจนในการแสดง "หากเป้าหมายนี้อยู่ในคำสั่ง
ให้สร้างอาร์ติแฟกต์เหล่านี้"
ขั้นตอนถัดไปคือการสร้างรูทการดำเนินการ เนื่องจาก Bazel มีตัวเลือกในการอ่าน
แพ็กเกจแหล่งที่มาจากตำแหน่งต่างๆ ในระบบไฟล์ (--package_path
)
ต้องมีการดำเนินการที่ทำในเครื่องที่มีโครงสร้างแหล่งที่มาแบบเต็ม นี่คือ
จัดการโดยคลาส SymlinkForest
และทำงานโดยจดบันทึกเป้าหมายทั้งหมด
ที่ใช้ในขั้นตอนการวิเคราะห์และสร้างแผนผังไดเรกทอรีเดี่ยวที่ Symlink
ทุกแพ็กเกจที่มีเป้าหมายที่ใช้แล้วจากตำแหน่งจริง อีกทางเลือกหนึ่งคือ
ส่งผ่านเส้นทางไปยังคำสั่งที่ถูกต้อง (โดยนำ --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
ค่านี้คือ checksum ของข้อมูล เขียนไว้)
นอกจากนี้ยังมี "แคชการดำเนินการจากด้านบน" ซึ่งเป็น "แคชการดำเนินการจากด้านบน" ที่ยังคงอยู่ภายใต้ ซึ่งจะใช้แฮชแบบทรานซิทีฟเพื่อหลีกเลี่ยงการไปที่แคช ครั้ง
การค้นพบอินพุตและการตัดอินพุต
การกระทำบางอย่างซับซ้อนกว่าแค่การมีชุดอินพุต การเปลี่ยนแปลงเป็น ชุดอินพุตของการทำงานมี 2 รูปแบบ ได้แก่
- การดำเนินการอาจค้นพบข้อมูลใหม่ๆ ก่อนดำเนินการ หรือตัดสินใจว่า
ของอินพุตที่ไม่จำเป็น ตัวอย่าง Canonical คือ C++
ที่ซึ่งคุณควรเดาอย่างมีหลักการว่าไฟล์ส่วนหัวใดเป็น C++
จากการปิดแบบทรานซิทีฟ ซึ่งทำให้เราไม่สนว่าจะส่ง
ไฟล์ให้กับผู้ดำเนินการระยะไกล เราจึงมีตัวเลือกที่จะไม่ลงทะเบียน
ไฟล์ส่วนหัวเป็น "อินพุต" แต่สแกนไฟล์แหล่งที่มาเพื่อหาการส่งผ่าน
รวมส่วนหัว และทำเครื่องหมายเฉพาะไฟล์ส่วนหัวเหล่านั้นเป็นอินพุต
ที่ระบุไว้ในข้อความ
#include
(เราประเมินให้สูงไว้ก่อนเพื่อที่จะได้ไม่ต้อง ใช้โปรเซสเซอร์ล่วงหน้าแบบ C เต็มรูปแบบ) ปัจจุบันตัวเลือกนี้ใช้งานสาย "เท็จ" ใน Bazel และมีการใช้งานที่ Google เท่านั้น - อาจมีการดำเนินการเกิดขึ้นว่าไฟล์บางไฟล์ไม่ได้ใช้ในระหว่างการดำเนินการ ใน C++ หรือเรียกว่า "ไฟล์ .d" โดยคอมไพเลอร์จะบอกว่าไฟล์ส่วนหัวใด ใช้หลังจากที่เกิดเหตุการณ์นั้น และเพื่อหลีกเลี่ยงความอับอายที่จะเกิดเหตุการณ์ที่แย่ลง ส่วนเพิ่มมากกว่าMake แล้ว Bazel ใช้ประโยชน์จากข้อเท็จจริงนี้ วิธีนี้จะช่วยเพิ่ม ที่มากกว่าเครื่องมือสแกนรวม เนื่องจากต้องใช้คอมไพเลอร์
วิธีดำเนินการต่างๆ มีดังนี้
- โทรหา
Action.discoverInputs()
ซึ่งควรแสดงผลชุดที่ซ้อนกันของ อาร์ติแฟกต์ที่พิจารณาแล้วว่าจําเป็นต้องใช้ รายการเหล่านี้ต้องเป็นอาร์ติแฟกต์ต้นฉบับ เพื่อให้ไม่มีขอบของทรัพยากร Dependency ในกราฟการทำงานที่ไม่มี เทียบเท่าในกราฟเป้าหมายที่กำหนดค่าไว้ - การดำเนินการนี้จะเรียกใช้
Action.execute()
- เมื่อสิ้น
Action.execute()
การดำเนินการจะเรียกใช้ได้Action.updateInputs()
เพื่อบอก Bazel ว่าอินพุตบางรายการไม่ได้ ที่จำเป็น ซึ่งอาจส่งผลให้บิลด์เพิ่มขึ้นไม่ถูกต้องหากอินพุตที่ใช้คือ รายงานว่าไม่มีการใช้งานแล้ว
เมื่อแคชการดำเนินการแสดง Hit ในอินสแตนซ์การดำเนินการใหม่ (เช่น สร้างแล้ว
หลังจากรีสตาร์ทเซิร์ฟเวอร์) Bazel จะเรียก updateInputs()
ด้วยตัวเอง ดังนั้นชุดของ
จะสะท้อนผลลัพธ์ของการค้นพบและการตัดอินพุตที่เคยทำก่อนหน้านี้
การดำเนินการของ Starlark ใช้ประโยชน์จากสถานบริการเพื่อประกาศว่าอินพุตบางรายการเป็นไม่ได้ใช้
โดยใช้อาร์กิวเมนต์ unused_inputs_list=
ของ
ctx.actions.run()
วิธีเรียกใช้การกระทำต่างๆ: กลยุทธ์/ActionContexts
การดำเนินการบางอย่างสามารถเรียกใช้ได้หลายวิธี ตัวอย่างเช่น บรรทัดคำสั่งอาจเป็น
ดำเนินการภายใน ภายใน แต่ในแซนด์บ็อกซ์ประเภทต่างๆ หรือจากระยะไกล
แนวคิดที่รวมสิ่งนี้เรียกว่า ActionContext
(หรือ Strategy
เนื่องจากเรา
ก็ทำสำเร็จไปได้ครึ่งทางด้วยการเปลี่ยนชื่อ...)
วงจรชีวิตของบริบทการดำเนินการมีดังนี้
- เมื่อเริ่มระยะการดำเนินการ ระบบจะถามอินสแตนซ์
BlazeModule
รายการว่า บริบทของการดำเนินการที่พวกเขามี สิ่งนี้เกิดขึ้นในเครื่องมือสร้างของExecutionTool
ประเภทบริบทการดำเนินการจะระบุโดย JavaClass
อินสแตนซ์ที่อ้างถึงอินเทอร์เฟซย่อยของ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 รายการเกิดขึ้นในบิลด์เดียวกันได้ การกำหนดค่า แต่ละไดเรกทอรีเพื่อให้ทั้งสองมีเวอร์ชันของตัวเอง action; มิฉะนั้น หากการกำหนดค่าทั้งสองขัดแย้งกัน เช่น คำสั่ง ของการทำงานซึ่งสร้างไฟล์เอาต์พุตเดียวกัน 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 และ stderr ไม่ใช่ อยู่แยกกันtest.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
สคริปต์นี้ตั้งค่าสภาพแวดล้อมของการทดสอบเพื่อเปิดใช้การรวบรวมการครอบคลุม และกำหนดตำแหน่งที่รันไทม์การครอบคลุมจะเขียนไฟล์การครอบคลุม จากนั้นจึงทำการทดสอบ การทดสอบอาจเรียกใช้กระบวนการย่อยหลายรายการและประกอบด้วย ที่เขียนด้วยภาษาโปรแกรมต่างๆ หลายภาษา (โดย รันไทม์ของการรวบรวมการครอบคลุม) สคริปต์ 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++ จำเป็นต้องใช้ตัวเลือกคอมไพเลอร์ที่แตกต่างกันเพื่อปล่อยโค้ดที่สามารถรวบรวมการครอบคลุม
ซึ่งช่วยลดปัญหานี้ได้พอสมควร เนื่องจากจำเป็นต้องมีการวิเคราะห์ใหม่ต่อไป)
ไฟล์สนับสนุนการครอบคลุมจะขึ้นอยู่กับป้ายกำกับโดยปริยาย ทรัพยากร Dependency ใหม่เพื่อให้ลบล้างได้ด้วยนโยบายการเรียกใช้ ซึ่งช่วยให้ แตกต่างกันไประหว่าง Bazel แต่ละเวอร์ชัน ตามหลักการแล้ว ความแตกต่างจะถูกลบออก และเราได้กำหนดมาตรฐานที่หนึ่งในนั้น
เรายังสร้าง "รายงานการครอบคลุม" อีกด้วย ซึ่งจะรวมความครอบคลุมที่รวบรวมสำหรับ
การทดสอบทุกครั้งในการเรียกใช้ Bazel ซึ่งจัดการโดย
CoverageReportActionFactory
และเรียกใช้จาก BuildView.createResult()
ทั้งนี้
เข้าถึงเครื่องมือที่จำเป็นได้โดยไปที่ :coverage_report_generator
ของการทดสอบแรกที่ดำเนินการ
เครื่องมือการค้นหา
Bazel มี ภาษาเล็กน้อย ที่ใช้ในการถามเรื่องต่างๆ เกี่ยวกับกราฟต่างๆ ประเภทคำค้นหาต่อไปนี้ ที่มีให้:
bazel query
จะใช้ในการตรวจสอบกราฟเป้าหมายbazel cquery
จะใช้ในการตรวจสอบกราฟเป้าหมายที่กำหนดค่าไว้bazel aquery
จะใช้เพื่อตรวจสอบกราฟการดำเนินการ
แต่ละรายการเหล่านี้ติดตั้งใช้งานโดยการแยกประเภทย่อย AbstractBlazeQueryEnvironment
ฟังก์ชันการค้นหาเพิ่มเติมอื่นๆ จะทำได้โดยการแยกประเภทย่อย QueryFunction
ที่ใช้เวลาเพียง 2 นาที หากต้องการอนุญาตการสตรีมผลการค้นหา แทนที่จะรวบรวมไว้ในบางรายการ
โครงสร้างข้อมูล query2.engine.Callback
จะถูกส่งไปยัง QueryFunction
ซึ่ง
เรียกผลการค้นหาที่ต้องการแสดงผล
ผลลัพธ์ของการค้นหาสามารถทำได้หลายวิธี ได้แก่ ป้ายกำกับ ป้ายกำกับ และกฎ
คลาส, XML, Protobuf และอื่นๆ ตัวแปรเหล่านี้นำไปใช้เป็นคลาสย่อยของ
OutputFormatter
ข้อกำหนดเล็กๆ น้อยๆ สำหรับรูปแบบเอาต์พุตของการค้นหาบางรูปแบบ (แน่นอนว่าต้องมีโปรโต) ก็คือ Bazel จำเป็นต้องปล่อย _all _ข้อมูลที่โหลดแพ็กเกจไว้เพื่อให้ สามารถดูความแตกต่างของผลลัพธ์ และพิจารณาว่าเป้าหมายหนึ่งๆ มีการเปลี่ยนแปลงหรือไม่ ดังนั้นค่าแอตทริบิวต์จึงต้องทำให้อนุกรมได้ ซึ่งเป็นเหตุผลที่ เป็นแอตทริบิวต์เพียงไม่กี่ประเภทที่ไม่มีแอตทริบิวต์ที่มี Starlark ซับซ้อน วิธีหลีกเลี่ยงปัญหาทั่วไปคือการใช้ป้ายกำกับ และการแนบแท็กที่ซับซ้อน ลงในกฎที่มีป้ายกำกับนั้น ไม่ใช่วิธีแก้ปัญหาที่น่าพอใจมาก คงจะดีไม่น้อยถ้าได้ยกเลิกข้อกำหนดนี้
ระบบโมดูล
คุณสามารถขยาย Bazel ได้โดยเพิ่มโมดูลลงไป แต่ละโมดูลต้องมีคลาสย่อย
BlazeModule
(ชื่อนี้เป็นวัตถุโบราณจากประวัติศาสตร์ของ Bazel เมื่อสมัยก่อน
ที่เรียกว่า Blaze) และรับข้อมูลเกี่ยวกับเหตุการณ์ต่างๆ ในระหว่างการดำเนินการ
คำสั่ง
ส่วนใหญ่จะใช้งานในส่วนที่ "ไม่ใช่เนื้อหาหลัก" หลายด้าน ฟังก์ชัน เฉพาะ Bazel บางเวอร์ชันเท่านั้น (เช่น เวอร์ชันที่เราใช้ที่ Google) ที่ต้องการ
- การเชื่อมต่อกับระบบปฏิบัติการระยะไกล
- คำสั่งใหม่
ชุดจุดต่อที่ BlazeModule
ข้อเสนอนั้นไม่เป็นธรรมชาติ สิ่งที่ไม่ควรทำ
ให้ใช้เป็นตัวอย่างของหลักการออกแบบที่ดี
รถบัสในงาน
วิธีหลักที่ BlazeModules สื่อสารกับส่วนที่เหลือของ Bazel ได้คือการใช้รถบัสของเหตุการณ์
(EventBus
): อินสแตนซ์ใหม่จะถูกสร้างขึ้นสำหรับทุกบิลด์หรือส่วนต่างๆ ของ Bazel
สามารถโพสต์เหตุการณ์ลงในไฟล์และโมดูลสามารถลงทะเบียน Listener สำหรับเหตุการณ์ที่เกิดขึ้น
สนใจ ตัวอย่างเช่น รายการต่อไปนี้จะแสดงเป็นเหตุการณ์
- กำหนดรายการเป้าหมายของบิลด์ที่จะสร้างแล้ว
(
TargetParsingCompleteEvent
) - กำหนดการกำหนดค่าระดับบนสุดแล้ว
(
BuildConfigurationEvent
) - สร้างเป้าหมายสำเร็จหรือไม่ (
TargetCompleteEvent
) - มีการทดสอบ (
TestAttempt
,TestSummary
)
กิจกรรมเหล่านี้บางส่วนมีการแสดงนอก Bazel ใน
โปรโตคอลเหตุการณ์บิลด์
(คือ BuildEvent
วินาที) วิธีนี้ไม่เพียงแค่อนุญาต BlazeModule
วินาที แต่ยัง
นอกกระบวนการ Bazel เพื่อสังเกตบิลด์ ซึ่งเข้าถึงได้ทั้งในรูปแบบ
ที่มีข้อความโปรโตคอล หรือ Bazel สามารถเชื่อมต่อกับเซิร์ฟเวอร์ (เรียกว่า
Build Event) เพื่อสตรีมเหตุการณ์
เรานำวิธีนี้ไปใช้ในbuild.lib.buildeventservice
และ
แพ็กเกจ Java build.lib.buildeventstream
แพ็กเกจ
ที่เก็บภายนอก
ในขณะที่ Bazel ออกแบบมาเพื่อใช้ในโมโนเรโป (แหล่งเดียว ต้นไม้ที่มีทุกอย่างที่จำเป็นต่อการสร้าง) Bazel อาศัยอยู่ในโลก ซึ่งไม่เป็นความจริงเสมอไป "ที่เก็บภายนอก" เป็นนามธรรมที่ใช้ เชื่อมโลกทั้งสองเข้าด้วยกัน โดยเป็นเหมือนโค้ดที่จำเป็นสำหรับงานสร้าง ไม่ได้อยู่ในแผนผังแหล่งที่มาหลัก
ไฟล์ WORKSPACE
ชุดที่เก็บภายนอกจะกำหนดโดยการแยกวิเคราะห์ไฟล์ WORKSPACE เช่น การประกาศที่มีลักษณะดังนี้
local_repository(name="foo", path="/foo/bar")
ผลลัพธ์ในที่เก็บที่ชื่อ @foo
กำลังพร้อมใช้งาน แหล่งที่มา
ความซับซ้อนก็คือคุณสามารถกำหนดกฎที่เก็บใหม่ในไฟล์ Starlark
จะใช้เพื่อโหลดโค้ด Starlark ใหม่ซึ่งสามารถใช้เพื่อกำหนด
กฎที่เก็บและอื่นๆ อีกมากมาย
ในการจัดการกรณีนี้ การแยกวิเคราะห์ของไฟล์ WORKSPACE (ใน
WorkspaceFileFunction
) จะแบ่งออกเป็นส่วนๆ ตามที่กำหนดโดย load()
ข้อความ ดัชนีกลุ่มจะแสดงด้วย WorkspaceFileKey.getIndex()
และ
การประมวลผล WorkspaceFileFunction
จนถึงดัชนี X หมายถึงการประเมินจนกว่า
คำสั่งที่ Xth load()
กำลังดึงข้อมูลที่เก็บ
ก่อนที่โค้ดของที่เก็บจะพร้อมใช้งานสำหรับ Bazel ต้องมี
ดึงข้อมูล วิธีนี้ทำให้ Bazel สร้างไดเรกทอรีภายใต้
$OUTPUT_BASE/external/<repository name>
การดึงข้อมูลที่เก็บจะเกิดขึ้นตามขั้นตอนต่อไปนี้
PackageLookupFunction
พบว่าต้องการที่เก็บและสร้างRepositoryName
ในรูปของSkyKey
ซึ่งเรียกใช้RepositoryLoaderFunction
RepositoryLoaderFunction
ส่งต่อคำขอไปยังRepositoryDelegatorFunction
ด้วยเหตุผลที่ไม่ชัดเจน (รหัสระบุว่าเป็น หลีกเลี่ยงการดาวน์โหลดอะไรใหม่ในกรณีที่ Skyframe รีสตาร์ท แต่ การให้เหตุผลที่ชัดเจน)RepositoryDelegatorFunction
ค้นหากฎที่เก็บที่ได้รับการร้องขอ ดึงข้อมูลโดยทำซ้ำบางส่วนของไฟล์ WORKSPACE จนกว่าจะส่งคำขอ พบที่เก็บ- พบ
RepositoryFunction
ที่เหมาะสมซึ่งใช้ที่เก็บ fetching; เป็นการใช้งานที่เก็บของ Starlark หรือ แผนที่แบบฮาร์ดโค้ดสำหรับที่เก็บซึ่งนำไปใช้ใน Java
การแคชมีหลายเลเยอร์ เนื่องจากการดึงข้อมูลที่เก็บสามารถทำได้ แพง:
- มีแคชสำหรับไฟล์ที่ดาวน์โหลดซึ่งถูกคีย์โดยการตรวจสอบข้อผิดพลาด
(
RepositoryCache
) ซึ่งต้องมี checksum อยู่ภายใน ไฟล์ WORKSPACE แต่ยังคงเก็บรายละเอียดได้ แชร์โดย อินสแตนซ์ของเซิร์ฟเวอร์ Bazel ทุกรายการในเวิร์กสเตชันเดียวกัน โดยไม่คำนึงว่าอินสแตนซ์ใด พื้นที่ทำงานหรือฐานเอาต์พุตที่ใช้งานอยู่ - "ไฟล์เครื่องหมาย" เขียนสำหรับที่เก็บแต่ละแห่งภายใต้
$OUTPUT_BASE/external
ที่มี checksum ของกฎที่ใช้ในการดึงข้อมูล ถ้าต้นบาเซล เซิร์ฟเวอร์รีสตาร์ท แต่ checksum ไม่เปลี่ยนแปลง จึงไม่มีการดึงข้อมูลอีกครั้ง ช่วงเวลานี้ จะนำมาใช้ในRepositoryDelegatorFunction.DigestWriter
- ตัวเลือกบรรทัดคำสั่ง
--distdir
จะกำหนดแคชอื่นที่ใช้เพื่อ ค้นหาอาร์ติแฟกต์ที่จะดาวน์โหลด ฟีเจอร์นี้มีประโยชน์ในการตั้งค่าองค์กร โดย Bazel ไม่ควร ดึงข้อมูลแบบสุ่มจากอินเทอร์เน็ต นี่คือ มีการใช้งานโดยDownloadManager
เมื่อดาวน์โหลดที่เก็บแล้ว ระบบจะถือว่าอาร์ติแฟกต์ในที่เก็บเป็นต้นฉบับ
และอาร์ติแฟกต์อื่นๆ ปัญหานี้อาจก่อให้เกิดปัญหาเนื่องจาก Bazel จะตรวจสอบความเป็นปัจจุบันอยู่เสมอ
ของอาร์ติแฟกต์ต้นฉบับด้วยการเรียก stat() ที่มี
และอาร์ติแฟกต์เหล่านี้ยัง
จะใช้ไม่ได้เมื่อมีการเปลี่ยนแปลงคำจำกัดความของที่เก็บ ดังนั้น
FileStateValue
สำหรับอาร์ติแฟกต์ในที่เก็บภายนอกจะต้องใช้
ที่เก็บภายนอกของตนได้ ซึ่งจัดการโดย ExternalFilesHelper
การแมปที่เก็บ
ที่เก็บหลายแหล่งอาจ
ต้องอาศัยที่เก็บเดียวกัน
แต่ในเวอร์ชันที่ต่างกัน (นี่เป็นอินสแตนซ์ของ "ทรัพยากร Dependency แบบ Diamond
ปัญหา") ตัวอย่างเช่น หากไบนารี 2 รายการในที่เก็บแยกกันในบิลด์
ต้องการพึ่งพา Guava พวกเขาน่าจะเรียกคำว่า Guava โดยใช้ป้ายกำกับ
ตั้งแต่วันที่ @guava//
และคาดว่านั่นจะหมายถึงเวอร์ชันอื่น
ดังนั้น 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 ส่วนใหญ่เขียนเป็นภาษา Java ยกเว้นส่วนที่ Java ไม่สามารถทำทุกอย่างได้ด้วยตัวเอง หรือไม่สามารถทำได้ด้วยตัวเองเมื่อเรานำมาใช้งาน ช่วงเวลานี้ ส่วนใหญ่จะจำกัดอยู่ที่การโต้ตอบกับระบบไฟล์ การควบคุมกระบวนการ และ ระดับล่างอื่นๆ อีกมากมาย
โค้ด C++ อยู่ภายใต้ src/main/native และคลาส Java ที่มีเนทีฟ ได้แก่
NativePosixFiles
และNativePosixFileSystem
ProcessUtils
WindowsFileOperations
และWindowsFileProcesses
com.google.devtools.build.lib.platform
เอาต์พุตของคอนโซล
การปล่อยเอาต์พุตของคอนโซลดูเหมือนว่าจะเป็นเรื่องง่าย แต่สิ่งที่จะเกิดขึ้นก็คือการวิ่ง กระบวนการหลายอย่าง (ซึ่งบางครั้งทำจากระยะไกล) การแคชแบบละเอียด ความต้องการ จะมีเอาต์พุตเทอร์มินัลและมีสีสันสวยงาม และการมีเซิร์ฟเวอร์ที่ทำงานมานาน เรื่องไม่ใช่เรื่องสำคัญ
หลังจากที่มีการเรียกใช้ RPC จากไคลเอ็นต์ นั่นคือ RpcOutputStream
อินสแตนซ์จะถูกสร้างขึ้น (สำหรับ 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 ประเภท คือ การทดสอบที่สังเกต Bazel เป็น "กล่องดำ" และ โฆษณาที่เรียกใช้เฉพาะขั้นตอนการวิเคราะห์เท่านั้น เราเรียกเดิมว่า "การทดสอบการผสานรวม" และ "การทดสอบ 1 หน่วย" อย่างหลัง แม้ว่าจะเป็นเหมือนกับการทดสอบการผสานรวมที่ ก็จะไม่ผสานรวมกันมากนัก นอกจากนี้เรายังมีการทดสอบ 1 หน่วยจริงด้วย ตามความจำเป็น
ของการทดสอบการผสานรวมมี 2 ประเภท ได้แก่
- โซลูชันที่นำไปใช้โดยใช้กรอบการทดสอบแบบ Bash ที่ละเอียดประณีตภายใต้
src/test/shell
- เบราว์เซอร์ที่ติดตั้งใช้งานใน Java ตัวแปรเหล่านี้นำไปใช้เป็นคลาสย่อยของ
BuildIntegrationTestCase
BuildIntegrationTestCase
เป็นเฟรมเวิร์กการทดสอบการผสานรวมที่แนะนำเนื่องจาก
ก็พร้อมใช้งานสำหรับสถานการณ์การทดสอบส่วนใหญ่ เนื่องจากเป็นเฟรมเวิร์ก Java
ให้ความสามารถในการแก้ไขข้อบกพร่องและการผสานรวมที่ราบรื่นกับการพัฒนาทั่วไปจำนวนมาก
และเครื่องมือการประมาณที่กำหนดได้เอง มีตัวอย่างของ BuildIntegrationTestCase
คลาสใน
ที่เก็บ Bazel
ใช้การทดสอบการวิเคราะห์เป็นคลาสย่อยของ BuildViewTestCase
มี
ระบบไฟล์ Scratch ที่คุณสามารถใช้เพื่อเขียนไฟล์ BUILD
ตามด้วยตัวช่วยต่างๆ
สามารถขอเป้าหมายที่กำหนดค่าแล้ว เปลี่ยนการกำหนดค่า และยืนยัน
หลายอย่างเกี่ยวกับผลการวิเคราะห์