กฎ

รายงานปัญหา ดูแหล่งที่มา

กฎกำหนดชุดของการดำเนินการที่ Bazel ดำเนินการกับอินพุตเพื่อสร้างชุดเอาต์พุต ซึ่งอ้างอิงในผู้ให้บริการที่แสดงผลโดยฟังก์ชันการใช้งานของกฎดังกล่าว เช่น กฎไบนารี C++ อาจ

  1. รับชุดไฟล์ต้นฉบับ (อินพุต) .cpp ชุด
  2. เรียกใช้ g++ ในไฟล์ต้นฉบับ (การดำเนินการ)
  3. แสดงผลผู้ให้บริการ DefaultInfo พร้อมเอาต์พุตที่เป็นไฟล์ปฏิบัติการและไฟล์อื่นๆ เพื่อให้พร้อมใช้งานระหว่างรันไทม์
  4. แสดงผลผู้ให้บริการ CcInfo พร้อมข้อมูลเฉพาะ C++ ที่รวบรวมจากเป้าหมายและทรัพยากร Dependency

จากมุมมองของ Bazel นั้น g++ และไลบรารี C++ มาตรฐานเป็นอินพุตสำหรับกฎนี้ด้วย ในฐานะผู้เขียนกฎ คุณต้องไม่เพียงพิจารณาข้อมูลที่ได้จากผู้ใช้สำหรับกฎเท่านั้น แต่ยังรวมถึงเครื่องมือและไลบรารีทั้งหมดที่จำเป็นต่อการดำเนินการตามกฎด้วย

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

Bazel สร้างขึ้นโดยกฎ 2-3 ข้อ กฎเนทีฟเหล่านี้ เช่น genrule และ filegroup ให้การสนับสนุนหลักบางอย่าง เมื่อกำหนดกฎของคุณเอง คุณจะเพิ่มการรองรับภาษาและเครื่องมือที่ Bazel ไม่ได้รองรับโดยค่าเริ่มต้นได้

Bazel ให้โมเดลการขยายการใช้งานสำหรับกฎการเขียนโดยใช้ภาษา Starlark กฎเหล่านี้เขียนไว้ใน .bzl ไฟล์ ซึ่งโหลดได้จาก BUILD ไฟล์ได้โดยตรง

เมื่อกำหนดกฎของคุณเอง คุณจะต้องเลือกแอตทริบิวต์ที่กฎรองรับและวิธีที่กฎดังกล่าวสร้างเอาต์พุต

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

การสร้างกฎ

ในไฟล์ .bzl ให้ใช้ฟังก์ชันกฎเพื่อตั้งกฎใหม่ และจัดเก็บผลลัพธ์เป็นตัวแปรร่วม การเรียก rule จะระบุแอตทริบิวต์และฟังก์ชันการใช้งาน ดังนี้

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "deps": attr.label_list(),
        ...
    },
)

การดำเนินการนี้จะกำหนดชนิดของกฎที่ชื่อว่า example_library

การเรียกใช้ rule ยังต้องระบุด้วยหากกฎสร้างเอาต์พุตปฏิบัติการ (มี executable = True) หรือเฉพาะไฟล์ปฏิบัติการทดสอบ (มี test = True) หากหลัง กฎจะเป็นกฎทดสอบ และชื่อของกฎต้องลงท้ายด้วย _test

อินสแตนซ์เป้าหมาย

คุณสามารถโหลดกฎและเรียกใช้กฎได้ใน BUILD ไฟล์ ดังนี้

load('//some/pkg:rules.bzl', 'example_library')

example_library(
    name = "example_target",
    deps = [":another_target"],
    ...
)

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

สามารถเรียกใช้กฎจากฟังก์ชัน Starlark และโหลดในไฟล์ .bzl ได้ด้วย ฟังก์ชัน Starlark ที่กฎการเรียกเรียกว่ามาโครของ Starlark ท้ายที่สุดแล้วจะต้องเรียกใช้มาโคร Starlark จากไฟล์ BUILD และจะเรียกได้เฉพาะระหว่างระยะการโหลดเท่านั้นเมื่อมีการประเมินไฟล์ BUILD เพื่อสร้างอินสแตนซ์เป้าหมาย

Attributes

attributeคืออาร์กิวเมนต์ของกฎ แอตทริบิวต์อาจให้ค่าเฉพาะแก่การใช้งานของเป้าหมาย หรืออาจอ้างอิงไปยังเป้าหมายอื่นๆ ก็ได้ เพื่อสร้างกราฟการอ้างอิง

แอตทริบิวต์เฉพาะของกฎ เช่น srcs หรือ deps จะกำหนดโดยการส่งแมปจากชื่อแอตทริบิวต์ไปยังสคีมา (สร้างโดยใช้โมดูล attr) ไปยังพารามิเตอร์ attrs ของ rule แอตทริบิวต์ทั่วไป เช่น name และ visibility จะเพิ่มลงในกฎทั้งหมดโดยปริยาย ระบบจะเพิ่มแอตทริบิวต์เพิ่มเติมลงในกฎปฏิบัติการและกฎการทดสอบโดยปริยาย ระบบจะไม่รวมแอตทริบิวต์ที่เพิ่มลงในกฎโดยปริยายในพจนานุกรมที่ส่งไปยัง attrs

แอตทริบิวต์การขึ้นต่อกัน

กฎที่ประมวลผลซอร์สโค้ดมักจะกำหนดแอตทริบิวต์ต่อไปนี้เพื่อจัดการทรัพยากร Dependency ประเภทต่างๆ

  • srcs จะระบุไฟล์ต้นฉบับที่ประมวลผลโดยการดำเนินการของเป้าหมาย สคีมาแอตทริบิวต์มักจะระบุนามสกุลไฟล์ที่คาดหวังสำหรับไฟล์ต้นทางที่ประมวลผลกฎ โดยทั่วไปแล้ว กฎสำหรับภาษาที่มีไฟล์ส่วนหัวจะระบุแอตทริบิวต์ hdrs แยกต่างหากสำหรับส่วนหัวที่ประมวลผลโดยกลุ่มเป้าหมายและผู้บริโภค
  • deps ระบุทรัพยากร Dependency ของโค้ดสำหรับเป้าหมาย สคีมาของแอตทริบิวต์ควรระบุว่าทรัพยากร Dependency เหล่านั้นต้องระบุผู้ให้บริการรายใด (เช่น cc_library จะระบุเป็น CcInfo)
  • data ระบุไฟล์ที่จะใช้ได้ขณะรันไทม์สำหรับไฟล์ปฏิบัติการใดๆ โดยขึ้นอยู่กับเป้าหมาย ซึ่งจะช่วยให้สามารถระบุไฟล์ได้ตามต้องการ
