github 地址: https://github.com/golang/protobuf/tree/master/protoc-gen-go
它是 protoc 的一个插件,通过它, golang/protobuf 使 proto 定义文件,生成 golang 版本协议代码
protoc-gen-go 具有良好的代码结构,可以简单在 protoc-gen-go 代码基础上,新增(不需要改 protoc-gen-go 代码)自己的适配逻辑
从而达成以下目的:
│ main.go // main 函数,与 protoc 进程交互、 代码生成过程。写自己 protoc 插件时,通常会完全拷贝其内容
│
├─descriptor // 一个 proto 文件中的所有信息,通过 descriptor.proto 来定义
│ descriptor.pb.go
│ descriptor.proto
│
├─generator // proto 协议类生成过程。这个部分可以复用。generator 中定义了插件的方式,让你扩展自己的代码
│ │ generator.go
│ │ name_test.go
│ │
│ └─internal
│ | 工具类,略
│
├─grpc // grpc service 定义过程,grpc 相关定义、函数。也是如何编写自己逻辑的参考例子
│ grpc.go
│
├─plugin // 与 protoc 进程数据交互的数据格式定义
│ plugin.pb.go
│ plugin.pb.golden
│ plugin.proto
│
└─testdata
│ 测试文件,略
func main() {
// Begin by allocating a generator. The request and response structures are stored there
// so we can do error handling easily - the response structure contains the field to
// report failure.
g := generator.New()
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
g.Error(err, "reading input")
}
if err := proto.Unmarshal(data, g.Request); err != nil {
g.Error(err, "parsing input proto")
}
if len(g.Request.FileToGenerate) == 0 {
g.Fail("no files to generate")
}
g.CommandLineParameters(g.Request.GetParameter())
// Create a wrapped version of the Descriptors and EnumDescriptors that
// point to the file that defines them.
g.WrapTypes()
g.SetPackageNames()
g.BuildTypeNameMap()
g.GenerateAllFiles()
// Send back the results.
data, err = proto.Marshal(g.Response)
if err != nil {
g.Error(err, "failed to marshal output proto")
}
_, err = os.Stdout.Write(data)
if err != nil {
g.Error(err, "failed to write output proto")
}
}
通过 main.go 可以看出 protoc 与 其插件的工作过程:
hello.proto => protoc -> stdin -> protoc-gen-go --> CodeGeneratorRequest --> CodeGeneratorResponse -> stdout -> protoc => hello.pb.go
细节过程如下:
因此,你写自己的go插件时,main.go 的代码基本上是原封不动的拷贝到自己的 main 函数中即可,作为启动运行流程
descriptor/descriptor.proto 、 descriptor/descriptor.pb.go 表示一个 proto 文件
这是 protobuf 官方规定维护的: protoc 与其插件数据互通的协议之一
这里有个有趣的鸡生蛋、蛋生鸡
的现象:
追溯根源的话,可以推测,早期 protoc-gen-go 应该是手动解析 descriptor.proto 内容。有了最初版本的 protoc-gen-go ,后面代码重构时才引入 descriptor/descriptor.proto 、 descriptor/descriptor.pb.go (这里仅自己猜测,请勿当真)
generator/generator.go 主要负责生成 golang 版本 protobuf 消息定义代码,并通过插件类,实现一些第 3 方的扩展代码
该文件各种细节,通常都不需要关注
重要的是以下 2 个方面:
// A Plugin provides functionality to add to the output during Go code generation,
// such as to produce RPC stubs.
type Plugin interface {
// Name identifies the plugin.
Name() string
// Init is called once after data structures are built but before
// code generation begins.
Init(g *Generator)
// Generate produces the code generated by the plugin for this file,
// except for the imports, by calling the generator's methods P, In, and Out.
Generate(file *FileDescriptor)
// GenerateImports produces the import declarations for this file.
// It is called after Generate.
GenerateImports(file *FileDescriptor)
}
g *Generator
对象,然后可以通过该对象获取所需要的file *FileDescriptor
通过该参数也可以获得 proto 文件的所有内容// Generator is the type whose methods generate the output, stored in the associated response structure.
type Generator struct {
*bytes.Buffer
Request *plugin.CodeGeneratorRequest // The input.
Response *plugin.CodeGeneratorResponse // The output.
Param map[string]string // Command-line parameters.
PackageImportPath string // Go import path of the package we're generating code for
ImportPrefix string // String to prefix to imported package file names.
ImportMap map[string]string // Mapping from .proto file name to import path
Pkg map[string]string // The names under which we import support packages
outputImportPath GoImportPath // Package we're generating code for.
allFiles []*FileDescriptor // All files in the tree
allFilesByName map[string]*FileDescriptor // All files by filename.
genFiles []*FileDescriptor // Those files we will generate output for.
file *FileDescriptor // The file we are compiling now.
packageNames map[GoImportPath]GoPackageName // Imported package names in the current file.
usedPackages map[GoImportPath]bool // Packages used in current file.
usedPackageNames map[GoPackageName]bool // Package names used in the current file.
addedImports map[GoImportPath]bool // Additional imports to emit.
typeNameToObject map[string]Object // Key is a fully-qualified name in input syntax.
init []string // Lines to emit in the init function.
indent string
pathType pathType // How to generate output filenames.
writeOutput bool
annotateCode bool // whether to store annotations
annotations []*descriptor.GeneratedCodeInfo_Annotation // annotations to store
}
作者基本上每个字段都写好了注释,不再一一复述
一个 grpc 代码扩展,通常你写自己的 go 插件,就可以拷贝这个文件,挖空,然后开工
引入包时,自动注册自己:
func init() {
generator.RegisterPlugin(new(grpc))
}
输出生成的代码用:
// P forwards to g.gen.P.
func (g *micro) P(args ...interface{}) { g.gen.P(args...) }
其他的就是 Init 、 Generate 等实现,细节可以不关注
这是 protobuf 官方规定维护的: protoc 与其插件数据互通的协议之一
主要定义了:
除非你第一个写新语言插件,通常不需要关注
唯一需要注意的地方是命名,generator/generator.go 文件代码内也有 plugin !作者命名的不是很好
本人在写 fananchong/v-micro ,参考 micro/go-micro ,与它的插件 protoc-gen-micro
在把 go-micro 的同步 Call 改成异步 Call 中时,需要调整 protoc-gen-micro 生成代码
于是写了 protoc-gen-vmicro , 并简化 protoc-gen-micro 代码
具体代码示例可参见:
https://github.com/fananchong/v-micro/tree/master/tools/protoc-gen-vmicro
│ main.go // 基本完全拷贝 protoc-gen-go 的 main.go
│
├─examples // 生成例子,并调试用
│ └─greeter
│ g.bat
│ greeter.pb.go
│ greeter.proto
│ README.md
│
└─plugin
└─micro
micro.go // 自己代码扩展,实现上 import 引用 protoc-gen-go 的 generator.go 来达成本插件内容的调用
比如 greeter.proto
syntax = "proto3";
package mypackage;
service Greeter {
rpc Hello(Request) returns (Response) {}
}
message Request {
string name = 1;
}
message Response {
string msg = 1;
}
除了需要生成 go 版 protobuf 协议基本定义(protoc-gen-go 的 generator.go 的功能)
通过自己的 plugin/micro/micro.go ,达成输出类似以下代码:
// Client API for Greeter service
type GreeterService interface {
Hello(ctx context.Context, req *Request, opts ...client.CallOption) error
}
type GreeterCallback interface {
Hello(ctx context.Context, rsp *Response)
}
type greeterService struct {
c client.Client
name string
}
func NewGreeterService(name string, hdcb GreeterCallback, c client.Client) GreeterService {
if c == nil {
panic("client is nil")
}
if len(name) == 0 {
panic("name is nil")
}
if err := c.Handle(hdcb); err != nil {
panic(err)
}
return &greeterService{
c: c,
name: name,
}
}
func (c *greeterService) Hello(ctx context.Context, req *Request, opts ...client.CallOption) error {
r := c.c.NewRequest(c.name, "Greeter.Hello", req)
err := c.c.Call(ctx, r, opts...)
if err != nil {
return err
}
return nil
}
// Server API for Greeter service
type GreeterHandler interface {
Hello(ctx context.Context, req *Request, rsp *Response) error
}
func RegisterGreeterHandler(s server.Server, hdlr GreeterHandler) error {
return s.Handle(hdlr)
}
以上