blob: 94f735e3c7e9f6684c735cdcc91e6cdea1619a9f [file] [log] [blame]
Joe Tsai19058432019-02-27 21:46:29 -08001// 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
7package main
8
9import (
10 "flag"
11 "fmt"
Joe Tsaid56458e2019-03-01 18:11:42 -080012 "go/format"
Joe Tsai19058432019-02-27 21:46:29 -080013 "io/ioutil"
14 "os"
15 "os/exec"
Joe Tsai1af1de02019-03-01 16:12:32 -080016 "path"
Joe Tsai19058432019-02-27 21:46:29 -080017 "path/filepath"
18 "regexp"
Joe Tsaid56458e2019-03-01 18:11:42 -080019 "sort"
Joe Tsai19058432019-02-27 21:46:29 -080020 "strings"
21
22 gengogrpc "github.com/golang/protobuf/v2/cmd/protoc-gen-go-grpc/internal_gengogrpc"
23 gengo "github.com/golang/protobuf/v2/cmd/protoc-gen-go/internal_gengo"
24 "github.com/golang/protobuf/v2/protogen"
Joe Tsai1af1de02019-03-01 16:12:32 -080025 "github.com/golang/protobuf/v2/reflect/protoreflect"
Joe Tsai19058432019-02-27 21:46:29 -080026)
27
28func init() {
29 // When the environment variable RUN_AS_PROTOC_PLUGIN is set,
30 // we skip running main and instead act as a protoc plugin.
31 // This allows the binary to pass itself to protoc.
Joe Tsaid56458e2019-03-01 18:11:42 -080032 if plugins := os.Getenv("RUN_AS_PROTOC_PLUGIN"); plugins != "" {
Joe Tsai19058432019-02-27 21:46:29 -080033 protogen.Run(nil, func(gen *protogen.Plugin) error {
Joe Tsaid56458e2019-03-01 18:11:42 -080034 for _, plugin := range strings.Split(plugins, ",") {
35 for _, file := range gen.Files {
36 if file.Generate {
37 switch plugin {
38 case "go":
39 gengo.GenerateFile(gen, file)
40 generateDescriptorFields(gen, file)
41 case "gogrpc":
42 gengogrpc.GenerateFile(gen, file)
43 }
44 }
Joe Tsai19058432019-02-27 21:46:29 -080045 }
46 }
47 return nil
48 })
49 os.Exit(0)
50 }
51}
52
53var (
Joe Tsaid56458e2019-03-01 18:11:42 -080054 run bool
55 protoRoot string
56 repoRoot string
57 modulePath string
Joe Tsai1af1de02019-03-01 16:12:32 -080058
59 generatedPreamble = []string{
60 "// Copyright 2019 The Go Authors. All rights reserved.",
61 "// Use of this source code is governed by a BSD-style.",
62 "// license that can be found in the LICENSE file.",
63 "",
64 "// Code generated by generate-protos. DO NOT EDIT.",
65 "",
66 }
Joe Tsai19058432019-02-27 21:46:29 -080067)
68
69func main() {
70 flag.BoolVar(&run, "execute", false, "Write generated files to destination.")
71 flag.StringVar(&protoRoot, "protoroot", os.Getenv("PROTOBUF_ROOT"), "The root of the protobuf source tree.")
72 flag.Parse()
73 if protoRoot == "" {
74 panic("protobuf source root is not set")
75 }
76
77 // Determine repository root path.
78 out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
79 check(err)
80 repoRoot = strings.TrimSpace(string(out))
81
Joe Tsaid56458e2019-03-01 18:11:42 -080082 // Determine the module path.
83 cmd := exec.Command("go", "list", "-m", "-f", "{{.Path}}")
84 cmd.Dir = repoRoot
85 out, err = cmd.CombinedOutput()
86 check(err)
87 modulePath = strings.TrimSpace(string(out))
88
Joe Tsai19058432019-02-27 21:46:29 -080089 generateLocalProtos()
90 generateRemoteProtos()
91}
92
93func generateLocalProtos() {
94 tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
95 check(err)
96 defer os.RemoveAll(tmpDir)
97
98 // Generate all local proto files (except version-locked files).
99 dirs := []struct {
100 path string
Joe Tsaid56458e2019-03-01 18:11:42 -0800101 grpcPlugin bool
Joe Tsai19058432019-02-27 21:46:29 -0800102 annotateFor map[string]bool
103 }{
Joe Tsaid56458e2019-03-01 18:11:42 -0800104 {path: "cmd/protoc-gen-go/testdata", annotateFor: map[string]bool{"annotations/annotations.proto": true}},
105 {path: "cmd/protoc-gen-go-grpc/testdata", grpcPlugin: true},
106 {path: "internal/testprotos"},
107 {path: "encoding/testprotos"},
108 {path: "reflect/protoregistry/testprotos"},
Joe Tsai19058432019-02-27 21:46:29 -0800109 }
110 semVerRx := regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+`)
111 for _, d := range dirs {
Joe Tsaid56458e2019-03-01 18:11:42 -0800112 subDirs := map[string]bool{}
113
Joe Tsai19058432019-02-27 21:46:29 -0800114 dstDir := filepath.Join(tmpDir, filepath.FromSlash(d.path))
115 check(os.MkdirAll(dstDir, 0775))
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") || semVerRx.MatchString(srcPath) {
120 return nil
121 }
122 relPath, err := filepath.Rel(srcDir, srcPath)
123 check(err)
Joe Tsaid56458e2019-03-01 18:11:42 -0800124 subDirs[filepath.Dir(relPath)] = true
Joe Tsai19058432019-02-27 21:46:29 -0800125
126 // Emit a .meta file for certain files.
127 var opts string
128 if d.annotateFor[filepath.ToSlash(relPath)] {
129 opts = ",annotate_code"
130 }
131
Joe Tsaid56458e2019-03-01 18:11:42 -0800132 // Determine which set of plugins to use.
133 plugins := "go"
134 if d.grpcPlugin {
135 plugins += ",gogrpc"
136 }
137
138 protoc(plugins, "-I"+filepath.Join(protoRoot, "src"), "-I"+srcDir, "--go_out=paths=source_relative"+opts+":"+dstDir, relPath)
Joe Tsai19058432019-02-27 21:46:29 -0800139 return nil
140 })
Joe Tsaid56458e2019-03-01 18:11:42 -0800141
142 // For directories in testdata, generate a test that links in all
143 // generated packages to ensure that it builds and initializes properly.
144 // This is done because "go build ./..." does not build sub-packages
145 // under testdata.
146 if filepath.Base(d.path) == "testdata" {
147 var imports []string
148 for sd := range subDirs {
149 imports = append(imports, fmt.Sprintf("_ %q", path.Join(modulePath, d.path, filepath.ToSlash(sd))))
150 }
151 sort.Strings(imports)
152
153 s := strings.Join(append(generatedPreamble, []string{
154 "package main",
155 "",
156 "import (" + strings.Join(imports, "\n") + ")",
157 }...), "\n")
158 b, err := format.Source([]byte(s))
159 check(err)
160 check(ioutil.WriteFile(filepath.Join(tmpDir, filepath.FromSlash(d.path+"/gen_test.go")), b, 0664))
161 }
Joe Tsai19058432019-02-27 21:46:29 -0800162 }
163
164 syncOutput(repoRoot, tmpDir)
165}
166
167func generateRemoteProtos() {
168 tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
169 check(err)
170 defer os.RemoveAll(tmpDir)
171
172 // Generate all remote proto files.
173 files := []struct{ prefix, path string }{
174 {"", "conformance/conformance.proto"},
175 {"src", "google/protobuf/any.proto"},
176 {"src", "google/protobuf/api.proto"},
177 {"src", "google/protobuf/descriptor.proto"},
178 {"src", "google/protobuf/duration.proto"},
179 {"src", "google/protobuf/empty.proto"},
180 {"src", "google/protobuf/field_mask.proto"},
181 {"src", "google/protobuf/source_context.proto"},
182 {"src", "google/protobuf/struct.proto"},
183 {"src", "google/protobuf/timestamp.proto"},
184 {"src", "google/protobuf/type.proto"},
185 {"src", "google/protobuf/wrappers.proto"},
186 {"src", "google/protobuf/compiler/plugin.proto"},
187 }
188 for _, f := range files {
Joe Tsaid56458e2019-03-01 18:11:42 -0800189 protoc("go", "-I"+filepath.Join(protoRoot, f.prefix), "--go_out="+tmpDir, f.path)
Joe Tsai19058432019-02-27 21:46:29 -0800190 }
191
Joe Tsai19058432019-02-27 21:46:29 -0800192 syncOutput(repoRoot, filepath.Join(tmpDir, modulePath))
193}
194
Joe Tsaid56458e2019-03-01 18:11:42 -0800195func protoc(plugins string, args ...string) {
Joe Tsai19058432019-02-27 21:46:29 -0800196 cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
197 cmd.Args = append(cmd.Args, args...)
Joe Tsaid56458e2019-03-01 18:11:42 -0800198 cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN="+plugins)
Joe Tsai19058432019-02-27 21:46:29 -0800199 cmd.Env = append(cmd.Env, "PROTOC_GEN_GO_ENABLE_REFLECT=1")
200 out, err := cmd.CombinedOutput()
201 if err != nil {
202 fmt.Printf("executing: %v\n%s\n", strings.Join(cmd.Args, " "), out)
203 }
204 check(err)
205}
206
Joe Tsai1af1de02019-03-01 16:12:32 -0800207// generateDescriptorFields generates an internal package for descriptor.proto.
208func generateDescriptorFields(gen *protogen.Plugin, file *protogen.File) {
209 if file.Desc.Path() != "google/protobuf/descriptor.proto" {
210 return
211 }
212
Joe Tsaid56458e2019-03-01 18:11:42 -0800213 importPath := modulePath + "/internal/descfield"
214 g := gen.NewGeneratedFile(importPath+"/field_gen.go", protogen.GoImportPath(importPath))
Joe Tsai1af1de02019-03-01 16:12:32 -0800215 for _, s := range generatedPreamble {
216 g.P(s)
217 }
218 g.P("// Package descfield contains constants for field numbers in descriptor.proto.")
219 g.P("package ", path.Base(importPath))
220 g.P("")
221
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.EnumType().FullName())
233 case protoreflect.MessageKind, protoreflect.GroupKind:
234 typeName = string(fd.MessageType().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
Joe Tsai19058432019-02-27 21:46:29 -0800245func syncOutput(dstDir, srcDir string) {
246 filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
247 if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") {
248 return nil
249 }
250 relPath, err := filepath.Rel(srcDir, srcPath)
251 check(err)
252 dstPath := filepath.Join(dstDir, relPath)
253
254 if run {
255 fmt.Println("#", relPath)
256 b, err := ioutil.ReadFile(srcPath)
257 check(err)
258 check(os.MkdirAll(filepath.Dir(dstPath), 0775))
259 check(ioutil.WriteFile(dstPath, b, 0664))
260 } else {
261 cmd := exec.Command("diff", dstPath, srcPath, "-N", "-u")
262 cmd.Stdout = os.Stdout
263 cmd.Run()
264 }
265 return nil
266 })
267}
268
269func check(err error) {
270 if err != nil {
271 panic(err)
272 }
273}