example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = [".example"]),
        "hdrs": attr.label_list(allow_files = [".header"]),
        "deps": attr.label_list(providers = [ExampleInfo]),
        "data": attr.label_list(allow_files = True),
        ...
    },
)

ต่อไปนี้คือตัวอย่างของแอตทริบิวต์การขึ้นต่อกัน แอตทริบิวต์ที่ระบุป้ายกำกับอินพุต (ที่กำหนดด้วย attr.label_list, attr.label หรือ attr.label_keyed_string_dict) จะระบุทรัพยากร Dependency ของประเภทหนึ่งๆ ระหว่างเป้าหมายและเป้าหมายที่มีป้ายกำกับ (หรือออบเจ็กต์Label ที่สอดคล้องกัน) แสดงอยู่ในแอตทริบิวต์ดังกล่าวเมื่อกำหนดเป้าหมาย ระบบจะแก้ไขที่เก็บและอาจมีเส้นทางสำหรับป้ายกำกับเหล่านี้ โดยสัมพันธ์กับเป้าหมายที่กำหนดไว้

example_library(
    name = "my_target",
    deps = [":other_target"],
)

example_library(
    name = "other_target",
    ...
)

ในตัวอย่างนี้ other_target เป็นทรัพยากร Dependency ของ my_target ดังนั้นจึงได้รับการวิเคราะห์ other_target ก่อน นี่เป็นข้อผิดพลาดหากมีวงจรในกราฟการอ้างอิงของเป้าหมาย

แอตทริบิวต์ส่วนตัวและการอ้างอิงโดยนัย

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

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

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        ...
        "_compiler": attr.label(
            default = Label("//tools:example_compiler"),
            allow_single_file = True,
            executable = True,
            cfg = "exec",
        ),
    },
)

ในตัวอย่างนี้ เป้าหมายทุกประเภท example_library มีการขึ้นต่อกันของคอมไพเลอร์ //tools:example_compiler วิธีนี้จะช่วยให้ฟังก์ชันการใช้งานของ example_library สร้างการดำเนินการที่เรียกใช้คอมไพเลอร์ แม้ว่าผู้ใช้จะไม่ส่งป้ายกำกับเป็นอินพุตก็ตาม เนื่องจาก _compiler เป็นแอตทริบิวต์ส่วนตัว จึงจะเป็นไปตามที่ ctx.attr._compiler จะชี้ไปยัง //tools:example_compiler ในเป้าหมายทั้งหมดของกฎประเภทนี้เสมอ หรือตั้งชื่อแอตทริบิวต์ compiler โดยไม่มีขีดล่างและใช้ค่าเริ่มต้น วิธีนี้ช่วยให้ผู้ใช้แทนที่คอมไพเลอร์อื่นได้หากจําเป็น แต่ไม่จําเป็นต้องทราบป้ายกํากับของคอมไพเลอร์

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

แอตทริบิวต์เอาต์พุต

แอตทริบิวต์เอาต์พุต เช่น attr.output และ attr.output_list จะประกาศไฟล์เอาต์พุตที่เป้าหมายสร้างขึ้น ซึ่งแตกต่างจากแอตทริบิวต์ทรัพยากร Dependency ใน 2 ลักษณะดังนี้

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

โดยปกติแล้ว แอตทริบิวต์เอาต์พุตจะใช้เมื่อกฎต้องสร้างเอาต์พุตที่มีชื่อกำหนดโดยผู้ใช้เท่านั้น ซึ่งจะอิงจากชื่อเป้าหมายไม่ได้ หากกฎมีแอตทริบิวต์เอาต์พุต 1 รายการ โดยทั่วไปจะมีชื่อว่า out หรือ outs

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

ฟังก์ชันการใช้งาน

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

โดยปกติแล้ว ฟังก์ชันการใช้งานกฎจะเป็นแบบส่วนตัว (ตั้งชื่อด้วยขีดล่าง) โดยโดยปกติจะใช้ชื่อเหมือนกับกฎ แต่ต่อท้ายด้วย _impl

ฟังก์ชันการใช้งานจะมีพารามิเตอร์เพียงตัวเดียว ซึ่งก็คือบริบทกฎ ซึ่งเรียกตามธรรมเนียมว่า ctx และแสดงรายชื่อผู้ให้บริการ

เป้าหมาย

การขึ้นต่อกันจะแสดงในขณะที่วิเคราะห์เป็นออบเจ็กต์ Target ออบเจ็กต์เหล่านี้มี providers ที่สร้างขึ้นเมื่อมีการเรียกใช้ฟังก์ชันการใช้งานของเป้าหมาย

ctx.attr มีช่องที่สอดคล้องกับชื่อของแอตทริบิวต์การขึ้นต่อกันแต่ละรายการ โดยมีออบเจ็กต์ Target รายการที่แสดงถึงการขึ้นต่อกันโดยตรงแต่ละรายการโดยใช้แอตทริบิวต์นั้น สำหรับแอตทริบิวต์ label_list นี่คือรายการของ Targets สำหรับแอตทริบิวต์ label นี่คือ Target หรือ None รายการเดียว

รายการออบเจ็กต์ผู้ให้บริการจะแสดงโดยฟังก์ชันการใช้งานของเป้าหมาย ดังนี้

return [ExampleInfo(headers = depset(...))]

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

ตัวอย่างเช่น หากกฎใช้ไฟล์ส่วนหัวโดยใช้แอตทริบิวต์ hdrs และระบุไฟล์ส่วนหัวแก่การดำเนินการรวบรวมของเป้าหมายและผู้บริโภค กฎอาจรวบรวมไฟล์ในลักษณะต่อไปนี้

