สัดส่วนภาพ

รายงานปัญหา ดูแหล่งที่มา

หน้านี้อธิบายพื้นฐานและประโยชน์ของการใช้แง่มุม พร้อมให้ตัวอย่างแบบง่ายและขั้นสูง

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

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

ข้อมูลพื้นฐานเกี่ยวกับสัดส่วนภาพ

ไฟล์ BUILD ให้คำอธิบายซอร์สโค้ดของโปรเจ็กต์ เช่น ไฟล์ต้นฉบับที่เป็นส่วนหนึ่งของโปรเจ็กต์ อาร์ติแฟกต์ (เป้าหมาย) ที่ควรสร้างจากไฟล์เหล่านี้ ทรัพยากร Dependency ระหว่างไฟล์เหล่านั้นคืออะไร และ Bazel ใช้ข้อมูลนี้เพื่อสร้างบิลด์ ซึ่งเป็นการคำนวณชุดการดำเนินการที่จำเป็นในการสร้างอาร์ติแฟกต์ (เช่น การเรียกใช้คอมไพเลอร์หรือ Linker) และดำเนินการกับการดำเนินการเหล่านั้น Bazel บรรลุเป้าหมายนี้โดยการสร้างกราฟการอ้างอิงระหว่างเป้าหมายต่างๆ และไปที่กราฟนี้เพื่อรวบรวมการดำเนินการเหล่านั้น

พิจารณาไฟล์ 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 กราฟทรัพยากร Dependency ของไฟล์ BUILD

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

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

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

สร้างกราฟด้วยมุมมอง

รูปที่ 2 สร้างกราฟพร้อมมุมมองต่างๆ

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

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

ตัวอย่างนี้แสดงวิธีพิมพ์ไฟล์ต้นฉบับสำหรับกฎและทรัพยากร Dependency ทั้งหมดที่มีแอตทริบิวต์ deps ซ้ำ โดยจะแสดงการใช้งานด้านต่างๆ การกำหนดลักษณะ และวิธีเรียกใช้องค์ประกอบจากบรรทัดคำสั่ง 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'],
)

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

คำจำกัดความ

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

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

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

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

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

การใช้มุมมอง

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 []

ฟังก์ชันการใช้งานส่วนต่างๆ จะคล้ายกับฟังก์ชันการใช้งานกฎ โดยจะแสดงผล providers สามารถสร้างactions และใช้อาร์กิวเมนต์ 2 รายการ ได้แก่

  • target: เป้าหมายที่มีการนำไปใช้
  • ctx: ออบเจ็กต์ ctx ที่ใช้เข้าถึงแอตทริบิวต์รวมถึงสร้างเอาต์พุตและการดำเนินการได้

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

ต้องมีลักษณะเพื่อแสดงรายการผู้ให้บริการ ในตัวอย่างนี้ ลักษณะไม่ได้ระบุข้อมูลใดๆ ดังนั้นจะแสดงรายการที่ว่างเปล่า

เรียกใช้ด้านดังกล่าวโดยใช้บรรทัดคำสั่ง

วิธีที่ง่ายที่สุดในการนำส่วนมาใช้คือการใช้อาร์กิวเมนต์ --aspects โดยใช้บรรทัดคำสั่ง สมมติว่าลักษณะข้างต้นได้กำหนดไว้ในไฟล์ชื่อ print.bzl ดังนี้

bazel build //MyExample:example --aspects print.bzl%print_aspect

จะใช้ print_aspect กับเป้าหมาย example และกฎเป้าหมายทั้งหมดที่เข้าถึงได้ซ้ำผ่านแอตทริบิวต์ deps

แฟล็ก --aspects จะมีอาร์กิวเมนต์ 1 รายการ ซึ่งเป็นข้อมูลจำเพาะของด้านต่างๆ ในรูปแบบ <extension file label>%<aspect top-level name>

ตัวอย่างขั้นสูง

ตัวอย่างต่อไปนี้แสดงการใช้องค์ประกอบจากกฎเป้าหมายที่นับไฟล์ในเป้าหมาย ซึ่งอาจกรองไฟล์ตามส่วนขยาย โดยจะแสดงวิธีใช้ผู้ให้บริการเพื่อแสดงผลค่า วิธีใช้พารามิเตอร์เพื่อส่งอาร์กิวเมนต์ไปยังการใช้งานด้านต่างๆ และวิธีเรียกใช้องค์ประกอบจากกฎ

ไฟล์ 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',
)

คำจำกัดความ

file_count_aspect = aspect(
    implementation = _file_count_aspect_impl,
    attr_aspects = ['deps'],
    attrs = {
        'extension' : attr.string(values = ['*', 'h', 'cc']),
    }
)

ตัวอย่างนี้แสดงลักษณะการเผยแพร่ผ่านแอตทริบิวต์ deps

attrs กำหนดชุดแอตทริบิวต์สำหรับลักษณะหนึ่งๆ แอตทริบิวต์ด้านสาธารณะ จะกำหนดพารามิเตอร์ และต้องเป็นประเภท bool, int หรือ string เท่านั้น สำหรับแง่มุมที่ปรับใช้กฎ พารามิเตอร์ int และ string จะต้องระบุ values ตัวอย่างนี้มีพารามิเตอร์ชื่อ extension ที่ได้รับอนุญาตให้ใช้ "*", "h" หรือ "cc" เป็นค่า

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

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

