アスペクト

問題を報告 ソースを表示

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

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

  • 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 つの引数を取ることができます。

  • 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 は、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 を介して再帰的にアクセス可能なすべてのターゲットが評価されます。

参照