def _example_library_impl(ctx):
    ...
    transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]

เรามีสไตล์โครงสร้างเดิมที่เราไม่แนะนําอย่างยิ่งและควรย้ายข้อมูลกฎออกไป

Files

ไฟล์จะแสดงด้วยออบเจ็กต์ File เนื่องจาก Bazel ไม่ได้ใช้ I/O ของไฟล์ในระหว่างขั้นตอนการวิเคราะห์ ออบเจ็กต์เหล่านี้จึงอ่านหรือเขียนเนื้อหาไฟล์โดยตรงไม่ได้ แต่จะส่งไปยังฟังก์ชันส่งการดำเนินการ (ดู ctx.actions) เพื่อสร้างกราฟการดำเนินการ

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

สำหรับแอตทริบิวต์ทรัพยากร Dependency แต่ละรายการ ช่องที่เกี่ยวข้องของ ctx.files มีรายการเอาต์พุตเริ่มต้นของทรัพยากร Dependency ทั้งหมดที่ใช้แอตทริบิวต์ดังกล่าว ดังนี้

def _example_library_impl(ctx):
    ...
    headers = depset(ctx.files.hdrs, transitive = transitive_headers)
    srcs = ctx.files.srcs
    ...

ctx.file มี File หรือ None รายการเดียวสำหรับแอตทริบิวต์การขึ้นต่อกันที่มีชุดข้อมูลจำเพาะเป็น allow_single_file = True ctx.executable ทำงานเหมือนกับ ctx.file แต่จะมีเฉพาะช่องสำหรับแอตทริบิวต์ทรัพยากร Dependency ที่ตั้งค่าข้อกำหนดของ executable = True ไว้

การประกาศเอาต์พุต

ในระหว่างขั้นตอนการวิเคราะห์ ฟังก์ชันการใช้งานของกฎจะสร้างเอาต์พุตได้ เนื่องจากระบบต้องทราบป้ายกำกับทั้งหมดในช่วงการโหลด เอาต์พุตเพิ่มเติมเหล่านี้จึงไม่มีป้ายกำกับ คุณสร้างออบเจ็กต์ File รายการสำหรับเอาต์พุตได้โดยใช้ ctx.actions.declare_file และ ctx.actions.declare_directory ชื่อของเอาต์พุตมักจะอิงตามชื่อของเป้าหมาย ctx.label.name ดังนี้

def _example_library_impl(ctx):
  ...
  output_file = ctx.actions.declare_file(ctx.label.name + ".output")
  ...

สำหรับเอาต์พุตที่ประกาศไว้ล่วงหน้า เช่น เอาต์พุตที่สร้างขึ้นสำหรับแอตทริบิวต์เอาต์พุต ระบบจะดึงออบเจ็กต์ File ขึ้นมาจากช่องที่เกี่ยวข้องของ ctx.outputs แทน

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

การดำเนินการอธิบายวิธีสร้างชุดเอาต์พุตจากชุดอินพุต เช่น "เรียกใช้ gcc ใน hello.c และรับ hello.o" เมื่อสร้างการทำงานแล้ว Bazel จะไม่เรียกใช้คำสั่งโดยทันที โดยจะบันทึกอยู่ในกราฟของทรัพยากร Dependency เนื่องจากการดำเนินการอาจขึ้นอยู่กับผลลัพธ์ของการดำเนินการอื่น เช่น ใน C จะต้องมีการเรียกตัวลิงก์หลังคอมไพเลอร์

ฟังก์ชันอเนกประสงค์ที่สร้างการดำเนินการจะกำหนดไว้ใน ctx.actions ดังต่อไปนี้

ctx.actions.args จะใช้เพื่อสะสมอาร์กิวเมนต์สำหรับการดำเนินการได้อย่างมีประสิทธิภาพ หลีกเลี่ยงไม่ให้มีการปรับค่าคงที่จนกว่าจะเวลาดำเนินการ

def _example_library_impl(ctx):
    ...

    transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
    headers = depset(ctx.files.hdrs, transitive = transitive_headers)
    srcs = ctx.files.srcs
    inputs = depset(srcs, transitive = [headers])
    output_file = ctx.actions.declare_file(ctx.label.name + ".output")

    args = ctx.actions.args()
    args.add_joined("-h", headers, join_with = ",")
    args.add_joined("-s", srcs, join_with = ",")
    args.add("-o", output_file)

    ctx.actions.run(
        mnemonic = "ExampleCompile",
        executable = ctx.executable._compiler,
        arguments = [args],
        inputs = inputs,
        outputs = [output_file],
    )
    ...

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

การดำเนินการต้องแสดงรายการข้อมูลทั้งหมด อนุญาตให้ใช้อินพุตรายการ ที่ไม่ได้ใช้ แต่ไม่มีประสิทธิภาพ

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

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

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

ผู้ให้บริการ

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

เนื่องจากฟังก์ชันการใช้งานของกฎจะอ่านได้เฉพาะผู้ให้บริการจากทรัพยากร Dependency ของเป้าหมายที่ตั้งไว้เท่านั้น กฎจึงต้องส่งต่อข้อมูลใดๆ จากทรัพยากร Dependency ของเป้าหมายที่ผู้บริโภคเป้าหมายต้องทราบ โดยทั่วไปแล้วด้วยการสะสมข้อมูลนั้นลงใน depset

ระบบจะระบุผู้ให้บริการของเป้าหมายโดยรายการออบเจ็กต์ผู้ให้บริการที่แสดงผลโดยฟังก์ชันการใช้งาน

นอกจากนี้ คุณยังเขียนฟังก์ชันการใช้งานแบบเก่าในสไตล์เดิมได้ ซึ่งฟังก์ชันการใช้งานจะแสดงผล struct แทนรายการออบเจ็กต์ผู้ให้บริการ เราไม่สนับสนุนให้ใช้รูปแบบนี้อย่างยิ่งและควรย้ายข้อมูลกฎออกไป

เอาต์พุตเริ่มต้น

