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 }