マクロ

このページでは、マクロの使用の基本、一般的なユースケース、デバッグ、規則について説明します。

マクロは、ルールをインスタンス化できる BUILD ファイルから呼び出される関数です。マクロは主に、既存のルールや他のマクロのカプセル化とコードの再利用に使用されます。

マクロには、このページで説明するシンボリック マクロとレガシー マクロの 2 種類があります。可能であれば、コードの明確化のためにシンボリック マクロを使用することをおすすめします。

シンボリック マクロでは、型付き引数(マクロが呼び出された場所を基準とした文字列からラベルへの変換)と、作成されたターゲットの公開設定を制限して指定する機能が提供されます。これらは、遅延評価(今後の Bazel リリースで追加される予定)に対応するように設計されています。シンボリック マクロは、Bazel 8 でデフォルトで使用できます。このドキュメントで macros と記載されている場合は、シンボリック マクロを指しています。

用途

マクロは、attrsimplementation の 2 つの必須パラメータを指定して macro() 関数を呼び出すことで、.bzl ファイルで定義されます。

属性

attrs は、属性名と属性タイプのディクショナリを受け取り、マクロの引数を表します。2 つの一般的な属性(namevisibility)は、すべてのマクロに暗黙的に追加され、attrs に渡される辞書には含まれません。

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

属性型宣言は、パラメータ mandatorydefaultdoc を受け入れます。ほとんどの属性タイプは、configurable パラメータも受け入れます。このパラメータは、属性が select を受け入れるかどうかを決定します。属性が configurable の場合、select 以外の値は構成不可の select として解析されます。"foo"select({"//conditions:default": "foo"}) になります。詳しくは、セレクトをご覧ください。

属性の継承

重要: 属性の継承は、--experimental_enable_macro_inherit_attrs フラグによって有効にされる試験運用版の機能です。このセクションで説明する動作の一部は、この機能がデフォルトで有効になる前に変更される可能性があります。

マクロは多くの場合、ルール(または別のマクロ)をラップすることを目的としています。マクロの作成者は、ラップされたシンボルの属性の大部分を変更せずに **kwargs を使用してマクロのメインのターゲット(またはメインの内部マクロ)に転送することを望んでいます。

このパターンをサポートするために、マクロはルールまたは別のマクロから属性を継承できます。これは、ルールまたはマクロ記号macro()inherit_attrs 引数に渡すことで実現できます。(ルールまたはマクロ記号の代わりに特殊な文字列 "common" を使用して、すべての Starlark ビルドルールに定義されている共通属性を継承することもできます)。公開属性のみが継承され、マクロ独自の attrs ディクショナリの属性は、同じ名前の継承された属性をオーバーライドします。継承された属性を削除するには、attrs ディクショナリの値として None を使用します。

# macro/macro.bzl
my_macro = macro(
    inherit_attrs = native.cc_library,
    attrs = {
        # override native.cc_library's `local_defines` attribute
        local_defines = attr.string_list(default = ["FOO"]),
        # do not inherit native.cc_library's `defines` attribute
        defines = None,
    },
    ...
)

必須ではない継承属性のデフォルト値は、元の属性定義のデフォルト値に関係なく、常に None にオーバーライドされます。継承された必須ではない属性を調べたり変更したりする必要がある場合(たとえば、継承された tags 属性にタグを追加する場合)は、マクロの実装関数で None ケースを処理する必要があります。

# macro/macro.bzl
_my_macro_implementation(name, visibility, tags, **kwargs):
    # Append a tag; tags attr is an inherited non-mandatory attribute, and
    # therefore is None unless explicitly set by the caller of our macro.
    my_tags = (tags or []) + ["another_tag"]
    native.cc_library(
        ...
        tags = my_tags,
        **kwargs,
    )
    ...

実装

implementation は、マクロのロジックを含む関数を受け取ります。実装関数は、多くの場合、1 つ以上のルールを呼び出してターゲットを作成します。通常、実装関数は非公開で(先頭にアンダースコアが付いた名前が付けられます)。通常、マクロと同じ名前にしますが、接頭辞に _ を付け、末尾に _impl を付けます。

