ルール

問題を報告 ソースを表示

ルールは、一連の出力を生成するために Bazel が入力に対して実行する一連のアクションを定義します。これらのアクションは、ルールの実装関数によって返されるプロバイダで参照されます。たとえば、C++ バイナリルールは次のようになります。

  1. 一連の .cpp ソースファイル(入力)を取得します。
  2. ソースファイルに対して g++ を実行します(アクション)。
  3. 実行可能な出力と、実行時に使用可能にするその他のファイルとともに DefaultInfo プロバイダを返します。
  4. ターゲットとその依存関係から収集された C++ 固有の情報を使用して CcInfo プロバイダを返します。

Bazel の観点からは、g++ と標準の C++ ライブラリもこのルールの入力になります。ルールの作成者は、ルールに対するユーザー提供の入力だけでなく、アクションの実行に必要なすべてのツールとライブラリも考慮する必要があります。

ルールを作成または変更する前に、Bazel のビルドフェーズを理解しておいてください。ビルドの 3 つのフェーズ(読み込み、分析、実行)を理解することが重要です。また、ルールとマクロの違いを理解するために、マクロについて学ぶことも有用です。まずは、ルールのチュートリアルをご覧ください。 そのうえで、このページを参考にしてください。

Bazel 自体には、いくつかのルールが組み込まれています。genrulefilegroup などのネイティブ ルールがコアサポートを提供します。独自のルールを定義することで、Bazel がネイティブにサポートしていない言語とツールのサポートを追加できます。

Bazel は、Starlark 言語を使用してルールを作成するための拡張モデルを提供します。これらのルールは .bzl ファイルに記述されており、BUILD ファイルから直接読み込むことができます。

独自のルールを定義するときは、ルールがサポートする属性と、ルールの出力の生成方法を決定する必要があります。

ルールの implementation 関数は、分析フェーズ中の厳密な動作を定義します。この関数は外部コマンドを実行しません。代わりに、必要に応じてルールの出力を作成するために、実行フェーズの後半で使用されるアクションを登録します。

ルールの作成

.bzl ファイルで、rule 関数を使用して新しいルールを定義し、結果をグローバル変数に格納します。rule の呼び出しでは、属性実装関数を指定します。

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "deps": attr.label_list(),
        ...
    },
)

ここでは、example_library という名前のルールの種類を定義します。

また、rule の呼び出しでは、ルールが実行ファイルの出力(executable = True を使用)を作成するのか、具体的にはテスト実行可能ファイル(test = True を使用)を作成するのかも指定する必要があります。後者の場合、ルールはテストルールであり、ルール名の末尾は _test である必要があります。

ターゲットのインスタンス化

ルールは、BUILD ファイルで読み込み、呼び出すことができます。

load('//some/pkg:rules.bzl', 'example_library')

example_library(
    name = "example_target",
    deps = [":another_target"],
    ...
)

ビルドルールを呼び出すたびに値が返されませんが、ターゲットを定義するという副作用が発生します。これはルールのインスタンス化と呼ばれます。これにより、新しいターゲットの名前とターゲットの属性の値を指定します。

ルールは Starlark 関数から呼び出して .bzl ファイルに読み込むこともできます。ルールを呼び出す Starlark 関数は、Starlark マクロと呼ばれます。Starlark マクロは、最終的には BUILD ファイルから呼び出す必要があり、BUILD ファイルが評価されてターゲットをインスタンス化する読み込みフェーズでのみ呼び出すことができます。

属性

属性はルールの引数です。属性は、ターゲットの実装に特定の値を指定することも、他のターゲットを参照して依存関係のグラフを作成することもできます。

srcsdeps などのルール固有の属性を定義するには、属性名からスキーマ(attr モジュールを使用して作成)へのマップを ruleattrs パラメータに渡します。namevisibility などの一般的な属性は、すべてのルールに暗黙的に追加されます。具体的には、実行ルールとテストルールに属性が暗黙的に追加されます。ルールに暗黙的に追加される属性は、attrs に渡される辞書に含めることはできません。

依存関係属性

通常、ソースコードを処理するルールでは、さまざまな依存関係のタイプを処理するために次の属性を定義します。

  • srcs には、ターゲットのアクションによって処理されるソースファイルを指定します。多くの場合、属性スキーマは、ルールが処理するソースファイルの種類に想定されるファイル拡張子を指定します。通常、ヘッダー ファイルを含む言語のルールでは、ターゲットとそのコンシューマによって処理されるヘッダーに個別の hdrs 属性を指定します。
  • deps は、ターゲットのコード依存関係を指定します。属性スキーマでは、それらの依存関係が提供する必要があるプロバイダを指定する必要があります。(たとえば、cc_libraryCcInfo を提供します)。
  • data は、ターゲットに依存する実行可能ファイルが実行時に利用できるようにするファイルを指定します。これにより、任意のファイルを指定できます。
example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        "srcs": attr.label_list(allow_files = [".example"]),
        "hdrs": attr.label_list(allow_files = [".header"]),
        "deps": attr.label_list(providers = [ExampleInfo]),
        "data": attr.label_list(allow_files = True),
        ...
    },
)

これらは依存関係属性の例です。入力ラベル(attr.label_listattr.labelattr.label_keyed_string_dict で定義されるラベル)を指定する属性により、ターゲットと、ターゲットの定義時に、そのラベルを持つターゲット(または対応する Label オブジェクト)との間の特定のタイプの依存関係を指定します。これらのラベルのリポジトリ(場合によってはパス)は、定義されたターゲットを基準として解決されます。

example_library(
    name = "my_target",
    deps = [":other_target"],
)

example_library(
    name = "other_target",
    ...
)

この例では、other_targetmy_target の依存関係であるため、other_target が最初に分析されます。ターゲットの依存関係グラフにサイクルがある場合は、エラーになります。

非公開属性と暗黙的な依存関係

