Quản lý các phần phụ thuộc bên ngoài bằng Bzlmod

Bzlmod là tên mã của hệ thống phần phụ thuộc bên ngoài mới được giới thiệu trong Bazel 5.0. Tài liệu này được giới thiệu để giải quyết một số vấn đề của hệ thống cũ mà không thể khắc phục dần dần; hãy xem phần Tuyên bố vấn đề trong tài liệu thiết kế gốc để biết thêm chi tiết.

Trong Bazel 5.0, Bzlmod không được bật theo mặc định; bạn cần chỉ định cờ --experimental_enable_bzlmod để các thuộc tính sau có hiệu lực. Như tên cờ cho thấy, tính năng này hiện đang thử nghiệm; các API và hành vi có thể thay đổi cho đến khi tính năng này chính thức ra mắt.

Để di chuyển dự án của bạn sang Bzlmod, hãy làm theo Hướng dẫn di chuyển cho Bzlmod. Bạn cũng có thể tìm thấy ví dụ về cách sử dụng Bzlmod trong kho lưu trữ ví dụ.

Mô-đun Bazel

Hệ thống phần phụ thuộc bên ngoài dựa trên WORKSPACE cũ tập trung vào kho lưu trữ (hay kho lưu trữ), được tạo thông qua quy tắc lưu trữ (hoặc quy tắc kho lưu trữ). Mặc dù kho lưu trữ vẫn là một khái niệm quan trọng trong hệ thống mới, nhưng mô-đun là đơn vị cốt lõi của phần phụ thuộc.

Mô-đun về cơ bản là một dự án Bazel có thể có nhiều phiên bản, mỗi phiên bản phát hành siêu dữ liệu về các mô-đun khác mà mô-đun đó phụ thuộc vào. Điều này tương tự như các khái niệm quen thuộc trong các hệ thống quản lý phần phụ thuộc khác: một cấu phần phần mềm Maven, một gói npm, một Thùng rác Cargo, một mô-đun Go, v.v.

Một mô-đun chỉ chỉ định các phần phụ thuộc của mình bằng cách sử dụng cặp nameversion, thay vì các URL cụ thể trong WORKSPACE. Sau đó, các phần phụ thuộc được tra cứu trong Sổ đăng ký Bazel; theo mặc định là Sổ đăng ký trung tâm Bazel (Bazel Central Registry). Trong không gian làm việc, mỗi mô-đun sau đó được chuyển thành một kho lưu trữ.

MODULE.bazel

Mỗi phiên bản của mỗi mô-đun đều có một tệp MODULE.bazel khai báo các phần phụ thuộc và siêu dữ liệu khác. Dưới đây là một ví dụ cơ bản:

module(
    name = "my-module",
    version = "1.0",
)

bazel_dep(name = "rules_cc", version = "0.0.1")
bazel_dep(name = "protobuf", version = "3.19.0")

Tệp MODULE.bazel phải nằm ở gốc của thư mục không gian làm việc (bên cạnh tệp WORKSPACE). Không giống như tệp WORKSPACE, bạn không cần chỉ định các phần phụ thuộc bắc cầu; thay vào đó, bạn chỉ nên chỉ định các phần phụ thuộc trực tiếp và các tệp MODULE.bazel của các phần phụ thuộc này sẽ được xử lý để tự động khám phá các phần phụ thuộc bắc cầu.

Tệp MODULE.bazel tương tự như các tệp BUILD vì không hỗ trợ bất kỳ hình thức quy trình kiểm soát nào; tệp này cũng cấm các câu lệnh load. Các tệp MODULE.bazel hỗ trợ bao gồm:

Định dạng phiên bản

Bazel có một hệ sinh thái đa dạng và các dự án sử dụng nhiều phương án tạo phiên bản. Cho đến nay, phổ biến nhất là SemVer, nhưng cũng có nhiều dự án nổi bật sử dụng các giao thức khác nhau như Abseil, có phiên bản dựa trên ngày, ví dụ: 20210324.2).

