Starlark giống như Python
ngôn ngữ cấu hình ban đầu được phát triển để sử dụng trong Bazel và kể từ khi được áp dụng
bằng các công cụ khác. Các tệp BUILD
và .bzl
của Bazel được viết bằng phương ngữ của
Starlark được biết đến đúng như "Ngôn ngữ xây dựng", mặc dù nó thường chỉ đơn giản
được gọi là "Starlark", đặc biệt khi nhấn mạnh rằng một đối tượng
được thể hiện bằng Ngôn ngữ xây dựng chứ không phải là ngôn ngữ "gốc" hoặc tích hợp sẵn phụ tùng
Bazel. Bazel tăng cường cho ngôn ngữ cốt lõi bằng nhiều hàm liên quan đến bản dựng
chẳng hạn như glob
, genrule
, java_binary
, v.v.
Xem Tài liệu về Bazel và Starlark về chi tiết hơn và Mẫu quy tắc SIG dưới dạng điểm bắt đầu cho tập hợp quy tắc mới.
Quy tắc trống
Để tạo quy tắc đầu tiên, hãy tạo tệp foo.bzl
:
def _foo_binary_impl(ctx):
pass
foo_binary = rule(
implementation = _foo_binary_impl,
)
Khi gọi hàm rule
, bạn
phải xác định hàm callback. Logic sẽ vẫn ở đó, nhưng bạn
có thể để trống hàm này vào lúc này. Đối số ctx
cung cấp thông tin về mục tiêu.
Bạn có thể tải và sử dụng quy tắc từ tệp BUILD
.
Tạo tệp BUILD
trong cùng thư mục:
load(":foo.bzl", "foo_binary")
foo_binary(name = "bin")
Lúc này, bạn có thể tạo mục tiêu:
$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)
Mặc dù quy tắc này không làm gì cả, nhưng nó đã hoạt động giống như các quy tắc khác: nó có
bắt buộc, vì thuộc tính này hỗ trợ các thuộc tính phổ biến như visibility
, testonly
và
tags
.
Mô hình đánh giá
Trước khi tiếp tục, bạn cần hiểu cách đánh giá mã.
Cập nhật foo.bzl
bằng một số câu lệnh in:
def _foo_binary_impl(ctx):
print("analyzing", ctx.label)
foo_binary = rule(
implementation = _foo_binary_impl,
)
print("bzl file evaluation")
và XÂY DỰNG:
load(":foo.bzl", "foo_binary")
print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")
ctx.label
tương ứng với nhãn của mục tiêu đang được phân tích. Đối tượng ctx
có
nhiều trường và phương pháp hữu ích; bạn có thể tìm thấy một danh sách đầy đủ trong
Tài liệu tham khảo API.
Truy vấn mã:
$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1
Lưu ý:
- "đánh giá tệp bzl" sẽ được in đầu tiên. Trước khi đánh giá tệp
BUILD
, Bazel đánh giá tất cả các tệp được tải. Nếu nhiều tệpBUILD
đang tải foo.bzl, bạn sẽ chỉ thấy một lần xuất hiện "đánh giá tệp bzl" bởi vì Bazel lưu kết quả đánh giá vào bộ nhớ đệm. - Hàm callback
_foo_binary_impl
không được gọi. Tải truy vấn BazelBUILD
tệp, nhưng không phân tích mục tiêu.
Để phân tích các mục tiêu, hãy sử dụng cquery
("được định cấu hình
truy vấn") hoặc lệnh build
:
$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...
Như bạn có thể thấy, _foo_binary_impl
hiện được gọi 2 lần, mỗi lần cho một mục tiêu.
Lưu ý rằng cả tính năng "đánh giá tệp bzl" cũng như "BUILD tệp" được in lại,
vì việc đánh giá foo.bzl
được lưu vào bộ nhớ đệm sau lệnh gọi đến bazel query
.
Bazel chỉ phát câu lệnh print
khi các câu lệnh này thực sự được thực thi.
Tạo tệp
Để làm cho quy tắc của bạn hữu ích hơn, hãy cập nhật quy tắc để tạo tệp. Trước tiên, hãy khai báo và đặt tên cho tệp. Trong ví dụ này, hãy tạo một tệp có cùng tên với mục tiêu:
ctx.actions.declare_file(ctx.label.name)
Nếu chạy bazel build :all
ngay bây giờ, bạn sẽ thấy một thông báo lỗi:
The following files have no generating action:
bin2
Mỗi khi khai báo một tệp, bạn phải cho Bazel biết cách tạo tệp đó bằng cách
tạo một hành động. Sử dụng ctx.actions.write
,
để tạo một tệp có nội dung đã cho.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello\n",
)
Mã này hợp lệ, nhưng sẽ không có tác dụng gì:
$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)
Hàm ctx.actions.write
đã đăng ký một hành động để dạy Bazel
cách tạo tệp. Nhưng Bazel sẽ không tạo tệp cho đến khi
thực sự được yêu cầu. Điều cuối cùng cần làm là cho Bazel biết rằng tệp này
là kết quả của quy tắc chứ không phải là tệp tạm thời được dùng trong quy tắc
trong quá trình triển khai.
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello!\n",
)
return [DefaultInfo(files = depset([out]))]
Hãy xem các hàm DefaultInfo
và depset
sau. Hiện tại,
giả định rằng dòng cuối cùng là cách chọn kết quả của quy tắc.
Bây giờ, hãy chạy Bazel:
$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
bazel-bin/bin1
$ cat bazel-bin/bin1
Hello!
Bạn đã tạo thành công một tệp!
Thuộc tính
Để làm cho quy tắc này hữu ích hơn, hãy thêm các thuộc tính mới bằng cách sử dụng
mô-đun attr
và cập nhật định nghĩa quy tắc.
Thêm thuộc tính chuỗi có tên là username
:
foo_binary = rule(
implementation = _foo_binary_impl,
attrs = {
"username": attr.string(),
},
)
Tiếp theo, hãy thiết lập trong tệp BUILD
:
foo_binary(
name = "bin",
username = "Alice",
)
Để truy cập giá trị trong hàm callback, hãy sử dụng ctx.attr.username
. Ví dụ:
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello {}!\n".format(ctx.attr.username),
)
return [DefaultInfo(files = depset([out]))]
Lưu ý rằng bạn có thể đặt thuộc tính này là bắt buộc hoặc đặt giá trị mặc định. Xem
tài liệu về attr.string
.
Bạn cũng có thể sử dụng các loại thuộc tính khác như boolean
hoặc danh sách số nguyên.
Phần phụ thuộc
Thuộc tính phần phụ thuộc, chẳng hạn như attr.label
và attr.label_list
,
khai báo phần phụ thuộc từ mục tiêu sở hữu thuộc tính này với mục tiêu có
sẽ xuất hiện trong giá trị của thuộc tính. Loại thuộc tính này tạo thành cơ sở
của biểu đồ mục tiêu.
Trong tệp BUILD
, nhãn mục tiêu xuất hiện dưới dạng một đối tượng chuỗi, chẳng hạn như
//pkg:name
Trong hàm triển khai, bạn có thể truy cập mục tiêu dưới dạng một
Đối tượng Target
. Ví dụ: xem các tệp được trả về
theo mục tiêu bằng cách sử dụng Target.files
.
Nhiều tệp
Theo mặc định, chỉ các mục tiêu do quy tắc tạo ra mới có thể xuất hiện dưới dạng phần phụ thuộc (chẳng hạn như
foo_library()
mục tiêu). Nếu bạn muốn thuộc tính này chấp nhận mục tiêu
tệp đầu vào (chẳng hạn như tệp nguồn trong kho lưu trữ), bạn có thể thực hiện việc này bằng
allow_files
và chỉ định danh sách các đuôi tệp được chấp nhận (hoặc True
thành
cho phép mọi đuôi tệp):
"srcs": attr.label_list(allow_files = [".java"]),
Bạn có thể truy cập vào danh sách tệp bằng ctx.files.<attribute name>
. Cho
Ví dụ: bạn có thể truy cập danh sách các tệp trong thuộc tính srcs
thông qua
ctx.files.srcs
Tệp đơn
Nếu bạn chỉ cần một tệp, hãy dùng allow_single_file
:
"src": attr.label(allow_single_file = [".java"])
Sau đó, bạn có thể truy cập vào tệp này trong ctx.file.<attribute name>
:
ctx.file.src
Tạo tệp bằng mẫu
Bạn có thể tạo quy tắc để tạo tệp .cc dựa trên mẫu. Ngoài ra, bạn
có thể sử dụng ctx.actions.write
để xuất ra một chuỗi được tạo trong quy tắc
nhưng có hai vấn đề. Trước tiên, khi mẫu nhận được
lớn hơn thì việc đặt tệp vào một tệp riêng sẽ tiết kiệm bộ nhớ hơn và tránh
tạo các chuỗi lớn trong giai đoạn phân tích. Thứ hai, sử dụng một
tệp thuận tiện hơn cho người dùng. Thay vào đó, hãy sử dụng
ctx.actions.expand_template
!
thực hiện thay thế trên tệp mẫu.
Tạo thuộc tính template
để khai báo phần phụ thuộc trên mẫu
tệp:
def _hello_world_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".cc")
ctx.actions.expand_template(
output = out,
template = ctx.file.template,
substitutions = {"{NAME}": ctx.attr.username},
)
return [DefaultInfo(files = depset([out]))]
hello_world = rule(
implementation = _hello_world_impl,
attrs = {
"username": attr.string(default = "unknown person"),
"template": attr.label(
allow_single_file = [".cc.tpl"],
mandatory = True,
),
},
)
Người dùng có thể sử dụng quy tắc như sau:
hello_world(
name = "hello",
username = "Alice",
template = "file.cc.tpl",
)
cc_binary(
name = "hello_bin",
srcs = [":hello"],
)
Nếu bạn không muốn hiển thị mẫu cho người dùng cuối và luôn sử dụng cùng một giá trị, bạn có thể đặt giá trị mặc định và đặt thuộc tính ở chế độ riêng tư:
"_template": attr.label(
allow_single_file = True,
default = "file.cc.tpl",
),
Các thuộc tính bắt đầu bằng dấu gạch dưới là riêng tư và không thể được đặt trong một
Tệp BUILD
. Mẫu hiện là một phần phụ thuộc ngầm ẩn: Mỗi hello_world
target có phần phụ thuộc vào tệp này. Đừng quên đặt tệp này ở chế độ hiển thị
sang các gói khác bằng cách cập nhật tệp BUILD
và sử dụng
exports_files
:
exports_files(["file.cc.tpl"])
Tìm hiểu thêm
- Hãy xem tài liệu tham khảo về các quy tắc.
- Làm quen với phần phụ thuộc.
- Xem kho lưu trữ mẫu trong đó bao gồm các ví dụ bổ sung về quy tắc.