blob: 1f24a1a12e6e7bac940ed99ad0a5fb2878de75a2 [file] [log] [blame]
Damien Neil220c2022018-08-15 11:24:18 -07001// Copyright 2018 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// The protoc-gen-go binary is a protoc plugin to generate a Go protocol
6// buffer package.
7package main
8
9import (
Damien Neil7779e052018-09-07 14:14:06 -070010 "bytes"
11 "compress/gzip"
12 "crypto/sha256"
13 "encoding/hex"
Damien Neil3cf6e622018-09-11 13:53:14 -070014 "flag"
Damien Neil7779e052018-09-07 14:14:06 -070015 "fmt"
16 "strconv"
Damien Neilcab8dfe2018-09-06 14:51:28 -070017 "strings"
Damien Neil7779e052018-09-07 14:14:06 -070018
19 "github.com/golang/protobuf/proto"
20 descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
Damien Neil220c2022018-08-15 11:24:18 -070021 "google.golang.org/proto/protogen"
Damien Neil46abb572018-09-07 12:45:37 -070022 "google.golang.org/proto/reflect/protoreflect"
Damien Neil220c2022018-08-15 11:24:18 -070023)
24
Damien Neil46abb572018-09-07 12:45:37 -070025const protoPackage = "github.com/golang/protobuf/proto"
26
Damien Neil220c2022018-08-15 11:24:18 -070027func main() {
Damien Neil3cf6e622018-09-11 13:53:14 -070028 var flags flag.FlagSet
29 // TODO: Decide what to do for backwards compatibility with plugins=grpc.
30 flags.String("plugins", "", "")
31 opts := &protogen.Options{
32 ParamFunc: flags.Set,
33 }
34 protogen.Run(opts, func(gen *protogen.Plugin) error {
Damien Neil220c2022018-08-15 11:24:18 -070035 for _, f := range gen.Files {
36 if !f.Generate {
37 continue
38 }
39 genFile(gen, f)
40 }
41 return nil
42 })
43}
44
Damien Neilcab8dfe2018-09-06 14:51:28 -070045type File struct {
46 *protogen.File
Damien Neil46abb572018-09-07 12:45:37 -070047 locationMap map[string][]*descpb.SourceCodeInfo_Location
48 descriptorVar string // var containing the gzipped FileDescriptorProto
49 init []string
Damien Neilcab8dfe2018-09-06 14:51:28 -070050}
51
52func genFile(gen *protogen.Plugin, file *protogen.File) {
53 f := &File{
54 File: file,
55 locationMap: make(map[string][]*descpb.SourceCodeInfo_Location),
56 }
57 for _, loc := range file.Proto.GetSourceCodeInfo().GetLocation() {
58 key := pathKey(loc.Path)
59 f.locationMap[key] = append(f.locationMap[key], loc)
60 }
61
Damien Neil46abb572018-09-07 12:45:37 -070062 // Determine the name of the var holding the file descriptor:
63 //
64 // fileDescriptor_<hash of filename>
65 filenameHash := sha256.Sum256([]byte(f.Desc.Path()))
66 f.descriptorVar = fmt.Sprintf("fileDescriptor_%s", hex.EncodeToString(filenameHash[:8]))
67
Damien Neil082ce922018-09-06 10:23:53 -070068 g := gen.NewGeneratedFile(f.GeneratedFilenamePrefix+".pb.go", f.GoImportPath)
Damien Neil220c2022018-08-15 11:24:18 -070069 g.P("// Code generated by protoc-gen-go. DO NOT EDIT.")
Damien Neilabc6fc12018-08-23 14:39:30 -070070 g.P("// source: ", f.Desc.Path())
Damien Neil220c2022018-08-15 11:24:18 -070071 g.P()
Damien Neilcab8dfe2018-09-06 14:51:28 -070072 const filePackageField = 2 // FileDescriptorProto.package
73 genComment(g, f, []int32{filePackageField})
74 g.P()
Damien Neil082ce922018-09-06 10:23:53 -070075 g.P("package ", f.GoPackageName)
Damien Neilc7d07d92018-08-22 13:46:02 -070076 g.P()
77
Damien Neil46abb572018-09-07 12:45:37 -070078 for _, enum := range f.Enums {
79 genEnum(gen, g, f, enum)
80 }
Damien Neilcab8dfe2018-09-06 14:51:28 -070081 for _, message := range f.Messages {
82 genMessage(gen, g, f, message)
Damien Neilc7d07d92018-08-22 13:46:02 -070083 }
Damien Neil220c2022018-08-15 11:24:18 -070084
Damien Neil46abb572018-09-07 12:45:37 -070085 if len(f.init) != 0 {
86 g.P("func init() {")
87 for _, s := range f.init {
88 g.P(s)
89 }
90 g.P("}")
91 g.P()
92 }
93
Damien Neil7779e052018-09-07 14:14:06 -070094 genFileDescriptor(gen, g, f)
95}
96
Damien Neilcab8dfe2018-09-06 14:51:28 -070097func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File) {
Damien Neil7779e052018-09-07 14:14:06 -070098 // Trim the source_code_info from the descriptor.
99 // Marshal and gzip it.
100 descProto := proto.Clone(f.Proto).(*descpb.FileDescriptorProto)
101 descProto.SourceCodeInfo = nil
102 b, err := proto.Marshal(descProto)
103 if err != nil {
104 gen.Error(err)
105 return
106 }
107 var buf bytes.Buffer
108 w, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
109 w.Write(b)
110 w.Close()
111 b = buf.Bytes()
112
Damien Neil46abb572018-09-07 12:45:37 -0700113 g.P("func init() { proto.RegisterFile(", strconv.Quote(f.Desc.Path()), ", ", f.descriptorVar, ") }")
Damien Neil7779e052018-09-07 14:14:06 -0700114 g.P()
Damien Neil46abb572018-09-07 12:45:37 -0700115 g.P("var ", f.descriptorVar, " = []byte{")
Damien Neil7779e052018-09-07 14:14:06 -0700116 g.P("// ", len(b), " bytes of a gzipped FileDescriptorProto")
117 for len(b) > 0 {
118 n := 16
119 if n > len(b) {
120 n = len(b)
121 }
122
123 s := ""
124 for _, c := range b[:n] {
125 s += fmt.Sprintf("0x%02x,", c)
126 }
127 g.P(s)
128
129 b = b[n:]
130 }
131 g.P("}")
132 g.P()
Damien Neil220c2022018-08-15 11:24:18 -0700133}
Damien Neilc7d07d92018-08-22 13:46:02 -0700134
Damien Neil46abb572018-09-07 12:45:37 -0700135func genEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, enum *protogen.Enum) {
136 genComment(g, f, enum.Path)
137 // TODO: deprecation
138 g.P("type ", enum.GoIdent, " int32")
139 g.P("const (")
140 for _, value := range enum.Values {
141 genComment(g, f, value.Path)
142 // TODO: deprecation
143 g.P(value.GoIdent, " ", enum.GoIdent, " = ", value.Desc.Number())
144 }
145 g.P(")")
146 g.P()
147 nameMap := enum.GoIdent.GoName + "_name"
148 g.P("var ", nameMap, " = map[int32]string{")
149 generated := make(map[protoreflect.EnumNumber]bool)
150 for _, value := range enum.Values {
151 duplicate := ""
152 if _, present := generated[value.Desc.Number()]; present {
153 duplicate = "// Duplicate value: "
154 }
155 g.P(duplicate, value.Desc.Number(), ": ", strconv.Quote(string(value.Desc.Name())), ",")
156 generated[value.Desc.Number()] = true
157 }
158 g.P("}")
159 g.P()
160 valueMap := enum.GoIdent.GoName + "_value"
161 g.P("var ", valueMap, " = map[string]int32{")
162 for _, value := range enum.Values {
163 g.P(strconv.Quote(string(value.Desc.Name())), ": ", value.Desc.Number(), ",")
164 }
165 g.P("}")
166 g.P()
167 if enum.Desc.Syntax() != protoreflect.Proto3 {
168 g.P("func (x ", enum.GoIdent, ") Enum() *", enum.GoIdent, " {")
169 g.P("p := new(", enum.GoIdent, ")")
170 g.P("*p = x")
171 g.P("return p")
172 g.P("}")
173 g.P()
174 }
175 g.P("func (x ", enum.GoIdent, ") String() string {")
176 g.P("return ", protogen.GoIdent{GoImportPath: protoPackage, GoName: "EnumName"}, "(", enum.GoIdent, "_name, int32(x))")
177 g.P("}")
178 g.P()
179
180 if enum.Desc.Syntax() != protoreflect.Proto3 {
181 g.P("func (x *", enum.GoIdent, ") UnmarshalJSON(data []byte) error {")
182 g.P("value, err := ", protogen.GoIdent{GoImportPath: protoPackage, GoName: "UnmarshalJSONEnum"}, "(", enum.GoIdent, `_value, data, "`, enum.GoIdent, `")`)
183 g.P("if err != nil {")
184 g.P("return err")
185 g.P("}")
186 g.P("*x = ", enum.GoIdent, "(value)")
187 g.P("return nil")
188 g.P("}")
189 g.P()
190 }
191
192 var indexes []string
193 for i := 1; i < len(enum.Path); i += 2 {
194 indexes = append(indexes, strconv.Itoa(int(enum.Path[i])))
195 }
196 g.P("func (", enum.GoIdent, ") EnumDescriptor() ([]byte, []int) {")
197 g.P("return ", f.descriptorVar, ", []int{", strings.Join(indexes, ","), "}")
198 g.P("}")
199 g.P()
200
201 genWellKnownType(g, enum.GoIdent, enum.Desc)
202
Damien Neil46abb572018-09-07 12:45:37 -0700203 f.init = append(f.init, fmt.Sprintf("%s(%q, %s, %s)",
204 g.QualifiedGoIdent(protogen.GoIdent{
205 GoImportPath: protoPackage,
206 GoName: "RegisterEnum",
207 }),
Damien Neil658051b2018-09-10 12:26:21 -0700208 enumRegistryName(enum), nameMap, valueMap,
Damien Neil46abb572018-09-07 12:45:37 -0700209 ))
210}
211
Damien Neil658051b2018-09-10 12:26:21 -0700212// enumRegistryName returns the name used to register an enum with the proto
213// package registry.
214//
215// Confusingly, this is <proto_package>.<go_ident>. This probably should have
216// been the full name of the proto enum type instead, but changing it at this
217// point would require thought.
218func enumRegistryName(enum *protogen.Enum) string {
219 // Find the FileDescriptor for this enum.
220 var desc protoreflect.Descriptor = enum.Desc
221 for {
222 p, ok := desc.Parent()
223 if !ok {
224 break
225 }
226 desc = p
227 }
228 fdesc := desc.(protoreflect.FileDescriptor)
229 return string(fdesc.Package()) + "." + enum.GoIdent.GoName
230}
231
Damien Neilcab8dfe2018-09-06 14:51:28 -0700232func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, message *protogen.Message) {
Damien Neil658051b2018-09-10 12:26:21 -0700233 for _, e := range message.Enums {
234 genEnum(gen, g, f, e)
Damien Neil46abb572018-09-07 12:45:37 -0700235 }
236
Damien Neilcab8dfe2018-09-06 14:51:28 -0700237 genComment(g, f, message.Path)
Damien Neil658051b2018-09-10 12:26:21 -0700238 // TODO: deprecation
Damien Neilcab8dfe2018-09-06 14:51:28 -0700239 g.P("type ", message.GoIdent, " struct {")
Damien Neil658051b2018-09-10 12:26:21 -0700240 for _, field := range message.Fields {
241 if field.Desc.OneofType() != nil {
242 // TODO oneofs
243 continue
244 }
245 genComment(g, f, field.Path)
246 g.P(field.GoIdent, " ", fieldGoType(g, field), fmt.Sprintf(" `protobuf:%q json:%q`", fieldProtobufTag(field), fieldJSONTag(field)))
247 }
248 g.P("XXX_NoUnkeyedLiteral struct{} `json:\"-\"`")
249 // TODO XXX_InternalExtensions
250 g.P("XXX_unrecognized []byte `json:\"-\"`")
251 g.P("XXX_sizecache int32 `json:\"-\"`")
Damien Neilc7d07d92018-08-22 13:46:02 -0700252 g.P("}")
253 g.P()
254
Damien Neilcab8dfe2018-09-06 14:51:28 -0700255 for _, nested := range message.Messages {
256 genMessage(gen, g, f, nested)
Damien Neilc7d07d92018-08-22 13:46:02 -0700257 }
258}
Damien Neilcab8dfe2018-09-06 14:51:28 -0700259
Damien Neil658051b2018-09-10 12:26:21 -0700260func fieldGoType(g *protogen.GeneratedFile, field *protogen.Field) string {
261 // TODO: map types
262 var typ string
263 switch field.Desc.Kind() {
264 case protoreflect.BoolKind:
265 typ = "bool"
266 case protoreflect.EnumKind:
267 typ = g.QualifiedGoIdent(field.EnumType.GoIdent)
268 case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
269 typ = "int32"
270 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
271 typ = "uint32"
272 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
273 typ = "int64"
274 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
275 typ = "uint64"
276 case protoreflect.FloatKind:
277 typ = "float32"
278 case protoreflect.DoubleKind:
279 typ = "float64"
280 case protoreflect.StringKind:
281 typ = "string"
282 case protoreflect.BytesKind:
283 typ = "[]byte"
284 case protoreflect.MessageKind, protoreflect.GroupKind:
285 typ = "*" + g.QualifiedGoIdent(field.MessageType.GoIdent)
286 }
287 if field.Desc.Cardinality() == protoreflect.Repeated {
288 return "[]" + typ
289 }
290 if field.Desc.Syntax() == protoreflect.Proto3 {
291 return typ
292 }
293 if field.Desc.OneofType() != nil {
294 return typ
295 }
296 nonPointerKinds := map[protoreflect.Kind]bool{
297 protoreflect.GroupKind: true,
298 protoreflect.MessageKind: true,
299 protoreflect.BytesKind: true,
300 }
301 if !nonPointerKinds[field.Desc.Kind()] {
302 return "*" + typ
303 }
304 return typ
305}
306
307func fieldProtobufTag(field *protogen.Field) string {
308 var tag []string
309 // wire type
310 tag = append(tag, wireTypes[field.Desc.Kind()])
311 // field number
312 tag = append(tag, strconv.Itoa(int(field.Desc.Number())))
313 // cardinality
314 switch field.Desc.Cardinality() {
315 case protoreflect.Optional:
316 tag = append(tag, "opt")
317 case protoreflect.Required:
318 tag = append(tag, "req")
319 case protoreflect.Repeated:
320 tag = append(tag, "rep")
321 }
322 // TODO: default values
323 // TODO: packed
324 // name
325 name := string(field.Desc.Name())
326 if field.Desc.Kind() == protoreflect.GroupKind {
327 // The name of the FieldDescriptor for a group field is
328 // lowercased. To find the original capitalization, we
329 // look in the field's MessageType.
330 name = string(field.MessageType.Desc.Name())
331 }
332 tag = append(tag, "name="+name)
333 // JSON name
334 if jsonName := field.Desc.JSONName(); jsonName != "" && jsonName != name {
335 tag = append(tag, "json="+jsonName)
336 }
337 // proto3
338 if field.Desc.Syntax() == protoreflect.Proto3 {
339 tag = append(tag, "proto3")
340 }
341 // enum
342 if field.Desc.Kind() == protoreflect.EnumKind {
343 tag = append(tag, "enum="+enumRegistryName(field.EnumType))
344 }
345 // oneof
346 if field.Desc.OneofType() != nil {
347 tag = append(tag, "oneof")
348 }
349 return strings.Join(tag, ",")
350}
351
352var wireTypes = map[protoreflect.Kind]string{
353 protoreflect.BoolKind: "varint",
354 protoreflect.EnumKind: "varint",
355 protoreflect.Int32Kind: "varint",
356 protoreflect.Sint32Kind: "zigzag32",
357 protoreflect.Uint32Kind: "varint",
358 protoreflect.Int64Kind: "varint",
359 protoreflect.Sint64Kind: "zigzag64",
360 protoreflect.Uint64Kind: "varint",
361 protoreflect.Sfixed32Kind: "fixed32",
362 protoreflect.Fixed32Kind: "fixed32",
363 protoreflect.FloatKind: "fixed32",
364 protoreflect.Sfixed64Kind: "fixed64",
365 protoreflect.Fixed64Kind: "fixed64",
366 protoreflect.DoubleKind: "fixed64",
367 protoreflect.StringKind: "bytes",
368 protoreflect.BytesKind: "bytes",
369 protoreflect.MessageKind: "bytes",
370 protoreflect.GroupKind: "group",
371}
372
373func fieldJSONTag(field *protogen.Field) string {
374 return string(field.Desc.Name()) + ",omitempty"
375}
376
Damien Neilcab8dfe2018-09-06 14:51:28 -0700377func genComment(g *protogen.GeneratedFile, f *File, path []int32) {
378 for _, loc := range f.locationMap[pathKey(path)] {
379 if loc.LeadingComments == nil {
380 continue
381 }
382 for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") {
383 g.P("//", line)
384 }
385 return
386 }
387}
388
389// pathKey converts a location path to a string suitable for use as a map key.
390func pathKey(path []int32) string {
391 var buf []byte
392 for i, x := range path {
393 if i != 0 {
394 buf = append(buf, ',')
395 }
396 buf = strconv.AppendInt(buf, int64(x), 10)
397 }
398 return string(buf)
399}
Damien Neil46abb572018-09-07 12:45:37 -0700400
401func genWellKnownType(g *protogen.GeneratedFile, ident protogen.GoIdent, desc protoreflect.Descriptor) {
402 if wellKnownTypes[desc.FullName()] {
403 g.P("func (", ident, `) XXX_WellKnownType() string { return "`, desc.Name(), `" }`)
404 g.P()
405 }
406}
407
408// Names of messages and enums for which we will generate XXX_WellKnownType methods.
409var wellKnownTypes = map[protoreflect.FullName]bool{
410 "google.protobuf.NullValue": true,
411}