สัดส่วนภาพ

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

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