モジュール拡張機能を使用すると、依存関係グラフ全体のモジュールから入力データを読み取り、依存関係を解決するために必要なロジックを実行し、repo ルールを呼び出してリポジトリを作成することで、モジュール システムを拡張できます。これらの拡張機能は repo ルールと同様の機能を備えているため、ファイル I/O やネットワーク リクエストの送信などを行うことができます。これにより、Bazel モジュールから構築された依存関係グラフを尊重しながら、Bazel が他のパッケージ管理システムとやり取りできるようになります。
repo ルールと同様に、.bzl ファイルでモジュール拡張機能を定義できます。直接呼び出されるのではなく、各モジュールは、拡張機能が読み取るタグと呼ばれるデータの一部を指定します。Bazel は、拡張機能を評価する前にモジュール解決を実行します。拡張機能は、依存関係グラフ全体でその拡張機能に属するすべてのタグを読み取ります。
拡張機能の使用
拡張機能は Bazel モジュール自体でホストされます。モジュールで拡張機能を使用するには、まず、拡張機能をホストするモジュールにbazel_depを追加し、次にuse_extension組み込み関数を呼び出してスコープに含めます。次の例は、
MODULE.bazel ファイルで定義された「maven」拡張機能を使用する
rules_jvm_external
モジュールのスニペットです。
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//:org_junit_junit
などのラベルを適切に解決して、「maven」拡張機能によって生成されたリポジトリを指すようにします。
拡張機能の定義
モジュール拡張機能は、repo ルールと同様に、
module_extension 関数を使用して定義できます。ただし、
repo ルールには多数の属性がありますが、モジュール拡張機能には
tag_classesがあり、それぞれに多数の
属性があります。タグクラスは、この拡張機能で使用されるタグのスキーマを定義します。たとえば、上記の「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 タグを指定できることを示しています。
モジュール拡張機能の実装関数は、repo
ルールの実装関数と似ていますが、module_ctxオブジェクトを取得します。
これにより、拡張機能を使用するすべてのモジュールとすべての関連タグにアクセスできます。
実装関数は、repo
ルールを呼び出してリポジトリを生成します。
# @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という形式の正規名が付けられます。正規名形式は、依存すべき API ではなく、随時変更される可能性があることに注意してください。
この命名ポリシーでは、各拡張機能に独自の「リポジトリ名前空間」があります。2
つの異なる拡張機能で、競合のリスクなしに同じ名前のリポジトリを定義できます。また、repository_ctx.name
はリポジトリの正規名を報告します。これは、repo ルールの呼び出しで指定された名前と同じではありません。
モジュール拡張機能によって生成されたリポジトリを考慮すると、リポジトリの公開設定には次のルールがあります。
- Bazel モジュール リポジトリは、
MODULE.bazelファイル を介して、bazel_depとuse_repoに導入されたすべてのリポジトリを確認できます。 - モジュール拡張機能によって生成されたリポジトリは、拡張機能をホストするモジュールに表示されるすべてのリポジトリと、同じモジュール拡張機能によって生成された他のすべてのリポジトリ(repo ルールの呼び出しで指定された名前を実際の名前として使用)を確認できます。
- これにより、競合が発生する可能性があります。モジュール リポジトリが実際の名前
fooのリポジトリを確認でき、拡張機能が指定された名前fooのリポジトリを生成する場合、その拡張機能によって生成されたすべてのリポジトリでfooは前者を参照します。
- これにより、競合が発生する可能性があります。モジュール リポジトリが実際の名前
- 同様に、モジュール拡張機能の実装関数では、拡張機能によって作成されたリポジトリは、作成順序に関係なく、属性内の実際の名前で相互に参照できます。
- モジュールに表示されるリポジトリとの競合が発生した場合、リポジトリ ルール属性に渡されるラベル
を
Labelの呼び出しでラップして、同じ名前の拡張機能によって生成されたリポジトリではなく、モジュールに表示されるリポジトリを参照するようにします。
- モジュールに表示されるリポジトリとの競合が発生した場合、リポジトリ ルール属性に渡されるラベル
を
モジュール拡張機能のリポジトリのオーバーライドと挿入
ルート モジュールは、
override_repo と
inject_repo を使用して、
モジュール拡張機能のリポジトリをオーバーライドまたは挿入できます。
例: rules_java の java_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"],
)
ベスト プラクティス
このセクションでは、拡張機能を記述する際のベスト プラクティスについて説明します。これにより、拡張機能は使いやすく、保守しやすく、時間の経過に伴う変更に適切に対応できます。
各拡張機能を個別のファイルに配置する
拡張機能が異なるファイルにある場合、1 つの拡張機能で別の拡張機能によって生成されたリポジトリを読み込むことができます。この機能を使用しない場合でも、後で必要になる場合に備えて、個別のファイルに配置することをおすすめします。これは、拡張機能の ID がファイルに基づいているため、後で拡張機能を別のファイルに移動すると、公開 API が変更され、ユーザーにとって下位互換性のない変更になるためです。
再現性を指定する
拡張機能が、同じ入力(拡張機能タグ、読み取るファイルなど)に対して常に同じリポジトリを定義し、特にチェックサムで保護されていない
あらゆるダウンロードに依存しない場合は、
extension_metadata を
reproducible = Trueを指定して返すことを検討してください。これにより、Bazel
はロックファイルへの書き込み時にこの拡張機能をスキップできます。
オペレーティング システムとアーキテクチャを指定する
拡張機能がオペレーティング システムまたはそのアーキテクチャ タイプに依存している場合は、os_dependent と
arch_dependent ブール値属性を使用して、拡張機能の定義でこれを示すようにしてください。これにより、Bazel
は、いずれかに変更があった場合に再評価が必要であることを認識します。
ホストへのこのような依存関係により、この拡張機能のロックファイル エントリの維持が難しくなるため、可能であれば拡張機能を再現可能にすることをおすすめします。
リポジトリ名に直接影響を与えるのはルート モジュールのみにする
拡張機能がリポジトリを作成すると、そのリポジトリは拡張機能の名前空間内に作成されます。つまり、異なるモジュールが同じ拡張機能を使用し、同じ名前のリポジトリを作成すると、競合が発生する可能性があります。これは多くの場合、モジュール拡張機能の
tag_class に、リポジトリ ルールの name 値として渡される name 引数がある場合に発生します。
たとえば、ルート モジュール A がモジュール B に依存しているとします。どちらのモジュールも mylang
モジュールに依存しています。A と B の両方が
mylang.toolchain(name="foo")を呼び出すと、両方ともmylangモジュール内に
fooという名前のリポジトリを作成しようとし、エラーが発生します。
これを回避するには、リポジトリ名を直接設定する機能を削除するか、ルート モジュールのみが設定できるようにします。ルート モジュールには何も依存しないため、ルート モジュールでこの機能を許可しても問題ありません。別のモジュールが競合する名前を作成することを心配する必要はありません。