กฎ

กฎจะกำหนดชุดการดำเนินการที่ 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 ออบเจ็กต์เหล่านี้มี 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]

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

ไฟล์

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

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

สำหรับแอตทริบิวต์การขึ้นต่อกันแต่ละรายการ ฟิลด์ที่เกี่ยวข้องของ 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สร้างออบเจ็กต์ 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.run เพื่อเรียกใช้ไฟล์ที่ปฏิบัติการได้
  • ctx.actions.run_shell เพื่อเรียกใช้คำสั่งเชลล์
  • ctx.actions.write เพื่อเขียนสตริงลงในไฟล์
  • ctx.actions.expand_template เพื่อ สร้างไฟล์จากเทมเพลต

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

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

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

สำหรับแอตทริบิวต์การขึ้นต่อกันแต่ละรายการ คุณสามารถใช้ 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
    ...
)

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

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

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

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

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

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

กฎการทดสอบ

กฎการทดสอบต้องมีการตั้งค่าเพิ่มเติมเพื่อสร้างรายงานความครอบคลุม กฎ ต้องเพิ่มแอตทริบิวต์โดยนัยต่อไปนี้

my_test = rule(
    ...,
    attrs = {
        ...,
        # Implicit dependencies used by Bazel to generate coverage reports.
        "_lcov_merger": attr.label(
            default = configuration_field(fragment = "coverage", name = "output_generator"),
            executable = True,
            cfg = config.exec(exec_group = "test"),
        ),
        "_collect_cc_coverage": attr.label(
            default = "@bazel_tools//tools/test:collect_cc_coverage",
            executable = True,
            cfg = config.exec(exec_group = "test"),
        )
    },
    test = True,
)

การใช้ configuration_field จะช่วยหลีกเลี่ยงการพึ่งพาเครื่องมือผสาน LCOV ของ Java ได้ ตราบใดที่ไม่ได้ขอความครอบคลุม

เมื่อเรียกใช้การทดสอบ ควรส่งข้อมูลความครอบคลุมในรูปแบบของไฟล์ LCOV อย่างน้อย 1 ไฟล์ ที่มีชื่อที่ไม่ซ้ำกันไปยังไดเรกทอรีที่ระบุโดยตัวแปรสภาพแวดล้อม COVERAGE_DIR จากนั้น Bazel จะผสานไฟล์เหล่านี้เป็นไฟล์ LCOV ไฟล์เดียวโดยใช้เครื่องมือ _lcov_merger หากมีอยู่ ระบบจะรวบรวมความครอบคลุมของ C/C++ โดยใช้เครื่องมือ _collect_cc_coverage ด้วย

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

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

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

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

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

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

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

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