マクロ

問題を報告 ソースを表示 Nightly · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

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

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

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

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

シンボリック マクロの実行可能な例は、サンプル リポジトリで確認できます。

用途

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

属性

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 を使用できます。ほとんどの属性型は、属性が select を受け入れるかどうかを決定する configurable パラメータも受け入れます。属性が configurable の場合、select 以外の値は構成不可能な select として解析されます。つまり、"foo"select({"//conditions:default": "foo"}) になります。詳しくは、選択をご覧ください。

属性の継承

マクロはルール(または別のマクロ)をラップすることを目的としていることが多く、マクロの作成者は、ラップされたシンボルの属性の大部分を **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
def _my_macro_impl(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() に発信できない
  • 名前が命名スキーマに準拠するターゲットを作成する必要があります。
  • 宣言されていないか、引数として渡されていない入力ファイルを参照できない
  • 呼び出し元のプライベート ターゲットを参照できません(詳しくは、可視性とマクロをご覧ください)。

可視性とマクロ

可視性システムは、(シンボリック) マクロとその呼び出し元の両方の実装の詳細を保護します。

デフォルトでは、シンボリック マクロで作成されたターゲットはマクロ自体内では表示されますが、マクロの呼び出し元には必ずしも表示されません。マクロは、some_rule(..., visibility = visibility) のように、独自の visibility 属性の値を転送することで、ターゲットをパブリック API として「エクスポート」できます。

マクロの可視性の主なアイデアは次のとおりです。

  1. 可視性は、マクロを呼び出したパッケージではなく、ターゲットを宣言したマクロに基づいてチェックされます。

    • つまり、同じパッケージに属しているだけでは、あるターゲットが別のターゲットから見えるようにはなりません。これにより、マクロの内部ターゲットが、パッケージ内の他のマクロやトップレベル ターゲットの依存関係になるのを防ぐことができます。
  2. ルールとマクロの両方のすべての visibility 属性には、ルールまたはマクロが呼び出された場所が自動的に含まれます。

    • したがって、ターゲットは、同じマクロ(マクロ内にない場合は BUILD ファイル)で宣言された他のターゲットに対して無条件に可視になります。

実際には、マクロが visibility を設定せずにターゲットを宣言すると、ターゲットはデフォルトでマクロの内部になります。(パッケージのデフォルトの公開設定はマクロ内では適用されません)。ターゲットをエクスポートすると、マクロの呼び出し元がマクロの visibility 属性で指定した内容に加えて、マクロの呼び出し元自体のパッケージとマクロ自身のコードにもターゲットが表示されます。マクロの公開設定によって、マクロ自体を除き、マクロのエクスポートされたターゲットを表示できるユーザーが決まります。

# tool/BUILD
...
some_rule(
    name = "some_tool",
    visibility = ["//macro:__pkg__"],
)
# macro/macro.bzl

def _impl(name, visibility):
    cc_library(
        name = name + "_helper",
        ...
        # No visibility passed in. Same as passing `visibility = None` or
        # `visibility = ["//visibility:private"]`. Visible to the //macro
        # package only.
    )
    cc_binary(
        name = name + "_exported",
        deps = [
            # Allowed because we're also in //macro. (Targets in any other
            # instance of this macro, or any other macro in //macro, can see it
            # too.)
            name + "_helper",
            # Allowed by some_tool's visibility, regardless of what BUILD file
            # we're called from.
            "//tool:some_tool",
        ],
        ...
        visibility = visibility,
    )

my_macro = macro(implementation = _impl, ...)
# pkg/BUILD
load("//macro:macro.bzl", "my_macro")
...

my_macro(
    name = "foo",
    ...
)

some_rule(
    ...
    deps = [
        # Allowed, its visibility is ["//pkg:__pkg__", "//macro:__pkg__"].
        ":foo_exported",
        # Disallowed, its visibility is ["//macro:__pkg__"] and
        # we are not in //macro.
        ":foo_helper",
    ]
)

my_macrovisibility = ["//other_pkg:__pkg__"] で呼び出された場合、または //pkg パッケージが default_visibility をその値に設定した場合、//pkg:foo_exported//other_pkg/BUILD 内または //other_pkg:defs.bzl で定義されたマクロ内でも使用できますが、//pkg:foo_helper は保護されたままになります。

マクロは、visibility = ["//some_friend:__pkg__"](内部ターゲットの場合)または visibility = visibility + ["//some_friend:__pkg__"](エクスポートされたターゲットの場合)を渡すことで、ターゲットがフレンド パッケージに可視であることを宣言できます。マクロが公開可視性(visibility = ["//visibility:public"])でターゲットを宣言するのはアンチパターンです。これは、呼び出し元がより制限された可視性を指定した場合でも、ターゲットがすべてのパッケージに対して無条件に可視になるためです。

可視性のチェックはすべて、現在実行中の最も内側のシンボリック マクロに対して行われます。ただし、可視性の委任メカニズムがあります。マクロがラベルを属性値として内部マクロに渡す場合、内部マクロでのラベルの使用は外部マクロに関してチェックされます。詳しくは、公開設定のページをご覧ください。

以前のマクロは可視性システムに対して完全に透過的であり、呼び出し元の BUILD ファイルまたはシンボリック マクロの場所にあるかのように動作します。

ファイナライザーと可視性

ルール ファイナライザーで宣言されたターゲットは、通常のシンボリック マクロの可視性ルールに従うターゲットに加えて、ファイナライザー ターゲットのパッケージに可視のすべてのターゲットも参照できます。

つまり、native.existing_rules() ベースのレガシー マクロをファイナライザーに移行しても、ファイナライザーによって宣言されたターゲットは古い依存関係を引き続き認識できます。

ただし、シンボリック マクロでターゲットを宣言すると、ファイナライザーが native.existing_rules() を使用して属性をイントロスペクトできる場合でも、ファイナライザーのターゲットが可視性システムでターゲットを確認できないことがあります。

選択する

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

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

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

my_macrodeps = ["//a"] で呼び出されると、_my_macro_impldeps パラメータを select({"//conditions:default": ["//a"]}) に設定して呼び出されます。これにより、実装関数が失敗した場合(たとえば、コードが 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,
)

怠惰

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

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

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

ここでは、移行でよく発生する問題とその解決方法について説明します。

  • 以前のマクロ呼び出し glob()

glob() 呼び出しを BUILD ファイル(または BUILD ファイルから呼び出されるレガシー マクロ)に移動し、ラベルリスト属性を使用して glob() 値をシンボリック マクロに渡します。

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

可能な限り多くのロジックをネストされたシンボリック マクロにプルしますが、最上位のマクロはレガシー マクロのままにします。

  • 命名スキーマに違反するターゲットを作成するルールを呼び出すレガシー マクロ

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