Macro

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

Trang này trình bày những thông tin cơ bản về cách sử dụng macro, bao gồm 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 bản sao 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 sẽ không còn tồn tại và Bazel chỉ thấy tập hợp quy tắc cụ thể được tạo bản sao.

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 một quy tắc.

Ví dụ: genrule trong tệp BUILD tạo một tệp bằng cách sử dụ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 vào một hàm macro. Hãy gọi macro là file_generator, có các tham số namearg. Thay thế genrule bằng nội dung sau:

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

Tại đâ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 vĩ mô trong một tệp .bzl riêng biệt, bạn có thể giữ cho các tệp BUILD của mình luôn sạch sẽ 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 genrule 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 để liên kết các quy tắc với nhau. Ví dụ này cho thấy các genrule được nối với nhau, trong đó một genrule sử dụng đầu ra của một genrule trước đó làm đầ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 genrule thứ hai. Điều này cho phép tác giả macro ẩn kết quả của các quy tắc trung gian khỏi các mục tiêu khác trong không gian làm việc.

Mở rộng macro

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 bản sao cho các quy tắc gốc

Bạn có thể tạo bản sao 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(). Lưu ý rằng bạn chỉ có thể sử dụng native trong tệp .bzl chứ không thể sử dụng trong tệp BUILD.

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

Vì các 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 được diễn giải tương ứng với tệp BUILD mà macro được sử dụng 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 mong muốn đối với các macro dùng trong các kho lưu trữ khác, chẳng hạn như vì các macro đó là một phần của quy tắc Starlark đã xuất bản.

Để có hành vi tương tự như đối với các 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 thấy giao diện của tệp BUILD sau khi đánh giá. Tất cả các macro, glob, vòng lặp đều được mở rộng. Hạn chế đã biết: Biểu thức select hiện không xuất hiện trong kết quả.

  • Bạn có thể lọc kết quả dựa trên generator_function (hàm tạo ra các 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 vị trí tạo quy tắc foo trong tệp BUILD, bạn có thể thử thủ thuật sau. Chèn dòng này vào 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 tên), ngoại lệ này sẽ hiển thị dấu vết ngăn xếp đầy đủ.

  • Bạn cũng có thể sử dụng print để gỡ lỗi. Phương thức này hiển thị thông báo dưới dạng 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 đó theo điều kiện trong thông số debugging mặc định là False trước khi gửi mã đến kho lưu trữ.

Lỗi

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

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

Quy ước

  • Tất cả hàm công khai (hàm không bắt đầu bằng dấu gạch dưới) tạo bản sao cho quy tắc đều phải có đối số name. Đối số này không được là đối số tuỳ chọn (không được cung cấp giá trị mặc định).

  • Các hàm công khai phải sử dụng docstring theo các quy ước của Python.

  • Trong tệp BUILD, đối số name của 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 ra phải bao gồm đối số tên làm tiền tố. Ví dụ: macro(name = "foo") có thể tạo cc_library foo và genrule foo_gen.

  • Trong hầu hết các trường hợp, các tham số không bắt buộc phải có giá trị mặc định là None. Bạn có thể truyền trực tiếp None đến các quy tắc gốc. Các quy tắc này sẽ xử lý None giống như khi bạn không truyền bất kỳ đối số nào. Do đó, bạn không cần thay thế lớp này bằng 0, False hoặc [] cho mục đích này. Thay vào đó, macro sẽ trì hoãn các quy tắc mà nó tạo ra, vì các quy tắc mặc định 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 truy cập thông qua ngôn ngữ truy vấn hoặc nội bộ hệ thống xây dựng.

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