Phần mở rộng về mô-đun

Tiện ích mô-đun cho phép người dùng mở rộng hệ thống mô-đun bằng cách đọc dữ liệu đầu vào từ các mô-đun trên biểu đồ phần phụ thuộc, thực hiện logic cần thiết để giải quyết các phần phụ thuộc và cuối cùng là tạo kho lưu trữ bằng cách gọi các quy tắc kho lưu trữ. Các tiện ích này có những khả năng tương tự như quy tắc kho lưu trữ, cho phép chúng thực hiện thao tác nhập/xuất tệp, gửi yêu cầu mạng, v.v. Ngoài những việc khác, các tiện ích này cho phép Bazel tương tác với các hệ thống quản lý gói khác, đồng thời tuân thủ biểu đồ phần phụ thuộc được xây dựng từ các mô-đun Bazel.

Bạn có thể xác định tiện ích mô-đun trong tệp .bzl, giống như quy tắc kho lưu trữ. Các tiện ích này không được gọi trực tiếp; thay vào đó, mỗi mô-đun chỉ định các phần dữ liệu có tên là thẻ để tiện ích đọc. Bazel chạy quy trình phân giải mô-đun trước khi đánh giá bất kỳ tiện ích nào. Tiện ích đọc tất cả các thẻ thuộc về tiện ích đó trên toàn bộ biểu đồ phần phụ thuộc.

Cách sử dụng tiện ích

Tiện ích được lưu trữ trong chính các mô-đun Bazel. Để sử dụng một tiện ích trong mô-đun, trước tiên, hãy thêm bazel_dep vào mô-đun lưu trữ tiện ích, sau đó gọi hàm tích hợp sẵn use_extension để đưa tiện ích đó vào phạm vi. Hãy xem xét ví dụ sau: một đoạn mã từ tệp MODULE.bazel để sử dụng tiện ích "maven" được xác định trong mô-đun rules_jvm_external :

bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

Thao tác này liên kết giá trị trả về của use_extension với một biến, cho phép người dùng sử dụng cú pháp dấu chấm để chỉ định thẻ cho tiện ích. Các thẻ phải tuân theo giản đồ do lớp thẻ tương ứng xác định, được chỉ định trong định nghĩa tiện ích. Ví dụ: chỉ định một số maven.installmaven.artifact thẻ:

maven.install(artifacts = ["org.junit:junit:4.13.2"])
maven.artifact(group = "com.google.guava",
               artifact = "guava",
               version = "27.0-jre",
               exclusions = ["com.google.j2objc:j2objc-annotations"])

Sử dụng chỉ thị use_repo để đưa các kho lưu trữ do tiện ích tạo vào phạm vi của mô-đun hiện tại.

use_repo(maven, "maven")

Các kho lưu trữ do tiện ích tạo là một phần của API. Trong ví dụ này, tiện ích mô-đun "maven" hứa hẹn sẽ tạo một kho lưu trữ có tên là maven. Với phần khai báo ở trên, tiện ích sẽ phân giải đúng các nhãn như @maven//:org_junit_junit để trỏ đến kho lưu trữ do tiện ích "maven" tạo.

Định nghĩa tiện ích

Bạn có thể xác định tiện ích mô-đun tương tự như quy tắc kho lưu trữ, bằng cách sử dụng hàm module_extension. Tuy nhiên, trong khi quy tắc kho lưu trữ có một số thuộc tính, thì tiện ích mô-đun có tag_classes, mỗi thuộc tính có một số thuộc tính. Các lớp thẻ xác định giản đồ cho các thẻ mà tiện ích này sử dụng. Ví dụ: tiện ích "maven" ở trên có thể được xác định như sau:

# @rules_jvm_external//:extensions.bzl

_install = tag_class(attrs = {"artifacts": attr.string_list(), ...})
_artifact = tag_class(attrs = {"group": attr.string(), "artifact": attr.string(), ...})
maven = module_extension(
  implementation = _maven_impl,
  tag_classes = {"install": _install, "artifact": _artifact},
)

