Bazel との日常的なやり取りは、主に build
、test
、run
のいくつかのコマンドによって行われます。ただし、これらは限られていると感じる場合もあります。たとえば、パッケージをリポジトリに 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",
)
staging
ターゲットで bazel build
を使用すると、k8s_object
ルールによって標準の 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
コマンドを提供できます。