Vì lý do này, Bzlmod sử dụng một phiên bản thông số kỹ thuật SemVer thoải mái hơn. Những điểm khác biệt bao gồm:

  • SemVer quy định rằng phần "bản phát hành" của phiên bản phải bao gồm 3 phân đoạn: MAJOR.MINOR.PATCH. Trong Bazel, yêu cầu này được nới lỏng để bất kỳ số lượng phân đoạn nào cũng được phép.
  • Trong SemVer, mỗi phân đoạn trong phần "bản phát hành" chỉ được là chữ số. Trong Bazel, tính năng này được nới lỏng để cho phép cả các chữ cái và ngữ nghĩa so sánh khớp với "giá trị nhận dạng" trong phần "bản phát hành trước".
  • Ngoài ra, ngữ nghĩa của các lần tăng phiên bản lớn, nhỏ và bản vá sẽ không được thực thi. (Tuy nhiên, hãy xem mức độ tương thích để biết thông tin chi tiết về cách chúng tôi biểu thị khả năng tương thích ngược.)

Mọi phiên bản SemVer hợp lệ đều là phiên bản mô-đun Bazel hợp lệ. Ngoài ra, hai phiên bản SeemVer ab so sánh a < b sẽ có cùng một yêu cầu lưu giữ dữ liệu khi chúng được so sánh với các phiên bản mô-đun Bazel.

Độ phân giải phiên bản

Vấn đề phần phụ thuộc kim cương là một vấn đề nghiêm trọng trong không gian quản lý phần phụ thuộc được tạo phiên bản. Giả sử bạn có biểu đồ phần phụ thuộc sau:

       A 1.0
      /     \
   B 1.0    C 1.1
     |        |
   D 1.0    D 1.1

Nên sử dụng phiên bản nào của D? Để giải quyết câu hỏi này, Bzlmod sử dụng thuật toán Lựa chọn phiên bản tối thiểu (MVS) được giới thiệu trong hệ thống mô-đun Go. MVS giả định rằng tất cả các phiên bản mới của một mô-đun đều có khả năng tương thích ngược và do đó chỉ cần chọn phiên bản cao nhất do bất kỳ phần phụ thuộc nào chỉ định (D 1.1 trong ví dụ của chúng tôi). Giá trị này được gọi là "tối thiểu" vì D 1.1 ở đây là phiên bản tối thiểu có thể đáp ứng các yêu cầu của chúng tôi; ngay cả khi D 1.2 trở lên, chúng tôi cũng không chọn các phiên bản đó. Điều này có thêm một lợi ích là lựa chọn phiên bản có độ chân thực caocó thể tái sản xuất.

Quá trình phân giải phiên bản được thực hiện trên máy của bạn chứ không phải do tổ chức quản lý tên miền thực hiện.

Mức độ tương thích

Lưu ý rằng giả định của MVS về khả năng tương thích ngược là có thể thực hiện được vì nó chỉ đơn giản là xem các phiên bản không tương thích ngược của một mô-đun là một mô-đun riêng biệt. Về phía SemVer, điều đó có nghĩa là A 1.x và A 2.x được coi là các mô-đun riêng biệt và có thể cùng tồn tại trong biểu đồ phần phụ thuộc đã được phân giải. Đổi lại, điều này có thể xảy ra do phiên bản lớn được mã hoá trong đường dẫn gói trong Go, do đó, sẽ không có bất kỳ xung đột thời gian biên dịch hoặc thời gian liên kết nào.

Ở Bazel, chúng tôi không có những đảm bảo như vậy. Do đó, chúng tôi cần một cách để biểu thị số "phiên bản chính" nhằm phát hiện các phiên bản không tương thích ngược. Số này được gọi là mức tương thích và được chỉ định theo mỗi phiên bản mô-đun trong lệnh module(). Với thông tin này, chúng ta có thể xảy ra lỗi khi phát hiện thấy các phiên bản của cùng một mô-đun có mức độ tương thích khác nhau tồn tại trong biểu đồ phần phụ thuộc đã phân giải.

Tên kho lưu trữ

Trong Bazel, mọi phần phụ thuộc bên ngoài đều có tên kho lưu trữ. Đôi khi, bạn có thể sử dụng cùng một phần phụ thuộc qua nhiều tên kho lưu trữ (ví dụ: cả @io_bazel_skylib@bazel_skylib đều có nghĩa là Bazel skylib) hoặc có thể sử dụng cùng một tên kho lưu trữ cho nhiều phần phụ thuộc trong các dự án khác nhau.

