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 các khía cạnh, đồng thời cung cấp các ví dụ đơn giản và nâng cao.
Các khía cạnh cho phép tăng cường biểu đồ phần phụ thuộc của bản dựng bằng thông tin và thao tác bổ sung. Một số trường hợp điển hình khi các khía cạnh có thể hữu ích:
- Các IDE 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 trên dữ liệu đầu vào theo cách không phụ thuộc vào mục tiêu. Ví dụ: các tệp
BUILD
có thể chỉ định một hệ thống phân cấp gồm các định nghĩa về 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ề khía cạnh
Tệp BUILD
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 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ừ những tệp đó, các 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 bản dựng, tức là Bazel sẽ tìm ra tập hợp các thao tác 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 thao tác đó. 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 những 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ư minh hoạ 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 những thao tác tạo cấu phần phần mềm, chẳng hạn như các tệp .jar
và truyền thông tin, chẳng hạn như vị trí và tên của những cấu phần phần mềm đó, đến các phần phụ thuộc đảo ngược của những mục tiêu đó trong trình cung cấp.
Các khía cạnh tương tự như các 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ề các trình cung cấp. Tuy nhiên, sức mạnh của chúng đến từ cách biểu đồ phần phụ thuộc được tạo cho chúng. Khía cạnh có một quá trình 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 xem xét một khía cạnh A truyền tải 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ột 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 đệ quy cho tất cả các mục tiêu mà X đề cập đến trong thuộc tính "deps" (tất cả các thuộc tính trong danh sách truyền tin của A).
Do đó, một hành động duy nhất là á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 xuất hiện trong hình sau:
Hình 2. Tạo 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 lan truyền, do đó, cạnh runtime_deps
không bị đổ bóng trong ví dụ này. Sau đó, một hàm triển khai khía cạnh sẽ đượ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 hoạt động 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 cách 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ụ này thành các phần và xem xét từng phần riêng lẻ.
Định nghĩa về 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, khía cạnh có một hàm triển khai. Trong trường hợp này, hàm đó là _print_aspect_impl
.
attr_aspects
là danh sách các thuộc tính quy tắc mà khía cạnh lan truyền theo.
Trong trường hợp này, khía cạnh sẽ lan truyền theo thuộc tính deps
của các quy tắc mà khía cạnh đó được áp dụng.
Một đối số phổ biến khác cho attr_aspects
là ['*']
. Đối số này sẽ truyền 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 khía cạnh 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à khía cạnh đang được áp dụng.ctx
: Đối tượngctx
có thể dùng để truy cập vào các thuộc tính, cũng như tạo ra các đầ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 đích thông qua ctx.rule.attr
. Bạn có thể kiểm tra những nhà cung cấp do mục tiêu mà bạn áp dụng (thông qua đối số target
) cung cấp.
Các khía cạnh bắt buộc phải trả về danh sách nhà cung cấp. Trong ví dụ này, khía cạnh không cung cấp thông tin nào, nên sẽ 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 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 là print.bzl
như sau:
bazel build //MyExample:example --aspects print.bzl%print_aspect
sẽ áp dụng print_aspect
cho example
đích và tất cả các quy tắc đích 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à quy cách về khía cạ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 tiện ích. Ví dụ này cho thấy cách sử dụng một trình cung cấp để trả về các giá trị, cách sử dụng các tham số để truyền một đối số vào quá trình triển khai khía cạnh và cách gọi một khía cạnh 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 về 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 khía cạnh lan truyền 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. Các thuộc tính khía cạnh công khai xác định các tham số và chỉ có thể thuộc các loại bool
, int
hoặc string
.
Đối với các khía cạnh được truyền theo quy tắc, các tham số int
và string
phải có values
được chỉ định trên các tham số đó. Ví dụ này có một tham số tên là extension
. Tham số này có thể có giá trị là "*
", "h
" hoặc "cc
".
Đối với các khía cạnh được truyền 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, bằng cách 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 khía cạnh dòng lệnh, bạn có thể truyền các giá trị tham số bằng cách sử dụng cờ --aspects_parameters
. Bạn có thể bỏ qua hạn chế values
của các tham số int
và string
.
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ể sử dụng các thuộc tính nhãn riêng tư để chỉ định các phần phụ thuộc vào những 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ư 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"
)
}
...
Cách 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 khía cạnh trả về một cấu trúc của các trì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à một 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 một trình cung cấp 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 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à một quy tắc triển khai truyền tải được tạo và cố định 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 đó cung cấp cho nhà cung cấp cùng một 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 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 truyề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 những tệp cần đếm.
Đối với các nhà cung cấp trả về, 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 một ứng dụng của khía cạnh cho các thuộc tính đó. Ví dụ: nếu đích X có Y và Z trong deps của nó, 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 Target (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 này 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 bắc cầu.
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 = '*'),
},
)
Việc triển khai quy tắc này 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à một 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 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',
)
Điều 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 quá trình 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 tạo, 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
.