กฎ

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

กฎกำหนดชุดของการดำเนินการที่ Bazel ดำเนินการกับอินพุตเพื่อสร้างชุดเอาต์พุต ซึ่งมีการอ้างอิงใน providers ที่ส่งคืนโดยฟังก์ชันการใช้งานของกฎ เช่น กฎไบนารีของ C++ อาจมีลักษณะดังนี้

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

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

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

Bazel สร้างกฎ 2-3 ข้อขึ้นเอง กฎเนทีฟเหล่านี้ เช่น cc_library และ java_binary มีการรองรับหลักสำหรับบางภาษา การกำหนดกฎเองจะช่วยให้คุณเพิ่มการรองรับภาษาและเครื่องมือที่คล้ายกันที่ 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 คืออาร์กิวเมนต์กฎ แอตทริบิวต์สามารถระบุค่าที่เจาะจงสำหรับการใช้งานของเป้าหมาย หรืออาจอ้างอิงถึงเป้าหมายอื่นๆ เพื่อสร้างกราฟของทรัพยากร Dependency

แอตทริบิวต์เฉพาะกฎ เช่น 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 อื่นๆ

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

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

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

เป้าหมาย

ทรัพยากร Dependency จะแสดงในเวลาการวิเคราะห์เป็นออบเจ็กต์ 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]

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

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

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

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

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

Files

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

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

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

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 and get Hello.o" เมื่อสร้างการทำงานขึ้น Bazel จะไม่เรียกใช้คำสั่งโดยทันที ระบบจะลงทะเบียนโมเดลดังกล่าวในกราฟของทรัพยากร Dependency เนื่องจากการทำงานหนึ่งอาจขึ้นอยู่กับเอาต์พุตของการทำงานอีกแบบหนึ่ง เช่น ใน C จะต้องเรียก Linker หลังคอมไพเลอร์

ฟังก์ชันอเนกประสงค์ที่สร้างการดำเนินการต่างๆ มีคำจำกัดความใน 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],
    )
    ...

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

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

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

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

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

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

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

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

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

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

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

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

พารามิเตอร์ files ของ DefaultInfo จะระบุเอาต์พุตเริ่มต้น ดังนี้

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

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

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

ไฟล์เรียกใช้

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

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

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

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

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

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

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

# //pkg:exampleinfo.bzl

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

# It's possible to define an init accepting positional arguments, but
# 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(
    ...
    init = _exampleinfo_init)

export ExampleInfo

จากนั้น การใช้กฎอาจสร้างตัวอย่างผู้ให้บริการดังนี้

    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 (ชื่อ target ของการทดสอบมักจะลงท้ายด้วย _test ตามแบบแผน แต่ไม่ได้เป็นข้อบังคับ) กฎที่ไม่ใช่การทดสอบต้องไม่มี คำต่อท้ายนี้

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

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(
 ...
)

ตำแหน่งของไฟล์เรียกใช้

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

# 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 ที่เรียกใช้ไฟล์ไม่สามารถคาดเดาได้เหมือนกัน เพื่อลดปัญหานี้ ไบนารีแต่ละรายการควรระบุวิธียอมรับรูทของรันไฟล์เป็นพารามิเตอร์โดยใช้สภาพแวดล้อมหรืออาร์กิวเมนต์/แฟล็กบรรทัดคำสั่ง วิธีนี้ช่วยให้ไบนารีส่งต่อรูทของไฟล์การเรียกใช้ Canonical ที่ถูกต้องไปยังไบนารีที่เรียกใช้ได้ หากไม่ได้ตั้งค่าไว้ ไบนารีจะเดาได้ว่าเป็นไบนารีแรกที่เรียกใช้ และมองหาไดเรกทอรี Runfiles ที่อยู่ติดกัน

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

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

