记一次奇妙的 Golang Go语言中 go-protobuf 包升级之旅

发布于 1周前 作者 htzhanglong 来自 Go语言

记一次奇妙的 Golang Go语言中 go-protobuf 包升级之旅

今天聊一个最近升级 go 的protobuf的故事。过程很是奇妙(曲折)😳

前两天,一个项目的dependabot提示包github.com/golang/protobuf 可以从V1.3.5升级到V1.4.0

Round One

本以为直接升级就行,但是没过 CI,是发现旧版(V1.3.5)测试代码用了 pb 生成代码的XXX_Size()方法计算消息大小

在新版(v1.4.0)里panic

我们来看下他们有啥不同:

为简化,我们 proto 文件用官方的helloworld.proto

通过以下方式生成V1.3.5版本的 pb 文件

curl -O https://raw.githubusercontent.com/grpc/grpc-go/master/examples/helloworld/helloworld/helloworld.proto
brew install protobuf
GO111MODULE=on go get -u github.com/golang/protobuf/[email protected]
protoc  --go_out=plugins=grpc:. helloworld.proto

再替换github.com/golang/protobuf/[email protected],生成新版 pb 文件

查找XXX_Size函数

旧版中没问题

// helloword.pb.go
func (m *HelloRequest) XXX_Size() int {
  return xxx_messageInfo_HelloRequest.Size(m)
}
var xxx_messageInfo_HelloRequest proto.InternalMessageInfo

// github.com/golang/[email protected]/proto/table_marshal.go func (a *InternalMessageInfo) Size(msg Message) int {

新版 pb 文件中没有了InternalMessageInfo类型

但源码中能找到,是被废弃了。

// github.com/golang/[email protected]/proto/deprecated.go
// Deprecated: Do not use.
type InternalMessageInfo struct{}

func (*InternalMessageInfo) Size(Message) int { panic(“not implemented”) } // 同样废弃的还有:DiscardUnknown, Marshal, Merge, Unmarshal

那新版代码中怎么获取大小?

查看源码中例子,proto.Size(m Message) int可以实现

InternalMessageInfo也变成了接口type Message = protoiface.MessageV1

这个InternalMessageInfo的废弃在版本升级中也提到了,详见generated-code

好,替换方法,第一回合结束

准备收工

Round Two

等等,再仔细看了下github.com/golang/protobuf文档,里边提到v1.4.0版本后, 后边代码将交由google.golang.org/protobuf Repo 维护,看来谷歌要把 pb 的包都收到自己组织下

那我顺手直接把grpc生成工具也替换过去呗!

GO111MODULE=on go get -u google.golang.org/protobuf/cmd/[email protected]

再次生成代码

新工具自动提示要求 proto 文件中增加:

option go_package = ".;helloworld";

居然失败了。。。提示:

--go_out: protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC

难道是新增了 flag,尝试:

protoc  --go-grpc_out=. helloworld.proto

What? 失败+1 。。。

protoc-gen-go-grpc: program not found or is not executable
Please specify a program using absolute path or make sure the program is available in your PATH system variable
--go-grpc_out: protoc-gen-go-grpc: Plugin failed with status code 1.

心塞 x1

protoc-gen-go-grpc是新工具?

搜了下,确实有这个工具,说新版本会用他来生成grpc,主要是为了更好支持protobuf reflection

这里说一下:

proto 文件中的service是需要grpcplugin才能生成对应 pb 代码 所以旧版工具有参数--go_out=plugins=grpc:

尝试安装下:

GO111MODULE=on go get google.golang.org/protobuf/cmd/protoc-gen-go-grpc

失败+2 。。。

go get google.golang.org/protobuf/cmd/protoc-gen-go-grpc: module google.golang.org/protobuf[@upgrade](/user/upgrade) found (v1.21.0), but does not contain package google.golang.org/protobuf/cmd/protoc-gen-go-grpc

心塞 x2

goDoc 里有protoc-gen-go-grpcRepo里没有,这是什么操作?

猜测这问题应该有人遇到吧,果然搜到了 issue:plugins are not supported:grpc

原来是google.golang.org/protobuf先发布了,里边也包含了新版的protoc-gen-go

只是其不再支持grpc生成,需要另一个工具protoc-gen-go-grpc

然而它还没有发布,还在Review中。。。(当然目前也不会是稳定版本)

令人窒息的操作

不过,官方也说了,依然可以用旧包github.com/golang/protobufprotoc-gen-go-grpc工具生成grpc代码

因为旧包protoc-gen-go-grpc里依然可以用新包protoc-gen-go-grpc里生成 grpc 代码的gengogrpc, 见comment

Round Three

好吧,那就只升级代码中调用的 protobuf 为google.golang.org/[email protected],代码生成工具还用旧版里github.com/golang/protobuf/[email protected]

再次生成 pb 文件, 终于没有问题了, peace finally

peace

proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"

只是看着生成代码里的依然需要import的旧包github.com/golang/protobuf,总感觉哪里怪怪的

升级完了,却依赖了两种protobuf包。。。

最后,劝大家不着急就再等等再升级吧

(另外没事干升级到新包干什么!)

当然这次 protobuf 的 breaking change 还是很有意义的,不仅让将 protobuf 反射作为 pb 的一级功能,还提供了很多处理工具,详细看下: v1.21.0-release


文章首发公众号:newbmiao

推荐阅读:Dig101-Go 系列

欢迎关注,获取及时更新内容


更多关于记一次奇妙的 Golang Go语言中 go-protobuf 包升级之旅的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

不想升级依赖到最新版本的开发不是好开发🐶

更多关于记一次奇妙的 Golang Go语言中 go-protobuf 包升级之旅的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


瞎折腾🤣

没办法,项目没有历史包袱,要求时常更新。。。

在您的“奇妙的 Golang Go语言中 go-protobuf 包升级之旅”帖子中,我深感共鸣,因为升级 Protobuf 确实是一个充满挑战但又充满学习机会的过程。

在升级过程中,您可能会遇到一些不兼容的更改,比如您提到的 XXX_Size() 方法在新版本中引发 panic。这是因为在新版本中,InternalMessageInfo 类型被废弃,并引入了新的接口和类型来处理消息。这种 API 的更改需要我们在升级时特别注意,并相应地修改我们的代码。

此外,您还提到了 google.golang.org/protobuf 仓库的引入,这是 Google 对 Protobuf 在 Go 语言中的实现进行的一次重大重构。这个新仓库提供了更现代、更灵活的 API,但也意味着我们需要更新我们的工具和依赖。

在升级过程中,我建议仔细查阅 Protobuf 的更新日志和版本迁移指南,以了解潜在的兼容性问题。同时,确保使用与新版本 Protobuf 兼容的插件版本,并检查生成的代码是否与预期一致。

最后,虽然升级过程可能会遇到一些困难,但这也是一个学习和成长的机会。通过深入了解 Protobuf 的内部实现和 API 更改,我们可以更好地掌握这个强大的序列化工具,并为我们的项目提供更稳定、更高效的代码。

希望这些建议能对您的升级之旅有所帮助,也期待您在未来的 Golang 开发中取得更多的成果。如果您有任何其他问题或需要进一步的帮助,请随时联系我。

回到顶部