Trong Bzlmod, các kho lưu trữ có thể được tạo bằng các mô-đun Bazel và phần mở rộng mô-đun. Để giải quyết xung đột về tên kho lưu trữ, chúng tôi sẽ áp dụng cơ chế ánh xạ kho lưu trữ trong hệ thống mới. Dưới đây là hai khái niệm quan trọng:

  • Tên kho lưu trữ chính tắc: Tên kho lưu trữ duy nhất trên toàn cầu cho mỗi kho lưu trữ. Đây sẽ là tên thư mục có chứa kho lưu trữ.
    Mã này có cấu trúc như sau (Cảnh báo: định dạng tên chuẩn hoá không phải là một API mà bạn nên dựa vào, vì định dạng này có thể thay đổi bất cứ lúc nào):

    • Đối với kho lưu trữ mô-đun Bazel: module_name~version
      (Ví dụ. @bazel_skylib~1.0.3)
    • Đối với kho lưu trữ tiện ích mô-đun: module_name~version~extension_name~repo_name
      (Ví dụ. @rules_cc~0.0.1~cc_configure~local_config_cc)
  • Tên kho lưu trữ gốc: Tên kho lưu trữ dùng trong các tệp BUILD.bzl trong một kho lưu trữ. Cùng một phần phụ thuộc có thể có nhiều tên rõ ràng trong các kho lưu trữ khác nhau.
    Giá trị này được xác định như sau:

    • Đối với kho lưu trữ mô-đun Bazel: module_name theo mặc định hoặc tên do thuộc tính repo_name chỉ định trong bazel_dep.
    • Đối với kho lưu trữ của tiện ích mô-đun: tên kho lưu trữ được giới thiệu thông qua use_repo.

Mỗi kho lưu trữ có một từ điển ánh xạ kho lưu trữ các phần phụ thuộc trực tiếp của kho lưu trữ đó, là mục ánh xạ từ tên rõ ràng của kho lưu trữ đến tên kho lưu trữ chính tắc. Chúng tôi sử dụng bản đồ ánh xạ kho lưu trữ để phân giải tên kho lưu trữ khi tạo nhãn. Xin lưu ý rằng không có xung đột giữa tên kho lưu trữ chính tắc. Bạn có thể phát hiện trường hợp sử dụng tên kho lưu trữ rõ ràng bằng cách phân tích cú pháp tệp MODULE.bazel. Do đó, bạn có thể dễ dàng phát hiện và giải quyết xung đột mà không ảnh hưởng đến các phần phụ thuộc khác.

Phần phụ thuộc Strict

Định dạng thông số kỹ thuật mới cho phần phụ thuộc cho phép chúng tôi thực hiện các quy trình kiểm tra nghiêm ngặt hơn. Cụ thể, hiện chúng tôi thực thi việc một mô-đun chỉ có thể sử dụng các kho lưu trữ được tạo từ các phần phụ thuộc trực tiếp của mô-đun đó. Điều này giúp ngăn chặn các sự cố vô tình và khó gỡ lỗi khi có nội dung nào đó trong biểu đồ phần phụ thuộc bắc cầu thay đổi.

Phần phụ thuộc nghiêm ngặt được triển khai dựa trên tính năng liên kết kho lưu trữ. Về cơ bản, bản đồ ánh xạ kho lưu trữ cho mỗi kho lưu trữ chứa tất cả phần phụ thuộc trực tiếp của kho lưu trữ đó, mọi kho lưu trữ khác sẽ không hiển thị. Các phần phụ thuộc hiển thị cho mỗi kho lưu trữ được xác định như sau:

  • Một kho lưu trữ mô-đun Bazel có thể xem tất cả các kho lưu trữ được giới thiệu trong tệp MODULE.bazel thông qua bazel_depuse_repo.
  • Kho lưu trữ tiện ích mô-đun có thể xem tất cả các phần phụ thuộc hiển thị của mô-đun cung cấp tiện ích, cùng với tất cả các kho lưu trữ khác được tạo bởi cùng một tiện ích mô-đun.

Tổ chức quản lý tên miền

