blob: c2bbac368977727f9bd856447166cabeb92ee10d [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"
Joe Tsaice07f392019-03-18 16:25:54 -070024 "github.com/golang/protobuf/v2/internal/detrand"
Joe Tsai19058432019-02-27 21:46:29 -080025 "github.com/golang/protobuf/v2/protogen"
Joe Tsai1af1de02019-03-01 16:12:32 -080026 "github.com/golang/protobuf/v2/reflect/protoreflect"
Joe Tsai19058432019-02-27 21:46:29 -080027)
28
29func init() {
30 // When the environment variable RUN_AS_PROTOC_PLUGIN is set,
31 // we skip running main and instead act as a protoc plugin.
32 // This allows the binary to pass itself to protoc.
Joe Tsaid56458e2019-03-01 18:11:42 -080033 if plugins := os.Getenv("RUN_AS_PROTOC_PLUGIN"); plugins != "" {
Joe Tsaice07f392019-03-18 16:25:54 -070034 // Disable deliberate output instability for generated files.
35 // This is reasonable since we fully control the output.
36 detrand.Disable()
37
Joe Tsai19058432019-02-27 21:46:29 -080038 protogen.Run(nil, func(gen *protogen.Plugin) error {
Joe Tsaid56458e2019-03-01 18:11:42 -080039 for _, plugin := range strings.Split(plugins, ",") {
40 for _, file := range gen.Files {
41 if file.Generate {
42 switch plugin {
43 case "go":
44 gengo.GenerateFile(gen, file)
45 generateDescriptorFields(gen, file)
46 case "gogrpc":
47 gengogrpc.GenerateFile(gen, file)
48 }
49 }
Joe Tsai19058432019-02-27 21:46:29 -080050 }
51 }
52 return nil
53 })
54 os.Exit(0)
55 }
56}
57
58var (
Joe Tsaid56458e2019-03-01 18:11:42 -080059 run bool
60 protoRoot string
61 repoRoot string
62 modulePath string
Joe Tsai1af1de02019-03-01 16:12:32 -080063
64 generatedPreamble = []string{
65 "// Copyright 2019 The Go Authors. All rights reserved.",
66 "// Use of this source code is governed by a BSD-style.",
67 "// license that can be found in the LICENSE file.",
68 "",
69 "// Code generated by generate-protos. DO NOT EDIT.",
70 "",
71 }
Joe Tsai19058432019-02-27 21:46:29 -080072)
73
74func main() {
75 flag.BoolVar(&run, "execute", false, "Write generated files to destination.")
76 flag.StringVar(&protoRoot, "protoroot", os.Getenv("PROTOBUF_ROOT"), "The root of the protobuf source tree.")
77 flag.Parse()
78 if protoRoot == "" {
79 panic("protobuf source root is not set")
80 }
81
82 // Determine repository root path.
83 out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
84 check(err)
85 repoRoot = strings.TrimSpace(string(out))
86
Joe Tsaid56458e2019-03-01 18:11:42 -080087 // Determine the module path.
88 cmd := exec.Command("go", "list", "-m", "-f", "{{.Path}}")
89 cmd.Dir = repoRoot
90 out, err = cmd.CombinedOutput()
91 check(err)
92 modulePath = strings.TrimSpace(string(out))
93
Joe Tsai19058432019-02-27 21:46:29 -080094 generateLocalProtos()
95 generateRemoteProtos()
96}
97
98func generateLocalProtos() {
99 tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
100 check(err)
101 defer os.RemoveAll(tmpDir)
102
103 // Generate all local proto files (except version-locked files).
104 dirs := []struct {
105 path string
Joe Tsaid56458e2019-03-01 18:11:42 -0800106 grpcPlugin bool
Joe Tsai19058432019-02-27 21:46:29 -0800107 annotateFor map[string]bool
108 }{
Joe Tsaid56458e2019-03-01 18:11:42 -0800109 {path: "cmd/protoc-gen-go/testdata", annotateFor: map[string]bool{"annotations/annotations.proto": true}},
110 {path: "cmd/protoc-gen-go-grpc/testdata", grpcPlugin: true},
111 {path: "internal/testprotos"},
112 {path: "encoding/testprotos"},
113 {path: "reflect/protoregistry/testprotos"},
Joe Tsai19058432019-02-27 21:46:29 -0800114 }
115 semVerRx := regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+`)
116 for _, d := range dirs {
Joe Tsaid56458e2019-03-01 18:11:42 -0800117 subDirs := map[string]bool{}
118
Joe Tsai19058432019-02-27 21:46:29 -0800119 dstDir := filepath.Join(tmpDir, filepath.FromSlash(d.path))
120 check(os.MkdirAll(dstDir, 0775))
121
122 srcDir := filepath.Join(repoRoot, filepath.FromSlash(d.path))
123 filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
124 if !strings.HasSuffix(srcPath, ".proto") || semVerRx.MatchString(srcPath) {
125 return nil
126 }
127 relPath, err := filepath.Rel(srcDir, srcPath)
128 check(err)
Joe Tsaid56458e2019-03-01 18:11:42 -0800129 subDirs[filepath.Dir(relPath)] = true
Joe Tsai19058432019-02-27 21:46:29 -0800130
131 // Emit a .meta file for certain files.
132 var opts string
133 if d.annotateFor[filepath.ToSlash(relPath)] {
134 opts = ",annotate_code"
135 }
136
Joe Tsaid56458e2019-03-01 18:11:42 -0800137 // Determine which set of plugins to use.
138 plugins := "go"
139 if d.grpcPlugin {
140 plugins += ",gogrpc"
141 }
142
143 protoc(plugins, "-I"+filepath.Join(protoRoot, "src"), "-I"+srcDir, "--go_out=paths=source_relative"+opts+":"+dstDir, relPath)
Joe Tsai19058432019-02-27 21:46:29 -0800144 return nil
145 })
Joe Tsaid56458e2019-03-01 18:11:42 -0800146
147 // For directories in testdata, generate a test that links in all
148 // generated packages to ensure that it builds and initializes properly.
149 // This is done because "go build ./..." does not build sub-packages
150 // under testdata.
151 if filepath.Base(d.path) == "testdata" {
152 var imports []string
153 for sd := range subDirs {
154 imports = append(imports, fmt.Sprintf("_ %q", path.Join(modulePath, d.path, filepath.ToSlash(sd))))
155 }
156 sort.Strings(imports)
157
158 s := strings.Join(append(generatedPreamble, []string{
159 "package main",
160 "",
161 "import (" + strings.Join(imports, "\n") + ")",
162 }...), "\n")
163 b, err := format.Source([]byte(s))
164 check(err)
165 check(ioutil.WriteFile(filepath.Join(tmpDir, filepath.FromSlash(d.path+"/gen_test.go")), b, 0664))
166 }
Joe Tsai19058432019-02-27 21:46:29 -0800167 }
168
169 syncOutput(repoRoot, tmpDir)
170}
171
172func generateRemoteProtos() {
173 tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
174 check(err)
175 defer os.RemoveAll(tmpDir)
176
177 // Generate all remote proto files.
178 files := []struct{ prefix, path string }{
179 {"", "conformance/conformance.proto"},
180 {"src", "google/protobuf/any.proto"},
181 {"src", "google/protobuf/api.proto"},
182 {"src", "google/protobuf/descriptor.proto"},
183 {"src", "google/protobuf/duration.proto"},
184 {"src", "google/protobuf/empty.proto"},
185 {"src", "google/protobuf/field_mask.proto"},
186 {"src", "google/protobuf/source_context.proto"},
187 {"src", "google/protobuf/struct.proto"},
188 {"src", "google/protobuf/timestamp.proto"},
189 {"src", "google/protobuf/type.proto"},
190 {"src", "google/protobuf/wrappers.proto"},
191 {"src", "google/protobuf/compiler/plugin.proto"},
192 }
193 for _, f := range files {
Joe Tsaid56458e2019-03-01 18:11:42 -0800194 protoc("go", "-I"+filepath.Join(protoRoot, f.prefix), "--go_out="+tmpDir, f.path)
Joe Tsai19058432019-02-27 21:46:29 -0800195 }
196
Joe Tsai19058432019-02-27 21:46:29 -0800197 syncOutput(repoRoot, filepath.Join(tmpDir, modulePath))
198}
199
Joe Tsaid56458e2019-03-01 18:11:42 -0800200func protoc(plugins string, args ...string) {
Joe Tsai19058432019-02-27 21:46:29 -0800201 cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
202 cmd.Args = append(cmd.Args, args...)
Joe Tsaid56458e2019-03-01 18:11:42 -0800203 cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN="+plugins)
Joe Tsai19058432019-02-27 21:46:29 -0800204 out, err := cmd.CombinedOutput()
205 if err != nil {
206 fmt.Printf("executing: %v\n%s\n", strings.Join(cmd.Args, " "), out)
207 }
208 check(err)
209}
210
Joe Tsai1af1de02019-03-01 16:12:32 -0800211// generateDescriptorFields generates an internal package for descriptor.proto.
212func generateDescriptorFields(gen *protogen.Plugin, file *protogen.File) {
213 if file.Desc.Path() != "google/protobuf/descriptor.proto" {
214 return
215 }
216
Joe Tsaid56458e2019-03-01 18:11:42 -0800217 importPath := modulePath + "/internal/descfield"
218 g := gen.NewGeneratedFile(importPath+"/field_gen.go", protogen.GoImportPath(importPath))
Joe Tsai1af1de02019-03-01 16:12:32 -0800219 for _, s := range generatedPreamble {
220 g.P(s)
221 }
222 g.P("// Package descfield contains constants for field numbers in descriptor.proto.")
223 g.P("package ", path.Base(importPath))
224 g.P("")
225
226 var processMessages func([]*protogen.Message)
227 processMessages = func(messages []*protogen.Message) {
228 for _, message := range messages {
229 g.P("// Field numbers for ", message.Desc.FullName(), ".")
230 g.P("const (")
231 for _, field := range message.Fields {
232 fd := field.Desc
233 typeName := fd.Kind().String()
234 switch fd.Kind() {
235 case protoreflect.EnumKind:
236 typeName = string(fd.EnumType().FullName())
237 case protoreflect.MessageKind, protoreflect.GroupKind:
238 typeName = string(fd.MessageType().FullName())
239 }
240 g.P(message.GoIdent.GoName, "_", field.GoName, "=", fd.Number(), "// ", fd.Cardinality(), " ", typeName)
241 }
242 g.P(")")
243 processMessages(message.Messages)
244 }
245 }
246 processMessages(file.Messages)
247}
248
Joe Tsai19058432019-02-27 21:46:29 -0800249func syncOutput(dstDir, srcDir string) {
250 filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
251 if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") {
252 return nil
253 }
254 relPath, err := filepath.Rel(srcDir, srcPath)
255 check(err)
256 dstPath := filepath.Join(dstDir, relPath)
257
258 if run {
259 fmt.Println("#", relPath)
260 b, err := ioutil.ReadFile(srcPath)
261 check(err)
262 check(os.MkdirAll(filepath.Dir(dstPath), 0775))
263 check(ioutil.WriteFile(dstPath, b, 0664))
264 } else {
265 cmd := exec.Command("diff", dstPath, srcPath, "-N", "-u")
266 cmd.Stdout = os.Stdout
267 cmd.Run()
268 }
269 return nil
270 })
271}
272
273func check(err error) {
274 if err != nil {
275 panic(err)
276 }
277}