กฎ

รายงานปัญหา ดูแหล่งที่มา รุ่น Nightly · 7.4 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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

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

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

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

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

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

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

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

การสร้างกฎ

ในไฟล์ .bzl ให้ใช้ฟังก์ชัน rule เพื่อกําหนดกฎใหม่ และจัดเก็บผลลัพธ์ไว้ในตัวแปรส่วนกลาง การเรียกใช้ 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ฟังก์ชันการใช้งานจะใช้พารามิเตอร์เพียง 1 รายการเท่านั้น ซึ่งก็คือบริบทของกฎที่มีชื่อตามธรรมเนียมว่า 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]

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

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

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

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

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

ไฟล์

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ไฟล์รันไทม์

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

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

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

ในกรณีนี้ เมื่อเรียกใช้สัญลักษณ์ผู้ให้บริการ แทนที่จะแสดงอินสแตนซ์ใหม่โดยตรง ระบบจะส่งต่ออาร์กิวเมนต์ไปยัง init 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 (ชื่อเป้าหมายการทดสอบมักจะลงท้ายด้วย _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(
 ...
)

ตำแหน่งของไฟล์รันไทม์

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

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

การกำหนดค่า

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

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

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

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

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

เครื่องมือที่ดำเนินการเป็นส่วนหนึ่งของบิลด์ (เช่น คอมไพเลอร์หรือเครื่องมือสร้างโค้ด) ควรสร้างสำหรับการกำหนดค่า exec ในกรณีนี้ ให้ระบุ 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
    ...
)

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

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

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

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

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

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

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

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

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

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

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

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

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

ปัญหาเหล่านี้จะแก้ไขได้โดยใช้กลุ่มเอาต์พุตการตรวจสอบ

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

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

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

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

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

  • เมื่อใช้เป้าหมายเป็นเครื่องมือ
  • เมื่อเป้าหมายเป็นข้อกําหนดโดยนัย (เช่น แอตทริบิวต์ที่ขึ้นต้นด้วย "_")
  • เมื่อสร้างเป้าหมายในการกําหนดค่าโฮสต์หรือ 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)

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

การดำเนินการตรวจสอบที่ทำงานอยู่จะควบคุมโดย Flag --run_validations บรรทัดคำสั่ง ซึ่งค่าเริ่มต้นคือ "จริง"

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

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

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

  • ช่อง 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. นำผู้ให้บริการเดิมออกจากกฎทั้งหมดอย่างสมบูรณ์