属性への参照を含む単一の引数(ctx)を取るルール実装関数とは異なり、マクロ実装関数は引数ごとにパラメータを受け付けます。

# macro/macro.bzl
def _my_macro_impl(name, visibility, 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,
        )

マクロが属性を継承する場合、その実装関数には **kwargs 残余キーワード パラメータが必要です。このパラメータは、継承されたルールまたはサブマクロを呼び出す呼び出しに転送できます。(これにより、継承元のルールまたはマクロに新しい属性が追加された場合でも、マクロが破損しないようにできます)。

宣言

マクロは、BUILD ファイル内の定義を読み込んで呼び出すことで宣言します。


# pkg/BUILD

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

これにより、ターゲット //pkg:macro_instance_cc_lib//pkg:macro_instance_test が作成されます。

ルール呼び出しと同様に、マクロ呼び出しの属性値が None に設定されている場合、その属性はマクロの呼び出し元によって省略されたものとして扱われます。たとえば、次の 2 つのマクロ呼び出しは同等です。

# pkg/BUILD
my_macro(name = "abc", srcs = ["src.cc"], deps = None)
my_macro(name = "abc", srcs = ["src.cc"])

これは通常、BUILD ファイルでは役に立ちませんが、マクロをプログラムで別のマクロ内にラップする場合に便利です。

詳細

作成されたターゲットの命名規則

シンボリック マクロによって作成されたターゲットまたはサブマクロの名前は、マクロの name パラメータと一致するか、name の前に _(推奨)、.、または - を追加する必要があります。たとえば、my_macro(name = "foo") は、foo という名前のファイルまたはターゲット、または foo_foo-foo. で始まるファイルまたはターゲット(foo_bar など)のみを作成できます。

マクロの命名規則に違反するターゲットまたはファイルは宣言できますが、ビルドすることはできず、依存関係として使用することもできません。

マクロ インスタンスと同じパッケージ内のマクロ以外のファイルとターゲットの名前は、マクロ ターゲット名と競合する名前にしない必要がありますが、この排他性は適用されません。シンボリック マクロのパフォーマンス改善として、遅延評価の実装を進めています。これは、命名スキーマに違反するパッケージでは損なわれます。

制限事項

シンボリック マクロには、従来のマクロと比較していくつかの制限があります。

シンボリック マクロ

  • name 引数と visibility 引数を受け取る必要がある
  • implementation 関数が必要です
  • 値が返されない
  • 引数を変更できない
  • 特別な finalizer マクロでない限り、native.existing_rules() を呼び出せない
  • native.package() に電話できない
  • glob() に電話できない
  • native.environment_group() に電話できない
  • 名前が命名スキーマに準拠するターゲットを作成する必要があります。
  • 宣言されていない入力ファイルや引数として渡されていない入力ファイルを参照することはできません(詳細については、公開設定とマクロをご覧ください)。

可視性とマクロ

Bazel での公開設定の詳細については、公開設定をご覧ください。

ターゲットの公開設定

デフォルトでは、シンボリック マクロによって作成されたターゲットは、マクロを定義する .bzl ファイルを含むパッケージでのみ表示されます。特に、呼び出し元がマクロの .bzl ファイルと同じパッケージにある場合を除き、呼び出し元には表示されません

シンボリック マクロの呼び出し元にターゲットを表示するには、visibility = visibility をルールまたは内部マクロに渡します。ターゲットの公開範囲を広げて(公開に設定して)、追加のパッケージでターゲットを表示することもできます。

パッケージのデフォルトの公開設定(package() で宣言されている)は、デフォルトで最外側のマクロの visibility パラメータに渡されますが、その visibility をインスタンス化するターゲットに渡すかどうかはマクロに任されています。

依存関係の可視性

マクロの実装で参照されるターゲットは、そのマクロの定義に表示されている必要があります。公開設定は次のいずれかの方法で指定できます。

  • ターゲットは、ラベル、ラベルリスト、ラベルキーまたはラベル値の辞書属性を介して明示的にマクロに渡されると、マクロに表示されます。

# pkg/BUILD
my_macro(... deps = ["//other_package:my_tool"] )
  • ... または属性のデフォルト値として指定します。
