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 khía cạnh và cung cấp các ví dụ đơn giản và nâng cao ví dụ.
Khía cạnh cho phép tăng cường biểu đồ phần phụ thuộc bản dựng bằng thông tin bổ sung và hành động. Sau đây là một số trường hợp điển hình mà khía cạnh có thể hữu ích:
- Các IDE tích hợp Bazel có thể sử dụng 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 khía cạnh để 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
BUILDcó thể chỉ định một hệ phân cấp các định nghĩaprotobufprotobuf và các quy tắc dành riêng cho ngôn ngữ có thể sử dụng khía cạnh để đí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ề khía cạnh
BUILD tệp cung cấp nội dung mô tả về mã nguồn của một dự án: những tệp nguồn nào là một phần của dự án, những cấu phần phần mềm (mục tiêu) nào cần được xây dựng 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 ra 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 phần phụ thuộc
biểu đồ 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 được hiển thị trong hình sau:

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 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
xây dựng 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
trình cung cấp.
Khía cạnh 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ề 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 cho chúng. Một khía cạnh có một nội dung triển khai và một danh sách tất cả các thuộc tính mà nó truyền bá. Hãy xem xét một khía cạnh A mà truyền bá dọc theo các thuộc tính có tên là "deps". Khía cạnh này có thể được áp dụng cho mục tiêu X, tạo ra một nút ứng dụng khía cạnh A(X). Trong quá trình áp dụng, khía cạnh A được áp dụng một cách đệ 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 áp dụng khía cạnh 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 được hiển thị trong hình sau:

Hình 2. Xây dựng biểu đồ với các khía cạnh.
Các cạnh duy nhất bị che khuất là các cạnh dọc theo các thuộc tính trong
tập hợp truyền bá, do đó, cạnh runtime_deps không bị che khuất trong ví dụ này. Sau đó, một hàm triển khai khía cạnh đượ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 nội dung 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
một nội dung triển khai khía cạnh, một định nghĩa 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ụ thành các phần và kiểm tra từng phần riêng lẻ.
Định nghĩa khía cạnh
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
Định nghĩa khía cạnh tương tự như định nghĩa quy tắc và được xác định bằng
hàm aspect.
Giống như một 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à khía cạnh truyền bá.
Trong trường hợp này, khía cạnh 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 là ['*'] sẽ truyền bá
khía cạnh đến tất cả các thuộc tính của một quy tắc.
Triển khai khía cạnh
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 khía cạnh tương tự như các hàm triển khai quy tắc. Chúng trả về trình cung cấp, có thể tạo hành động và nhận hai đối số:
target: mục tiêu mà khía cạnh đang được áp dụng.ctx: đối tượngctxcó 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 trình cung cấp do mục tiêu mà nó được áp dụng cung cấp (thông qua đối số target).
Các khía cạnh phải trả về danh sách trình cung cấp. Trong ví dụ này, khía cạnh không cung cấp gì, vì vậy, nó trả về một danh sách trống.
Gọi khía cạ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 đối số
--aspects. Giả sử khía cạnh ở trên được xác định trong một tệp có tên là print.bzl
thế này:
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 một cách đệ 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 khía cạnh
ở định dạng <extension file label>%<aspect top-level name>.
Ví dụ nâng cao
Ví dụ sau minh hoạ cách sử dụng một khía cạnh từ một quy tắc mục tiêu đếm các tệp trong mục tiêu, có khả năng lọc các tệp đó theo phần mở rộng. Ví dụ này cho thấy cách sử dụng trình cung cấp để trả về các giá trị, cách sử dụng tham số để truyền đối số vào nội dung triển khai khía cạnh và cách gọi khía cạnh từ một quy tắc.
file_count.bzl tệp:
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 khía cạ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 khía cạnh 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 một khía cạnh. Thuộc tính khía cạnh công khai
xác định các tham số và chỉ có thể thuộc loại bool, int hoặc string.
Đối với các khía cạnh được truyền bá theo quy tắc, các tham số int và string phải có
values được chỉ định trên đó. Ví dụ này có một tham số có tên là extension
được phép có '*', 'h' hoặc 'cc' làm giá trị.
Đối với các khía cạnh được truyền bá theo quy tắc, cá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, các 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 các tham số int và string có thể bị
bỏ qua.
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. Thuộc tính nhãn riêng tư có thể được dù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 khía cạnh tạo ra. Không có
thuộc tính riêng tư 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 một khía cạnh:
...
attrs = {
'_protoc' : attr.label(
default = Label('//tools:protoc'),
executable = True,
cfg = "exec"
)
}
...
Triển khai khía cạ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 trả về một cấu trúc của các trình cung cấp có thể truy cập vào các phần phụ thuộc của nó.
Trong ví dụ này, FileCountInfo được xác định là một trì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
trình cung cấp bằng thuộc tính fields.
Tập hợp các trình cung cấp cho một ứng dụng khía cạnh A(X) là hợp nhất các trì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 trình cung cấp mà một nội dung triển khai quy tắc truyền bá
đượ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. Đây là lỗi nếu một mục tiêu và một khía cạnh được áp dụng cho mục tiêu đó, mỗi mục tiêu
cung cấp một trì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à khía cạnh chỉ định các nhóm đầu ra khác nhau) và
InstrumentedFilesInfo
(được lấy từ khía cạnh). Điều này có nghĩa là các nội dung 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 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ề trình cung cấp, các giá trị của thuộc tính mà
khía cạnh được truyền bá (từ danh sách attr_aspects) sẽ được thay thế bằng
kết quả của việc áp dụng khía cạnh cho các thuộc tính đó. 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 khía cạnh cho 'deps' của mục tiêu ban đầu mà khía cạnh đã được áp dụng.
Trong ví dụ này, khía cạnh truy cập vào trì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 chuyển tiếp.
Gọi khía cạnh 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 = '*'),
},
)
Nội dung 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 tham số (extension)
và gán cho tham số đó một giá trị mặc định (*). Xin lưu ý rằng việc có một 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 trên tham số trong định nghĩa khía cạnh.
Gọi một khía cạnh thông qua một 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 khía cạnh
thông qua quy tắc. Vì tham số extension có giá trị mặc định trong nội dung triển khai quy tắc, nên extension sẽ được coi là một tham số không bắt buộc.
Khi mục tiêu file_count được xây dựng, khía cạnh 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 một cách đệ quy thông qua deps.