หน้านี้จะอธิบายข้อมูลเบื้องต้นและประโยชน์ของการใช้ Aspect รวมถึงแสดงตัวอย่างแบบง่ายและแบบขั้นสูง
Aspect ช่วยเพิ่มกราฟการขึ้นต่อกันของการบิลด์ด้วยข้อมูลและการดำเนินการเพิ่มเติม สถานการณ์ทั่วไปที่ Aspect มีประโยชน์มีดังนี้
- IDE ที่ผสานรวม Bazel สามารถใช้ Aspect เพื่อรวบรวมข้อมูลเกี่ยวกับโปรเจ็กต์
- เครื่องมือสร้างโค้ดสามารถใช้ประโยชน์จาก Aspect เพื่อดำเนินการกับอินพุตในลักษณะ ไม่ขึ้นอยู่กับเป้าหมาย ตัวอย่างเช่น ไฟล์
BUILDสามารถระบุลำดับชั้น ของ protobuf คำจำกัดความไลบรารี และกฎเฉพาะภาษาจะใช้ Aspect เพื่อแนบ การดำเนินการที่สร้างโค้ดสนับสนุน protobuf สำหรับภาษาหนึ่งๆ ได้
ข้อมูลเบื้องต้นเกี่ยวกับ Aspect
ไฟล์ BUILD จะอธิบายซอร์สโค้ดของโปรเจ็กต์ เช่น ไฟล์ซอร์สโค้ดใดเป็นส่วนหนึ่งของโปรเจ็กต์ อาร์ติแฟกต์ (เป้าหมาย) ใดควรสร้างจากไฟล์เหล่านั้น ไฟล์เหล่านั้นขึ้นต่อกันอย่างไร เป็นต้น Bazel ใช้ข้อมูลนี้เพื่อทำการบิลด์ ซึ่งหมายความว่า Bazel จะพิจารณาชุดการดำเนินการที่จำเป็นในการสร้างอาร์ติแฟกต์ (เช่น การเรียกใช้คอมไพเลอร์หรือลิงเกอร์) และดำเนินการเหล่านั้น Bazel ทำได้โดยการสร้าง กราฟทรัพยากร Dependencyระหว่างเป้าหมายต่างๆ และเข้าชมกราฟนี้เพื่อรวบรวมการดำเนินการเหล่านั้น
ลองดูไฟล์ BUILD ต่อไปนี้
java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)
ไฟล์ BUILD นี้กำหนดกราฟการขึ้นต่อกันที่แสดงในรูปต่อไปนี้

รูปที่ 1 กราฟการขึ้นต่อกันของไฟล์ BUILD
Bazel จะวิเคราะห์กราฟการขึ้นต่อกันนี้โดยการเรียกใช้ฟังก์ชันการใช้งานของ
กฎที่เกี่ยวข้อง (ในกรณีนี้คือ "java_library") สำหรับทุก
เป้าหมายในตัวอย่างข้างต้น ฟังก์ชันการใช้งานกฎจะสร้างการดำเนินการที่สร้างอาร์ติแฟกต์ เช่น ไฟล์ .jar และส่งข้อมูล เช่น ตำแหน่งและชื่อของอาร์ติแฟกต์เหล่านั้น ไปยังทรัพยากร Dependency แบบย้อนกลับของเป้าหมายเหล่านั้นใน ตัวจัดหาข้อมูล
Aspect มีลักษณะคล้ายกับกฎตรงที่มีฟังก์ชันการใช้งานที่สร้างการดำเนินการและแสดงผลตัวจัดหาข้อมูล อย่างไรก็ตาม ประสิทธิภาพของ Aspect มาจากวิธีสร้างกราฟการขึ้นต่อกันสำหรับ Aspect Aspect มีการใช้งานและรายการแอตทริบิวต์ทั้งหมดที่ Aspect เผยแพร่ ลองพิจารณา Aspect A ที่เผยแพร่ตามแอตทริบิวต์ที่ชื่อว่า "deps" คุณสามารถใช้ Aspect นี้กับเป้าหมาย X ซึ่งจะให้โหนดการใช้ Aspect A(X) ระหว่างการใช้ Aspect A จะใช้แบบเรียกซ้ำกับเป้าหมายทั้งหมดที่ X อ้างอิงในแอตทริบิวต์ "deps" (แอตทริบิวต์ทั้งหมดในรายการการเผยแพร่ของ A)
ดังนั้นการดำเนินการเพียงครั้งเดียวในการใช้เฉพาะด้าน A กับเป้าหมาย X จะให้ "กราฟเงา" ของกราฟการขึ้นต่อกันเดิมของเป้าหมายที่แสดงในรูปต่อไปนี้

