Trang này trình bày thông tin cơ bản và lợi ích khi sử dụng các khía cạnh, đơn giản và 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. Sau đây là một số trường hợp điển hình trong đó 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 dữ liệu đầu vào trong
không phân biệt mục tiêu. Ví dụ: các tệp
BUILD
có thể chỉ định một hệ phân cấp của thư viện protobuf định nghĩa và 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 để tạo mã hỗ trợ protobuf cho một ngôn ngữ cụ thể.
Thông tin cơ bản về Aspect
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à 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 hoàn thành điều này bằng cách xây dựng 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 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ư trong hình sau:
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 đó, thành 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ó triển khai và danh sách tất cả thuộc tính mà đoạn mã đó được lan truyền cùng. 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 đó, chỉ một hành động áp dụng khía cạnh A cho một mục tiêu X tạo ra "biểu đồ bóng" trong số biểu đồ phần phụ thuộc ban đầu của các mục tiêu được minh hoạ trong hình sau:
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
và tất cả các phần phụ thuộc của quy tắc đó có thuộc tính deps
. Chiến dịch này cho thấy
phương thức triển khai khung hình, định nghĩa khung hình và cách gọi khung hì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 nhỏ ví dụ này thành nhiều phần rồi kiểm tra riêng từng phần.
Định nghĩa 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ó hàm triển khai mà 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, khung hình sẽ được lan truyền dọc theo thuộc tính deps
của
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 phương thức
vào tất cả các thuộc tính của 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 tham số này trả về nhà cung cấp, có thể tạo hành động và dùng 2 đối số:
target
: mục tiêu mà khung hì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 kết quả 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
. Công cụ này có thể kiểm tra các nhà cung cấp
do mục tiêu áp dụng (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, khía cạnh không cung cấp giá trị nào, do đó phương thức này 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
--aspects
đối số. 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
lấy 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 đây minh hoạ việc sử dụng một khía cạnh của quy tắc mục tiêu đếm tệp trong mục tiêu, có thể lọc các tệp đó theo tiện ích. 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. Các thuộc tính của khía cạ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ố int
và string
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ó '*
', 'h
' hoặc 'cc
' làm giá trị.
Đối với các khía cạnh được áp dụng quy tắc, giá trị thông 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, giá trị tham số có thể được chuyển bằng cách sử dụng
--aspects_parameters
cờ. Quy định hạn chế values
đối với tham số int
và string
có thể là
đã bỏ qua.
Các khía cạnh cũng được phép 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"
)
}
...
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 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 nhà cung cấp cho một ứng dụng khung hình A(X) là tập hợp các nhà cung cấp
đến từ việc triển khai quy tắc cho mục tiêu X và từ
trong quá trình triển khai khía cạnh A. Các trình cung cấp mà quá trình triển khai quy tắc sẽ áp dụng
được tạo và cố định trước khi các thành phần được áp dụng và không thể sửa đổi được trên
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à việc triển khai khung hình có thể
không bao giờ trả lại 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 nhà cung cấp cũ, giá trị của các thuộc tính mà theo đó
khía cạnh được truyền tải (từ danh sách attr_aspects
) được thay thế bằng
kết quả của việc áp dụng khía cạnh đó vào chúng. Ví dụ: nếu mục tiêu
X có Y và Z trong các phần phụ thuộc, 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 khung hình cho các "deps" của mục tiêu ban đầu mà
đã được áp dụng khung hình.
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 mã
triển khai quy tắc, 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
.