สัดส่วนภาพ

หน้านี้จะอธิบายข้อมูลเบื้องต้นและประโยชน์ของการใช้ Aspect รวมถึงแสดงตัวอย่างแบบง่ายและแบบขั้นสูง

Aspect ช่วยเพิ่มกราฟทรัพยากร Dependency ของการบิลด์ด้วยข้อมูลและการดำเนินการเพิ่มเติม สถานการณ์ทั่วไปที่ Aspect อาจมีประโยชน์มีดังนี้

  • IDE ที่ผสานรวม Bazel สามารถใช้ Aspect เพื่อรวบรวมข้อมูลเกี่ยวกับโปรเจ็กต์
  • เครื่องมือสร้างโค้ดสามารถใช้ประโยชน์จาก Aspect เพื่อดำเนินการกับอินพุตในลักษณะ ไม่ขึ้นอยู่กับเป้าหมาย ตัวอย่างเช่น ไฟล์ BUILD สามารถระบุลำดับชั้น ของ protobuf คำจำกัดความไลบรารี และกฎเฉพาะภาษาจะใช้ Aspect เพื่อแนบ การดำเนินการที่สร้างโค้ดสนับสนุน protobuf สำหรับภาษาหนึ่งๆ

ข้อมูลเบื้องต้นเกี่ยวกับ Aspect

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

กราฟบิลด์

รูปที่ 1 กราฟทรัพยากร Dependency ของไฟล์ BUILD

Bazel จะวิเคราะห์กราฟทรัพยากร Dependency นี้โดยการเรียกใช้ฟังก์ชันการใช้งานของ กฎที่เกี่ยวข้อง (ในกรณีนี้คือ "java_library") สำหรับทุก เป้าหมายในตัวอย่างข้างต้น ฟังก์ชันการใช้งานกฎจะสร้างการดำเนินการที่ สร้างอาร์ติแฟกต์ เช่น ไฟล์ .jar และส่งข้อมูล เช่น ตำแหน่ง และชื่อของอาร์ติแฟกต์เหล่านั้น ไปยังทรัพยากร Dependency แบบย้อนกลับของเป้าหมายเหล่านั้นใน ผู้ให้บริการ

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

ดังนั้น การดำเนินการเพียงครั้งเดียวในการใช้ Aspect A กับเป้าหมาย X จะสร้าง "กราฟเงา" ของกราฟทรัพยากร Dependency เดิมของเป้าหมายที่แสดงในรูปต่อไปนี้

สร้างกราฟด้วย Aspect

รูปที่ 2 กราฟการบิลด์ที่มี Aspect

ขอบที่ถูกเงาคือขอบตามแอตทริบิวต์ในชุดการเผยแพร่เท่านั้น ดังนั้นขอบ runtime_deps จึงไม่ถูกเงาในตัวอย่างนี้ จากนั้นระบบจะเรียกใช้ฟังก์ชันการใช้งาน Aspect กับโหนดทั้งหมดในกราฟเงาในลักษณะเดียวกับที่เรียกใช้การใช้งานกฎกับโหนดของกราฟเดิม

ตัวอย่างง่ายๆ

ตัวอย่างนี้แสดงวิธีพิมพ์ไฟล์ซอร์สแบบเรียกซ้ำสำหรับกฎและทรัพยากร Dependency ทั้งหมดที่มีแอตทริบิวต์ 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'],
    required_providers = [CcInfo],
)

เรามาแบ่งตัวอย่างออกเป็นส่วนๆ และดูแต่ละส่วนแยกกัน

คำจำกัดความของ Aspect

print_aspect = aspect(
    implementation = _print_aspect_impl,
    attr_aspects = ['deps'],
    required_providers = [CcInfo],
)

คำจำกัดความของ Aspect มีลักษณะคล้ายกับคำจำกัดความของกฎ และกำหนดโดยใช้ ฟังก์ชัน aspect

Aspect มีฟังก์ชันการใช้งานซึ่งในกรณีนี้คือ _print_aspect_impl เช่นเดียวกับกฎ

attr_aspects คือรายการแอตทริบิวต์กฎที่ Aspect เผยแพร่ ในกรณีนี้ Aspect จะเผยแพร่ตามแอตทริบิวต์ deps ของกฎที่ใช้

อาร์กิวเมนต์ทั่วไปอีกรายการหนึ่งสำหรับ attr_aspects คือ ['*'] ซึ่งจะเผยแพร่ Aspect ไปยังแอตทริบิวต์ทั้งหมดของกฎ

required_providers คือรายการผู้ให้บริการที่อนุญาตให้ Aspect จำกัดการเผยแพร่เฉพาะเป้าหมายที่กฎประกาศผู้ให้บริการที่จำเป็น โปรดดูรายละเอียดเพิ่มเติมในเอกสารประกอบของฟังก์ชัน Aspect ในกรณีนี้ Aspect จะใช้ได้เฉพาะกับเป้าหมายที่ประกาศผู้ให้บริการ CcInfo

การใช้งาน 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: เป้าหมายที่ใช้ Aspect
  • ctx: ออบเจ็กต์ ctx ที่ใช้เข้าถึงแอตทริบิวต์ และสร้างเอาต์พุตและการดำเนินการ

ฟังก์ชันการใช้งานสามารถเข้าถึงแอตทริบิวต์ของกฎเป้าหมายผ่าน ctx.rule.attr และตรวจสอบผู้ให้บริการที่เป้าหมายซึ่งใช้ฟังก์ชันการใช้งาน Aspect ให้ไว้ (ผ่านอาร์กิวเมนต์ target)

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 สาธารณะกำหนดพารามิเตอร์และมีประเภทเป็น bool, int หรือ string เท่านั้น สำหรับ Aspect ที่เผยแพร่โดยกฎ พารามิเตอร์ int และ string ต้องระบุ values ตัวอย่างนี้มีพารามิเตอร์ที่ชื่อ extension ซึ่งอนุญาตให้มีค่าเป็น '*', 'h' หรือ 'cc'

สำหรับ Aspect ที่เผยแพร่โดยกฎ ระบบจะนำค่าพารามิเตอร์มาจากกฎที่ขอ Aspect โดยใช้แอตทริบิวต์ของกฎที่มีชื่อและประเภทเดียวกัน (ดูคำจำกัดความของ file_count_rule)

สำหรับ Aspect บรรทัดคำสั่ง ระบบจะส่งค่าพารามิเตอร์โดยใช้ --aspects_parameters แฟล็ก ข้อจำกัด values ของพารามิเตอร์ int และ string อาจละเว้นได้

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

ในตัวอย่างนี้ 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 จากทรัพยากร Dependency ของเป้าหมายเพื่อสะสมจำนวนไฟล์แบบส่งผ่านทั้งหมด

การเรียกใช้ 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

ข้อมูลอ้างอิง