モジュール拡張機能を使用すると、ユーザーは依存関係グラフ全体のモジュールから入力データを読み取り、依存関係を解決するために必要なロジックを実行して、最後にリポジトリ ルールを呼び出してリポジトリを作成することで、モジュール システムを拡張できます。これらの拡張機能には、リポジトリ ルールに似た機能があり、ファイル I/O の実行、ネットワーク リクエストの送信などを行うことができます。特に、Bazel モジュールから構築された依存関係グラフを尊重しながら、Bazel が他のパッケージ管理システムと連携できるようにします。
リポジトリ ルールと同様に、.bzl
ファイルでモジュールの拡張機能を定義できます。直接呼び出されることはありません。各モジュールは、拡張機能が読み取るタグと呼ばれるデータを指定します。Bazel は、拡張機能を評価する前にモジュールの解決を実行します。この拡張機能は、依存関係グラフ全体で自身に属するすべてのタグを読み取ります。
拡張機能の使用状況
拡張機能は、Bazel モジュール自体でホストされます。モジュールで拡張機能を使用するには、まず拡張機能をホストするモジュールに bazel_dep
を追加してから、use_extension
組み込み関数を呼び出してスコープに含めます。次の例をご覧ください。rules_jvm_external
モジュールで定義された「maven」拡張機能を使用する MODULE.bazel
ファイルのスニペットです。
bazel_dep(name = "rules_jvm_external", version = "4.5")
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
これにより、use_extension
の戻り値が変数にバインドされ、ユーザーはドット構文を使用して拡張機能のタグを指定できるようになります。タグは、拡張機能の定義で指定された、対応するタグクラスによって定義されたスキーマに従う必要があります。以下に、maven.install
タグと maven.artifact
タグを指定する例を示します。
maven.install(artifacts = ["org.junit:junit:4.13.2"])
maven.artifact(group = "com.google.guava",
artifact = "guava",
version = "27.0-jre",
exclusions = ["com.google.j2objc:j2objc-annotations"])
use_repo
ディレクティブを使用して、拡張機能によって生成されたリポジトリを現在のモジュールのスコープにします。
use_repo(maven, "maven")
拡張機能によって生成されたリポジトリは、拡張機能の API の一部です。この例では、「maven」モジュール拡張機能によって maven
というリポジトリが生成されることが保証されています。上記の宣言により、拡張機能は「maven」拡張機能によって生成されたリポジトリを指すように @maven//:org_junit_junit
などのラベルを適切に解決します。
広告表示オプションの定義
モジュール拡張機能は、リポジトリ ルールと同様に、module_extension
関数を使用して定義できます。ただし、リポジトリ ルールには複数の属性がありますが、モジュール拡張機能には tag_class
があり、それぞれに複数の属性があります。タグクラスは、この拡張機能で使用されるタグのスキーマを定義します。たとえば、上記の「maven」拡張機能は次のように定義できます。
# @rules_jvm_external//:extensions.bzl
_install = tag_class(attrs = {"artifacts": attr.string_list(), ...})
_artifact = tag_class(attrs = {"group": attr.string(), "artifact": attr.string(), ...})
maven = module_extension(
implementation = _maven_impl,
tag_classes = {"install": _install, "artifact": _artifact},
)
これらの宣言は、指定された属性スキーマを使用して maven.install
タグと maven.artifact
タグを指定できることを示しています。
モジュール拡張機能の実装関数は、リポジトリ ルールに似ていますが、module_ctx
オブジェクトを取得する点が異なります。このオブジェクトは、拡張機能とすべての関連タグを使用してすべてのモジュールへのアクセスを許可します。次に、実装関数はリポジトリ ルールを呼び出してリポジトリを生成します。
# @rules_jvm_external//:extensions.bzl
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") # a repo rule
def _maven_impl(ctx):
# This is a fake implementation for demonstration purposes only
# collect artifacts from across the dependency graph
artifacts = []
for mod in ctx.modules:
for install in mod.tags.install:
artifacts += install.artifacts
artifacts += [_to_artifact(artifact) for artifact in mod.tags.artifact]
# call out to the coursier CLI tool to resolve dependencies
output = ctx.execute(["coursier", "resolve", artifacts])
repo_attrs = _process_coursier_output(output)
# call repo rules to generate repos
for attrs in repo_attrs:
http_file(**attrs)
_generate_hub_repo(name = "maven", repo_attrs)
拡張機能の ID
モジュールの拡張機能は、名前と、use_extension
の呼び出しに含まれる .bzl
ファイルによって識別されます。次の例では、拡張機能 maven
は .bzl
ファイル @rules_jvm_external//:extension.bzl
と名前 maven
で識別されます。
maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven")
拡張機能を別の .bzl
ファイルから再エクスポートすると、新しい ID が得られます。推移的モジュール グラフで両方の拡張機能が使用されている場合、これらは個別に評価され、その特定の ID に関連付けられたタグのみが表示されます。
拡張機能の作成者は、ユーザーが 1 つの .bzl
ファイルからのみモジュール拡張機能を使用するようにする必要があります。
リポジトリ名と公開設定
拡張機能によって生成されたリポジトリには、module_repo_canonical_name~extension_name~repo_name
形式の正規名が付けられます。ルート モジュールでホストされている拡張機能の場合、module_repo_canonical_name
の部分は文字列 _main
に置き換えられます。正規名の形式は、API に依存すべきものではありません。いつでも変更される可能性があります。
この命名ポリシーにより、各拡張機能には独自の「リポ名前空間」があります。2 つの異なる拡張機能が、競合するリスクを負うことなく、同じ名前のリポジトリをそれぞれ定義できます。また、repository_ctx.name
がリポジトリの正規名を報告することも意味します。これはリポジトリ ルール呼び出しで指定された名前と同じではありません。
モジュール拡張機能によって生成されたリポジトリを考慮すると、リポジトリの公開設定ルールがいくつかあります。
- Bazel モジュール リポジトリでは、
bazel_dep
とuse_repo
でMODULE.bazel
ファイルに導入されたすべてのリポジトリを確認できます。 - モジュール拡張機能によって生成されたリポジトリは、拡張機能をホストするモジュールに表示されるすべてのリポジトリに加えて、同じモジュール拡張機能によって生成された他のすべてのリポジトリを表示できます(リポジトリルールの呼び出しで指定された名前が名前になります)。
- 競合が発生する可能性があります。モジュール リポジトリが
foo
という名前のリポジトリを参照でき、拡張機能が指定された名前のリポジトリを生成する場合、foo
は、その拡張機能によって生成されたすべてのリポジトリのfoo
を参照します。
- 競合が発生する可能性があります。モジュール リポジトリが
ベスト プラクティス
このセクションでは、使いやすくメンテナンスしやすく、時間の経過に伴う変更にうまく適応できるように、拡張機能を記述する際のベスト プラクティスについて説明します。
各拡張機能を個別のファイルに配置する
拡張機能が別のファイルにある場合、ある拡張機能で別の拡張機能によって生成されたリポジトリを読み込むことができます。この機能を使用しない場合でも、後で必要になる場合に備えて、別々のファイルに保存することをおすすめします。これは、拡張機能の識別がファイルに基づいて行われるため、拡張機能を別のファイルに移動すると、後で公開 API が変更され、ユーザーにとっては下位互換性のない変更になるためです。
オペレーティング システムとアーキテクチャを指定する
拡張機能がオペレーティング システムまたはそのアーキテクチャ タイプに依存している場合は、拡張機能の定義で os_dependent
と arch_dependent
のブール値属性を使用して、そのことを指定します。これにより、いずれかに変更があった場合に Bazel は再評価の必要性を認識できます。
リポジトリ名に直接影響するのはルート モジュールのみ
拡張機能でリポジトリが作成されると、拡張機能の Namespace 内に作成されます。つまり、異なるモジュールが同じ拡張機能を使用し、同じ名前のリポジトリが作成された場合に競合が発生する可能性があります。これは多くの場合、リポジトリ ルールの name
値として渡される name
引数を持つモジュール拡張機能の tag_class
として現れます。
たとえば、ルート モジュール A
がモジュール B
に依存しているとします。どちらのモジュールもモジュール mylang
に依存します。A
と B
の両方が mylang.toolchain(name="foo")
を呼び出すと、どちらも mylang
モジュール内に foo
という名前のリポジトリを作成しようとし、エラーが発生します。
これを回避するには、リポジトリ名を直接設定する機能を削除するか、ルート モジュールにのみ許可します。ルート モジュールにこの機能を許可しても問題ありません。何も依存しないため、別のモジュールが競合する名前を作成することを心配する必要はありません。