เอาต์พุตเริ่มต้นของเป้าหมายเป็นเอาต์พุตที่ขอโดยค่าเริ่มต้นเมื่อมีการขอเป้าหมายสําหรับบิลด์ที่บรรทัดคำสั่ง เช่น เป้าหมาย java_library //pkg:foo มี foo.jar เป็นเอาต์พุตเริ่มต้น ดังนั้นระบบจะสร้างด้วยคำสั่ง bazel build //pkg:foo

เอาต์พุตเริ่มต้นจะกำหนดโดยพารามิเตอร์ files ของ DefaultInfo ดังนี้

def _example_library_impl(ctx):
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        ...
    ]

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

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

ไฟล์รัน

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

คุณสามารถเพิ่มไฟล์ Runfile ด้วยตนเองได้ในระหว่างการสร้างกฎ คุณสามารถสร้างออบเจ็กต์ runfiles ได้ด้วยเมธอด runfiles ในบริบทของกฎ ctx.runfiles และส่งไปยังพารามิเตอร์ runfiles ใน DefaultInfo ระบบจะเพิ่มเอาต์พุตที่เป็นไฟล์ปฏิบัติการของกฎปฏิบัติการลงใน Runfile โดยปริยาย

กฎบางอย่างจะระบุแอตทริบิวต์ซึ่งมักตั้งชื่อว่า data ซึ่งมีเอาต์พุตที่จะเพิ่มไปยัง Runfile ของเป้าหมาย ควรรวม Runfile จาก data รวมถึงจากแอตทริบิวต์ที่อาจระบุโค้ดสำหรับการดำเนินการในท้ายที่สุดสำหรับการดำเนินการในท้ายที่สุด โดยทั่วไปคือ srcs (ซึ่งอาจมี filegroup เป้าหมายที่เชื่อมโยง data) และ deps

def _example_library_impl(ctx):
    ...
    runfiles = ctx.runfiles(files = ctx.files.data)
    transitive_runfiles = []
    for runfiles_attr in (
        ctx.attr.srcs,
        ctx.attr.hdrs,
        ctx.attr.deps,
        ctx.attr.data,
    ):
        for target in runfiles_attr:
            transitive_runfiles.append(target[DefaultInfo].default_runfiles)
    runfiles = runfiles.merge_all(transitive_runfiles)
    return [
        DefaultInfo(..., runfiles = runfiles),
        ...
    ]

ผู้ให้บริการที่กำหนดเอง

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

ExampleInfo = provider(
    "Info needed to compile/link Example code.",
    fields = {
        "headers": "depset of header Files from transitive dependencies.",
        "files_to_link": "depset of Files from compilation.",
    },
)

จากนั้นฟังก์ชันการใช้งานกฎจะสร้างและแสดงผลอินสแตนซ์ผู้ให้บริการได้ดังนี้

def _example_library_impl(ctx):
  ...
  return [
      ...
      ExampleInfo(
          headers = headers,
          files_to_link = depset(
              [output_file],
              transitive = [
                  dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
              ],
          ),
      )
  ]
การเริ่มต้นผู้ให้บริการที่กำหนดเอง

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

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

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

ในทางตรงกันข้ามตัวสร้างดิบจะข้ามโค้ดเรียกกลับ init

ตัวอย่างต่อไปนี้ใช้ init ในการประมวลผลและตรวจสอบอาร์กิวเมนต์ล่วงหน้า

# //pkg:exampleinfo.bzl

_core_headers = [...]  # private constant representing standard library files

# Keyword-only arguments are preferred.
def _exampleinfo_init(*, files_to_link, headers = None, allow_empty_files_to_link = False):
    if not files_to_link and not allow_empty_files_to_link:
        fail("files_to_link may not be empty")
    all_headers = depset(_core_headers, transitive = headers)
    return {"files_to_link": files_to_link, "headers": all_headers}

ExampleInfo, _new_exampleinfo = provider(
    fields = ["files_to_link", "headers"],
    init = _exampleinfo_init,
)

จากนั้นการใช้กฎอาจสร้างอินสแตนซ์ของผู้ให้บริการดังนี้

ExampleInfo(
    files_to_link = my_files_to_link,  # may not be empty
    headers = my_headers,  # will automatically include the core headers
)

ตัวสร้างดิบสามารถใช้เพื่อกำหนดฟังก์ชันโรงงานสาธารณะทางเลือกที่ไม่ผ่านตรรกะ init เช่น exampleinfo.bzl อาจให้คำจำกัดความดังนี้

def make_barebones_exampleinfo(headers):
    """Returns an ExampleInfo with no files_to_link and only the specified headers."""
    return _new_exampleinfo(files_to_link = depset(), headers = all_headers)

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

การใช้ init อีกอย่างหนึ่งคือป้องกันไม่ให้ผู้ใช้เรียกสัญลักษณ์ผู้ให้บริการไปเลย และบังคับให้ใช้ฟังก์ชัน เริ่มต้นแทน ดังนี้

def _exampleinfo_init_banned(*args, **kwargs):
    fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")

ExampleInfo, _new_exampleinfo = provider(
    ...
    init = _exampleinfo_init_banned)

def make_exampleinfo(...):
    ...
    return _new_exampleinfo(...)

กฎที่ปฏิบัติได้และกฎการทดสอบ

กฎที่ปฏิบัติได้จะกำหนดเป้าหมายที่เรียกใช้ได้ด้วยคำสั่ง bazel run กฎการทดสอบเป็นกฎพิเศษที่เรียกใช้ได้ซึ่งมีคำสั่ง bazel test เรียกใช้เป้าหมายได้ด้วย ระบบจะสร้างกฎที่ปฏิบัติได้และกฎการทดสอบโดยการตั้งค่าอาร์กิวเมนต์ executable หรือ test ที่เกี่ยวข้องเป็น True ในการเรียกใช้ rule ดังนี้

example_binary = rule(
   implementation = _example_binary_impl,
   executable = True,
   ...
)

example_test = rule(
   implementation = _example_binary_impl,
   test = True,
   ...
)

กฎการทดสอบต้องมีชื่อที่ลงท้ายด้วย _test (ชื่อเป้าหมายทดสอบมักจะลงท้ายด้วย _test แต่ไม่จำเป็นต้องเป็นแบบนี้) กฎที่ไม่ใช่การทดสอบต้องไม่มี ส่วนต่อท้ายนี้