デフォルト値を持つ依存関係属性により、暗黙的な依存関係が作成されます。これは、ユーザーが BUILD ファイルで指定しないターゲット グラフの一部であるため、暗黙的です。暗黙的な依存関係は、ルールとツール(コンパイラなどのビルド時の依存関係)との関係をハードコードする場合に役立ちます。これは、ほとんどの場合、ユーザーはルールで使用するツールを指定することに関心がないためです。ルールの実装関数内では、他の依存関係と同様に扱われます。

ユーザーにその値のオーバーライドを許可しないで暗黙的な依存関係を提供する場合は、アンダースコア(_)で始まる名前を指定して、属性を private にすることができます。非公開属性にはデフォルト値が必要です。通常は、暗黙的な依存関係にのみプライベート属性を使用するのが合理的です。

example_library = rule(
    implementation = _example_library_impl,
    attrs = {
        ...
        "_compiler": attr.label(
            default = Label("//tools:example_compiler"),
            allow_single_file = True,
            executable = True,
            cfg = "exec",
        ),
    },
)

この例では、example_library 型のすべてのターゲットに、コンパイラ //tools:example_compiler への暗黙的な依存関係があります。これにより、ユーザーがラベルを入力として渡していなくても、example_library の実装関数でコンパイラを呼び出すアクションを生成できます。_compiler は非公開属性であるため、このルールタイプのすべてのターゲットで ctx.attr._compiler は常に //tools:example_compiler を指すことになります。また、属性 compiler にアンダースコアを付けずに名前を付け、デフォルト値をそのまま使用することもできます。これにより、ユーザーは必要に応じて別のコンパイラに置き換えることができますが、コンパイラのラベルを認識する必要はありません。

暗黙的な依存関係は通常、ルールの実装と同じリポジトリにあるツールに使用されます。ツールが実行プラットフォームまたは別のリポジトリから提供されている場合、ルールはツールチェーンからそのツールを取得する必要があります。

出力属性

attr.outputattr.output_list などの出力属性は、ターゲットが生成する出力ファイルを宣言します。これらは、次の 2 つの点で依存関係属性と異なります。

  • 出力ファイル ターゲットは、別の場所で定義されたターゲットを参照するのではなく、定義します。
  • 出力ファイル ターゲットは、その逆ではなく、インスタンス化されたルール ターゲットに依存します。

通常、出力属性は、ターゲット名に基づいて使用できないユーザー定義名で出力をルールで作成する必要がある場合にのみ使用されます。ルールに 1 つの出力属性がある場合、通常は out または outs という名前になります。

出力属性は、事前に宣言された出力を作成する場合におすすめの方法です。この出力は、明示的に依存するか、コマンドラインでリクエストできます。

実装関数

すべてのルールに implementation 関数が必要です。これらの機能は分析フェーズで厳密に実行され、読み込みフェーズで生成されたターゲットのグラフを実行フェーズで実行するアクションのグラフに変換します。そのため、実装関数で実際にファイルの読み取りや書き込みを行うことはできません。

ルール実装関数は通常非公開です(名前の先頭にアンダースコアが付きます)。通常はルールと同じ名前になりますが、末尾に _impl が付きます。

実装関数は、1 つのパラメータ(従来は ctx という名前のルール コンテキスト)を受け取ります。プロバイダのリストが返されます。

目標

依存関係は、分析時に Target オブジェクトとして表されます。これらのオブジェクトには、ターゲットの実装関数の実行時に生成されたプロバイダが含まれています。

ctx.attr には各依存関係属性の名前に対応するフィールドがあり、その属性を使用して各直接依存関係を表す Target オブジェクトが含まれます。label_list 属性の場合、これは Targets のリストです。label 属性の場合、これは単一の Target または None です。

ターゲットの実装関数によって、プロバイダ オブジェクトのリストが返されます。

return [ExampleInfo(headers = depset(...))]

これらは、プロバイダの型をキーとしてインデックス表記([])を使用してアクセスできます。これらは、Starlark で定義されたカスタム プロバイダか、Starlark グローバル変数として利用可能なネイティブ ルールのプロバイダです。

たとえば、ルールが hdrs 属性を使用してヘッダー ファイルを取得し、ターゲットとそのコンシューマのコンパイル アクションに提供する場合、次のように収集できます。

def _example_library_impl(ctx):
    ...
    transitive_headers = [hdr[ExampleInfo].headers for hdr in ctx.attr.hdrs]

以前の構造体スタイルがありますが、これは強く推奨されず、ルールをスタイルから移行する必要があります。

ファイル

ファイルは File オブジェクトで表されます。Bazel は分析フェーズでファイルの I/O を行いないため、これらのオブジェクトを使用してファイルの内容を直接読み書きすることはできません。アクション グラフを構成するために、アクションを生成する関数(ctx.actions を参照)に渡されます。

File は、ソースファイルまたは生成されたファイルのいずれかです。生成される各ファイルは、1 つのアクションの出力である必要があります。ソースファイルをアクションの出力にすることはできません。

依存関係属性ごとに、対応する ctx.files のフィールドには、その属性を使用するすべての依存関係のデフォルト出力のリストが含まれます。

def _example_library_impl(ctx):
    ...
    headers = depset(ctx.files.hdrs, transitive = transitive_headers)
    srcs = ctx.files.srcs
    ...

ctx.file には、仕様で allow_single_file = True が設定されている依存関係属性の File または None が 1 つ含まれます。ctx.executablectx.file と同じように動作しますが、仕様で executable = True が設定されている依存関係属性のフィールドのみを含みます。

出力の宣言

分析フェーズでは、ルールの実装関数から出力を生成できます。読み込みフェーズですべてのラベルを把握している必要があるため、これらの追加の出力にラベルはありません。出力用の File オブジェクトは、ctx.actions.declare_filectx.actions.declare_directory を使用して作成できます。多くの場合、出力の名前はターゲットの名前 ctx.label.name に基づいています。

