Tương tác hằng ngày với Bazel chủ yếu diễn ra thông qua một số lệnh: build
, test
và run
. Tuy nhiên, đôi khi những công cụ này có thể bị hạn chế: bạn có thể muốn đẩy các gói vào một kho lưu trữ, xuất bản tài liệu cho người dùng cuối hoặc triển khai một ứng dụng bằng Kubernetes. Nhưng Bazel không có lệnh publish
hoặc deploy
– những hành động này phù hợp với lệnh nào?
Lệnh bazel run
Bazel tập trung vào tính khép kín, khả năng tái tạo và tính gia tăng, nghĩa là các lệnh build
và test
không hữu ích cho các tác vụ nêu trên. Những thao tác này có thể chạy trong một hộp cát, có quyền truy cập mạng bị hạn chế và không đảm bảo sẽ chạy lại với mọi bazel build
.
Thay vào đó, hãy dựa vào bazel run
: công cụ hỗ trợ cho những tác vụ mà bạn muốn có tác dụng phụ. Người dùng Bazel đã quen với các quy tắc tạo tệp thực thi và tác giả quy tắc có thể tuân theo một bộ mẫu chung để mở rộng quy tắc này thành "động từ tuỳ chỉnh".
Trong tự nhiên: rules_k8s
Ví dụ: hãy xem xét rules_k8s
, các quy tắc Kubernetes cho Bazel. Giả sử bạn có mục tiêu sau:
# BUILD file in //application/k8s
k8s_object(
name = "staging",
kind = "deployment",
cluster = "testing",
template = "deployment.yaml",
)
Quy tắc k8s_object
tạo một tệp YAML Kubernetes tiêu chuẩn khi bazel build
được dùng trên mục tiêu staging
. Tuy nhiên, các mục tiêu bổ sung cũng được tạo bởi macro k8s_object
với các tên như staging.apply
và :staging.delete
. Các tập lệnh này sẽ tạo các tập lệnh để thực hiện những hành động đó và khi được thực thi bằng bazel run
staging.apply
, các tập lệnh này sẽ hoạt động như các lệnh bazel k8s-apply
hoặc bazel
k8s-delete
của riêng chúng tôi.
Ví dụ khác: ts_api_guardian_test
Bạn cũng có thể thấy mẫu này trong dự án Angular. Macro ts_api_guardian_test
tạo ra hai mục tiêu. Đầu tiên là mục tiêu nodejs_test
tiêu chuẩn, so sánh một số đầu ra được tạo với tệp "golden" (tức là tệp chứa đầu ra dự kiến). Bạn có thể tạo và chạy việc này bằng một lệnh gọi bazel
test
thông thường. Trong angular-cli
, bạn có thể chạy một mục tiêu như vậy bằng bazel test //etc/api:angular_devkit_core_api
.
Theo thời gian, tệp tham chiếu này có thể cần được cập nhật vì những lý do chính đáng.
Việc cập nhật tệp này theo cách thủ công rất tẻ nhạt và dễ xảy ra lỗi, vì vậy, macro này cũng cung cấp một mục tiêu nodejs_binary
để cập nhật tệp tham chiếu thay vì so sánh với tệp đó. Trên thực tế, bạn có thể viết cùng một tập lệnh kiểm thử để chạy ở chế độ "xác minh" hoặc "chấp nhận", dựa trên cách gọi tập lệnh đó. Điều này tuân theo mẫu tương tự mà bạn đã học: không có lệnh bazel test-accept
gốc, nhưng bạn có thể đạt được hiệu ứng tương tự bằng bazel run //etc/api:angular_devkit_core_api.accept
.
Mẫu này có thể khá mạnh mẽ và hoá ra khá phổ biến khi bạn học cách nhận biết nó.
Điều chỉnh quy tắc của riêng bạn
Macro là trọng tâm của mẫu này. Macro được dùng như các quy tắc, nhưng có thể tạo nhiều mục tiêu. Thông thường, chúng sẽ tạo một mục tiêu có tên được chỉ định để thực hiện thao tác chính trong quá trình tạo: có thể là tạo một tệp nhị phân thông thường, một hình ảnh Docker hoặc một kho lưu trữ mã nguồn. Trong mẫu này, các mục tiêu bổ sung được tạo để tạo ra các tập lệnh thực hiện các hiệu ứng phụ dựa trên đầu ra của mục tiêu chính, chẳng hạn như xuất bản tệp nhị phân kết quả hoặc cập nhật đầu ra kiểm thử dự kiến.
Để minh hoạ điều này, hãy bao bọc một quy tắc tưởng tượng tạo ra một trang web bằng Sphinx bằng một macro để tạo một mục tiêu bổ sung cho phép người dùng xuất bản trang web đó khi đã sẵn sàng. Hãy xem xét quy tắc hiện có sau đây để tạo một trang web bằng Sphinx:
_sphinx_site = rule(
implementation = _sphinx_impl,
attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)
Tiếp theo, hãy xem xét một quy tắc như sau, quy tắc này sẽ tạo một tập lệnh. Khi chạy, tập lệnh này sẽ xuất bản các trang đã tạo:
_sphinx_publisher = rule(
implementation = _publish_impl,
attrs = {
"site": attr.label(),
"_publisher": attr.label(
default = "//internal/sphinx:publisher",
executable = True,
),
},
executable = True,
)
Cuối cùng, hãy xác định macro tượng trưng sau đây (có trong Bazel 8 trở lên) để tạo các mục tiêu cho cả hai quy tắc trên cùng nhau:
def _sphinx_site_impl(name, visibility, srcs, **kwargs):
# This creates the primary target, producing the Sphinx-generated HTML. We
# set `visibility = visibility` to make it visible to callers of the
# macro.
_sphinx_site(name = name, visibility = visibility, srcs = srcs, **kwargs)
# This creates the secondary target, which produces a script for publishing
# the site generated above. We don't want it to be visible to callers of
# our macro, so we omit visibility for it.
_sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)
sphinx_site = macro(
implementation = _sphinx_site_impl,
attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
# Inherit common attributes like tags and testonly
inherit_attrs = "common",
)
Hoặc nếu cần hỗ trợ các bản phát hành Bazel cũ hơn Bazel 8, bạn sẽ xác định một macro cũ:
def sphinx_site(name, srcs = [], **kwargs):
# This creates the primary target, producing the Sphinx-generated HTML.
_sphinx_site(name = name, srcs = srcs, **kwargs)
# This creates the secondary target, which produces a script for publishing
# the site generated above.
_sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)
Trong tệp BUILD
, hãy sử dụng macro như thể macro này chỉ tạo mục tiêu chính:
sphinx_site(
name = "docs",
srcs = ["index.md", "providers.md"],
)
Trong ví dụ này, một đích đến "docs" được tạo, giống như thể macro là một quy tắc Bazel tiêu chuẩn, duy nhất. Khi được tạo, quy tắc này sẽ tạo ra một số cấu hình và chạy Sphinx để tạo ra một trang web HTML, sẵn sàng cho việc kiểm tra thủ công. Tuy nhiên, một mục tiêu "docs.publish" bổ sung cũng được tạo, mục tiêu này sẽ tạo một tập lệnh để xuất bản trang web. Sau khi kiểm tra đầu ra của mục tiêu chính, bạn có thể sử dụng bazel run :docs.publish
để xuất bản đầu ra đó cho người dùng công khai, giống như lệnh bazel publish
tưởng tượng.
Bạn có thể chưa nhận ra ngay quy trình triển khai quy tắc _sphinx_publisher
. Thông thường, những thao tác như thế này sẽ ghi một tập lệnh shell trình chạy.
Phương thức này thường liên quan đến việc sử dụng ctx.actions.expand_template
để viết một tập lệnh shell rất đơn giản, trong trường hợp này là gọi tệp nhị phân của nhà xuất bản bằng một đường dẫn đến đầu ra của mục tiêu chính. Bằng cách này, việc triển khai của nhà xuất bản có thể vẫn chung chung, quy tắc _sphinx_site
chỉ có thể tạo ra HTML và tập lệnh nhỏ này là tất cả những gì cần thiết để kết hợp hai quy tắc này với nhau.
Trong rules_k8s
, đây thực sự là những gì .apply
làm:
expand_template
viết một tập lệnh Bash rất đơn giản, dựa trên apply.sh.tpl
, chạy kubectl
với đầu ra của mục tiêu chính. Sau đó, tập lệnh này có thể được tạo và chạy bằng bazel run :staging.apply
, cung cấp hiệu quả lệnh k8s-apply
cho các mục tiêu k8s_object
.