Khía cạnh

Báo cáo vấn đề Xem nguồn Nightly · 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Trang này trình bày thông tin cơ bản và lợi ích khi sử dụng nhiều khía cạnh và cung cấp các bước đơn giản cũng như nâng cao ví dụ.

Các khung hình cho phép tăng cường các biểu đồ phần phụ thuộc của bản dựng bằng thông tin bổ sung và hành động. Một số trường hợp điển hình khi các phương diện có thể hữu ích:

  • Các IDE có tích hợp Bazel có thể sử dụng các khía cạnh để thu thập thông tin về dự án.
  • Các công cụ tạo mã có thể tận dụng các khía cạnh để thực thi dữ liệu đầu vào của chúng trong không phân biệt mục tiêu. Ví dụ: tệp BUILD có thể chỉ định hệ phân cấp của các định nghĩa thư viện protobuf và các quy tắc dành riêng cho ngôn ngữ có thể sử dụng các khía cạnh để đính kèm các thao tác tạo mã hỗ trợ protobuf cho một ngôn ngữ cụ thể.

Kiến thức cơ bản về tỷ lệ khung hình

Các tệp BUILD cung cấp nội dung mô tả về mã nguồn của dự án: nguồn nào tệp là một phần của dự án, nên xây dựng từ cấu phần phần mềm nào (mục tiêu) những tệp đó, phần phụ thuộc giữa những tệp đó là gì, v.v. Bazel sử dụng thông tin này để thực hiện một quá trình tạo bản dựng, tức là nó chỉ ra tập hợp các hành động cần để tạo các cấu phần phần mềm (chẳng hạn như chạy trình biên dịch hoặc trình liên kết) và thực thi các hành động đó. Bazel thực hiện việc này bằng cách tạo một biểu đồ phần phụ thuộc giữa các mục tiêu và truy cập vào biểu đồ này để thu thập các hành động đó.

Hãy xem xét tệp BUILD sau:

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'], ...)

Tệp BUILD này xác định một biểu đồ phần phụ thuộc như trong hình sau:

Tạo biểu đồ

Hình 1. Biểu đồ phần phụ thuộc tệp BUILD.

Bazel phân tích biểu đồ phần phụ thuộc này bằng cách gọi một hàm triển khai của quy tắc tương ứng (trong trường hợp này là "java_library") cho mỗi mục tiêu trong ví dụ trên. Các hàm triển khai quy tắc tạo ra các hành động tạo cấu phần phần mềm, chẳng hạn như tệp .jar, và truyền thông tin, chẳng hạn như vị trí và tên của các cấu phần phần mềm đó, đến các phần phụ thuộc ngược của các mục tiêu đó trong nhà cung cấp.

Các khía cạnh tương tự với quy tắc ở chỗ chúng có chức năng triển khai tạo các thao tác và trả về trình cung cấp. Tuy nhiên, sức mạnh của chúng đến từ cách xây dựng biểu đồ phần phụ thuộc. Một khía cạnh có một phương thức triển khai và danh sách tất cả các thuộc tính mà khía cạnh đó truyền tải. Hãy cân nhắc một khía cạnh A truyền dọc theo các thuộc tính có tên là "deps". Có thể áp dụng khía cạnh này cho một mục tiêu X, tạo ra một nút ứng dụng khung hình A(X). Trong quá trình áp dụng, khía cạnh A được áp dụng đệ quy cho tất cả các mục tiêu mà X tham chiếu đến trong các "dep" của nó (tất cả các thuộc tính trong danh sách truyền của A).

Do đó, một hành động áp dụng phương diện A cho mục tiêu X sẽ tạo ra một "biểu đồ bóng" của biểu đồ phần phụ thuộc ban đầu của các mục tiêu như minh hoạ trong hình sau:

Tạo biểu đồ có Aspect

Hình 2. Xây dựng biểu đồ có các khía cạnh.

Các cạnh duy nhất bị đổ bóng là các cạnh dọc theo các thuộc tính trong tập hợp truyền lan, do đó cạnh runtime_deps không bị đổ bóng trong ví dụ: Sau đó, hàm triển khai khung hình được gọi trên tất cả các nút trong biểu đồ bóng tương tự như cách gọi phương thức triển khai quy tắc trên các nút của đồ thị ban đầu.