def _example_library_impl(ctx):
  ...
  output_file = ctx.actions.declare_file(ctx.label.name + ".output")
  ...

出力属性用に作成されたものと同様に、事前に宣言された出力の場合、代わりに File オブジェクトを ctx.outputs の対応するフィールドから取得できます。

アクション

アクションは、一連の入力から一連の出力を生成する方法を記述します(例: 「run gcc on hello.c and get hello.o」など)。アクションが作成されても、Bazel はコマンドをすぐに実行しません。アクションは別のアクションの出力に依存する可能性があるため、依存関係のグラフに登録します。たとえば C では、リンカーはコンパイラの後に呼び出す必要があります。

アクションを作成する汎用関数は、ctx.actions で定義されています。

ctx.actions.args を使用すると、アクションの引数を効率的に蓄積できます。これにより、実行時まで依存関係のフラット化が回避されます。

def _example_library_impl(ctx):
    ...

    transitive_headers = [dep[ExampleInfo].headers for dep in ctx.attr.deps]
    headers = depset(ctx.files.hdrs, transitive = transitive_headers)
    srcs = ctx.files.srcs
    inputs = depset(srcs, transitive = [headers])
    output_file = ctx.actions.declare_file(ctx.label.name + ".output")

    args = ctx.actions.args()
    args.add_joined("-h", headers, join_with = ",")
    args.add_joined("-s", srcs, join_with = ",")
    args.add("-o", output_file)

    ctx.actions.run(
        mnemonic = "ExampleCompile",
        executable = ctx.executable._compiler,
        arguments = [args],
        inputs = inputs,
        outputs = [output_file],
    )
    ...

アクションは、入力ファイルのリストまたは依存関係を取得し、出力ファイルの(空でない)リストを生成します。分析フェーズで、入力ファイルと出力ファイルのセットを把握しておく必要があります。依存関係のプロバイダなどの属性の値には依存できますが、実行の結果には依存できません。たとえば、アクションで unzip コマンドを実行する場合は、(unzip を実行する前に)インフレートするファイルを指定する必要があります。可変数のファイルを内部で作成するアクションでは、それらのファイルを 1 つのファイル(zip、tar、またはその他のアーカイブ形式)にラップできます。

アクションはすべての入力をリストする必要があります。使用されていない入力を一覧表示することは許可されていますが、非効率的です。

アクションは、その出力をすべて作成する必要があります。他のファイルを書き込むことはできますが、出力に含まれないものはコンシューマを使用できません。宣言された出力はすべて、なんらかのアクションによって書き込まれる必要があります。

アクションは純粋な関数に匹敵します。指定された入力のみに依存し、コンピュータ情報、ユーザー名、クロック、ネットワーク、I/O デバイス(入力の読み取りと出力の書き込みを除く)へのアクセスを避ける必要があります。出力はキャッシュに保存され、再利用されるため、これは重要です。

依存関係は、実行するアクションを決定する Bazel によって解決されます。依存関係グラフにサイクルがある場合はエラーになります。アクションを作成しても、そのアクションの実行が保証されるわけではありません。これは、ビルドにその出力が必要かどうかによって異なります。

プロバイダ

プロバイダは、ルールによって、それに依存する他のルールに公開される情報です。このデータには、出力ファイル、ライブラリ、ツールのコマンドラインに渡すパラメータなど、ターゲットのコンシューマが知っておくべきすべてのものを含めることができます。

ルールの実装関数は、インスタンス化されたターゲットの直接的な依存関係からのみプロバイダを読み取ることができるため、ルールでは、ターゲットのコンシューマが認識する必要があるターゲットの依存関係から、通常は情報を depset に蓄積して転送する必要があります。

ターゲットのプロバイダは、実装関数から返されるプロバイダ オブジェクトのリストで指定されます。

古い実装関数は、プロバイダ オブジェクトのリストではなく struct を返す以前のスタイルで記述することもできます。このスタイルは使用しないことを強くおすすめします。スタイルからルールを移行する必要があります。

デフォルトの出力

ターゲットのデフォルト出力は、コマンドラインでターゲットのビルドがリクエストされたときに、デフォルトでリクエストされる出力です。たとえば、java_library ターゲット //pkg:foo にはデフォルトの出力として foo.jar があり、これはコマンド bazel build //pkg:foo でビルドされます。

デフォルトの出力は、DefaultInfofiles パラメータで指定されます。

def _example_library_impl(ctx):
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        ...
    ]

ルール実装によって DefaultInfo が返されない場合、または files パラメータが指定されていない場合、DefaultInfo.files はデフォルトですべての事前に宣言された出力(通常は出力属性によって作成された出力)になります。

アクションを実行するルールでは、出力を直接使用することが想定されていない場合でも、デフォルトの出力を提供する必要があります。リクエストされた出力のグラフにないアクションはプルーニングされます。出力がターゲットのコンシューマーによってのみ使用される場合、ターゲットが独立して組み込まれている場合、これらのアクションは実行されません。失敗したターゲットのみを再構築しても失敗は再現されないため、デバッグが難しくなります。

実行ファイル

実行ファイルは、(ビルド時ではなく)実行時にターゲットによって使用される一連のファイルです。実行フェーズで、Bazel は実行ファイルを指すシンボリック リンクを含むディレクトリ ツリーを作成します。これにより、バイナリの環境がステージングされ、実行時に実行ファイルにアクセスできるようになります。

実行ファイルはルールの作成時に手動で追加できます。runfiles オブジェクトは、ルール コンテキストの runfiles メソッド(ctx.runfiles)によって作成され、DefaultInforunfiles パラメータに渡すことができます。実行可能ルールの実行可能な出力は、暗黙的に実行ファイルに追加されます。

一部のルールでは属性(通常は data という名前)が指定されており、この属性の出力はターゲットの実行ファイルに追加されます。実行ファイルは data だけでなく、最終的な実行用のコードを提供する属性(通常は srcsdata が関連付けられた filegroup ターゲットを含む))と deps からマージする必要があります。