กฎทั้ง 2 ประเภทต้องสร้างไฟล์เอาต์พุตที่เป็นไฟล์ปฏิบัติการ (ซึ่งอาจมีหรือไม่ได้ประกาศไว้ล่วงหน้า) ซึ่งคำสั่ง run หรือ test จะเรียกใช้ หากต้องการบอก Bazel ว่าจะใช้เอาต์พุตของกฎใดเป็นไฟล์ปฏิบัติการนี้ ให้ส่งเป็นอาร์กิวเมนต์ executable ของผู้ให้บริการ DefaultInfo ที่ส่งคืน ไฟล์ executable ดังกล่าวจะเพิ่มลงในเอาต์พุตเริ่มต้นของกฎ (คุณจึงไม่ต้องส่งให้กับทั้ง executable และ files) และเพิ่มไปยัง runfiles โดยนัยด้วยดังนี้

def _example_binary_impl(ctx):
    executable = ctx.actions.declare_file(ctx.label.name)
    ...
    return [
        DefaultInfo(executable = executable, ...),
        ...
    ]

การดำเนินการที่สร้างไฟล์นี้ต้องตั้งค่าบิตที่ดำเนินการได้ในไฟล์ สำหรับการดำเนินการ ctx.actions.run หรือ ctx.actions.run_shell ควรดำเนินการโดยเครื่องมือที่สำคัญที่การดำเนินการดังกล่าวเรียกใช้ สำหรับการดำเนินการ ctx.actions.write ให้ผ่าน is_executable = True

ในฐานะลักษณะการทำงานเดิม กฎที่เรียกใช้ได้จะมีเอาต์พุตที่ประกาศไว้ล่วงหน้า ctx.outputs.executable แบบพิเศษ ไฟล์นี้ทำหน้าที่เป็นไฟล์ปฏิบัติการเริ่มต้นหากคุณไม่ได้ระบุไฟล์โดยใช้ DefaultInfo มิฉะนั้นต้องไม่ใช้ เลิกใช้งานกลไกเอาต์พุตนี้แล้วเนื่องจากไม่รองรับการปรับแต่งชื่อไฟล์ปฏิบัติการในเวลาวิเคราะห์

ดูตัวอย่างกฎปฏิบัติการและกฎทดสอบ

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

def example_test(size = "small", **kwargs):
  _example_test(size = size, **kwargs)

_example_test = rule(
 ...
)

ตำแหน่ง Runfile

เมื่อเป้าหมายที่เรียกใช้ได้ทำงานด้วย bazel run (หรือ test) รูทของไดเรกทอรี Runfiles จะอยู่ติดกับไฟล์ปฏิบัติการ เส้นทางมีความเกี่ยวข้องกันดังนี้

# Given launcher_path and runfile_file:
runfiles_root = launcher_path.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
    runfiles_root, workspace_name, runfile_path)

เส้นทางไปยัง File ภายใต้ไดเรกทอรี Runfiles จะสอดคล้องกับ File.short_path

ไบนารีที่เรียกใช้โดย bazel โดยตรงจะอยู่ติดกับรากของไดเรกทอรี runfiles อย่างไรก็ตาม ไบนารีที่เรียกว่า from สำหรับไฟล์เรียกใช้จะไม่สามารถตั้งสมมติฐานเดียวกันได้ เพื่อลดปัญหานี้ ไบนารีแต่ละรายการควรบอกวิธียอมรับรูทของ Runfile เป็นพารามิเตอร์โดยใช้สภาพแวดล้อม หรืออาร์กิวเมนต์หรือแฟล็กบรรทัดคำสั่ง ซึ่งจะช่วยให้ไบนารีสามารถส่งรูทไฟล์ Canonical ที่ถูกต้องไปยังไบนารีที่เรียกใช้ได้ หากไม่ได้ตั้งค่า ไบนารีจะเดาได้ว่าเป็นไบนารีแรกที่มีการเรียกใช้ และมองหาไดเรกทอรี Runfile ที่อยู่ติดกัน

หัวข้อขั้นสูง

การขอไฟล์เอาต์พุต

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

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

นอกเหนือจากเอาต์พุตเริ่มต้นแล้ว ยังมีกลุ่มเอาต์พุตซึ่งเป็นคอลเล็กชันของไฟล์เอาต์พุตที่อาจขอด้วยกันได้ คุณส่งคำขอเหล่านี้ได้ผ่าน --output_groups เช่น หาก //pkg:mytarget เป้าหมายเป็นประเภทกฎที่มีกลุ่มเอาต์พุต debug_files คุณจะสร้างไฟล์เหล่านี้ได้โดยการเรียกใช้ bazel build //pkg:mytarget --output_groups=debug_files เนื่องจากเอาต์พุตที่ไม่ได้ประกาศล่วงหน้าไม่มีป้ายกำกับ คุณจึงขอเอาต์พุตได้โดยปรากฏในเอาต์พุตเริ่มต้นหรือกลุ่มเอาต์พุตเท่านั้น

คุณระบุกลุ่มเอาต์พุตกับผู้ให้บริการ OutputGroupInfo ได้ โปรดทราบว่า OutputGroupInfo จะใช้พารามิเตอร์ที่มีชื่อได้ตามต้องการเพื่อกำหนดกลุ่มเอาต์พุตที่มีชื่อนั้น ซึ่งต่างจากผู้ให้บริการที่มีในตัวมากมาย

def _example_library_impl(ctx):
    ...
    debug_file = ctx.actions.declare_file(name + ".pdb")
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        OutputGroupInfo(
            debug_files = depset([debug_file]),
            all_files = depset([output_file, debug_file]),
        ),
        ...
    ]

นอกจากนี้ ยังต่างจากผู้ให้บริการส่วนใหญ่ตรงที่จะแสดงผล OutputGroupInfo ได้จากทั้งองค์ประกอบและเป้าหมายกฎซึ่งใช้ด้านนั้นๆ ตราบใดที่ไม่ได้กำหนดกลุ่มเอาต์พุตเดียวกัน ในกรณีดังกล่าว ระบบจะรวมผู้ให้บริการที่ได้

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

การกำหนดค่า

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

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

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

