สัดส่วนภาพ

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

Aspect ช่วยเพิ่มกราฟทรัพยากร Dependency ของการบิลด์ด้วยข้อมูลและการดำเนินการเพิ่มเติม ตัวอย่างสถานการณ์ทั่วไปที่ 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 นี้กำหนดกราฟทรัพยากร 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'],
)

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

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

ฟังก์ชันการใช้งานสามารถเข้าถึงแอตทริบิวต์ของกฎเป้าหมายผ่าน ctx.rule.attr และตรวจสอบผู้ให้บริการที่เป้าหมายซึ่งใช้ฟังก์ชันการใช้งานนั้นให้ไว้ (ผ่านอาร์กิวเมนต์ 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 แบบสาธารณะมีประเภท string และเรียกว่าพารามิเตอร์ พารามิเตอร์ต้องมีแอตทริบิวต์ values ที่ระบุไว้ ตัวอย่างนี้มีพารามิเตอร์ที่ชื่อ extension ซึ่งอนุญาตให้มีค่าเป็น '*', 'h' หรือ 'cc'

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

นอกจากนี้ 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