Ví dụ đơn giản

Ví dụ này minh hoạ cách in đệ quy các tệp nguồn cho một quy tắc và tất cả các phần phụ thuộc có thuộc tính deps. Trang này cho thấy cách triển khai một khía cạnh, định nghĩa một khía cạnh và cách gọi khía cạnh đó từ dòng lệnh 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'],
)

Hãy chia ví dụ này thành các phần và kiểm tra từng phần.

Định nghĩa về tỷ lệ khung hình

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

Các định nghĩa về khung hình tương tự như định nghĩa quy tắc và được xác định bằng cách sử dụng hàm aspect.

Giống như quy tắc, một khía cạnh có một hàm triển khai, trong trường hợp này là _print_aspect_impl.

attr_aspects là danh sách các thuộc tính quy tắc mà khung hình sẽ được phổ biến theo đó. Trong trường hợp này, phương diện sẽ được truyền dọc theo thuộc tính deps của các quy tắc mà phương diện đó được áp dụng.

Một đối số phổ biến khác cho attr_aspects['*']. Đối số này sẽ truyền tải khía cạnh đến tất cả các thuộc tính của một quy tắc.

Cách triển khai 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 []

Các hàm triển khai Aspect tương tự như triển khai quy tắc . Các hàm này trả về nhà cung cấp, có thể tạo hành động và nhận hai đối số:

  • target: Mục tiêu mà phương diện đang được áp dụng.
  • ctx: Đối tượng ctx có thể được dùng để truy cập vào các thuộc tính, đồng thời tạo ra đầu ra và hành động.

Hàm triển khai có thể truy cập vào các thuộc tính của quy tắc mục tiêu thông qua ctx.rule.attr. Phương thức này có thể kiểm tra các trình cung cấp do mục tiêu cung cấp (thông qua đối số target).

Cần có các phương diện để trả về danh sách nhà cung cấp. Trong ví dụ này, phương diện không cung cấp bất kỳ giá trị nào, do đó sẽ trả về một danh sách trống.

Gọi khung hình bằng dòng lệnh

Cách đơn giản nhất để áp dụng một khía cạnh là từ dòng lệnh bằng cách sử dụng đối số --aspects. Giả sử khía cạnh ở trên đã được xác định trong một tệp có tên print.bzl sau:

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

sẽ áp dụng print_aspect cho example mục tiêu và tất cả các quy tắc mục tiêu có thể truy cập đệ quy thông qua thuộc tính deps.

Cờ --aspects nhận một đối số, là thông số kỹ thuật của tỷ lệ khung hình ở định dạng <extension file label>%<aspect top-level name>.

Ví dụ nâng cao

Ví dụ sau đây minh hoạ việc sử dụng một khía cạnh từ quy tắc mục tiêu để đếm các tệp trong mục tiêu, có thể lọc các tệp đó theo đuôi tệp. Hướng dẫn này cho biết cách sử dụng trình cung cấp để trả về giá trị, cách sử dụng tham số để chuyển một đối số vào phương thức triển khai khung hình và cách gọi một khía cạnh từ quy tắc.

Tệp 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 = '*'),
    },
)

Tệp 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',
)

Định nghĩa khung hình

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

Ví dụ này cho thấy cách truyền tải một khung hình thông qua thuộc tính deps.

attrs xác định một tập hợp các thuộc tính của một khung hình. Thuộc tính khung hình công khai định nghĩa tham số và chỉ có thể thuộc kiểu bool, int hoặc string. Đối với các khía cạnh được truyền tải theo quy tắc, tham số intstring phải có values được chỉ định trên chúng. Ví dụ này có một tham số có tên là extension được phép có giá trị là "*", "h" hoặc "cc".

Đối với các khía cạnh được truyền theo quy tắc, giá trị tham số được lấy từ quy tắc yêu cầu khía cạnh, sử dụng thuộc tính của quy tắc có cùng tên và loại. (xem định nghĩa của file_count_rule).

