Sử dụng macro để tạo động từ tuỳ chỉnh

Báo cáo sự cố Xem nguồn

Hoạt động 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, testrun. Tuy nhiên, đôi khi, những việc này có thể mang lại cảm giác hạn chế: có thể bạn muốn đẩy các gói vào 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 thao tác này phù hợp ở đâu?

Lệnh chạy bazel

Bazel tập trung vào độ nhạy, khả năng tái tạo và mức độ gia tăng khiến các lệnh buildtest không hữu ích cho các nhiệm vụ trên. Những thao tác này có thể chạy trong một hộp cát, với quyền truy cập mạng hạn chế và không được đảm bảo sẽ chạy lại sau mỗi bazel build.

Thay vào đó, hãy dựa vào bazel run: công cụ cho các tác vụ mà bạn muốn có hiệu ứng phụ. Người dùng Bazel đã quen với các quy tắc tạo ra các tệp thực thi và tác giả quy tắc có thể tuân theo một tập hợp 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 của Kubernetes dành 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 sẽ tạo một tệp YAML tiêu chuẩn của Kubernetes 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 do macro k8s_object tạo ra có tên như staging.apply:staging.delete. Các tập lệnh bản dựng này để thực hiện các thao tác đó. Khi được thực thi bằng bazel run staging.apply, các tập lệnh này sẽ hoạt động giống như các lệnh bazel k8s-apply hoặc bazel k8s-delete của riêng chúng ta.

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 2 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 đã tạo với tệp "vàng" (nghĩa là tệp chứa đầu ra dự kiến). Bạn có thể tạo và chạy mã này bằng 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 Golden này có thể cần được cập nhật vì 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ục tiêu nodejs_binary cập nhật tệp Golden, thay vì so sánh với tệp đó. Để thực sự, 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 tập lệnh đó được gọi. Điều này tuân theo cùng một mẫu mà bạn đã học được: không có lệnh bazel test-accept gốc, nhưng bạn có thể đạt được hiệu quả tương tự với 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 tìm hiểu cách nhận dạng.

Điều chỉnh các quy tắc của riêng bạn

Macro là trọng tâm của mẫu này. Macro được sử 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 hành động tạo chính: có thể mục tiêu sẽ tạo một tệp nhị phân thông thường, hình ảnh Docker hoặc kho lưu trữ mã nguồn. Trong mẫu này, các mục tiêu bổ sung sẽ được tạo để tạo các tập lệnh biểu diễn các hiệu ứng phụ dựa trên kết quả của mục tiêu chính, chẳng hạn như phát hành tệp nhị phân thu được hoặc cập nhật kết quả kiểm thử dự kiến.

Để minh hoạ điều này, hãy gói một quy tắc ảo sẽ tạo một trang web với Sphinx bằng macro để tạo 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 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 tập lệnh để khi chạy sẽ phát hành 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 sau đây để tạo mục tiêu cho cả hai quy tắc ở trên:

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 các 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ục tiêu "tài liệu" được tạo, giống như macro là một quy tắc Bazel chuẩn, duy nhất. Khi được tạo, quy tắc này sẽ tạo một số cấu hình và chạy Sphinx để tạo một trang web HTML, sẵn sàng cho việc kiểm tra thủ công. Tuy nhiên, mục tiêu "docs.publish" bổ sung cũng được tạo, giúp tạo tập lệnh để phát hành trang web. Sau khi kiểm tra kết quả của mục tiêu chính, bạn có thể sử dụng bazel run :docs.publish để phát hành mục tiêu đó công khai, giống như một lệnh bazel publish ảo.

Sẽ không rõ ràng ngay việc triển khai quy tắc _sphinx_publisher. Thông thường, những thao tác như thế này sẽ viết tập lệnh shell trình chạy. Phương thức này thường bao gồm 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, sẽ 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, hoạt động triển khai của nhà xuất bản có thể vẫn là quy tắc chung, quy tắc _sphinx_site chỉ có thể tạo HTML và tập lệnh nhỏ này là tất cả những gì cần thiết để kết hợp cả hai 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 kết quả của mục tiêu chính. Sau đó, bạn có thể tạo và chạy tập lệnh này bằng bazel run :staging.apply, cung cấp lệnh k8s-apply cho các mục tiêu k8s_object một cách hiệu quả.