Macro

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

Trang này trình bày những thông tin cơ bản về cách sử dụng macro và trình bày các trường hợp sử dụng, gỡ lỗi và quy ước thông thường.

Macro là một hàm được gọi từ tệp BUILD có thể tạo thực thể cho các quy tắc. Macro chủ yếu được dùng để đóng gói và sử dụng lại mã của các quy tắc hiện có cũng như các macro khác. Vào cuối giai đoạn tải, các macro không còn tồn tại nữa và Bazel chỉ thấy tập hợp cụ thể các quy tắc tạo thực thể.

Cách sử dụng

Trường hợp sử dụng điển hình cho macro là khi bạn muốn sử dụng lại quy tắc.

Ví dụ: quy tắc tạo trong tệp BUILD sẽ tạo tệp bằng //:generator với đối số some_arg được mã hoá cứng trong lệnh:

genrule(
    name = "file",
    outs = ["file.txt"],
    cmd = "$(location //:generator) some_arg > $@",
    tools = ["//:generator"],
)

Nếu muốn tạo thêm tệp có các đối số khác nhau, bạn nên trích xuất mã này thành một hàm macro. Hãy gọi macro là file_generator, macro này có các tham số namearg. Thay thế quy tắc tạo bằng:

load("//path:generator.bzl", "file_generator")

file_generator(
    name = "file",
    arg = "some_arg",
)

file_generator(
    name = "file-two",
    arg = "some_arg_two",
)

file_generator(
    name = "file-three",
    arg = "some_arg_three",
)

Ở đây, bạn tải biểu tượng file_generator từ tệp .bzl nằm trong gói //path. Bằng cách đặt các định nghĩa hàm macro vào một tệp .bzl riêng biệt, bạn đảm bảo các tệp BUILD luôn gọn gàng và mang tính khai báo. Bạn có thể tải tệp .bzl từ bất kỳ gói nào trong không gian làm việc.

Cuối cùng, trong path/generator.bzl, hãy viết định nghĩa của macro để đóng gói và tham số hoá định nghĩa quy tắc tạo ban đầu:

def file_generator(name, arg, visibility=None):
  native.genrule(
    name = name,
    outs = [name + ".txt"],
    cmd = "$(location //:generator) %s > $@" % arg,
    tools = ["//:generator"],
    visibility = visibility,
  )

Bạn cũng có thể sử dụng macro để xâu chuỗi các quy tắc lại với nhau. Ví dụ này cho thấy quy tắc tạo chuỗi theo chuỗi, trong đó quy tắc tạo sử dụng kết quả của quy tắc tạo trước trước làm dữ liệu đầu vào:

def chained_genrules(name, visibility=None):
  native.genrule(
    name = name + "-one",
    outs = [name + ".one"],
    cmd = "$(location :tool-one) $@",
    tools = [":tool-one"],
    visibility = ["//visibility:private"],
  )

  native.genrule(
    name = name + "-two",
    srcs = [name + ".one"],
    outs = [name + ".two"],
    cmd = "$(location :tool-two) $< $@",
    tools = [":tool-two"],
    visibility = visibility,
  )

Ví dụ này chỉ gán giá trị chế độ hiển thị cho quy tắc tạo sinh thứ hai. Điều này cho phép tác giả vĩ mô ẩn kết quả của các quy tắc trung gian để các mục tiêu khác không phụ thuộc vào các mục tiêu khác trong không gian làm việc.

Macro mở rộng

Khi bạn muốn tìm hiểu chức năng của một macro, hãy sử dụng lệnh query với --output=build để xem dạng mở rộng:

$ bazel query --output=build :file
# /absolute/path/test/ext.bzl:42:3
genrule(
  name = "file",
  tools = ["//:generator"],
  outs = ["//test:file.txt"],
  cmd = "$(location //:generator) some_arg > $@",
)

Tạo thực thể cho các quy tắc gốc

Bạn có thể tạo thực thể cho các quy tắc gốc (quy tắc không cần câu lệnh load()) từ mô-đun gốc:

def my_macro(name, visibility=None):
  native.cc_library(
    name = name,
    srcs = ["main.cc"],
    visibility = visibility,
  )

Nếu bạn cần biết tên gói (ví dụ: tệp BUILD nào đang gọi macro), hãy sử dụng hàm native.package_name(). Xin lưu ý rằng bạn chỉ có thể dùng native trong tệp .bzl chứ không phải trong tệp BUILD.

Độ phân giải nhãn trong macro