สำหรับแอตทริบิวต์ทรัพยากร Dependency แต่ละรายการ คุณสามารถใช้ cfg เพื่อตัดสินใจว่าทรัพยากร Dependency ควรสร้างในการกำหนดค่าเดียวกันหรือเปลี่ยนไปใช้การกำหนดค่าผู้บริหาร หากแอตทริบิวต์ทรัพยากร Dependency มีแฟล็ก executable = True คุณจะต้องตั้งค่า cfg อย่างชัดแจ้ง ทั้งนี้ก็เพื่อป้องกันการสร้างเครื่องมือสำหรับการกำหนดค่าที่ไม่ถูกต้องโดยไม่ได้ตั้งใจ ดูตัวอย่าง

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

ควรสร้างเครื่องมือต่างๆ ที่ดำเนินการในฐานะส่วนหนึ่งของบิลด์ (เช่น คอมไพเลอร์หรือเครื่องมือสร้างโค้ด) สำหรับการกำหนดค่าปฏิบัติการ ในกรณีนี้ ให้ระบุ cfg = "exec" ในแอตทริบิวต์

มิเช่นนั้น ควรสร้างไฟล์สั่งการที่ใช้ขณะรันไทม์ (เช่น เป็นส่วนหนึ่งของการทดสอบ) สำหรับการกำหนดค่าเป้าหมาย ในกรณีนี้ ให้ระบุ cfg = "target" ในแอตทริบิวต์

cfg = "target" ไม่ได้ทำอะไรเลย แต่เป็นเพียงค่าอำนวยความสะดวกที่ช่วยให้ผู้ออกแบบกฎเข้าใจความตั้งใจของตนอย่างชัดเจน หากเป็น executable = False ซึ่งหมายความว่าคุณจะเลือก cfg หรือไม่ก็ได้ โปรดตั้งค่านี้เฉพาะในกรณีที่ช่วยให้ผู้ใช้อ่านง่ายขึ้นเท่านั้น

นอกจากนี้ คุณยังใช้ cfg = my_transition เพื่อใช้การเปลี่ยนที่กำหนดโดยผู้ใช้ได้ ซึ่งจะช่วยให้ผู้เขียนกฎมีความยืดหยุ่นอย่างมากในการเปลี่ยนการกำหนดค่า และดึงเงินคืนจากการทำให้กราฟบิลด์มีขนาดใหญ่ขึ้นและเข้าใจได้น้อยลง

หมายเหตุ: ที่ผ่านมา Bazel ไม่มีแนวคิดเกี่ยวกับแพลตฟอร์มการดำเนินการ และถือว่าการทำงานของบิลด์ทั้งหมดทำงานบนเครื่องโฮสต์แทน เวอร์ชัน Bazel ก่อน 6.0 สร้างการกำหนดค่า "โฮสต์" ที่แตกต่างกันเพื่อแสดงถึงคีย์นี้ หากเห็นการอ้างอิงถึง "โฮสต์" ในโค้ดหรือเอกสารเก่า จะหมายถึง เราขอแนะนำให้ใช้ Bazel 6.0 หรือใหม่กว่าเพื่อหลีกเลี่ยงค่าใช้จ่ายจากแนวคิดพิเศษนี้

ส่วนย่อยการกำหนดค่า

กฎอาจเข้าถึงส่วนย่อยการกำหนดค่า เช่น cpp และ java อย่างไรก็ตาม คุณต้องประกาศ Fragment ที่จำเป็นทั้งหมดเพื่อหลีกเลี่ยงข้อผิดพลาดในการเข้าถึง

def _impl(ctx):
    # Using ctx.fragments.cpp leads to an error since it was not declared.
    x = ctx.fragments.java
    ...

my_rule = rule(
    implementation = _impl,
    fragments = ["java"],      # Required fragments of the target configuration
    ...
)

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

    ...
    runfiles = ctx.runfiles(
        root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
        symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
    )
    # Creates something like:
    # sometarget.runfiles/
    #     some/
    #         path/
    #             here.foo -> some_data_file2
    #     <workspace_name>/
    #         some/
    #             path/
    #                 here.bar -> some_data_file3

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

การครอบคลุมของโค้ด

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

หากการใช้กฎเพิ่มการวัดคุมการครอบคลุม ณ เวลาที่สร้าง ระบบจะต้องพิจารณาสิ่งนี้ในฟังก์ชันการติดตั้งใช้งาน ctx.coverage_instrumented จะแสดงผล True ในโหมดการครอบคลุมหากแหล่งที่มาของเป้าหมายควรมีการวัดคุมดังนี้

# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
  # Do something to turn on coverage for this compile action

ตรรกะที่ต้องเปิดในโหมดการครอบคลุมเสมอ (ไม่ว่าจะมีการใช้เครื่องมือของแหล่งที่มาของเป้าหมายโดยเฉพาะหรือไม่) จะปรับเงื่อนไขใน ctx.configuration.coverage_enabled ได้

หากกฎรวมแหล่งที่มาจากทรัพยากร Dependency โดยตรงก่อนการคอมไพล์ (เช่น ไฟล์ส่วนหัว) อาจต้องเปิดการวัดคุมเวลาคอมไพล์ด้วยหากควรใช้แหล่งที่มาของทรัพยากร Dependency ต่อไปนี้

# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if (ctx.configuration.coverage_enabled and
    (ctx.coverage_instrumented() or
     any([ctx.coverage_instrumented(dep) for dep in ctx.attr.deps]))):
    # Do something to turn on coverage for this compile action

นอกจากนี้ กฎควรระบุข้อมูลเกี่ยวกับแอตทริบิวต์ที่เกี่ยวข้องกับการครอบคลุมของผู้ให้บริการ InstrumentedFilesInfo ซึ่งสร้างขึ้นโดยใช้ coverage_common.instrumented_files_info พารามิเตอร์ dependency_attributes ของ instrumented_files_info ควรแสดงแอตทริบิวต์ทรัพยากร Dependency ของรันไทม์ทั้งหมด รวมถึงทรัพยากร Dependency ของโค้ด เช่น deps และทรัพยากร Dependency ของข้อมูล เช่น data พารามิเตอร์ source_attributes ควรแสดงแอตทริบิวต์ไฟล์แหล่งที่มาของกฎหากอาจมีการเพิ่มเครื่องมือการครอบคลุม