Bzlmod khám phá các phần phụ thuộc bằng cách yêu cầu thông tin về các phần phụ thuộc đó từ tổ chức quản lý tên miền Bazel. Hệ thống tên miền Bazel chỉ đơn giản là một cơ sở dữ liệu gồm các mô-đun Bazel. Hình thức đăng ký được hỗ trợ duy nhất là sổ đăng ký chỉ mục, tức là một thư mục cục bộ hoặc một máy chủ HTTP tĩnh theo một định dạng cụ thể. Trong tương lai, chúng tôi dự định hỗ trợ thêm hệ thống đăng ký mô-đun đơn, đơn giản là git repos chứa nguồn và nhật ký của dự án.

Sổ đăng ký chỉ mục

Sổ đăng ký chỉ mục là một thư mục cục bộ hoặc một máy chủ HTTP tĩnh chứa thông tin về danh sách các mô-đun, bao gồm cả trang chủ, trình bảo trì, tệp MODULE.bazel của từng phiên bản và cách tìm nạp nguồn của từng phiên bản. Đáng chú ý là ứng dụng này không cần phân phát chính các bản lưu trữ nguồn.

Tổ chức quản lý chỉ mục phải tuân theo định dạng dưới đây:

  • /bazel_registry.json: Tệp JSON chứa siêu dữ liệu về sổ đăng ký như:
    • mirrors, chỉ định danh sách bản sao để sử dụng cho kho lưu trữ nguồn.
    • module_base_path, chỉ định đường dẫn cơ sở cho các mô-đun có loại local_repository trong tệp source.json.
  • /modules: Thư mục chứa một thư mục con cho mỗi mô-đun trong sổ đăng ký này.
  • /modules/$MODULE: Thư mục chứa thư mục con cho mỗi phiên bản của mô-đun này, cũng như tệp sau:
    • metadata.json: Tệp JSON chứa thông tin về mô-đun, với các trường sau:
      • homepage: URL trang chủ của dự án.
      • maintainers: Danh sách các đối tượng JSON, mỗi đối tượng tương ứng với thông tin của một trình bảo trì mô-đun trong sổ đăng ký. Xin lưu ý rằng tên này không nhất thiết phải giống với tác giả của dự án.
      • versions: Danh sách tất cả các phiên bản của mô-đun này có trong sổ đăng ký này.
      • yanked_versions: Danh sách các phiên bản được kéo mạnh của mô-đun này. Hiện tại, tính năng này không hoạt động, nhưng trong tương lai, các phiên bản bị giật sẽ bị bỏ qua hoặc gây ra lỗi.
  • /modules/$MODULE/$VERSION: Thư mục chứa các tệp sau:
    • MODULE.bazel: Tệp MODULE.bazel của phiên bản mô-đun này.
    • source.json: Tệp JSON chứa thông tin về cách tìm nạp nguồn của phiên bản mô-đun này.
      • Loại mặc định là "lưu trữ" với các trường sau:
        • url: URL của kho lưu trữ nguồn.
        • integrity: Tổng kiểm tra Tính toàn vẹn của tài nguyên phụ của kho lưu trữ.
        • strip_prefix: Một tiền tố thư mục cần loại bỏ khi trích xuất tệp lưu trữ nguồn.
        • patches: Danh sách các chuỗi, mỗi chuỗi nêu tên một tệp bản vá để áp dụng cho bản lưu trữ đã trích xuất. Các tệp bản vá nằm trong thư mục /modules/$MODULE/$VERSION/patches.
        • patch_strip: Giống như đối số --strip của bản vá Unix.
      • Bạn có thể thay đổi loại để sử dụng đường dẫn cục bộ với các trường sau:
        • type: local_path
        • path: Đường dẫn cục bộ đến kho lưu trữ, được tính như sau:
          • Nếu đường dẫn là đường dẫn tuyệt đối, đường dẫn sẽ được sử dụng như nguyên trạng.
          • Nếu đường dẫn là đường dẫn tương đối và module_base_path là đường dẫn tuyệt đối, thì đường dẫn sẽ được phân giải thành <module_base_path>/<path>
          • Nếu đường dẫn và module_base_path đều là đường dẫn tương đối, thì đường dẫn sẽ được phân giải thành <registry_path>/<module_base_path>/<path>. Sổ đăng ký phải được lưu trữ cục bộ và được --registry=file://<registry_path> sử dụng. Nếu không, Bazel sẽ gửi một lỗi.
    • patches/: Một thư mục không bắt buộc chứa các tệp bản vá, chỉ được dùng khi source.json có loại "lưu trữ".

