記一次奇妙的 go-protobuf 包升級之旅

newbmiao發表於2020-04-18

檢視更多: 歷史集錄


今天聊一個最近升級 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/protoc-gen-go@v1.3.5
protoc  --go_out=plugins=grpc:. helloworld.proto

再替換github.com/golang/protobuf/protoc-gen-go@v1.4.0,生成新版 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/protobuf@v1.3.5/proto/table_marshal.go
func (a *InternalMessageInfo) Size(msg Message) int {

新版 pb 檔案中沒有了InternalMessageInfo型別

但原始碼中能找到,是被廢棄了。

// github.com/golang/protobuf@v1.4.0/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/protoc-gen-go@v1.21.0

再次生成程式碼

新工具自動提示要求 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 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/protobuf@v1.21.0,程式碼生成工具還用舊版裡github.com/golang/protobuf/protoc-gen-go@v1.4.0

再次生成 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 系列

歡迎關注,獲取及時更新內容

更多原創文章乾貨分享,請關注公眾號
  • 記一次奇妙的 go-protobuf 包升級之旅
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章