เป้าหมายเดียวมีไฟล์เอาต์พุตได้หลายไฟล์ เมื่อเรียกใช้คำสั่ง 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 โดยทั้ง aspect และเป้าหมายของกฎที่ใช้กับลักษณะนั้น ตราบใดที่ไม่ได้กำหนดกลุ่มเอาต์พุตเดียวกัน ในกรณีนี้ ระบบจะรวมผู้ให้บริการ ที่ได้เข้าด้วยกัน

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

การกำหนดค่า

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

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

โดยค่าเริ่มต้น Bazel จะสร้างทรัพยากร 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 และ jvm อย่างไรก็ตาม คุณต้องประกาศส่วนย่อยที่จำเป็นทั้งหมดเพื่อหลีกเลี่ยงข้อผิดพลาดในการเข้าถึง ดังนี้

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
    host_fragments = ["java"], # Required fragments of the host configuration
    ...
)

โดยปกติเส้นทางแบบสัมพัทธ์ของไฟล์ในผังการเรียกใช้ไฟล์จะเหมือนกับเส้นทางแบบสัมพัทธ์ของไฟล์นั้นในแผนผังต้นทางหรือแผนผังเอาต์พุตที่สร้างขึ้น หากค่าเหล่านี้ต้องแตกต่างกันด้วยเหตุผลบางประการ ให้ระบุอาร์กิวเมนต์ 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 ไฟล์ที่ต่างกันกับเส้นทางเดียวกันในโครงสร้างการเรียกใช้ไฟล์ ซึ่งจะทำให้บิลด์ทำงานไม่สำเร็จ พร้อมทั้งแสดงข้อผิดพลาดในการอธิบายความขัดแย้ง หากต้องการแก้ไข คุณจะต้องแก้ไขอาร์กิวเมนต์ ctx.runfiles เพื่อนำข้อขัดแย้งออก ระบบจะทำการตรวจสอบนี้กับเป้าหมายที่ใช้กฎของคุณ รวมถึงเป้าหมายทุกประเภทที่ขึ้นอยู่กับเป้าหมายเหล่านั้น ซึ่งมีความเสี่ยงอย่างยิ่งหากเครื่องมืออื่นมีแนวโน้มที่จะใช้เครื่องมือของคุณแบบทรานซิชัน โดยชื่อลิงก์สัญลักษณ์ต้องไม่ซ้ำกันในทุกการเรียกใช้ไฟล์ของเครื่องมือและการอ้างอิงทั้งหมด

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

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

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

# 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 และการอ้างอิงข้อมูล เช่น 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 ระบบจะสร้างแอตทริบิวต์เริ่มต้นโดยมีแอตทริบิวต์ Dependency ที่ไม่ใช่เครื่องมือแต่ละรายการซึ่งไม่ได้ตั้งค่า cfg เป็น "host" หรือ "exec" ในสคีมาแอตทริบิวต์) ใน dependency_attributes (นี่ไม่ใช่ลักษณะการทำงานที่ดีที่สุดเนื่องจากจะใส่แอตทริบิวต์อย่าง srcs ใน dependency_attributes แทนที่จะเป็น source_attributes แต่ก็ทำให้ไม่จำเป็นต้องกำหนดค่าการครอบคลุมอย่างชัดแจ้งสำหรับกฎทั้งหมดในห่วงโซ่การขึ้นต่อกัน)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

การใช้กลุ่มเอาต์พุตของการตรวจสอบความถูกต้อง

กลุ่มเอาต์พุตของการตรวจสอบมีชื่อว่า _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 ซึ่งมีค่าเริ่มต้นเป็น true

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

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

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

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

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

ฟีเจอร์ Runfiles ที่ควรหลีกเลี่ยง

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

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

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

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

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

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

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

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

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 แล้ว Struct ที่แสดงกลับมาอาจใช้ช่องอื่นๆ ที่มีความหมายพิเศษด้วย (จึงไม่ได้สร้างผู้ให้บริการเดิมที่เกี่ยวข้อง) ดังนี้

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

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

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

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

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

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