def _example_library_impl(ctx):
    ...
    return [
        ...
        coverage_common.instrumented_files_info(
            ctx,
            dependency_attributes = ["deps", "data"],
            # Omitted if coverage is not supported for this rule:
            source_attributes = ["srcs", "hdrs"],
        )
        ...
    ]

หากไม่แสดงผล InstrumentedFilesInfo ระบบจะสร้างแอตทริบิวต์เริ่มต้นพร้อมแอตทริบิวต์การขึ้นต่อกันแต่ละรายการที่ไม่ใช่เครื่องมือ ซึ่งไม่ได้ตั้งค่า cfg เป็น "exec" ในสคีมาแอตทริบิวต์ใน dependency_attributes (นี่ไม่ใช่ลักษณะการทำงานที่ดีที่สุดเนื่องจากใส่แอตทริบิวต์ เช่น srcs ใน dependency_attributes แทน source_attributes แต่ทำให้ไม่จำเป็นต้องกำหนดค่าการครอบคลุมอย่างชัดเจนสำหรับกฎทั้งหมดในเชนการพึ่งพากัน)

การดำเนินการตรวจสอบ

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

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

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

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

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

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

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

วิธีแก้ปัญหาเหล่านี้คือการใช้กลุ่มเอาต์พุตการตรวจสอบ

กลุ่มเอาต์พุตการตรวจสอบ

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

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

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

การดำเนินการตรวจสอบเป้าหมายจะไม่ทำงานใน 3 กรณีต่อไปนี้

  • เมื่อเป้าหมายขึ้นอยู่กับเครื่องมือ
  • เมื่อเป้าหมายขึ้นอยู่กับทรัพยากร Dependency โดยนัย (เช่น แอตทริบิวต์ที่ขึ้นต้นด้วย "_")
  • เมื่อสร้างเป้าหมายในการกำหนดค่า exec

โดยมีสมมติฐานว่าเป้าหมายเหล่านี้มีบิลด์และการทดสอบแยกต่างหากซึ่งจะเผยให้เห็นความล้มเหลวในการตรวจสอบ

การใช้กลุ่มเอาต์พุตการตรวจสอบ

กลุ่มเอาต์พุตการตรวจสอบมีชื่อว่า _validation และมีการใช้งานเช่นเดียวกับกลุ่มเอาต์พุตอื่นๆ

def _rule_with_validation_impl(ctx):

  ctx.actions.write(ctx.outputs.main, "main output\n")
  ctx.actions.write(ctx.outputs.implicit, "implicit output\n")

  validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
  ctx.actions.run(
    outputs = [validation_output],
    executable = ctx.executable._validation_tool,
    arguments = [validation_output.path],
  )

  return [
    DefaultInfo(files = depset([ctx.outputs.main])),
    OutputGroupInfo(_validation = depset([validation_output])),
  ]


rule_with_validation = rule(
  implementation = _rule_with_validation_impl,
  outputs = {
    "main": "%{name}.main",
    "implicit": "%{name}.implicit",
  },
  attrs = {
    "_validation_tool": attr.label(
        default = Label("//validation_actions:validation_tool"),
        executable = True,
        cfg = "exec"
    ),
  }
)

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

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

load("@bazel_skylib//lib:unittest.bzl", "analysistest")

def _validation_outputs_test_impl(ctx):
  env = analysistest.begin(ctx)

  actions = analysistest.target_actions(env)
  target = analysistest.target_under_test(env)
  validation_outputs = target.output_groups._validation.to_list()
  for action in actions:
    for validation_output in validation_outputs:
      if validation_output in action.inputs.to_list():
        analysistest.fail(env,
            "%s is a validation action output, but is an input to action %s" % (
                validation_output, action))

  return analysistest.end(env)

validation_outputs_test = analysistest.make(_validation_outputs_test_impl)

แฟล็กการดำเนินการตรวจสอบ

การเรียกใช้การดำเนินการตรวจสอบจะควบคุมโดยแฟล็กบรรทัดคำสั่ง --run_validations ซึ่งมีค่าเริ่มต้นเป็น "จริง"

ฟีเจอร์ที่เลิกใช้งาน

เอาต์พุตที่ประกาศไว้ล่วงหน้าที่เลิกใช้งาน

การใช้เอาต์พุตที่ประกาศไว้ล่วงหน้ามี 2 วิธีที่เลิกใช้งานแล้วดังนี้

  • พารามิเตอร์ outputs ของ rule จะระบุการแมประหว่างชื่อแอตทริบิวต์เอาต์พุตและเทมเพลตสตริงเพื่อสร้างป้ายกำกับเอาต์พุตที่ประกาศไว้ล่วงหน้า ต้องการใช้เอาต์พุตที่ไม่ได้ประกาศไว้ล่วงหน้าและเพิ่มเอาต์พุตลงใน DefaultInfo.files อย่างชัดแจ้ง ใช้ป้ายกำกับของเป้าหมายกฎเป็นอินพุตสำหรับกฎที่ใช้เอาต์พุตแทนป้ายกำกับของเอาต์พุตที่ประกาศไว้ล่วงหน้า

  • สำหรับกฎปฏิบัติการ ctx.outputs.executable จะหมายถึงเอาต์พุตสั่งการที่ประกาศไว้ล่วงหน้าซึ่งมีชื่อเดียวกับเป้าหมายของกฎ หากต้องการประกาศเอาต์พุตอย่างชัดเจน เช่น ด้วย ctx.actions.declare_file(ctx.label.name) และตรวจสอบว่าคำสั่งที่สร้างไฟล์ปฏิบัติการกำหนดสิทธิ์ของตนเองเพื่ออนุญาตการดำเนินการแล้ว ส่งเอาต์พุตที่สั่งการได้ไปยังพารามิเตอร์ executable ของ DefaultInfo อย่างชัดแจ้ง

ฟีเจอร์เรียกใช้ไฟล์เพื่อหลีกเลี่ยง

