กฎจะกำหนดชุดการดำเนินการที่ Bazel ดำเนินการกับข้อมูลอินพุตเพื่อสร้างชุดเอาต์พุต ซึ่งจะอ้างอิงในผู้ให้บริการที่แสดงผลโดยฟังก์ชันการใช้งานของกฎ เช่น กฎไบนารี C++ อาจมีลักษณะดังนี้
- นำชุดไฟล์ต้นทาง
.cpp
(อินพุต) - เรียกใช้
g++
ในไฟล์ต้นฉบับ (การดำเนินการ) - แสดงผลผู้ให้บริการ
DefaultInfo
ที่มีเอาต์พุตที่เรียกใช้งานได้และไฟล์อื่นๆ เพื่อทำให้พร้อมใช้งานเมื่อรันไทม์ - แสดงผลผู้ให้บริการ
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
ไม่ได้
แอตทริบิวต์การขึ้นต่อกัน
กฎที่ประมวลผลซอร์สโค้ดมักจะกำหนดแอตทริบิวต์ต่อไปนี้เพื่อจัดการทรัพยากร Dependency ประเภทต่างๆ
srcs
ระบุไฟล์ต้นฉบับที่ประมวลผลโดยการดำเนินการของเป้าหมาย บ่อยครั้ง สคีมาแอตทริบิวต์จะระบุนามสกุลไฟล์ที่คาดไว้สำหรับประเภทไฟล์ต้นทางที่กฎประมวลผล โดยทั่วไป กฎสำหรับภาษาที่มีไฟล์ส่วนหัวจะระบุแอตทริบิวต์hdrs
แยกต่างหากสำหรับส่วนหัวที่ประมวลผลโดยเป้าหมายและผู้ใช้งานdeps
ระบุทรัพยากร Dependency ของโค้ดสําหรับเป้าหมาย สคีมาแอตทริบิวต์ควรระบุผู้ให้บริการที่ต้องมีทรัพยากร Dependency เหล่านั้น (เช่นcc_library
จะระบุCcInfo
)data
ระบุไฟล์ที่จะพร้อมใช้งานเมื่อรันไทม์สำหรับไฟล์ที่เรียกใช้งานได้ซึ่งขึ้นอยู่กับเป้าหมาย ซึ่งควรอนุญาตให้ระบุไฟล์ใดก็ได้
example_library = rule(
implementation = _example_library_impl,
attrs = {
"srcs": attr.label_list(allow_files = [".example"]),
"hdrs": attr.label_list(allow_files = [".header"]),
"deps": attr.label_list(providers = [ExampleInfo]),
"data": attr.label_list(allow_files = True),
...
},
)
ตัวอย่างแอตทริบิวต์ Dependency แอตทริบิวต์ที่ระบุป้ายกำกับอินพุต (ซึ่งกำหนดด้วย attr.label_list
, attr.label
หรือ attr.label_keyed_string_dict
) จะระบุการพึ่งพาของประเภทหนึ่งระหว่างเป้าหมายกับเป้าหมายที่มีป้ายกำกับ (หรือออบเจ็กต์ Label
ที่เกี่ยวข้อง) แสดงอยู่ในแอตทริบิวต์นั้นเมื่อมีการกําหนดเป้าหมาย ระบบจะแก้ไขที่เก็บและอาจแก้ไขเส้นทางสําหรับป้ายกำกับเหล่านี้โดยสัมพันธ์กับเป้าหมายที่กําหนด
example_library(
name = "my_target",
deps = [":other_target"],
)
example_library(
name = "other_target",
...
)
ในตัวอย่างนี้ other_target
เป็นทรัพยากร Dependency ของ my_target
ระบบจึงวิเคราะห์ other_target
ก่อน กราฟแสดงความเกี่ยวข้องของเป้าหมายจะมีข้อผิดพลาดหากมีวงจร
แอตทริบิวต์ส่วนตัวและการอ้างอิงโดยนัย
แอตทริบิวต์การขึ้นต่อกันซึ่งมีค่าเริ่มต้นจะสร้างการขึ้นต่อกันโดยนัย การดำเนินการนี้เป็นการระบุโดยนัยเนื่องจากเป็นส่วนหนึ่งของกราฟเป้าหมายที่ผู้ใช้ไม่ได้ระบุไว้ในไฟล์ BUILD
Dependency ที่ไม่ชัดจะใช้ได้กับการเขียนโค้ดความสัมพันธ์ระหว่างกฎกับเครื่องมือ (Dependency ของช่วงบิลด์ เช่น คอมไพเลอร์) ไว้ในโค้ดอย่างถาวร เนื่องจากผู้ใช้มักไม่สนใจที่จะระบุเครื่องมือที่กฎใช้ ในฟังก์ชันการใช้งานของกฎ ระบบจะถือว่าค่านี้เหมือนกับการอ้างอิงอื่นๆ
หากต้องการระบุการพึ่งพาโดยนัยโดยไม่อนุญาตให้ผู้ใช้ลบล้างค่าดังกล่าว ให้ทําให้แอตทริบิวต์เป็นแบบส่วนตัวโดยตั้งชื่อแอตทริบิวต์นั้นขึ้นต้นด้วยขีดล่าง (_
) แอตทริบิวต์ส่วนตัวต้องมีค่าเริ่มต้น โดยทั่วไปแล้ว การใช้แอตทริบิวต์ส่วนตัวควรใช้กับข้อกําหนดเบื้องต้นโดยนัยเท่านั้น
example_library = rule(
implementation = _example_library_impl,
attrs = {
...
"_compiler": attr.label(
default = Label("//tools:example_compiler"),
allow_single_file = True,
executable = True,
cfg = "exec",
),
},
)
ในตัวอย่างนี้ เป้าหมายทุกรายการของประเภท example_library
มีการพึ่งพาคอมไพเลอร์ //tools:example_compiler
โดยนัย ซึ่งช่วยให้ฟังก์ชันการใช้งานของ example_library
สร้างการดำเนินการที่เรียกใช้คอมไพเลอร์ได้ แม้ว่าผู้ใช้จะไม่ได้ส่งผ่านป้ายกำกับเป็นอินพุตก็ตาม เนื่องจาก _compiler
เป็นแอตทริบิวต์ส่วนตัว ctx.attr._compiler
จึงจะชี้ไปยัง //tools:example_compiler
เสมอในเป้าหมายทั้งหมดของกฎประเภทนี้ หรือจะตั้งชื่อแอตทริบิวต์เป็น compiler
โดยไม่ต้องใส่ขีดล่างและเก็บค่าเริ่มต้นไว้ก็ได้ วิธีนี้จะช่วยให้ผู้ใช้แทนที่คอมไพเลอร์อื่นได้หากจำเป็น แต่ต้องไม่รับรู้เกี่ยวกับป้ายกำกับของคอมไพเลอร์
โดยทั่วไปแล้ว Dependency ที่ไม่ชัดจะใช้กับเครื่องมือที่อยู่ในที่เก็บข้อมูลเดียวกับการติดตั้งใช้งานกฎ หากเครื่องมือมาจากแพลตฟอร์มการเรียกใช้หรือที่เก็บข้อมูลอื่นแทน กฎควรรับเครื่องมือนั้นจากชุดเครื่องมือ
แอตทริบิวต์เอาต์พุต
แอตทริบิวต์เอาต์พุต เช่น attr.output
และ attr.output_list
ประกาศไฟล์เอาต์พุตที่เป้าหมายสร้างขึ้น แอตทริบิวต์เหล่านี้แตกต่างจากแอตทริบิวต์ของ Dependency ตรง 2 ประการดังนี้
- พวกเขากำหนดเป้าหมายไฟล์เอาต์พุตแทนที่จะอ้างอิงเป้าหมายที่กำหนดไว้ที่อื่น
- เป้าหมายไฟล์เอาต์พุตจะขึ้นอยู่กับเป้าหมายของกฎที่สร้างอินสแตนซ์ ไม่ใช่ในทางกลับกัน
โดยปกติแล้ว แอตทริบิวต์เอาต์พุตจะใช้ก็ต่อเมื่อกฎต้องสร้างเอาต์พุตที่มีชื่อที่ผู้ใช้กำหนด ซึ่งไม่สามารถอิงตามชื่อเป้าหมายได้ หากกฎมีแอตทริบิวต์เอาต์พุต 1 รายการ โดยทั่วไปจะมีชื่อว่า out
หรือ outs
แอตทริบิวต์เอาต์พุตเป็นวิธีที่แนะนำในการสร้างเอาต์พุตที่ประกาศไว้ล่วงหน้า ซึ่งสามารถอ้างอิงหรือขอในบรรทัดคำสั่งโดยเฉพาะ
ฟังก์ชันการใช้งาน
กฎทุกข้อต้องมีฟังก์ชัน implementation
ฟังก์ชันเหล่านี้จะทํางานในระยะการวิเคราะห์อย่างเคร่งครัด และเปลี่ยนรูปแบบกราฟของเป้าหมายที่สร้างขึ้นในระยะการโหลดเป็นกราฟของการดําเนินการที่จะดําเนินการในระยะการดําเนินการ ด้วยเหตุนี้ ฟังก์ชันการใช้งานจึงอ่านหรือเขียนไฟล์ไม่ได้
ฟังก์ชันการใช้กฎมักจะเป็นแบบส่วนตัว (ตั้งชื่อโดยมีขีดล่างนำหน้า) ตามธรรมเนียมแล้ว ไฟล์เหล่านี้จะมีชื่อเหมือนกับกฎ แต่จะมี_impl
ต่อท้าย
ฟังก์ชันการติดตั้งใช้งานจะใช้พารามิเตอร์เพียง 1 ตัวเท่านั้น ซึ่งก็คือบริบทของกฎ ซึ่งปกติจะมีชื่อว่า ctx
ซึ่งจะแสดงรายการผู้ให้บริการ
เป้าหมาย
ระบบจะแสดงข้อมูลพึ่งพาเป็นออบเจ็กต์ Target
ในเวลาที่วิเคราะห์ ออบเจ็กต์เหล่านี้มี providers ที่สร้างขึ้นเมื่อระบบเรียกใช้ฟังก์ชันการใช้งานของเป้าหมาย
ctx.attr
มีช่องที่สอดคล้องกับชื่อแอตทริบิวต์การพึ่งพาแต่ละรายการ ซึ่งมีออบเจ็กต์ Target
ที่แสดงถึงความสัมพันธ์โดยตรงแต่ละรายการโดยใช้แอตทริบิวต์นั้น สำหรับแอตทริบิวต์ label_list
รายการนี้คือ
Targets
สำหรับแอตทริบิวต์ label
จะเป็น Target
หรือ None
รายการเดียว
ฟังก์ชันการใช้งานของเป้าหมายจะแสดงผลรายการออบเจ็กต์ผู้ให้บริการ ดังนี้
return [ExampleInfo(headers = depset(...))]
ซึ่งเข้าถึงได้โดยใช้การเขียนดัชนี ([]
) โดยให้ประเภทของผู้ให้บริการเป็นคีย์ ผู้ให้บริการเหล่านี้อาจเป็นผู้ให้บริการที่กำหนดเองที่กำหนดไว้ใน Starlark หรือผู้ให้บริการสำหรับกฎแบบเนทีฟ และเป็นตัวแปรร่วมของ Starlark
ตัวอย่างเช่น หากกฎใช้ไฟล์ส่วนหัวโดยใช้แอตทริบิวต์ hdrs
และส่งไฟล์เหล่านั้นไปยังการดำเนินการคอมไพล์ของเป้าหมายและผู้ใช้งาน กฎอาจรวบรวมไฟล์เหล่านั้นดังนี้
def _example_library_impl(ctx):
...
transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]
มีรูปแบบโครงสร้างเดิมที่เราไม่แนะนําอย่างยิ่งและควรย้ายข้อมูลกฎออกจากรูปแบบดังกล่าว
ไฟล์
ไฟล์จะแสดงด้วยออบเจ็กต์ File
เนื่องจาก Bazel ไม่ได้ใช้ I/O ไฟล์ในช่วงการวิเคราะห์ ระบบจึงไม่สามารถใช้ออบเจ็กต์เหล่านี้เพื่ออ่านหรือเขียนเนื้อหาไฟล์โดยตรง แต่ระบบจะส่งค่าไปยังฟังก์ชันที่ส่งออกการดำเนินการ (ดู ctx.actions
) เพื่อสร้างชิ้นส่วนของกราฟการดำเนินการ
File
อาจเป็นไฟล์ต้นฉบับหรือไฟล์ที่สร้างขึ้นก็ได้ ไฟล์ที่สร้างขึ้นแต่ละไฟล์ต้องเป็นผลลัพธ์ของการดำเนินการเพียง 1 รายการ ไฟล์ต้นฉบับต้องไม่ใช่เอาต์พุตของการดำเนินการใดๆ
สำหรับแอตทริบิวต์ทรัพยากร Dependency แต่ละรายการ ช่องที่เกี่ยวข้องของ ctx.files
จะประกอบด้วยรายการเอาต์พุตเริ่มต้นของการขึ้นต่อกันทั้งหมดที่ใช้แอตทริบิวต์ดังกล่าว ดังนี้
def _example_library_impl(ctx):
...
headers = depset(ctx.files.hdrs, transitive = transitive_headers)
srcs = ctx.files.srcs
...
ctx.file
มี File
หรือ None
รายการเดียวสำหรับแอตทริบิวต์ Dependency ที่กำหนดข้อกำหนดเป็น allow_single_file = True
ctx.executable
จะทํางานเหมือนกับ ctx.file
แต่จะประกอบด้วยเฉพาะช่องสําหรับแอตทริบิวต์ Dependency ที่ตั้งค่าข้อกําหนดเป็น executable = True
การประกาศเอาต์พุต
ในช่วงการวิเคราะห์ ฟังก์ชันการใช้งานของกฎสามารถสร้างเอาต์พุตได้
เนื่องจากต้องทราบป้ายกำกับทั้งหมดในระยะการโหลดเอาต์พุตเพิ่มเติมเหล่านี้จึงไม่มีป้ายกำกับ คุณสร้างออบเจ็กต์ File
สำหรับเอาต์พุตได้โดยใช้ ctx.actions.declare_file
และ ctx.actions.declare_directory
ชื่อของเอาต์พุตมักจะอิงตามชื่อของเป้าหมาย ซึ่งก็คือctx.label.name
def _example_library_impl(ctx):
...
output_file = ctx.actions.declare_file(ctx.label.name + ".output")
...
สําหรับเอาต์พุตที่ประกาศไว้ล่วงหน้า เช่น เอาต์พุตที่สร้างสําหรับแอตทริบิวต์เอาต์พุต ระบบจะดึงข้อมูลออบเจ็กต์ File
จากช่องที่เกี่ยวข้องของ ctx.outputs
แทน
การทำงาน
การดำเนินการจะอธิบายวิธีสร้างชุดเอาต์พุตจากชุดอินพุต เช่น "เรียกใช้ gcc ใน hello.c และรับ hello.o" เมื่อสร้างการดำเนินการแล้ว Bazel จะไม่เรียกใช้คำสั่งทันที โดยจะบันทึกไว้ในกราฟของความสัมพันธ์ เนื่องจากการดำเนินการหนึ่งๆ อาจขึ้นอยู่กับเอาต์พุตของการดำเนินการอื่น ตัวอย่างเช่น ใน C ต้องเรียก linker หลังคอมไพเลอร์
ฟังก์ชันเอนกประสงค์ที่สร้างการดำเนินการจะกำหนดไว้ใน ctx.actions
ctx.actions.run
เพื่อเรียกใช้ไฟล์ปฏิบัติการctx.actions.run_shell
เพื่อเรียกใช้คำสั่ง Shellctx.actions.write
เพื่อเขียนสตริงลงในไฟล์ctx.actions.expand_template
เพื่อ generate ไฟล์จากเทมเพลต
ctx.actions.args
ใช้เพื่อรวบรวมอาร์กิวเมนต์สําหรับการดําเนินการได้อย่างมีประสิทธิภาพ ซึ่งจะหลีกเลี่ยงการยุบชุดข้อมูลย่อยจนกว่าจะถึงเวลาดำเนินการ
def _example_library_impl(ctx):
...
transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
headers = depset(ctx.files.hdrs, transitive = transitive_headers)
srcs = ctx.files.srcs
inputs = depset(srcs, transitive = [headers])
output_file = ctx.actions.declare_file(ctx.label.name + ".output")
args = ctx.actions.args()
args.add_joined("-h", headers, join_with = ",")
args.add_joined("-s", srcs, join_with = ",")
args.add("-o", output_file)
ctx.actions.run(
mnemonic = "ExampleCompile",
executable = ctx.executable._compiler,
arguments = [args],
inputs = inputs,
outputs = [output_file],
)
...
การดำเนินการจะนํารายการหรือชุดไฟล์อินพุตไปสร้างรายการไฟล์เอาต์พุต (ที่ไม่ใช่ไฟล์ว่าง) คุณต้องทราบชุดไฟล์อินพุตและเอาต์พุตในระยะการวิเคราะห์ ซึ่งอาจขึ้นอยู่กับค่าของแอตทริบิวต์ รวมถึงผู้ให้บริการจากข้อกําหนด แต่ต้องไม่ขึ้นอยู่กับผลลัพธ์ของการดําเนินการ เช่น หากการดำเนินการเรียกใช้คำสั่ง unzip คุณต้องระบุไฟล์ที่คุณต้องการขยาย (ก่อนเรียกใช้ unzip) การดำเนินการที่จะสร้างจำนวนตัวแปรของไฟล์ภายในสามารถรวมไฟล์เหล่านั้นไว้ในไฟล์เดียว (เช่น zip, tar หรือรูปแบบที่เก็บถาวรอื่นๆ)
การดำเนินการต้องป้อนข้อมูลทั้งหมด อนุญาตให้ป้อนข้อมูลรายการที่ไม่ได้ใช้ แต่จะไม่มีประสิทธิภาพ
การดำเนินการต้องสร้างเอาต์พุตทั้งหมด ผู้ใช้อาจเขียนไฟล์อื่นๆ แต่ผู้บริโภคจะไม่เห็นข้อมูลที่ไม่ได้อยู่ในเอาต์พุต เอาต์พุตที่ประกาศทั้งหมดจะต้องเขียนโดยการดำเนินการบางอย่าง
การดำเนินการจะคล้ายกับฟังก์ชันบริสุทธิ์ กล่าวคือ ควรใช้เฉพาะอินพุตที่ระบุไว้ และหลีกเลี่ยงการเข้าถึงข้อมูลคอมพิวเตอร์ ชื่อผู้ใช้ นาฬิกา เครือข่าย หรืออุปกรณ์ I/O (ยกเว้นการอ่านอินพุตและเขียนเอาต์พุต) ซึ่งสำคัญเนื่องจากระบบจะแคชเอาต์พุตไว้และนํามาใช้ซ้ำ
Bazel จะแก้ไขการพึ่งพา ซึ่งจะเป็นผู้ตัดสินใจว่าจะใช้การดำเนินการใด หากมีวงจรในกราฟความเกี่ยวข้อง แสดงว่ามีข้อผิดพลาด การสร้างการดำเนินการไม่ได้รับประกันว่าการดำเนินการนั้นจะได้รับการดำเนินการ ซึ่งขึ้นอยู่กับว่าจำเป็นต้องใช้เอาต์พุตสำหรับบิลด์หรือไม่
ผู้ให้บริการ
ผู้ให้บริการคือส่วนของข้อมูลที่กฎเปิดเผยต่อกฎอื่นๆ ที่เกี่ยวข้อง ข้อมูลนี้อาจรวมถึงไฟล์เอาต์พุต ไลบรารี พารามิเตอร์ที่จะส่งผ่านไปยังบรรทัดคำสั่งของเครื่องมือ หรืออื่นๆ ที่ผู้บริโภคเป้าหมายควรทราบ
เนื่องจากฟังก์ชันการใช้งานของกฎจะอ่านได้เฉพาะผู้ให้บริการจากข้อกําหนดเบื้องต้นโดยตรงของเป้าหมายที่สร้างขึ้น กฎจึงต้องส่งต่อข้อมูลจากข้อกําหนดเบื้องต้นของเป้าหมายที่ผู้ใช้ปลายทางของเป้าหมายจําเป็นต้องทราบ โดยปกติแล้วจะเป็นการเก็บรวบรวมข้อมูลดังกล่าวไว้ใน depset
ผู้ให้บริการของเป้าหมายจะระบุด้วยรายการออบเจ็กต์ผู้ให้บริการที่ฟังก์ชันการติดตั้งใช้งานแสดงผล
นอกจากนี้ คุณยังเขียนฟังก์ชันการติดตั้งใช้งานแบบเก่าในสไตล์เดิมได้ โดยที่ฟังก์ชันการติดตั้งใช้งานจะแสดงผล struct
แทนรายการออบเจ็กต์ผู้ให้บริการ เราขอแนะนำอย่างยิ่งว่าอย่าใช้รูปแบบนี้และควรย้ายข้อมูลกฎออกจากรูปแบบนี้
เอาต์พุตเริ่มต้น
เอาต์พุตเริ่มต้นของเป้าหมายคือเอาต์พุตที่ระบบขอโดยค่าเริ่มต้นเมื่อมีการขอบิลด์เป้าหมายในบรรทัดคำสั่ง เช่น เป้าหมาย //pkg:foo
ของ java_library
มี foo.jar
เป็นเอาต์พุตเริ่มต้น จึงสร้างด้วยคำสั่ง bazel build //pkg:foo
พารามิเตอร์ files
ของ DefaultInfo
จะระบุเอาต์พุตเริ่มต้น ดังนี้
def _example_library_impl(ctx):
...
return [
DefaultInfo(files = depset([output_file]), ...),
...
]
หากการใช้งานกฎไม่ได้แสดงผล DefaultInfo
หรือไม่ได้ระบุพารามิเตอร์ files
ค่าเริ่มต้นของ DefaultInfo.files
จะเป็นเอาต์พุตที่ประกาศล่วงหน้าทั้งหมด (โดยทั่วไปคือเอาต์พุตที่สร้างโดยแอตทริบิวต์เอาต์พุต)
กฎที่ดำเนินการต่างๆ ควรมีเอาต์พุตเริ่มต้น แม้ว่าจะไม่มีการกำหนดให้มีการใช้เอาต์พุตเหล่านั้นโดยตรงก็ตาม ระบบจะตัดการดำเนินการที่ไม่ได้อยู่ในกราฟของเอาต์พุตที่ขอออก หากเอาต์พุตมีไว้สําหรับผู้ใช้เป้าหมายเท่านั้น ระบบจะไม่ดําเนินการเหล่านั้นเมื่อสร้างเป้าหมายแยกต่างหาก ซึ่งจะทำให้การแก้ไขข้อบกพร่องยากขึ้นเพราะการสร้างเป้าหมายที่ดำเนินการไม่สำเร็จขึ้นมาใหม่จะไม่ทำให้เกิดปัญหาซ้ำอีก
ไฟล์รันไทม์
ไฟล์รันไทม์คือชุดไฟล์ที่เป้าหมายใช้ขณะรันไทม์ (ไม่ใช่ขณะสร้าง) ในระหว่างระยะการดําเนินการ Bazel จะสร้างลําดับชั้นไดเรกทอรีที่มีลิงก์สัญลักษณ์ซึ่งชี้ไปยังไฟล์รันไทม์ ซึ่งจะจัดเตรียมสภาพแวดล้อมสําหรับไบนารีเพื่อให้เข้าถึงไฟล์รันไทม์ได้ในระหว่างรันไทม์
คุณสามารถเพิ่มไฟล์เรียกใช้ด้วยตนเองระหว่างการสร้างกฎ
สามารถสร้างออบเจ็กต์ runfiles
โดยใช้เมธอด runfiles
ในบริบทกฎ ctx.runfiles
และส่งไปยังพารามิเตอร์ runfiles
ใน DefaultInfo
ระบบจะเพิ่มเอาต์พุตที่เรียกใช้งานได้ของกฎที่เรียกใช้งานได้ลงในไฟล์เรียกใช้โดยปริยาย
กฎบางข้อจะระบุแอตทริบิวต์ ซึ่งโดยทั่วไปจะตั้งชื่อว่า data
ซึ่งจะมีการเพิ่มเอาต์พุตลงในการเรียกใช้ไฟล์ของเป้าหมาย นอกจากนี้ คุณควรผสานไฟล์รันไทม์จาก data
รวมถึงจากแอตทริบิวต์ใดๆ ที่อาจให้โค้ดสําหรับการเรียกใช้ขั้นสุดท้าย ซึ่งโดยทั่วไปแล้วคือ srcs
(ซึ่งอาจมีเป้าหมาย filegroup
ที่มี data
ที่เกี่ยวข้อง) และ deps
def _example_library_impl(ctx):
...
runfiles = ctx.runfiles(files = ctx.files.data)
transitive_runfiles = []
for runfiles_attr in (
ctx.attr.srcs,
ctx.attr.hdrs,
ctx.attr.deps,
ctx.attr.data,
):
for target in runfiles_attr:
transitive_runfiles.append(target[DefaultInfo].default_runfiles)
runfiles = runfiles.merge_all(transitive_runfiles)
return [
DefaultInfo(..., runfiles = runfiles),
...
]
ผู้ให้บริการที่กําหนดเอง
คุณสามารถกําหนดผู้ให้บริการได้โดยใช้ฟังก์ชัน provider
เพื่อสื่อข้อมูลเฉพาะกฎ
ExampleInfo = provider(
"Info needed to compile/link Example code.",
fields = {
"headers": "depset of header Files from transitive dependencies.",
"files_to_link": "depset of Files from compilation.",
},
)
จากนั้นฟังก์ชันการใช้งานกฎจะสร้างและแสดงผลอินสแตนซ์ของผู้ให้บริการ ดังนี้
def _example_library_impl(ctx):
...
return [
...
ExampleInfo(
headers = headers,
files_to_link = depset(
[output_file],
transitive = [
dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
],
),
)
]
การเริ่มต้นที่กำหนดเองของผู้ให้บริการ
คุณอาจป้องกันการเริ่มอินสแตนซ์ของผู้ให้บริการด้วยตรรกะการประมวลผลล่วงหน้าและการตรวจสอบที่กำหนดเอง ซึ่งสามารถใช้เพื่อตรวจสอบว่าอินสแตนซ์ผู้ให้บริการทั้งหมดเป็นไปตามอินตัวแปรคงที่บางรายการ หรือเพื่อให้ผู้ใช้ได้รับ API ที่สะอาดขึ้นสำหรับการรับอินสแตนซ์
ซึ่งทำได้โดยการส่ง init
Callback ไปยังฟังก์ชัน provider
หากระบุ Callback นี้ ประเภทการคืนสินค้าของ provider()
จะเปลี่ยนเป็น Tuple ที่มี 2 ค่า ได้แก่ สัญลักษณ์ผู้ให้บริการซึ่งเป็นค่าการแสดงผลปกติเมื่อไม่ได้ใช้ init
และ "ตัวสร้างดิบ"
ในกรณีนี้ เมื่อมีการเรียกสัญลักษณ์ผู้ให้บริการ แทนที่จะส่งคืนอินสแตนซ์ใหม่โดยตรง ระบบจะส่งต่ออาร์กิวเมนต์ไปตาม Callback init
ค่าที่แสดงผลของ callback ต้องเป็นพจนานุกรมที่แมปชื่อฟิลด์ (สตริง) กับค่า ซึ่งจะใช้เพื่อเริ่มต้นฟิลด์ของอินสแตนซ์ใหม่ โปรดทราบว่าการเรียกกลับอาจมีลายเซ็นใดก็ได้ และหากอาร์กิวเมนต์ไม่ตรงกับลายเซ็น ระบบจะรายงานข้อผิดพลาดเหมือนกับว่ามีการเรียกใช้การเรียกกลับโดยตรง
ในทางตรงกันข้าม เครื่องมือสร้างแบบดิบจะข้ามการเรียกกลับ init
ตัวอย่างต่อไปนี้ใช้ 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
)
ตัวสร้างแบบดิบสามารถใช้เพื่อกำหนดฟังก์ชันการสร้างแบบสาธารณะทางเลือกซึ่งไม่ผ่านตรรกะ 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
อย่างไรก็ตาม ไบนารีที่เรียกจากไฟล์รันไทม์จะไม่สามารถทําสมมติฐานเดียวกันได้ เพื่อลดปัญหานี้ แต่ละไบนารีควรมีวิธีรับรูทของไฟล์รันไทม์เป็นพารามิเตอร์โดยใช้อาร์กิวเมนต์หรือแฟล็กของสภาพแวดล้อมหรือบรรทัดคำสั่ง วิธีนี้ช่วยให้ไบนารีส่งต่อรูทของไฟล์การเรียกใช้ 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
ยังแสดงผลได้จากทั้งแง่มุมและเป้าหมายกฎที่ใช้แง่มุมนั้น โดยไม่จําเป็นต้องกำหนดกลุ่มเอาต์พุตเดียวกัน ต่างจากผู้ให้บริการส่วนใหญ่ ในกรณีนี้ ระบบจะผสานผู้ให้บริการที่รวมกัน
โปรดทราบว่าโดยทั่วไปแล้ว OutputGroupInfo
ไม่ควรใช้เพื่อสื่อถึงการจัดเรียงไฟล์ที่เฉพาะเจาะจงจากเป้าหมายไปยังการกระทําของผู้บริโภค ให้กําหนดผู้ให้บริการเฉพาะกฎแทน
การกำหนดค่า
สมมติว่าคุณต้องการสร้างไบนารี C++ สําหรับสถาปัตยกรรมอื่น การสร้างอาจมีความซับซ้อนและมีหลายขั้นตอน ไฟล์ไบนารีระดับกลางบางไฟล์ เช่น คอมไพเลอร์และเครื่องมือสร้างโค้ด ต้องทำงานบนแพลตฟอร์มการเรียกใช้ (ซึ่งอาจเป็นโฮสต์หรือผู้ดำเนินการระยะไกล) คุณต้องสร้างไบนารีบางรายการ เช่น เอาต์พุตสุดท้ายสำหรับสถาปัตยกรรมเป้าหมาย
ด้วยเหตุนี้ Bazel จึงมีแนวคิด "การกำหนดค่า" และการเปลี่ยน เป้าหมายที่อยู่บนสุด (เป้าหมายที่ขอในบรรทัดคำสั่ง) จะอยู่ในการกำหนดค่า "target" ส่วนเครื่องมือที่ควรทำงานบนแพลตฟอร์มการดำเนินการจะอยู่ในการกำหนดค่า "exec" กฎอาจสร้างการดำเนินการที่แตกต่างกันตามการกำหนดค่า เช่น เปลี่ยนสถาปัตยกรรม CPU ที่ส่งไปยังคอมไพเลอร์ ในบางกรณี คุณอาจต้องใช้คลังเดียวกันสำหรับการกําหนดค่าที่แตกต่างกัน หากเป็นเช่นนั้น ระบบจะวิเคราะห์และอาจสร้างไฟล์หลายครั้ง
โดยค่าเริ่มต้น Bazel จะสร้างทรัพยากร Dependency ของเป้าหมายในการกำหนดค่าเดียวกันกับตัวเป้าหมาย กล่าวคือไม่มีการเปลี่ยน เมื่อทรัพยากร Dependency เป็นเครื่องมือที่จําเป็นต่อการสร้างเป้าหมาย แอตทริบิวต์ที่เกี่ยวข้องควรระบุการเปลี่ยนไปใช้การกําหนดค่า exec ซึ่งจะทำให้เครื่องมือและการขึ้นต่อกันทั้งหมดของเครื่องมือสร้างขึ้นสำหรับแพลตฟอร์มการดำเนินการ
สําหรับแอตทริบิวต์การพึ่งพาแต่ละรายการ คุณสามารถใช้ cfg
เพื่อตัดสินใจว่าควรสร้างการพึ่งพาในการกําหนดค่าเดียวกันหรือเปลี่ยนไปใช้การกําหนดค่า exec
หากแอตทริบิวต์ทรัพยากร Dependency มีแฟล็ก 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
อย่างไรก็ตาม คุณต้องประกาศส่วนย่อยที่จำเป็นทั้งหมดเพื่อหลีกเลี่ยงข้อผิดพลาดในการเข้าถึง ดังนี้
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
...
)
ลิงก์สัญลักษณ์ของ Runfile
โดยทั่วไป เส้นทางแบบสัมพัทธ์ของไฟล์ในต้นไม้ runfiles จะเหมือนกับเส้นทางแบบสัมพัทธ์ของไฟล์นั้นในต้นไม้ต้นทางหรือต้นไม้เอาต์พุตที่สร้างขึ้น หากต้องแตกต่างกันด้วยเหตุผลบางอย่าง คุณสามารถระบุอาร์กิวเมนต์ root_symlinks
หรือ symlinks
root_symlinks
คือพจนานุกรมที่จับคู่เส้นทางกับไฟล์ โดยเส้นทางจะสัมพันธ์กับรูทของไดเรกทอรี runfiles พจนานุกรม symlinks
เหมือนกัน แต่ชื่อพื้นที่ทำงานหลักจะมีคำนำหน้าโดยนัย (ไม่ใช่ชื่อของที่เก็บที่มีเป้าหมายปัจจุบัน)
...
runfiles = ctx.runfiles(
root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
)
# Creates something like:
# sometarget.runfiles/
# some/
# path/
# here.foo -> some_data_file2
# <workspace_name>/
# some/
# path/
# here.bar -> some_data_file3
หากใช้ symlinks
หรือ root_symlinks
โปรดระมัดระวังอย่าแมปไฟล์ 2 ไฟล์ที่แตกต่างกันไปยังเส้นทางเดียวกันในลําดับชั้น runfiles ซึ่งจะทำให้การบิลด์ล้มเหลวพร้อมข้อผิดพลาดที่อธิบายถึงความขัดแย้ง หากต้องการแก้ไข คุณจะต้องแก้ไขอาร์กิวเมนต์ ctx.runfiles
เพื่อนำการทับซ้อนออก การตรวจสอบนี้จะดำเนินการกับเป้าหมายทั้งหมดที่ใช้กฎของคุณ รวมถึงเป้าหมายทุกประเภทที่ขึ้นอยู่กับเป้าหมายเหล่านั้น ซึ่งจะเสี่ยงอย่างยิ่งหากเครื่องมือของคุณมีแนวโน้มที่จะถูกใช้โดยเครื่องมืออื่นแบบเปลี่ยนเส้นทาง ชื่อลิงก์สัญลักษณ์ต้องไม่ซ้ำกันในไฟล์รันไทม์ของเครื่องมือและไฟล์ที่ต้องพึ่งพาทั้งหมด
การครอบคลุมของโค้ด
เมื่อเรียกใช้คําสั่ง coverage
บิลด์อาจต้องเพิ่มเครื่องมือวัดการครอบคลุมสําหรับเป้าหมายบางรายการ นอกจากนี้ บิลด์ยังจะรวบรวมรายการไฟล์ต้นฉบับที่มีการวัดคุมด้วย กลุ่มเป้าหมายย่อยที่พิจารณาจะควบคุมโดย Flag --instrumentation_filter
จะยกเว้นเป้าหมายทดสอบไว้ เว้นแต่จะระบุ --instrument_test_targets
ไว้
หากการใช้งานกฎเพิ่มเครื่องมือวัดการครอบคลุม ณ เวลาสร้าง การใช้งานจะต้องพิจารณาเรื่องนี้ในฟังก์ชันการใช้งาน ctx.coverage_instrumented จะแสดงผลเป็น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
ระบบจะสร้าง InstrumentedFilesInfo
เริ่มต้นขึ้นโดยแต่ละแอตทริบิวต์ความเกี่ยวข้องที่ไม่ใช่เครื่องมือซึ่งไม่ได้ตั้งค่า cfg
เป็น "exec"
ในสคีมาแอตทริบิวต์ใน dependency_attributes
(นี่ไม่ใช่ลักษณะการทำงานที่ดีที่สุดเนื่องจากจะใส่แอตทริบิวต์อย่าง srcs
ใน dependency_attributes
แทนที่จะเป็น source_attributes
แต่ก็ทำให้ไม่จำเป็นต้องกำหนดค่าการครอบคลุมอย่างชัดแจ้งสำหรับกฎทั้งหมดในห่วงโซ่การขึ้นต่อกัน)
การดำเนินการตรวจสอบ
บางครั้งคุณต้องตรวจสอบบางอย่างเกี่ยวกับบิลด์ และข้อมูลที่ต้องใช้ในการตรวจสอบความถูกต้องนั้นมีอยู่ในอาร์ติแฟกต์เท่านั้น (ไฟล์ต้นฉบับหรือไฟล์ที่สร้างขึ้น) เนื่องจากข้อมูลนี้อยู่ในอาร์ติแฟกต์ กฎจึงไม่สามารถทำการตรวจสอบนี้ ณ เวลาการวิเคราะห์ได้ เนื่องจากกฎไม่สามารถอ่านไฟล์ได้ แต่การดำเนินการต้องดำเนินการตรวจสอบนี้ ณ เวลาดำเนินการ เมื่อการตรวจสอบล้มเหลว การดำเนินการจะล้มเหลว และบิลด์ก็จะล้มเหลวเช่นกัน
ตัวอย่างการตรวจสอบที่อาจทำงาน ได้แก่ การวิเคราะห์แบบคงที่ การตรวจหาข้อบกพร่อง การตรวจสอบความเชื่อมโยงและความสอดคล้อง และการตรวจสอบรูปแบบ
การดำเนินการตรวจสอบยังช่วยปรับปรุงประสิทธิภาพการสร้างได้โดยย้ายการดำเนินการบางส่วนที่ไม่จำเป็นต่อการสร้างอาร์ติแฟกต์ไปยังการดำเนินการแยกต่างหาก เช่น หากสามารถแยกการดำเนินการเดียวที่มีการคอมไพล์และ Linting ออกจากการทำงานการคอมไพล์และการดำเนินการวิเคราะห์โค้ด ก็สามารถใช้การดำเนินการวิเคราะห์โค้ดเป็นการดำเนินการตรวจสอบ และเรียกใช้ไปพร้อมกับการดำเนินการอื่นๆ
"การดำเนินการตรวจสอบ" เหล่านี้มักจะไม่สร้างสิ่งที่นำไปใช้ที่อื่นในบิลด์ เนื่องจากต้องยืนยันเฉพาะข้อมูลเกี่ยวกับอินพุตเท่านั้น แต่วิธีนี้ทำให้เกิดปัญหาขึ้น หากการดำเนินการตรวจสอบไม่สร้างสิ่งที่ใช้ในที่อื่นในบิลด์ กฎจะทําให้การดำเนินการทำงานได้อย่างไร ที่ผ่านมาแนวทางคือให้การดำเนินการตรวจสอบแสดงผลไฟล์ว่าง และเพิ่มเอาต์พุตนั้นลงในอินพุตของการดำเนินการสำคัญอื่นๆ ในบิลด์
วิธีนี้ใช้งานได้เนื่องจาก Bazel จะเรียกใช้การดำเนินการตรวจสอบทุกครั้งที่เรียกใช้การดำเนินการคอมไพล์ แต่มีข้อเสียที่สำคัญดังนี้
การดำเนินการตรวจสอบอยู่ในเส้นทางที่สำคัญของการสร้าง เนื่องจาก Bazel คิดว่าเอาต์พุตที่ว่างเปล่าจำเป็นสำหรับการเรียกใช้การดำเนินการคอมไพล์ จึงเรียกใช้การดำเนินการตรวจสอบก่อน แม้ว่าการดำเนินการคอมไพล์จะไม่สนใจอินพุตก็ตาม ซึ่งจะลดการทำงานแบบขนานและทำให้บิลด์ช้าลง
หากการดำเนินการอื่นๆ ในบิลด์อาจทำงานแทนการดำเนินการคอมไพล์ คุณจะต้องเพิ่มเอาต์พุตที่ว่างเปล่าของการดำเนินการตรวจสอบลงในการดำเนินการเหล่านั้นด้วย (เช่น เอาต์พุต Jar ต้นทางของ
java_library
) การดำเนินการนี้ยังเป็นปัญหาหากมีการเพิ่มการดำเนินการใหม่ที่อาจทำงานแทนการดำเนินการคอมไพล์ในภายหลัง และไม่ได้ออกผลการตรวจสอบที่ว่างเปล่าไว้โดยไม่ตั้งใจ
ปัญหาเหล่านี้จะแก้ไขได้โดยใช้กลุ่มเอาต์พุตการตรวจสอบ
กลุ่มเอาต์พุตของการตรวจสอบ
กลุ่มเอาต์พุตการตรวจสอบคือกลุ่มเอาต์พุตที่ออกแบบมาเพื่อเก็บเอาต์พุตที่ไม่มีการนําไปใช้ของการดำเนินการตรวจสอบ เพื่อที่จะไม่ต้องเพิ่มเอาต์พุตเหล่านี้ลงในอินพุตของการดำเนินการอื่นๆ
กลุ่มนี้มีความเป็นพิเศษตรงที่ระบบจะขอเอาต์พุตของกลุ่มนี้เสมอ ไม่ว่าค่าของ Flag --output_groups
จะเป็นอย่างไร และไม่ว่าระบบจะพึ่งพาเป้าหมายอย่างไร (เช่น ในบรรทัดคำสั่ง ในฐานะทรัพยากร หรือการเรียกใช้เอาต์พุตโดยนัยของเป้าหมาย) โปรดทราบว่าการแคชตามปกติและการเพิ่มทีละน้อยจะยังคงมีผลอยู่ หากอินพุตของการดำเนินการตรวจสอบไม่มีการเปลี่ยนแปลงและการดำเนินการตรวจสอบก่อนหน้านี้สำเร็จ ระบบจะไม่เรียกใช้การดำเนินการตรวจสอบ
การใช้กลุ่มเอาต์พุตนี้ยังกำหนดให้การดำเนินการตรวจสอบต้องแสดงผลไฟล์บางไฟล์ แม้ว่าจะเป็นไฟล์เปล่าก็ตาม ซึ่งอาจต้องรวมเครื่องมือบางอย่างที่โดยปกติแล้วจะไม่สร้างเอาต์พุตเพื่อให้สร้างไฟล์
ระบบจะไม่เรียกใช้การดำเนินการตรวจสอบของเป้าหมายในกรณีต่อไปนี้
- เมื่อมีการพึ่งพาเป้าหมายในฐานะเครื่องมือ
- เมื่อเป้าหมายเป็นข้อกําหนดโดยนัย (เช่น แอตทริบิวต์ที่ขึ้นต้นด้วย "_")
- เมื่อสร้างเป้าหมายในการกําหนดค่า exec
โดยสมมติว่าเป้าหมายเหล่านี้มีบิลด์และการทดสอบแยกต่างหากซึ่งจะเปิดเผยความล้มเหลวในการตรวจสอบ
การใช้กลุ่มเอาต์พุตการตรวจสอบ
กลุ่มเอาต์พุตของการตรวจสอบมีชื่อว่า _validation
และใช้เหมือนกับกลุ่มเอาต์พุตอื่นๆ
def _rule_with_validation_impl(ctx):
ctx.actions.write(ctx.outputs.main, "main output\n")
ctx.actions.write(ctx.outputs.implicit, "implicit output\n")
validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
ctx.actions.run(
outputs = [validation_output],
executable = ctx.executable._validation_tool,
arguments = [validation_output.path],
)
return [
DefaultInfo(files = depset([ctx.outputs.main])),
OutputGroupInfo(_validation = depset([validation_output])),
]
rule_with_validation = rule(
implementation = _rule_with_validation_impl,
outputs = {
"main": "%{name}.main",
"implicit": "%{name}.implicit",
},
attrs = {
"_validation_tool": attr.label(
default = Label("//validation_actions:validation_tool"),
executable = True,
cfg = "exec"
),
}
)
โปรดสังเกตว่าไม่มีการเพิ่มไฟล์เอาต์พุตสำหรับการตรวจสอบลงใน DefaultInfo
หรืออินพุตสำหรับการดำเนินการอื่นๆ การดําเนินการตรวจสอบสําหรับเป้าหมายของกฎประเภทนี้จะยังคงทํางานหากเป้าหมายนั้นใช้ป้ายกํากับ หรือใช้เอาต์พุตโดยนัยของเป้าหมายโดยตรงหรือโดยอ้อม
โดยปกติแล้วเอาต์พุตของการดำเนินการตรวจสอบจะต้องไปอยู่ในกลุ่มเอาต์พุตการตรวจสอบเท่านั้น และไม่เพิ่มลงในอินพุตของการดำเนินการอื่นๆ เนื่องจากอาจทำลายผลลัพธ์การทำงานพร้อมกัน อย่างไรก็ตาม โปรดทราบว่า Bazel ไม่มี การตรวจสอบพิเศษใดๆ เพื่อบังคับใช้การตรวจสอบนี้ ดังนั้น คุณควรทดสอบว่าจะไม่มีการเพิ่มเอาต์พุตการดำเนินการตรวจสอบลงในอินพุตของการดำเนินการใดๆ ในการทดสอบกฎของ Starlark เช่น
load("@bazel_skylib//lib:unittest.bzl", "analysistest")
def _validation_outputs_test_impl(ctx):
env = analysistest.begin(ctx)
actions = analysistest.target_actions(env)
target = analysistest.target_under_test(env)
validation_outputs = target.output_groups._validation.to_list()
for action in actions:
for validation_output in validation_outputs:
if validation_output in action.inputs.to_list():
analysistest.fail(env,
"%s is a validation action output, but is an input to action %s" % (
validation_output, action))
return analysistest.end(env)
validation_outputs_test = analysistest.make(_validation_outputs_test_impl)
แฟล็กการดำเนินการตรวจสอบ
การดำเนินการตรวจสอบที่ทำงานอยู่จะควบคุมโดย Flag --run_validations
บรรทัดคำสั่ง ซึ่งค่าเริ่มต้นคือ "จริง"
ฟีเจอร์ที่เลิกใช้งาน
เอาต์พุตที่ประกาศไว้ล่วงหน้าซึ่งเลิกใช้งานแล้ว
การใช้เอาต์พุตที่ประกาศไว้ล่วงหน้าซึ่งเลิกใช้งานแล้วมี 2 วิธีดังนี้
พารามิเตอร์
outputs
ของrule
จะระบุการแมประหว่างชื่อแอตทริบิวต์เอาต์พุตกับเทมเพลตสตริงเพื่อสร้างป้ายกำกับเอาต์พุตที่ประกาศไว้ล่วงหน้า แนะนำให้ใช้เอาต์พุตที่ไม่ได้ประกาศไว้ล่วงหน้าและเพิ่มเอาต์พุตลงในDefaultInfo.files
อย่างชัดแจ้ง ใช้ป้ายกำกับของเป้าหมายกฎเป็นอินพุตสำหรับกฎที่ใช้เอาต์พุตแทนป้ายกำกับของเอาต์พุตที่ประกาศไว้ล่วงหน้าสําหรับกฎที่เรียกใช้ได้
ctx.outputs.executable
จะหมายถึงเอาต์พุตที่เรียกใช้ได้ซึ่งประกาศไว้ล่วงหน้าซึ่งมีชื่อเดียวกับเป้าหมายของกฎ แนะนำให้ประกาศเอาต์พุตอย่างชัดเจน เช่น ใช้ctx.actions.declare_file(ctx.label.name)
และตรวจสอบว่าคำสั่งที่สร้างไฟล์ปฏิบัติการได้ตั้งค่าสิทธิ์ให้อนุญาตการเรียกใช้ ส่งเอาต์พุตไฟล์ปฏิบัติการไปยังพารามิเตอร์executable
ของDefaultInfo
อย่างชัดแจ้ง
ฟีเจอร์ Runfiles ที่ควรหลีกเลี่ยง
ประเภท ctx.runfiles
และ runfiles
มีชุดฟีเจอร์ที่ซับซ้อน ซึ่งหลายรายการยังคงอยู่เนื่องจากเหตุผลด้านเดิม
คําแนะนําต่อไปนี้จะช่วยลดความซับซ้อน
หลีกเลี่ยงการใช้โหมด
collect_data
และcollect_default
ของctx.runfiles
โหมดเหล่านี้จะรวบรวมไฟล์รันไทม์โดยปริยายในขอบเขตของขอบเขตการพึ่งพาที่ฮาร์ดโค้ดไว้บางรายการในลักษณะที่ทำให้เกิดความสับสน แต่ให้เพิ่มไฟล์โดยใช้พารามิเตอร์files
หรือtransitive_files
ของctx.runfiles
หรือโดยการผสานไฟล์รันไทม์จาก Dependency กับrunfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
หลีกเลี่ยงการใช้
data_runfiles
และdefault_runfiles
ของเครื่องมือสร้างDefaultInfo
โปรดระบุDefaultInfo(runfiles = ...)
แทน ระบบจะยังคงแยกความแตกต่างระหว่างไฟล์รันไทม์ "เริ่มต้น" กับ "ข้อมูล" ไว้เพื่อเหตุผลเดิม เช่น กฎบางข้อจะใส่เอาต์พุตเริ่มต้นไว้ในdata_runfiles
แต่ไม่ใส่default_runfiles
แทนที่จะใช้data_runfiles
กฎควรทั้งรวมเอาเอาต์พุตเริ่มต้นและผสานdefault_runfiles
จากแอตทริบิวต์ที่ให้ไฟล์รันไทม์ (มักเป็นdata
)เมื่อดึงข้อมูล
runfiles
จากDefaultInfo
(โดยทั่วไปมีไว้สำหรับการผสานไฟล์เรียกใช้ระหว่างกฎปัจจุบันกับรายการที่เกี่ยวข้องเท่านั้น) ให้ใช้DefaultInfo.default_runfiles
ไม่ใช่DefaultInfo.data_runfiles
การย้ายข้อมูลจากผู้ให้บริการรายเดิม
ก่อนหน้านี้ ผู้ให้บริการ Bazel คือช่องแบบง่ายในออบเจ็กต์ Target
ระบบเข้าถึงโดยใช้โอเปอเรเตอร์จุด และสร้างโดยใส่ฟิลด์ใน 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
แล้ว Struct ที่แสดงกลับมาอาจใช้ช่องอื่นๆ อีกหลายช่องที่มีความหมายพิเศษ (จึงไม่สร้างผู้ให้บริการเดิมที่เกี่ยวข้อง) ดังนี้
ช่อง
files
,runfiles
,data_runfiles
,default_runfiles
และexecutable
สอดคล้องกับช่องชื่อเดียวกันของDefaultInfo
ไม่อนุญาตให้ระบุช่องเหล่านี้ขณะแสดงผลผู้ให้บริการDefaultInfo
ด้วยฟิลด์
output_groups
ใช้ค่าสตริงและสอดคล้องกับOutputGroupInfo
ในการประกาศกฎ provides
และในประกาศแอตทริบิวต์การพึ่งพา providers
ระบบจะส่งผู้ให้บริการเดิมเป็นสตริง และส่งผู้ให้บริการสมัยใหม่ด้วยสัญลักษณ์ Info
อย่าลืมเปลี่ยนจากสตริงเป็นสัญลักษณ์เมื่อย้ายข้อมูล สําหรับชุดกฎที่ซับซ้อนหรือมีขนาดใหญ่ซึ่งอัปเดตกฎทั้งหมดพร้อมกันได้ยาก คุณอาจทําได้ง่ายขึ้นหากทําตามลําดับขั้นตอนต่อไปนี้
แก้ไขกฎที่สร้างผู้ให้บริการเดิมเพื่อสร้างทั้งผู้ให้บริการเดิมและผู้ให้บริการสมัยใหม่โดยใช้ไวยากรณ์ก่อนหน้า สําหรับกฎที่ประกาศว่าแสดงผู้ให้บริการเดิม ให้อัปเดตการประกาศดังกล่าวให้รวมทั้งผู้ให้บริการเดิมและผู้ให้บริการสมัยใหม่
แก้ไขกฎที่ใช้ผู้ให้บริการเดิมให้ใช้ผู้ให้บริการสมัยใหม่แทน หากการประกาศแอตทริบิวต์ใดๆ ต้องใช้ผู้ให้บริการเดิม ให้อัปเดตการประกาศดังกล่าวให้ต้องใช้ผู้ให้บริการสมัยใหม่แทน หรือคุณอาจแทรกสลับการทำงานนี้ในขั้นตอนที่ 1 ได้โดยให้ผู้บริโภคยอมรับหรือกําหนดให้ใช้ผู้ให้บริการรายใดรายหนึ่ง: ทดสอบการมีอยู่ของผู้ให้บริการเดิมที่ใช้
hasattr(target, 'foo')
หรือผู้ให้บริการรายใหม่ที่ใช้FooInfo in target
นําผู้ให้บริการเดิมออกจากกฎทั้งหมดโดยสมบูรณ์