protoc的golang自动代码生成, 源码文件【google.golang.org/protobuf/types/pluginpb/plugin.pb.go】中有如下的一段文档,很好的解释了protoc插件的工作,和使用过程。

  1. // protoc (aka the Protocol Compiler) can be extended via plugins. A plugin is
  2. // just a program that reads a CodeGeneratorRequest from stdin and writes a
  3. // CodeGeneratorResponse to stdout.
  4. //
  5. // Plugins written using C++ can use google/protobuf/compiler/plugin.h instead
  6. // of dealing with the raw protocol defined here.
  7. //
  8. // A plugin executable needs only to be placed somewhere in the path. The
  9. // plugin should be named "protoc-gen-${NAME}", and will then be used when the
  10. // flag "--${NAME}_out" is passed to protoc.
  11. // Code generated by protoc-gen-go. DO NOT EDIT.
  12. // source: google/protobuf/compiler/plugin.proto

这段话翻译过来大致如下:
protoc可以通过插件扩展。插件的工作原理是从标准输入中读取 CodeGeneratorRequest, 处理后生成CodeGeneratorResponse ,并写入标准输出中。
插件必须按照 protoc-gen-{NAME} 格式命名,在使用protoc中将, —{NAME}_out 当作参数传给protoc, 就会起作用。

实际调用过程中,可以把生成的插件文件放在环境变量PATH中,也可直接通过—plugin=[ plugin-name=plugin-path, ….] 传给protoc

  1. protoc --plugin=protoc-gen-NAME=path/to/mybinary --NAME_out=OUT_DIR
  2. protoc --NAME_out=OUT_DIR

这样, protoc就知道要使用 protoc-gen-NAME, 并输出文件到 OUT_DIR 中去了

DEMO

image.png
main.go

  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "go/format"
  6. "google.golang.org/protobuf/compiler/protogen"
  7. "regexp"
  8. )
  9. func fileGen(p *protogen.Plugin,file *protogen.File) error{
  10. fileName := *file.Proto.Name
  11. //fmt.Println(fileName) 标准输出也会被protoc拿到
  12. //messageName := *file.Proto.MessageType[0].Name
  13. //fmt.Println(messageName)
  14. mainContent := fmt.Sprintf(`
  15. // Code generated by protoc-gen-jwtest. DO NOT EDIT.
  16. package jwtest
  17. import (
  18. "fmt"
  19. )
  20. func Print() {
  21. fmt.Println("hello")
  22. }
  23. `)
  24. formatContent, err := format.Source([]byte(mainContent))
  25. if err != nil {
  26. return err
  27. }
  28. f := getMainGeneratedFile(p,fileName,file)
  29. if _, err := f.Write(formatContent); err != nil {
  30. return err
  31. }
  32. return nil
  33. }
  34. func getMainGeneratedFile(p *protogen.Plugin, fileName string, file *protogen.File) *protogen.GeneratedFile{
  35. path := regexp.MustCompile(`^proto/(.*).proto`).ReplaceAllString(fileName, `jwtest/$1.jwtest.go`)
  36. generatedFile := p.NewGeneratedFile(
  37. path,
  38. protogen.GoImportPath(file.GeneratedFilenamePrefix),
  39. )
  40. return generatedFile
  41. }
  42. func main() {
  43. var flags flag.FlagSet
  44. // 插件参数
  45. opts := &protogen.Options{
  46. ParamFunc: flags.Set,
  47. }
  48. opts.Run(func(plugin *protogen.Plugin) error {
  49. for _,fileName := range plugin.Request.FileToGenerate{
  50. fileToGenerate := plugin.FilesByPath[fileName]
  51. if err := fileGen(plugin,fileToGenerate); err != nil {
  52. return err
  53. }
  54. }
  55. return nil
  56. })
  57. }

需要注意的点:不要做额外的打印操作,否则报:Plugin output is unparseable

compiler/protogen

protogen包提供了对编写protoc插件的支持

protoc插件,即protoc缓冲区编译器,是一种程序,它从标准输入读取CodeGeneratorRequest消息,并将CodeGeneratorResponse消息写入标准输出。这个包支持编写生成Go代码的插件。
文档:https://pkg.go.dev/google.golang.org/protobuf@v1.27.1/compiler/protogen