Bazelで推移的依存と戦う
自分のリポジトリは基本的にビルドツールに Bazel を導入しているのですが同様に Bazel を使用しているライブラリ等に依存する場合にビルドが難しくなる場合があります。
この記事ではそのような場合に小さなパッチで解決する Tips を紹介します。
依存関係の解決で困るケース
具体的に推移的依存で困ったのは以下のようなケースです。

Repo A がメインのリポジトリです。 Repo B は自分が作っているライブラリで、どちらのリポジトリもビルドツールは Bazel です。
Repo B は Go module だけではなく Bazel module も提供していて Repo A からはそのどちらも利用しています。
この時 Repo B が更に依存しているライブラリのリポジトリも Bazel でビルドされていました。
依存ライブラリの中に Bazel のビルドファイルが入っていると自リポジトリのビルドファイルとしばしば競合しうまくビルドできないので依存ライブラリのビルドファイルは消してしまうことが多いです。
Repo B:MODULE.bazel
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
go_deps.gazelle_default_attributes(build_file_generation = "clean")
Repo B で Bazel を使ってテストを実行するには上の設定が必要ですが、これが入っていると他のリポジトリが Repo B を Bazel module として追加することができません。
gazelle_default_attributes 他いくつかのオーバーライド系はルートモジュールのときのみ使えるためです。
パッチで解決する
問題の根本は pingcap/tidb/pkg/parser のアーカイブファイルにビルドファイルが含まれていることなのでこれを削除して Repo B で vendor してしまうのが手っ取り早い解決方法です。
しかし自分のリポジトリ(Repo A, Repo B)は Bzlmod に移行し vendor をしない方向にしようとしているのでこれ以外の解決方法が必要でした。
そこで Repo A で Repo B にパッチを当てる方法にしました。
Repo B にはビルド・テストするために pingcap/tidb/pkg/parser のビルドファイルを削除する設定を入れておきます。
Repo B:MODULE.bazel
go_deps.gazelle_override(
build_file_generation = "clean",
path = "github.com/pingcap/tidb/pkg/parser",
)
この設定さえなければ Repo A から Bazel module として依存できるのでこれを削除するパッチを Repo A に追加します。
Repo A:patch/p1.patch
diff --git a/MODULE.bazel b/MODULE.bazel
index d75ba81eb6..6b1e5c14ee 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -17,11 +17,6 @@
go_deps.from_file(go_mod = "//:go.mod")
-go_deps.gazelle_override(
- build_file_generation = "clean",
- path = "github.com/pingcap/tidb/pkg/parser",
-)
-
use_repo(
go_deps,
"com_github_deckarep_golang_set_v2",
このパッチを依存取得時に適用させます。
Repo A:MODULE.bazel
git_override(
module_name = "repo_b",
commit = "35d68bd327e5039c940d47e1136d1bed94c53efd",
patch_strip = 1,
patches = ["//patch:p1.patch"],
remote = "https://github.com/f110/repo_b",
)
(repo_b は Go module としても依存していて go.mod ファイルで依存が定義されるので git_override で上書きしています)
まとめ
依存先リポジトリのビルドファイルにより自リポジトリのビルドがうまく動かない場合の対処方法について紹介しました。
Go module として依存した場合関係ないファイルがアーカイブファイルに含まれないようになっていればこのような問題は起きないのですが残念ながら Go module proxy はそのようになっていません。
また Checksum Database があるのでこの挙動が将来的に変更されるという可能性は低いと思います。
実際にビルドファイルを含めない Go module proxy を実装してみましたが、チェックサムの検証を行わないように GONOSUMDB を使うか、そもそも sum.golang.org ではなく自前の Checksum Database を提供する必要がありやりたいことに対して大げさな実装が必要な上、セキュリティ上の懸念も生まれてしまいます。
パッチファイルをメンテしていく必要があるというコストが生じてしまいますが今のところこれが最適解なのかなと考えています。
Discussion