Starlark は、元々 Bazel で使用するために開発され、他のツールにも採用されている Python に似た構成言語です。Bazel の BUILD
ファイルと .bzl
ファイルは、「ビルド言語」として一般に知られている Starlark 言語で記述されていますが、単に「Starlark」と呼ばれることがよくあります。特に、Bazel の組み込み部分や「ネイティブ」部分ではなく、ビルド言語で機能が表現されることを強調する場合は特にそうです。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
関数を呼び出すときは、コールバック関数を定義する必要があります。ロジックは配置されますが、現時点では関数を空のままにしておくことができます。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
などの共通の属性をサポートします。
評価モデル
先に進む前に、コードの評価方法を理解することが重要です。
foo.bzl
を print ステートメントで更新します。
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 assessment」が出力されます。Bazel は、
BUILD
ファイルを評価する前に、読み込むすべてのファイルを評価します。複数のBUILD
ファイルが foo.bzl を読み込む場合、Bazel は評価の結果をキャッシュに保存しているため、「bzl ファイル評価」は 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 回)呼び出されるようになりました。
foo.bzl
の評価は bazel query
の呼び出し後にキャッシュに保存されるため、「bzl file education」も「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!
ファイルが正常に生成されました。
属性
ルールの利便性を高めるには、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
のドキュメントをご覧ください。また、booleanや整数のリストなど、他のタイプの属性を使用することもできます。
依存関係
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"])
さらに高度な活用方法
- ルールのリファレンス ドキュメントを確認する。
- デプセットについて十分に理解する。
- サンプル リポジトリを確認する。ルールのその他の例が含まれています。