สัดส่วนภาพ

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

ในตัวอย่างนี้ 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