main.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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. //go:generate go run . -execute
  5. package main
  6. import (
  7. "flag"
  8. "fmt"
  9. "go/format"
  10. "io/ioutil"
  11. "os"
  12. "os/exec"
  13. "path"
  14. "path/filepath"
  15. "regexp"
  16. "sort"
  17. "strings"
  18. gengogrpc "google.golang.org/protobuf/cmd/protoc-gen-go-grpc/internal_gengogrpc"
  19. gengo "google.golang.org/protobuf/cmd/protoc-gen-go/internal_gengo"
  20. "google.golang.org/protobuf/compiler/protogen"
  21. "google.golang.org/protobuf/internal/detrand"
  22. "google.golang.org/protobuf/reflect/protoreflect"
  23. )
  24. func init() {
  25. // Determine repository root path.
  26. out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
  27. check(err)
  28. repoRoot = strings.TrimSpace(string(out))
  29. // Determine the module path.
  30. cmd := exec.Command("go", "list", "-m", "-f", "{{.Path}}")
  31. cmd.Dir = repoRoot
  32. out, err = cmd.CombinedOutput()
  33. check(err)
  34. modulePath = strings.TrimSpace(string(out))
  35. // When the environment variable RUN_AS_PROTOC_PLUGIN is set,
  36. // we skip running main and instead act as a protoc plugin.
  37. // This allows the binary to pass itself to protoc.
  38. if plugins := os.Getenv("RUN_AS_PROTOC_PLUGIN"); plugins != "" {
  39. // Disable deliberate output instability for generated files.
  40. // This is reasonable since we fully control the output.
  41. detrand.Disable()
  42. protogen.Run(nil, func(gen *protogen.Plugin) error {
  43. for _, plugin := range strings.Split(plugins, ",") {
  44. for _, file := range gen.Files {
  45. if file.Generate {
  46. switch plugin {
  47. case "go":
  48. gengo.GenerateVersionMarkers = false
  49. gengo.GenerateFile(gen, file)
  50. generateFieldNumbers(gen, file)
  51. case "gogrpc":
  52. gengogrpc.GenerateFile(gen, file)
  53. }
  54. }
  55. }
  56. }
  57. return nil
  58. })
  59. os.Exit(0)
  60. }
  61. }
  62. var (
  63. run bool
  64. protoRoot string
  65. repoRoot string
  66. modulePath string
  67. generatedPreamble = []string{
  68. "// Copyright 2019 The Go Authors. All rights reserved.",
  69. "// Use of this source code is governed by a BSD-style.",
  70. "// license that can be found in the LICENSE file.",
  71. "",
  72. "// Code generated by generate-protos. DO NOT EDIT.",
  73. "",
  74. }
  75. )
  76. func main() {
  77. flag.BoolVar(&run, "execute", false, "Write generated files to destination.")
  78. flag.StringVar(&protoRoot, "protoroot", os.Getenv("PROTOBUF_ROOT"), "The root of the protobuf source tree.")
  79. flag.Parse()
  80. if protoRoot == "" {
  81. panic("protobuf source root is not set")
  82. }
  83. generateLocalProtos()
  84. generateRemoteProtos()
  85. }
  86. func generateLocalProtos() {
  87. tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
  88. check(err)
  89. defer os.RemoveAll(tmpDir)
  90. // Generate all local proto files (except version-locked files).
  91. dirs := []struct {
  92. path string
  93. grpcPlugin bool
  94. annotateFor map[string]bool
  95. exclude map[string]bool
  96. }{
  97. {path: "cmd/protoc-gen-go/testdata", annotateFor: map[string]bool{"annotations/annotations.proto": true}},
  98. {path: "cmd/protoc-gen-go-grpc/testdata", grpcPlugin: true},
  99. {path: "internal/testprotos", exclude: map[string]bool{"irregular/irregular.proto": true}},
  100. {path: "encoding/testprotos"},
  101. {path: "reflect/protoregistry/testprotos"},
  102. }
  103. semVerRx := regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+`)
  104. for _, d := range dirs {
  105. subDirs := map[string]bool{}
  106. dstDir := filepath.Join(tmpDir, filepath.FromSlash(d.path))
  107. check(os.MkdirAll(dstDir, 0775))
  108. srcDir := filepath.Join(repoRoot, filepath.FromSlash(d.path))
  109. filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
  110. if !strings.HasSuffix(srcPath, ".proto") || semVerRx.MatchString(srcPath) {
  111. return nil
  112. }
  113. relPath, err := filepath.Rel(srcDir, srcPath)
  114. check(err)
  115. subDirs[filepath.Dir(relPath)] = true
  116. if d.exclude[filepath.ToSlash(relPath)] {
  117. return nil
  118. }
  119. // Emit a .meta file for certain files.
  120. var opts string
  121. if d.annotateFor[filepath.ToSlash(relPath)] {
  122. opts = ",annotate_code"
  123. }
  124. // Determine which set of plugins to use.
  125. plugins := "go"
  126. if d.grpcPlugin {
  127. plugins += ",gogrpc"
  128. }
  129. protoc(plugins, "-I"+filepath.Join(protoRoot, "src"), "-I"+srcDir, "--go_out=paths=source_relative"+opts+":"+dstDir, relPath)
  130. return nil
  131. })
  132. // For directories in testdata, generate a test that links in all
  133. // generated packages to ensure that it builds and initializes properly.
  134. // This is done because "go build ./..." does not build sub-packages
  135. // under testdata.
  136. if filepath.Base(d.path) == "testdata" {
  137. var imports []string
  138. for sd := range subDirs {
  139. imports = append(imports, fmt.Sprintf("_ %q", path.Join(modulePath, d.path, filepath.ToSlash(sd))))
  140. }
  141. sort.Strings(imports)
  142. s := strings.Join(append(generatedPreamble, []string{
  143. "package main",
  144. "",
  145. "import (" + strings.Join(imports, "\n") + ")",
  146. }...), "\n")
  147. b, err := format.Source([]byte(s))
  148. check(err)
  149. check(ioutil.WriteFile(filepath.Join(tmpDir, filepath.FromSlash(d.path+"/gen_test.go")), b, 0664))
  150. }
  151. }
  152. syncOutput(repoRoot, tmpDir)
  153. }
  154. func generateRemoteProtos() {
  155. tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
  156. check(err)
  157. defer os.RemoveAll(tmpDir)
  158. // Generate all remote proto files.
  159. files := []struct{ prefix, path string }{
  160. {"", "conformance/conformance.proto"},
  161. {"benchmarks", "benchmarks.proto"},
  162. {"benchmarks", "datasets/google_message1/proto2/benchmark_message1_proto2.proto"},
  163. {"benchmarks", "datasets/google_message1/proto3/benchmark_message1_proto3.proto"},
  164. {"benchmarks", "datasets/google_message2/benchmark_message2.proto"},
  165. {"benchmarks", "datasets/google_message3/benchmark_message3.proto"},
  166. {"benchmarks", "datasets/google_message3/benchmark_message3_1.proto"},
  167. {"benchmarks", "datasets/google_message3/benchmark_message3_2.proto"},
  168. {"benchmarks", "datasets/google_message3/benchmark_message3_3.proto"},
  169. {"benchmarks", "datasets/google_message3/benchmark_message3_4.proto"},
  170. {"benchmarks", "datasets/google_message3/benchmark_message3_5.proto"},
  171. {"benchmarks", "datasets/google_message3/benchmark_message3_6.proto"},
  172. {"benchmarks", "datasets/google_message3/benchmark_message3_7.proto"},
  173. {"benchmarks", "datasets/google_message3/benchmark_message3_8.proto"},
  174. {"benchmarks", "datasets/google_message4/benchmark_message4.proto"},
  175. {"benchmarks", "datasets/google_message4/benchmark_message4_1.proto"},
  176. {"benchmarks", "datasets/google_message4/benchmark_message4_2.proto"},
  177. {"benchmarks", "datasets/google_message4/benchmark_message4_3.proto"},
  178. {"src", "google/protobuf/any.proto"},
  179. {"src", "google/protobuf/api.proto"},
  180. {"src", "google/protobuf/compiler/plugin.proto"},
  181. {"src", "google/protobuf/descriptor.proto"},
  182. {"src", "google/protobuf/duration.proto"},
  183. {"src", "google/protobuf/empty.proto"},
  184. {"src", "google/protobuf/field_mask.proto"},
  185. {"src", "google/protobuf/source_context.proto"},
  186. {"src", "google/protobuf/struct.proto"},
  187. {"src", "google/protobuf/test_messages_proto2.proto"},
  188. {"src", "google/protobuf/test_messages_proto3.proto"},
  189. {"src", "google/protobuf/timestamp.proto"},
  190. {"src", "google/protobuf/type.proto"},
  191. {"src", "google/protobuf/wrappers.proto"},
  192. }
  193. for _, f := range files {
  194. protoc("go", "-I"+filepath.Join(protoRoot, f.prefix), "--go_out="+tmpDir, f.path)
  195. }
  196. syncOutput(repoRoot, filepath.Join(tmpDir, modulePath))
  197. }
  198. func protoc(plugins string, args ...string) {
  199. cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
  200. cmd.Args = append(cmd.Args, args...)
  201. cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN="+plugins)
  202. out, err := cmd.CombinedOutput()
  203. if err != nil {
  204. fmt.Printf("executing: %v\n%s\n", strings.Join(cmd.Args, " "), out)
  205. }
  206. check(err)
  207. }
  208. // generateFieldNumbers generates an internal package for descriptor.proto
  209. // and well-known types.
  210. func generateFieldNumbers(gen *protogen.Plugin, file *protogen.File) {
  211. if file.Desc.Package() != "google.protobuf" {
  212. return
  213. }
  214. importPath := modulePath + "/internal/fieldnum"
  215. base := strings.TrimSuffix(path.Base(file.Desc.Path()), ".proto")
  216. g := gen.NewGeneratedFile(importPath+"/"+base+"_gen.go", protogen.GoImportPath(importPath))
  217. for _, s := range generatedPreamble {
  218. g.P(s)
  219. }
  220. g.P("package ", path.Base(importPath))
  221. g.P("")
  222. var processMessages func([]*protogen.Message)
  223. processMessages = func(messages []*protogen.Message) {
  224. for _, message := range messages {
  225. g.P("// Field numbers for ", message.Desc.FullName(), ".")
  226. g.P("const (")
  227. for _, field := range message.Fields {
  228. fd := field.Desc
  229. typeName := fd.Kind().String()
  230. switch fd.Kind() {
  231. case protoreflect.EnumKind:
  232. typeName = string(fd.Enum().FullName())
  233. case protoreflect.MessageKind, protoreflect.GroupKind:
  234. typeName = string(fd.Message().FullName())
  235. }
  236. g.P(message.GoIdent.GoName, "_", field.GoName, "=", fd.Number(), "// ", fd.Cardinality(), " ", typeName)
  237. }
  238. g.P(")")
  239. processMessages(message.Messages)
  240. }
  241. }
  242. processMessages(file.Messages)
  243. }
  244. func syncOutput(dstDir, srcDir string) {
  245. filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
  246. if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") {
  247. return nil
  248. }
  249. relPath, err := filepath.Rel(srcDir, srcPath)
  250. check(err)
  251. dstPath := filepath.Join(dstDir, relPath)
  252. if run {
  253. fmt.Println("#", relPath)
  254. b, err := ioutil.ReadFile(srcPath)
  255. check(err)
  256. check(os.MkdirAll(filepath.Dir(dstPath), 0775))
  257. check(ioutil.WriteFile(dstPath, b, 0664))
  258. } else {
  259. cmd := exec.Command("diff", dstPath, srcPath, "-N", "-u")
  260. cmd.Stdout = os.Stdout
  261. cmd.Run()
  262. }
  263. return nil
  264. })
  265. }
  266. func check(err error) {
  267. if err != nil {
  268. panic(err)
  269. }
  270. }