This guide will walk you through the steps to create a plugin for protoc
, the protocol buffer compiler. A plugin allows you to generate custom code based on your .proto
files.
Step 1: Set Up Your Project
Create a new directory for your plugin and initialize a new Go module:
mkdir myprotocplugin
cd myprotocplugin
go mod init myprotocplugin
Step 2: Define the Main Function
Your plugin will read a CodeGeneratorRequest
from standard input and write a CodeGeneratorResponse
to standard output. Start by creating a main.go
file:
package main
import (
"google.golang.org/protobuf/compiler/protogen"
)
func main() {
protogen.Options{}.Run(func(gen *protogen.Plugin) error {
for _, file := range gen.Files {
if !file.Generate {
continue
}
generateFile(gen, file)
}
return nil
})
}
Step 3: Generate Code
Create a function to generate code for each file:
func generateFile(gen *protogen.Plugin, file *protogen.File) {
filename := file.GeneratedFilenamePrefix + ".pb.go"
g := gen.NewGeneratedFile(filename, file.GoImportPath)
g.P("// Code generated by protoc-gen-myplugin. DO NOT EDIT.")
g.P()
g.P("package ", file.GoPackageName)
g.P()
for _, message := range file.Messages {
generateMessage(g, message)
}
for _, service := range file.Services {
generateService(g, service)
}
}
Step 4: Generate Message Code
Create a function to generate code for each message:
func generateMessage(g *protogen.GeneratedFile, message *protogen.Message) {
g.P("// ", message.GoIdent.GoName, " is a message.")
g.P("type ", message.GoIdent.GoName, " struct {")
for _, field := range message.Fields {
g.P(field.GoName, " ", field.GoType, " `json:\"", field.Desc.JSONName(), "\"`")
}
g.P("}")
g.P()
}
Step 5: Generate Service Code
Create a function to generate code for each service:
func generateService(g *protogen.GeneratedFile, service *protogen.Service) {
g.P("// ", service.GoName, " is a service.")
g.P("type ", service.GoName, " interface {")
for _, method := range service.Methods {
g.P(method.GoName, "(", method.Input.GoIdent, ") (", method.Output.GoIdent, ", error)")
}
g.P("}")
g.P()
}
Step 6: Handle Dependencies
If you need to add dependencies to the generated file, use the QualifiedGoIdent
method. This method ensures that the correct import path is added to the file’s imports and returns the fully qualified name of the identifier.
Here is an example:
ErrorfName := g.QualifiedGoIdent(protogen.GoIdent{
GoName: "Errorf",
GoImportPath: "fmt",
})
In this example, the QualifiedGoIdent
method will add the fmt
package to the file’s imports and return the correct name of the Errorf
function. The ErrorfName
variable will contain the string fmt.Errorf
.
Step 7: Build and Test Your Plugin
Build your plugin:
go build -o protoc-gen-myplugin
Now, you need to copy your executable into one of the directories listed in your PATH
environment variable to make it available for the protoc
utility.
Test your plugin with protoc
:
protoc --myplugin_out=. --myplugin_opt=paths=source_relative echosrv.proto
Conclusion
You have now created a basic plugin for protoc
. This plugin reads .proto
files and generates Go code based on the messages and services defined in those files. You can extend this plugin to generate more complex code as needed.