แง่มุมยังมีแอตทริบิวต์ส่วนตัวประเภท label หรือ label_list ได้ด้วย แอตทริบิวต์ป้ายกำกับส่วนตัวใช้เพื่อระบุทรัพยากร Dependency ของเครื่องมือหรือไลบรารีที่จำเป็นสำหรับการดำเนินการที่สร้างโดยองค์ประกอบ ตัวอย่างนี้ไม่มีแอตทริบิวต์ส่วนตัวที่กำหนดไว้ในตัวอย่างนี้ แต่ข้อมูลโค้ดต่อไปนี้จะแสดงให้เห็นว่าคุณจะส่งผ่านเครื่องมือไปยังมุมมองหนึ่งๆ ได้อย่างไร

...
    attrs = {
        '_protoc' : attr.label(
            default = Label('//tools:protoc'),
            executable = True,
            cfg = "exec"
        )
    }
...

การใช้มุมมอง

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)]

เช่นเดียวกับฟังก์ชันการใช้งานกฎ ฟังก์ชันการใช้งานด้านการแสดงผลจะแสดงผลโครงสร้างของผู้ให้บริการที่ทรัพยากร Dependency เข้าถึงได้

ในตัวอย่างนี้ FileCountInfo คือผู้ให้บริการที่มีช่อง count เพียงช่องเดียว แนวทางปฏิบัติแนะนำคือการกำหนดช่องของผู้ให้บริการอย่างชัดเจนโดยใช้แอตทริบิวต์ fields

กลุ่มของผู้ให้บริการสำหรับแอปพลิเคชัน A(X) คือการรวมผู้ให้บริการที่มาจากการติดตั้งใช้งานกฎสำหรับเป้าหมาย X และจากการใช้งานด้าน A ระบบจะสร้างและตรึงผู้ให้บริการที่ใช้กฎการเผยแพร่และตรึงก่อนที่จะนำแง่มุมมาใช้ และจะแก้ไขจากแง่มุมหนึ่งไม่ได้ ระบบจะแสดงข้อผิดพลาดหากเป้าหมายและส่วนที่ใช้กับแต่ละเป้าหมายระบุผู้ให้บริการที่มีประเภทเดียวกัน โดยมีข้อยกเว้นคือ OutputGroupInfo (ซึ่งรวมกัน ตราบใดที่กฎและแง่มุมระบุกลุ่มเอาต์พุตที่ต่างกัน) และ InstrumentedFilesInfo (ซึ่งดึงมาจากแง่มุม) ซึ่งหมายความว่าการติดตั้งใช้งานลักษณะต่างๆ อาจไม่แสดงผล DefaultInfo

ระบบจะส่งพารามิเตอร์และแอตทริบิวต์ส่วนตัวไปในแอตทริบิวต์ของ ctx ตัวอย่างนี้อ้างอิงพารามิเตอร์ extension และกําหนดไฟล์ที่จะนับ

สำหรับผู้ให้บริการที่กลับมา ค่าของแอตทริบิวต์ที่มีการเผยแพร่ด้าน (จากรายการ attr_aspects) จะถูกแทนที่ด้วยผลลัพธ์ของการใช้มุมมองดังกล่าว เช่น หากเป้าหมาย X มี Y และ Z อยู่ใน Dep แล้ว ctx.rule.attr.deps ของ A(X) จะเป็น [A(Y), A(Z)] ในตัวอย่างนี้ ctx.rule.attr.deps คือออบเจ็กต์เป้าหมายที่เป็นผลลัพธ์ของการนำลักษณะไปใช้กับ "ระดับลง" ของเป้าหมายเดิมที่มีการนำไปใช้

ในตัวอย่างนี้ มุมมองเข้าถึงผู้ให้บริการ FileCountInfo จากทรัพยากร Dependency ของเป้าหมายเพื่อสะสมจำนวนไฟล์ทางอ้อมทั้งหมด

การเรียกใช้องค์ประกอบจากกฎ

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" หรือ "*" จะเป็นข้อผิดพลาดเนื่องจากมีข้อจำกัดเกี่ยวกับพารามิเตอร์ในคำจำกัดความด้านต่างๆ

การเรียกใช้องค์ประกอบผ่านกฎเป้าหมาย

load('//:file_count.bzl', 'file_count_rule')

cc_binary(
    name = 'app',
...
)

file_count_rule(
    name = 'file_count',
    deps = ['app'],
    extension = 'h',
)

ตัวอย่างวิธีส่งพารามิเตอร์ extension ไปยังองค์ประกอบผ่านกฎ เนื่องจากพารามิเตอร์ extension มีค่าเริ่มต้นในการใช้งานกฎ จึงถือว่า extension เป็นพารามิเตอร์ที่ไม่บังคับ

เมื่อสร้างเป้าหมาย file_count แล้ว ระบบจะประเมินแง่มุมของเราเองและเป้าหมายทั้งหมดที่เข้าถึงได้ซ้ำผ่าน deps

รายการอ้างอิง