สัดส่วนภาพ

หน้านี้จะอธิบายข้อมูลเบื้องต้นและประโยชน์ของการใช้ 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)

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

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

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