このページでは、アスペクトの基本とメリットについて説明し、簡単な例と高度な例を紹介します。
アスペクトを使用すると、追加情報とアクションでビルド依存関係グラフを拡張できます。アスペクトが役立つ一般的なシナリオの例:
- Bazel を統合する IDE は、アスペクトを使用してプロジェクトに関する情報を収集できます。
- コード生成ツールは、さまざまな側面を活用して、ターゲットに依存しない方法で入力に対して実行できます。たとえば、
BUILD
ファイルで protobuf ライブラリ定義の階層を指定し、言語固有のルールでアスペクトを使用して、特定の言語の protobuf サポートコードを生成するアクションをアタッチできます。
アスペクトの基本
BUILD
ファイルには、プロジェクトのソースコードを記述し、どのソースファイルがプロジェクトに含まれているか、どのアーティファクト(ターゲット)、それらのファイルからビルドする必要があるか、それらのファイル間の依存関係は何かなどを記述します。Bazel はこの情報を使用してビルドを実行します。つまり、アーティファクトの生成(コンパイラやリンカーの実行など)に必要な一連のアクションを把握して、それらのアクションを実行します。Bazel は、ターゲット間の依存関係グラフを作成し、このグラフにアクセスしてアクションを収集します。
次の BUILD
ファイルについて考えてみましょう。
java_library(name = 'W', ...)
java_library(name = 'Y', deps = [':W'], ...)
java_library(name = 'Z', deps = [':W'], ...)
java_library(name = 'Q', ...)
java_library(name = 'T', deps = [':Q'], ...)
java_library(name = 'X', deps = [':Y',':Z'], runtime_deps = [':T'], ...)
この BUILD
ファイルは、次の図に示す依存関係グラフを定義します。
図 1. BUILD
ファイルの依存関係グラフ。
Bazel は、上の例のすべてのターゲットについて、対応するルールの実装関数(この場合は java_library)を呼び出して、この依存関係グラフを分析します。ルール実装関数は、アーティファクト(.jar
ファイルなど)をビルドするアクションを生成し、アーティファクトの場所や名前などの情報を、プロバイダ内のターゲットの逆依存関係に渡します。
各アスペクトは、アクションを生成してプロバイダを返す実装関数を備えているという点で、ルールと似ています。ただし、その効果は依存関係グラフの作成方法に起因しています。アスペクトには、実装と、それが伝播されるすべての属性のリストがあります。「deps」という名前の属性に沿って伝播するアスペクト A について考えてみましょう。このアスペクトをターゲット X に適用して、アスペクト アプリケーション ノード A(X) を生成します。適用時に、アスペクト A は、X が「deps」属性で参照するすべてのターゲット(A の伝播リスト内のすべての属性)に再帰的に適用されます。
したがって、ターゲット X にアスペクト A を適用する 1 回の操作で、次の図に示すように、ターゲットの元の依存関係グラフの「シャドウグラフ」が生成されます。
図 2. アスペクトを使用してグラフを作成する。
シャドーイングされるエッジは、伝播セット内の属性に沿ったエッジのみです。したがって、この例では runtime_deps
エッジはシャドーイングされていません。すると、元のグラフのノードでルール実装が呼び出されるのと同様に、シャドウグラフ内のすべてのノードでアスペクト実装関数が呼び出されます。
簡単な例
この例では、ルールのソースファイルと、deps
属性を持つすべての依存関係を再帰的に出力する方法を示しています。アスペクトの実装、アスペクトの定義、Bazel コマンドラインからアスペクトを呼び出す方法について説明します。
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
サンプルを複数の要素に分割して、それぞれを個別に確認しましょう。
アスペクトの定義
print_aspect = aspect(
implementation = _print_aspect_impl,
attr_aspects = ['deps'],
)
アスペクトの定義はルールの定義と同様に、aspect
関数を使用して定義されます。
ルールと同様に、アスペクトには実装関数があります。このケースでは _print_aspect_impl
です。
attr_aspects
は、アスペクトが伝播するルール属性のリストです。この場合、アスペクトは適用されるルールの deps
属性に沿って伝播されます。
attr_aspects
のもう 1 つの一般的な引数は ['*']
です。これは、ルールのすべての属性にアスペクトを伝播します。
アスペクトの実装
def _print_aspect_impl(target, ctx):
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the files that make up the sources and
# print their paths.
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
print(f.path)
return []
アスペクト実装関数は、ルール実装関数と同様です。これらのメソッドはプロバイダを返し、アクションを生成し、次の 2 つの引数を取ることができます。
実装関数は、ctx.rule.attr
を介してターゲット ルールの属性にアクセスできます。適用先のターゲットによって(target
引数を介して)提供されるプロバイダを調べることができます。
プロバイダのリストを返すには、アスペクトが必要です。この例では、アスペクトは何も提供しないため、空のリストを返します。
コマンドラインを使用したアスペクトの呼び出し
アスペクトを適用する最も簡単な方法は、コマンドラインから --aspects
引数を使用することです。上記のアスペクトが print.bzl
という名前のファイルで定義されていると仮定します。
bazel build //MyExample:example --aspects print.bzl%print_aspect
この場合、print_aspect
がターゲット example
と、deps
属性を介して再帰的にアクセスできるすべてのターゲット ルールに適用されます。
--aspects
フラグは 1 つの引数を取ります。これは、<extension file label>%<aspect top-level name>
形式でアスペクトの仕様です。
高度な例
次の例は、ターゲット内のファイルをカウントするターゲット ルールのアスペクトを使用して、場合によっては拡張子でフィルタリングする方法を示しています。プロバイダを使用して値を返す方法、パラメータを使用してアスペクトの実装に引数を渡す方法、ルールからアスペクトを呼び出す方法について説明します。
file_count.bzl
ファイル:
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
BUILD.bazel
ファイル:
load('//:file_count.bzl', 'file_count_rule')
cc_library(
name = 'lib',
srcs = [
'lib.h',
'lib.cc',
],
)
cc_binary(
name = 'app',
srcs = [
'app.h',
'app.cc',
'main.cc',
],
deps = ['lib'],
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
アスペクトの定義
file_count_aspect = aspect(
implementation = _file_count_aspect_impl,
attr_aspects = ['deps'],
attrs = {
'extension' : attr.string(values = ['*', 'h', 'cc']),
}
)
この例は、deps
属性を通じてアスペクトがどのように伝播するかを示しています。
attrs
は特徴のセットを定義します。パブリック アスペクト属性は string
型であり、パラメータと呼ばれます。パラメータには values
属性を指定する必要があります。この例には、値として「*
」、「h
」、「cc
」を使用できる extension
というパラメータがあります。
アスペクトのパラメータ値は、アスペクトをリクエストするルールと同じ名前の文字列属性から取得されます(file_count_rule
の定義をご覧ください)。パラメータを持つアスペクトは、パラメータを定義する構文がないため、コマンドライン経由では使用できません。
アスペクトには、label
または label_list
タイプのプライベート属性を含めることもできます。プライベート ラベル属性を使用すると、アスペクトによって生成されるアクションに必要なツールやライブラリへの依存関係を指定できます。この例では非公開属性は定義されていませんが、次のコード スニペットは、ツールをアスペクトに渡す方法を示しています。
...
attrs = {
'_protoc' : attr.label(
default = Label('//tools:protoc'),
executable = True,
cfg = "exec"
)
}
...
アスペクトの実装
FileCountInfo = provider(
fields = {
'count' : 'number of files'
}
)
def _file_count_aspect_impl(target, ctx):
count = 0
# Make sure the rule has a srcs attribute.
if hasattr(ctx.rule.attr, 'srcs'):
# Iterate through the sources counting files
for src in ctx.rule.attr.srcs:
for f in src.files.to_list():
if ctx.attr.extension == '*' or ctx.attr.extension == f.extension:
count = count + 1
# Get the counts from our dependencies.
for dep in ctx.rule.attr.deps:
count = count + dep[FileCountInfo].count
return [FileCountInfo(count = count)]
ルール実装関数と同様に、アスペクト実装関数は、依存関係にアクセス可能なプロバイダの構造体を返します。
この例では、FileCountInfo
は、1 つのフィールド count
を持つプロバイダとして定義されています。fields
属性を使用してプロバイダのフィールドを明示的に定義することをおすすめします。
アスペクト アプリ A(X) のプロバイダ セットは、ターゲット X のルール実装とアスペクト A の実装に由来するプロバイダの結合です。ルールの実装を伝播するプロバイダは、アスペクトが適用される前に作成および凍結され、アスペクトから変更することはできません。ターゲットとそれに適用されるアスペクトが、同じタイプのプロバイダを提供している場合はエラーになります。ただし、OutputGroupInfo
(ルールとアスペクトが異なる出力グループを指定する限りマージされる)と InstrumentedFilesInfo
(アスペクトから取得される)は例外です。つまり、アスペクトの実装が DefaultInfo
を返すことはありません。
パラメータとプライベート属性は ctx
の属性で渡されます。この例では、extension
パラメータを参照し、カウントするファイルを決定します。
戻りプロバイダの場合、アスペクトが伝播される属性の値(attr_aspects
リストから)は、そのアスペクトの適用結果で置き換えられます。たとえば、ターゲット X の依存関係に Y と Z がある場合、A(X) の ctx.rule.attr.deps
は [A(Y), A(Z)] になります。この例の ctx.rule.attr.deps
は、アスペクトが適用された元のターゲットの「deps」にアスペクトを適用した結果である Target オブジェクトです。
この例のアスペクトでは、ターゲットの依存関係から FileCountInfo
プロバイダにアクセスし、ファイルの総数の推移を累積します。
ルールからアスペクトを呼び出す
def _file_count_rule_impl(ctx):
for dep in ctx.attr.deps:
print(dep[FileCountInfo].count)
file_count_rule = rule(
implementation = _file_count_rule_impl,
attrs = {
'deps' : attr.label_list(aspects = [file_count_aspect]),
'extension' : attr.string(default = '*'),
},
)
このルールの実装は、ctx.attr.deps
を介して FileCountInfo
にアクセスする方法を示しています。
ルール定義では、パラメータ(extension
)を定義してデフォルト値(*
)を指定する方法を示しています。「cc
」、「h
」、「*
」のいずれでもないデフォルト値を指定すると、アスペクト定義のパラメータに適用される制限により、エラーになります。
ターゲット ルールを使用したアスペクトの呼び出し
load('//:file_count.bzl', 'file_count_rule')
cc_binary(
name = 'app',
...
)
file_count_rule(
name = 'file_count',
deps = ['app'],
extension = 'h',
)
これは、ルールを介して extension
パラメータをアスペクトに渡す方法を示しています。ルールの実装では、extension
パラメータにデフォルト値があるため、extension
は省略可能なパラメータと見なされます。
file_count
ターゲットが作成されると、アスペクト自体が評価され、deps
を介して再帰的にアクセス可能なすべてのターゲットが評価されます。