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

Báo cáo vấn đề Xem nguồn Nightly · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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 tính năng này có thể bị hạn chế: bạn có thể muốn đẩy các gói vào kho lưu trữ, phát hành tài liệu cho người dùng cuối hoặc triển khai ứ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 đâu?

Lệnh chạy bazel

Việc Bazel tập trung vào tính kín, khả năng tái tạo và tính gia tăng có nghĩa là các lệnh buildtest sẽ không hữu ích cho các tác vụ trên. Các thao tác này có thể chạy trong hộp cát, với quyền truy cập mạng bị hạn chế và không được đả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ụ chính cho các 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ể làm theo một bộ mẫu phổ biến để mở rộng quy tắc này thành "động từ tuỳ chỉnh".

Trong thực tế: 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 sử 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ằng macro k8s_object với 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 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 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 hai mục tiêu. Mục tiêu đầu tiên là một mục tiêu nodejs_test chuẩn so sánh một số kết quả được tạo với tệp "vàng" (tức là tệp chứa kết quả dự kiến). Bạn có thể tạo và chạy ứng dụng 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 vàng 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 sẽ rất tẻ nhạt và dễ gặp 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 chuẩn, thay vì so sánh với tệp đó. 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 đó. Cách này tuân theo cùng một mẫu mà bạn đã tìm hiểu: 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à khá phổ biến sau khi bạn học cách nhận ra 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 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, các lệnh này sẽ tạo một mục tiêu có tên được chỉ định để thực hiện hành động xây dựng chính: có thể mục tiêu này sẽ tạo một tệp nhị phân thông thường, hình ảnh Docker hoặc bản lưu trữ mã nguồn. Trong mẫu này, các mục tiêu bổ sung được tạo để tạo tập lệnh thực hiện hiệu ứng phụ dựa trên đầu ra của mục tiêu chính, chẳng hạn như phát hành 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 gói 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 cân nhắc 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 để tạo một tập lệnh. Khi chạy, tập lệnh nà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 tượng trưng sau đây (có trong Bazel 8 trở lên) để tạo 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ẽ định nghĩa 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 các tệp BUILD, hãy sử dụng macro như thể macro 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 mục tiêu "docs" (tài liệu) được tạo, giống như 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 một số cấu hình và chạy Sphinx để tạo một trang web HTML, sẵn sàng để kiểm tra thủ công. Tuy nhiên, một mục tiêu "docs.publish" bổ sung cũng được tạo để tạo một 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 kết quả đó cho công chúng, giống như một lệnh bazel publish tưởng tượng.

Bạn có thể chưa nhận ra ngay cách triển khai quy tắc _sphinx_publisher. Thông thường, các 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 trình phát hành có thể vẫn chung 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 hai quy tắc này với nhau.

Trong rules_k8s, đây thực sự là những gì .apply thực hiện: 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 hiệu quả lệnh k8s-apply cho các mục tiêu k8s_object.