ฐานของโค้ด Bazel

วันที่ รายงานปัญหา ดูแหล่งที่มา ตอนกลางคืน · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

เอกสารนี้จะอธิบายฐานของโค้ดและโครงสร้างของ 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") ได้รับการสนับสนุน ช่วยตั้งค่ากระบวนการของเซิร์ฟเวอร์ที่เหมาะสมโดยใช้ ขั้นตอนต่อไปนี้

  1. ตรวจสอบว่ามีการแยกตัวเองแล้วหรือไม่ แต่หากไม่เป็นเช่นนั้น ช่วงเวลานี้ คือที่มาของการติดตั้งใช้งานเซิร์ฟเวอร์
  2. ตรวจสอบว่ามีอินสแตนซ์เซิร์ฟเวอร์ที่ใช้งานอยู่ซึ่งทำงานอยู่หรือไม่ จะมีตัวเลือกเริ่มต้นที่เหมาะสมและใช้ไดเรกทอรีพื้นที่ทำงานที่เหมาะสม ทั้งนี้ ค้นหาเซิร์ฟเวอร์ที่ทำงานอยู่ โดยดูที่ไดเรกทอรี $OUTPUT_BASE/server ซึ่งมีไฟล์ล็อกกับพอร์ตที่เซิร์ฟเวอร์กำลังฟังอยู่
  3. ปิดกระบวนการของเซิร์ฟเวอร์เก่าหากจำเป็น
  4. หากจำเป็น ให้เริ่มกระบวนการของเซิร์ฟเวอร์ใหม่

หลังจากกระบวนการของเซิร์ฟเวอร์ที่เหมาะสมพร้อมแล้ว คำสั่งที่จำเป็นต้องเรียกใช้คือ สื่อสารกับคอมพิวเตอร์ผ่านอินเทอร์เฟซ gRPC จากนั้นระบบจะ ส่งเอาต์พุตของ Bazel กลับคืน ไปยังเครื่องชำระเงิน เรียกใช้คำสั่งได้ครั้งละ 1 รายการเท่านั้น นี่คือ โดยใช้กลไกการล็อกที่ซับซ้อนด้วยชิ้นส่วนต่างๆ ใน C++ และชิ้นส่วนต่างๆ ใน Java มีโครงสร้างพื้นฐานสำหรับการเรียกใช้คำสั่งหลายรายการพร้อมกัน เนื่องจากเรียกใช้ bazel version พร้อมกันกับคำสั่งอื่นไม่ได้ ค่อนข้างน่าอาย ตัวบล็อกหลักคือวงจรชีวิตของ BlazeModule และบางรัฐใน BlazeRuntime

เมื่อสิ้นสุดคำสั่ง เซิร์ฟเวอร์ Bazel จะส่งโค้ดสำหรับออกที่ไคลเอ็นต์ ควรกลับมา รอยย่นที่น่าสนใจคือการใช้ bazel run: งานของคำสั่งนี้คือเรียกใช้สิ่งที่ Bazel เพิ่งสร้างขึ้น แต่ดำเนินการดังกล่าวไม่ได้ จากกระบวนการของเซิร์ฟเวอร์เนื่องจากไม่มีเทอร์มินัล แทนที่เราจะบอก ไคลเอ็นต์คือไบนารีใดที่ควร ujexec() และมีอาร์กิวเมนต์ใด

เมื่อกด 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 ได้ควบคุมและได้รับแจ้งเกี่ยวกับคำสั่งที่จำเป็นแล้ว จะดำเนินการตามลำดับเหตุการณ์ต่อไปนี้

  1. BlazeCommandDispatcher ได้รับแจ้งเกี่ยวกับคำขอใหม่แล้ว ตัดสินใจเลือก คำสั่งต้องใช้พื้นที่ทำงานหรือไม่ (เกือบทุกคำสั่งยกเว้น สำหรับรหัสที่ไม่มีส่วนเกี่ยวข้องกับซอร์สโค้ด เช่น เวอร์ชันหรือ ความช่วยเหลือ) และดูว่ามีคำสั่งอื่นทำงานอยู่หรือไม่

  2. พบคำสั่งที่ถูกต้อง แต่ละคําสั่งต้องใช้อินเทอร์เฟซ BlazeCommand และต้องมีคำอธิบายประกอบ @Command (นี่เป็น แพทเทิร์น: คงจะดีถ้าเราต้องใช้ข้อมูลเมตาทั้งหมดที่คำสั่งต้องการ อธิบายโดยใช้วิธีการใน BlazeCommand)

  3. ตัวเลือกบรรทัดคำสั่งจะได้รับการแยกวิเคราะห์ แต่ละคำสั่งมีบรรทัดคำสั่งแตกต่างกัน อื่นๆ ซึ่งอธิบายไว้ในคำอธิบายประกอบ @Command

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

  5. โดยคำสั่งจะได้รับการควบคุม คำสั่งที่น่าสนใจที่สุดคือคำสั่งที่เรียกใช้ สร้าง: สร้าง ทดสอบ เรียกใช้ ครอบคลุม และอื่นๆ: ฟังก์ชันการทำงานนี้ ติดตั้งใช้งานโดย BuildTool

  6. ชุดของรูปแบบเป้าหมายในบรรทัดคำสั่งได้รับการแยกวิเคราะห์และไวลด์การ์ดเช่น //pkg:all และ //pkg/... ได้รับการแก้ไขแล้ว วิธีนี้ใช้ใน AnalysisPhaseRunner.evaluateTargetPatterns() และรีเฟรชใน Skyframe เป็น TargetPatternPhaseValue

  7. ขั้นตอนการโหลด/การวิเคราะห์จะทำงานเพื่อสร้างกราฟการดำเนินการ (มีทิศทาง กราฟแอซิกของคำสั่งที่ต้องดำเนินการกับบิลด์)

  8. ขั้นตอนการดำเนินการจะทำงาน ซึ่งหมายถึงการดำเนินการทุกอย่างที่จำเป็น สร้างเป้าหมายระดับบนสุดที่มีการร้องขอจะทำงาน

ตัวเลือกบรรทัดคำสั่ง