def _example_library_impl(ctx):
    ...
    runfiles = ctx.runfiles(files = ctx.files.data)
    transitive_runfiles = []
    for runfiles_attr in (
        ctx.attr.srcs,
        ctx.attr.hdrs,
        ctx.attr.deps,
        ctx.attr.data,
    ):
        for target in runfiles_attr:
            transitive_runfiles.append(target[DefaultInfo].default_runfiles)
    runfiles = runfiles.merge_all(transitive_runfiles)
    return [
        DefaultInfo(..., runfiles = runfiles),
        ...
    ]

カスタム プロバイダ

provider 関数を使用してプロバイダを定義して、ルール固有の情報を伝達できます。

ExampleInfo = provider(
    "Info needed to compile/link Example code.",
    fields = {
        "headers": "depset of header Files from transitive dependencies.",
        "files_to_link": "depset of Files from compilation.",
    },
)

ルール実装関数は、プロバイダ インスタンスを作成して返すことができます。

def _example_library_impl(ctx):
  ...
  return [
      ...
      ExampleInfo(
          headers = headers,
          files_to_link = depset(
              [output_file],
              transitive = [
                  dep[ExampleInfo].files_to_link for dep in ctx.attr.deps
              ],
          ),
      )
  ]
プロバイダのカスタム初期化

カスタムの前処理と検証のロジックを使用して、プロバイダのインスタンス化を保護できます。これを使用して、すべてのプロバイダ インスタンスが特定の不変条件を満たせるようにすることや、インスタンスを取得するためのよりクリーンな API をユーザーに提供することができます。

これを行うには、init コールバックを provider 関数に渡します。このコールバックを指定すると、provider() の戻り値の型は 2 つの値のタプルに変わります。1 つは init が使用されていない場合の通常の戻り値であるプロバイダ シンボル、もう 1 つは「生のコンストラクタ」です。

この場合、プロバイダ シンボルが呼び出されたときに、新しいインスタンスを直接返すのではなく、引数を init コールバックに転送します。コールバックの戻り値は、フィールド名(文字列)を値にマッピングする dict である必要があります。これは、新しいインスタンスのフィールドの初期化に使用されます。コールバックには任意のシグネチャがあり、引数がシグネチャと一致しない場合は、コールバックが直接呼び出された場合と同様にエラーが報告されます。

一方、未加工のコンストラクタは、init コールバックをバイパスします。

次の例では、init を使用して引数の前処理と検証を行います。

# //pkg:exampleinfo.bzl

_core_headers = [...]  # private constant representing standard library files

# Keyword-only arguments are preferred.
def _exampleinfo_init(*, files_to_link, headers = None, allow_empty_files_to_link = False):
    if not files_to_link and not allow_empty_files_to_link:
        fail("files_to_link may not be empty")
    all_headers = depset(_core_headers, transitive = headers)
    return {"files_to_link": files_to_link, "headers": all_headers}

ExampleInfo, _new_exampleinfo = provider(
    fields = ["files_to_link", "headers"],
    init = _exampleinfo_init,
)

ルールの実装では、次のようにプロバイダをインスタンス化できます。

ExampleInfo(
    files_to_link = my_files_to_link,  # may not be empty
    headers = my_headers,  # will automatically include the core headers
)

未加工のコンストラクタを使用すると、init ロジックを経由しない代替パブリック ファクトリ関数を定義できます。たとえば、exampleinfo.bzl では以下を定義できます。

def make_barebones_exampleinfo(headers):
    """Returns an ExampleInfo with no files_to_link and only the specified headers."""
    return _new_exampleinfo(files_to_link = depset(), headers = all_headers)

通常、未加工のコンストラクタは、名前がアンダースコアで始まる変数にバインドされているため(上記の _new_exampleinfo)、ユーザーコードでこのコンストラクタを読み込んで任意のプロバイダ インスタンスを生成することはできません。

init のもう一つの用途は、ユーザーがプロバイダ シンボルをまったく呼び出さないようにして、代わりにファクトリ関数を使用するよう強制することです。

def _exampleinfo_init_banned(*args, **kwargs):
    fail("Do not call ExampleInfo(). Use make_exampleinfo() instead.")

ExampleInfo, _new_exampleinfo = provider(
    ...
    init = _exampleinfo_init_banned)

def make_exampleinfo(...):
    ...
    return _new_exampleinfo(...)

実行可能なルールとテストルール

実行可能ルールは、bazel run コマンドで呼び出すことができるターゲットを定義します。テストルールは特別な種類の実行可能ルールで、ターゲットは bazel test コマンドでも呼び出すことができます。実行可能ルールとテストルールは、rule の呼び出しで、それぞれの executable 引数または test 引数を True に設定することで作成されます。

example_binary = rule(
   implementation = _example_binary_impl,
   executable = True,
   ...
)

example_test = rule(
   implementation = _example_binary_impl,
   test = True,
   ...
)

テストルールの名前は _test で終わる必要があります。(テスト ターゲット名も慣例により _test で終わることがよくありますが、必須ではありません)。テスト以外のルールにこの接尾辞を含めることはできません。

どちらの種類のルールでも、run コマンドまたは test コマンドによって呼び出される実行可能な出力ファイルを生成する必要があります(このファイルは事前に宣言されている場合があります)。この実行可能ファイルとして使用するルールの出力を Bazel に指示するには、返された DefaultInfo プロバイダの executable 引数として渡します。この executable は、ルールのデフォルトの出力に追加されます(そのため、executablefiles の両方に渡す必要はありません)。また、暗黙的に runfiles にも追加されます。

def _example_binary_impl(ctx):
    executable = ctx.actions.declare_file(ctx.label.name)
    ...
    return [
        DefaultInfo(executable = executable, ...),
        ...
    ]

