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

7.3 · 7.2 · 7.1 · 7.0 · 6.5

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 để phân giải 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ó các chức năng tương tự như quy tắc kho lưu trữ, cho phép thực hiện I/O tệp, gửi yêu cầu mạng, v.v. Ngoài ra, các mô-đun 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 theo biểu đồ phần phụ thuộc được tạo từ các mô-đun Bazel.

Bạn có thể xác định các tiện ích mô-đun trong tệp .bzl, giống như các quy tắc kho lưu trữ. Các mô-đun 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 gọi là thẻ để các tiện ích đọc. Bazel chạy độ phân giải mô-đun trước khi đánh giá bất kỳ tiện ích nào. Tiện ích này đọc tất cả thẻ thuộc về tiện ích đó trên toàn bộ biểu đồ phần phụ thuộc.

Mức sử dụng tiện ích

Các tiện ích được lưu trữ trong chính các mô-đun Bazel. Để sử dụng tiện ích trong một mô-đun, trước tiên hãy thêm bazel_dep trên mô-đun lưu trữ tiện ích, sau đó gọi hàm tích hợp use_extension để đưa tiện ích đó vào phạm vi. Hãy xem xét ví dụ sau đây – 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 các thẻ cho tiện ích. Các thẻ phải tuân theo giản đồ do các lớp thẻ tương ứng xác định trong định nghĩa tiện ích. Xem ví dụ về cách chỉ định một số thẻ maven.installmaven.artifact:

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"])

Dùng lệnh use_repo để đưa các repos 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 một tiện ích tạo ra là một phần của API của tiện ích đó. 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 nội dung khai báo ở trên, tiện ích sẽ phân giải chính xác 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 về phần mở rộng

Bạn có thể xác định các phần mở rộng của 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, mặc dù quy tắc kho lưu trữ có một số thuộc tính, nhưng tiện ích mô-đun có tag_class, mỗi tag_class 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ụ: bạn có thể xác định tiện ích "maven" ở trên 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 nội dung khai báo này cho thấy 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 các tiện ích mô-đun tương tự như các hàm của quy tắc repo, ngoại trừ việc các hàm này nhận được đối tượng module_ctx, 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)

Thông tin nhận dạng của 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, phần mở rộng 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 được sử dụng trong biểu đồ mô-đun bắc cầu, thì các phiên bản đó sẽ được đánh giá riêng biệt và sẽ 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 phải đả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à chế độ hiển thị kho lưu trữ

Các kho lưu trữ do các tiện ích tạo ra 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 chuẩn hoá 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 đều 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 các tiện ích mô-đun tạo ra, có một số quy tắc về chế độ hiển thị kho lưu trữ:

  • Kho lưu trữ mô-đun Bazel có thể xem tất cả kho lưu trữ được giới thiệu trong tệp MODULE.bazel thông qua bazel_depuse_repo.
  • Một kho lưu trữ do phần mở rộng mô-đun tạo ra có thể xem tất cả kho lưu trữ hiển thị với mô-đun lưu trữ phần mở rộng, cộng với tất cả kho lưu trữ khác do cùng một phần mở rộng mô-đun tạo ra (sử dụng tên được chỉ định trong lệnh gọi quy tắc kho lưu trữ làm tên hiển thị).
    • Đ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 rõ ràng là 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 ra, foo sẽ tham chiếu đến mô-đun 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 đến nhau theo tên hiển thị trong các thuộc tính, bất kể thứ tự tạo.
    • Trong trường hợp xung đột với 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 lệnh gọi đến Label để đảm bảo rằng các nhãn đó 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ữ phần mở rộng mô-đun.

Ví dụ: Thay thế java_tools của rules_java bằng một bản sao của nhà 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 phần mở rộng để chúng dễ sử dụng, dễ bảo trì và thích ứng tốt với các 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, điều này cho phép một tiện ích tải các kho lưu trữ do một tiện ích khác tạo. Ngay cả khi không sử dụng chức năng này, tốt nhất bạn nên đặt các tệp đó trong các tệp riêng phòng khi sau này bạn cần đến. Lý do là mã nhận dạng 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 của bạn và là một thay đổi không tương thích ngược cho người dùng.

Chỉ định khả năng tái sản xuất

Nếu tiện ích của bạn luôn xác định cùng một kho lưu trữ cho cùng một dữ liệu đầu vào (thẻ tiện ích, tệp mà tiện ích đọc, v.v.) và đặc biệt không dựa vào bất kỳ tệp tải xuống nào không được bảo vệ bằng tổng kiểm, hãy cân nhắc trả về extension_metadata bằng reproducible = True. Điều này cho phép Bazel bỏ qua tiện ích này khi ghi vào tệp khoá.

Chỉ định hệ điều hành và cấu trúc

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

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

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

Hãy nhớ rằng khi một phần mở rộng tạo kho lưu trữ, các kho lưu trữ đó sẽ được tạo trong không gian tên của phần mở rộng. Điều này có nghĩa là các xung đột có thể xảy ra nếu các mô-đun khác nhau sử dụng cùng một phần mở rộng và cuối cùng tạo ra một kho lưu trữ có cùng tên. Điều này thường biểu thị dưới dạng tag_class của tiện ích mô-đun 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 foo trong mô-đun mylang và lỗi sẽ xảy ra.

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