ルールでは、一連の出力を生成するために Bazel が入力に対して実行する一連のアクションを定義します。これらの出力は、ルールの実装関数によって返されるプロバイダで参照されます。たとえば、C++ バイナリルールは次のようになります。
- 一連の
.cpp
ソースファイル(入力)を受け取ります。 - ソースファイルに対して
g++
を実行します(アクション)。 - 実行可能出力と、実行時に使用可能にするその他のファイルとともに
DefaultInfo
プロバイダを返します。 - ターゲットとその依存関係から収集された C++ 固有の情報を使用して、
CcInfo
プロバイダを返します。
Bazel の観点からは、g++
と標準 C++ ライブラリもこのルールの入力になります。ルールの作成者は、ルールに対するユーザー提供の入力だけでなく、アクションの実行に必要なすべてのツールとライブラリも考慮する必要があります。
ルールを作成または変更する前に、Bazel のビルドフェーズについて理解しておいてください。ビルドの 3 つのフェーズ(読み込み、分析、実行)を理解することが重要です。また、ルールとマクロの違いを理解するために、マクロについて学ぶことも役立ちます。ご利用にあたっては、まずルールのチュートリアルをご覧ください。 このページを参照用としてご活用ください。
Bazel 自体にはいくつかのルールが組み込まれています。これらのネイティブ ルール(cc_library
や java_binary
など)は、特定の言語に対するコアサポートを提供します。独自のルールを定義することで、Bazel がネイティブにサポートしていない言語やツールに対する同様のサポートを追加できます。
Bazel は、Starlark 言語を使用してルールを作成するための拡張モデルを提供します。これらのルールは、BUILD
ファイルから直接読み込むことができる .bzl
ファイルに記述されます。
独自のルールを定義する際は、サポートする属性と出力の生成方法を決める必要があります。
ルールの 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
ファイルが評価されてターゲットをインスタンス化する際に、読み込みフェーズでのみ呼び出すことができます。
属性
属性はルールの引数です。属性は、ターゲットの実装に特定の値を指定することも、他のターゲットを参照して依存関係のグラフを作成することもできます。
srcs
や deps
などのルール固有の属性を定義するには、属性名からスキーマ(attr
モジュールを使用して作成)へのマップを rule
の attrs
パラメータに渡します。name
や visibility
などの一般的な属性は、すべてのルールに暗黙的に追加されます。追加属性は、実行ルールとテストルールに暗黙的に追加されます。ルールに暗黙的に追加される属性は、attrs
に渡されるディクショナリに含めることはできません。
依存関係属性
通常、ソースコードを処理するルールでは、さまざまな依存関係のタイプを処理するために次の属性を定義します。
srcs
には、ターゲットのアクションによって処理されるソースファイルを指定します。多くの場合、属性スキーマではルールが処理するソースファイルの種類に想定されるファイル拡張子を指定します。通常、ヘッダー ファイルを持つ言語のルールでは、ターゲットとそのコンシューマによって処理されるヘッダーに個別のhdrs
属性を指定します。deps
には、ターゲットのコード依存関係を指定します。属性スキーマでは、これらの依存関係が提供する必要があるプロバイダを指定する必要があります。(たとえば、cc_library
はCcInfo
を提供します)。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_list
、attr.label
、attr.label_keyed_string_dict
で定義される属性)は、ターゲットとそのラベル(または対応する Label
オブジェクト)を持つターゲットの間の特定のタイプの依存関係を指定します。これらのラベルのリポジトリ(場合によってはパス)は、定義されたターゲットを基準として解決されます。
example_library(
name = "my_target",
deps = [":other_target"],
)
example_library(
name = "other_target",
...
)
この例では、other_target
は my_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.output
や attr.output_list
などの出力属性は、ターゲットが生成する出力ファイルを宣言します。これらは、次の 2 つの点で依存関係属性と異なります。
- 出力ファイル ターゲットは、別の場所で定義されたターゲットを参照するのではなく、定義します。
- 出力ファイルのターゲットは、インスタンス化されたルール ターゲットに依存します。その逆ではありません。
通常、出力属性は、ターゲット名に基づかないユーザー定義名で出力をルールで作成する必要がある場合にのみ使用されます。ルールの出力属性が 1 つの場合、通常は out
または outs
という名前になります。
出力属性は、事前に宣言された出力を作成する場合におすすめの方法です。事前に宣言された出力は、明示的に依存するか、コマンドラインでリクエストできます。
実装関数
すべてのルールに implementation
関数が必要です。これらの関数は分析フェーズで厳格に実行され、読み込みフェーズで生成されたターゲットのグラフを実行フェーズで行われるアクションのグラフに変換します。そのため、実装関数でファイルを読み書きすることはできません。
通常、ルール実装関数は非公開(先頭にアンダースコアが付いています)です。通常はルールと同じ名前が付けられますが、末尾に _impl
が付加されます。
実装関数は、ルール コンテキストという 1 つのパラメータを受け取ります。通常は ctx
という名前です。プロバイダのリストを返します。
ターゲット
依存関係は、分析時に Target
オブジェクトとして表されます。これらのオブジェクトには、ターゲットの実装関数が実行されたときに生成された providers が含まれます。
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
が 1 つ含まれます。ctx.executable
は ctx.file
と同じように動作しますが、仕様で executable=True
が設定されている依存関係属性のフィールドのみを含みます。
出力の宣言
分析フェーズでは、ルールの実装関数によって出力を生成できます。読み込みフェーズではすべてのラベルを知る必要があるため、これらの追加出力にはラベルがありません。出力の File
オブジェクトは、ctx.actions.declare_file
と ctx.actions.declare_directory
を使用して作成できます。多くの場合、出力の名前はターゲットの名前 ctx.label.name
に基づいています。
def _example_library_impl(ctx):
...
output_file = ctx.actions.declare_file(ctx.label.name + ".output")
...
事前に宣言された出力(出力属性用に作成された出力など)の場合、代わりに File
オブジェクトを、ctx.outputs
の対応するフィールドから取得できます。
アクション
アクションは、「hello.c で gcc を実行して hello.o を取得する」など、一連の入力から一連の出力を生成する方法を表します。アクションが作成されても、Bazel はコマンドをすぐに実行しません。アクションは別のアクションの出力に依存する可能性があるため、依存関係のグラフに登録されます。たとえば C では、リンカーはコンパイラの後に呼び出す必要があります。
アクションを作成する汎用関数は、ctx.actions
で定義されています。
ctx.actions.run
: 実行可能ファイルを実行します。ctx.actions.run_shell
: シェルコマンドを実行します。ctx.actions.write
: 文字列をファイルに書き込みます。ctx.actions.expand_template
: テンプレートからファイルを生成します。
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
に蓄積します)。
ターゲットのプロバイダは、実装関数が返す Provider
オブジェクトのリストで指定されます。
古い実装関数は、プロバイダ オブジェクトのリストではなく struct
を返すレガシー スタイルで記述することもできます。このスタイルは使用しないことを強くおすすめします。ルールをスタイルから移行する必要があります。
デフォルトの出力
ターゲットのデフォルトの出力は、コマンドラインでターゲットのビルドがリクエストされたときにデフォルトでリクエストされる出力です。たとえば、java_library
ターゲット //pkg:foo
にはデフォルトの出力として foo.jar
があるため、bazel build //pkg:foo
コマンドでビルドされます。
デフォルトの出力は、DefaultInfo
の files
パラメータで指定します。
def _example_library_impl(ctx):
...
return [
DefaultInfo(files = depset([output_file]), ...),
...
]
ルール実装によって DefaultInfo
が返されない場合、または files
パラメータが指定されていない場合、DefaultInfo.files
はデフォルトですべての事前に宣言された出力(通常は出力属性で作成された出力)になります。
アクションを実行するルールでは、出力を直接使用することが想定されていない場合でも、デフォルトの出力を提供する必要があります。リクエストされた出力のグラフにないアクションはプルーニングされます。出力がターゲットのコンシューマのみで使用される場合、ターゲットが独立して組み込まれているときには、このようなアクションは実行されません。この場合、失敗したターゲットを再構築するだけでは障害が再現されないため、デバッグが困難になります。
実行ファイル
ランファイルは、ビルド時ではなく、実行時にターゲットによって使用されるファイルのセットです。実行フェーズで、Bazel は runfile へのシンボリック リンクを含むディレクトリ ツリーを作成します。これにより、バイナリの環境がステージングされ、実行時にランファイルにアクセスできるようになります。
実行ファイルはルールの作成時に手動で追加できます。
runfiles
オブジェクトは、ルール コンテキストの runfiles
メソッド(ctx.runfiles
)で作成し、DefaultInfo
の runfiles
パラメータに渡すことができます。実行可能ルールの実行可能出力は、暗黙的に実行ファイルに追加されます。
一部のルールでは属性を指定します(通常は data
という名前)。この属性の出力はターゲットのランファイルに追加されます。実行ファイルは data
からマージするだけでなく、最終的な実行のためのコードを提供する可能性のある属性(通常は srcs
(data
が関連付けられた 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 つの値のタプルに変わります。init
が使用されていないときの通常の戻り値であるプロバイダ シンボルと、「未加工のコンストラクタ」です。
この場合、プロバイダ シンボルが呼び出されたときに、新しいインスタンスを直接返すのではなく、引数が init
コールバックに転送されます。コールバックの戻り値は、フィールド名(文字列)を値にマッピングする dict 型でなければなりません。これは、新しいインスタンスのフィールドの初期化に使用されます。コールバックには任意の署名があり、引数が署名と一致しない場合、コールバックが直接呼び出された場合と同様にエラーが報告されます。
一方、未加工のコンストラクタは、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
のもう 1 つの使用方法は、ユーザーがプロバイダ シンボルをまったく呼び出さないようにして、代わりにファクトリ関数を使用するように強制することです。
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
は、ルールのデフォルト出力に追加されます(そのため、executable
と files
の両方に渡す必要はありません)。また、暗黙的に runfile にも追加されます。
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
)で実行すると、runfiles ディレクトリのルートが実行可能ファイルに隣接します。パスの関係は次のとおりです。
# Given executable_file and runfile_file:
runfiles_root = executable_file.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:mytarget
が debug_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 には「構成」と移行の概念があります。最上位ターゲット(コマンドラインでリクエストされるターゲット)は「ターゲット」構成でビルドされ、実行プラットフォームで実行されるツールは「exec」構成でビルドされます。コンパイラに渡される CPU アーキテクチャを変更するなど、構成に基づいてルールによってさまざまなアクションが生成されることがあります。異なる構成で同じライブラリが必要になる場合があります。その場合、分析が行われ、場合によっては複数回ビルドされます。
デフォルトでは、Bazel はターゲットの依存関係をターゲット自体と同じ構成にビルドします(つまり移行なしでビルドします)。依存関係がターゲットのビルドに必要なツールである場合は、対応する属性で exec 構成への移行を指定する必要があります。これにより、ツールとそのすべての依存関係が実行プラットフォーム用にビルドされます。
依存関係属性ごとに、cfg
を使用して、依存関係を同じ構成でビルドするか、exec 構成に移行するかを決定できます。依存関係属性に executable=True
フラグがある場合は、cfg
を明示的に設定する必要があります。これは、誤った構成のツールを誤ってビルドするのを防ぐためです。
例を見る
一般に、実行時に必要になるソース、依存ライブラリ、実行可能ファイルは、同じ構成を使用できます。
ビルドの一部として実行されるツール(コンパイラ、コード生成ツールなど)は、実行構成用にビルドする必要があります。この場合は、属性に cfg="exec"
を指定します。
それ以外の場合は、実行時に(テストの一部としてなど)使用される実行可能ファイルは、ターゲット構成用にビルドする必要があります。この場合は、属性に cfg="target"
を指定します。
cfg="target"
は実際には何もしません。ルール設計者がその意図を明確に示すのに便利な値にすぎません。executable=False
(cfg
は省略可能です)は、読みやすさに役立った場合にのみ設定します。
また、cfg=my_transition
を使用してユーザー定義の遷移を使用することもできます。これにより、ルール作成者は構成の変更に非常に高い柔軟性を持たせることができますが、ビルドグラフを拡大し、わかりにくくなるという欠点があります。
注: 従来、Bazel には実行プラットフォームの概念がなく、すべてのビルド アクションがホストマシンで実行されると考えられていました。このため、単一の「ホスト」構成と、ホスト構成内で依存関係を構築するために使用できる「ホスト」移行が存在します。多くのルールでは現在もツールに「host」移行を使用していますが、これは現在非推奨となっており、可能な限り「exec」移行を使用するように移行中です。
「host」構成と「exec」構成には多くの違いがあります。
- 「host」はターミナルであり、「exec」は対象外である。依存関係が「host」構成に入ると、それ以上の移行は許可されません。「exec」構成に移行した後は、さらなる構成の移行を続けることができます。
- 「host」はモノリシックですが、「exec」は異なります。「host」構成は 1 つだけですが、実行プラットフォームごとに異なる「exec」構成を指定できます。
- 「host」は、Bazel と同じマシン、または非常によく似たマシンでツールを実行していることを前提としています。これは現在は該当しないため、ローカルマシンまたはリモート エグゼキュータでビルド アクションを実行できます。リモート エグゼキュータがローカルマシンと同じ CPU および OS であるという保証はありません。
「exec」構成と「host」構成のどちらでも、同じオプションの変更が適用されます(たとえば、--host_compilation_mode
から --compilation_mode
を設定し、--host_cpu
から --cpu
を設定します)。違いは、「host」構成は他のすべてのフラグのデフォルト値から始まり、「exec」構成はターゲット構成に基づくフラグの現在の値から始まります。
構成フラグメント
ルールは、cpp
、java
、jvm
などの構成フラグメントにアクセスすることがあります。ただし、アクセスエラーを回避するには、必要なフラグメントをすべて宣言する必要があります。
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
...
)
ctx.fragments
は、ターゲット構成の構成フラグメントのみを提供します。ホスト構成のフラグメントにアクセスする場合は、代わりに ctx.host_fragments
を使用します。
Runfiles シンボリック リンク
通常、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_info
の dependency_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 が検証アクションを常に実行するためです。ただし、これには大きな欠点があります。
検証アクションはビルドのクリティカル パスにあります。Bazel はコンパイル アクションの実行には空の出力が必要であると判断するため、このコンパイル アクションで入力が無視されても、まず検証アクションが実行されます。これにより、並列処理が減少し、ビルドが遅くなります。
コンパイル アクションの代わりにビルドの他のアクションが実行される可能性がある場合、検証アクションの空の出力をこれらのアクションにも追加する必要があります(
java_library
のソース jar 出力など)。これは、コンパイル アクションの代わりに実行される可能性のある新しいアクションが後で追加され、空の検証出力が誤ってなくなった場合にも問題になります。
これらの問題を解決するには、検証出力グループを使用します。
検証の出力グループ
検証出力グループは、本来は使用されていない検証アクションの出力を保持するために設計された出力グループです。これにより、他のアクションの入力に人為的に追加する必要がなくなります。
このグループは、--output_groups
フラグの値に関係なく、またターゲットの依存関係(コマンドラインで、依存関係として、ターゲットの暗黙的な出力など)に関係なく、常に出力がリクエストされるという点が特別なものです。通常のキャッシュとインクリメンタリティは引き続き適用されます。検証アクションの入力が変更されておらず、以前に検証アクションが成功した場合、検証アクションは実行されません。
この出力グループを使用する場合、検証アクションでなんらかのファイルが(空のファイルであっても)出力される必要があります。ファイルが作成されるように、通常は出力を作成しない一部のツールをラップする必要が生じる場合があります。
ターゲットの検証アクションは、次の 3 つのケースでは実行されません。
- ターゲットがツールとして依存している場合
- ターゲットが暗黙的な依存関係として依存している場合(「_」で始まる属性など)
- ターゲットがホスト構成または実行構成でビルドされたとき。
これらのターゲットには、検証エラーを発見する独自のビルドとテストがあることを前提としています。
Validations 出力グループの使用
検証出力グループには _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 つの方法が非推奨になっています。
rule
のoutputs
パラメータは、事前に宣言された出力ラベルを生成するための出力属性名と文字列テンプレート間のマッピングを指定します。事前に宣言されていない出力を使用し、出力をDefaultInfo.files
に明示的に追加することをおすすめします。事前に宣言された出力のラベルではなく、出力を使用するルールの入力として、ルールのターゲットのラベルを使用します。実行ルールの場合、
ctx.outputs.executable
はルールの対象と同じ名前で事前に宣言された実行ファイルの出力を指します。ctx.actions.declare_file(ctx.label.name)
などを使用して出力を明示的に宣言し、実行可能ファイルを生成するコマンドで権限を設定して実行を許可します。実行可能出力をDefaultInfo
のexecutable
パラメータに明示的に渡します。
避けるべき Runfile 機能
ctx.runfiles
型と runfiles
型には複雑な機能セットがあり、その多くは古い理由で保持されています。次の推奨事項は、複雑さを軽減するのに役立ちます。
ctx.runfiles
のcollect_data
モードとcollect_default
モードは使用しないでください。これらのモードでは、特定のハードコードされた依存関係エッジ全体で、紛らわしい方法でランファイルが暗黙的に収集されます。代わりに、ctx.runfiles
のfiles
パラメータまたはtransitive_files
パラメータを使用するか、依存関係のランファイルとrunfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
をマージして、ファイルを追加します。DefaultInfo
コンストラクタのdata_runfiles
とdefault_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.x
と dep[MyInfo].y
として取得できます。
返される構造体は、providers
に加えて、特別な意味を持つ他のフィールドを取ることもできます(したがって、対応するレガシー プロバイダは作成されません)。
フィールド
files
、runfiles
、data_runfiles
、default_runfiles
、executable
は、DefaultInfo
の同じ名前のフィールドに対応しています。DefaultInfo
プロバイダを返す場合は、これらのフィールドを指定することはできません。フィールド
output_groups
は構造体値で、OutputGroupInfo
に対応します。
ルールの provides
宣言と、依存関係属性の providers
宣言では、レガシー プロバイダは文字列として渡され、最新のプロバイダは *Info
シンボルで渡されます。移行するときは、必ず文字列から記号に変更してください。すべてのルールをアトミックに更新するのが難しい複雑なルールセットや大規模なルールセットでは、以下の手順に従うと簡単になる場合があります。
上記の構文を使用して、レガシー プロバイダを生成するルールを変更して、レガシー プロバイダと最新のプロバイダの両方を生成するようにします。以前のプロバイダを返すことを宣言するルールについては、以前のプロバイダと最新のプロバイダの両方を含めるようにその宣言を更新します。
レガシー プロバイダを使用するルールを変更して、代わりに最新のプロバイダを使用するようにします。属性の宣言に以前のプロバイダが必要な場合は、最新のプロバイダを要求するように更新します。必要に応じて、コンシューマーにプロバイダを承認または要求することで、この処理をステップ 1 にインターリーブできます。つまり、
hasattr(target, 'foo')
を使用して以前のプロバイダの存在をテストし、FooInfo in target
を使用して新しいプロバイダの存在をテストします。すべてのルールから従来のプロバイダを完全に削除します。