概要
前回に引き続き今回もBazelについて書きます。
今回はProtocol BuffersをビルドしてGoで利用できるコードを生成します。
環境
- Bazel 4.2.2
設定
protoファイル
以下のような依存関係を持つprotoファイルを用意します。
proto/ ├── address.proto ├── user.proto └── zipcode.proto
zipcode.proto
syntax = "proto3"; option go_package = "github.com/jun06t/bazel-sample/protobuf/proto;user"; package user; message ZipCode { string code = 1; }
address.proto
syntax = "proto3"; option go_package = "github.com/jun06t/bazel-sample/protobuf/proto;user"; package user; import "proto/zipcode.proto"; message Address { string city = 1; ZipCode zip_code = 2; }
user.proto
syntax = "proto3"; option go_package = "github.com/jun06t/bazel-sample/protobuf/proto;user"; package user; import "proto/address.proto"; import "google/protobuf/any.proto"; message User { string id = 1; string name = 2; Address address = 3; google.protobuf.Any tags = 4; }
今回Well Known Typesであるgoogle/protobuf/any.proto
にも依存しているとします。
Bazelの設定
WORKSPACE
とBUILD.bazel
を用意します。
. ├── BUILD.bazel ├── Makefile ├── WORKSPACE └── proto ├── address.proto ├── user.proto └── zipcode.proto
WORKSPACE
前回のWORKSPACE
に下記を追加しただけのものを用意します。
http_archive( name = "com_google_protobuf", sha256 = "d0f5f605d0d656007ce6c8b5a82df3037e1d8fe8b121ed42e536f569dec16113", strip_prefix = "protobuf-3.14.0", urls = [ "https://mirror.bazel.build/github.com/protocolbuffers/protobuf/archive/v3.14.0.tar.gz", "https://github.com/protocolbuffers/protobuf/archive/v3.14.0.tar.gz", ], ) load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") protobuf_deps()
ルートのBUILD.bazel
BUILD.bazel
はprefix以外は変わりません。
load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix github.com/jun06t/bazel-sample/protobuf gazelle(name = "gazelle")
この状態で一度
$ bazel run //:gazelle
を実行して、protoディレクトリにもBUILD.bazel
が配置されるようにします。
すると以下のBUILD.bazel
が生成されます。
load("@rules_proto//proto:defs.bzl", "proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library") load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") proto_library( name = "user_proto", srcs = [ "address.proto", "user.proto", "zipcode.proto", ], visibility = ["//visibility:public"], deps = ["@com_google_protobuf//:any_proto"], ) go_proto_library( name = "user_go_proto", importpath = "github.com/jun06t/bazel-sample/protobuf/proto", proto = ":user_proto", visibility = ["//visibility:public"], ) go_library( name = "proto", embed = [":user_go_proto"], importpath = "github.com/jun06t/bazel-sample/protobuf/proto", visibility = ["//visibility:public"], )
user.protoにあったgoogle/protobuf/any.proto
への依存については@com_google_protobuf//:any_proto
で解決していることが分かります。
ビルド
ではprotobufのビルドをしてみます。
$ bazel build //proto
ローカルとの差分
ビルドした際のprotocやプラグインのバージョンの差異がありますが、コードの部分はほぼ同じであることが分かります。
$ diff bazel-bin/proto/user_go_proto_/github.com/jun06t/bazel-sample/protobuf/proto/user.pb.go dist/user.pb.go 3,4c3,4 < // protoc-gen-go v1.27.1 < // protoc v3.14.0 --- > // protoc-gen-go v1.25.0 > // protoc v3.17.3 9a10 > proto "github.com/golang/protobuf/proto" 23a25,28 > // This is a compile-time assertion that a sufficiently up-to-date version > // of the legacy proto package is being used. > const _ = proto.ProtoPackageIsVersion4 >
GoGo Protobufを使う場合はどうするか
次にgogo protobufの場合どうするかです。
先程のuser.protoがgogo.proto
に依存しているとします。
syntax = "proto3"; option go_package = "github.com/jun06t/bazel-sample/gogoproto/proto;user"; package user; import "proto/address.proto"; import "google/protobuf/any.proto"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; message User { string id = 1 [(gogoproto.customname) = "ID"]; string name = 2; Address address = 3; google.protobuf.Any tags = 4; }
Bazelの設定
WORKSPACE
go_repositoryにgogoprotoを追記します。
go_repository( name = "com_github_gogo_protobuf", importpath = "github.com/gogo/protobuf", sum = "h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=", version = "v1.3.2", build_file_proto_mode = "disable", )
詳細は後述しますがbuild_file_proto_mode = "disable",
を付けているのがポイントです。
ルートのBUILD.bazel
3つほどDirectivesを追加します。
load("@bazel_gazelle//:def.bzl", "gazelle") # gazelle:prefix github.com/jun06t/bazel-sample/gogoproto # gazelle:resolve proto github.com/gogo/protobuf/gogoproto/gogo.proto @gogo_special_proto//github.com/gogo/protobuf/gogoproto # gazelle:resolve proto go github.com/gogo/protobuf/gogoproto/gogo.proto @com_github_gogo_protobuf//gogoproto:go_default_library # gazelle:go_proto_compilers @io_bazel_rules_go//proto:gogo_proto gazelle(name = "gazelle")
解説
1. gazelle:resolve ... @gogo_special_proto//github.com/gogo/protobuf/gogoproto
ない場合
proto_libraryの依存解決がうまく行かない下記のようなBUILD.bazel
になります。
proto_library( name = "user_proto", srcs = [ "address.proto", "user.proto", "zipcode.proto", ], visibility = ["//visibility:public"], deps = [ "//github.com/gogo/protobuf/gogoproto:gogoproto_proto", // ココ "@com_google_protobuf//:any_proto", ], )
しかし当然ローカルに該当のパッケージはないので依存が解決できません。
ある場合
proto_library( name = "user_proto", srcs = [ "address.proto", "user.proto", "zipcode.proto", ], visibility = ["//visibility:public"], deps = [ "@com_google_protobuf//:any_proto", "@gogo_special_proto//github.com/gogo/protobuf/gogoproto", ], )
というBUILD.bazel
になり、依存が解決できます。
gogo_special_proto
はrules_goで定義されてるのでWORKSPACEに追記する必要はありません。
2. gazelle:resolve ... @com_github_gogo_protobuf//gogoproto:go_default_library
ない場合
以下のようになります。
go_proto_library( name = "user_go_proto", importpath = "github.com/jun06t/bazel-sample/gogoproto/proto", proto = ":user_proto", visibility = ["//visibility:public"], deps = [ "@gogo_special_proto//github.com/gogo/protobuf/gogoproto", ], )
ビルドしようとすると
in deps attribute of go_proto_library rule //proto:user_go_proto: '@gogo_special_proto//github.com/gogo/protobuf/gogoproto:gogoproto' does not have mandatory providers: 'GoLibrary'
というエラーが出ます。
ある場合
以下のようになります。
go_proto_library( name = "user_go_proto", importpath = "github.com/jun06t/bazel-sample/gogoproto/proto", proto = ":user_proto", visibility = ["//visibility:public"], deps = [ "@com_github_gogo_protobuf//gogoproto:go_default_library", // ココ ], )
これは別途必要なので前述したようにWORKSPACE
のgo_repositoryに追記が必要です。
3. gazelle:go_proto_compilers @io_bazel_rules_go//proto:gogo_proto
ない場合
go_proto_libraryのコンパイラがgogoprotoのものにならず、ビルド自体は成功するものの生成されたコードはgogoproto.customname
などが反映されてない通常のprotobufのコードになってしまいます。
go_proto_library( name = "user_go_proto", importpath = "github.com/jun06t/bazel-sample/gogoproto/proto", proto = ":user_proto", visibility = ["//visibility:public"], deps = [ "@com_github_gogo_protobuf//gogoproto:go_default_library", ], )
ある場合
以下のようにコンパイラが指定されます。
go_proto_library( name = "user_go_proto", compilers = ["@io_bazel_rules_go//proto:gogo_proto"], // ココ importpath = "github.com/jun06t/bazel-sample/gogoproto/proto", proto = ":user_proto", visibility = ["//visibility:public"], deps = [ "@com_github_gogo_protobuf//gogoproto:go_default_library", ], )
ビルドすると
Well Known Typesの部分がgogoprotoのものにreplaceされている点以外はローカルのビルドと一致しました。
$ diff bazel-bin/proto/user_go_proto_/github.com/jun06t/bazel-sample/gogoproto/proto/user.pb.go proto/user.pb.go 10c10 < types "github.com/gogo/protobuf/types" --- > anypb "google.golang.org/protobuf/types/known/anypb" 29c29 < Tags *types.Any `protobuf:"bytes,4,opt,name=tags,proto3" json:"tags,omitempty"` --- > Tags *anypb.Any `protobuf:"bytes,4,opt,name=tags,proto3" json:"tags,omitempty"` 80c80 < func (m *User) GetTags() *types.Any { --- > func (m *User) GetTags() *anypb.Any {
その他
試行錯誤していた頃に遭遇したエラーと対処法です。
no such package '@org_golang_google_protobuf//types/known/fieldmaskpb'
ERROR: ... no such package '@org_golang_google_protobuf//types/known/fieldmaskpb': BUILD file not found in directory 'types/known/fieldmaskpb' of external repository @org_golang_google_protobuf. Add a BUILD file to a directory to mark it as a package. and referenced by '@io_bazel_rules_go//proto:go_proto'
や
ERROR: ... no such package '@org_golang_google_protobuf//types/known/sourcecontextpb': BUILD file not found in directory 'types/known/sourcecontextpb' of external repository @org_golang_google_protobuf. Add a BUILD file to a directory to mark it as a package. and referenced by '@io_bazel_rules_go//proto:go_proto'
といったエラーです。
go_repositoryにorg_golang_google_protobuf
があり、かつバージョンが古いと起きます。上記ディレクトリは新しいバージョンにしか存在しないためです。
対応
update-repos
でバージョンを上げればOKです。
$ bazel run //:gazelle -- update-repos google.golang.org/protobuf
cannot use E_Embed (type *impl.ExtensionInfo)...
ERROR: /private/var/tmp/_bazel_a13156/4844ac1f9ad7427341a4ebab3cf95b0f/external/com_github_gogo_protobuf/gogoproto/BUILD.bazel:19:11: GoCompilePkg external/com_github_gogo_protobuf/gogoproto/gogoproto.a failed: (Exit 1): builder failed: error executing command bazel-out/host/bin/external/go_sdk/builder compilepkg -sdk external/go_sdk -installsuffix darwin_amd64 -src ... (remaining 57 argument(s) skipped) Use --sandbox_debug to see verbose messages from the sandbox external/com_github_gogo_protobuf/gogoproto/helper.go:35:37: cannot use E_Embed (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:39:37: cannot use E_Nullable (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:43:37: cannot use E_Stdtime (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:47:37: cannot use E_Stdduration (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:51:37: cannot use E_Wktpointer (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:55:37: cannot use E_Wktpointer (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:59:37: cannot use E_Wktpointer (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:63:37: cannot use E_Wktpointer (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:67:37: cannot use E_Wktpointer (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:71:37: cannot use E_Wktpointer (type *impl.ExtensionInfo) as type *"github.com/gogo/protobuf/proto".ExtensionDesc in argument to "github.com/gogo/protobuf/proto".GetBoolExtension external/com_github_gogo_protobuf/gogoproto/helper.go:71:37: too many errors compilepkg: error running subcommand external/go_sdk/pkg/tool/darwin_amd64/compile: exit status 2
対応
build_file_proto_mode = "disable",
をWORKSPACEのgo_repositoryに追記します。
go_repository( name="com_github_gogo_protobuf", commit="1adfc126b41513cc696b209667c8656ea7aac67c", importpath="github.com/gogo/protobuf", build_file_proto_mode = "disable", // ココ )
外部ライブラリが.proto
や.pb.go
を持つ場合、デフォルトだとGazelleはそれらもビルド対象に含めてしまうためそれを避けるために設定するオプションです。
OpenCensusエラー
ERROR: .../external/com_github_census_instrumentation_opencensus_proto/gen-go/metrics/v1/BUILD.bazel:3:11: GoCompilePkg external/com_github_census_instrumentation_opencensus_proto/gen-go/metrics/v1/metrics.a failed: (Exit 1): builder failed: error executing command bazel-out/host/bin/external/go_sdk/builder compilepkg -sdk external/go_sdk -installsuffix darwin_amd64 -src external/com_github_census_instrumentation_opencensus_proto/gen-go/metrics/v1/metrics.pb.go ... (remaining 24 argument(s) skipped)
対応
build_extra_args = ["-exclude=src"]
をWORKSPACEのgo_repositoryに追記します。
go_repository( name = "com_github_census_instrumentation_opencensus_proto", importpath = "github.com/census-instrumentation/opencensus-proto", sum = "h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=", version = "v0.2.1", build_extra_args = ["-exclude=src"], # keep )
まとめ
proto周りはとにかく色んなエラーに遭遇し、都度ググって出てきたIssueを元に対応していました。
dockerで環境を固定すればprotoの生成物の再現性は保てるので、無理にBazelでやり込む必要はないかもと感じるほどに大変でした。
サンプルコード
今回は通常のprotoとgogo protoの2種類用意しています。