アスペクト

このページでは、 アスペクトを使用する際の基本とメリットについて説明し、簡単な例と高度な 例を示します。

アスペクトを使用すると、ビルド依存関係グラフに追加情報 とアクションを追加できます。アスペクトが役立つ一般的なシナリオは次のとおりです。

  • Bazel と統合された IDE は、アスペクトを使用して プロジェクトに関する情報を収集できます。
  • コード生成ツールは、アスペクトを活用して、入力に対して ターゲットに依存しない方法で実行できます。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 の伝播リスト内のすべての属性)に再帰的に適用されます。

したがって、アスペクト A をターゲット X に適用するだけで、 次の図に示すターゲットの元の依存関係グラフの「シャドウ グラフ」が生成されます。

Aspect を使用してグラフを作成する

図 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 つの引数を取ります。

  • target: アスペクトが適用されるターゲット
  • ctx: 属性へのアクセス、出力とアクションの生成に使用できる ctx オブジェクト 。

実装関数は、ターゲット ルールの属性に 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 は、アスペクトの一連の属性を定義します。公開アスペクト属性 はパラメータを定義し、型は boolintstring のいずれかになります。 ルール伝播アスペクトの場合、int パラメータと string パラメータには values を指定する必要があります。この例には、値として '*'、'h'、'cc' を使用できる extension というパラメータがあります。

ルール伝播アスペクトの場合、パラメータ値は、同じ名前と型のルール属性を使用して、アスペクトをリクエストするルールから取得されます (file_count_rule の定義をご覧ください)。

コマンドライン アスペクトの場合、パラメータ値は --aspects_parameters フラグを使用して渡すことができます。int パラメータと string パラメータの values の制限は 省略できます。

アスペクトには、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 は、フィールド count が 1 つあるプロバイダとして定義されています。fields 属性を使用して、 プロバイダのフィールドを明示的に定義することをおすすめします。

アスペクト アプリケーション A(X)のプロバイダのセットは、ターゲット X のルールの実装とアスペクト A の 実装から取得されるプロバイダの和集合です。ルール実装が伝播するプロバイダは、アスペクトが適用される前に作成されてフリーズされるため、アスペクトから変更することはできません。ターゲットと、適用されるアスペクトがそれぞれ同じ型のプロバイダを提供する場合、OutputGroupInfo(ルールとアスペクトが異なる出力グループを指定している場合はマージされます)とInstrumentedFilesInfo(アスペクトから取得されます)を除き、エラーになります。つまり、アスペクト実装は DefaultInfoを返すことはできません。

パラメータと限定公開属性は、 ctx の属性で渡されます。この例では、extension パラメータを参照して、 カウントするファイルを決定します。

プロバイダを返す場合、アスペクトが伝播する属性の値(attr_aspects リストから)は、アスペクトを適用した結果に置き換えられます。たとえば、ターゲット X の deps に Y と Z がある場合、ctx.rule.attr.depsA(X)は [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 = '*'),
    },
)

ルール実装は、FileCountInfo を介して ctx.attr.deps にアクセスする方法を示しています。

ルール定義は、パラメータ(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 を介して再帰的にアクセスできるすべてのターゲットが評価されます。

参照