ルールのチュートリアル

問題を報告する ソースを表示 ナイトリー · 8.0 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

Starlark は、もともと Bazel での使用のために開発された Python のような構成言語で、その後他のツールでも採用されています。Bazel の BUILD ファイルと .bzl ファイルは、Starlark の一種である「Build Language」で記述されています。ただし、特に Bazel の組み込みまたは「ネイティブ」な部分ではなく、Build Language で記述されていることを強調する場合は、単に「Starlark」と呼ばれることもあります。Bazel では、コア言語に globgenrulejava_binary などの多数のビルド関連関数を追加しています。

詳細については、BazelStarlark のドキュメントをご覧ください。新しいルールセットの出発点として、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)

このルールは何もしませんが、他のルールと同様に動作します。名前は必須で、visibilitytestonlytags などの一般的な属性をサポートします。

評価モデル

先に進む前に、コードがどのように評価されるかを理解しておくことが重要です。

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 evaluation」と出力されます。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:8:1: bzl file evaluation
DEBUG: /usr/home/bazel-codelab/BUILD:2:1: BUILD file
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 がターゲットごとに 1 回ずつ 2 回呼び出されるようになります。

bazel query の呼び出し後に foo.bzl の評価がキャッシュに保存されているにもかかわらず、「bzl ファイルの評価」が再度出力されていることに気付くかもしれません。Bazel はコードを再評価せず、出力イベントのみを再生します。キャッシュの状態に関係なく、同じ出力になります。

ファイルの作成

ルールをより有用にするには、ファイルを生成するように更新します。まず、ファイルを宣言して名前を付けます。この例では、ターゲットと同じ名前のファイルを作成します。

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 のドキュメントをご覧ください。ブール値整数のリストなど、他のタイプの属性を使用することもできます。

依存関係

依存関係属性(attr.labelattr.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"])

詳細