ตัวเลือกบรรทัดคำสั่งสำหรับการเรียกใช้ Bazel มีรายละเอียดอยู่ใน OptionsParsingResult ซึ่งมีแผนที่จาก "ตัวเลือก คลาส" ค่าของตัวเลือก "คลาสตัวเลือก" เป็นคลาสย่อยของ OptionsBase และจัดกลุ่มตัวเลือกบรรทัดคำสั่งเข้าด้วยกัน ซึ่งมีความเกี่ยวข้องกับแต่ละตัวเลือก อื่นๆ เช่น

  1. ตัวเลือกที่เกี่ยวข้องกับภาษาโปรแกรม (CppOptions หรือ JavaOptions) ชุดค่าผสมเหล่านี้ควรเป็นคลาสย่อยของ FragmentOptions และได้รับการรวม ลงในออบเจ็กต์ BuildOptions
  2. ตัวเลือกที่เกี่ยวข้องกับวิธีที่ Bazel ดำเนินการ (ExecutionOptions)

ตัวเลือกเหล่านี้ออกแบบมาเพื่อใช้งานในขั้นตอนการวิเคราะห์และ ( ผ่าน RuleContext.getFragment() ใน Java หรือ ctx.fragments ใน Starlark) ระบบอ่านคำสั่ง (เช่น รวมการสแกนด้วย C++ ไหม) จะต้องติดตั้งระบบประปาทันทีตั้งแต่ BuildConfiguration ไม่พร้อมให้บริการในตอนนี้ สำหรับข้อมูลเพิ่มเติม โปรดดู ส่วน "Configurations"

คำเตือน: เราเหมือนจะสมมติว่าอินสแตนซ์ OptionsBase นั้นเปลี่ยนแปลงไม่ได้และ ใช้วิธีการดังกล่าว (เช่น ส่วนหนึ่งของ SkyKeys) โดยไม่เป็นเช่นนั้นและ การดัดแปลงโมเดลเป็นวิธีที่ยอดเยี่ยมในการทำลายบาเซลด้วยวิธีที่แยบยลซึ่งทำได้ยาก เพื่อแก้ไขข้อบกพร่อง น่าเสียดายที่การทำให้กลยุทธ์เหล่านี้เปลี่ยนแปลงไม่ได้นั้นต้องอาศัยความพยายามอย่างมาก (การแก้ไข FragmentOptions ทันทีหลังจากการก่อสร้างก่อนมีผู้ใดโดยเฉพาะ จะมีโอกาสได้เก็บการอ้างอิงไว้ และก่อนวันที่ equals() หรือ hashCode() จะ ก็ไม่มีปัญหา)

Bazel ได้เรียนรู้เกี่ยวกับคลาสตัวเลือกด้วยวิธีต่อไปนี้

  1. บางรุ่นต่อสายเข้ากับ Bazel (CommonCommandOptions)
  2. จากคำอธิบายประกอบ @Command ในคำสั่ง Bazel แต่ละรายการ
  3. จาก ConfiguredRuleClassProvider (ตัวเลือกเหล่านี้เกี่ยวข้องกับตัวเลือกบรรทัดคำสั่ง สำหรับภาษาโปรแกรมแต่ละภาษา)
  4. กฎของ Starlark ยังกำหนดตัวเลือกของตัวเองด้วย (โปรดดู ที่นี่)

แต่ละตัวเลือก (ไม่รวมตัวเลือกที่กำหนดโดย Starlark) เป็นตัวแปรสมาชิกของ คลาสย่อย FragmentOptions ที่มีคำอธิบายประกอบ @Option ซึ่งระบุ ชื่อและประเภทของตัวเลือกบรรทัดคำสั่ง พร้อมกับข้อความช่วยเหลือบางอย่าง

ประเภท Java ของค่าของตัวเลือกบรรทัดคำสั่งมักจะเป็นแบบง่ายๆ (เช่น สตริง จำนวนเต็ม บูลีน ป้ายกำกับ ฯลฯ) อย่างไรก็ตาม เรายังสนับสนุน ประเภทที่ซับซ้อนขึ้น ในกรณีนี้ งานในการแปลงจาก บรรทัดคำสั่งของประเภทข้อมูลเป็นประเภท การติดตั้งใช้งาน com.google.devtools.common.options.Converter

ต้นกำเนิดอย่างที่ Bazel เห็น

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

ที่เก็บ

"ที่เก็บ" เป็นแผนผังแหล่งที่มาที่นักพัฒนาซอฟต์แวร์ทำงาน โดยปกติ แสดงโปรเจ็กต์เดียว บรรพบุรุษของ Bazel ชื่อ Blaze ซึ่งทำงานโดยใช้ระบบ Monorepo ซึ่งก็คือแผนผังแหล่งที่มาเดียวที่มีซอร์สโค้ดทั้งหมดที่ใช้ในการเรียกใช้บิลด์ ในทางกลับกัน Bazel สนับสนุนโปรเจ็กต์ที่มีซอร์สโค้ดครอบคลุมหลายรหัส ที่เก็บได้ ที่เก็บที่มีการเรียกใช้ Bazel เรียกว่า "main ที่เก็บอื่นๆ เรียกว่า "ที่เก็บภายนอก"

ที่เก็บมีการทำเครื่องหมายโดยไฟล์ชื่อ WORKSPACE (หรือ WORKSPACE.bazel) ใน ไดเรกทอรีราก ไฟล์นี้มีข้อมูลที่เป็น "ส่วนกลาง" กับทั้ง ตัวอย่างเช่น ชุดของที่เก็บภายนอกที่ใช้ได้ ซึ่งมีลักษณะการทำงานเหมือน ไฟล์ Starlark ปกติ ซึ่งหมายความว่ามีไฟล์ Starlark อื่นอีก load() ไฟล์ โดยทั่วไปแล้วจะใช้เพื่อดึงข้อมูลลงในที่เก็บที่ที่เก็บจำเป็นต้องใช้ ที่มีการอ้างอิงอย่างชัดแจ้ง (เราเรียกสิ่งนี้ว่า "รูปแบบ deps.bzl")

โค้ดของที่เก็บภายนอกมีการลิงก์หรือดาวน์โหลดภายใต้ $OUTPUT_BASE/external

ขณะเรียกใช้บิลด์ ต้นไม้ต้นทางทั้งหมดต้องนำมารวมกัน นี้ ดำเนินการโดย SymlinkForest ซึ่งจะลิงก์ทุกแพ็กเกจในที่เก็บหลัก ไปยัง $EXECROOT และที่เก็บภายนอกทั้งหมดไปยัง $EXECROOT/external หรือ $EXECROOT/.. (แต่เดิมทำให้ไม่มีแพ็กเกจ ที่เรียกว่า external ในที่เก็บหลัก นั่นคือเหตุผลที่เราย้ายข้อมูลออกจาก )

