ルール

問題を報告する ソースを表示

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

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

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

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

Bazel にはいくつかのルールが組み込まれていますが、cc_libraryjava_binary などのネイティブ ルールは、一部の言語でコアサポートを提供します。独自のルールを定義すると、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 ファイルで指定していないため、暗黙的です。暗黙的な依存関係は、ほとんどの場合、ルールが使用するツールの指定に関心がないため、ルールとツールの関係(ビルド時の依存関係、コンパイラなど)をハードコードするのに役立ちます。ルールの実装関数内では、これは他の依存関係と同様に処理されます。

ユーザーがその値をオーバーライドせずに暗黙的な依存関係を提供する場合は、アンダースコア(_)で始まる名前を指定して、属性を非公開にします。非公開属性にはデフォルト値を指定する必要があります。一般的には、暗黙の依存関係に非公開属性を使用するのは理にかなっています。

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]

プロバイダ オブジェクトのリストではなく、ターゲットの実装関数から struct が返される従来のスタイルの場合:

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

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

transitive_headers = [hdr.example_info.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 が含まれています。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 の対応するフィールドから取得できます。

Actions

アクションは、一連の入力から一連の出力を生成する方法について説明します(例: 「hello.c で gcc を実行して 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 コマンドを実行する場合は、インフレートする前に、どのファイルをインフレートするかを指定する必要があります。内部で可変的な数のファイルを作成するアクションは、単一のファイル(zip や tar などのアーカイブ形式)にラップできます。

操作では、すべての入力内容を一覧表示する必要があります。使用されていない入力の一覧表示は許可されますが、非効率的です。

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

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

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

プロバイダ

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

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

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

古い実装関数は、プロバイダ オブジェクトのリストではなく 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 や、最終的に実行するためのコードを提供する属性(通常は srcsfilegroup ターゲットと関連付けられた data を含む)および 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 を取得してインスタンスを取得したりできます。

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

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

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

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

# //pkg:exampleinfo.bzl

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

# It's possible to define an init accepting positional arguments, but
# 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(
    ...
    init = _exampleinfo_init)

export ExampleInfo

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

    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(
 ...
)

Runfile のロケーション

bazel run(または test)で実行可能ターゲットを実行している場合、実行ファイル ディレクトリのルートは実行可能ファイルの隣にあります。パスは次のとおりです。

# 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 から呼び出されるバイナリに同じ前提条件を設定することはできません。これを軽減するには、各バイナリは環境またはコマンドライン引数/フラグを使用して、その 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 には「構成」と移行という概念があります。最上位のターゲット(コマンドラインでリクエストされるターゲット)は「target」構成でビルドされ、実行プラットフォームで実行されるツールは「exec」構成でビルドされます。ルールが構成に基づいて異なるアクションを生成することがあります。たとえば、コンパイラに渡される CPU アーキテクチャを変更する場合などです。構成によっては、同じライブラリが必要になることがあります。この場合、分析され、複数回ビルドされる可能性があります。

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

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

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

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

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

cfg="target" は、実際には何も行いません。ルールの設計者が意図を明示的に行えるように、純粋な便宜的な値となります。executable=Falsecfg は任意)を使用する場合は、読みやすくなる場合にのみ設定します。

また、cfg=my_transition を使用してユーザー定義の遷移を使用することもできます。これにより、ルールの作成者は構成の変更で高い柔軟性を得ることができますが、ビルドグラフが大きくなり、理解しにくくなります

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

構成フラグメント

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

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
    host_fragments = ["java"], # Required fragments of the host 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 が返されない場合は、dependency_attributes の属性スキーマで cfg"host" または "exec" に設定していない、各ツール以外の依存関係属性を使用してデフォルトの属性が作成されます。(これは srcs のような属性を source_attributes ではなく dependency_attributes に配置するため、理想的な動作ではありませんが、依存関係チェーン内のすべてのルールで明示的なカバレッジ構成が不要になります)。

検証アクション

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

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

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

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

これは、Bazel が常にコンパイル アクションの実行時に検証アクションを実行するためです。ただし、大きな欠点があります。

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

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

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

検証の出力グループ

検証出力グループは、それ以外の、検証アクションの出力に含まれていないデータを保持するために設計された出力グループであり、他のアクションの入力に人為的に追加する必要はありません。

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

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

ターゲットの検証アクションが次の 3 つのケースで実行されない。

  • ターゲットがツールに依存している場合
  • ターゲットが暗黙的な依存関係として依存されている場合(例: 「_」で始まる属性)
  • ターゲットがホストまたは実行構成でビルドされた場合。

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

検証出力グループの使用

検証出力グループは _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 パラメータに明示的に渡します。

回避する Runfile 機能

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 オブジェクトの単純なフィールドでした。これらはドット演算子を使用してアクセスされ、ルールの実装関数によって返された構造体にこのフィールドを追加して作成されました。

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

現時点では、従来のプロバイダはまだサポートされています。ルールでは、従来のプロバイダと最新のプロバイダの両方を返すことができます。

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. 従来のプロバイダを使用するルールを、最新のプロバイダを使用するように変更します。属性の宣言で従来のプロバイダが必要な場合は、代わりに最新のプロバイダを使用するように更新してください。必要に応じて、hasattr(target, 'foo') を使用してレガシー プロバイダ、または FooInfo in target を使用して新しいプロバイダの有無をチェックするようコンシューマにリクエストまたは要求してもらうことで、ステップ 1 とこの作業をインターリーブできます。

  3. すべてのルールから以前のプロバイダを完全に削除する