protoc go插件編寫之四 (實(shí)現(xiàn)生成自己的proto文件)

前面幾篇文章講述了protoc 的原理,實(shí)現(xiàn),及參數(shù),這里終于要開始動(dòng)手了。

準(zhǔn)備工作

    1. 安裝go 1.14
    1. 在一個(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 完成了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容