Các phần khai báo này cho thấy rằng bạn có thể chỉ định thẻ maven.installmaven.artifact bằng cách sử dụng giản đồ thuộc tính đã chỉ định.

Hàm triển khai của tiện ích mô-đun tương tự như hàm của quy tắc kho lưu trữ, ngoại trừ việc chúng nhận được đối tượng module_ctx, đối tượng này cấp quyền truy cập vào tất cả các mô-đun sử dụng tiện ích và tất cả các thẻ liên quan. Sau đó, hàm triển khai sẽ gọi các quy tắc kho lưu trữ để tạo kho lưu trữ.

# @rules_jvm_external//:extensions.bzl

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file")  # a repo rule
def _maven_impl(ctx):
  # This is a fake implementation for demonstration purposes only

  # collect artifacts from across the dependency graph
  artifacts = []
  for mod in ctx.modules:
    for install in mod.tags.install:
      artifacts += install.artifacts
    artifacts += [_to_artifact(artifact) for artifact in mod.tags.artifact]

  # call out to the coursier CLI tool to resolve dependencies
  output = ctx.execute(["coursier", "resolve", artifacts])
  repo_attrs = _process_coursier_output(output)

  # call repo rules to generate repos
  for attrs in repo_attrs:
    http_file(**attrs)
  _generate_hub_repo(name = "maven", repo_attrs)

Nhận dạng tiện ích

Tiện ích mô-đun được xác định bằng tên và tệp .bzl xuất hiện trong lệnh gọi đến use_extension. Trong ví dụ sau, tiện ích maven được xác định bằng tệp .bzl @rules_jvm_external//:extension.bzl và tên maven:

maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")

Việc xuất lại một tiện ích từ một tệp .bzl khác sẽ cung cấp cho tiện ích đó một danh tính mới và nếu cả hai phiên bản của tiện ích đều được sử dụng trong biểu đồ mô-đun bắc cầu, thì chúng sẽ được đánh giá riêng biệt và chỉ thấy các thẻ được liên kết với danh tính cụ thể đó.

Là tác giả tiện ích, bạn nên đảm bảo rằng người dùng sẽ chỉ sử dụng tiện ích mô-đun của bạn từ một tệp .bzl duy nhất.

Tên và khả năng hiển thị của kho lưu trữ

Các kho lưu trữ do tiện ích tạo có tên chính tắc ở dạng module_repo_canonical_name+extension_name+repo_name. Xin lưu ý rằng định dạng tên chính tắc không phải là API mà bạn nên phụ thuộc vào – định dạng này có thể thay đổi bất cứ lúc nào.

Chính sách đặt tên này có nghĩa là mỗi tiện ích có "không gian tên kho lưu trữ" riêng; hai tiện ích riêng biệt có thể xác định một kho lưu trữ có cùng tên mà không có nguy cơ xảy ra xung đột. Điều này cũng có nghĩa là repository_ctx.name báo cáo tên chính tắc của kho lưu trữ, tên này không giống với tên được chỉ định trong lệnh gọi quy tắc kho lưu trữ.

Khi xem xét các kho lưu trữ do tiện ích mô-đun tạo, có một số quy tắc về khả năng hiển thị của kho lưu trữ:

  • Kho lưu trữ mô-đun Bazel có thể thấy tất cả các kho lưu trữ được giới thiệu trong tệp MODULE.bazel của nó thông qua bazel_depuse_repo.
  • Kho lưu trữ do tiện ích mô-đun tạo có thể thấy tất cả các kho lưu trữ hiển thị cho mô-đun lưu trữ tiện ích, cộng với tất cả các kho lưu trữ khác do cùng tiện ích mô-đun đó tạo (sử dụng tên được chỉ định trong các lệnh gọi quy tắc kho lưu trữ làm tên hiển thị của chúng).
    • Điều này có thể dẫn đến xung đột. Nếu kho lưu trữ mô-đun có thể thấy một kho lưu trữ có tên hiển thị foo, và tiện ích tạo một kho lưu trữ có tên được chỉ định là foo, thì đối với tất cả các kho lưu trữ do tiện ích đó tạo, foo sẽ đề cập đến kho lưu trữ trước đó.
  • Tương tự, trong hàm triển khai của tiện ích mô-đun, các kho lưu trữ do tiện ích tạo có thể tham chiếu lẫn nhau bằng tên hiển thị trong các thuộc tính, bất kể thứ tự tạo.
    • Trong trường hợp xảy ra xung đột với một kho lưu trữ hiển thị cho mô-đun, các nhãn được truyền đến thuộc tính quy tắc kho lưu trữ có thể được gói trong một lệnh gọi đến Label để đảm bảo rằng chúng tham chiếu đến kho lưu trữ hiển thị cho mô-đun thay vì kho lưu trữ do tiện ích tạo có cùng tên.