Cơ quan đăng ký trung tâm Bazel

Bazel Central Registry (BCR) là một tổ chức quản lý chỉ mục có tại bcr.bazel.build. Nội dung của kho lưu trữ này được kho lưu trữ GitHub bazelbuild/bazel-central-registry hỗ trợ.

BCR do cộng đồng Bazel duy trì; cộng tác viên có thể gửi yêu cầu kéo. Xem Chính sách và quy trình của Cơ quan đăng ký trung tâm Bazel.

Ngoài việc tuân theo định dạng của sổ đăng ký chỉ mục thông thường, BCR cần có tệp presubmit.yml cho mỗi phiên bản mô-đun (/modules/$MODULE/$VERSION/presubmit.yml). Tệp này chỉ định một số mục tiêu bản dựng và kiểm thử thiết yếu có thể dùng để kiểm tra tính hợp lệ của phiên bản mô-đun này. Quy trình CI của BCR sử dụng tệp này để đảm bảo khả năng tương tác giữa các mô-đun trong BCR.

Chọn tổ chức quản lý tên miền

Bạn có thể sử dụng cờ Bazel --registry lặp lại để chỉ định danh sách tổ chức quản lý tên miền cần yêu cầu mô-đun, nhờ đó, bạn có thể thiết lập dự án để tìm nạp các phần phụ thuộc từ sổ đăng ký nội bộ hoặc bên thứ ba. Tổ chức quản lý tên miền trước đó sẽ được ưu tiên. Để thuận tiện, bạn có thể đặt danh sách các cờ --registry vào tệp .bazelrc của dự án.

Tiện ích mô-đun

Tiện ích mô-đun cho phép bạn 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 hàm này tương tự như hàm của các macro WORKSPACE hiện nay, nhưng phù hợp hơn với các mô-đun và phần phụ thuộc bắc cầu.

Tiện ích mô-đun được xác định trong các tệp .bzl, giống như các quy tắc kho lưu trữ hoặc macro WORKSPACE. Các mô-đun này không được gọi trực tiếp; thay vào đó, mỗi mô-đun có thể chỉ định các phần dữ liệu được gọi là thẻ để tiện ích đọc. Sau khi quá trình phân giải phiên bản mô-đun hoàn tất, các tiện ích mô-đun sẽ chạy. Mỗi tiện ích sẽ chạy một lần sau khi phân giải mô-đun (vẫn trước khi bất kỳ bản dựng nào thực sự xảy ra) và được đọ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.

          [ A 1.1                ]
          [   * maven.dep(X 2.1) ]
          [   * maven.pom(...)   ]
              /              \
   bazel_dep /                \ bazel_dep
            /                  \
[ B 1.2                ]     [ C 1.0                ]
[   * maven.dep(X 1.2) ]     [   * maven.dep(X 2.1) ]
[   * maven.dep(Y 1.3) ]     [   * cargo.dep(P 1.1) ]
            \                  /
   bazel_dep \                / bazel_dep
              \              /
          [ D 1.4                ]
          [   * maven.dep(Z 1.4) ]
          [   * cargo.dep(Q 1.1) ]

Trong biểu đồ phần phụ thuộc mẫu ở trên, A 1.1B 1.2, v.v. là các mô-đun Bazel; bạn có thể xem mỗi mô-đun là một tệp MODULE.bazel. Mỗi mô-đun có thể chỉ định một số thẻ cho phần mở rộng mô-đun; ở đây một số thẻ được chỉ định cho phần mở rộng "maven" và một số thẻ được chỉ định cho phần mở rộng "cargo". Khi biểu đồ phần phụ thuộc này được hoàn tất (ví dụ: có thể B 1.2 thực sự có bazel_dep trên D 1.3 nhưng đã được nâng cấp lên D 1.4 do C), phần mở rộng "maven" sẽ được chạy và đọc tất cả các thẻ maven.*, sử dụng thông tin trong đó để quyết định tạo kho lưu trữ nào. Tương tự như vậy cho phần mở rộng "hàng".

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. Vì vậy, để sử dụng tiện ích trong mô-đun, trước tiên, bạn cần thêm bazel_dep vào mô-đun đó, 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, một đoạn mã từ tệp MODULE.bazel để sử dụng tiện ích "maven" giả định được xác định trong mô-đun rules_jvm_external:

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

