github.com/whiteCcinn/protobuf-go@v1.0.9/internal/cmd/generate-protos/main.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:generate go run . -execute
     6  
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"flag"
    12  	"fmt"
    13  	"go/format"
    14  	"io/ioutil"
    15  	"os"
    16  	"os/exec"
    17  	"path"
    18  	"path/filepath"
    19  	"regexp"
    20  	"sort"
    21  	"strconv"
    22  	"strings"
    23  
    24  	gengo "github.com/whiteCcinn/protobuf-go/cmd/protoc-gen-go/internal_gengo"
    25  	"github.com/whiteCcinn/protobuf-go/compiler/protogen"
    26  	"github.com/whiteCcinn/protobuf-go/internal/detrand"
    27  )
    28  
    29  func init() {
    30  	// Determine repository root path.
    31  	out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
    32  	check(err)
    33  	repoRoot = strings.TrimSpace(string(out))
    34  
    35  	// Determine the module path.
    36  	cmd := exec.Command("go", "list", "-m", "-f", "{{.Path}}")
    37  	cmd.Dir = repoRoot
    38  	out, err = cmd.CombinedOutput()
    39  	check(err)
    40  	modulePath = strings.TrimSpace(string(out))
    41  
    42  	// When the environment variable RUN_AS_PROTOC_PLUGIN is set,
    43  	// we skip running main and instead act as a protoc plugin.
    44  	// This allows the binary to pass itself to protoc.
    45  	if plugin := os.Getenv("RUN_AS_PROTOC_PLUGIN"); plugin != "" {
    46  		// Disable deliberate output instability for generated files.
    47  		// This is reasonable since we fully control the output.
    48  		detrand.Disable()
    49  
    50  		protogen.Options{}.Run(func(gen *protogen.Plugin) error {
    51  			for _, file := range gen.Files {
    52  				if file.Generate {
    53  					gengo.GenerateVersionMarkers = false
    54  					gengo.GenerateFile(gen, file)
    55  					generateIdentifiers(gen, file)
    56  					generateSouceContextStringer(gen, file)
    57  				}
    58  			}
    59  			gen.SupportedFeatures = gengo.SupportedFeatures
    60  			return nil
    61  		})
    62  		os.Exit(0)
    63  	}
    64  }
    65  
    66  var (
    67  	run        bool
    68  	protoRoot  string
    69  	repoRoot   string
    70  	modulePath string
    71  
    72  	generatedPreamble = []string{
    73  		"// Copyright 2019 The Go Authors. All rights reserved.",
    74  		"// Use of this source code is governed by a BSD-style",
    75  		"// license that can be found in the LICENSE file.",
    76  		"",
    77  		"// Code generated by generate-protos. DO NOT EDIT.",
    78  		"",
    79  	}
    80  )
    81  
    82  func main() {
    83  	flag.BoolVar(&run, "execute", false, "Write generated files to destination.")
    84  	flag.StringVar(&protoRoot, "protoroot", os.Getenv("PROTOBUF_ROOT"), "The root of the protobuf source tree.")
    85  	flag.Parse()
    86  	if protoRoot == "" {
    87  		panic("protobuf source root is not set")
    88  	}
    89  
    90  	generateLocalProtos()
    91  	generateRemoteProtos()
    92  }
    93  
    94  func generateLocalProtos() {
    95  	tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
    96  	check(err)
    97  	defer os.RemoveAll(tmpDir)
    98  
    99  	// Generate all local proto files (except version-locked files).
   100  	dirs := []struct {
   101  		path     string
   102  		pkgPaths map[string]string // mapping of .proto path to Go package path
   103  		annotate map[string]bool   // .proto files to annotate
   104  		exclude  map[string]bool   // .proto files to exclude from generation
   105  	}{{
   106  		path:     "cmd/protoc-gen-go/testdata",
   107  		pkgPaths: map[string]string{"cmd/protoc-gen-go/testdata/nopackage/nopackage.proto": "github.com/whiteCcinn/protobuf-go/cmd/protoc-gen-go/testdata/nopackage"},
   108  		annotate: map[string]bool{"cmd/protoc-gen-go/testdata/annotations/annotations.proto": true},
   109  	}, {
   110  		path:    "internal/testprotos",
   111  		exclude: map[string]bool{"internal/testprotos/irregular/irregular.proto": true},
   112  	}}
   113  	excludeRx := regexp.MustCompile(`legacy/.*/`)
   114  	for _, d := range dirs {
   115  		subDirs := map[string]bool{}
   116  
   117  		srcDir := filepath.Join(repoRoot, filepath.FromSlash(d.path))
   118  		filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
   119  			if !strings.HasSuffix(srcPath, ".proto") || excludeRx.MatchString(srcPath) {
   120  				return nil
   121  			}
   122  			relPath, err := filepath.Rel(repoRoot, srcPath)
   123  			check(err)
   124  
   125  			srcRelPath, err := filepath.Rel(srcDir, srcPath)
   126  			check(err)
   127  			subDirs[filepath.Dir(srcRelPath)] = true
   128  
   129  			if d.exclude[filepath.ToSlash(relPath)] {
   130  				return nil
   131  			}
   132  
   133  			opts := "module=" + modulePath
   134  			for protoPath, goPkgPath := range d.pkgPaths {
   135  				opts += fmt.Sprintf(",M%v=%v", protoPath, goPkgPath)
   136  			}
   137  			if d.annotate[filepath.ToSlash(relPath)] {
   138  				opts += ",annotate_code"
   139  			}
   140  			protoc("-I"+filepath.Join(protoRoot, "src"), "-I"+repoRoot, "--go_out="+opts+":"+tmpDir, relPath)
   141  			return nil
   142  		})
   143  
   144  		// For directories in testdata, generate a test that links in all
   145  		// generated packages to ensure that it builds and initializes properly.
   146  		// This is done because "go build ./..." does not build sub-packages
   147  		// under testdata.
   148  		if filepath.Base(d.path) == "testdata" {
   149  			var imports []string
   150  			for sd := range subDirs {
   151  				imports = append(imports, fmt.Sprintf("_ %q", path.Join(modulePath, d.path, filepath.ToSlash(sd))))
   152  			}
   153  			sort.Strings(imports)
   154  
   155  			s := strings.Join(append(generatedPreamble, []string{
   156  				"package main",
   157  				"",
   158  				"import (" + strings.Join(imports, "\n") + ")",
   159  			}...), "\n")
   160  			b, err := format.Source([]byte(s))
   161  			check(err)
   162  			check(ioutil.WriteFile(filepath.Join(tmpDir, filepath.FromSlash(d.path+"/gen_test.go")), b, 0664))
   163  		}
   164  	}
   165  
   166  	syncOutput(repoRoot, tmpDir)
   167  }
   168  
   169  func generateRemoteProtos() {
   170  	tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
   171  	check(err)
   172  	defer os.RemoveAll(tmpDir)
   173  
   174  	// Generate all remote proto files.
   175  	files := []struct{ prefix, path, goPkgPath string }{
   176  		// Well-known protos.
   177  		{"src", "google/protobuf/any.proto", ""},
   178  		{"src", "google/protobuf/api.proto", ""},
   179  		{"src", "google/protobuf/duration.proto", ""},
   180  		{"src", "google/protobuf/empty.proto", ""},
   181  		{"src", "google/protobuf/field_mask.proto", ""},
   182  		{"src", "google/protobuf/source_context.proto", ""},
   183  		{"src", "google/protobuf/struct.proto", ""},
   184  		{"src", "google/protobuf/timestamp.proto", ""},
   185  		{"src", "google/protobuf/type.proto", ""},
   186  		{"src", "google/protobuf/wrappers.proto", ""},
   187  
   188  		// Compiler protos.
   189  		{"src", "google/protobuf/compiler/plugin.proto", ""},
   190  		{"src", "google/protobuf/descriptor.proto", ""},
   191  
   192  		// Conformance protos.
   193  		{"", "conformance/conformance.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/conformance;conformance"},
   194  		{"src", "google/protobuf/test_messages_proto2.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/conformance;conformance"},
   195  		{"src", "google/protobuf/test_messages_proto3.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/conformance;conformance"},
   196  
   197  		// Benchmark protos.
   198  		{"benchmarks", "benchmarks.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks;benchmarks"},
   199  		{"benchmarks", "datasets/google_message1/proto2/benchmark_message1_proto2.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message1/proto2;proto2"},
   200  		{"benchmarks", "datasets/google_message1/proto3/benchmark_message1_proto3.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message1/proto3;proto3"},
   201  		{"benchmarks", "datasets/google_message2/benchmark_message2.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message2;google_message2"},
   202  		{"benchmarks", "datasets/google_message3/benchmark_message3.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message3;google_message3"},
   203  		{"benchmarks", "datasets/google_message3/benchmark_message3_1.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message3;google_message3"},
   204  		{"benchmarks", "datasets/google_message3/benchmark_message3_2.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message3;google_message3"},
   205  		{"benchmarks", "datasets/google_message3/benchmark_message3_3.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message3;google_message3"},
   206  		{"benchmarks", "datasets/google_message3/benchmark_message3_4.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message3;google_message3"},
   207  		{"benchmarks", "datasets/google_message3/benchmark_message3_5.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message3;google_message3"},
   208  		{"benchmarks", "datasets/google_message3/benchmark_message3_6.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message3;google_message3"},
   209  		{"benchmarks", "datasets/google_message3/benchmark_message3_7.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message3;google_message3"},
   210  		{"benchmarks", "datasets/google_message3/benchmark_message3_8.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message3;google_message3"},
   211  		{"benchmarks", "datasets/google_message4/benchmark_message4.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message4;google_message4"},
   212  		{"benchmarks", "datasets/google_message4/benchmark_message4_1.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message4;google_message4"},
   213  		{"benchmarks", "datasets/google_message4/benchmark_message4_2.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message4;google_message4"},
   214  		{"benchmarks", "datasets/google_message4/benchmark_message4_3.proto", "github.com/whiteCcinn/protobuf-go/internal/testprotos/benchmarks/datasets/google_message4;google_message4"},
   215  	}
   216  
   217  	opts := "module=" + modulePath
   218  	for _, file := range files {
   219  		if file.goPkgPath != "" {
   220  			opts += fmt.Sprintf(",M%v=%v", file.path, file.goPkgPath)
   221  		}
   222  	}
   223  	for _, f := range files {
   224  		protoc("-I"+filepath.Join(protoRoot, f.prefix), "--go_out="+opts+":"+tmpDir, f.path)
   225  	}
   226  
   227  	syncOutput(repoRoot, tmpDir)
   228  }
   229  
   230  func protoc(args ...string) {
   231  	// TODO: Remove --experimental_allow_proto3_optional flag.
   232  	cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0], "--experimental_allow_proto3_optional")
   233  	cmd.Args = append(cmd.Args, args...)
   234  	cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1")
   235  	out, err := cmd.CombinedOutput()
   236  	if err != nil {
   237  		fmt.Printf("executing: %v\n%s\n", strings.Join(cmd.Args, " "), out)
   238  	}
   239  	check(err)
   240  }
   241  
   242  // generateIdentifiers generates an internal package for descriptor.proto
   243  // and well-known types.
   244  func generateIdentifiers(gen *protogen.Plugin, file *protogen.File) {
   245  	if file.Desc.Package() != "google.protobuf" {
   246  		return
   247  	}
   248  
   249  	importPath := modulePath + "/internal/genid"
   250  	base := strings.TrimSuffix(path.Base(file.Desc.Path()), ".proto")
   251  	g := gen.NewGeneratedFile(importPath+"/"+base+"_gen.go", protogen.GoImportPath(importPath))
   252  	for _, s := range generatedPreamble {
   253  		g.P(s)
   254  	}
   255  	g.P("package ", path.Base(importPath))
   256  	g.P()
   257  
   258  	g.P("const ", file.GoDescriptorIdent.GoName, " = ", strconv.Quote(file.Desc.Path()))
   259  	g.P()
   260  
   261  	var processEnums func([]*protogen.Enum)
   262  	var processMessages func([]*protogen.Message)
   263  	const protoreflectPackage = protogen.GoImportPath("github.com/whiteCcinn/protobuf-go/reflect/protoreflect")
   264  	processEnums = func(enums []*protogen.Enum) {
   265  		for _, enum := range enums {
   266  			g.P("// Full and short names for ", enum.Desc.FullName(), ".")
   267  			g.P("const (")
   268  			g.P(enum.GoIdent.GoName, "_enum_fullname = ", strconv.Quote(string(enum.Desc.FullName())))
   269  			g.P(enum.GoIdent.GoName, "_enum_name = ", strconv.Quote(string(enum.Desc.Name())))
   270  			g.P(")")
   271  			g.P()
   272  		}
   273  	}
   274  	processMessages = func(messages []*protogen.Message) {
   275  		for _, message := range messages {
   276  			g.P("// Names for ", message.Desc.FullName(), ".")
   277  			g.P("const (")
   278  			g.P(message.GoIdent.GoName, "_message_name ", protoreflectPackage.Ident("Name"), " = ", strconv.Quote(string(message.Desc.Name())))
   279  			g.P(message.GoIdent.GoName, "_message_fullname ", protoreflectPackage.Ident("FullName"), " = ", strconv.Quote(string(message.Desc.FullName())))
   280  			g.P(")")
   281  			g.P()
   282  
   283  			if len(message.Fields) > 0 {
   284  				g.P("// Field names for ", message.Desc.FullName(), ".")
   285  				g.P("const (")
   286  				for _, field := range message.Fields {
   287  					g.P(message.GoIdent.GoName, "_", field.GoName, "_field_name ", protoreflectPackage.Ident("Name"), " = ", strconv.Quote(string(field.Desc.Name())))
   288  				}
   289  				g.P()
   290  				for _, field := range message.Fields {
   291  					g.P(message.GoIdent.GoName, "_", field.GoName, "_field_fullname ", protoreflectPackage.Ident("FullName"), " = ", strconv.Quote(string(field.Desc.FullName())))
   292  				}
   293  				g.P(")")
   294  				g.P()
   295  
   296  				g.P("// Field numbers for ", message.Desc.FullName(), ".")
   297  				g.P("const (")
   298  				for _, field := range message.Fields {
   299  					g.P(message.GoIdent.GoName, "_", field.GoName, "_field_number ", protoreflectPackage.Ident("FieldNumber"), " = ", field.Desc.Number())
   300  				}
   301  				g.P(")")
   302  				g.P()
   303  			}
   304  
   305  			if len(message.Oneofs) > 0 {
   306  				g.P("// Oneof names for ", message.Desc.FullName(), ".")
   307  				g.P("const (")
   308  				for _, oneof := range message.Oneofs {
   309  					g.P(message.GoIdent.GoName, "_", oneof.GoName, "_oneof_name ", protoreflectPackage.Ident("Name"), " = ", strconv.Quote(string(oneof.Desc.Name())))
   310  				}
   311  				g.P()
   312  				for _, oneof := range message.Oneofs {
   313  					g.P(message.GoIdent.GoName, "_", oneof.GoName, "_oneof_fullname ", protoreflectPackage.Ident("FullName"), " = ", strconv.Quote(string(oneof.Desc.FullName())))
   314  				}
   315  				g.P(")")
   316  				g.P()
   317  			}
   318  
   319  			processEnums(message.Enums)
   320  			processMessages(message.Messages)
   321  		}
   322  	}
   323  	processEnums(file.Enums)
   324  	processMessages(file.Messages)
   325  }
   326  
   327  // generateSouceContextStringer generates the implementation for the
   328  // protoreflect.SourcePath.String method by using information present
   329  // in the descriptor.proto.
   330  func generateSouceContextStringer(gen *protogen.Plugin, file *protogen.File) {
   331  	if file.Desc.Path() != "google/protobuf/descriptor.proto" {
   332  		return
   333  	}
   334  
   335  	importPath := modulePath + "/reflect/protoreflect"
   336  	g := gen.NewGeneratedFile(importPath+"/source_gen.go", protogen.GoImportPath(importPath))
   337  	for _, s := range generatedPreamble {
   338  		g.P(s)
   339  	}
   340  	g.P("package ", path.Base(importPath))
   341  	g.P()
   342  
   343  	var messages []*protogen.Message
   344  	for _, message := range file.Messages {
   345  		if message.Desc.Name() == "FileDescriptorProto" {
   346  			messages = append(messages, message)
   347  		}
   348  	}
   349  	seen := make(map[*protogen.Message]bool)
   350  
   351  	for len(messages) > 0 {
   352  		m := messages[0]
   353  		messages = messages[1:]
   354  		if seen[m] {
   355  			continue
   356  		}
   357  		seen[m] = true
   358  
   359  		g.P("func (p *SourcePath) append", m.GoIdent.GoName, "(b []byte) []byte {")
   360  		g.P("if len(*p) == 0 { return b }")
   361  		g.P("switch (*p)[0] {")
   362  		for _, f := range m.Fields {
   363  			g.P("case ", f.Desc.Number(), ":")
   364  			var cardinality string
   365  			switch {
   366  			case f.Desc.IsMap():
   367  				panic("maps are not supported")
   368  			case f.Desc.IsList():
   369  				cardinality = "Repeated"
   370  			default:
   371  				cardinality = "Singular"
   372  			}
   373  			nextAppender := "nil"
   374  			if f.Message != nil {
   375  				nextAppender = "(*SourcePath).append" + f.Message.GoIdent.GoName
   376  				messages = append(messages, f.Message)
   377  			}
   378  			g.P("b = p.append", cardinality, "Field(b, ", strconv.Quote(string(f.Desc.Name())), ", ", nextAppender, ")")
   379  		}
   380  		g.P("}")
   381  		g.P("return b")
   382  		g.P("}")
   383  		g.P()
   384  	}
   385  }
   386  
   387  func syncOutput(dstDir, srcDir string) {
   388  	filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
   389  		if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") {
   390  			return nil
   391  		}
   392  		relPath, err := filepath.Rel(srcDir, srcPath)
   393  		check(err)
   394  		dstPath := filepath.Join(dstDir, relPath)
   395  
   396  		if run {
   397  			if copyFile(dstPath, srcPath) {
   398  				fmt.Println("#", relPath)
   399  			}
   400  		} else {
   401  			cmd := exec.Command("diff", dstPath, srcPath, "-N", "-u")
   402  			cmd.Stdout = os.Stdout
   403  			cmd.Run()
   404  		}
   405  		return nil
   406  	})
   407  }
   408  
   409  func copyFile(dstPath, srcPath string) (changed bool) {
   410  	src, err := ioutil.ReadFile(srcPath)
   411  	check(err)
   412  	check(os.MkdirAll(filepath.Dir(dstPath), 0775))
   413  	dst, _ := ioutil.ReadFile(dstPath)
   414  	if bytes.Equal(src, dst) {
   415  		return false
   416  	}
   417  	check(ioutil.WriteFile(dstPath, src, 0664))
   418  	return true
   419  }
   420  
   421  func check(err error) {
   422  	if err != nil {
   423  		panic(err)
   424  	}
   425  }