Vì macro được đánh giá trong giai đoạn tải, nên các chuỗi nhãn như "//foo:bar" xuất hiện trong macro sẽ được diễn giải tương ứng với tệp BUILD có sử dụng macro thay vì tương ứng với tệp .bzl mà macro được xác định. Hành vi này thường không được mong muốn đối với các macro được dùng trong các kho lưu trữ khác, chẳng hạn như do chúng thuộc bộ quy tắc Starlark đã phát hành.

Để có hành vi tương tự như quy tắc Starlark, hãy gói các chuỗi nhãn bằng hàm khởi tạo Label:

# @my_ruleset//rules:defs.bzl
def my_cc_wrapper(name, deps = [], **kwargs):
  native.cc_library(
    name = name,
    deps = deps + select({
      # Due to the use of Label, this label is resolved within @my_ruleset,
      # regardless of its site of use.
      Label("//config:needs_foo"): [
        # Due to the use of Label, this label will resolve to the correct target
        # even if the canonical name of @dep_of_my_ruleset should be different
        # in the main repo, such as due to repo mappings.
        Label("@dep_of_my_ruleset//tools:foo"),
      ],
      "//conditions:default": [],
    }),
    **kwargs,
  )

Gỡ lỗi

  • bazel query --output=build //my/path:all sẽ cho bạn biết tệp BUILD trông như thế nào sau khi đánh giá. Tất cả các macro, khối cầu, vòng lặp đều được mở rộng. Giới hạn đã biết: biểu thức select hiện không hiển thị trong kết quả.

  • Bạn có thể lọc kết quả theo generator_function (hàm đã tạo quy tắc) hoặc generator_name (thuộc tính tên của macro): bash $ bazel query --output=build 'attr(generator_function, my_macro, //my/path:all)'

  • Để tìm hiểu chính xác nơi quy tắc foo được tạo trong tệp BUILD, bạn có thể thử thủ thuật sau. Chèn dòng này gần đầu tệp BUILD: cc_library(name = "foo"). Chạy Bazel. Bạn sẽ nhận được một ngoại lệ khi tạo quy tắc foo (do xung đột về tên). Trường hợp này sẽ hiển thị cho bạn toàn bộ dấu vết ngăn xếp.

  • Bạn cũng có thể sử dụng lệnh in để gỡ lỗi. Cửa sổ này hiển thị thông điệp dưới dạng một dòng nhật ký DEBUG trong giai đoạn tải. Ngoại trừ một số ít trường hợp, hãy xoá các lệnh gọi print hoặc đặt các lệnh gọi này có điều kiện trong tham số debugging mặc định là False trước khi gửi mã đến kho.

Lỗi

Nếu bạn muốn báo lỗi, hãy sử dụng hàm fail (không thành công). Giải thích rõ ràng cho người dùng về sự cố và cách khắc phục tệp BUILD. Không phát hiện được lỗi.

def my_macro(name, deps, visibility=None):
  if len(deps) < 2:
    fail("Expected at least two values in deps")
  # ...

Hội nghị

  • Tất cả hàm công khai (hàm không bắt đầu bằng dấu gạch dưới) có quy tắc tạo thực thể phải có đối số name. Đối số này không nên là không bắt buộc (đừng cung cấp giá trị mặc định).

  • Các hàm công khai phải sử dụng một chuỗi tài liệu theo quy ước Python.

  • Trong các tệp BUILD, đối số name của các macro phải là đối số từ khoá (không phải đối số vị trí).

  • Thuộc tính name của các quy tắc do macro tạo phải bao gồm đối số tên làm tiền tố. Ví dụ: macro(name = "foo") có thể tạo một cc_library foo và một quy tắc tạo sinh foo_gen.

  • Trong hầu hết các trường hợp, tham số không bắt buộc phải có giá trị mặc định là None. None có thể được chuyển trực tiếp đến các quy tắc gốc, xử lý nó giống như khi bạn chưa truyền bất kỳ đối số nào. Do đó, bạn không cần phải thay thế bằng 0, False hay [] cho mục đích này. Thay vào đó, macro phải tuân theo các quy tắc mà nó tạo ra, vì các giá trị mặc định của chúng có thể phức tạp hoặc có thể thay đổi theo thời gian. Ngoài ra, một tham số được đặt rõ ràng thành giá trị mặc định sẽ khác với tham số không bao giờ được đặt (hoặc được đặt thành None) khi được truy cập thông qua ngôn ngữ truy vấn hoặc nội bộ của hệ thống xây dựng.

  • Macro phải có một đối số visibility không bắt buộc.