ctx.runfiles และประเภท runfiles มีชุดฟีเจอร์ที่ซับซ้อน ซึ่งส่วนใหญ่ฟีเจอร์จะเก็บไว้ด้วยเหตุผลเดิม คำแนะนำต่อไปนี้ช่วยลดความซับซ้อน

  • หลีกเลี่ยงการใช้โหมด collect_data และ collect_default ของ ctx.runfiles โหมดเหล่านี้จะรวบรวมไฟล์ Runfile ผ่านขอบของทรัพยากร Dependency แบบฮาร์ดโค้ดโดยปริยายเพื่อทำให้สับสน แต่ให้เพิ่มไฟล์โดยใช้พารามิเตอร์ files หรือ transitive_files ของ ctx.runfiles หรือผสานรวมใน Runfile จากทรัพยากร Dependency กับ runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) แทน

  • หลีกเลี่ยงการใช้ data_runfiles และ default_runfiles ของเครื่องมือสร้าง DefaultInfo ให้ระบุ DefaultInfo(runfiles = ...) แทน ความแตกต่างระหว่างไฟล์การเรียกใช้ "ค่าเริ่มต้น" และ "ข้อมูล" จะยังคงอยู่ตามเหตุผลเดิม เช่น กฎบางข้อจะใส่เอาต์พุตเริ่มต้นไว้ใน data_runfiles แต่ไม่ใส่ default_runfiles กฎควรทั้งสองจะรวมเอาต์พุตเริ่มต้นและผสานรวมใน default_runfiles จากแอตทริบิวต์ที่มี Runfile (มักเป็น data) แทนที่จะใช้ data_runfiles

  • เมื่อเรียกข้อมูล runfiles จาก DefaultInfo (โดยทั่วไปมีไว้สำหรับการผสานไฟล์ Runfile ระหว่างกฎปัจจุบันและการอ้างอิงของกฎเท่านั้น) ให้ใช้ DefaultInfo.default_runfiles ไม่ใช่ DefaultInfo.data_runfiles

การย้ายข้อมูลจากผู้ให้บริการเดิม

ก่อนหน้านี้ ผู้ให้บริการ Bazel เป็นช่องที่เรียบง่ายในออบเจ็กต์ Target มิติข้อมูลเหล่านี้มีการเข้าถึงโดยใช้โอเปอเรเตอร์จุด และสร้างขึ้นโดยใส่ช่องใน struct ที่แสดงผลโดยฟังก์ชันการใช้งานของกฎแทนรายการออบเจ็กต์ผู้ให้บริการ ดังนี้

return struct(example_info = struct(headers = depset(...)))

ตัวผู้ให้บริการดังกล่าวจะดึงได้จากช่องที่เกี่ยวข้องของออบเจ็กต์ Target ดังนี้

transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]

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

ตอนนี้ระบบยังคงรองรับผู้ให้บริการเดิมอยู่ กฎอาจแสดงผู้ให้บริการทั้งรายเดิมและสมัยใหม่ดังนี้

def _old_rule_impl(ctx):
  ...
  legacy_data = struct(x = "foo", ...)
  modern_data = MyInfo(y = "bar", ...)
  # When any legacy providers are returned, the top-level returned value is a
  # struct.
  return struct(
      # One key = value entry for each legacy provider.
      legacy_info = legacy_data,
      ...
      # Additional modern providers:
      providers = [modern_data, ...])

หาก dep เป็นออบเจ็กต์ Target ที่ได้สำหรับอินสแตนซ์ของกฎนี้ ระบบจะดึงข้อมูลผู้ให้บริการและเนื้อหาของผู้ให้บริการเป็น dep.legacy_info.x และ dep[MyInfo].y

นอกเหนือจาก providers โครงสร้างที่แสดงผลอาจมีช่องอื่นๆ อีกหลายช่องที่มีความหมายพิเศษ (จึงไม่สร้างผู้ให้บริการเดิมที่เกี่ยวข้อง) ดังนี้

  • ช่อง files, runfiles, data_runfiles, default_runfiles และ executable สอดคล้องกับช่องชื่อเดียวกันของ DefaultInfo ไม่อนุญาตให้ระบุช่องเหล่านี้ขณะที่ส่งคืนผู้ให้บริการ DefaultInfo ด้วย

  • ช่อง output_groups ใช้ค่าโครงสร้างและสอดคล้องกับ OutputGroupInfo

ในการประกาศกฎ provides และในการประกาศแอตทริบิวต์การพึ่งพากัน providers รายการ ระบบจะส่งผู้ให้บริการเดิมเป็นสตริง และผู้ให้บริการขั้นสูงจะส่งผ่านสัญลักษณ์ Info ของผู้ให้บริการเดิม อย่าลืมเปลี่ยนจากสตริงเป็นสัญลักษณ์ เมื่อย้ายข้อมูล สำหรับชุดกฎที่ซับซ้อนหรือใหญ่ซึ่งอัปเดตกฎทั้งหมดได้ยาก คุณอาจทำงานได้ง่ายขึ้นหากปฏิบัติตามลำดับขั้นตอนต่อไปนี้

  1. แก้ไขกฎที่ผลิตผู้ให้บริการเดิมเพื่อสร้างทั้งผู้ให้บริการเดิมและสมัยใหม่ โดยใช้ไวยากรณ์ก่อนหน้า สำหรับกฎที่ประกาศว่าส่งคืนผู้ให้บริการเดิม ให้อัปเดตการประกาศนั้นให้รวมผู้ให้บริการเดิมและสมัยใหม่

  2. แก้ไขกฎที่ใช้ผู้ให้บริการเดิมเพื่อให้ใช้ผู้ให้บริการสมัยใหม่แทน หากการประกาศแอตทริบิวต์ต้องใช้ผู้ให้บริการเดิม ให้อัปเดตเพื่อกำหนดให้ต้องใช้ผู้ให้บริการสมัยใหม่แทนด้วย หรือคุณอาจแทรกงานนี้ด้วยขั้นตอนที่ 1 โดยให้ผู้บริโภคยอมรับหรือกำหนดให้ต้องทดสอบจากผู้ให้บริการเดิมโดยใช้ hasattr(target, 'foo') หรือผู้ให้บริการใหม่โดยใช้ FooInfo in target

  3. นําผู้ให้บริการเดิมออกจากกฎทั้งหมดโดยสมบูรณ์