กฎจะกำหนดชุดการดำเนินการที่ Bazel ดำเนินการกับ อินพุตเพื่อสร้างชุดเอาต์พุต ซึ่งอ้างอิงใน ผู้ให้บริการที่ฟังก์ชัน การใช้งานของกฎส่งคืน เช่น กฎไบนารี C++ อาจมีลักษณะดังนี้
- ใช้ชุด
.cpp
ไฟล์ต้นฉบับ (อินพุต) - เรียกใช้
g++
ในไฟล์ต้นฉบับ (การดำเนินการ) - ส่งคืน
DefaultInfo
provider พร้อมเอาต์พุตที่เรียกใช้งานได้และไฟล์อื่นๆ เพื่อให้พร้อมใช้งานในรันไทม์ - ส่งคืนผู้ให้บริการ
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
)
จะระบุการขึ้นต่อกันของประเภทหนึ่งๆ
ระหว่างเป้าหมายกับเป้าหมายที่มีป้ายกำกับ (หรือออบเจ็กต์ Label
ที่เกี่ยวข้อง)
ซึ่งแสดงอยู่ในแอตทริบิวต์นั้นเมื่อกำหนดเป้าหมาย ระบบจะระบุที่เก็บและอาจรวมถึงเส้นทางสำหรับป้ายกำกับเหล่านี้
เทียบกับเป้าหมายที่กำหนด
example_library(
name = "my_target",
deps = [":other_target"],
)
example_library(
name = "other_target",
...
)
ในตัวอย่างนี้ other_target
เป็นการขึ้นต่อกันของ my_target
ดังนั้น
ระบบจะวิเคราะห์ other_target
ก่อน ถือเป็นข้อผิดพลาดหากมีวงจรใน
กราฟการขึ้นต่อกันของเป้าหมาย
แอตทริบิวต์ส่วนตัวและการอ้างอิงโดยนัย
แอตทริบิวต์การขึ้นต่อกันที่มีค่าเริ่มต้นจะสร้างการขึ้นต่อกันโดยนัย ซึ่งเป็นแบบโดยนัยเนื่องจากเป็นส่วนหนึ่งของกราฟเป้าหมายที่ผู้ใช้ไม่ได้
ระบุไว้ในไฟล์ BUILD
การขึ้นต่อกันโดยนัยมีประโยชน์สำหรับการฮาร์ดโค้ดความสัมพันธ์ระหว่างกฎกับเครื่องมือ (การขึ้นต่อกันในเวลาบิลด์ เช่น คอมไพเลอร์) เนื่องจากส่วนใหญ่ผู้ใช้จะไม่สนใจที่จะระบุว่ากฎใช้เครื่องมือใด ภายในฟังก์ชันการใช้งานของกฎ ระบบจะถือว่าค่านี้เหมือนกับ
การขึ้นต่อกันอื่นๆ
หากต้องการระบุการอ้างอิงโดยนัยโดยไม่อนุญาตให้ผู้ใช้ลบล้างค่าดังกล่าว คุณสามารถทำให้แอตทริบิวต์เป็นส่วนตัวได้โดยตั้งชื่อให้ขึ้นต้นด้วยขีดล่าง (_
) แอตทริบิวต์ส่วนตัวต้องมีค่าเริ่มต้น โดยทั่วไปแล้ว การใช้แอตทริบิวต์ส่วนตัวจะสมเหตุสมผลก็ต่อเมื่อใช้กับ
การอ้างอิงโดยนัยเท่านั้น
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
ออบเจ็กต์สำหรับเอาต์พุตสามารถสร้างได้โดยใช้
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 คือชุดไฟล์ที่เป้าหมายใช้ในเวลาเรียกใช้ (ตรงข้ามกับเวลาบิลด์) ในระยะการดำเนินการ 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 ที่สะอาดขึ้นสำหรับการรับอินสแตนซ์
โดยทำได้ด้วยการส่ง Callback ของ init
ไปยังฟังก์ชัน provider
หากมีการระบุการเรียกกลับนี้ ประเภทการคืนค่าของ provider()
จะเปลี่ยนเป็น Tuple ของค่า 2 ค่า ได้แก่ สัญลักษณ์ของผู้ให้บริการซึ่งเป็นค่าคืนค่าปกติเมื่อไม่ได้ใช้ init
และ "ตัวสร้างแบบดิบ"
ในกรณีนี้ เมื่อมีการเรียกใช้สัญลักษณ์ของผู้ให้บริการ แทนที่จะส่งคืนอินสแตนซ์ใหม่โดยตรง
ระบบจะส่งต่ออาร์กิวเมนต์ไปยังinit
การเรียกกลับ ค่าที่ฟังก์ชันเรียกกลับส่งคืนต้องเป็นชื่อฟิลด์การแมปพจนานุกรม (สตริง) กับค่า
ซึ่งใช้เพื่อเริ่มต้นฟิลด์ของอินสแตนซ์ใหม่ โปรดทราบว่า
การเรียกกลับอาจมีลายเซ็นใดก็ได้ และหากอาร์กิวเมนต์ไม่ตรงกับลายเซ็น
ระบบจะรายงานข้อผิดพลาดราวกับว่ามีการเรียกใช้การเรียกกลับโดยตรง
ในทางตรงกันข้าม เครื่องมือสร้างแบบดิบจะข้าม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
อย่างไรก็ตาม ไบนารีที่เรียกจากไฟล์ที่เรียกใช้จะ
ใช้สมมติฐานเดียวกันไม่ได้ เพื่อลดปัญหานี้ ไบนารีแต่ละรายการควรมีวิธี
ยอมรับรูทของไฟล์ที่รันได้เป็นพารามิเตอร์โดยใช้ตัวแปรสภาพแวดล้อม หรืออาร์กิวเมนต์หรือแฟล็กบรรทัดคำสั่ง
ซึ่งจะช่วยให้ไบนารีส่งรูทไฟล์ที่ถูกต้องไปยังไบนารีที่เรียกใช้ได้
หากไม่ได้ตั้งค่าไว้ ไบนารีจะคาดเดาได้ว่าไบนารีนั้นเป็น
ไบนารีแรกที่เรียกใช้และค้นหาไดเรกทอรี Runfiles ที่อยู่ติดกัน
หัวข้อขั้นสูง
การขอไฟล์เอาต์พุต
เป้าหมายเดียวอาจมีไฟล์เอาต์พุตหลายไฟล์ เมื่อเรียกใช้bazel build
คำสั่ง
ระบบจะถือว่าเอาต์พุตบางส่วนของเป้าหมายที่ระบุในคำสั่งเป็นคำขอ Bazel จะสร้างเฉพาะไฟล์ที่ขอและไฟล์ที่ไฟล์เหล่านั้น
ขึ้นอยู่ด้วยโดยตรงหรือโดยอ้อม (ในแง่ของกราฟการดำเนินการ Bazel จะดำเนินการเฉพาะการดำเนินการที่เข้าถึงได้เป็นทรัพยากร Dependency แบบทรานซิทีฟของไฟล์ที่ขอ)
นอกเหนือจากเอาต์พุตเริ่มต้นแล้ว คุณยังขอเอาต์พุตที่ประกาศไว้ล่วงหน้าได้อย่างชัดแจ้งในบรรทัดคำสั่ง
กฎสามารถระบุเอาต์พุตที่ประกาศไว้ล่วงหน้าได้โดยใช้แอตทริบิวต์เอาต์พุต ในกรณีดังกล่าว ผู้ใช้จะเลือกป้ายกำกับสำหรับเอาต์พุตอย่างชัดเจนเมื่อสร้างอินสแตนซ์ของกฎ หากต้องการรับออบเจ็กต์
File
สำหรับแอตทริบิวต์เอาต์พุต ให้ใช้แอตทริบิวต์ที่เกี่ยวข้องของ ctx.outputs
กฎสามารถกำหนดเอาต์พุตที่ประกาศไว้ล่วงหน้าโดยนัยตามชื่อเป้าหมายได้ด้วย แต่ฟีเจอร์นี้เลิกใช้งานแล้ว
นอกจากเอาต์พุตเริ่มต้นแล้ว ยังมีกลุ่มเอาต์พุต ซึ่งเป็นคอลเล็กชัน
ของไฟล์เอาต์พุตที่อาจขอพร้อมกัน คุณขอรับข้อมูลเหล่านี้ได้โดยใช้
--output_groups
เช่น หาก//pkg:mytarget
เป้าหมายเป็นประเภทกฎที่มีdebug_files
กลุ่มเอาต์พุต คุณจะสร้างไฟล์เหล่านี้ได้โดยการเรียกใช้ bazel build //pkg:mytarget
--output_groups=debug_files
เนื่องจากเอาต์พุตที่ไม่ได้ประกาศล่วงหน้าไม่มีป้ายกำกับ
จึงขอได้โดยปรากฏในเอาต์พุตเริ่มต้นหรือกลุ่มเอาต์พุตเท่านั้น
คุณระบุกลุ่มเอาต์พุตได้ด้วยผู้ให้บริการ OutputGroupInfo
โปรดทราบว่าOutputGroupInfo
ต่างจากผู้ให้บริการในตัวหลายรายตรงที่สามารถใช้พารามิเตอร์ที่มีชื่อใดก็ได้
เพื่อกำหนดกลุ่มเอาต์พุตที่มีชื่อนั้น
def _example_library_impl(ctx):
...
debug_file = ctx.actions.declare_file(name + ".pdb")
...
return [
DefaultInfo(files = depset([output_file]), ...),
OutputGroupInfo(
debug_files = depset([debug_file]),
all_files = depset([output_file, debug_file]),
),
...
]
นอกจากนี้ OutputGroupInfo
ยังแสดงผลได้ทั้งจากaspect และเป้าหมายของกฎที่ใช้ aspect นั้น ซึ่งต่างจากผู้ให้บริการส่วนใหญ่ ตราบใดที่ไม่ได้กำหนดกลุ่มเอาต์พุตเดียวกัน ในกรณีดังกล่าว ระบบจะผสานรวมผู้ให้บริการที่ได้
โปรดทราบว่าโดยทั่วไปแล้วไม่ควรใช้ OutputGroupInfo
เพื่อสื่อถึงไฟล์ประเภทใดประเภทหนึ่ง
จากเป้าหมายไปยังการกระทำของผู้บริโภค ให้กำหนดผู้ให้บริการที่เฉพาะเจาะจงสำหรับกฎนั้นแทน
การกำหนดค่า
สมมติว่าคุณต้องการสร้างไบนารี C++ สำหรับสถาปัตยกรรมอื่น การสร้างอาจมีความซับซ้อนและเกี่ยวข้องกับหลายขั้นตอน ไบนารีระดับกลางบางรายการ เช่น คอมไพเลอร์และตัวสร้างโค้ด ต้องทำงานบนแพลตฟอร์มการดำเนินการ (ซึ่งอาจเป็นโฮสต์ของคุณหรือตัวดำเนินการระยะไกล) ไบนารีบางรายการ เช่น เอาต์พุตสุดท้าย ต้องสร้างขึ้นสำหรับ สถาปัตยกรรมเป้าหมาย
ด้วยเหตุนี้ Bazel จึงมีแนวคิดเรื่อง "การกำหนดค่า" และการเปลี่ยนสถานะ เป้าหมายบนสุด (เป้าหมายที่ขอในบรรทัดคำสั่ง) จะสร้างขึ้นในการกำหนดค่า "target" ส่วนเครื่องมือที่ควรทำงานบนแพลตฟอร์มการดำเนินการ จะสร้างขึ้นในการกำหนดค่า "exec" กฎอาจสร้างการดำเนินการที่แตกต่างกันตามการกำหนดค่า เช่น เพื่อเปลี่ยนสถาปัตยกรรม CPU ที่ส่งไปยังคอมไพเลอร์ ในบางกรณี คุณอาจต้องใช้ไลบรารีเดียวกันสำหรับการกำหนดค่าที่แตกต่างกัน หากเกิดกรณีนี้ ระบบจะวิเคราะห์และอาจสร้างหลายครั้ง
โดยค่าเริ่มต้น Bazel จะสร้างการอ้างอิงของเป้าหมายในการกำหนดค่าเดียวกันกับ ตัวเป้าหมายเอง กล่าวคือไม่มีการเปลี่ยน เมื่อการขึ้นต่อกันเป็นเครื่องมือที่จำเป็นต่อการสร้างเป้าหมาย แอตทริบิวต์ที่เกี่ยวข้องควรระบุการเปลี่ยนไปใช้การกำหนดค่า 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
คือพจนานุกรมที่แมปเส้นทางไปยัง
ไฟล์ โดยเส้นทางจะสัมพันธ์กับรูทของไดเรกทอรีไฟล์ที่เรียกใช้ symlinks
พจนานุกรมจะเหมือนกัน แต่ระบบจะใส่คำนำหน้าเส้นทางโดยนัยด้วยชื่อของเวิร์กสเปซหลัก (ไม่ใช่ชื่อของที่เก็บที่มีเป้าหมายปัจจุบัน)
...
runfiles = ctx.runfiles(
root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
)
# Creates something like:
# sometarget.runfiles/
# some/
# path/
# here.foo -> some_data_file2
# <workspace_name>/
# some/
# path/
# here.bar -> some_data_file3
หากใช้ symlinks
หรือ root_symlinks
ให้ระมัดระวังอย่าแมปไฟล์ 2 ไฟล์ที่แตกต่างกัน
ไปยังเส้นทางเดียวกันในโครงสร้างไฟล์ที่ใช้ในการทดสอบ ซึ่งจะทำให้บิลด์ล้มเหลว
พร้อมข้อผิดพลาดที่อธิบายถึงความขัดแย้ง หากต้องการแก้ไข คุณจะต้องแก้ไขอาร์กิวเมนต์ ctx.runfiles
เพื่อหลีกเลี่ยงการชนกัน การตรวจสอบนี้จะดำเนินการกับ
เป้าหมายใดก็ตามที่ใช้กฎของคุณ รวมถึงเป้าหมายทุกประเภทที่ขึ้นอยู่กับเป้าหมายเหล่านั้น
ซึ่งจะมีความเสี่ยงเป็นอย่างยิ่งหากเครื่องมือของคุณมีแนวโน้มที่จะถูกใช้โดยเครื่องมืออื่นแบบทรานซิทีฟ ชื่อของ Symlink ต้องไม่ซ้ำกันในไฟล์ที่รันของเครื่องมือและ
การอ้างอิงทั้งหมดของเครื่องมือ
การครอบคลุมของโค้ด
เมื่อเรียกใช้คำสั่ง coverage
บิลด์อาจต้องเพิ่มเครื่องมือวัดความครอบคลุมสำหรับเป้าหมายบางอย่าง
การสร้างยังรวบรวมรายการไฟล์ต้นฉบับที่ได้รับการวัดด้วย โดยชุดย่อยของ
เป้าหมายที่พิจารณาจะควบคุมโดยแฟล็ก
--instrumentation_filter
ระบบจะยกเว้นเป้าหมายการทดสอบ เว้นแต่จะมีการระบุ
--instrument_test_targets
หากการใช้งานกฎเพิ่มเครื่องมือวัดความครอบคลุมในเวลาบิลด์ จะต้อง
พิจารณาถึงเรื่องนี้ในฟังก์ชันการใช้งาน
ctx.coverage_instrumented จะแสดงผล
True
ในโหมดความครอบคลุมหากควรวัดแหล่งที่มาของเป้าหมาย
# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
# Do something to turn on coverage for this compile action
ตรรกะที่ต้องเปิดอยู่เสมอในโหมดความครอบคลุม (ไม่ว่าแหล่งที่มาของเป้าหมายจะได้รับการวัดผลโดยเฉพาะหรือไม่) สามารถกำหนดเงื่อนไขได้ใน ctx.configuration.coverage_enabled
หากกฎมีแหล่งที่มาจากทรัพยากร Dependency โดยตรงก่อนการคอมไพล์ (เช่น ไฟล์ส่วนหัว) คุณอาจต้องเปิดใช้การวัดประสิทธิภาพขณะคอมไพล์ด้วยหาก ควรวัดประสิทธิภาพแหล่งที่มาของทรัพยากร Dependency
# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if ctx.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
ด้วย
ความครอบคลุมพื้นฐาน
เนื่องจากระบบจะรวบรวมความครอบคลุมเฉพาะโค้ดที่อยู่ในแผนผังการอ้างอิงของ
การทดสอบ รายงานความครอบคลุมจึงอาจทำให้เข้าใจผิดได้เนื่องจากไม่ได้ครอบคลุมโค้ดทั้งหมดที่ตรงกับแฟล็ก --instrumentation_filter
เสมอไป
ด้วยเหตุนี้ Bazel จึงอนุญาตให้กฎระบุไฟล์ความครอบคลุมพื้นฐานโดยใช้แอตทริบิวต์ baseline_coverage_files
ของ ctx.instrumented_files_info
) ไฟล์เหล่านี้ต้องสร้างในรูปแบบ LCOV โดยการดำเนินการที่ผู้ใช้กำหนด และควรแสดงรายการบรรทัด สาขา ฟังก์ชัน และ/หรือบล็อกทั้งหมดในไฟล์แหล่งที่มาของเป้าหมาย (ตามพารามิเตอร์ sources_attributes
และ extensions
) สำหรับไฟล์ต้นฉบับในเป้าหมายที่ได้รับการวัดผลเพื่อการครอบคลุม Bazel จะผสานการครอบคลุมพื้นฐานเข้ากับรายงานการครอบคลุมที่รวมกันซึ่งสร้างขึ้นด้วย --combined_report
เพื่อให้มั่นใจว่าไฟล์ที่ไม่ได้ทดสอบจะยังคงแสดงเป็นไฟล์ที่ไม่มีการครอบคลุม
หากกฎไม่ได้ระบุไฟล์ความครอบคลุมพื้นฐาน Bazel จะสร้างข้อมูลความครอบคลุมสังเคราะห์ซึ่งระบุเฉพาะเส้นทางไฟล์ต้นฉบับ แต่ไม่มีข้อมูลเกี่ยวกับเนื้อหาของไฟล์
การดำเนินการตรวจสอบ
ในบางครั้ง คุณอาจต้องตรวจสอบความถูกต้องของสิ่งต่างๆ เกี่ยวกับการสร้าง และ ข้อมูลที่จำเป็นต่อการตรวจสอบความถูกต้องนั้นจะอยู่ในอาร์ติแฟกต์เท่านั้น (ไฟล์ต้นฉบับหรือไฟล์ที่สร้างขึ้น) เนื่องจากข้อมูลนี้อยู่ในอาร์ติแฟกต์ กฎจึงไม่สามารถทำการตรวจสอบนี้ในเวลาที่วิเคราะห์ได้เนื่องจากกฎไม่สามารถอ่านไฟล์ได้ แต่การดำเนินการต้องทำการตรวจสอบนี้ในเวลาที่เรียกใช้ เมื่อ การตรวจสอบไม่สำเร็จ การดำเนินการจะไม่สำเร็จ และบิลด์ก็จะไม่สำเร็จด้วย
ตัวอย่างการตรวจสอบที่อาจดำเนินการ ได้แก่ การวิเคราะห์แบบคงที่ การตรวจสอบความถูกต้อง การตรวจสอบการขึ้นต่อกันและความสอดคล้อง และการตรวจสอบรูปแบบ
นอกจากนี้ การดำเนินการตรวจสอบยังช่วยปรับปรุงประสิทธิภาพการสร้างได้ด้วยการย้ายส่วนต่างๆ ของการดำเนินการที่ไม่จำเป็นสำหรับการสร้างอาร์ติแฟกต์ไปยังการดำเนินการแยกต่างหาก ตัวอย่างเช่น หากการดำเนินการเดียวที่ทำการคอมไพล์และ Lint สามารถแยกออกเป็นการดำเนินการคอมไพล์และการดำเนินการ Lint ได้ การดำเนินการ Lint ก็จะสามารถเรียกใช้เป็นการดำเนินการตรวจสอบและเรียกใช้แบบขนานกับการดำเนินการอื่นๆ ได้
"การดำเนินการตรวจสอบ" เหล่านี้มักจะไม่สร้างสิ่งใดที่ใช้ที่อื่น ในการสร้าง เนื่องจากมีหน้าที่เพียงยืนยันสิ่งต่างๆ เกี่ยวกับอินพุตของตน แต่ปัญหาก็คือ หากการดำเนินการตรวจสอบไม่สร้างสิ่งใดที่ใช้ที่อื่นในการสร้าง กฎจะสั่งให้ดำเนินการได้อย่างไร ในอดีต แนวทางคือการให้การดำเนินการตรวจสอบส่งออกไฟล์ว่าง และเพิ่มเอาต์พุตนั้นลงในอินพุตของการดำเนินการอื่นๆ ที่สำคัญ ในการสร้างโดยไม่เป็นธรรมชาติ
วิธีนี้ใช้ได้เนื่องจาก Bazel จะเรียกใช้การดำเนินการตรวจสอบเสมอเมื่อมีการเรียกใช้การดำเนินการคอมไพล์ แต่ก็มีข้อเสียที่สำคัญดังนี้
การดำเนินการตรวจสอบอยู่ในเส้นทางวิกฤตของการสร้าง เนื่องจาก Bazel คิดว่าต้องมีเอาต์พุตที่ว่างเปล่าเพื่อเรียกใช้การดำเนินการคอมไพล์ จึงจะเรียกใช้การดำเนินการ ตรวจสอบก่อน แม้ว่าการดำเนินการคอมไพล์จะละเว้นอินพุตก็ตาม ซึ่งจะลดการทำงานแบบคู่ขนานและทำให้การสร้างช้าลง
หากการดำเนินการอื่นๆ ในบิลด์อาจทำงานแทนการดำเนินการ คอมไพล์ คุณจะต้องเพิ่มเอาต์พุตที่ว่างเปล่าของการดำเนินการตรวจสอบลงในการดำเนินการเหล่านั้นด้วย (เช่น เอาต์พุต JAR ของแหล่งที่มาของ
java_library
) นอกจากนี้ ปัญหานี้จะเกิดขึ้นด้วยหากมีการเพิ่มการดำเนินการใหม่ที่อาจทำงานแทนการดำเนินการคอมไพล์ในภายหลัง และมีการละเว้นเอาต์พุตการตรวจสอบที่ว่างเปล่าโดยไม่ได้ตั้งใจ
วิธีแก้ปัญหาเหล่านี้คือการใช้กลุ่มเอาต์พุตการตรวจสอบ
กลุ่มเอาต์พุตการตรวจสอบ
กลุ่มเอาต์พุตการตรวจสอบเป็นกลุ่มเอาต์พุตที่ออกแบบมาเพื่อเก็บเอาต์พุตที่ไม่ได้ใช้ของการดำเนินการตรวจสอบ เพื่อไม่จำเป็นต้องเพิ่มเอาต์พุตดังกล่าวลงในอินพุตของการดำเนินการอื่นๆ โดยไม่จำเป็น
กลุ่มนี้มีความพิเศษตรงที่ระบบจะขอเอาต์พุตของกลุ่มเสมอ ไม่ว่าค่าของแฟล็ก --output_groups
จะเป็นเท่าใด และไม่ว่าเป้าหมายจะขึ้นอยู่กับอะไร (เช่น ในบรรทัดคำสั่ง เป็นการขึ้นต่อกัน หรือผ่านเอาต์พุตโดยนัยของเป้าหมาย) โปรดทราบว่าการแคชและการเพิ่มขึ้นตามปกติ
ยังคงมีผลอยู่ หากอินพุตของการดำเนินการตรวจสอบไม่เปลี่ยนแปลงและ
การดำเนินการตรวจสอบสำเร็จก่อนหน้านี้ ระบบจะไม่
เรียกใช้การดำเนินการตรวจสอบ
การใช้กลุ่มเอาต์พุตนี้ยังคงกำหนดให้การดำเนินการตรวจสอบต้องส่งออกไฟล์บางไฟล์ แม้จะเป็นไฟล์ว่างก็ตาม ซึ่งอาจต้องมีการห่อหุ้มเครื่องมือบางอย่างที่ปกติแล้วจะไม่ สร้างเอาต์พุตเพื่อให้มีการสร้างไฟล์
ระบบจะไม่เรียกใช้การดำเนินการตรวจสอบของเป้าหมายใน 3 กรณีต่อไปนี้
- เมื่อใช้เป้าหมายเป็นเครื่องมือ
- เมื่อเป้าหมายขึ้นอยู่กับการพึ่งพาโดยนัย (เช่น แอตทริบิวต์ที่ขึ้นต้นด้วย "_")
- เมื่อสร้างเป้าหมายในการกำหนดค่า 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 โดยให้ผู้บริโภคยอมรับหรือกำหนดให้ใช้ ผู้ให้บริการ: ทดสอบการมีอยู่ของผู้ให้บริการเดิมโดยใช้
hasattr(target, 'foo')
หรือผู้ให้บริการรายใหม่โดยใช้FooInfo in target
นำผู้ให้บริการเดิมออกจากกฎทั้งหมดโดยสมบูรณ์