# my_macro:macro.bzl
my_macro = macro(
  attrs = {"deps" : attr.label_list(default = ["//other_package:my_tool"])},
  ...
)
  • マクロを定義する .bzl ファイルを含むパッケージに対してターゲットが公開されている場合、マクロにも公開されます。
# other_package/BUILD
# Any macro defined in a .bzl file in //my_macro package can use this tool.
cc_binary(
    name = "my_tool",
    visibility = "//my_macro:\\__pkg__",
)

選択

属性が configurable(デフォルト)で、その値が None でない場合、マクロ実装関数は、属性値が単純な select でラップされていると見なします。これにより、属性値が select になる可能性があることをマクロ作成者が想定していないバグを簡単に検出できます。

たとえば、次のマクロについて考えてみましょう。

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

my_macrodeps = ["//a"] で呼び出されると、deps パラメータが select({"//conditions:default": ["//a"]}) に設定された _my_macro_impl が呼び出されます。これが原因で実装関数が失敗した場合(たとえば、コードが deps[0] のように値にインデックスを設定しようとしたが、select では許可されない場合など)、マクロ作成者は、select と互換性のあるオペレーションのみを使用するようにマクロを書き換えるか、属性を構成不可(attr.label_list(configurable = False))としてマークできます。後者の場合、ユーザーは select 値を渡すことが許可されなくなります。

ルール ターゲットは、この変換を逆にして、単純な select を無条件の値として格納します。上記の例では、_my_macro_impl がルール ターゲット my_rule(..., deps = deps) を宣言すると、そのルール ターゲットの deps["//a"] として格納されます。これにより、select ラップによって、マクロによってインスタンス化されたすべてのターゲットに単純な select 値が保存されることがなくなります。

構成可能な属性の値が None の場合、select でラップされません。これにより、my_attr == None などのテストが引き続き機能し、属性が計算されたデフォルトを含むルールに転送されたときに、ルールが適切に動作します(つまり、属性がまったく渡されていない場合と同じように動作します)。属性が None 値を取得できるとは限りません。ただし、attr.label() タイプと、継承された必須でない属性の場合は可能です。

ファイナライザ

ルール ファイナライザは、BUILD ファイル内の字句位置に関係なく、ファイナライザ以外のすべてのターゲットが定義された後、パッケージの読み込みの最終段階で評価される特別な記号マクロです。通常のシンボリック マクロとは異なり、ファイナライザは native.existing_rules() を呼び出すことができます。この場合、従来のマクロとは動作が少し異なります。ファイナライザ以外のルール ターゲットのセットのみが返されます。ファイナライザは、そのセットの状態をアサートしたり、新しいターゲットを定義したりできます。

ファイナライザを宣言するには、finalizer = True を指定して macro() を呼び出します。

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

怠惰

重要: Google では現在、遅延マクロ展開と評価の実装を進めています。この機能はまだご利用いただけません。

現在、すべてのマクロは BUILD ファイルが読み込まれるとすぐに評価されます。これにより、関連性のないコストの高いマクロも含まれているパッケージ内のターゲットのパフォーマンスに悪影響が及ぶ可能性があります。今後、ファイナライザ以外のシンボリック マクロは、ビルドに必要な場合にのみ評価されます。接頭辞命名スキーマは、リクエストされたターゲットに基づいて展開するマクロを Bazel が決定するのに役立ちます。

移行のトラブルシューティング

移行に関する一般的な問題とその解決方法は次のとおりです。

  • 従来のマクロが glob() を呼び出す

glob() 呼び出しを BUILD ファイル(または BUILD ファイルから呼び出される以前のマクロ)に移動し、label-list 属性を使用して glob() 値をシンボリック マクロに渡します。

# BUILD file
my_macro(
    ...,
    deps = glob(...),
)
  • 以前のマクロに、有効な Starlark attr 型ではないパラメータが含まれています。

ネストされたシンボリック マクロにできるだけ多くのロジックを組み込みますが、最上位のマクロはレガシー マクロのままにします。

  • 以前のマクロが、命名スキーマに違反するターゲットを作成するルールを呼び出す

問題ありません。ただし、「問題のある」ターゲットに依存しないでください。命名チェックは無視されます。