รูปที่ 2 กราฟการบิลด์ที่มี Aspect
ขอบที่แสดงเป็นเงาคือขอบตามแอตทริบิวต์ในชุดการเผยแพร่ ดังนั้นขอบ runtime_deps จึงไม่แสดงเป็นเงาในตัวอย่างนี้ จากนั้นระบบจะเรียกใช้ฟังก์ชันการใช้งาน Aspect กับโหนดทั้งหมดในกราฟเงาในลักษณะเดียวกับที่เรียกใช้การใช้งานกฎกับโหนดของกราฟเดิม
ตัวอย่างแบบง่าย
ตัวอย่างนี้แสดงวิธีพิมพ์ไฟล์ซอร์สโค้ดแบบเรียกซ้ำสำหรับกฎและกฎการขึ้นต่อกันทั้งหมดที่มีแอตทริบิวต์ deps โดยจะแสดงการใช้งาน Aspect, คำจำกัดความของ Aspect และวิธีเรียกใช้ Aspect จากบรรทัดคำสั่งของ Bazel
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
ลองแบ่งตัวอย่างออกเป็นส่วนๆ และดูแต่ละส่วนแยกกัน
คำจำกัดความของ Aspect
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
คำจำกัดความของ Aspect มีลักษณะคล้ายกับคำจำกัดความของกฎ และกำหนดโดยใช้
aspect ฟังก์ชัน
Aspect มีฟังก์ชันการใช้งานเช่นเดียวกับกฎ ซึ่งในกรณีนี้คือ _print_aspect_impl
attr_aspects คือรายการแอตทริบิวต์กฎที่ Aspect เผยแพร่
ในกรณีนี้ Aspect จะเผยแพร่ตามแอตทริบิวต์ deps ของกฎที่ใช้
อาร์กิวเมนต์ทั่วไปอีกรายการหนึ่งสำหรับ attr_aspects คือ ['*'] ซึ่งจะเผยแพร่
Aspect ไปยังแอตทริบิวต์ทั้งหมดของกฎ
การใช้งาน Aspect
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
ฟังก์ชันการใช้งาน Aspect มีลักษณะคล้ายกับฟังก์ชันการใช้งานกฎ โดยจะแสดงผลตัวจัดหาข้อมูล สร้าง การดำเนินการ และใช้อาร์กิวเมนต์ 2 รายการดังนี้
target: เป้าหมายที่ใช้ Aspectctx: ออบเจ็กต์ctxที่ใช้เข้าถึงแอตทริบิวต์ และสร้างเอาต์พุตและการดำเนินการได้
ฟังก์ชันการใช้งานสามารถเข้าถึงแอตทริบิวต์ของกฎเป้าหมายผ่าน
ctx.rule.attr และตรวจสอบตัวจัดหาข้อมูลที่เป้าหมายแสดง (ผ่านอาร์กิวเมนต์ target) ซึ่ง Aspect ใช้ได้
Aspect ต้องแสดงผลรายการตัวจัดหาข้อมูล ในตัวอย่างนี้ Aspect ไม่ได้แสดงผลสิ่งใด จึงแสดงผลรายการว่าง
การเรียกใช้ Aspect โดยใช้บรรทัดคำสั่ง
วิธีที่ง่ายที่สุดในการใช้ Aspect คือจากบรรทัดคำสั่งโดยใช้
--aspects
อาร์กิวเมนต์ สมมติว่า Aspect ด้านบนกำหนดไว้ในไฟล์ที่ชื่อว่า print.bzl การดำเนินการต่อไปนี้
bazel build //MyExample:example --aspects print.bzl%print_aspect
จะใช้ print_aspect กับเป้าหมาย example และกฎเป้าหมายทั้งหมดที่เข้าถึงได้แบบเรียกซ้ำผ่านแอตทริบิวต์ deps
แฟล็ก --aspects ใช้อาร์กิวเมนต์ 1 รายการ ซึ่งเป็นการระบุ Aspect
ในรูปแบบ <extension file label>%<aspect top-level name>
ตัวอย่างแบบขั้นสูง
ตัวอย่างต่อไปนี้แสดงการใช้ Aspect จากกฎเป้าหมายที่นับไฟล์ในเป้าหมาย และอาจกรองไฟล์ตามนามสกุล โดยจะแสดงวิธีใช้ตัวจัดหาข้อมูลเพื่อแสดงผลค่า วิธีใช้พารามิเตอร์เพื่อส่งอาร์กิวเมนต์ไปยังการใช้งาน Aspect และวิธีเรียกใช้ Aspect จากกฎ
ไฟล์ file_count.bzl
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
ไฟล์ BUILD.bazel
load('//:file_count.bzl', 'file_count_rule')
cc_library(
name = 'lib',
srcs = [
'lib.h',
'lib.cc',
],
)
cc_binary(
name = 'app',
srcs = [
'app.h',
'app.cc',
'main.cc',
],
deps = ['lib'],
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
คำจำกัดความของ Aspect
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
ตัวอย่างนี้แสดงวิธีที่ Aspect เผยแพร่ผ่านแอตทริบิวต์ deps
attrs กำหนดชุดแอตทริบิวต์สำหรับ Aspect แอตทริบิวต์ Aspect แบบสาธารณะมีประเภท string และเรียกว่าพารามิเตอร์ พารามิเตอร์ต้องมีแอตทริบิวต์ values ที่ระบุไว้ ตัวอย่างนี้มีพารามิเตอร์ที่ชื่อว่า extension ซึ่งอนุญาตให้มีค่าเป็น '*', 'h' หรือ 'cc'
ระบบจะนำค่าพารามิเตอร์สำหรับ Aspect มาจากแอตทริบิวต์สตริงที่มีชื่อเดียวกับกฎที่ขอ Aspect (ดูคำจำกัดความของ file_count_rule) คุณไม่สามารถใช้ Aspect ที่มีพารามิเตอร์ผ่านบรรทัดคำสั่งได้เนื่องจากไม่มีไวยากรณ์ในการกำหนดพารามิเตอร์
นอกจากนี้ Aspect ยังมีแอตทริบิวต์แบบส่วนตัวประเภท label หรือ
label_list ได้ด้วย คุณสามารถใช้แอตทริบิวต์ป้ายกำกับแบบส่วนตัวเพื่อระบุการขึ้นต่อกันของเครื่องมือหรือไลบรารีที่จำเป็นสำหรับการดำเนินการที่ Aspect สร้างขึ้น ตัวอย่างนี้ไม่ได้กำหนดแอตทริบิวต์แบบส่วนตัว แต่ข้อมูลโค้ดต่อไปนี้แสดงวิธีส่งเครื่องมือไปยัง Aspect
...
attrs = {
'_protoc' : attr.label(
default = Label('//tools:protoc'),
executable = True,
cfg = "exec"
)
}
...
การใช้งาน Aspect
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
ฟังก์ชันการใช้งาน Aspect จะแสดงผลโครงสร้างของตัวจัดหาข้อมูลที่การขึ้นต่อกันเข้าถึงได้เช่นเดียวกับฟังก์ชันการใช้งานกฎ
ในตัวอย่างนี้ FileCountInfo กำหนดเป็นตัวจัดหาข้อมูลที่มีช่อง count 1 ช่อง แนวทางปฏิบัติแนะนำคือการกำหนดช่องของตัวจัดหาข้อมูลอย่างชัดเจนโดยใช้แอตทริบิวต์ fields
ชุดตัวจัดหาข้อมูลสำหรับการใช้ Aspect A(X) คือการรวมตัวจัดหาข้อมูลที่มาจากการใช้งานกฎสำหรับเป้าหมาย X และจากการใช้งาน Aspect A ระบบจะสร้างและตรึงตัวจัดหาข้อมูลที่การใช้งานกฎเผยแพร่ก่อนที่จะใช้ Aspect และคุณจะแก้ไขตัวจัดหาข้อมูลเหล่านี้จาก Aspect ไม่ได้ ระบบจะแสดงข้อผิดพลาดหากเป้าหมายและ Aspect ที่ใช้กับเป้าหมายนั้น
แสดงตัวจัดหาข้อมูลประเภทเดียวกัน โดยมีข้อยกเว้นคือ
OutputGroupInfo
(ซึ่งจะผสานรวมกัน ตราบใดที่
กฎและ Aspect ระบุกลุ่มเอาต์พุตที่แตกต่างกัน) และ
InstrumentedFilesInfo
(ซึ่งนำมาจาก Aspect) ซึ่งหมายความว่าการใช้งาน Aspect จะแสดงผล
ไม่ได้DefaultInfo
ระบบจะส่งพารามิเตอร์และแอตทริบิวต์แบบส่วนตัวในแอตทริบิวต์ของ ctx ตัวอย่างนี้อ้างอิงพารามิเตอร์ extension และกำหนดไฟล์ที่จะนับ
สำหรับการแสดงผลตัวจัดหาข้อมูล ระบบจะแทนที่ค่าของแอตทริบิวต์ที่ Aspect เผยแพร่ (จากรายการ attr_aspects) ด้วยผลลัพธ์ของการใช้ Aspect กับแอตทริบิวต์เหล่านั้น ตัวอย่างเช่น หากเป้าหมาย X มี Y และ Z ใน deps, ctx.rule.attr.deps สำหรับ A(X) จะเป็น [A(Y), A(Z)]
ในตัวอย่างนี้ ctx.rule.attr.deps คือออบเจ็กต์เป้าหมายที่เป็นผลลัพธ์ของการใช้ Aspect กับ "deps" ของเป้าหมายเดิมที่ใช้ Aspect
ในตัวอย่างนี้ Aspect จะเข้าถึงตัวจัดหาข้อมูล FileCountInfo จากการขึ้นต่อกันของเป้าหมายเพื่อสะสมจำนวนไฟล์แบบทรานซิทีฟทั้งหมด
การเรียกใช้ Aspect จากกฎ
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
การใช้งานกฎแสดงวิธีเข้าถึง FileCountInfo ผ่าน ctx.attr.deps
คำจำกัดความของกฎแสดงวิธีกำหนดพารามิเตอร์ (extension) และกำหนดค่าเริ่มต้น (*) โปรดทราบว่าการมีค่าเริ่มต้นที่ไม่ใช่ 'cc', 'h' หรือ '*' จะทำให้เกิดข้อผิดพลาดเนื่องจากข้อจำกัดที่กำหนดไว้ในพารามิเตอร์ในคำจำกัดความของ Aspect
การเรียกใช้ Aspect ผ่านกฎเป้าหมาย
load('//:file_count.bzl', 'file_count_rule')
cc_binary(
name = 'app',
...
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
ตัวอย่างนี้แสดงวิธีส่งพารามิเตอร์ extension ไปยัง Aspect ผ่านกฎ เนื่องจากพารามิเตอร์ extension มีค่าเริ่มต้นในการใช้งานกฎ ระบบจึงถือว่า extension เป็นพารามิเตอร์ที่ไม่บังคับ
เมื่อสร้างเป้าหมาย file_count ระบบจะประเมิน Aspect สำหรับตัวเป้าหมายเองและเป้าหมายทั้งหมดที่เข้าถึงได้แบบเรียกซ้ำผ่าน deps