Starlark は、元々 Bazel で使用するために開発された Python に似た
構成言語で、他のツールでも採用されています
。Bazel の BUILD ファイルと .bzl ファイルは、Starlark の方言である「ビルド言語」で記述されていますが、特に機能が Bazel の組み込み部分または「ネイティブ」部分ではなくビルド言語で表現されていることを強調する場合に、単に「Starlark」と呼ばれることがよくあります。Bazel は、glob、genrule、java_binary など、ビルド関連の多数の関数でコア言語を拡張します。
詳細については、 Bazel と Starlark のドキュメントをご覧ください。新しいルールセットの出発点として、 Rules SIG テンプレート をご覧ください。
空のルール
最初のルールを作成するには、foo.bzl ファイルを作成します。
def _foo_binary_impl(ctx):
pass
foo_binary = rule(
implementation = _foo_binary_impl,
)
rule 関数を呼び出す場合は、コールバック関数を定義する必要があります。ロジックはそこに記述しますが、今のところ関数は空のままでかまいません。The ctx 引数
は、ターゲットに関する情報を提供します。
ルールを読み込んで、BUILD ファイルから使用できます。
同じディレクトリに BUILD ファイルを作成します。
load(":foo.bzl", "foo_binary")
foo_binary(name = "bin")
これで、ターゲットをビルドできます。
$ bazel build bin
INFO: Analyzed target //:bin (2 packages loaded, 17 targets configured).
INFO: Found 1 target...
Target //:bin up-to-date (nothing to build)
ルールは何も行いませんが、他のルールと同様に動作します。必須の名前があり、visibility、testonly、tags などの一般的な属性をサポートしています。
評価モデル
先に進む前に、コードがどのように評価されるかを理解しておくことが重要です。
いくつかの print ステートメントで foo.bzl を更新します。
def _foo_binary_impl(ctx):
print("analyzing", ctx.label)
foo_binary = rule(
implementation = _foo_binary_impl,
)
print("bzl file evaluation")
BUILD:
load(":foo.bzl", "foo_binary")
print("BUILD file")
foo_binary(name = "bin1")
foo_binary(name = "bin2")
ctx.label
は、分析対象のターゲットのラベルに対応します。ctx オブジェクトには
多くの便利なフィールドとメソッドがあります。完全なリストについては、
API リファレンスをご覧ください。
コードをクエリします。
$ bazel query :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
//:bin2
//:bin1
いくつかの点に注意してください。
- 最初に「bzl file evaluation」が出力されます。
BUILDファイルを評価する前に、Bazel は読み込むすべてのファイルを評価します。複数のBUILDファイルが foo.bzl を読み込んでいる場合、Bazel は評価の結果をキャッシュに保存するため、「bzl file evaluation」は 1 回しか表示されません。 - コールバック関数
_foo_binary_implは呼び出されません。Bazel クエリはBUILDファイルを読み込みますが、ターゲットを分析しません。
ターゲットを分析するには、cquery("構成済み
クエリ")または build コマンドを使用します。
$ bazel build :all
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin1
DEBUG: /usr/home/bazel-codelab/foo.bzl:2:5: analyzing //:bin2
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...
ご覧のとおり、_foo_binary_impl が 2 回呼び出されるようになりました。これは、ターゲットごとに 1 回です。
bazel query の呼び出し後に foo.bzl の評価がキャッシュに保存されるため、「bzl file evaluation」も「BUILD file」も再度出力されません。Bazel は、実際に実行された場合にのみ print ステートメントを出力します。
ファイルを作成する
ルールをより便利にするには、ファイルを生成するように更新します。まず、ファイルを宣言して名前を付けます。この例では、ターゲットと同じ名前のファイルを作成します。
ctx.actions.declare_file(ctx.label.name)
ここで bazel build :all を実行すると、エラーが発生します。
The following files have no generating action:
bin2
ファイルを宣言するたびに、アクションを作成して、Bazel にファイルの生成方法を指示する必要があります。ctx.actions.write、
を使用して、指定されたコンテンツを含むファイルを作成します。
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello\n",
)
コードは有効ですが、何も行いません。
$ bazel build bin1
Target //:bin1 up-to-date (nothing to build)
ctx.actions.write 関数は、Bazel にファイルの生成方法を指示するアクションを登録しました。ただし、Bazel は実際にリクエストされるまでファイルを作成しません。そのため、最後に Bazel に、ファイルがルールの出力であり、ルール実装内で使用される一時ファイルではないことを伝えます。
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello!\n",
)
return [DefaultInfo(files = depset([out]))]
DefaultInfo 関数と depset 関数については後で説明します。今のところ、最後の行がルールの出力を選択する方法であると想定してください。
次に、Bazel を実行します。
$ bazel build bin1
INFO: Found 1 target...
Target //:bin1 up-to-date:
bazel-bin/bin1
$ cat bazel-bin/bin1
Hello!
ファイルの生成に成功しました。
属性
ルールをより便利にするには、
the attr モジュールを使用して新しい属性を追加し、ルール定義を更新します。
username という文字列属性を追加します。
foo_binary = rule(
implementation = _foo_binary_impl,
attrs = {
"username": attr.string(),
},
)
次に、BUILD ファイルで設定します。
foo_binary(
name = "bin",
username = "Alice",
)
コールバック関数で値にアクセスするには、ctx.attr.username を使用します。次に例を示します。
def _foo_binary_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "Hello {}!\n".format(ctx.attr.username),
)
return [DefaultInfo(files = depset([out]))]
属性を必須にしたり、デフォルト値を設定したりできます。
attr.string のドキュメントをご覧ください。
ブール値
や整数のリストなど、他のタイプの属性を使用することもできます。
依存関係
attr.label
や attr.label_listなどの依存関係属性は、属性を所有するターゲットから、属性の値にラベルが表示されるターゲットへの依存関係を宣言します。この種の属性は、ターゲット グラフの基礎となります。
BUILD ファイルでは、ターゲット ラベルは //pkg:name などの文字列オブジェクトとして表示されます。実装関数では、ターゲットは
Target オブジェクトとしてアクセスできます。たとえば、ターゲットから返されたファイルを表示するには、Target.files を使用します。
複数のファイル
デフォルトでは、ルールによって作成されたターゲットのみが依存関係として表示されます(foo_library() ターゲットなど)。属性で入力ファイル(リポジトリ内のソースファイルなど)であるターゲットを受け入れる場合は、allow_files を使用して、受け入れられるファイル拡張子のリスト(または任意のファイル拡張子を許可する場合は True)を指定します。
"srcs": attr.label_list(allow_files = [".java"]),
ファイルのリストには、ctx.files.<attribute name> でアクセスできます。たとえば、srcs 属性のファイルのリストには、
ctx.files.srcs
単一ファイル
必要なファイルが 1 つだけの場合は、allow_single_file を使用します。
"src": attr.label(allow_single_file = [".java"])
このファイルには、ctx.file.<attribute name> でアクセスできます。
ctx.file.src
テンプレートを使用してファイルを作成する
テンプレートに基づいて .cc ファイルを生成するルールを作成できます。また、ctx.actions.write を使用して、ルール実装関数で作成された文字列を出力することもできますが、これには 2 つの問題があります。まず、テンプレートが大きくなるにつれて、別のファイルに配置して、分析フェーズで大きな文字列を作成しない方がメモリ効率が向上します。2 つ目は、別のファイルを使用する方がユーザーにとって便利です。代わりに、テンプレート ファイルで置換を行う
ctx.actions.expand_template,
を使用します。
template 属性を作成して、テンプレート ファイルへの依存関係を宣言します。
def _hello_world_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".cc")
ctx.actions.expand_template(
output = out,
template = ctx.file.template,
substitutions = {"{NAME}": ctx.attr.username},
)
return [DefaultInfo(files = depset([out]))]
hello_world = rule(
implementation = _hello_world_impl,
attrs = {
"username": attr.string(default = "unknown person"),
"template": attr.label(
allow_single_file = [".cc.tpl"],
mandatory = True,
),
},
)
ユーザーは次のようにルールを使用できます。
hello_world(
name = "hello",
username = "Alice",
template = "file.cc.tpl",
)
cc_binary(
name = "hello_bin",
srcs = [":hello"],
)
テンプレートをエンドユーザーに公開せず、常に同じテンプレートを使用する場合は、デフォルト値を設定して属性を非公開にできます。
"_template": attr.label(
allow_single_file = True,
default = "file.cc.tpl",
),
アンダースコアで始まる属性は非公開であり、BUILD ファイルで設定することはできません。テンプレートは暗黙的な依存関係になりました。すべてのhello_world
ターゲットはこのファイルに依存しています。このファイルを他のパッケージから見えるようにすることを忘れないでください。
BUILD ファイルを更新して
exports_files を使用してください。
exports_files(["file.cc.tpl"])
さらに便利なサービス
- ルールのリファレンス ドキュメントをご覧ください。
- depset について理解を深めます。
- ルールの追加の例を含むサンプル リポジトリ を確認します。