难点:
插件是属于被protoc调用的工具, 所以无法单独启动,很难调试
protoc插件运行需要一些输入,这些输入是由protoc传递过来放入os.Stdin中
插件的输入是到stdout而不是由我们自己确定的,所以很难输出文件查看生成效果
注意:
下面属于魔改源码使得为了方便调试,所以本着尽量少改源码的方式去修改
修改源码 protobuf/compiler/protogen/protogen.go
新增函数
func fileDescriptorProto(files …string) (*descriptorpb.FileDescriptorProto, error) {
var p protoparse.Parser
fd, err := p.ParseFiles(files...)
if err != nil {
return nil, err
}
return fd[0].AsFileDescriptorProto(), nil
}
b. 修改func run(opts Options, f func(*Plugin) error) 函数
import "github.com/jhump/protoreflect/desc/protoparse"
func runV2(opts Options, f func(*Plugin) error) error {
if len(os.Args) > 1 {
return fmt.Errorf("unknown argument %q (this program should be run by protoc, not directly)", os.Args[1])
}
fileName := "E:/xxx/api.proto"
fdpb, err := fileDescriptorProto(fileName)
if err != nil {
panic(err)
}
req1 := []*descriptorpb.FileDescriptorProto{fdpb}
req := &pluginpb.CodeGeneratorRequest{
ProtoFile: req1,
FileToGenerate: []string{fileName},
}
gen, err := opts.New(req)
if err != nil {
return err
}
New函数中增加一行
func (opts Options) New(req pluginpb.CodeGeneratorRequest) (Plugin, error) {
//新增
gen.fileReg = protoregistry.GlobalFiles
}
导入相关proto文件
- 需要将third_party中依赖的google相关的proto文件拷贝到工具目录下
- 在goland的运行配置中设置当前运行目录为 E:/myprojects/gostart/tools 要和main文件在同一个目录下
自定义注册proto源码
package main
import (
"flag"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
"io/ioutil"
"os"
"os/exec"
"path"
"github.com/golang/protobuf/proto"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/types/pluginpb"
)
func registerProtoFile(srcDir string, filename string) error {
// First, convert the .proto file to a file descriptor set.
tmpFile := path.Join(srcDir, filename+".pb")
cmd := exec.Command("protoc",
"--include_source_info",
"--descriptor_set_out="+tmpFile,
"--proto_path="+srcDir,
path.Join(srcDir, filename))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
panic(err)
}
defer os.Remove(tmpFile)
// Now load that temporary file as a file descriptor set protobuf.
protoFile, err := ioutil.ReadFile(tmpFile)
if err != nil {
panic(err)
}
pbSet := new(descriptorpb.FileDescriptorSet)
if err := proto.Unmarshal(protoFile, pbSet); err != nil {
panic(err)
}
// We know protoc was invoked with a single .proto file.
pb := pbSet.GetFile()[0]
// Initialize the file descriptor object.
fd, err := protodesc.NewFile(pb, protoregistry.GlobalFiles)
if err != nil {
panic(err)
}
// and finally register it.
return protoregistry.GlobalFiles.RegisterFile(fd)
}
func registerProto() {
files := []string{
"google/api/http.proto",
"google/api/annotations.proto",
"google/api/client.proto",
"google/api/field_behavior.proto",
"google/api/resource.proto",
}
for _, f := range files {
err := registerProtoFile("./", f)
if err != nil {
panic(err)
}
}
}
func main() {
registerProto()
flag.Parse()
var flags flag.FlagSet
protogen.Options{
ParamFunc: flags.Set,
}.Run(func(gen *protogen.Plugin) error {
gen.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
for _, f := range gen.Files {
if !f.Generate {
continue
}
//generateFile(gen, f)
}
return nil
})
}