แพ็กเกจ

ที่เก็บทั้งหมดประกอบด้วยแพ็กเกจ คอลเล็กชันของไฟล์ที่เกี่ยวข้อง และ ข้อกำหนดของทรัพยากร 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 เองมีสมาชิกบางรายที่ใช้เฉพาะ แยกวิเคราะห์ไฟล์ WORKSPACE ซึ่งไม่เหมาะกับแพ็กเกจจริง นี่คือ ข้อบกพร่องในการออกแบบเนื่องจากวัตถุที่อธิบายแพ็กเกจปกติไม่ควรมี ที่อธิบายสิ่งอื่นๆ ซึ่งได้แก่

  • การแมปที่เก็บ
  • Toolchains ที่จดทะเบียน
  • แพลตฟอร์มการดำเนินการที่ลงทะเบียนไว้

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

ป้ายกำกับ เป้าหมาย และกฎ

แพ็กเกจประกอบด้วยเป้าหมายในประเภทต่อไปนี้

  1. ไฟล์: สิ่งต่างๆ ที่เป็นอินพุตหรือเอาต์พุตของบิลด์ ใน Bazel parlance เราเรียกว่าอาร์ติแฟกต์ (มีการพูดถึงที่อื่น) ไม่ใช่ทั้งหมด ไฟล์ที่สร้างขึ้นระหว่างการสร้างเป็นเป้าหมาย โดยทั่วไปสำหรับเอาต์พุต Bazel ต้องไม่มีป้ายกำกับที่เกี่ยวข้อง
  2. กฎ: ส่วนนี้จะอธิบายขั้นตอนการดึงเอาต์พุตจากอินพุต โฆษณาเหล่านี้ มักเชื่อมโยงกับภาษาโปรแกรม (เช่น cc_library java_library หรือ py_library) แต่ก็มีบางวิธีที่เข้าใจได้โดยไม่จำเป็นต้องเข้าใจภาษาที่พูด (เช่น genrule หรือ filegroup)
  3. กลุ่มแพ็กเกจ: อธิบายในหัวข้อระดับการเข้าถึง

โดยชื่อของเป้าหมายจะเรียกว่าป้ายกำกับ ไวยากรณ์ของป้ายกำกับคือ @repo//pac/kage:name โดยที่ repo คือชื่อของที่เก็บป้ายกำกับ pac/kage คือไดเรกทอรีที่ไฟล์ BUILD อยู่ และ name คือเส้นทางของ ไฟล์ (หากป้ายกำกับอ้างอิงไฟล์แหล่งที่มา) ที่เกี่ยวข้องกับไดเรกทอรีของ ใหม่ เมื่อพูดถึงเป้าหมายในบรรทัดคำสั่ง บางส่วนของป้ายกำกับ อาจละเว้นได้ดังนี้

  1. หากไม่ระบุที่เก็บ ระบบจะนำป้ายกำกับนี้ไปอยู่ในที่เก็บหลัก ที่เก็บได้
  2. หากละเว้นส่วนแพ็กเกจ (เช่น name หรือ :name) ระบบจะใช้ป้ายกำกับ อยู่ในแพ็กเกจของไดเรกทอรีการทำงานปัจจุบัน (เส้นทางแบบสัมพัทธ์ ไม่อนุญาตให้ใช้การอ้างอิงแบบยกระดับ (..))

ชนิดของกฎ (เช่น "ไลบรารี C++") จะเรียกว่า "คลาสกฎ" คลาสของกฎอาจ นำมาใช้ได้ใน Starlark (ฟังก์ชัน rule()) หรือใน Java (ซึ่งจะเรียกว่า "กฎเนทีฟ" พิมพ์ RuleClass) ในระยะยาว ทุกภาษาที่เฉพาะเจาะจง จะมีการใช้กฎใน Starlark แต่กลุ่มกฎเดิมบางกลุ่ม (เช่น Java หรือ C++) ยังคงอยู่ใน Java ในขณะนี้

ต้องนำเข้าคลาสกฎ Starlark ที่ตอนต้นของ BUILD ไฟล์ โดยใช้คำสั่ง load() ในขณะที่คลาสของกฎ Java จะเป็น รู้จักโดย Bazel ตามที่จดทะเบียนไว้กับConfiguredRuleClassProvider

คลาสของกฎจะมีข้อมูลต่างๆ เช่น

  1. แอตทริบิวต์ (เช่น srcs, deps) ได้แก่ ประเภท ค่าเริ่มต้น ข้อจำกัด ฯลฯ
  2. การเปลี่ยนการกำหนดค่าและลักษณะที่แนบมากับแอตทริบิวต์แต่ละรายการ หากมี
  3. การใช้กฎ
  4. ผู้ให้บริการข้อมูลทรานซิทีฟที่กฎ "โดยปกติ" สร้าง

หมายเหตุคำศัพท์: ในฐานของโค้ด เรามักจะใช้ "กฎ" เพื่อหมายถึงเป้าหมาย สร้างโดยคลาสกฎ แต่ใน 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 ซึ่งแคชไว้ ดังนั้น เราจึงมักจะทำงาน เกี่ยวกับปัญหานี้โดย:

  1. การประกาศทรัพยากร Dependency เป็นกลุ่ม (โดยใช้ getValuesAndExceptions()) เพื่อ จำกัดจำนวนการรีสตาร์ท
  2. การแบ่ง SkyValue ออกเป็นส่วนต่างๆ ที่คํานวณโดยความแตกต่าง SkyFunction เพื่อให้คำนวณและแคชได้อย่างอิสระ ช่วงเวลานี้ ควรทำอย่างมีกลยุทธ์ เนื่องจากจะช่วยเพิ่มหน่วยความจำ
  3. สถานะการจัดเก็บระหว่างการรีสตาร์ท ไม่ว่าจะโดยใช้ SkyFunction.Environment.getState() หรือเก็บแคชเฉพาะกิจ "เบื้องหลัง Skyframe"

โดยพื้นฐานแล้ว เราต้องใช้วิธีแก้ปัญหาเฉพาะหน้า เพราะเรามี โหนด Skyframe บนเครื่องบินหลายแสนโหนด และ Java ไม่รองรับ ชุดข้อความที่มีน้ำหนักเบา

สตาร์ลาร์ก

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

