blob: cf7c688dea17f925d284902c60e1e9b349faf12f [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"
12 "io/ioutil"
13 "os"
14 "os/exec"
Joe Tsai1af1de02019-03-01 16:12:32 -080015 "path"
Joe Tsai19058432019-02-27 21:46:29 -080016 "path/filepath"
17 "regexp"
18 "strings"
19
20 gengogrpc "github.com/golang/protobuf/v2/cmd/protoc-gen-go-grpc/internal_gengogrpc"
21 gengo "github.com/golang/protobuf/v2/cmd/protoc-gen-go/internal_gengo"
22 "github.com/golang/protobuf/v2/protogen"
Joe Tsai1af1de02019-03-01 16:12:32 -080023 "github.com/golang/protobuf/v2/reflect/protoreflect"
Joe Tsai19058432019-02-27 21:46:29 -080024)
25
26func init() {
27 // When the environment variable RUN_AS_PROTOC_PLUGIN is set,
28 // we skip running main and instead act as a protoc plugin.
29 // This allows the binary to pass itself to protoc.
30 if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" {
31 protogen.Run(nil, func(gen *protogen.Plugin) error {
32 for _, file := range gen.Files {
33 if file.Generate {
34 gengo.GenerateFile(gen, file)
35 gengogrpc.GenerateFile(gen, file)
Joe Tsai1af1de02019-03-01 16:12:32 -080036 generateDescriptorFields(gen, file)
Joe Tsai19058432019-02-27 21:46:29 -080037 }
38 }
39 return nil
40 })
41 os.Exit(0)
42 }
43}
44
45var (
46 run bool
47 protoRoot string
48 repoRoot string
Joe Tsai1af1de02019-03-01 16:12:32 -080049
50 generatedPreamble = []string{
51 "// Copyright 2019 The Go Authors. All rights reserved.",
52 "// Use of this source code is governed by a BSD-style.",
53 "// license that can be found in the LICENSE file.",
54 "",
55 "// Code generated by generate-protos. DO NOT EDIT.",
56 "",
57 }
Joe Tsai19058432019-02-27 21:46:29 -080058)
59
60func main() {
61 flag.BoolVar(&run, "execute", false, "Write generated files to destination.")
62 flag.StringVar(&protoRoot, "protoroot", os.Getenv("PROTOBUF_ROOT"), "The root of the protobuf source tree.")
63 flag.Parse()
64 if protoRoot == "" {
65 panic("protobuf source root is not set")
66 }
67
68 // Determine repository root path.
69 out, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
70 check(err)
71 repoRoot = strings.TrimSpace(string(out))
72
73 generateLocalProtos()
74 generateRemoteProtos()
75}
76
77func generateLocalProtos() {
78 tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
79 check(err)
80 defer os.RemoveAll(tmpDir)
81
82 // Generate all local proto files (except version-locked files).
83 dirs := []struct {
84 path string
85 annotateFor map[string]bool
86 }{
87 {"cmd/protoc-gen-go/testdata", map[string]bool{"annotations/annotations.proto": true}},
88 {"cmd/protoc-gen-go-grpc/testdata", nil},
89 {"internal/testprotos", nil},
90 {"encoding/testprotos", nil},
91 {"reflect/protoregistry/testprotos", nil},
92 }
93 semVerRx := regexp.MustCompile(`v[0-9]+\.[0-9]+\.[0-9]+`)
94 for _, d := range dirs {
95 dstDir := filepath.Join(tmpDir, filepath.FromSlash(d.path))
96 check(os.MkdirAll(dstDir, 0775))
97
98 srcDir := filepath.Join(repoRoot, filepath.FromSlash(d.path))
99 filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
100 if !strings.HasSuffix(srcPath, ".proto") || semVerRx.MatchString(srcPath) {
101 return nil
102 }
103 relPath, err := filepath.Rel(srcDir, srcPath)
104 check(err)
105
106 // Emit a .meta file for certain files.
107 var opts string
108 if d.annotateFor[filepath.ToSlash(relPath)] {
109 opts = ",annotate_code"
110 }
111
112 protoc("-I"+filepath.Join(protoRoot, "src"), "-I"+srcDir, "--go_out=paths=source_relative"+opts+":"+dstDir, relPath)
113 return nil
114 })
115 }
116
117 syncOutput(repoRoot, tmpDir)
118}
119
120func generateRemoteProtos() {
121 tmpDir, err := ioutil.TempDir(repoRoot, "tmp")
122 check(err)
123 defer os.RemoveAll(tmpDir)
124
125 // Generate all remote proto files.
126 files := []struct{ prefix, path string }{
127 {"", "conformance/conformance.proto"},
128 {"src", "google/protobuf/any.proto"},
129 {"src", "google/protobuf/api.proto"},
130 {"src", "google/protobuf/descriptor.proto"},
131 {"src", "google/protobuf/duration.proto"},
132 {"src", "google/protobuf/empty.proto"},
133 {"src", "google/protobuf/field_mask.proto"},
134 {"src", "google/protobuf/source_context.proto"},
135 {"src", "google/protobuf/struct.proto"},
136 {"src", "google/protobuf/timestamp.proto"},
137 {"src", "google/protobuf/type.proto"},
138 {"src", "google/protobuf/wrappers.proto"},
139 {"src", "google/protobuf/compiler/plugin.proto"},
140 }
141 for _, f := range files {
142 protoc("-I"+filepath.Join(protoRoot, f.prefix), "--go_out="+tmpDir, f.path)
143 }
144
145 // Determine the module path.
146 cmd := exec.Command("go", "list", "-m", "-f", "{{.Path}}")
147 cmd.Dir = repoRoot
148 out, err := cmd.CombinedOutput()
149 check(err)
150 modulePath := strings.TrimSpace(string(out))
151
152 syncOutput(repoRoot, filepath.Join(tmpDir, modulePath))
153}
154
155func protoc(args ...string) {
156 cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
157 cmd.Args = append(cmd.Args, args...)
158 cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1")
159 cmd.Env = append(cmd.Env, "PROTOC_GEN_GO_ENABLE_REFLECT=1")
160 out, err := cmd.CombinedOutput()
161 if err != nil {
162 fmt.Printf("executing: %v\n%s\n", strings.Join(cmd.Args, " "), out)
163 }
164 check(err)
165}
166
Joe Tsai1af1de02019-03-01 16:12:32 -0800167// generateDescriptorFields generates an internal package for descriptor.proto.
168func generateDescriptorFields(gen *protogen.Plugin, file *protogen.File) {
169 if file.Desc.Path() != "google/protobuf/descriptor.proto" {
170 return
171 }
172
173 const importPath = "github.com/golang/protobuf/v2/internal/descfield"
174 g := gen.NewGeneratedFile(importPath+"/field_gen.go", importPath)
175 for _, s := range generatedPreamble {
176 g.P(s)
177 }
178 g.P("// Package descfield contains constants for field numbers in descriptor.proto.")
179 g.P("package ", path.Base(importPath))
180 g.P("")
181
182 var processMessages func([]*protogen.Message)
183 processMessages = func(messages []*protogen.Message) {
184 for _, message := range messages {
185 g.P("// Field numbers for ", message.Desc.FullName(), ".")
186 g.P("const (")
187 for _, field := range message.Fields {
188 fd := field.Desc
189 typeName := fd.Kind().String()
190 switch fd.Kind() {
191 case protoreflect.EnumKind:
192 typeName = string(fd.EnumType().FullName())
193 case protoreflect.MessageKind, protoreflect.GroupKind:
194 typeName = string(fd.MessageType().FullName())
195 }
196 g.P(message.GoIdent.GoName, "_", field.GoName, "=", fd.Number(), "// ", fd.Cardinality(), " ", typeName)
197 }
198 g.P(")")
199 processMessages(message.Messages)
200 }
201 }
202 processMessages(file.Messages)
203}
204
Joe Tsai19058432019-02-27 21:46:29 -0800205func syncOutput(dstDir, srcDir string) {
206 filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, _ error) error {
207 if !strings.HasSuffix(srcPath, ".go") && !strings.HasSuffix(srcPath, ".meta") {
208 return nil
209 }
210 relPath, err := filepath.Rel(srcDir, srcPath)
211 check(err)
212 dstPath := filepath.Join(dstDir, relPath)
213
214 if run {
215 fmt.Println("#", relPath)
216 b, err := ioutil.ReadFile(srcPath)
217 check(err)
218 check(os.MkdirAll(filepath.Dir(dstPath), 0775))
219 check(ioutil.WriteFile(dstPath, b, 0664))
220 } else {
221 cmd := exec.Command("diff", dstPath, srcPath, "-N", "-u")
222 cmd.Stdout = os.Stdout
223 cmd.Run()
224 }
225 return nil
226 })
227}
228
229func check(err error) {
230 if err != nil {
231 panic(err)
232 }
233}