กฎกำหนดชุดของการดำเนินการที่ Bazel ดำเนินการกับอินพุตเพื่อสร้างชุดเอาต์พุต ซึ่งอ้างอิงในผู้ให้บริการที่แสดงผลโดยฟังก์ชันการใช้งานของกฎดังกล่าว ตัวอย่างเช่น กฎไบนารี C++ อาจ
- ถ่ายภาพชุดไฟล์ต้นฉบับ
.cpp
รายการ (อินพุต) - เรียกใช้
g++
ในไฟล์ต้นฉบับ (การดำเนินการ) - แสดงผลผู้ให้บริการ
DefaultInfo
พร้อมเอาต์พุตที่เป็นไฟล์ปฏิบัติการและไฟล์อื่นๆ เพื่อให้พร้อมใช้งานระหว่างรันไทม์ - แสดงผลผู้ให้บริการ
CcInfo
พร้อมข้อมูลเฉพาะ C++ ที่รวบรวมจากเป้าหมายและการอ้างอิง
จากมุมมองของ Bazel นั้น g++
และไลบรารี C++ มาตรฐานก็เป็นอินพุต
ในกฎนี้เช่นกัน ในฐานะผู้เขียนกฎ คุณต้องไม่เพียงพิจารณาอินพุตที่ผู้ใช้ให้ไว้ในกฎเท่านั้น แต่ยังต้องพิจารณาเครื่องมือและไลบรารีทั้งหมดที่จำเป็นต่อการดำเนินการดังกล่าวด้วย
ก่อนที่จะสร้างหรือแก้ไขกฎใดๆ โปรดทำความคุ้นเคยกับขั้นตอนการสร้างของ Bazel คุณต้องเข้าใจ 3 ขั้นตอนของบิลด์ (การโหลด การวิเคราะห์ และการดำเนินการ) นอกจากนี้คุณยังควรเรียนรู้เกี่ยวกับมาโครเพื่อทำความเข้าใจความแตกต่างระหว่างกฎกับมาโครด้วย หากต้องการเริ่มต้นใช้งาน โปรดดูบทแนะนำเกี่ยวกับกฎก่อน จากนั้นให้ใช้หน้านี้เป็นข้อมูลอ้างอิง
Bazel สร้างขึ้นด้วยกฎ 2-3 ข้อ กฎเนทีฟเหล่านี้ เช่น cc_library
และ java_binary
จะให้การสนับสนุนหลักบางอย่างในบางภาษา
การกำหนดกฎของคุณเองช่วยให้คุณสามารถเพิ่มการสนับสนุนที่คล้ายกันสำหรับภาษาและเครื่องมือที่ Bazel ไม่รองรับโดยค่าเริ่มต้นได้
Bazel มอบโมเดลการขยายการใช้งานสำหรับกฎการเขียนโดยใช้ภาษา Starlark กฎเหล่านี้เขียนขึ้นในไฟล์ .bzl
ซึ่งโหลดได้โดยตรงจาก BUILD
ไฟล์
เมื่อกำหนดกฎของคุณเอง คุณจะต้องเลือกแอตทริบิวต์ที่กฎรองรับและวิธีที่กฎดังกล่าวสร้างผลลัพธ์
ฟังก์ชัน implementation
ของกฎจะกำหนดลักษณะการทำงานที่แน่นอนระหว่างขั้นตอนการวิเคราะห์ ฟังก์ชันนี้ไม่ได้เรียกใช้คำสั่งภายนอก แต่จะบันทึกการดำเนินการที่จะใช้ภายหลังในระหว่างระยะการดำเนินการเพื่อสร้างเอาต์พุตของกฎ หากจำเป็น
การสร้างกฎ
ในไฟล์ .bzl
ให้ใช้ฟังก์ชันกฎเพื่อกำหนดกฎใหม่และจัดเก็บผลลัพธ์ในตัวแปรร่วม การเรียก 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
แอตทริบิวต์คืออาร์กิวเมนต์ของกฎ แอตทริบิวต์อาจระบุค่าที่เจาะจงสำหรับการใช้งานของเป้าหมาย หรืออาจอ้างอิงถึงเป้าหมายอื่นๆ ด้วยการสร้างกราฟทรัพยากร Dependency ก็ได้
ระบบจะกำหนดแอตทริบิวต์เฉพาะของกฎ เช่น 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),
...
},
)
ต่อไปนี้เป็นตัวอย่างของแอตทริบิวต์การขึ้นต่อกัน แอตทริบิวต์ที่ระบุ
ป้ายกำกับอินพุต (ที่กำหนดด้วย
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
ก่อน ซึ่งเป็นข้อผิดพลาดหากมีวงกลมอยู่ในกราฟการอ้างอิงของเป้าหมาย
แอตทริบิวต์ส่วนตัวและการพึ่งพากันโดยนัย
แอตทริบิวต์ทรัพยากร Dependency ที่มีค่าเริ่มต้นจะสร้างทรัพยากร Dependency โดยนัย โดยปริยายเพราะเป็นส่วนหนึ่งของกราฟเป้าหมายที่ผู้ใช้ไม่ได้ระบุไว้ในไฟล์ BUILD
ทรัพยากร Dependency โดยนัยมีประโยชน์ในการฮาร์ดโค้ดความสัมพันธ์ระหว่างกฎกับเครื่องมือ (ทรัพยากร Dependency ในบิวด์ เช่น คอมไพเลอร์) เนื่องจากส่วนใหญ่ผู้ใช้ไม่สนใจว่ากฎดังกล่าวใช้เครื่องมือใด ภายในฟังก์ชันการใช้งานของกฎ ระบบจะพิจารณาว่าข้อมูลนี้เหมือนกับทรัพยากร 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 แบบโดยนัยจะใช้สำหรับเครื่องมือที่อยู่ในที่เก็บเดียวกันกับการใช้กฎ หากเครื่องมือมาจากแพลตฟอร์มการดำเนินการหรือที่เก็บอื่นแทน กฎควรรับเครื่องมือนั้นจาก toolchain
แอตทริบิวต์เอาต์พุต
แอตทริบิวต์เอาต์พุต เช่น attr.output
และ attr.output_list
จะประกาศไฟล์เอาต์พุตที่เป้าหมายสร้างขึ้น ซึ่งแตกต่างจากแอตทริบิวต์ทรัพยากร Dependency 2 ประการดังนี้
- โดยการกำหนดเป้าหมายไฟล์เอาต์พุตแทนการอ้างถึงเป้าหมายที่กำหนดไว้ในตำแหน่งอื่น
- เป้าหมายไฟล์เอาต์พุตจะขึ้นอยู่กับเป้าหมายของกฎอินสแตนซ์ ไม่ใช่ในทางกลับกัน
โดยปกติแล้ว แอตทริบิวต์เอาต์พุตจะใช้เมื่อกฎจำเป็นต้องสร้างเอาต์พุตที่มีชื่อกำหนดโดยผู้ใช้เท่านั้น ซึ่งจะอิงตามชื่อเป้าหมายไม่ได้ หากกฎมีแอตทริบิวต์เอาต์พุต 1 รายการ โดยปกติแล้วจะมีชื่อว่า out
หรือ outs
แอตทริบิวต์เอาต์พุตคือวิธีที่แนะนำให้สร้างเอาต์พุตที่ประกาศไว้ล่วงหน้า ซึ่งจะขึ้นอยู่กับหรือส่งคำขอในบรรทัดคำสั่งโดยเฉพาะ
ฟังก์ชันการใช้งาน
กฎทุกข้อต้องมีฟังก์ชัน implementation
ฟังก์ชันเหล่านี้จะใช้งานอย่างเข้มงวดในขั้นตอนการวิเคราะห์ และแปลงกราฟเป้าหมายที่สร้างขึ้นในขั้นตอนการโหลดให้เป็นกราฟการดำเนินการที่ต้องทำในระหว่างขั้นตอนการดำเนินการ ดังนั้น ฟังก์ชันการใช้งานจึงไม่สามารถอ่านหรือเขียนไฟล์ได้จริงๆ
ฟังก์ชันการใช้งานกฎมักเป็นแบบส่วนตัว (ตั้งชื่อด้วยขีดล่าง) โดยมักตั้งชื่อเหมือนกับกฎ แต่ต่อท้ายด้วย _impl
ฟังก์ชันการใช้งานจะมีพารามิเตอร์เพียงตัวเดียว ซึ่งก็คือบริบทกฎ ซึ่งโดยทั่วไปจะตั้งชื่อว่า ctx
และแสดงรายชื่อผู้ให้บริการ
เป้าหมาย
ทรัพยากร Dependency จะแสดงเป็นออบเจ็กต์ Target
ณ เวลาที่วิเคราะห์ ออบเจ็กต์เหล่านี้มี providers ที่สร้างขึ้นเมื่อมีการเรียกใช้ฟังก์ชันการใช้งานของเป้าหมาย
ctx.attr
มีช่องที่สอดคล้องกับชื่อของแอตทริบิวต์การขึ้นต่อกันแต่ละรายการ โดยมีออบเจ็กต์ Target
รายการที่แสดงถึงการขึ้นต่อกันโดยตรงแต่ละรายการผ่านแอตทริบิวต์ดังกล่าว สำหรับแอตทริบิวต์ label_list
นี่คือรายการ Targets
สำหรับแอตทริบิวต์ label
ค่านี้จะเป็น Target
หรือ None
รายการเดียว
รายการออบเจ็กต์ผู้ให้บริการจะแสดงโดยฟังก์ชันการใช้งานของเป้าหมาย ดังนี้
return [ExampleInfo(headers = depset(...))]
ข้อมูลเหล่านั้นจะเข้าถึงได้โดยใช้สัญกรณ์ดัชนี ([]
) โดยมีประเภทของผู้ให้บริการเป็นคีย์ ซึ่งอาจเป็นผู้ให้บริการที่กำหนดเองที่กำหนดไว้ใน Starlark หรือผู้ให้บริการสำหรับกฎเนทีฟที่มีเป็นตัวแปรร่วม Starlark
เช่น หากกฎรับไฟล์ส่วนหัวผ่านแอตทริบิวต์ hdrs
และมอบไฟล์ส่วนหัวแก่การดำเนินการคอมไพล์ของเป้าหมายและผู้บริโภค กฎอาจรวบรวมไฟล์ในลักษณะต่อไปนี้
def _example_library_impl(ctx):
...
transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]
สำหรับสไตล์เดิมที่ struct
แสดงผลจากฟังก์ชันการใช้งานของเป้าหมายแทนที่จะเป็นรายการออบเจ็กต์ผู้ให้บริการ
return struct(example_info = struct(headers = depset(...)))
ดึงผู้ให้บริการได้จากช่องที่เกี่ยวข้องของออบเจ็กต์ Target
ดังนี้
transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]
เราไม่แนะนำให้ใช้สไตล์นี้อย่างยิ่งและควรย้ายกฎออกไป
Files
ไฟล์จะแสดงด้วยออบเจ็กต์ 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
รายการเดียวสำหรับแอตทริบิวต์การขึ้นต่อกันซึ่งตั้งค่าข้อกำหนดเป็น 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
แทนได้
การดำเนินการ
การดำเนินการอธิบายวิธีสร้างชุดเอาต์พุตจากชุดอินพุต เช่น "run gcc ใน hello.c and get hello.o" เมื่อสร้างการทำงานแล้ว Bazel จะไม่เรียกใช้คำสั่งในทันที โดยจะบันทึกไว้ในกราฟของทรัพยากร Dependency เนื่องจากการดำเนินการอาจขึ้นอยู่กับผลลัพธ์ของการดำเนินการอื่นได้ เช่น ใน C จะต้องเรียกใช้ Linker หลังคอมไพเลอร์
ฟังก์ชันสำหรับวัตถุประสงค์ทั่วไปที่สร้างการดำเนินการกำหนดไว้ใน ctx.actions
ดังต่อไปนี้
ctx.actions.run
เพื่อเรียกใช้ไฟล์ปฏิบัติการctx.actions.run_shell
เพื่อเรียกใช้คำสั่ง Shellctx.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) คุณต้องระบุไฟล์ที่ต้องการให้ขยาย (ก่อนเรียกใช้การคลายการบีบอัด) การดำเนินการที่สร้างจำนวนตัวแปรของไฟล์ภายในสามารถรวมไฟล์เหล่านั้นไว้ในไฟล์เดียว (เช่น 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]), ...),
...
]
หากไม่ได้ระบุ DefaultInfo
จากการใช้กฎหรือไม่ได้ระบุพารามิเตอร์ files
ไว้ DefaultInfo.files
จะใช้ค่าเริ่มต้นเป็นเอาต์พุตที่ประกาศไว้ล่วงหน้าทั้งหมด (โดยทั่วไปคือเอาต์พุตที่สร้างโดยแอตทริบิวต์เอาต์พุต)
กฎสำหรับการดำเนินการควรมีเอาต์พุตเริ่มต้น แม้จะไม่มีการใช้เอาต์พุตเหล่านั้นโดยตรงก็ตาม การดำเนินการที่ไม่อยู่ในกราฟของเอาต์พุตที่ขอจะถูกตัดออก หากผู้บริโภคเป้าหมายใช้เอาต์พุตเท่านั้น การดำเนินการเหล่านั้นจะไม่เกิดขึ้นเมื่อมีการสร้างเป้าหมายแยกต่างหาก ซึ่งทำให้แก้ไขข้อบกพร่องได้ยากขึ้น เนื่องจากการสร้างเฉพาะเป้าหมายที่ล้มเหลวจะไม่ทำให้ความล้มเหลวเกิดขึ้นอีก
Runfiles
Runfiles คือชุดไฟล์ที่เป้าหมายใช้ขณะรันไทม์ (ไม่ใช่เวลาที่สร้าง) ระหว่างขั้นตอนการดำเนินการ Bazel จะสร้างแผนผังไดเรกทอรีที่มีลิงก์สัญลักษณ์ที่ชี้ไปยัง Runfiles ซึ่งจะเก็บสภาพแวดล้อมสำหรับไบนารีนั้นเพื่อเข้าถึงรันไฟล์ระหว่างรันไทม์
คุณสามารถเพิ่ม Runfiles ได้ด้วยตนเองในระหว่างการสร้างกฎ
คุณสร้างออบเจ็กต์ runfiles
ได้ด้วยเมธอด runfiles
ในบริบทของกฎ ctx.runfiles
และส่งไปยังพารามิเตอร์ runfiles
ใน DefaultInfo
ระบบจะเพิ่มเอาต์พุตที่เป็นไฟล์ดำเนินการของกฎที่ดำเนินการได้ลงใน Runfile โดยปริยาย
กฎบางข้อจะระบุแอตทริบิวต์ โดยทั่วไปจะตั้งชื่อว่า data
ซึ่งมีเอาต์พุตที่ระบบจะเพิ่มไปยัง Runfiles ของเป้าหมาย คุณควรรวม 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
ไปยังฟังก์ชัน provider
หากมีการระบุโค้ดเรียกกลับนี้ ประเภทการแสดงผล provider()
จะเปลี่ยนเป็น Tuple ของ 2 ค่า ได้แก่ สัญลักษณ์ผู้ให้บริการที่เป็นค่าผลลัพธ์ปกติเมื่อไม่ได้ใช้ init
และ "ตัวสร้างดิบ"
ในกรณีนี้ เมื่อมีการเรียกใช้สัญลักษณ์ผู้ให้บริการ แทนที่จะแสดงผลอินสแตนซ์ใหม่โดยตรง ระบบจะส่งต่ออาร์กิวเมนต์ไปยังโค้ดเรียกกลับของ init
ค่าที่แสดงผลของโค้ดเรียกกลับต้องเป็นชื่อช่องการแมปคำสั่ง (สตริง) กับค่า ซึ่งใช้ในการเริ่มต้นช่องของอินสแตนซ์ใหม่ โปรดทราบว่าการเรียกกลับอาจมีลายเซ็นใดๆ และถ้าอาร์กิวเมนต์ไม่ตรงกับลายเซ็น ระบบจะรายงานข้อผิดพลาดเสมือนว่ามีการเรียกกลับโดยตรง
ในทางตรงกันข้าม ตัวสร้างดิบจะข้ามโค้ดเรียกกลับ 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
อีกอย่างหนึ่งคือป้องกันไม่ให้ผู้ใช้เรียกสัญลักษณ์ผู้ให้บริการไปเลย และบังคับให้ใช้ฟังก์ชัน Factory แทน ดังนี้
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(
...
)
ตำแหน่ง Runfiles
เมื่อเรียกใช้เป้าหมายปฏิบัติการด้วย 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 จะคาดเดาแบบเดียวกันไม่ได้ เพื่อลดปัญหานี้ ไบนารีแต่ละรายการควรระบุวิธียอมรับรูทของ Runfiles เป็นพารามิเตอร์โดยใช้สภาพแวดล้อมหรืออาร์กิวเมนต์/แฟล็กบรรทัดคำสั่ง ซึ่งจะช่วยให้ไบนารีสามารถส่งรูทของ Runfiles หลักที่ถูกต้องไปยังไบนารีที่เรียกใช้ หากไม่ได้ตั้งค่า ไบนารีจะเดาได้ว่าเป็นไบนารีแรกที่มีการเรียกใช้ และมองหาไดเรกทอรี Runfiles ที่อยู่ติดกัน
หัวข้อขั้นสูง
กำลังขอไฟล์เอาต์พุต
เป้าหมายเดียวมีไฟล์เอาต์พุตได้หลายไฟล์ เมื่อเรียกใช้คำสั่ง bazel build
เอาต์พุตบางอย่างของเป้าหมายที่กำหนดให้กับคำสั่งจะถือว่ามีการส่งคำขอ Bazel จะสร้างเฉพาะไฟล์ที่ร้องขอเหล่านี้และไฟล์ที่ไฟล์เหล่านี้ใช้อ้างอิงโดยตรงหรือโดยอ้อมเท่านั้น (ในแง่ของกราฟการดำเนินการ Bazel จะดำเนินการเฉพาะการดำเนินการที่เข้าถึงได้ว่าเป็นทรัพยากร Dependency ชั่วคราวของไฟล์ที่ขอเท่านั้น)
นอกเหนือจากเอาต์พุตเริ่มต้นแล้ว คุณยังขอเอาต์พุตที่ประกาศไว้ล่วงหน้าอย่างชัดแจ้งในบรรทัดคำสั่งได้ด้วย กฎจะระบุเอาต์พุตที่ประกาศไว้ล่วงหน้าผ่านแอตทริบิวต์เอาต์พุตได้ ในกรณีนี้ ผู้ใช้จะเลือกป้ายกำกับสำหรับเอาต์พุตอย่างชัดแจ้งเมื่อเริ่มต้นกฎ หากต้องการรับออบเจ็กต์ File
สำหรับแอตทริบิวต์เอาต์พุต ให้ใช้แอตทริบิวต์ที่สอดคล้องกันของ ctx.outputs
กฎสามารถกำหนดเอาต์พุตที่ประกาศไว้ล่วงหน้าโดยนัยตามชื่อเป้าหมายได้เช่นกัน แต่ฟีเจอร์นี้เลิกใช้งานแล้ว
นอกจากเอาต์พุตเริ่มต้นแล้ว ยังมีกลุ่มเอาต์พุตซึ่งเป็นคอลเล็กชันของไฟล์เอาต์พุตที่อาจขอพร้อมกันด้วย สามารถขอโดยใช้ --output_groups
เช่น หากเป้าหมาย //pkg:mytarget
เป็นประเภทกฎที่มีกลุ่มเอาต์พุต debug_files
คุณจะสร้างไฟล์เหล่านี้ได้โดยการเรียกใช้ bazel build //pkg:mytarget
--output_groups=debug_files
เนื่องจากเอาต์พุตที่ไม่ได้ประกาศไว้ล่วงหน้าไม่มีป้ายกำกับ คุณจะขอเอาต์พุตเหล่านี้ได้ด้วยการปรากฏในเอาต์พุตเริ่มต้นหรือกลุ่มเอาต์พุตเท่านั้น
คุณระบุกลุ่มเอาต์พุตกับผู้ให้บริการ OutputGroupInfo
ได้ โปรดทราบว่า OutputGroupInfo
จะใช้พารามิเตอร์ที่มีชื่อที่กำหนดเองเพื่อนิยามกลุ่มเอาต์พุตที่มีชื่อดังกล่าว ซึ่งต่างจากผู้ให้บริการที่มีในตัวหลายราย ดังนี้
def _example_library_impl(ctx):
...
debug_file = ctx.actions.declare_file(name + ".pdb")
...
return [
DefaultInfo(files = depset([output_file]), ...),
OutputGroupInfo(
debug_files = depset([debug_file]),
all_files = depset([output_file, debug_file]),
),
...
]
ต่างจากผู้ให้บริการส่วนใหญ่ตรงที่จะแสดงผล OutputGroupInfo
ได้จากทั้งปัจจัยและเป้าหมายกฎที่นำแง่มุมนั้นไปใช้ ตราบใดที่แอตทริบิวต์เหล่านั้นไม่ได้กำหนดกลุ่มเอาต์พุตเดียวกัน ในกรณีดังกล่าว ผู้ให้บริการที่ได้
จะถูกรวมเข้าด้วยกัน
โปรดทราบว่าโดยทั่วไป OutputGroupInfo
ไม่ควรใช้เพื่อถ่ายทอดไฟล์บางประเภทจากเป้าหมายไปจนถึงการกระทำของผู้บริโภค กำหนดผู้ให้บริการเฉพาะกฎสำหรับผู้ให้บริการดังกล่าวแทน
การกำหนดค่า
สมมติว่าคุณต้องการสร้างไบนารี C++ สำหรับสถาปัตยกรรมอื่น การสร้างอาจมีความซับซ้อนและเกี่ยวข้องกับหลายขั้นตอน ไบนารีระดับกลาง เช่น คอมไพเลอร์และเครื่องมือสร้างโค้ด ต้องทำงานบนแพลตฟอร์มการดำเนินการ (ซึ่งอาจเป็นโฮสต์หรือผู้ดำเนินการระยะไกล) ไบนารีบางรายการ เช่น เอาต์พุตสุดท้าย ต้องสร้างขึ้นสำหรับสถาปัตยกรรมเป้าหมาย
ด้วยเหตุนี้ Bazel จึงมีแนวคิดเรื่อง "การกำหนดค่า" และการเปลี่ยนฉาก เป้าหมายระดับบนสุด (เป้าหมายที่ขอในบรรทัดคำสั่ง) จะสร้างไว้ในการกำหนดค่า "เป้าหมาย" ส่วนเครื่องมือที่ควรทำงานบนแพลตฟอร์มการดำเนินการจะสร้างไว้ในการกำหนดค่า "exec" กฎอาจสร้างการทำงานที่แตกต่างกันตามการกำหนดค่า เช่น เพื่อเปลี่ยนสถาปัตยกรรม CPU ที่ส่งไปยังคอมไพเลอร์ ในบางกรณี การกำหนดค่าที่ต่างกันอาจต้องใช้ไลบรารีเดียวกัน หากเกิดกรณีเช่นนี้ขึ้น ระบบจะวิเคราะห์และอาจสร้างขึ้นหลายครั้ง
โดยค่าเริ่มต้น Bazel จะสร้างทรัพยากร Dependency ของเป้าหมายด้วยการกำหนดค่าเดียวกันกับตัวเป้าหมาย นั่นคือไม่มีการเปลี่ยน เมื่อทรัพยากร Dependency เป็นเครื่องมือที่จำเป็นต่อการช่วยสร้างเป้าหมาย แอตทริบิวต์ที่เกี่ยวข้องควรระบุการเปลี่ยนไปใช้การกำหนดค่าปฏิบัติการ ซึ่งทำให้เครื่องมือและการพึ่งพาทั้งหมดของเครื่องมือสร้างขึ้นสำหรับแพลตฟอร์มการดำเนินการ
สำหรับแอตทริบิวต์ทรัพยากร Dependency แต่ละรายการ คุณสามารถใช้ cfg
เพื่อตัดสินใจว่าควรสร้างทรัพยากร Dependency ด้วยการกำหนดค่าเดียวกันหรือเปลี่ยนไปใช้การกำหนดค่าของผู้บริหาร
หากแอตทริบิวต์การอ้างอิงมีแฟล็ก executable=True
ก็ต้องตั้งค่า cfg
อย่างชัดแจ้ง ซึ่งเป็นการป้องกันการสร้างเครื่องมือสำหรับการกำหนดค่าที่ไม่ถูกต้องโดยไม่ได้ตั้งใจ
ดูตัวอย่าง
โดยทั่วไปแล้วแหล่งที่มา ไลบรารีที่เกี่ยวข้อง และไฟล์สั่งการที่ต้องใช้ในรันไทม์จะใช้การกำหนดค่าเดียวกันได้
คุณควรสร้างเครื่องมือต่างๆ ที่ดำเนินการโดยเป็นส่วนหนึ่งของบิลด์ (เช่น คอมไพเลอร์หรือโปรแกรมสร้างโค้ด) สำหรับการกำหนดค่าของผู้บริหาร ในกรณีนี้ ให้ระบุ cfg="exec"
ในแอตทริบิวต์
ไม่เช่นนั้น ควรสร้างไฟล์สั่งการที่ใช้ขณะรันไทม์ (เช่น เป็นส่วนหนึ่งของการทดสอบ) สำหรับการกำหนดค่าเป้าหมาย ในกรณีนี้ ให้ระบุ cfg="target"
ในแอตทริบิวต์
cfg="target"
ไม่ได้ทำอะไรเลย แต่เป็นเพียงค่าอำนวยความสะดวกที่ช่วยให้ผู้ออกแบบกฎเข้าใจเจตนาของตนอย่างชัดเจน เมื่อ executable=False
ซึ่งหมายความว่า cfg
จะไม่บังคับ โปรดตั้งค่านี้เฉพาะในกรณีที่มีประโยชน์ในการอ่านเท่านั้น
นอกจากนี้ คุณยังใช้ cfg=my_transition
เพื่อใช้การเปลี่ยนที่กำหนดโดยผู้ใช้ได้ ซึ่งจะช่วยให้ผู้เขียนกฎมีความยืดหยุ่นอย่างมากในการเปลี่ยนแปลงการกำหนดค่า พร้อมทั้งสามารถคืนค่าการทำให้กราฟบิลด์มีขนาดใหญ่ขึ้นและเข้าใจง่ายน้อยลง
หมายเหตุ: ที่ผ่านมา Bazel ไม่มีแนวคิดเรื่องแพลตฟอร์มการดำเนินการ และถือว่าการกระทำของบิลด์ทั้งหมดทำงานบนเครื่องโฮสต์แทน ดังนั้นจึงมีการกำหนดค่า "โฮสต์" เดียวและการเปลี่ยน "โฮสต์" ที่ใช้สร้างทรัพยากร Dependency ในการกำหนดค่าโฮสต์ได้ กฎจำนวนมากยังคงใช้การเปลี่ยน "โฮสต์" สำหรับเครื่องมือของตน แต่ขณะนี้เลิกใช้งานไปแล้วและกำลังมีการย้ายข้อมูลไปใช้การเปลี่ยนแบบ "exec" เมื่อเป็นไปได้
การกำหนดค่า "โฮสต์" และ "exec" มีความแตกต่างอยู่มากมาย:
- "host" เป็นเทอร์มินัล แต่ "exec" จะไม่เป็น: เมื่อทรัพยากร Dependency อยู่ในการกำหนดค่า "โฮสต์" แล้ว ระบบจะไม่อนุญาตการเปลี่ยนเพิ่มเติมอีก คุณสามารถทำการเปลี่ยนการกำหนดค่าเพิ่มเติมต่อได้เมื่ออยู่ในการกำหนดค่า "exec"
- "โฮสต์" เป็นแบบโมโนลิธ ไม่มี "exec" เพราะมีการกำหนดค่า "โฮสต์" เพียงรายการเดียว แต่อาจมีการกำหนดค่า "exec" ที่ต่างกันสำหรับแต่ละแพลตฟอร์มการดำเนินการ
- "host" ถือว่าคุณเรียกใช้เครื่องมือในเครื่องเดียวกันกับ Bazel หรือในเครื่องที่คล้ายๆ กัน ซึ่งไม่เป็นความจริงอีกต่อไป คุณจะเรียกใช้การทำงานของบิลด์ในเครื่องของคุณเองหรือในโปรแกรมสั่งการระยะไกลก็ได้ และไม่มีการรับประกันว่าผู้ดำเนินการระยะไกลจะเป็น CPU และระบบปฏิบัติการเดียวกันกับเครื่องของคุณ
ทั้งการกำหนดค่า "exec" และ "โฮสต์" จะใช้การเปลี่ยนแปลงตัวเลือกเดียวกัน (เช่น ตั้งค่า --compilation_mode
จาก --host_compilation_mode
ตั้งค่า --cpu
จาก --host_cpu
เป็นต้น) ความแตกต่างคือการกำหนดค่า "โฮสต์" เริ่มต้นด้วยค่าเริ่มต้นของแฟล็กอื่นๆ ทั้งหมด ในขณะที่การกำหนดค่า "exec" เริ่มต้นด้วยค่าปัจจุบันของแฟล็ก โดยอิงตามการกำหนดค่าเป้าหมาย
ส่วนย่อยของการกำหนดค่า
กฎอาจเข้าถึงส่วนย่อยการกำหนดค่า เช่น cpp
, java
และ jvm
อย่างไรก็ตาม คุณต้องประกาศ 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
host_fragments = ["java"], # Required fragments of the host configuration
...
)
ctx.fragments
มีเฉพาะ Fragment การกำหนดค่าสำหรับการกำหนดค่าเป้าหมายเท่านั้น หากต้องการเข้าถึง Fragment สำหรับการกำหนดค่าโฮสต์ ให้ใช้ ctx.host_fragments
แทน
ลิงก์สัญลักษณ์ของ Runfiles
โดยปกติ เส้นทางแบบสัมพัทธ์ของไฟล์ในโครงสร้าง 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
เพื่อนำการชนกันออก การตรวจสอบนี้จะทำกับทุกเป้าหมายที่ใช้กฎของคุณ รวมถึงเป้าหมายทุกประเภทที่อิงตามเป้าหมายเหล่านั้นด้วย การทำเช่นนี้มีความเสี่ยงสูงโดยเฉพาะอย่างยิ่งหากเครื่องมือของคุณมีแนวโน้มที่จะใช้เครื่องมืออื่นในทางอ้อม ชื่อลิงก์สัญลักษณ์ต้องไม่ซ้ำกันในไฟล์ต่างๆ ของเครื่องมือและทรัพยากร 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 ก่อนการคอมไพล์ (เช่น ไฟล์ส่วนหัว) อาจต้องเปิดการวัดคุมเวลาคอมไพล์ด้วยหากควรใช้การวัดคุมแหล่งที่มาของการอ้างอิง ดังนี้
# 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 ของโค้ด เช่น 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
เป็น "host"
หรือ "exec"
ในสคีมาแอตทริบิวต์) ในdependency_attributes
(ซึ่งไม่ใช่ลักษณะการทำงานที่ดีที่สุดเนื่องจากจะใส่แอตทริบิวต์ เช่น srcs
ใน dependency_attributes
แทน source_attributes
แต่ทำให้ไม่จำเป็นต้องกำหนดค่าการครอบคลุมที่ชัดเจนสำหรับกฎทั้งหมดในเชนการขึ้นต่อกัน)
การดำเนินการตรวจสอบ
บางครั้งคุณต้องตรวจสอบบางอย่างเกี่ยวกับบิลด์ และข้อมูลที่จำเป็นต่อการตรวจสอบจะมีอยู่ในอาร์ติแฟกต์ (ไฟล์ต้นฉบับหรือไฟล์ที่สร้างขึ้น) เท่านั้น เนื่องจากข้อมูลนี้อยู่ในอาร์ติแฟกต์ กฎจึงไม่สามารถตรวจสอบความถูกต้องนี้ได้ขณะวิเคราะห์เนื่องจากกฎไม่สามารถอ่านไฟล์ได้ แต่จะต้องทำการตรวจสอบนี้เมื่อประมวลผล เมื่อการตรวจสอบล้มเหลว การดำเนินการจะล้มเหลว บิลด์ก็จะล้มเหลวเช่นกัน
ตัวอย่างของการตรวจสอบที่ใช้ได้ ได้แก่ การวิเคราะห์แบบคงที่ การวิเคราะห์ซอร์สโค้ด การตรวจสอบการขึ้นต่อกันและความสอดคล้อง และการตรวจสอบรูปแบบ
การดำเนินการตรวจสอบยังช่วยปรับปรุงประสิทธิภาพของบิลด์ได้ด้วยการย้ายการดำเนินการบางส่วนที่ไม่จำเป็นในการสร้างอาร์ติแฟกต์ไปยังการดำเนินการที่แยกกัน เช่น หากสามารถแยกการดำเนินการ 1 รายการที่ทำการคอมไพล์และ Lint ออกเป็นการดำเนินการคอมไพล์และทอยเอ็นจินได้ การดำเนินการวิเคราะห์ซอร์สโค้ดจะเรียกใช้เป็นการดำเนินการตรวจสอบและเรียกใช้พร้อมกันกับการดำเนินการอื่นๆ ได้
"การดำเนินการตรวจสอบ" เหล่านี้มักไม่สร้างการใช้งานที่อื่นในบิลด์ เนื่องจากต้องการแค่ยืนยันสิ่งต่างๆ เกี่ยวกับข้อมูลอินพุตเท่านั้น แต่ถ้าการดำเนินการตรวจสอบไม่มีผลใดๆ ที่ใช้ในที่อื่นในบิวด์ กฎจะทำงานอย่างไร ก่อนหน้านี้วิธีการคือให้การดำเนินการตรวจสอบเป็นไฟล์เปล่า แล้วเพิ่มเอาต์พุตนั้นเข้าไปในอินพุตของการดำเนินการที่สำคัญอื่นๆ ในบิลด์อย่างไม่เป็นจริง
วิธีนี้ได้ผลเนื่องจาก Bazel จะเรียกใช้การตรวจสอบความถูกต้องเสมอเมื่อมีการทำงานคอมไพล์ แต่มีข้อเสียสำคัญดังนี้
การดำเนินการตรวจสอบอยู่ในเส้นทางสำคัญของบิลด์ เนื่องจาก Bazel คิดว่าจำเป็นต้องมีเอาต์พุตที่ว่างเปล่าเพื่อเรียกใช้การดำเนินการคอมไพล์ จึงจะเรียกใช้การดำเนินการตรวจสอบก่อน แม้ว่าการดำเนินการคอมไพล์จะไม่สนใจอินพุตก็ตาม การทำเช่นนี้จะช่วยลดการทำงานพร้อมกันและทำให้งานสร้างช้าลง
หากการดำเนินการอื่นๆ ในบิลด์อาจทำงานแทนการดำเนินการคอมไพล์ คุณต้องเพิ่มเอาต์พุตเปล่าของการดำเนินการตรวจสอบไปยังการดำเนินการเหล่านั้นด้วย (เช่น เอาต์พุต Jar ต้นทางของ
java_library
) กรณีนี้จะเกิดขึ้นอีกเมื่อมีการเพิ่มการดำเนินการใหม่ที่อาจแสดงแทนการดำเนินการคอมไพล์ในภายหลัง และเอาต์พุตการตรวจสอบที่ว่างเปล่าหายไปโดยไม่ตั้งใจ
วิธีแก้ปัญหาเหล่านี้คือการใช้กลุ่มเอาต์พุตการตรวจสอบ
กลุ่มเอาต์พุตของการตรวจสอบ
กลุ่มเอาต์พุตของการตรวจสอบ คือกลุ่มเอาต์พุตที่ออกแบบมาเพื่อเก็บเอาต์พุตที่ไม่ได้ใช้ของการดำเนินการตรวจสอบ เพื่อที่จะได้ไม่ต้องเพิ่มเอาต์พุตเหล่านี้เข้าไปในอินพุตของการดำเนินการอื่นๆ อย่างไม่เป็นจริง
กลุ่มนี้มีความพิเศษตรงที่จะมีการขอเอาต์พุตเสมอ โดยไม่คำนึงถึงค่าของแฟล็ก --output_groups
และโดยไม่คำนึงถึงว่าเป้าหมายจะขึ้นอยู่กับบรรทัดคำสั่ง (เช่น ในบรรทัดคำสั่ง เป็นการอ้างอิง หรือผ่านเอาต์พุตโดยนัยของเป้าหมาย) โปรดทราบว่าการแคชปกติและส่วนเพิ่มจะยังคงมีผลอยู่ ถ้าอินพุตไปยังการดำเนินการตรวจสอบไม่มีการเปลี่ยนแปลง และการดำเนินการตรวจสอบประสบความสำเร็จก่อนหน้านี้ จะไม่มีการเรียกใช้การตรวจสอบ
การใช้กลุ่มเอาต์พุตนี้ยังกำหนดให้การดำเนินการตรวจสอบแสดงผลบางไฟล์ แม้ว่าไฟล์จะเป็นไฟล์ที่ว่างเปล่าก็ตาม ซึ่งอาจต้องมีการตัดเครื่องมือบางอย่างที่ปกติแล้วจะไม่สร้างเอาต์พุตเพื่อให้ระบบสร้างไฟล์
การดำเนินการตรวจสอบของเป้าหมายจะไม่ทำงานในสามกรณีต่อไปนี้
- เมื่อเป้าหมายได้รับการพึ่งพาเป็นเครื่องมือ
- เมื่อเป้าหมายขึ้นอยู่กับทรัพยากร 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
ซึ่งมีค่าเริ่มต้นเป็น "จริง"
ฟีเจอร์ที่เลิกใช้งาน
เอาต์พุตที่ประกาศไว้ล่วงหน้าที่เลิกใช้งานแล้ว
การใช้เอาต์พุตที่ประกาศไว้ล่วงหน้ามี 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
โหมดเหล่านี้จะรวบรวมไฟล์เรียกใช้โดยปริยายจากขอบของทรัพยากร Dependency ที่ฮาร์ดโค้ดบางรายการไว้ ซึ่งทำให้สับสน แต่ให้เพิ่มไฟล์โดยใช้พารามิเตอร์files
หรือtransitive_files
ของctx.runfiles
หรือผสานใน Runfiles จากทรัพยากร Dependency กับrunfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
แทนหลีกเลี่ยงการใช้
data_runfiles
และdefault_runfiles
ของเครื่องมือสร้างDefaultInfo
โปรดระบุDefaultInfo(runfiles = ...)
แทน ความแตกต่างระหว่างรันไฟล์ "เริ่มต้น" และ "ข้อมูล" จะยังคงอยู่ตามเหตุผลเดิม เช่น กฎบางข้อจะใส่เอาต์พุตเริ่มต้นในdata_runfiles
แต่ไม่ใส่default_runfiles
แทนที่จะใช้data_runfiles
กฎควรทั้งคู่จะมีเอาต์พุตเริ่มต้นและผสานในdefault_runfiles
จากแอตทริบิวต์ที่มี Runfile (มักเป็นdata
)เมื่อเรียกข้อมูล
runfiles
จากDefaultInfo
(โดยทั่วไปมีไว้สำหรับการรวมรันไฟล์ระหว่างกฎปัจจุบันและทรัพยากร Dependency เท่านั้น) ให้ใช้DefaultInfo.default_runfiles
ไม่ใช่DefaultInfo.data_runfiles
การย้ายข้อมูลจากผู้ให้บริการเดิม
ก่อนหน้านี้ ผู้ให้บริการ Bazel เป็นช่องธรรมดาในออบเจ็กต์ Target
ข้อมูลเหล่านี้มีการเข้าถึงโดยใช้โอเปอเรเตอร์จุด และสร้างขึ้นโดยการวางฟิลด์ในโครงสร้างที่แสดงผลโดยฟังก์ชันการใช้งานของกฎ
เราเลิกใช้งานสไตล์นี้แล้วและไม่ควรใช้ในโค้ดใหม่ โปรดดูข้อมูลด้านล่างที่อาจช่วยคุณย้ายข้อมูลได้ กลไกของผู้ให้บริการใหม่จะช่วยป้องกันไม่ให้ชื่อเกิดความขัดแย้ง นอกจากนี้ยังรองรับการซ่อนข้อมูลโดยกําหนดให้โค้ดเข้าถึงอินสแตนซ์ของผู้ให้บริการเพื่อดึงข้อมูลโดยใช้สัญลักษณ์ผู้ให้บริการ
ในตอนนี้เรายังรองรับผู้ให้บริการรายเดิมอยู่ กฎหนึ่งอาจแสดงผลทั้งผู้ให้บริการเดิมและผู้ให้บริการสมัยใหม่ดังนี้
def _old_rule_impl(ctx):
...
legacy_data = struct(x="foo", ...)
modern_data = MyInfo(y="bar", ...)
# When any legacy providers are returned, the top-level returned value is a
# struct.
return struct(
# One key = value entry for each legacy provider.
legacy_info = legacy_data,
...
# Additional modern providers:
providers = [modern_data, ...])
หาก dep
เป็นออบเจ็กต์ Target
ที่ได้สำหรับอินสแตนซ์ของกฎนี้ คุณจะเรียกข้อมูลผู้ให้บริการและเนื้อหาของผู้ให้บริการเป็น dep.legacy_info.x
และ dep[MyInfo].y
ได้
นอกเหนือจาก providers
โครงสร้างที่แสดงผลอาจใช้ช่องอื่นๆ อีกหลายช่องที่มีความหมายพิเศษ (จึงไม่สร้างผู้ให้บริการเดิมที่เกี่ยวข้อง)
ช่อง
files
,runfiles
,data_runfiles
,default_runfiles
และexecutable
ตรงกับช่องที่มีชื่อเดียวกันของDefaultInfo
ระบบจะไม่อนุญาตให้ระบุข้อมูลใดๆ ในช่องเหล่านี้ขณะแสดงผลผู้ให้บริการDefaultInfo
ด้วยช่อง
output_groups
จะใช้ค่าโครงสร้างและสอดคล้องกับOutputGroupInfo
ในการประกาศกฎ provides
และในการประกาศแอตทริบิวต์การขึ้นต่อกันของ providers
ระบบจะส่งผ่านผู้ให้บริการเดิมเป็นสตริง ส่วนผู้ให้บริการสมัยใหม่จะมีสัญลักษณ์ *Info
เข้ามาส่งเข้ามา อย่าลืมเปลี่ยนจากสตริงเป็นสัญลักษณ์
เมื่อย้ายข้อมูล สำหรับชุดกฎที่ซับซ้อนหรือขนาดใหญ่ซึ่งอัปเดตกฎทั้งหมดแบบอะตอมได้ยาก คุณอาจใช้ได้ง่ายขึ้นหากทำตามขั้นตอนต่อไปนี้
แก้ไขกฎที่สร้างผู้ให้บริการเดิมเพื่อสร้างทั้งผู้ให้บริการรายเดิมและสมัยใหม่โดยใช้ไวยากรณ์ข้างต้น สำหรับกฎที่ประกาศว่าส่งคืนผู้ให้บริการเดิม ให้อัปเดตการประกาศนั้นให้รวมผู้ให้บริการเดิมและผู้ให้บริการรายใหม่
แก้ไขกฎที่ใช้ผู้ให้บริการเดิมเพื่อใช้ผู้ให้บริการที่ทันสมัยแทน หากการประกาศแอตทริบิวต์ต้องใช้ผู้ให้บริการเดิม ให้อัปเดตด้วยเพื่อกำหนดให้มีผู้ให้บริการที่ทันสมัยแทน หรือคุณอาจแทรกงานนี้ไว้ในขั้นตอนที่ 1 โดยให้ผู้บริโภคยอมรับ/กำหนดให้ใช้ผู้ให้บริการรายใดก็ได้: ทดสอบการมีอยู่ของผู้ให้บริการเดิมโดยใช้
hasattr(target, 'foo')
หรือผู้ให้บริการใหม่โดยใช้FooInfo in target
นําผู้ให้บริการเดิมออกจากกฎทั้งหมดโดยสมบูรณ์