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:

  1. The CodeGeneratorRequest is first generated from standard input
  2. Initialize the plug-in plugin through CodeGeneratorRequest
  3. Get the File File from the plug-in plugin
  4. Iterate through files, process content, generate files
  5. 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:

  1. –xxx_out=aaa=10, BBB =20:

  2. 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