มีการใช้งาน Starlark ในแพ็กเกจ net.starlark.java นอกจากนี้ยังมีการใช้ Go อย่างอิสระ ที่นี่ ชวา ที่ใช้ใน Bazel เป็นล่ามอยู่ในขณะนี้

Starlark มีการใช้ในบริบทต่างๆ ได้แก่

  1. ภาษา BUILD นี่คือที่ที่มีการกำหนดกฎใหม่ รหัส Starlark การเรียกใช้ในบริบทนี้มีสิทธิ์เข้าถึงเฉพาะเนื้อหาของไฟล์ BUILD และ .bzl ไฟล์ที่ตัวนี้โหลดขึ้นมา
  2. คำจำกัดความของกฎ นี่คือวิธีที่กฎใหม่ (เช่น การสนับสนุนสำหรับกฎใหม่ ภาษา) รหัส Starlark ที่เรียกใช้ในบริบทนี้มีสิทธิ์เข้าถึง การกำหนดค่าและข้อมูลที่มาจากทรัพยากร Dependency โดยตรง (ข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ ในภายหลัง)
  3. ไฟล์ WORKSPACE ในจุดนี้ที่เก็บภายนอก (โค้ดที่ไม่ใช่ ในแผนผังแหล่งข้อมูลหลัก) ที่กำหนดไว้
  4. คำจำกัดความของกฎที่เก็บ ในจุดนี้ ประเภทที่เก็บภายนอกแบบใหม่ มีการกำหนดไว้ โค้ด Starlark ที่ทำงานอยู่ในบริบทนี้สามารถเรียกใช้โค้ดที่กำหนดเองบน เครื่องที่ Bazel ทำงานอยู่และเข้าถึงนอกพื้นที่ทำงานได้

ภาษาถิ่นที่ใช้กับไฟล์ BUILD และ .bzl มีความแตกต่างกันเล็กน้อย เพราะแสดงออกถึงสิ่งต่างๆ แตกต่างกัน ดูรายการความแตกต่างได้ ที่นี่

มีข้อมูลเพิ่มเติมเกี่ยวกับ Starlark ที่นี่

ระยะการโหลด/การวิเคราะห์

ขั้นตอนการโหลด/การวิเคราะห์คือจุดที่ Bazel พิจารณาว่าต้องดำเนินการใดบ้าง สร้างกฎหนึ่งๆ หน่วยพื้นฐานของหน่วยโฆษณาคือ "เป้าหมายที่กำหนดค่า" ซึ่งก็คือ คู่ (เป้าหมาย การกำหนดค่า) อย่างสมเหตุสมผล

ซึ่งเรียกว่า "ระยะการโหลด/การวิเคราะห์" เพราะสามารถแบ่งออกเป็น ส่วนต่างๆ ที่เคยเป็นแบบต่อเนื่องกัน แต่คราวนี้สามารถซ้อนทับกันได้เมื่อเวลาผ่านไป

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

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

  1. การกำหนดค่า ("วิธีการ" สร้างกฎ เช่น เป้าหมาย แต่ยังรวมถึงตัวเลือกบรรทัดคำสั่งที่ผู้ใช้ต้องการ ที่ส่งไปยังคอมไพเลอร์ C++)
  2. ทรัพยากร Dependency โดยตรง พบผู้ให้บริการข้อมูลทางอ้อม ลงในกฎที่วิเคราะห์ เราเรียกสิ่งนี้ว่าแบบนั้นเนื่องจากให้ "ภาพรวม" ในการปิดการรับส่งข้อมูลของการกำหนดค่า เช่น ไฟล์ .jar ทั้งหมดใน classpath หรือไฟล์ .o ทั้งหมดที่ ต้องลิงก์อยู่ในไบนารี C++)
  3. ตัวเป้าหมาย นี่เป็นผลมาจากการโหลดแพ็กเกจเป้าหมาย อยู่ใน สำหรับกฎ แอตทริบิวต์นี้จะรวมถึงแอตทริบิวต์ของกฎ ซึ่งมักจะเป็น เป็นสิ่งสำคัญ
  4. การใช้งานเป้าหมายที่กำหนดค่า สำหรับกฎ เกณฑ์นี้สามารถเป็น อยู่ใน Starlark หรือ Java มีการใช้เป้าหมายที่กำหนดค่าที่ไม่ใช่กฎทั้งหมด ใน Java

เอาต์พุตของการวิเคราะห์เป้าหมายที่กำหนดค่าไว้คือ

  1. ผู้ให้บริการข้อมูลทรานซิทีฟที่กำหนดค่าเป้าหมายซึ่งพึ่งพาข้อมูลดังกล่าวสามารถ เข้าถึง
  2. อาร์ติแฟกต์ที่ 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 ที่ ดังนี้

  1. อยู่ใน Dependency Edge การเปลี่ยนแปลงเหล่านี้มีการระบุไว้ใน Attribute.Builder.cfg() และเป็นฟังก์ชันจาก Rule (โดยที่ฟังก์ชัน การเปลี่ยนแปลงเกิดขึ้น) และ BuildOptions (การกำหนดค่าเดิม) เป็น 1 BuildOptions หรือมากกว่า (การกำหนดค่าเอาต์พุต)
  2. ใน Edge ขาเข้าไปยังเป้าหมายที่กำหนดค่าไว้ รายการเหล่านี้ระบุไว้ใน RuleClass.Builder.cfg()

ชั้นเรียนที่เกี่ยวข้องคือ TransitionFactory และ ConfigurationTransition

ระบบจะใช้การเปลี่ยนการกำหนดค่า เช่น

  1. เพื่อประกาศว่ามีการใช้ทรัพยากร Dependency ที่เฉพาะเจาะจงระหว่างบิลด์และ ดังนั้นควรสร้างขึ้นในสถาปัตยกรรมการดำเนินการ
  2. ในการประกาศว่าต้องสร้างทรัพยากร Dependency บางอย่างสำหรับ สถาปัตยกรรม (เช่น สำหรับโค้ดแบบเนทีฟใน Android APK ขนาดใหญ่)

ถ้าการเปลี่ยนการกำหนดค่าส่งผลให้เกิดการกำหนดค่าหลายรายการ จะถือเป็น การเปลี่ยนสไลด์

การเปลี่ยนการกำหนดค่าสามารถนำไปใช้ใน Starlark (เอกสารประกอบ ที่นี่)

ผู้ให้บริการข้อมูลทางอ้อม

