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