Sau khi đưa tiện ích vào phạm vi, bạn có thể sử dụng cú pháp dấu chấm để chỉ định thẻ cho tiện ích đó. Xin lưu ý rằng các thẻ cần phải tuân theo giản đồ mà các lớp thẻ tương ứng xác định (xem định nghĩa phần mở rộng dưới đây). Dưới đây là ví dụ về cách chỉ định một số thẻ maven.depmaven.pom.

maven.dep(coord="org.junit:junit:3.0")
maven.dep(coord="com.google.guava:guava:1.2")
maven.pom(pom_xml="//:pom.xml")

Nếu tiện ích tạo ra các kho lưu trữ mà bạn muốn dùng trong mô-đun của mình, hãy dùng lệnh use_repo để khai báo các kho lưu trữ đó. Điều này nhằm đáp ứng các điều kiện phụ thuộc nghiêm ngặt và tránh xung đột tên kho lưu trữ cục bộ.

use_repo(
    maven,
    "org_junit_junit",
    guava="com_google_guava_guava",
)

Các kho lưu trữ do tiện ích tạo ra đều là một phần của API, vì vậy, từ các thẻ mà bạn đã chỉ định, bạn nên biết rằng tiện ích "maven" sẽ tạo một kho lưu trữ có tên là "org_junit_junit" và một kho lưu trữ có tên là "com_google_guava_guava". Với use_repo, bạn có thể tuỳ ý đổi tên các tên này trong phạm vi mô-đun của mình, chẳng hạn như "guava" ở đây.

Định nghĩa phần mở rộng

Phần mở rộng mô-đun được xác định tương tự như các quy tắc kho lưu trữ, bằng cách sử dụng hàm module_extension. Cả hai đều có hàm triển khai; nhưng mặc dù các quy tắc kho lưu trữ có một số thuộc tính, nhưng phần mở rộng mô-đun lại có một số tag_class, mỗi loại 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. Tiếp tục ví dụ của chúng tôi về tiện ích "maven" giả định ở trên:

# @rules_jvm_external//:extensions.bzl
maven_dep = tag_class(attrs = {"coord": attr.string()})
maven_pom = tag_class(attrs = {"pom_xml": attr.label()})
maven = module_extension(
    implementation=_maven_impl,
    tag_classes={"dep": maven_dep, "pom": maven_pom},
)

Các nội dung khai báo này làm rõ rằng bạn có thể chỉ định thẻ maven.depmaven.pom bằng giản đồ thuộc tính được xác định ở trên.

Hàm triển khai tương tự như macro WORKSPACE, ngoại trừ việc hàm này nhận được đối tượng module_ctx. Hàm này cấp quyền truy cập vào biểu đồ phần phụ thuộc và tất cả các thẻ thích hợp. 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("//:repo_rules.bzl", "maven_single_jar")
def _maven_impl(ctx):
  coords = []
  for mod in ctx.modules:
    coords += [dep.coord for dep in mod.tags.dep]
  output = ctx.execute(["coursier", "resolve", coords])  # hypothetical call
  repo_attrs = process_coursier(output)
  [maven_single_jar(**attrs) for attrs in repo_attrs]

Trong ví dụ trên, chúng ta sẽ xem qua tất cả mô-đun trong biểu đồ phần phụ thuộc (ctx.modules), mỗi mô-đun là một đối tượng bazel_module có trường tags hiển thị tất cả các thẻ maven.* trên mô-đun. Sau đó, chúng ta gọi bộ mã hoá tiện ích CLI để liên hệ với Maven và tiến hành phân giải. Cuối cùng, chúng tôi sử dụng kết quả phân giải để tạo một số kho lưu trữ bằng cách sử dụng quy tắc kho lưu trữ maven_single_jar giả định.