ผู้ให้บริการข้อมูลทางอ้อมคือวิธี (และ _ทาง _เท่านั้น) สำหรับเป้าหมายที่กําหนดค่าไว้ เพื่อบอกสิ่งต่างๆ เกี่ยวกับเป้าหมายที่กำหนดค่าไว้อื่นๆ ที่ต้องใช้ เหตุผลที่ "สกรรมกริยา" คือชื่อที่สรุปแล้ว การปิดแบบทางอ้อมของเป้าหมายที่กำหนดค่าไว้

โดยทั่วไปจะมีการโต้ตอบแบบ 1:1 ระหว่างผู้ให้บริการข้อมูลแบบทรานซิทีฟของ Java และ Starlark (ข้อยกเว้นคือ DefaultInfo ซึ่งเป็นการผสมระหว่าง FileProvider, FilesToRunProvider และ RunfilesProvider เนื่องจาก API นั้นถูก ถือว่าเป็น Starlark-ish มากกว่าการทับศัพท์ของ Java โดยตรง) คีย์ของพวกเขาเป็นหนึ่งในสิ่งต่อไปนี้:

  1. ออบเจ็กต์คลาส Java ตัวเลือกนี้มีให้เฉพาะผู้ให้บริการที่ เข้าถึงได้จาก Starlark ผู้ให้บริการเหล่านี้เป็นคลาสย่อยของ TransitiveInfoProvider
  2. สตริง นี่เป็นเรื่องเก่าและไม่สนับสนุนอย่างยิ่งเพราะอาจมีความเสี่ยงที่จะ ชื่อที่ตรงกัน ผู้ให้บริการข้อมูลทรานซิทีฟดังกล่าวคือคลาสย่อยโดยตรงของ build.lib.packages.Info
  3. สัญลักษณ์ผู้ให้บริการ รายการนี้สร้างจาก Starlark โดยใช้ provider() ได้ และเป็นวิธีที่แนะนำในการสร้างผู้ให้บริการใหม่ สัญลักษณ์คือ แสดงโดยอินสแตนซ์ Provider.Key ใน Java

ผู้ให้บริการรายใหม่ที่ใช้งานใน Java ควรใช้งานโดยใช้ BuiltinProvider NativeProvider เลิกใช้งานแล้ว (เรายังไม่ทราบเวลาในการนำออก) และ เข้าถึงคลาสย่อย TransitiveInfoProvider รายการจาก Starlark ไม่ได้

เป้าหมายที่กำหนดค่าแล้ว

เป้าหมายที่กำหนดค่าไว้จะนำไปใช้เป็น RuleConfiguredTargetFactory มี คลาสย่อยสำหรับคลาสกฎแต่ละคลาสที่ใช้งานใน Java เป้าหมายที่กำหนดค่าของ Starlark สร้างขึ้นผ่านทาง StarlarkRuleConfiguredTargetUtil.buildRule()

โรงงานเป้าหมายที่กำหนดค่าไว้ควรใช้ RuleConfiguredTargetBuilder เพื่อ สร้างมูลค่าการแสดงผล ซึ่งประกอบด้วยสิ่งต่อไปนี้

  1. filesToBuild, แนวคิดที่ไม่ชัดเจนของ "ชุดของไฟล์ในกฎนี้ แทน" ไฟล์เหล่านี้คือไฟล์ที่สร้างขึ้นเมื่อเป้าหมายที่กำหนดค่าไว้ อยู่ในบรรทัดคำสั่งหรือใน srcs ของ Genrule
  2. ไฟล์รันไฟล์ ปกติ และข้อมูล
  3. กลุ่มเอาต์พุต นี่คือ "ชุดไฟล์อื่นๆ" ที่หลากหลาย กฎนั้นสามารถ งานสร้าง โดยเข้าถึงได้ผ่านแอตทริบิวต์ 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() ตัวอย่างคือ ชั้นเรียนที่มีชื่อสร้างความสับสนซึ่งมีส่วนร่วมในกระบวนการ:

  1. AspectClass คือการใช้งานในด้านต่างๆ สามารถแสดงใน Java (ในกรณีที่เป็นคลาสย่อย) หรือใน Starlark (ซึ่งในกรณีนี้คือ StarlarkAspectClass) ซึ่งคล้ายกับ RuleConfiguredTargetFactory
  2. AspectDefinition คือคำจำกัดความของลักษณะนี้ จะมี ผู้ให้บริการที่ต้องการ ผู้ให้บริการที่ระบุ และมีการอ้างอิงไปยัง การนำไปใช้ เช่น อินสแตนซ์ AspectClass ที่เหมาะสม ตอนนี้ คล้ายกับ RuleClass
  3. AspectParameters เป็นวิธีแบ่งลักษณะออกเป็นหลายๆ ด้านที่ค่อยๆ ขยายลงไป กราฟทรัพยากร Dependency ค่าปัจจุบันคือสตริงที่เชื่อมกับสตริง ตัวอย่างที่ดี ข้อดีคือบัฟเฟอร์โปรโตคอล ถ้าภาษาหนึ่งมี API หลายรายการ ข้อมูลว่าควรสร้างบัฟเฟอร์โปรโตคอล API ใด สามารถกระจายลงในกราฟทรัพยากร Dependency ได้
  4. Aspect แสดงข้อมูลทั้งหมดที่จำเป็นสำหรับการคำนวณด้านที่ จะกระจายกราฟทรัพยากร Dependency ลงมา ซึ่งประกอบด้วยคลาสด้าน และพารามิเตอร์ของพารามิเตอร์
  5. 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 ส่วนดังนี้

  1. กฎ toolchain() ที่อธิบายชุดการดำเนินการและเป้าหมาย จำกัดที่ Toolchain สนับสนุนและบอกชนิด (เช่น C++ หรือ Java) Toolchain นั้น (รายการหลังจะแสดงด้วยกฎ toolchain_type())
  2. กฎเฉพาะภาษาที่อธิบาย Toolchain จริง (เช่น cc_toolchain())

เนื่องจากเราจําเป็นต้องทราบข้อจํากัดสําหรับ Toolchain เพื่อทำการแปลง Toolchain และเจาะจงภาษา กฎ *_toolchain() ข้อมีข้อมูลมากกว่านั้นมาก จึงใช้เวลามากกว่า เวลาที่ใช้ในการโหลด

แพลตฟอร์มการดำเนินการจะระบุโดยใช้วิธีใดวิธีหนึ่งต่อไปนี้

  1. ในไฟล์ WORKSPACE โดยใช้ฟังก์ชัน register_execution_platforms()
  2. ในบรรทัดคำสั่งโดยใช้บรรทัดคำสั่ง --extra_execution_platforms ตัวเลือก

