概要
Bazelでビルド時に
multiple copies of package passed to linker: @io_bazel_rules_go//proto/wkt:field_mask_go_proto @org_golang_google_genproto//protobuf/field_mask:field_mask
のようなエラーが出た時の対応です。
環境
- Bazel 5.0.0
- gazelle 0.25.0
- grpc-gateway 2.10.3
原因
protobufのWKT(Well Known Types)は複数のルールで提供されています。
- @org_golang_google_genproto//protobuf/xxx
- @com_github_golang_protobuf//xxx
- @io_bazel_rules_go//proto/wkt:xxx
複数のライブラリが同じproto(今回だとgoogle.golang.org/genproto/protobuf/field_mask
)に依存している状態で、けれど依存するルールが異なる場合にこの問題が発生します。
よく起きるケースとしては以下の両方を満たす場合です。
- あるライブラリは事前に生成された.pb.goに依存している
- 別のライブラリはBazelビルド時に.protoから生成される
デバッグ方法
何が依存しているのか確認する
bazel cquery
を使うと依存関係を調べることが可能です。今回だと
- @io_bazel_rules_go//proto/wkt:field_mask_go_proto
- @org_golang_google_genproto//protobuf/field_mask:field_mask
に依存しているライブラリがどれかを調べます。
@io_bazel_rules_go//proto/wkt:field_mask_go_proto
bazel cquery 'somepath(//cmd/gateway:gateway, @io_bazel_rules_go//proto/wkt:field_mask_go_proto )' --output=graph (21:41:05) INFO: Current date is 2022-06-22 (21:41:05) INFO: Analyzed 2 targets (0 packages loaded, 193 targets configured). (21:41:05) INFO: Found 2 targets... digraph mygraph { node [shape=box]; "//cmd/gateway:gateway (f03c3a9)" "//cmd/gateway:gateway (f03c3a9)" -> "//cmd/gateway:gateway_lib (f03c3a9)" "//cmd/gateway:gateway_lib (f03c3a9)" "//cmd/gateway:gateway_lib (f03c3a9)" -> "//interface/http/handler:handler (f03c3a9)" "//interface/http/handler:handler (f03c3a9)" "//interface/http/handler:handler (f03c3a9)" -> "@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:runtime (f03c3a9)" "@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:runtime (f03c3a9)" "@com_github_grpc_ecosystem_grpc_gateway_v2//runtime:runtime (f03c3a9)" -> "@io_bazel_rules_go//proto/wkt:field_mask_go_proto (f03c3a9)" "@io_bazel_rules_go//proto/wkt:field_mask_go_proto (f03c3a9)" } (21:41:06) INFO: Elapsed time: 0.356s (21:41:06) INFO: 0 processes. (21:41:06) INFO: Build completed successfully, 0 total actions
@com_github_grpc_ecosystem_grpc_gateway_v2//runtime
とあるので、依存しているライブラリはgithub.com/grpc-ecosystem/grpc-gateway/v2/runtime
であると分かります。
コード見ると"google.golang.org/genproto/protobuf/field_mask"
への依存が確認できます。
"google.golang.org/genproto/protobuf/field_mask" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect"
@org_golang_google_genproto//protobuf/field_mask:field_mask
$ bazel cquery 'somepath(//cmd/gateway:gateway, @org_golang_google_genproto//protobuf/field_mask:field_mask)' --output=graph (20:39:43) INFO: Current date is 2022-06-22 (20:39:43) INFO: Analyzed 2 targets (0 packages loaded, 596 targets configured). (20:39:43) INFO: Found 2 targets... digraph mygraph { node [shape=box]; "//cmd/gateway:gateway (f03c3a9)" "//cmd/gateway:gateway (f03c3a9)" -> "//cmd/gateway:gateway_lib (f03c3a9)" "//cmd/gateway:gateway_lib (f03c3a9)" "//cmd/gateway:gateway_lib (f03c3a9)" -> "//interface/pubsub:pubsub (f03c3a9)" "//interface/pubsub:pubsub (f03c3a9)" "//interface/pubsub:pubsub (f03c3a9)" -> "@com_google_cloud_go_pubsub//:pubsub (f03c3a9)" "@com_google_cloud_go_pubsub//:pubsub (f03c3a9)" "@com_google_cloud_go_pubsub//:pubsub (f03c3a9)" -> "@org_golang_google_genproto//protobuf/field_mask:field_mask (f03c3a9)" "@org_golang_google_genproto//protobuf/field_mask:field_mask (f03c3a9)" } (20:39:43) INFO: Elapsed time: 0.358s (20:39:43) INFO: 0 processes. (20:39:43) INFO: Build completed successfully, 0 total actions
@com_google_cloud_go_pubsub//:pubsub
とあるので、依存しているライブラリはcloud.google.com/go/pubsub
であると分かります。
コード見ると"google.golang.org/genproto/protobuf/field_mask"
への依存が確認できます。
pb "google.golang.org/genproto/googleapis/pubsub/v1" fmpb "google.golang.org/genproto/protobuf/field_mask" "google.golang.org/grpc"
対策
①-build_file_proto_mode=disable_globalをつける
先に述べた
- あるライブラリは事前に生成された.pb.goに依存している
- 別のライブラリはBazelビルド時に.protoから生成される
の前者に寄せる方法です。
以下のようにbuild_file_proto_mode = "disable_global"
をつけます。
go_repository( name = "com_google_cloud_go_pubsub", build_file_proto_mode = "disable_global", # here importpath = "cloud.google.com/go/pubsub", sum = "h1:e6A4rhtMX4opff/jDWApl4HwLtsCdV9VULVfpFRp6eo=", version = "v1.22.2", )
一律で付ける場合はルートのBUILD.bazel
に# gazelle:proto disable_global
を追記してください。
load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix github.com/jun06t/bazel-sample # gazelle:proto disable_global // here gazelle(name = "gazelle") gazelle( name = "gazelle-update-repos", args = [ "-from_file=go.mod", "-to_macro=deps.bzl%go_dependencies", "-prune", "-build_file_proto_mode=disable_global", ], command = "update-repos", )
②事前生成された.pb.goを除外してBazelビルドで生成する
先に述べた
- あるライブラリは事前に生成された.pb.goに依存している
- 別のライブラリはBazelビルド時に.protoから生成される
の後者に寄せる方法です。
ただしこれはGazelleのデフォルトの挙動なので、この問題にぶつかった時の解決にはならない可能性が高いです。
③patchをあてる
実は先の調査で出てきたgrpc-gatewayの場合は①、②だけではうまくいきません。
というのもgrpc-gateway自体もBazelを使っており、そちらの設定の影響で自分のBazelでビルドする際にgrpc-gatewayの.pb.go
が除外されてしまいます(disable_global
がないので②の挙動になってる?)。
解決策としてはgrpc-gateway側がdisable_global
を使うことですが、変更点が多すぎるため消極的なようです。
なので自前でdisable_global
を有効にしたpatchを当てるようにします。
以下のスクリプトでpatchを生成します。grpc-gatewayのバージョンは自分のBazel環境で使っているものを指定してください。
# Create a directory to prepare a patch mkdir /tmp/grpc-gateway-patch/ cd /tmp/grpc-gateway-patch/ # Shallow clone based on version git clone --depth 1 --branch v2.10.3 https://github.com/grpc-ecosystem/grpc-gateway.git cd ./grpc-gateway # Add Gazelle directive to disable proto compilation echo '# gazelle:proto disable_global' >> BUILD # Run Gazelle update-repos command to update repositories.bzl with # disable_global flag bazel run gazelle -- update-repos \ -from_file=go.mod \ -to_macro=repositories.bzl%go_repositories \ --build_file_proto_mode=disable_global # Remove BUILD.bazel file with conflicting import rm runtime/BUILD.bazel rm runtime/internal/examplepb/BUILD.bazel # Run Gazelle fix command to regenerate BUILD.bazel based on diasble_global bazel run gazelle -- fix # Create a patch file for two files that causes the build error: # - `repositories.bzl` # - `runtime/BUILD.bazel` # - `runtime/internal/examplepb/BUILD.bazel` git diff -u repositories.bzl runtime/BUILD.bazel runtime/internal/examplepb/BUILD.bazel > ../grpc-gateway.patch # Get back and clean up shallow clone cd ../ rm -rf ./grpc-gateway
次にpatchを入れるフォルダをリポジトリに用意します。
$ cd {リポジトリ} $ mkdir bazel-patches $ cp /tmp/grpc-gateway-patch/grpc-gateway.patch bazel-patches/ $ touch bazel-patches/BUILD.bazel
Bazelビルド時に参照されるよう、ディレクトリに空のBUILD.bazel
を配置します。
最後にgo_repository
にpatchを適用するように修正します。
go_repository( name = "com_github_grpc_ecosystem_grpc_gateway_v2", build_file_proto_mode = "disable_global", importpath = "github.com/grpc-ecosystem/grpc-gateway/v2", + patch_args = ["-p1"], + patches = ["//bazel-patches:grpc-gateway.patch"], sum = "h1:BGNSrTRW4rwfhJiFwvwF4XQ0Y72Jj9YEgxVrtovbD5o=", version = "v2.10.3", )
以上で完了です。
まとめ
BazelでWell Known Types周りはよくハマるのでまとめてみました。
参考
- rules_go:Avoiding conflicts
- 'multiple copies of package passed to linker' error when using `go_proto_library` · Issue #2818 · bazelbuild/rules_go · GitHub
- Building with Bazel not compatible when using Gazelle and disable_global · Issue #1847 · grpc-ecosystem/grpc-gateway · GitHub
- Conflicting resolutions for WKT dependencies · Issue #1646 · bazelbuild/rules_go · GitHub