Ghi đè và chèn kho lưu trữ tiện ích mô-đun

Mô-đun gốc có thể sử dụng override_repoinject_repo để ghi đè hoặc chèn kho lưu trữ tiện ích mô-đun.

Ví dụ: Thay thế rules_java của java_tools bằng một bản sao được cung cấp

# MODULE.bazel
local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")
local_repository(
  name = "my_java_tools",
  path = "vendor/java_tools",
)

bazel_dep(name = "rules_java", version = "7.11.1")
java_toolchains = use_extension("@rules_java//java:extension.bzl", "toolchains")

override_repo(java_toolchains, remote_java_tools = "my_java_tools")

Ví dụ: Vá một phần phụ thuộc Go để phụ thuộc vào @zlib thay vì zlib hệ thống

# MODULE.bazel
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "zlib", version = "1.3.1.bcr.3")

go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_deps.module_override(
  patches = [
    "//patches:my_module_zlib.patch",
  ],
  path = "example.com/my_module",
)
use_repo(go_deps, ...)

inject_repo(go_deps, "zlib")
# patches/my_module_zlib.patch
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,6 +1,6 @@
 go_binary(
     name = "my_module",
     importpath = "example.com/my_module",
     srcs = ["my_module.go"],
-    copts = ["-lz"],
+    cdeps = ["@zlib"],
 )

Các phương pháp hay nhất

Phần này mô tả các phương pháp hay nhất khi viết tiện ích để tiện ích dễ sử dụng, dễ bảo trì và thích ứng tốt với những thay đổi theo thời gian.

Đặt mỗi tiện ích vào một tệp riêng

Khi các tiện ích nằm trong các tệp khác nhau, một tiện ích có thể tải các kho lưu trữ do một tiện ích khác tạo. Ngay cả khi bạn không sử dụng chức năng này, tốt nhất là bạn nên đặt các tiện ích vào các tệp riêng biệt trong trường hợp bạn cần sau này. Lý do là vì danh tính của tiện ích dựa trên tệp của tiện ích đó, vì vậy, việc di chuyển tiện ích vào một tệp khác sau này sẽ thay đổi API công khai và là một thay đổi không tương thích ngược cho người dùng của bạn.

Chỉ định khả năng tái tạo và sử dụng dữ kiện

Nếu tiện ích của bạn luôn xác định cùng một kho lưu trữ khi có cùng dữ liệu đầu vào (thẻ tiện ích, tệp mà tiện ích đọc, v.v.) và đặc biệt là không dựa vào bất kỳ bản tải xuống nào không được bảo vệ bằng tổng kiểm tra, hãy cân nhắc việc trả về extension_metadata với reproducible = True. Điều này cho phép Bazel bỏ qua tiện ích này khi ghi vào tệp khoá MODULE.bazel, giúp giữ cho tệp khoá nhỏ và giảm khả năng xảy ra xung đột hợp nhất. Xin lưu ý rằng Bazel vẫn lưu kết quả của các tiện ích có thể tái tạo vào bộ nhớ đệm theo cách duy trì trên các lần khởi động lại máy chủ, vì vậy, ngay cả một tiện ích chạy trong thời gian dài cũng có thể được đánh dấu là có thể tái tạo mà không bị ảnh hưởng đến hiệu suất.