ชุดของแพลตฟอร์มการดำเนินการที่ใช้ได้จะถูกคำนวณใน RegisteredExecutionPlatformsFunction

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

ชุดของ Toolchain ที่จะใช้สำหรับเป้าหมายที่กำหนดค่าจะกำหนดโดย ToolchainResolutionFunction โดยเป็นฟังก์ชันของสิ่งต่อไปนี้

  • ชุด Toolchain ที่ลงทะเบียนแล้ว (ในไฟล์ WORKSPACE และ การกำหนดค่า)
  • แพลตฟอร์มการดำเนินการและแพลตฟอร์มที่ต้องการ (ในการกำหนดค่า)
  • ชุดของประเภท 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()

คุณระบุสภาพแวดล้อมที่รองรับสำหรับกฎได้หลายวิธี ดังนี้

  1. ผ่านแอตทริบิวต์ restricted_to= นี่คือรูปแบบโดยตรงที่สุดของ ข้อกำหนด กฎดังกล่าวจะประกาศชุดสภาพแวดล้อมที่แน่นอนที่กฎรองรับ สำหรับกลุ่มนี้
  2. ผ่านแอตทริบิวต์ compatible_with= นี่เป็นการประกาศกฎเกี่ยวกับสภาพแวดล้อม รองรับนอกเหนือจาก "มาตรฐาน" สภาพแวดล้อมที่ระบบรองรับ "ค่าเริ่มต้น"
  3. ผ่านแอตทริบิวต์ระดับแพ็กเกจ default_restricted_to= และ default_compatible_with=
  4. ผ่านข้อกำหนดเริ่มต้นในกฎ environment_group() รายการ ทุก เป็นของกลุ่มแอปเทียบเท่าที่มีธีมเกี่ยวข้องกัน (เช่น "CPU สถาปัตยกรรม", "เวอร์ชัน JDK" หรือ "ระบบปฏิบัติการบนอุปกรณ์เคลื่อนที่") คำจำกัดความของกลุ่มสภาพแวดล้อมรวมถึงสภาพแวดล้อมต่อไปนี้ ควรได้รับการรองรับโดย "ค่าเริ่มต้น" หากไม่ได้ระบุไว้เป็นอย่างอื่นโดย แอตทริบิวต์ restricted_to= / environment() รายการ กฎที่ไม่มีกฎเกณฑ์ดังกล่าว จะรับช่วงค่าเริ่มต้นทั้งหมด
  5. ผ่านค่าเริ่มต้นของคลาสกฎ การดำเนินการนี้จะลบล้างค่าเริ่มต้นส่วนกลางสำหรับ อินสแตนซ์ของคลาสกฎที่ระบุ ข้อมูลนี้สามารถใช้เพื่อ กฎ *_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 จะเริ่มปฏิบัติการ) และอาร์ติแฟกต์ที่ดึงมา (สิ่งที่จำเป็นต้อง สร้าง) อาร์ติแฟกต์ที่ได้มาอาจมีได้หลายประเภท ดังนี้

  1. **อาร์ติแฟกต์ปกติ **ข้อมูลเหล่านี้ได้รับการตรวจสอบความล่าสุดโดยการคำนวณ ผลรวมตรวจสอบโดยมีเวลาเป็นทางลัด เราจะไม่ตรวจสอบความถูกต้องของไฟล์หาก เวลาไม่เปลี่ยนแปลง
  2. อาร์ติแฟกต์symlink ที่ยังไม่ได้รับการแก้ไข ซึ่งมีการตรวจสอบความทันสมัยโดย การเรียกใช้ Readlink() อาร์ติแฟกต์เหล่านี้อาจไม่ปลอดภัย ซึ่งต่างจากอาร์ติแฟกต์ทั่วไป ลิงก์สัญลักษณ์ มักใช้ในกรณีที่มีการแพคไฟล์บางไฟล์ลงใน การเก็บถาวรข้อมูลบางประเภท
  3. อาร์ติแฟกต์ของต้นไม้ ไฟล์เหล่านี้ไม่ใช่ไฟล์เดี่ยว แต่เป็นแผนผังไดเรกทอรี โฆษณาเหล่านี้ ตรวจสอบความทันสมัยด้วยการตรวจสอบชุดของไฟล์ในไฟล์และ เนื้อหา โดยจะแสดงเป็น TreeArtifact
  4. อาร์ติแฟกต์ข้อมูลเมตาแบบคงที่ การเปลี่ยนแปลงอาร์ติแฟกต์เหล่านี้จะไม่ทริกเกอร์ สร้างใหม่ ซึ่งจะใช้สําหรับข้อมูลแสตมป์ของบิลด์เท่านั้น เราไม่ต้องการ สร้างใหม่เพียงเพราะเวลาปัจจุบันเปลี่ยนไป

ซึ่งไม่มีเหตุผลพื้นฐานว่าทำไมอาร์ติแฟกต์ต้นทางจึงไม่สามารถเป็นอาร์ติแฟกต์แบบต้นไม้หรือ อาร์ติแฟกต์ 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()

ตรงกันข้ามกับชื่อ แผนที่ เป็นแผนที่จากเส้นทางของอาร์ติแฟกต์ที่ได้รับไปยัง ที่ทำให้โค้ดนั้นแสดงขึ้น การดำเนินการมีคำอธิบายดังนี้

  1. ชุดของไฟล์อินพุตและเอาต์พุตและการตรวจสอบข้อผิดพลาด
  2. "คีย์การดำเนินการ" ซึ่งมักจะเป็นบรรทัดคำสั่งที่เคยมีการลงนาม โดยทั่วไปจะแสดงทุกอย่างที่ไม่ได้บันทึกไว้โดยการตรวจสอบข้อผิดพลาด ไฟล์อินพุต (เช่น สำหรับ FileWriteAction ค่านี้คือ checksum ของข้อมูล เขียนไว้)

นอกจากนี้ยังมี "แคชการดำเนินการจากด้านบน" ซึ่งเป็น "แคชการดำเนินการจากด้านบน" ที่ยังคงอยู่ภายใต้ ซึ่งจะใช้แฮชแบบทรานซิทีฟเพื่อหลีกเลี่ยงการไปที่แคช ครั้ง

การค้นพบอินพุตและการตัดอินพุต

