Khía cạnh

Trang này giải thích những điều cơ bản và lợi ích của việc sử dụng aspect và cung cấp các ví dụ đơn giản và nâng cao ví dụ.

Aspect cho phép tăng cường biểu đồ phần phụ thuộc bản dựng bằng thông tin và hành động bổ sung. Sau đây là một số trường hợp thường gặp mà aspect có thể hữu ích:

  • IDE tích hợp Bazel có thể sử dụng aspect để thu thập thông tin về dự án.
  • Các công cụ tạo mã có thể tận dụng aspect để thực thi trên dữ liệu đầu vào theo cách không phụ thuộc vào mục tiêu. Ví dụ: tệp BUILD có thể chỉ định hệ phân cấp 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 aspect để đính kèm các hành động tạo mã hỗ trợ protobuf cho một ngôn ngữ cụ thể.

Thông tin cơ bản về aspect

Tệp BUILD cung cấp nội dung mô tả về mã nguồn của dự án: những tệp nguồn nào thuộc dự án, những cấu phần phần mềm (mục tiêu) nào cần được tạo từ các tệp đó, các phần phụ thuộc giữa các tệp đó là gì, v.v. Bazel sử dụng thông tin này để thực hiện bản dựng, tức là xác định tập hợp các hành động cần thiết để tạo 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 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 biểu đồ phần phụ thuộc được minh hoạ trong hình sau:

Tạo biểu đồ

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

Bazel phân tích biểu đồ phần phụ thuộc này bằng cách gọi 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 xây dựng cấu phần phần mềm, chẳng hạn như tệp .jar và chuyể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.

Aspect tương tự như quy tắc ở chỗ chúng có một hàm triển khai tạo ra các hành động và trả về 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 cho chúng. Aspect có một quy trình triển khai và danh sách tất cả các thuộc tính mà nó truyền bá. Hãy xem xét aspect A truyền bá dọc theo các thuộc tính có tên là "deps". Aspect này có thể được áp dụng cho mục tiêu X, tạo ra nút ứng dụng aspect A(X). Trong quá trình áp dụng, aspect A được áp dụng đệ quy cho tất cả các mục tiêu mà X tham chiếu đến trong thuộc tính "deps" (tất cả các thuộc tính trong danh sách truyền bá của A).

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

Tạo biểu đồ bằng khía cạnh

Hình 2. Biểu đồ bản dựng có aspect.

Chỉ các cạnh dọc theo các thuộc tính trong tập hợp truyền bá mới bị che khuất, do đó, cạnh runtime_deps không bị che khuất trong ví dụ này. Sau đó, hàm triển khai aspect được gọi trên tất cả các nút trong biểu đồ bóng tương tự như cách các quy trình triển khai quy tắc được gọi trên các nút của biểu đồ 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ủa quy tắc đó có thuộc tính deps. Ví dụ này cho thấy quy trình triển khai aspect, định nghĩa aspect và cách gọi aspect 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à xem xét từng phần riêng lẻ.

Định nghĩa aspect

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

Định nghĩa aspect tương tự như định nghĩa quy tắc và được xác định bằng hàm aspect.

Giống như quy tắc, aspect 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à aspect truyền bá dọc theo. Trong trường hợp này, aspect sẽ truyền bá dọc theo thuộc tính deps của các quy tắc mà nó được áp dụng.

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

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ư các hàm triển khai quy tắc. Chúng trả về nhà cung cấp, có thể tạo hành động và nhận 2 đối số:

  • target: mục tiêu mà aspect đang được áp dụng.
  • ctx: đối tượng ctx có thể dùng để truy cập vào các thuộc tính và tạo đầ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. Hàm này có thể kiểm tra các nhà cung cấp do mục tiêu mà nó được áp dụng cung cấp (thông qua đối số target).

Aspect phải trả về danh sách nhà cung cấp. Trong ví dụ này, aspect không cung cấp gì, vì vậy, nó trả về danh sách trống.

Gọi aspect bằng dòng lệnh

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

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

sẽ áp dụng print_aspect cho mục tiêu example 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 aspect ở đị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 aspect từ mộ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 phần mở rộng. Ví dụ này cho thấy cách sử dụng nhà cung cấp để trả về giá trị, cách sử dụng tham số để truyền đối số vào quy trình triển khai aspect và cách gọi aspect từ mộ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 aspect

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 aspect truyền bá thông qua thuộc tính deps.

attrs xác định một tập hợp các thuộc tính cho aspect. Các thuộc tính aspect công khai xác định tham số và chỉ có thể thuộc loại bool, int hoặc string. Đối với các aspect được truyền bá 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ố tên là extension được phép có giá trị là '*', 'h' hoặc 'cc'.

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

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

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

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

Triển khai 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)]

Giống như hàm triển khai quy tắc, hàm triển khai aspect trả về một cấu trúc của các nhà cung cấp mà các phần phụ thuộc của nó có thể truy cập.

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

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

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

Đối với việc trả về nhà cung cấp, các giá trị của thuộc tính mà aspect được truyền bá dọc theo (từ danh sách attr_aspects) sẽ được thay thế bằng kết quả của việc áp dụng aspect cho chúng. Ví dụ: nếu mục tiêu X có Y và Z trong các 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 aspect cho "các phần phụ thuộc" của mục tiêu ban đầu mà aspect đã được áp dụng.

Trong ví dụ này, aspect truy cập vào nhà cung cấp FileCountInfo từ các phần phụ thuộc của mục tiêu để tích luỹ tổng số tệp bắc cầu.

Gọi aspect 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 = '*'),
    },
)

Quy 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 tham số (extension) và gán giá trị mặc định (*) cho tham số đó. Xin 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ẽ gây ra lỗi do các hạn chế được áp dụng cho tham số trong định nghĩa aspect.

Gọi aspect 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 aspect thông qua quy tắc. Vì tham số extension có giá trị mặc định trong quy 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 xây dựng, aspect của chúng ta sẽ được đánh giá cho chính nó và tất cả các mục tiêu có thể truy cập đệ quy thông qua deps.

Tài liệu tham khảo