กฎ

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

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

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

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

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

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

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

  • 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 เป็นทรัพยากรที่ my_target ต้องใช้ ดังนั้นระบบจะวิเคราะห์ other_target ก่อน ถือเป็นข้อผิดพลาดหากมีวงจรใน กราฟการขึ้นต่อกันของเป้าหมาย

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

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

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

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

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

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

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

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

ฟังก์ชันการติดตั้งใช้งาน

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

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

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

เป้าหมาย

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

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]

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

ไฟล์

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

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

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

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

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

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

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

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

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

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

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

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

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

ในทางตรงกันข้าม เครื่องมือสร้างดิบจะข้ามinit Callback

ตัวอย่างต่อไปนี้ใช้ 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
)

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

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

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

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

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

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

การกำหนดค่า

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ระบบจะไม่เรียกใช้การดำเนินการตรวจสอบความถูกต้องของเป้าหมายใน 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 อย่างชัดเจน

ฟีเจอร์ 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 = ...) แทน ระบบจะยังคงแยกความแตกต่างระหว่างไฟล์ที่เรียกใช้ "default" กับ "data" ไว้ด้วยเหตุผลเดิม ตัวอย่างเช่น กฎบางอย่างจะวางเอาต์พุตเริ่มต้นไว้ใน data_runfiles แต่ไม่ใช่ default_runfiles แทนที่จะใช้ data_runfiles กฎควรทั้งรวมเอาต์พุตเริ่มต้นและผสานรวมใน default_runfiles จากแอตทริบิวต์ที่ให้ไฟล์ที่เรียกใช้ (มักจะเป็น data)

  • เมื่อเรียกข้อมูล runfiles จาก DefaultInfo (โดยทั่วไปจะใช้สำหรับการผสานรวม ไฟล์ที่เรียกใช้ระหว่างกฎปัจจุบันกับทรัพยากร Dependency เท่านั้น) ให้ใช้ 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 รับค่า Struct และสอดคล้องกับ OutputGroupInfo

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

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

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

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