การกระทำบางอย่างซับซ้อนกว่าแค่การมีชุดอินพุต การเปลี่ยนแปลงเป็น ชุดอินพุตของการทำงานมี 2 รูปแบบ ได้แก่

  • การดำเนินการอาจค้นพบข้อมูลใหม่ๆ ก่อนดำเนินการ หรือตัดสินใจว่า ของอินพุตที่ไม่จำเป็น ตัวอย่าง Canonical คือ C++ ที่ซึ่งคุณควรเดาอย่างมีหลักการว่าไฟล์ส่วนหัวใดเป็น C++ จากการปิดแบบทรานซิทีฟ ซึ่งทำให้เราไม่สนว่าจะส่ง ไฟล์ให้กับผู้ดำเนินการระยะไกล เราจึงมีตัวเลือกที่จะไม่ลงทะเบียน ไฟล์ส่วนหัวเป็น "อินพุต" แต่สแกนไฟล์แหล่งที่มาเพื่อหาการส่งผ่าน รวมส่วนหัว และทำเครื่องหมายเฉพาะไฟล์ส่วนหัวเหล่านั้นเป็นอินพุต ที่ระบุไว้ในข้อความ #include (เราประเมินให้สูงไว้ก่อนเพื่อที่จะได้ไม่ต้อง ใช้โปรเซสเซอร์ล่วงหน้าแบบ C เต็มรูปแบบ) ปัจจุบันตัวเลือกนี้ใช้งานสาย "เท็จ" ใน Bazel และมีการใช้งานที่ Google เท่านั้น
  • อาจมีการดำเนินการเกิดขึ้นว่าไฟล์บางไฟล์ไม่ได้ใช้ในระหว่างการดำเนินการ ใน C++ หรือเรียกว่า "ไฟล์ .d" โดยคอมไพเลอร์จะบอกว่าไฟล์ส่วนหัวใด ใช้หลังจากที่เกิดเหตุการณ์นั้น และเพื่อหลีกเลี่ยงความอับอายที่จะเกิดเหตุการณ์ที่แย่ลง ส่วนเพิ่มมากกว่าMake แล้ว Bazel ใช้ประโยชน์จากข้อเท็จจริงนี้ วิธีนี้จะช่วยเพิ่ม ที่มากกว่าเครื่องมือสแกนรวม เนื่องจากต้องใช้คอมไพเลอร์

วิธีดำเนินการต่างๆ มีดังนี้

  1. โทรหา Action.discoverInputs() ซึ่งควรแสดงผลชุดที่ซ้อนกันของ อาร์ติแฟกต์ที่พิจารณาแล้วว่าจําเป็นต้องใช้ รายการเหล่านี้ต้องเป็นอาร์ติแฟกต์ต้นฉบับ เพื่อให้ไม่มีขอบของทรัพยากร Dependency ในกราฟการทำงานที่ไม่มี เทียบเท่าในกราฟเป้าหมายที่กำหนดค่าไว้
  2. การดำเนินการนี้จะเรียกใช้ Action.execute()
  3. เมื่อสิ้น Action.execute() การดำเนินการจะเรียกใช้ได้ Action.updateInputs() เพื่อบอก Bazel ว่าอินพุตบางรายการไม่ได้ ที่จำเป็น ซึ่งอาจส่งผลให้บิลด์เพิ่มขึ้นไม่ถูกต้องหากอินพุตที่ใช้คือ รายงานว่าไม่มีการใช้งานแล้ว

เมื่อแคชการดำเนินการแสดง Hit ในอินสแตนซ์การดำเนินการใหม่ (เช่น สร้างแล้ว หลังจากรีสตาร์ทเซิร์ฟเวอร์) Bazel จะเรียก updateInputs() ด้วยตัวเอง ดังนั้นชุดของ จะสะท้อนผลลัพธ์ของการค้นพบและการตัดอินพุตที่เคยทำก่อนหน้านี้

การดำเนินการของ Starlark ใช้ประโยชน์จากสถานบริการเพื่อประกาศว่าอินพุตบางรายการเป็นไม่ได้ใช้ โดยใช้อาร์กิวเมนต์ unused_inputs_list= ของ ctx.actions.run()

วิธีเรียกใช้การกระทำต่างๆ: กลยุทธ์/ActionContexts

การดำเนินการบางอย่างสามารถเรียกใช้ได้หลายวิธี ตัวอย่างเช่น บรรทัดคำสั่งอาจเป็น ดำเนินการภายใน ภายใน แต่ในแซนด์บ็อกซ์ประเภทต่างๆ หรือจากระยะไกล แนวคิดที่รวมสิ่งนี้เรียกว่า ActionContext (หรือ Strategy เนื่องจากเรา ก็ทำสำเร็จไปได้ครึ่งทางด้วยการเปลี่ยนชื่อ...)

วงจรชีวิตของบริบทการดำเนินการมีดังนี้

  1. เมื่อเริ่มระยะการดำเนินการ ระบบจะถามอินสแตนซ์ BlazeModule รายการว่า บริบทของการดำเนินการที่พวกเขามี สิ่งนี้เกิดขึ้นในเครื่องมือสร้างของ ExecutionTool ประเภทบริบทการดำเนินการจะระบุโดย Java Class อินสแตนซ์ที่อ้างถึงอินเทอร์เฟซย่อยของ ActionContext และ ที่บริบทการดำเนินการต้องใช้
  2. เลือกบริบทการดำเนินการที่เหมาะสมจากรายการที่มีอยู่ และ ส่งต่อไปยัง ActionExecutionContext และ BlazeExecutor
  3. การดำเนินการขอบริบทโดยใช้ 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 รายการ ดังนี้

  1. หากการกำหนดค่า 2 รายการเกิดขึ้นในบิลด์เดียวกันได้ การกำหนดค่า แต่ละไดเรกทอรีเพื่อให้ทั้งสองมีเวอร์ชันของตัวเอง action; มิฉะนั้น หากการกำหนดค่าทั้งสองขัดแย้งกัน เช่น คำสั่ง ของการทำงานซึ่งสร้างไฟล์เอาต์พุตเดียวกัน Bazel ไม่ทราบว่า การทำงานเพื่อเลือก ("ความขัดแย้งในการกระทำ")
  2. หากการกำหนดค่า 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>

