Carpe Diem

備忘録

Bazel で multiple copies of package passed to linker の対応方法

概要

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)は複数のルールで提供されています。

複数のライブラリが同じ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"

ref: grpc-gateway/fieldmask.go at 0ab885c70c7826116ef1230a2ea5d0e950c5e540 · grpc-ecosystem/grpc-gateway · GitHub

@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"

ref: https://github.com/googleapis/google-cloud-go/blob/675b333f659a3f8c5eac8a132092218f56e5752b/pubsub/topic.go#L37

対策

①-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周りはよくハマるのでまとめてみました。

参考