Macro

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.

Macro có hai loại: macro biểu tượng (được mô tả trên trang này) và macro cũ. Nếu có thể, bạn nên sử dụng các macro tượng trưng để mã rõ ràng hơn.

Macro tượng trưng cung cấp các đối số đã nhập (chuỗi đến chuyển đổi nhãn, tương ứng với vị trí macro được gọi) cũng như khả năng hạn chế và chỉ định chế độ hiển thị của các mục tiêu được tạo. Các lớp này được thiết kế để có thể chấp nhận việc đánh giá lười biếng (sẽ được thêm vào bản phát hành Bazel trong tương lai). Theo mặc định, các macro tượng trưng có sẵn trong Bazel 8. Khi tài liệu này đề cập đến macros, tức là đề cập đến macro tượng trưng.

Cách sử dụng

Macro được xác định trong tệp .bzl bằng cách gọi hàm macro() với 2 tham số: attrsimplementation.

Thuộc tính

attrs chấp nhận từ điển tên thuộc tính cho loại thuộc tính, đại diện cho các đối số của macro. Hai thuộc tính phổ biến – tên và chế độ hiển thị – được ngầm thêm vào tất cả các macro và không có trong từ điển được truyền vào thuộc tính.

# macro/macro.bzl
my_macro = macro(
    attrs = {
        "deps": attr.label_list(mandatory = True, doc = "The dependencies passed to the inner cc_binary and cc_test targets"),
        "create_test": attr.bool(default = False, configurable = False, doc = "If true, creates a test target"),
    },
    implementation = _my_macro_impl,
)

Nội dung khai báo loại thuộc tính chấp nhận các tham số, mandatory, defaultdoc. Hầu hết các loại thuộc tính cũng chấp nhận tham số configurable. Tham số này sẽ xác định xem thuộc tính có chấp nhận select hay không. Nếu thuộc tính là configurable, thì thuộc tính đó sẽ phân tích cú pháp các giá trị không phải select dưới dạng select không thể định cấu hình – "foo" sẽ trở thành select({"//conditions:default": "foo"}). Tìm hiểu thêm trong phần chọn.

Triển khai

implementation chấp nhận một hàm chứa logic của macro. Các hàm triển khai thường tạo mục tiêu bằng cách gọi một hoặc nhiều quy tắc và thường là riêng tư (được đặt tên bằng dấu gạch dưới ở đầu). Thông thường, các macro này được đặt tên giống với macro nhưng có tiền tố là _ và có hậu tố là _impl.

Không giống như các hàm triển khai quy tắc, hàm triển khai macro chấp nhận một tham số cho mỗi đối số. Các hàm này nhận một đối số duy nhất (ctx) chứa tham chiếu đến các thuộc tính.

# macro/macro.bzl
def _my_macro_impl(name, deps, create_test):
    cc_library(
        name = name + "_cc_lib",
        deps = deps,
    )

    if create_test:
        cc_test(
            name = name + "_test",
            srcs = ["my_test.cc"],
            deps = deps,
        )

Khai báo

Bạn có thể khai báo macro bằng cách tải và gọi định nghĩa của macro trong tệp BUILD.


# pkg/BUILD

my_macro(
    name = "macro_instance",
    deps = ["src.cc"] + select(
        {
            "//config_setting:special": ["special_source.cc"],
            "//conditions:default": [],
        },
    ),
    create_tests = True,
)

Thao tác này sẽ tạo các mục tiêu //pkg:macro_instance_cc_lib//pkg:macro_instance_test.

Thông tin chi tiết

quy ước đặt tên cho các mục tiêu đã tạo

Tên của mọi mục tiêu hoặc macro con do macro tượng trưng tạo ra phải khớp với tham số name của macro hoặc phải có tiền tố là name, theo sau là _ (ưu tiên), . hoặc -. Ví dụ: my_macro(name = "foo") chỉ có thể tạo các tệp hoặc mục tiêu có tên foo hoặc có tiền tố là foo_, foo- hoặc foo., ví dụ: foo_bar.

Bạn có thể khai báo các mục tiêu hoặc tệp vi phạm quy ước đặt tên macro, nhưng không thể tạo và không thể dùng làm phần phụ thuộc.

Các tệp và mục tiêu không phải macro trong cùng một gói với một thực thể macro không được có tên xung đột với tên mục tiêu macro tiềm năng, mặc dù tính chất độc quyền này không được thực thi. Chúng tôi đang trong quá trình triển khai tính năng đánh giá lười để cải thiện hiệu suất cho các macro biểu tượng. Tính năng này sẽ bị hạn chế trong các gói vi phạm giản đồ đặt tên.

quy định hạn chế

Macro biểu tượng có một số hạn chế bổ sung so với macro cũ.

Macro tượng trưng

  • phải nhận một đối số name và một đối số visibility
  • phải có hàm implementation
  • có thể không trả về giá trị
  • không được thay đổi args của chúng
  • không được gọi native.existing_rules() trừ phi đó là các macro finalizer đặc biệt
  • không được gọi native.package()
  • không được gọi glob()
  • không được gọi native.environment_group()
  • phải tạo các mục tiêu có tên tuân thủ giản đồ đặt tên
  • không thể tham chiếu đến các tệp đầu vào không được khai báo hoặc được chuyển vào làm đối số (xem chế độ hiển thị để biết thêm chi tiết).