การดึงข้อมูลที่เก็บจะเกิดขึ้นตามขั้นตอนต่อไปนี้

  1. PackageLookupFunction พบว่าต้องการที่เก็บและสร้าง RepositoryName ในรูปของ SkyKey ซึ่งเรียกใช้ RepositoryLoaderFunction
  2. RepositoryLoaderFunction ส่งต่อคำขอไปยัง RepositoryDelegatorFunction ด้วยเหตุผลที่ไม่ชัดเจน (รหัสระบุว่าเป็น หลีกเลี่ยงการดาวน์โหลดอะไรใหม่ในกรณีที่ Skyframe รีสตาร์ท แต่ การให้เหตุผลที่ชัดเจน)
  3. RepositoryDelegatorFunction ค้นหากฎที่เก็บที่ได้รับการร้องขอ ดึงข้อมูลโดยทำซ้ำบางส่วนของไฟล์ WORKSPACE จนกว่าจะส่งคำขอ พบที่เก็บ
  4. พบ RepositoryFunction ที่เหมาะสมซึ่งใช้ที่เก็บ fetching; เป็นการใช้งานที่เก็บของ Starlark หรือ แผนที่แบบฮาร์ดโค้ดสำหรับที่เก็บซึ่งนำไปใช้ใน Java

การแคชมีหลายเลเยอร์ เนื่องจากการดึงข้อมูลที่เก็บสามารถทำได้ แพง:

  1. มีแคชสำหรับไฟล์ที่ดาวน์โหลดซึ่งถูกคีย์โดยการตรวจสอบข้อผิดพลาด (RepositoryCache) ซึ่งต้องมี checksum อยู่ภายใน ไฟล์ WORKSPACE แต่ยังคงเก็บรายละเอียดได้ แชร์โดย อินสแตนซ์ของเซิร์ฟเวอร์ Bazel ทุกรายการในเวิร์กสเตชันเดียวกัน โดยไม่คำนึงว่าอินสแตนซ์ใด พื้นที่ทำงานหรือฐานเอาต์พุตที่ใช้งานอยู่
  2. "ไฟล์เครื่องหมาย" เขียนสำหรับที่เก็บแต่ละแห่งภายใต้ $OUTPUT_BASE/external ที่มี checksum ของกฎที่ใช้ในการดึงข้อมูล ถ้าต้นบาเซล เซิร์ฟเวอร์รีสตาร์ท แต่ checksum ไม่เปลี่ยนแปลง และจะไม่มีการดึงข้อมูลใหม่ ช่วงเวลานี้ จะนำมาใช้ใน RepositoryDelegatorFunction.DigestWriter
  3. ตัวเลือกบรรทัดคำสั่ง --distdir จะกำหนดแคชอื่นที่ใช้เพื่อ ค้นหาอาร์ติแฟกต์ที่จะดาวน์โหลด ฟีเจอร์นี้มีประโยชน์ในการตั้งค่าองค์กร โดย Bazel ไม่ควร ดึงข้อมูลแบบสุ่มจากอินเทอร์เน็ต นี่คือ มีการใช้งานโดย DownloadManager

เมื่อดาวน์โหลดที่เก็บแล้ว ระบบจะถือว่าอาร์ติแฟกต์ในที่เก็บเป็นต้นฉบับ และอาร์ติแฟกต์อื่นๆ ปัญหานี้อาจก่อให้เกิดปัญหาเนื่องจาก Bazel จะตรวจสอบความเป็นปัจจุบันอยู่เสมอ ของอาร์ติแฟกต์ต้นฉบับด้วยการเรียก stat() ที่มี และอาร์ติแฟกต์เหล่านี้ยัง จะใช้ไม่ได้เมื่อมีการเปลี่ยนแปลงคำจำกัดความของที่เก็บ ดังนั้น FileStateValue สำหรับอาร์ติแฟกต์ในที่เก็บภายนอกจะต้องใช้ ที่เก็บภายนอกของตนได้ ซึ่งจัดการโดย ExternalFilesHelper

ไดเรกทอรีที่มีการจัดการ

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

  1. อนุญาตให้ผู้ใช้ระบุไดเรกทอรีย่อยของพื้นที่ทำงาน Bazel สามารถเข้าถึงได้ รายชื่อเหล่านี้แสดงอยู่ในไฟล์ชื่อ .bazelignore และ มีการติดตั้งใช้งานฟังก์ชันนี้ใน BlacklistedPackagePrefixesFunction
  2. เราเข้ารหัสการแมปจากไดเรกทอรีย่อยของพื้นที่ทำงานไปยังภายนอก ManagedDirectoriesKnowledge และจัดการ FileStateValue อ้างอิงถึงรายการเหล่านี้ในลักษณะเดียวกับรายการปกติ ที่เก็บภายนอกได้

การแมปที่เก็บ

ที่เก็บหลายแหล่งอาจ ต้องอาศัยที่เก็บเดียวกัน แต่ในเวอร์ชันที่ต่างกัน (นี่เป็นอินสแตนซ์ของ "ทรัพยากร 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 นั้น_ ส่วนใหญ่เป็น _write ใน 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 ประเภท ได้แก่

  1. โซลูชันที่นำไปใช้โดยใช้กรอบการทดสอบแบบ Bash ที่ละเอียดประณีตภายใต้ src/test/shell
  2. เบราว์เซอร์ที่ติดตั้งใช้งานใน Java ตัวแปรเหล่านี้นำไปใช้เป็นคลาสย่อยของ BuildIntegrationTestCase

BuildIntegrationTestCase เป็นเฟรมเวิร์กการทดสอบการผสานรวมที่แนะนำเนื่องจาก ก็เพียบพร้อมสำหรับสถานการณ์การทดสอบส่วนใหญ่ เนื่องจากเป็นเฟรมเวิร์ก Java ให้ความสามารถในการแก้ไขข้อบกพร่องและการผสานรวมที่ราบรื่นกับการพัฒนาทั่วไปจำนวนมาก และเครื่องมือการประมาณที่กำหนดได้เอง มีตัวอย่างของ BuildIntegrationTestCase คลาสใน ที่เก็บ Bazel

ใช้การทดสอบการวิเคราะห์เป็นคลาสย่อยของ BuildViewTestCase มี ระบบไฟล์ Scratch ที่คุณสามารถใช้เพื่อเขียนไฟล์ BUILD ตามด้วยตัวช่วยต่างๆ สามารถขอเป้าหมายที่กำหนดค่าแล้ว เปลี่ยนการกำหนดค่า และยืนยัน หลายอย่างเกี่ยวกับผลการวิเคราะห์