このファイルを生成するアクションでは、ファイルの実行可能ビットを設定する必要があります。ctx.actions.run アクションまたは ctx.actions.run_shell アクションの場合は、アクションによって呼び出される基盤ツールがこれを実行する必要があります。ctx.actions.write アクションの場合は、is_executable = True を渡します。

従来の動作として、実行可能ルールには特別な ctx.outputs.executable が事前に宣言された出力があります。DefaultInfo を使用して指定しない場合、このファイルはデフォルトの実行可能ファイルとして機能します。それ以外の場合は使用しないでください。この出力メカニズムは、分析時の実行可能ファイルの名前のカスタマイズをサポートしていないため、非推奨になりました。

実行可能ルールテストルールの例をご覧ください。

すべてのルールに追加された属性に加えて、実行可能ルールテストルールには追加の属性が暗黙的に定義されます。暗黙的に追加された属性のデフォルトは変更できませんが、デフォルトを変更する Starlark マクロでプライベート ルールをラップすることでこの問題を回避できます。

def example_test(size = "small", **kwargs):
  _example_test(size = size, **kwargs)

_example_test = rule(
 ...
)

実行ファイルの場所

実行可能なターゲットを bazel run(または test)で実行すると、runfiles ディレクトリのルートが実行可能ファイルに隣接します。各パスは次のように関係します。

# Given launcher_path and runfile_file:
runfiles_root = launcher_path.path + ".runfiles"
workspace_name = ctx.workspace_name
runfile_path = runfile_file.short_path
execution_root_relative_path = "%s/%s/%s" % (
    runfiles_root, workspace_name, runfile_path)

runfiles ディレクトリの下の File へのパスは File.short_path に対応します。

bazel によって直接実行されるバイナリは、runfiles ディレクトリのルートに隣接しています。ただし、ランファイルから呼び出されるバイナリでは、同じことを想定できません。これを軽減するには、各バイナリで、環境、コマンドラインの引数、フラグを使用して、runfile のルートをパラメータとして受け入れる方法を提供する必要があります。これにより、バイナリは呼び出すバイナリに正しい正規の実行ファイルのルートを渡すことができます。設定されていない場合、バイナリは最初に呼び出されたバイナリであると推測し、隣接する runfiles ディレクトリを検索できます。

高度なトピック

出力ファイルのリクエスト

1 つのターゲットに複数の出力ファイルを含めることができます。bazel build コマンドを実行すると、コマンドに指定されたターゲットの出力の一部がリクエストされているとみなされます。Bazel は、これらのリクエストされたファイルと、それらが直接的または間接的に依存するファイルのみをビルドします。(アクション グラフについては、Bazel はリクエストされたファイルの推移的依存関係として到達可能なアクションのみを実行します)。

デフォルトの出力に加えて、事前に宣言された出力をコマンドラインで明示的にリクエストできます。ルールでは、出力属性を使用して、事前に宣言された出力を指定できます。その場合、ユーザーはルールをインスタンス化するときに出力のラベルを明示的に選択します。出力属性の File オブジェクトを取得するには、ctx.outputs の対応する属性を使用します。ルールでは、ターゲット名に基づいて事前に宣言された出力を暗黙的に定義することもできますが、この機能は非推奨になりました。

デフォルトの出力に加えて、出力グループがあります。これは、一緒にリクエストできる出力ファイルのコレクションです。これらは --output_groups でリクエストできます。たとえば、ターゲット //pkg:mytargetdebug_files 出力グループを含むルールタイプの場合、bazel build //pkg:mytarget --output_groups=debug_files を実行することでこれらのファイルを作成できます。事前宣言されていない出力にはラベルがないため、デフォルトの出力または出力グループに表示されることでのみリクエストできます。

出力グループは、OutputGroupInfo プロバイダで指定できます。多くの組み込みプロバイダとは異なり、OutputGroupInfo は任意の名前のパラメータを受け取り、その名前の出力グループを定義できます。

def _example_library_impl(ctx):
    ...
    debug_file = ctx.actions.declare_file(name + ".pdb")
    ...
    return [
        DefaultInfo(files = depset([output_file]), ...),
        OutputGroupInfo(
            debug_files = depset([debug_file]),
            all_files = depset([output_file, debug_file]),
        ),
        ...
    ]

また、ほとんどのプロバイダとは異なり、OutputGroupInfo は、同じ出力グループを定義しない限り、アスペクトとそのアスペクトが適用されるルール ターゲットの両方から返されます。その場合、結果のプロバイダはマージされます。

OutputGroupInfo は通常、ターゲットからコンシューマーのアクションに特定の種類のファイルを伝えるためには使用しないでください。代わりに、ルール固有のプロバイダを定義します。

構成

別のアーキテクチャ用に C++ バイナリをビルドする場合について考えてみましょう。ビルドは複雑で、複数のステップが含まれることがあります。コンパイラやコード生成ツールなどの中間バイナリの一部は、実行プラットフォーム(ホストまたはリモート エグゼキュータ)で実行する必要があります。最終出力などの一部のバイナリは、ターゲット アーキテクチャ用にビルドする必要があります。

このため、Bazel には「構成」と移行の概念があります。最上位のターゲット(コマンドラインでリクエストされるターゲット)は「ターゲット」構成に組み込まれていますが、実行プラットフォームで実行するツールは「実行可能ファイル」構成に組み込まれています。ルールでは、コンパイラに渡される CPU アーキテクチャを変更するなど、構成に基づいてさまざまなアクションが生成される場合があります。場合によっては、さまざまな構成で同じライブラリが必要になることがあります。その場合、分析が行われ、場合によっては複数回ビルドされます。

デフォルトでは、Bazel はターゲット自体と同じ構成で、つまり移行なしでターゲットの依存関係をビルドします。依存関係がターゲットのビルドに必要なツールである場合は、対応する属性で exec 構成への移行を指定する必要があります。これにより、ツールとそのすべての依存関係が実行プラットフォーム用にビルドされます。