Nếu tiện ích của bạn dựa vào dữ liệu gần như bất biến thu được từ bên ngoài bản dựng, thường là từ mạng, nhưng bạn không có tổng kiểm tra để bảo vệ bản tải xuống, hãy cân nhắc sử dụng tham số facts của extension_metadata để ghi lại dữ liệu đó một cách liên tục và do đó cho phép tiện ích của bạn trở thành có thể tái tạo. facts dự kiến là một từ điển có các khoá chuỗi và các giá trị Starlark tuỳ ý giống như JSON, luôn được duy trì trong tệp khoá và có sẵn cho các lần đánh giá tiện ích trong tương lai thông qua trường facts của module_ctx.

facts không bị vô hiệu hoá ngay cả khi mã của tiện ích mô-đun thay đổi, vì vậy, hãy chuẩn bị để xử lý trường hợp cấu trúc của facts thay đổi. Bazel cũng giả định rằng hai từ điển facts khác nhau do hai lần đánh giá khác nhau của cùng một tiện ích tạo ra có thể được hợp nhất một cách nông cạn (tức là như thể bằng cách sử dụng toán tử | trên hai từ điển). Điều này được thực thi một phần bằng cách module_ctx.facts không hỗ trợ việc liệt kê các mục của nó, chỉ tra cứu theo khoá.

Ví dụ về cách sử dụng facts là ghi lại một mối liên kết từ số phiên bản của một số SDK đến đối tượng chứa URL tải xuống và tổng kiểm tra của phiên bản đó. Lần đầu tiên tiện ích được đánh giá, tiện ích có thể tìm nạp mối liên kết này từ mạng, nhưng trong các lần đánh giá sau, tiện ích có thể sử dụng mối liên kết từ facts để tránh các yêu cầu mạng.

Chỉ định sự phụ thuộc vào hệ điều hành và kiến trúc

Nếu tiện ích của bạn dựa vào hệ điều hành hoặc loại kiến trúc của hệ điều hành, hãy đảm bảo chỉ ra điều này trong định nghĩa tiện ích bằng cách sử dụng các thuộc tính boolean os_dependentarch_dependent. Điều này đảm bảo rằng Bazel nhận ra sự cần thiết phải đánh giá lại nếu có thay đổi đối với một trong hai thuộc tính này.

Vì loại phụ thuộc này vào máy chủ khiến việc duy trì mục tệp khoá cho tiện ích này trở nên khó khăn hơn, nên hãy cân nhắc đánh dấu tiện ích là có thể tái tạo nếu có thể.

Chỉ mô-đun gốc mới được ảnh hưởng trực tiếp đến tên kho lưu trữ

Hãy nhớ rằng khi một tiện ích tạo kho lưu trữ, các kho lưu trữ này sẽ được tạo trong không gian tên của tiện ích. Điều này có nghĩa là có thể xảy ra xung đột nếu các mô-đun khác nhau sử dụng cùng một tiện ích và cuối cùng tạo một kho lưu trữ với cùng tên. Điều này thường biểu hiện dưới dạng tiện ích mô-đun tag_class có đối số name được truyền dưới dạng giá trị name của quy tắc kho lưu trữ.

Ví dụ: giả sử mô-đun gốc, A, phụ thuộc vào mô-đun B. Cả hai mô-đun đều phụ thuộc vào mô-đun mylang. Nếu cả AB đều gọi mylang.toolchain(name="foo"), thì cả hai sẽ cố gắng tạo một kho lưu trữ có tên là foo trong mô-đun mylang và sẽ xảy ra lỗi.

Để tránh điều này, hãy xoá khả năng đặt tên kho lưu trữ trực tiếp, hoặc chỉ cho phép mô-đun gốc thực hiện việc này. Bạn có thể cho phép mô-đun gốc có khả năng này vì không có gì phụ thuộc vào mô-đun gốc, vì vậy, mô-đun gốc không phải lo lắng về việc một mô-đun khác tạo tên xung đột.