Đối với các khía cạnh dòng lệnh, bạn có thể truyền giá trị tham số bằng cờ --aspects_parameters. Bạn có thể bỏ qua quy tắc hạn chế values của các tham số intstring.

Các khía cạnh cũng được phép có các thuộc tính riêng tư thuộc loại label hoặc label_list. Bạn có thể dùng thuộc tính nhãn riêng tư để chỉ định các phần phụ thuộc trên các công cụ hoặc thư viện cần thiết cho các thao tác do các khía cạnh tạo ra. Không có thuộc tính riêng tư được xác định trong ví dụ này, nhưng đoạn mã sau đây minh hoạ cách bạn có thể chuyển một công cụ đến một khía cạnh:

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

Triển khai tỷ lệ khung hình

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

Giống như hàm triển khai quy tắc, hàm triển khai khía cạnh sẽ trả về một cấu trúc gồm các trình cung cấp có thể truy cập được đối với các phần phụ thuộc của nó.

Trong ví dụ này, FileCountInfo được định nghĩa là một trình cung cấp có một trường count. Tốt nhất là bạn nên xác định rõ các trường của bằng cách sử dụng thuộc tính fields.

Tập hợp các nhà cung cấp cho một ứng dụng khía cạnh A(X) là tập hợp hợp nhất các nhà cung cấp đến từ việc triển khai một quy tắc cho mục tiêu X và từ việc triển khai khía cạnh A. Các nhà cung cấp mà quá trình triển khai quy tắc sẽ truyền tải được tạo và đóng băng trước khi các khía cạnh được áp dụng và không thể sửa đổi từ một khía cạnh. Sẽ là lỗi nếu một mục tiêu và một khía cạnh được áp dụng cho mỗi mục tiêu và một khía cạnh cung cấp tên nhà cung cấp cùng loại, với ngoại lệ là OutputGroupInfo (được hợp nhất, miễn là quy tắc và khía cạnh xác định các nhóm đầu ra khác nhau) và InstrumentedFilesInfo (được lấy từ khung hình). Điều này có nghĩa là các phương thức triển khai khía cạnh có thể không bao giờ trả về DefaultInfo.

Các tham số và thuộc tính riêng tư được chuyển vào các thuộc tính của ctx. Ví dụ này tham chiếu đến tham số extension và xác định tệp nào cần được tính.

Đối với các nhà cung cấp trả về, giá trị của các thuộc tính mà theo đó phương diện được truyền (từ danh sách attr_aspects) sẽ được thay thế bằng kết quả của việc áp dụng phương diện cho các nhà cung cấp đó. Ví dụ: nếu mục tiêu X có Y và Z trong phần phụ thuộc, thì ctx.rule.attr.deps cho A(X) sẽ là [A(Y), A(Z)]. Trong ví dụ này, ctx.rule.attr.deps là các đối tượng Mục tiêu là kết quả của việc áp dụng phương diện cho "phần phụ thuộc" của mục tiêu ban đầu mà phương diện đã được áp dụng.

Trong ví dụ này, thành phần này truy cập vào trình cung cấp FileCountInfo từ để tích luỹ tổng số tệp bắc cầu.

Gọi phương diện từ một quy tắc

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 = '*'),
    },
)

Quá trình triển khai quy tắc minh hoạ cách truy cập vào FileCountInfo thông qua ctx.attr.deps.

Định nghĩa quy tắc minh hoạ cách xác định một thông số (extension) và cung cấp giá trị mặc định (*). Lưu ý rằng việc có giá trị mặc định không phải là một trong các giá trị 'cc', 'h' hoặc '*' sẽ là lỗi do các hạn chế được đặt đối với tham số trong định nghĩa khung hình.

Gọi một khía cạnh thông qua quy tắc mục tiêu

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

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

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

Ví dụ này minh hoạ cách truyền tham số extension vào khung hình thông qua quy tắc. Vì tham số extension có giá trị mặc định trong quá trình triển khai quy tắc, nên extension sẽ được coi là tham số không bắt buộc.

Khi mục tiêu file_count được tạo, khía cạnh của chúng ta sẽ được đánh giá để và tất cả các mục tiêu có thể truy cập theo cách đệ quy thông qua deps.

Tài liệu tham khảo