依存関係属性ごとに、cfg を使用して、依存関係を同じ構成でビルドするか、exec 構成に移行するかを決定できます。依存関係属性に executable = True フラグがある場合は、cfg を明示的に設定する必要があります。これは、誤った構成でツールを誤ってビルドすることを防ぐためです。例を見る

一般に、実行時に必要になるソース、依存ライブラリ、実行可能ファイルは、同じ構成を使用できます。

ビルドの一部として実行されるツール(コンパイラ、コード生成ツールなど)は、実行構成用にビルドする必要があります。この場合は、属性に cfg = "exec" を指定します。

それ以外の場合は、テストの一環としてなど、実行時に使用される実行可能ファイルをターゲット構成用にビルドする必要があります。この場合は、属性に cfg = "target" を指定します。

cfg = "target" は実際には何も行いません。ルール設計者が意図を明示するための便利な値です。executable = Falsecfg が省略可能であることを意味する)は、読みやすくするのに役立つ場合にのみ設定します。

また、cfg = my_transition を使用してユーザー定義の遷移を使用することもできます。この場合、ビルドグラフが大きくなり、わかりにくくなるという欠点がありますが、ルール作成者は柔軟に構成を変更できます。

: 従来、Bazel には実行プラットフォームの概念がなく、すべてのビルド アクションがホストマシン上で実行されると考えられていました。6.0 より前のバージョンの Bazel では、これを表す個別の「host」構成が作成されました。コードまたは古いドキュメントで「ホスト」への言及がある場合は、これが指名しています。この余分なコンセプトのオーバーヘッドを避けるため、Bazel 6.0 以降を使用することをおすすめします。

構成フラグメント

ルールは、cppjava などの構成フラグメントにアクセスできます。ただし、アクセスエラーを回避するために、必要なすべてのフラグメントを宣言する必要があります。

def _impl(ctx):
    # Using ctx.fragments.cpp leads to an error since it was not declared.
    x = ctx.fragments.java
    ...

my_rule = rule(
    implementation = _impl,
    fragments = ["java"],      # Required fragments of the target configuration
    ...
)

通常、runfiles ツリー内のファイルの相対パスは、ソースツリーまたは生成された出力ツリー内のファイルの相対パスと同じです。なんらかの理由でこれらを異なる値にする必要がある場合は、root_symlinks 引数または symlinks 引数を指定できます。root_symlinks は、パスをファイルにマッピングする辞書です。このパスは、runfiles ディレクトリのルートからの相対パスです。symlinks ディクショナリは同じですが、パスの先頭にメイン ワークスペースの名前が暗黙的に付加されます(現在のターゲットを含むリポジトリの名前ではありません)。

    ...
    runfiles = ctx.runfiles(
        root_symlinks = {"some/path/here.foo": ctx.file.some_data_file2}
        symlinks = {"some/path/here.bar": ctx.file.some_data_file3}
    )
    # Creates something like:
    # sometarget.runfiles/
    #     some/
    #         path/
    #             here.foo -> some_data_file2
    #     <workspace_name>/
    #         some/
    #             path/
    #                 here.bar -> some_data_file3

symlinks または root_symlinks を使用する場合は、2 つの異なるファイルを runfiles ツリー内の同じパスにマッピングしないように注意してください。これによりビルドは失敗し、競合を示すエラーが表示されます。これを修正するには、ctx.runfiles 引数を変更して競合を解消する必要があります。このチェックは、ルールを使用するすべてのターゲットと、それらのターゲットに依存するすべての種類のターゲットに対して行われます。特定のツールが別のツールによって推移的に使用される可能性がある場合、これは特に危険です。シンボリック リンク名は、ツールの実行ファイルとそのすべての依存関係で一意である必要があります。

コード カバレッジ

coverage コマンドを実行する際、ビルドで特定のターゲットに対するカバレッジ インストルメンテーションの追加が必要になる場合があります。このビルドでは、インストゥルメント化されたソースファイルのリストも収集されます。考慮されるターゲットのサブセットは、--instrumentation_filter フラグによって制御されます。--instrument_test_targets が指定されていない限り、テスト ターゲットは除外されます。

ルールの実装でビルド時にカバレッジのインストルメンテーションを追加する場合は、実装関数でそれを考慮する必要があります。ターゲットのソースをインストルメント化する必要がある場合、ctx.coverage_instrumented はカバレッジ モードで True を返します。

# Are this rule's sources instrumented?
if ctx.coverage_instrumented():
  # Do something to turn on coverage for this compile action

カバレッジ モードで常にオンにする必要があるロジック(ターゲットのソースが具体的にインストルメント化されているかどうかに関係なく)は、ctx.configuration.coverage_enabled で条件付けできます。

コンパイル前に依存関係のソース(ヘッダー ファイルなど)がルールに直接含まれている場合、依存関係のソースをインストルメント化する必要がある場合は、コンパイル時のインストルメンテーションを有効にする必要があります。

# Are this rule's sources or any of the sources for its direct dependencies
# in deps instrumented?
if (ctx.configuration.coverage_enabled and
    (ctx.coverage_instrumented() or
     any([ctx.coverage_instrumented(dep) for dep in ctx.attr.deps]))):
    # Do something to turn on coverage for this compile action

また、ルールでは、coverage_common.instrumented_files_info を使用して作成された InstrumentedFilesInfo プロバイダのカバレッジに関連する属性に関する情報も指定する必要があります。instrumented_files_infodependency_attributes パラメータは、deps などのコード依存関係や data などのデータ依存関係を含むすべてのランタイム依存関係属性を一覧表示する必要があります。カバレッジ インストルメンテーションが追加される可能性がある場合、source_attributes パラメータはルールのソースファイル属性をリストする必要があります。

def _example_library_impl(ctx):
    ...
    return [
        ...
        coverage_common.instrumented_files_info(
            ctx,
            dependency_attributes = ["deps", "data"],
            # Omitted if coverage is not supported for this rule:
            source_attributes = ["srcs", "hdrs"],
        )
        ...
    ]