Chế độ hiển thị

VIỆC CẦN LÀM: Mở rộng phần này

Khả năng hiển thị mục tiêu

Theo mặc định, các mục tiêu do các macro tượng trưng tạo sẽ hiển thị với gói tạo ra các mục tiêu đó. Các thuộc tính này cũng chấp nhận thuộc tính visibility. Thuộc tính này có thể mở rộng chế độ hiển thị đó cho phương thức gọi của macro (bằng cách truyền thuộc tính visibility trực tiếp từ lệnh gọi macro đến mục tiêu đã tạo) và đến các gói khác (bằng cách chỉ định rõ các gói đó trong chế độ hiển thị của mục tiêu).

Mức độ hiển thị phần phụ thuộc

Macro phải có quyền truy cập vào các tệp và mục tiêu mà chúng tham chiếu đến. Họ có thể làm như vậy theo một trong những cách sau:

  • Được truyền vào một cách rõ ràng dưới dạng giá trị attr cho macro

# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
  • Giá trị mặc định ngầm ẩn của giá trị attr
# my_macro:macro.bzl
my_macro = macro(
  attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])} )
  • Đã hiển thị cho định nghĩa macro
# other_package/BUILD
cc_binary(
    name = "my_tool",
    visibility = "//my_macro:\\__pkg__",
)

Chọn

Nếu một thuộc tính là configurable, thì hàm triển khai macro sẽ luôn xem giá trị thuộc tính là select. Ví dụ: hãy xem xét macro sau:

my_macro = macro(
    attrs = {"deps": attr.label_list()},  # configurable unless specified otherwise
    implementation = _my_macro_impl,
)

Nếu my_macro được gọi bằng deps = ["//a"], thì _my_macro_impl sẽ được gọi với tham số deps được đặt thành select({"//conditions:default": ["//a"]}).

Quy tắc nhắm mục tiêu đảo ngược phép biến đổi này và lưu trữ các select tầm thường dưới dạng giá trị vô điều kiện. Trong ví dụ này, nếu _my_macro_impl khai báo một mục tiêu quy tắc my_rule(..., deps = deps), thì deps của mục tiêu quy tắc đó sẽ được lưu trữ dưới dạng ["//a"].

Phương thức hoàn tất

Trình kết thúc quy tắc là một macro biểu tượng đặc biệt – bất kể vị trí từ vựng của macro đó trong tệp BUILD – được đánh giá ở giai đoạn cuối cùng của quá trình tải gói, sau khi tất cả các mục tiêu không phải là trình kết thúc đã được xác định. Không giống như các macro tượng trưng thông thường, trình kết thúc có thể gọi native.existing_rules(), trong đó trình kết thúc hoạt động hơi khác so với trong các macro cũ: trình kết thúc chỉ trả về tập hợp các mục tiêu quy tắc không phải là trình kết thúc. Phương thức hoàn tất có thể xác nhận trạng thái của tập hợp đó hoặc xác định các mục tiêu mới.

Để khai báo một trình hoàn tất, hãy gọi macro() bằng finalizer = True:

def _my_finalizer_impl(name, visibility, tags_filter):
    for r in native.existing_rules().values():
        for tag in r.get("tags", []):
            if tag in tags_filter:
                my_test(
                    name = name + "_" + r["name"] + "_finalizer_test",
                    deps = [r["name"]],
                    data = r["srcs"],
                    ...
                )
                continue

my_finalizer = macro(
    attrs = {"tags_filter": attr.string_list(configurable = False)},
    implementation = _impl,
    finalizer = True,
)

Lười biếng

LƯU Ý QUAN TRỌNG: Chúng tôi đang trong quá trình triển khai việc đánh giá và mở rộng macro tải từng phần. Tính năng này chưa được hỗ trợ.

Hiện tại, tất cả các macro đều được đánh giá ngay khi tệp BUILD được tải, điều này có thể ảnh hưởng tiêu cực đến hiệu suất của các mục tiêu trong các gói cũng có các macro không liên quan và tốn kém. Trong tương lai, các macro biểu tượng không phải là trình hoàn thiện sẽ chỉ được đánh giá nếu cần thiết cho bản dựng. Giản đồ đặt tên tiền tố giúp Bazel xác định macro nào cần mở rộng theo mục tiêu được yêu cầu.

Khắc phục sự cố di chuyển

Dưới đây là một số vấn đề thường gặp khi di chuyển và cách khắc phục.

  • Lệnh gọi macro cũ glob()

Di chuyển lệnh gọi glob() đến tệp BUILD (hoặc đến một macro cũ được gọi từ tệp BUILD) và chuyển giá trị glob() vào macro biểu tượng bằng cách sử dụng thuộc tính danh sách nhãn:

# BUILD file
my_macro(
    ...,
    deps = glob(...),
)
  • Macro cũ có một tham số không phải là loại attr starlark hợp lệ.

Kéo nhiều logic nhất có thể vào một macro biểu tượng lồng nhau, nhưng hãy giữ macro cấp cao nhất là macro cũ.

  • Macro cũ gọi quy tắc tạo mục tiêu phá vỡ giản đồ đặt tên

Không sao, chỉ cần không phụ thuộc vào mục tiêu "vi phạm". Quy trình kiểm tra tên sẽ bị bỏ qua.