マクロを使ってカスタム動詞を作成する

問題を報告 ソースを表示 Nightly · 7.4 .

Bazel との日常的な操作は、主に buildtestrun のいくつかのコマンドを使用して行います。ただし、パッケージをリポジトリに push したり、エンドユーザー向けのドキュメントを公開したり、Kubernetes でアプリケーションをデプロイしたりする必要がある場合は、制限を感じるかもしれません。しかし、Bazel には publish コマンドや deploy コマンドがありません。これらのアクションはどこに収まるのでしょうか?

bazel run コマンド

Bazel は完全性、再現性、増分性に重点を置いているため、build コマンドと test コマンドは上記のタスクには役立ちません。これらのアクションは、ネットワーク アクセスが制限されたサンドボックスで実行される可能性があり、bazel build のたびに再実行される保証はありません。

代わりに、bazel run に依存します。これは、副作用を発生させたいタスクの主力となるものです。Bazel ユーザーは、実行可能ファイルを作成するルールに慣れています。ルールの作成者は、一般的なパターンセットに従ってこれを「カスタム動詞」に拡張できます。

実際の環境: rules_k8s

たとえば、Bazel の Kubernetes ルールである rules_k8s について考えてみましょう。次のようなターゲットがあるとします。

# BUILD file in //application/k8s
k8s_object(
    name = "staging",
    kind = "deployment",
    cluster = "testing",
    template = "deployment.yaml",
)

k8s_object ルールは、staging ターゲットで bazel build が使用されている場合に、標準の Kubernetes YAML ファイルをビルドします。ただし、k8s_object マクロによって staging.apply:staging.delete などの名前で追加のターゲットも作成されます。これらのスクリプトは、これらのアクションを実行するようにビルドされます。bazel run staging.apply で実行すると、独自の bazel k8s-apply コマンドまたは bazel k8s-delete コマンドのように動作します。

別の例: ts_api_guardian_test

このパターンは Angular プロジェクトでも見られます。ts_api_guardian_test マクロは 2 つのターゲットを生成します。1 つ目は、生成された出力を「ゴールド」ファイル(期待される出力を含むファイル)と比較する標準の nodejs_test ターゲットです。これは、通常の bazel test 呼び出しでビルドして実行できます。angular-cli では、bazel test //etc/api:angular_devkit_core_api を使用してこのようなターゲットを 1 つ実行できます。

時間の経過とともに、このゴールドファイルを正当な理由で更新する必要が生じることがあります。これを手動で更新すると面倒でエラーが発生しやすいため、このマクロではサンプル ファイルと比較するのではなく、ゴールデン ファイルを更新する nodejs_binary ターゲットも用意されています。実際には、同じテスト スクリプトを、呼び出し方法に応じて「検証」モードまたは「承認」モードで実行するように記述できます。これはすでに学習したパターンに従います。ネイティブの bazel test-accept コマンドはありませんが、bazel run //etc/api:angular_devkit_core_api.accept でも同じ効果が得られます。

このパターンは非常に強力で、認識できるようになれば非常に一般的であることがわかります。

独自のルールを調整する

このパターンの中核となるのがマクロです。マクロはルールと同様に使用されますが、複数のターゲットを作成できます。通常は、メインのビルド アクションを実行するターゲットを、指定した名前で作成します。通常のバイナリ、Docker イメージ、ソースコードのアーカイブなどをビルドします。このパターンでは、追加のターゲットが作成され、結果のバイナリの公開や想定されるテスト出力の更新など、プライマリ ターゲットの出力に基づいて副作用を実行するスクリプトが生成されます。

わかりやすいように、Sphinx でウェブサイトを生成する架空のルールをマクロでラップし、準備ができたらユーザーが公開できるようにする追加のターゲットを作成します。Sphinx でウェブサイトを生成する既存のルールは次のとおりです。

_sphinx_site = rule(
     implementation = _sphinx_impl,
     attrs = {"srcs": attr.label_list(allow_files = [".rst"])},
)

次に、次のようなルールについて考えてみましょう。このルールは、実行時に生成されたページを公開するスクリプトをビルドします。

_sphinx_publisher = rule(
    implementation = _publish_impl,
    attrs = {
        "site": attr.label(),
        "_publisher": attr.label(
            default = "//internal/sphinx:publisher",
            executable = True,
        ),
    },
    executable = True,
)

最後に、次のマクロを定義して、上記の両方のルールのターゲットを一緒に作成します。

def sphinx_site(name, srcs = [], **kwargs):
    # This creates the primary target, producing the Sphinx-generated HTML.
    _sphinx_site(name = name, srcs = srcs, **kwargs)
    # This creates the secondary target, which produces a script for publishing
    # the site generated above.
    _sphinx_publisher(name = "%s.publish" % name, site = name, **kwargs)

BUILD ファイルで、プライマリ ターゲットを作成する場合と同じようにマクロを使用します。

sphinx_site(
    name = "docs",
    srcs = ["index.md", "providers.md"],
)

この例では、マクロが標準の単一の Bazel ルールであるかのように、「docs」ターゲットが作成されます。このルールを構築すると、構成を生成し、Sphinx を実行して、手動検査が可能な HTML サイトを生成します。ただし、さらに「docs.publish」ターゲットも作成され、それによってサイトを公開するためのスクリプトが作成されます。プライマリ ターゲットの出力を確認したら、架空の bazel publish コマンドと同様に、bazel run :docs.publish を使用して一般公開できます。

_sphinx_publisher ルールの実装はすぐにはわかりません。多くの場合、このようなアクションではランチャー シェル スクリプトが作成されます。この方法では通常、ctx.actions.expand_template を使用して非常にシンプルなシェル スクリプトを作成します。この場合、プライマリ ターゲットの出力パスを指定してパブリッシャー バイナリを呼び出します。これにより、パブリッシャーの実装は汎用性を維持でき、_sphinx_site ルールは HTML のみを生成できます。この小さなスクリプトだけで、2 つを組み合わせることができます。

rules_k8s では、これがまさに .apply の役割です。expand_template は、apply.sh.tpl に基づいて非常にシンプルな Bash スクリプトを作成し、プライマリ ターゲットの出力で kubectl を実行します。このスクリプトは、bazel run :staging.apply を使用してビルドおよび実行でき、実質的に k8s_object ターゲットに k8s-apply コマンドを提供できます。