InstrumentedFilesInfo が返されない場合、属性スキーマで cfg"exec" に設定していないツール以外の依存関係属性ごとにデフォルトの属性が作成されます。dependency_attributes(これは srcs などの属性が source_attributes ではなく dependency_attributes に配置されるため、理想的な動作ではありませんが、依存関係チェーン内のすべてのルールに対して明示的なカバレッジ構成を行う必要はありません)。

検証アクション

ビルドに関するなんらかの検証が必要になることがあります。その検証に必要な情報は、アーティファクト(ソースファイルまたは生成されたファイル)でのみ利用可能です。この情報はアーティファクトであるため、ルールではファイルを読み取ることができないため、分析時にこの検証を行うことはできません。代わりに、アクションが実行時にこの検証を行う必要があります。検証が失敗するとアクションも失敗し、ビルドも失敗します。

実行される検証の例としては、静的分析、lint チェック、依存関係と整合性のチェック、スタイル チェックなどがあります。

検証アクションは、アーティファクトのビルドに不要なアクションの一部を個別のアクションに移動することで、ビルドのパフォーマンスを改善することもできます。たとえば、コンパイルと lint チェックを行う単一のアクションをコンパイル アクションと lint チェック アクションに分割できる場合、lint アクションは検証アクションとして実行し、他のアクションと並行して実行できます。

多くの場合、これらの「検証アクション」は入力に関するアサートのみが必要となるため、ビルドの他の場所で使用されるものは生成されません。しかし、これには問題があります。ビルドの別の場所で使用されるものが検証アクションによって生成されない場合、ルールはどのようにアクションを実行させるのでしょうか。従来は、検証アクションで空のファイルを出力し、その出力をビルド内の他の重要なアクションの入力に人為的に追加していました。

コンパイル アクションの実行時に常に Bazel が検証アクションを実行するため、これは機能しますが、次の大きなデメリットがあります。

  1. 検証アクションはビルドのクリティカル パスにあります。Bazel はコンパイル アクションの実行には空の出力が必要であると判断するため、コンパイル アクションが入力を無視しても、最初に検証アクションを実行します。これにより、並列処理が減少し、ビルドが遅くなります。

  2. コンパイル アクションの代わりにビルド内の他のアクションが実行される可能性がある場合は、それらのアクションに検証アクションの空の出力(java_library のソース jar 出力など)も追加する必要があります。これは、コンパイル アクションの代わりに実行される可能性のある新しいアクションが後で追加され、空の検証出力が誤って削除された場合にも問題になります。

これらの問題を解決するには、Validation Output Group(検証出力グループ)を使用します。

検証出力グループ

検証出力グループは、本来使用されない検証アクションの出力を保持するように設計された出力グループであり、他のアクションの入力に人為的に追加する必要はありません。

このグループは、--output_groups フラグの値に関係なく、またターゲットの依存関係(コマンドライン、依存関係として、ターゲットの暗黙的な出力など)に関係なく、出力が常にリクエストされるという点で特殊です。通常のキャッシュ保存とインクリメンタリティは引き続き適用されます。検証アクションへの入力が変更されておらず、以前に検証アクションが成功している場合、検証アクションは実行されません。

この出力グループを使用する場合、検証アクションでは、空のファイルであっても、なんらかのファイルを出力する必要があります。通常は出力を作成しない一部のツールをラップして、ファイルが作成されることが必要になる場合があります。

ターゲットの検証アクションは、次の 3 つのケースでは実行されません。

  • ターゲットがツールとして依存している場合
  • ターゲットが暗黙的な依存関係として依存している場合(「_」で始まる属性など)
  • ターゲットが exec 構成でビルドされたとき。

これらのターゲットには、検証の失敗を検出する独自のビルドとテストがあることを前提としています。

検証出力グループの使用

検証出力グループの名前は _validation で、他の出力グループと同様に使用されます。

def _rule_with_validation_impl(ctx):

  ctx.actions.write(ctx.outputs.main, "main output\n")
  ctx.actions.write(ctx.outputs.implicit, "implicit output\n")

  validation_output = ctx.actions.declare_file(ctx.attr.name + ".validation")
  ctx.actions.run(
    outputs = [validation_output],
    executable = ctx.executable._validation_tool,
    arguments = [validation_output.path],
  )

  return [
    DefaultInfo(files = depset([ctx.outputs.main])),
    OutputGroupInfo(_validation = depset([validation_output])),
  ]


rule_with_validation = rule(
  implementation = _rule_with_validation_impl,
  outputs = {
    "main": "%{name}.main",
    "implicit": "%{name}.implicit",
  },
  attrs = {
    "_validation_tool": attr.label(
        default = Label("//validation_actions:validation_tool"),
        executable = True,
        cfg = "exec"
    ),
  }
)

検証出力ファイルは、DefaultInfo や他のアクションへの入力には追加されません。このルール種類のターゲットに対する検証アクションは、ターゲットがラベルに依存している場合や、ターゲットの暗黙的な出力が直接的または間接的に依存している場合は、引き続き実行されます。

並列処理の効果を損なう可能性があるため、検証アクションの出力は検証出力グループにのみ送信し、他のアクションの入力には追加しないことが重要です。ただし、Bazel には、これを強制する特別なチェックはありません。したがって、Starlark ルールのテストでは、検証アクションの出力がアクションの入力に追加されていないことをテストする必要があります。次に例を示します。

load("@bazel_skylib//lib:unittest.bzl", "analysistest")

def _validation_outputs_test_impl(ctx):
  env = analysistest.begin(ctx)

  actions = analysistest.target_actions(env)
  target = analysistest.target_under_test(env)
  validation_outputs = target.output_groups._validation.to_list()
  for action in actions:
    for validation_output in validation_outputs:
      if validation_output in action.inputs.to_list():
        analysistest.fail(env,
            "%s is a validation action output, but is an input to action %s" % (
                validation_output, action))

  return analysistest.end(env)

