モジュール拡張機能

問題を報告 ソースを表示 Nightly · 7.4 .

モジュール拡張機能を使用すると、依存関係グラフ全体のモジュールから入力データを読み取り、依存関係を解決するために必要なロジックを実行し、最後にリポジトリ ルールを呼び出してリポジトリを作成することで、モジュール システムを拡張できます。これらの拡張機能には、リポジトリ ルールに似た機能があり、ファイル 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_depuse_repo を使用して、MODULE.bazel ファイルで導入されたすべてのリポジトリを確認できます。
  • モジュール拡張機能によって生成されたリポジトリは、拡張機能をホストするモジュールに表示されるすべてのリポジトリと、同じモジュール拡張機能によって生成された他のすべてのリポジトリを参照できます(リポジトリ ルール呼び出しで指定された名前を表示名として使用します)。
    • 競合が発生する可能性があります。モジュール リポジトリが foo という見かけ上の名前のリポジトリを認識でき、拡張機能が指定された名前 foo のリポジトリを生成する場合、その拡張機能によって生成されたすべてのリポジトリで、foo は前者を参照します。
  • 同様に、モジュール拡張機能の実装関数では、拡張機能によって作成されたリポジトリは、作成順序に関係なく、属性の表示名で相互に参照できます。
    • モジュールに表示されるリポジトリと競合する場合は、拡張機能によって生成された同じ名前のリポジトリではなく、モジュールに表示されるリポジトリを参照できるように、リポジトリ ルールの属性に渡されるラベルを Label の呼び出しでラップできます。

モジュール拡張リポジトリのオーバーライドと挿入

ルート モジュールは、override_repoinject_repo を使用して、モジュール拡張リポジトリをオーバーライドまたは挿入できます。

例: rules_javajava_tools をベンダー提供のコピーに置き換える

# MODULE.bazel
local_repository = use_repo_rule("@bazel_tools//tools/build_defs/repo:local.bzl", "local_repository")
local_repository(
  name = "my_java_tools",
  path = "vendor/java_tools",
)

bazel_dep(name = "rules_java", version = "7.11.1")
java_toolchains = use_extension("@rules_java//java:extension.bzl", "toolchains")

override_repo(java_toolchains, remote_java_tools = "my_java_tools")

例: Go の依存関係にパッチを適用して、システム zlib ではなく @zlib に依存するようにする

# MODULE.bazel
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "zlib", version = "1.3.1.bcr.3")

go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_deps.module_override(
  patches = [
    "//patches:my_module_zlib.patch",
  ],
  path = "example.com/my_module",
)
use_repo(go_deps, ...)

inject_repo(go_deps, "zlib")
# patches/my_module_zlib.patch
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,6 +1,6 @@
 go_binary(
     name = "my_module",
     importpath = "example.com/my_module",
     srcs = ["my_module.go"],
-    copts = ["-lz"],
+    cdeps = ["@zlib"],
 )

ベスト プラクティス

このセクションでは、拡張機能を簡単に使用でき、メンテナンスが容易で、時間の経過とともに変化に適応できるようにする際のベスト プラクティスについて説明します。

各拡張機能を個別のファイルに配置する

拡張機能が別のファイルにある場合、ある拡張機能で別の拡張機能によって生成されたリポジトリを読み込むことができます。この機能を使用しない場合でも、後で必要になる場合に備えて、別々のファイルに保存することをおすすめします。これは、拡張機能の識別がファイルに基づいて行われるため、拡張機能を別のファイルに移動すると、後で公開 API が変更され、ユーザーにとっては下位互換性のない変更になるためです。

再現性を指定する

拡張機能が常に同じ入力(拡張タグ、読み取るファイルなど)に対して同じリポジトリを定義し、特にチェックサムで保護されないダウンロードに依存しない場合は、reproducible = True を使用して extension_metadata を返すことを検討してください。これにより、Bazel はロックファイルに書き込むときにこの拡張機能をスキップできます。

オペレーティング システムとアーキテクチャを指定する

拡張機能がオペレーティング システムまたはそのアーキテクチャ タイプに依存している場合は、os_dependent ブール値属性と arch_dependent ブール値属性を使用して、拡張機能の定義でこれを明記してください。これにより、どちらかに変更があった場合に、再評価の必要性を Bazel が認識できるようになります。

ホストへのこのような依存関係があると、この拡張機能のロックファイル エントリの維持が難しくなるため、可能であれば拡張機能を再現可能としてマークすることを検討してください。

リポジトリ名に直接影響するのはルート モジュールのみ

拡張機能がリポジトリを作成するときに、リポジトリは拡張機能の Namespace 内に作成されます。つまり、異なるモジュールが同じ拡張機能を使用し、同じ名前のリポジトリを作成すると、競合が発生する可能性があります。これは多くの場合、リポジトリ ルールの name 値として渡される name 引数を持つモジュール拡張機能の tag_class として現れます。

たとえば、ルート モジュール A がモジュール B に依存しているとします。どちらのモジュールもモジュール mylang に依存しています。AB の両方が mylang.toolchain(name="foo") を呼び出すと、どちらも mylang モジュール内に foo という名前のリポジトリを作成しようとするため、エラーが発生します。

これを回避するには、リポジトリ名を直接設定する機能を削除するか、ルート モジュールにのみ許可します。ルート モジュールにこの機能を許可しても問題ありません。何も依存しないため、別のモジュールが競合する名前を作成することを心配する必要はありません。