1. Naming rules for plug-ins
For protobuf plug-in name, use protoc-gen-xxx
Proto-gen-xxx plugin is called when protoc –xxx_out is used
2. Protobuf parsing general flow
Method 1:
- The CodeGeneratorRequest is first generated from standard input
- Initialize the plug-in plugin through CodeGeneratorRequest
- Get the File File from the plug-in plugin
- Iterate through files, process content, generate files
- Write the Response plugin.response as standard output
Sample code:
s := flag.String("a".""."a")
flag.Parse()
/ / generated the Request
input, _ := ioutil.ReadAll(os.Stdin)
var req pluginpb.CodeGeneratorRequest
proto.Unmarshal(input, &req)
// Set parameters to generate plugin
opts := protogen.Options{
ParamFunc: flag.CommandLine.Set,
}
plugin, err := opts.New(&req)
iferr ! =nil {
panic(err)
}
fmt.Fprintf(os.Stderr, "a=%s\n", *s)
// Protoc passes a set of file structures to the program for processing, including files from the import in proto
for _, file := range plugin.Files {
if! file.Generate {// Show that the file passed in is true
continue
}
fmt.Fprintf(os.Stderr, "path:%s\n", file.GoImportPath)
genF := plugin.NewGeneratedFile(fmt.Sprintf("%s_error.pb.go", file.GeneratedFilenamePrefix), file.GoImportPath) // The object used to process the build file
GenFile(genF, file, *s)
}
/ / to generate the response
resp := plugin.Response()
out, err := proto.Marshal(resp)
iferr ! =nil {
panic(err)
}
// Output accordingly to stdout, which will be received by protoc
fmt.Fprintf(os.Stdout, string(out))
Copy the code
Method 2:
var s = flag.String("aaa".""."aaa")
var s1 = flag.String("bbb".""."aaa")
flag.Parse()
protogen.Options{
ParamFunc: flag.CommandLine.Set, // Set the command-line arguments --xxx_out=aaa=10, BBB =20:.
}.Run(func(gen *protogen.Plugin) error { //Run encapsulates the steps of method 1 internally
for _, f := range gen.Files {
if! f.Generate {// Determine whether code generation is required. The reason why this is true is whether the protoc specifies this file
continue
}
// Iterate through various message/ services... , same as method 1}})Copy the code
Sample code:
protogen.Options{
ParamFunc: flag.CommandLine.Set,
}.Run(func(gen *protogen.Plugin) error { //Run encapsulates request and response
fmt.Fprintf(os.Stderr, "aaa=%s\n", *s)
fmt.Fprintf(os.Stderr, "bbb=%s\n", *s1)
for _, f := range gen.Files {
fmt.Fprintf(os.Stderr, "f.Generate:%s==>%v\n", f.Desc.Name(), f.Generate)
if! f.Generate {// Determine if you need to generate code
continue
}
for _ ,file := range gen.Files {
for _, serv := range file.Services {
fmt.Fprintf(os.Stderr, "%s ==> %s\n", serv.Desc.Name(), serv.GoName)
}
}
}
return nil
})
Copy the code
Note the printable message: cannot be printed on standard output because the program response returns using standard output to be passed to the parent protoc process to debug and print to standard error
Note:
-
–xxx_out=aaa=10, BBB =20:
-
GeneratedFile must comply with the GO syntax, otherwise an error will be reported
Some of the details
Guide package
/ / import packages
//gen *protogen.Plugin
//protogen.GoImportPath("errors") converts errors to GoImportPath
// protogen.goimportPath ("errors").ident ("") returns variable names, function names, etc., or package names if empty
/ / such as:
//errors = protogen.GoImportPath("errors")
Ident("A") errors The identifier of A in the package, errors.a
Ident("") errors Package name errors
Errors. Ident("New(\"a ")") --> generate errors.New("a")
QualifiedGoIdent(b) although it has the function to import the package and return a valid name, if GoIdent is passed directly to P the function is called QualifiedGoIdent inside P, that is, the package is automatically directed with a valid name
b := protogen.GoIdent{ // represents b under package A /b
GoName: "b",
GoImportPath: "a/b",
}
name := genF.QualifiedGoIdent(b) // Automatically import a/ B package and return the name b.b
fmt.Fprintf(os.Stderr, "b:%s\n", name)
c := protogen.GoIdent{
GoName: "b",
GoImportPath: "c/b",
}
name = genF.QualifiedGoIdent(c) // Automatically import c/b package and return the name of this package
fmt.Fprintf(os.Stderr, "c:%s\n", name)
QualifiedGoIdent is not called manually if you are using the P function generation code, but if you are using the text template generation code, you need to manually call QualifiedGoIdent to generate the string name
Copy the code
The factor that affects whether file.Generate is true is whether the protoc specifies the file
For example, a. proto defines enumerations, b. proto imports A, protoc –xxx_out=.b. Proto uses protoc –xxx_out=. But because protoc only specifies B, then A.generate =false, b.generate =true
3. Protobuf extension
Go (no other language has studied) in protobuf parsing is through the proto self-describing, description file in (Google/protobuf descriptor. Proto)
Such as Google. Protobuf. FieldOptions is description field option, by extending the message description can achieve advanced functionality
Note: Proto3 syntax only extends custom options, not the extensions keyword
extend google.protobuf.FieldOptions {
optional float my_field_option = 50002; // The number needs to be in the range specified by the FieldOptions Extensions, which can be extended without modifying the source file
}
Copy the code
for _, msg := range file.Proto.MessageType { // Traverses all the message bodies in the proto file
for _, field := range msg.Field { // Iterate over the message field
opt := proto.GetExtension(field.Options, aaa.E_MyFieldOption)Aaa.E_MyFieldOption is a file output with protoc -go_out, and the extended fields all start with a big "E"
if v, ok := opt.(float32); ok { // If the extension is a message, it is usually converted to a pointer. Click on the generated extension go file to view the ExtensionType property
fmt.Fprintf(&buf, "//%s=%s=%f\n", *msg.Name, *field.Name, v)
}
}
}
Copy the code
Protobuf extension syntax (developers.google.com/protocol-bu…).
Official examples:
import "google/protobuf/descriptor.proto";
extend google.protobuf.FileOptions { // File options are extended
optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions { // Message option extension
optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions { // Field options are extended
optional float my_field_option = 50002;
}
extend google.protobuf.EnumOptions { // Enumeration option extension
optional bool my_enum_option = 50003;
}
extend google.protobuf.EnumValueOptions { // Enumeration value (like field) option extension
optional uint32 my_enum_value_option = 50004;
}
extend google.protobuf.ServiceOptions { // Service option extension
optional MyEnum my_service_option = 50005;
}
extend google.protobuf.MethodOptions { // Method options are extended
optional MyMessage my_method_option = 50006;
}
option (my_file_option) = "Hello world!"; // Use the file option
message MyMessage {
option (my_message_option) = 1234; // Use the message option
int32 foo = 1 [(my_field_option) = 4.5]; // Use the field option
string bar = 2;
}
enum MyEnum {
option (my_enum_option) = true; // Enumeration options
FOO = 1 [(my_enum_value_option) = 321]; // Enumeration value options
BAR = 2;
}
message RequestType {}
message ResponseType {}
service MyService {
option (my_service_option) = FOO; // Service options
rpc MyMethod(RequestType) returns(ResponseType) {
// Note: my_method_option has type MyMessage. We can set each field
// within it using a separate "option" line.
option (my_method_option).foo = 567; // Method options
option (my_method_option).bar = "Some string";
/ / or
option (my_method_option) = {
foo:123,
bar:"123123"}}}Copy the code
The sample code