validation_outputs_test = analysistest.make(_validation_outputs_test_impl)

検証操作フラグ

検証アクションの実行は、--run_validations コマンドライン フラグ(デフォルトは true)で制御されます。

サポートが終了した機能

事前に宣言された出力が非推奨になりました

事前に宣言された出力を使用する方法としては、次の 2 つ(非推奨)があります。

  • ruleoutputs パラメータは、事前に宣言された出力ラベルを生成するための出力属性名と文字列テンプレート間のマッピングを指定します。事前に宣言されていない出力を使用し、出力を DefaultInfo.files に明示的に追加することをおすすめします。事前に宣言された出力のラベルではなく、出力を使用するルールの入力としてルール ターゲットのラベルを使用します。

  • 実行可能ルールの場合、ctx.outputs.executable はルール ターゲットと同じ名前の、事前に宣言された実行可能ファイルの出力を指します。ctx.actions.declare_file(ctx.label.name) などを使用して出力を明示的に宣言し、実行可能ファイルを生成するコマンドが実行を許可するように権限を設定します。実行可能な出力を DefaultInfoexecutable パラメータに明示的に渡します。

避けるべき Runfiles の機能

ctx.runfiles 型と runfiles 型には複雑な機能セットがあり、その多くは従来の理由で保持されています。次の推奨事項は複雑さを軽減するのに役立ちます。

  • ctx.runfilescollect_data モードと collect_default モードを使用しないでください。これらのモードでは、紛らわしい方法で、特定のハードコードされた依存関係エッジにわたるランファイルを暗黙的に収集します。代わりに、ctx.runfilesfiles パラメータまたは transitive_files パラメータを使用するか、依存関係にある実行ファイルを runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) にマージして、ファイルを追加します。

  • DefaultInfo コンストラクタの data_runfilesdefault_runfiles使用しないでください。代わりに DefaultInfo(runfiles = ...) を指定してください。「デフォルト」と「データ」の実行ファイルの区別は、従来の理由から維持されます。たとえば、一部のルールでは、デフォルトの出力が data_runfiles に格納されますが、default_runfiles に格納されません。ルールに data_runfiles を使用する代わりに、デフォルトの出力を含めるとともに、実行ファイル(多くの場合は data)を提供する属性から default_runfiles にマージする必要があります。

  • DefaultInfo から runfiles を取得する場合(通常は、現在のルールとその依存関係の間のランファイルをマージする場合にのみ)、DefaultInfo.data_runfiles ではなく DefaultInfo.default_runfiles を使用します。

以前のプロバイダからの移行

従来、Bazel プロバイダは Target オブジェクトの単純なフィールドでした。ドット演算子を使用してアクセスし、プロバイダ オブジェクトのリストではなく、ルールの実装関数から返される struct にフィールドを指定することで作成されました。

return struct(example_info = struct(headers = depset(...)))

このようなプロバイダは、Target オブジェクトの対応するフィールドから取得できます。

transitive_headers = [hdr.example_info.headers for hdr in ctx.attr.hdrs]

このスタイルはサポートが終了しているため、新しいコードでは使用できません。移行に役立つ情報については、以下をご覧ください。新しいプロバイダ メカニズムは名前の競合を回避します。また、プロバイダ シンボルを使用してプロバイダ インスタンスを取得するコードを要求することにより、データの非表示もサポートしています。

現時点では、従来のプロバイダは引き続きサポートされます。次のように、ルールでレガシー プロバイダと最新のプロバイダの両方を返すことができます。

def _old_rule_impl(ctx):
  ...
  legacy_data = struct(x = "foo", ...)
  modern_data = MyInfo(y = "bar", ...)
  # When any legacy providers are returned, the top-level returned value is a
  # struct.
  return struct(
      # One key = value entry for each legacy provider.
      legacy_info = legacy_data,
      ...
      # Additional modern providers:
      providers = [modern_data, ...])

dep がこのルールのインスタンスの結果 Target オブジェクトである場合、プロバイダとそのコンテンツは dep.legacy_info.xdep[MyInfo].y として取得できます。

返される構造体は、providers に加えて、特別な意味を持つ(つまり、対応するレガシー プロバイダが作成されない)他のフィールドを取ることもできます。

  • フィールド filesrunfilesdata_runfilesdefault_runfilesexecutable は、DefaultInfo の同じ名前のフィールドに対応しています。DefaultInfo プロバイダを返すときに、これらのフィールドを指定することはできません。

  • フィールド output_groups は構造体の値を取り、OutputGroupInfo に対応します。

ルールの provides 宣言と依存関係属性の providers 宣言では、レガシー プロバイダは文字列として渡され、最新のプロバイダは Info シンボルで渡されます。移行する際は、必ず文字列から記号に変更してください。すべてのルールをアトミックに更新することが難しい複雑なルールセットや大規模なルールセットの場合は、次の一連の手順に従うと、より簡単な場合があります。

  1. 上記の構文を使用して、レガシー プロバイダを生成するルールを変更し、レガシーと最新のプロバイダの両方を生成します。以前のプロバイダを返すことを宣言しているルールについては、その宣言を更新して、従来のプロバイダと最新のプロバイダの両方を含めるようにします。

  2. 以前のプロバイダを使用するルールを変更して、代わりに最新のプロバイダを使用するようにします。属性の宣言で以前のプロバイダが必要な場合は、最新のプロバイダを必要とするように更新します。必要に応じて、コンシューマにプロバイダを承認または要求することで、この作業をステップ 1 にインターリーブできます。つまり、hasattr(target, 'foo') を使用して以前のプロバイダの存在をテストするか、FooInfo in target を使用して新しいプロバイダの存在を確認します。

  3. すべてのルールから従来のプロバイダを完全に削除します。