กฎ

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

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

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

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

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

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

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

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

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

การสร้างกฎ

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

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

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

เป้าหมาย

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

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

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

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

ctx.file มี File หรือ None รายการเดียวสำหรับ แอตทริบิวต์ของทรัพยากร 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 และ 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],
    )
    ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

คุณสามารถกำหนดผู้ให้บริการโดยใช้ provider ในการสื่อสารข้อมูลเฉพาะกฎ:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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