前面幾篇文章講述了protoc 的原理,實(shí)現(xiàn),及參數(shù),這里終于要開始動(dòng)手了。
準(zhǔn)備工作
- 安裝go 1.14
- 在一個(gè)新的目錄下, 建立一個(gè)go項(xiàng)目
go mod init github.com/kekek/protoc-gen-foo
go get google.golang.org/protobuf@v1.21.0
??: 這里文件夾的名字及其重要,必須以protoc-gen- 開頭, 比如proto-gen-foo
plan
在此示例中,我們想擴(kuò)展Protobuf編譯器以添加方法Foo(),該方法為.proto文件中定義的每條消息返回bar。
建立如下test.proto 文件
syntax = "proto3";
package test;
option go_package = ".;test";
message Message {
string data = 1;
}
調(diào)用編譯器插件
首先,我們將編寫一個(gè)無用的插件,該插件將寫入stdout。將以下內(nèi)容添加到main.go中:
package main
import (
"log"
)
func main() {
log.Println("hello protoc")
return
}
運(yùn)行如下代碼,調(diào)用插件:
# 建立文件夾out,輸出生成內(nèi)容
mkdir out
# 安裝插件
go install .
# 運(yùn)行 protoc,傳入 --foo_out 參數(shù)
protoc \
--proto_path . \
-I=. \
test.proto \
--foo_out=./out \
--go_out=./out
?? protoc 通過 --foo_out 搜索插件 可執(zhí)行文件 protoc-gen-foo, 也可使用參數(shù) protoc --plugin=protoc-gen-foo=/path/to/protoc-gen-foo 指定插件位置
如果上述命令成功執(zhí)行,則應(yīng)觀察到hello protoc已打印到控制臺(tái),以及生成的protobuf文件out / test.pb.go。
生成代碼
用以下代碼替換我們的準(zhǔn)備系統(tǒng)插件,請注意注釋中的解釋:
package main
import (
"bytes"
"fmt"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/pluginpb"
"io/ioutil"
"os"
)
func main() {
// Protoc 將protobuf文件編譯為 pluginpb.CodeGeneratorRequest結(jié)構(gòu),并輸出到stdin中
input, _ := ioutil.ReadAll(os.Stdin)
var req pluginpb.CodeGeneratorRequest
proto.Unmarshal(input, &req)
// 使用默認(rèn)選項(xiàng)初始化我們的插件
opts := protogen.Options{}
plugin, err := opts.New(&req)
if err != nil {
panic(err)
}
// protoc 將一組文件結(jié)構(gòu)傳遞給程序處理
for _, file := range plugin.Files {
// 是時(shí)候生成代碼了……!
// 1. 初始化緩沖區(qū)以保存生成的代碼
var buf bytes.Buffer
// 2. 生成包名稱
pkg := fmt.Sprintf("package %s", file.GoPackageName)
buf.Write([]byte(pkg))
// 3. 為每個(gè)message生成 Foo() 方法
for _, msg := range file.Proto.MessageType {
buf.Write([]byte(fmt.Sprintf(`
func (x %s) Foo() string {
return "bar"
}`, *msg.Name)))
}
// 4. 指定輸出文件名,在這種情況下為test.foo.go
filename := file.GeneratedFilenamePrefix + ".foo.go"
file := plugin.NewGeneratedFile(filename, ".")
// 5. 將設(shè)概念車呢個(gè)的代碼,從緩沖區(qū)寫入到文件
file.Write(buf.Bytes())
}
// 從我們的插件生成響應(yīng),并將其編組為protobuf
stdout := plugin.Response()
out, err := proto.Marshal(stdout)
if err != nil {
panic(err)
}
// 相應(yīng)輸出到stdout, 它將被 protoc 接收
fmt.Fprintf(os.Stdout, string(out))
}
重新運(yùn)行上一部分中的命令,并觀察新生成的文件out/test.foo.go
package test
func (x Message) Foo() string {
return "bar"
}
一個(gè)簡單的protocbuf 完成了