blob: 9213ea82270b8e5cc25c1444cc16b19f001daa88 [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 Neild4127922018-09-12 11:13:49 -070025// generatedCodeVersion indicates a version of the generated code.
26// It is incremented whenever an incompatibility between the generated code and
27// proto package is introduced; the generated code references
28// a constant, proto.ProtoPackageIsVersionN (where N is generatedCodeVersion).
29const generatedCodeVersion = 2
30
Damien Neil46abb572018-09-07 12:45:37 -070031const protoPackage = "github.com/golang/protobuf/proto"
32
Damien Neil220c2022018-08-15 11:24:18 -070033func main() {
Damien Neil3cf6e622018-09-11 13:53:14 -070034 var flags flag.FlagSet
35 // TODO: Decide what to do for backwards compatibility with plugins=grpc.
36 flags.String("plugins", "", "")
37 opts := &protogen.Options{
38 ParamFunc: flags.Set,
39 }
40 protogen.Run(opts, func(gen *protogen.Plugin) error {
Damien Neil220c2022018-08-15 11:24:18 -070041 for _, f := range gen.Files {
42 if !f.Generate {
43 continue
44 }
45 genFile(gen, f)
46 }
47 return nil
48 })
49}
50
Damien Neilcab8dfe2018-09-06 14:51:28 -070051type File struct {
52 *protogen.File
Damien Neil46abb572018-09-07 12:45:37 -070053 locationMap map[string][]*descpb.SourceCodeInfo_Location
54 descriptorVar string // var containing the gzipped FileDescriptorProto
55 init []string
Damien Neilcab8dfe2018-09-06 14:51:28 -070056}
57
58func genFile(gen *protogen.Plugin, file *protogen.File) {
59 f := &File{
60 File: file,
61 locationMap: make(map[string][]*descpb.SourceCodeInfo_Location),
62 }
63 for _, loc := range file.Proto.GetSourceCodeInfo().GetLocation() {
64 key := pathKey(loc.Path)
65 f.locationMap[key] = append(f.locationMap[key], loc)
66 }
67
Damien Neil46abb572018-09-07 12:45:37 -070068 // Determine the name of the var holding the file descriptor:
69 //
70 // fileDescriptor_<hash of filename>
71 filenameHash := sha256.Sum256([]byte(f.Desc.Path()))
72 f.descriptorVar = fmt.Sprintf("fileDescriptor_%s", hex.EncodeToString(filenameHash[:8]))
73
Damien Neil082ce922018-09-06 10:23:53 -070074 g := gen.NewGeneratedFile(f.GeneratedFilenamePrefix+".pb.go", f.GoImportPath)
Damien Neil220c2022018-08-15 11:24:18 -070075 g.P("// Code generated by protoc-gen-go. DO NOT EDIT.")
Damien Neilabc6fc12018-08-23 14:39:30 -070076 g.P("// source: ", f.Desc.Path())
Damien Neil220c2022018-08-15 11:24:18 -070077 g.P()
Damien Neilcab8dfe2018-09-06 14:51:28 -070078 const filePackageField = 2 // FileDescriptorProto.package
79 genComment(g, f, []int32{filePackageField})
80 g.P()
Damien Neil082ce922018-09-06 10:23:53 -070081 g.P("package ", f.GoPackageName)
Damien Neilc7d07d92018-08-22 13:46:02 -070082 g.P()
Damien Neild4127922018-09-12 11:13:49 -070083 g.P("// This is a compile-time assertion to ensure that this generated file")
84 g.P("// is compatible with the proto package it is being compiled against.")
85 g.P("// A compilation error at this line likely means your copy of the")
86 g.P("// proto package needs to be updated.")
87 g.P("const _ = ", protogen.GoIdent{
88 GoImportPath: protoPackage,
89 GoName: fmt.Sprintf("ProtoPackageIsVersion%d", generatedCodeVersion),
90 }, "// please upgrade the proto package")
91 g.P()
Damien Neilc7d07d92018-08-22 13:46:02 -070092
Damien Neil46abb572018-09-07 12:45:37 -070093 for _, enum := range f.Enums {
94 genEnum(gen, g, f, enum)
95 }
Damien Neilcab8dfe2018-09-06 14:51:28 -070096 for _, message := range f.Messages {
97 genMessage(gen, g, f, message)
Damien Neilc7d07d92018-08-22 13:46:02 -070098 }
Damien Neil220c2022018-08-15 11:24:18 -070099
Damien Neil46abb572018-09-07 12:45:37 -0700100 if len(f.init) != 0 {
101 g.P("func init() {")
102 for _, s := range f.init {
103 g.P(s)
104 }
105 g.P("}")
106 g.P()
107 }
108
Damien Neil7779e052018-09-07 14:14:06 -0700109 genFileDescriptor(gen, g, f)
110}
111
Damien Neilcab8dfe2018-09-06 14:51:28 -0700112func genFileDescriptor(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File) {
Damien Neil7779e052018-09-07 14:14:06 -0700113 // Trim the source_code_info from the descriptor.
114 // Marshal and gzip it.
115 descProto := proto.Clone(f.Proto).(*descpb.FileDescriptorProto)
116 descProto.SourceCodeInfo = nil
117 b, err := proto.Marshal(descProto)
118 if err != nil {
119 gen.Error(err)
120 return
121 }
122 var buf bytes.Buffer
123 w, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
124 w.Write(b)
125 w.Close()
126 b = buf.Bytes()
127
Damien Neil46abb572018-09-07 12:45:37 -0700128 g.P("func init() { proto.RegisterFile(", strconv.Quote(f.Desc.Path()), ", ", f.descriptorVar, ") }")
Damien Neil7779e052018-09-07 14:14:06 -0700129 g.P()
Damien Neil46abb572018-09-07 12:45:37 -0700130 g.P("var ", f.descriptorVar, " = []byte{")
Damien Neil7779e052018-09-07 14:14:06 -0700131 g.P("// ", len(b), " bytes of a gzipped FileDescriptorProto")
132 for len(b) > 0 {
133 n := 16
134 if n > len(b) {
135 n = len(b)
136 }
137
138 s := ""
139 for _, c := range b[:n] {
140 s += fmt.Sprintf("0x%02x,", c)
141 }
142 g.P(s)
143
144 b = b[n:]
145 }
146 g.P("}")
147 g.P()
Damien Neil220c2022018-08-15 11:24:18 -0700148}
Damien Neilc7d07d92018-08-22 13:46:02 -0700149
Damien Neil46abb572018-09-07 12:45:37 -0700150func genEnum(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, enum *protogen.Enum) {
151 genComment(g, f, enum.Path)
152 // TODO: deprecation
153 g.P("type ", enum.GoIdent, " int32")
154 g.P("const (")
155 for _, value := range enum.Values {
156 genComment(g, f, value.Path)
157 // TODO: deprecation
158 g.P(value.GoIdent, " ", enum.GoIdent, " = ", value.Desc.Number())
159 }
160 g.P(")")
161 g.P()
162 nameMap := enum.GoIdent.GoName + "_name"
163 g.P("var ", nameMap, " = map[int32]string{")
164 generated := make(map[protoreflect.EnumNumber]bool)
165 for _, value := range enum.Values {
166 duplicate := ""
167 if _, present := generated[value.Desc.Number()]; present {
168 duplicate = "// Duplicate value: "
169 }
170 g.P(duplicate, value.Desc.Number(), ": ", strconv.Quote(string(value.Desc.Name())), ",")
171 generated[value.Desc.Number()] = true
172 }
173 g.P("}")
174 g.P()
175 valueMap := enum.GoIdent.GoName + "_value"
176 g.P("var ", valueMap, " = map[string]int32{")
177 for _, value := range enum.Values {
178 g.P(strconv.Quote(string(value.Desc.Name())), ": ", value.Desc.Number(), ",")
179 }
180 g.P("}")
181 g.P()
182 if enum.Desc.Syntax() != protoreflect.Proto3 {
183 g.P("func (x ", enum.GoIdent, ") Enum() *", enum.GoIdent, " {")
184 g.P("p := new(", enum.GoIdent, ")")
185 g.P("*p = x")
186 g.P("return p")
187 g.P("}")
188 g.P()
189 }
190 g.P("func (x ", enum.GoIdent, ") String() string {")
191 g.P("return ", protogen.GoIdent{GoImportPath: protoPackage, GoName: "EnumName"}, "(", enum.GoIdent, "_name, int32(x))")
192 g.P("}")
193 g.P()
194
195 if enum.Desc.Syntax() != protoreflect.Proto3 {
196 g.P("func (x *", enum.GoIdent, ") UnmarshalJSON(data []byte) error {")
197 g.P("value, err := ", protogen.GoIdent{GoImportPath: protoPackage, GoName: "UnmarshalJSONEnum"}, "(", enum.GoIdent, `_value, data, "`, enum.GoIdent, `")`)
198 g.P("if err != nil {")
199 g.P("return err")
200 g.P("}")
201 g.P("*x = ", enum.GoIdent, "(value)")
202 g.P("return nil")
203 g.P("}")
204 g.P()
205 }
206
207 var indexes []string
208 for i := 1; i < len(enum.Path); i += 2 {
209 indexes = append(indexes, strconv.Itoa(int(enum.Path[i])))
210 }
211 g.P("func (", enum.GoIdent, ") EnumDescriptor() ([]byte, []int) {")
212 g.P("return ", f.descriptorVar, ", []int{", strings.Join(indexes, ","), "}")
213 g.P("}")
214 g.P()
215
216 genWellKnownType(g, enum.GoIdent, enum.Desc)
217
Damien Neil46abb572018-09-07 12:45:37 -0700218 f.init = append(f.init, fmt.Sprintf("%s(%q, %s, %s)",
219 g.QualifiedGoIdent(protogen.GoIdent{
220 GoImportPath: protoPackage,
221 GoName: "RegisterEnum",
222 }),
Damien Neil658051b2018-09-10 12:26:21 -0700223 enumRegistryName(enum), nameMap, valueMap,
Damien Neil46abb572018-09-07 12:45:37 -0700224 ))
225}
226
Damien Neil658051b2018-09-10 12:26:21 -0700227// enumRegistryName returns the name used to register an enum with the proto
228// package registry.
229//
230// Confusingly, this is <proto_package>.<go_ident>. This probably should have
231// been the full name of the proto enum type instead, but changing it at this
232// point would require thought.
233func enumRegistryName(enum *protogen.Enum) string {
234 // Find the FileDescriptor for this enum.
235 var desc protoreflect.Descriptor = enum.Desc
236 for {
237 p, ok := desc.Parent()
238 if !ok {
239 break
240 }
241 desc = p
242 }
243 fdesc := desc.(protoreflect.FileDescriptor)
244 return string(fdesc.Package()) + "." + enum.GoIdent.GoName
245}
246
Damien Neilcab8dfe2018-09-06 14:51:28 -0700247func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, message *protogen.Message) {
Damien Neil658051b2018-09-10 12:26:21 -0700248 for _, e := range message.Enums {
249 genEnum(gen, g, f, e)
Damien Neil46abb572018-09-07 12:45:37 -0700250 }
251
Damien Neilcab8dfe2018-09-06 14:51:28 -0700252 genComment(g, f, message.Path)
Damien Neil658051b2018-09-10 12:26:21 -0700253 // TODO: deprecation
Damien Neilcab8dfe2018-09-06 14:51:28 -0700254 g.P("type ", message.GoIdent, " struct {")
Damien Neil658051b2018-09-10 12:26:21 -0700255 for _, field := range message.Fields {
256 if field.Desc.OneofType() != nil {
257 // TODO oneofs
258 continue
259 }
260 genComment(g, f, field.Path)
261 g.P(field.GoIdent, " ", fieldGoType(g, field), fmt.Sprintf(" `protobuf:%q json:%q`", fieldProtobufTag(field), fieldJSONTag(field)))
262 }
263 g.P("XXX_NoUnkeyedLiteral struct{} `json:\"-\"`")
264 // TODO XXX_InternalExtensions
265 g.P("XXX_unrecognized []byte `json:\"-\"`")
266 g.P("XXX_sizecache int32 `json:\"-\"`")
Damien Neilc7d07d92018-08-22 13:46:02 -0700267 g.P("}")
268 g.P()
269
Damien Neila1c6abc2018-09-12 13:36:34 -0700270 // Reset
271 g.P("func (m *", message.GoIdent, ") Reset() { *m = ", message.GoIdent, "{} }")
272 // String
273 g.P("func (m *", message.GoIdent, ") String() string { return ", protogen.GoIdent{
274 GoImportPath: protoPackage,
275 GoName: "CompactTextString",
276 }, "(m) }")
277 // ProtoMessage
278 g.P("func (*", message.GoIdent, ") ProtoMessage() {}")
279 // Descriptor
280 var indexes []string
281 for i := 1; i < len(message.Path); i += 2 {
282 indexes = append(indexes, strconv.Itoa(int(message.Path[i])))
283 }
284 g.P("func (*", message.GoIdent, ") Descriptor() ([]byte, []int) {")
285 g.P("return ", f.descriptorVar, ", []int{", strings.Join(indexes, ","), "}")
286 g.P("}")
287 // TODO: extension support methods
288
289 // Table-driven proto support.
290 //
291 // TODO: It does not scale to keep adding another method for every
292 // operation on protos that we want to switch over to using the
293 // table-driven approach. Instead, we should only add a single method
294 // that allows getting access to the *InternalMessageInfo struct and then
295 // calling Unmarshal, Marshal, Merge, Size, and Discard directly on that.
296 messageInfoVar := "xxx_messageInfo_" + message.GoIdent.GoName
297 // XXX_Unmarshal
298 g.P("func (m *", message.GoIdent, ") XXX_Unmarshal(b []byte) error {")
299 g.P("return ", messageInfoVar, ".Unmarshal(m, b)")
300 g.P("}")
301 // XXX_Marshal
302 g.P("func (m *", message.GoIdent, ") XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {")
303 g.P("return ", messageInfoVar, ".Marshal(b, m, deterministic)")
304 g.P("}")
305 // XXX_Merge
306 g.P("func (m *", message.GoIdent, ") XXX_Merge(src proto.Message) {")
307 g.P(messageInfoVar, ".Merge(m, src)")
308 g.P("}")
309 // XXX_Size
310 g.P("func (m *", message.GoIdent, ") XXX_Size() int {")
311 g.P("return ", messageInfoVar, ".Size(m)")
312 g.P("}")
313 // XXX_DiscardUnknown
314 g.P("func (m *", message.GoIdent, ") XXX_DiscardUnknown() {")
315 g.P(messageInfoVar, ".DiscardUnknown(m)")
316 g.P("}")
317 g.P()
318 g.P("var ", messageInfoVar, " ", protogen.GoIdent{
319 GoImportPath: protoPackage,
320 GoName: "InternalMessageInfo",
321 })
322 g.P()
323
324 // TODO: getters
325
Damien Neilcab8dfe2018-09-06 14:51:28 -0700326 for _, nested := range message.Messages {
327 genMessage(gen, g, f, nested)
Damien Neilc7d07d92018-08-22 13:46:02 -0700328 }
329}
Damien Neilcab8dfe2018-09-06 14:51:28 -0700330
Damien Neil658051b2018-09-10 12:26:21 -0700331func fieldGoType(g *protogen.GeneratedFile, field *protogen.Field) string {
332 // TODO: map types
333 var typ string
334 switch field.Desc.Kind() {
335 case protoreflect.BoolKind:
336 typ = "bool"
337 case protoreflect.EnumKind:
338 typ = g.QualifiedGoIdent(field.EnumType.GoIdent)
339 case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
340 typ = "int32"
341 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
342 typ = "uint32"
343 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
344 typ = "int64"
345 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
346 typ = "uint64"
347 case protoreflect.FloatKind:
348 typ = "float32"
349 case protoreflect.DoubleKind:
350 typ = "float64"
351 case protoreflect.StringKind:
352 typ = "string"
353 case protoreflect.BytesKind:
354 typ = "[]byte"
355 case protoreflect.MessageKind, protoreflect.GroupKind:
356 typ = "*" + g.QualifiedGoIdent(field.MessageType.GoIdent)
357 }
358 if field.Desc.Cardinality() == protoreflect.Repeated {
359 return "[]" + typ
360 }
361 if field.Desc.Syntax() == protoreflect.Proto3 {
362 return typ
363 }
364 if field.Desc.OneofType() != nil {
365 return typ
366 }
367 nonPointerKinds := map[protoreflect.Kind]bool{
368 protoreflect.GroupKind: true,
369 protoreflect.MessageKind: true,
370 protoreflect.BytesKind: true,
371 }
372 if !nonPointerKinds[field.Desc.Kind()] {
373 return "*" + typ
374 }
375 return typ
376}
377
378func fieldProtobufTag(field *protogen.Field) string {
379 var tag []string
380 // wire type
381 tag = append(tag, wireTypes[field.Desc.Kind()])
382 // field number
383 tag = append(tag, strconv.Itoa(int(field.Desc.Number())))
384 // cardinality
385 switch field.Desc.Cardinality() {
386 case protoreflect.Optional:
387 tag = append(tag, "opt")
388 case protoreflect.Required:
389 tag = append(tag, "req")
390 case protoreflect.Repeated:
391 tag = append(tag, "rep")
392 }
393 // TODO: default values
394 // TODO: packed
395 // name
396 name := string(field.Desc.Name())
397 if field.Desc.Kind() == protoreflect.GroupKind {
398 // The name of the FieldDescriptor for a group field is
399 // lowercased. To find the original capitalization, we
400 // look in the field's MessageType.
401 name = string(field.MessageType.Desc.Name())
402 }
403 tag = append(tag, "name="+name)
404 // JSON name
405 if jsonName := field.Desc.JSONName(); jsonName != "" && jsonName != name {
406 tag = append(tag, "json="+jsonName)
407 }
408 // proto3
409 if field.Desc.Syntax() == protoreflect.Proto3 {
410 tag = append(tag, "proto3")
411 }
412 // enum
413 if field.Desc.Kind() == protoreflect.EnumKind {
414 tag = append(tag, "enum="+enumRegistryName(field.EnumType))
415 }
416 // oneof
417 if field.Desc.OneofType() != nil {
418 tag = append(tag, "oneof")
419 }
420 return strings.Join(tag, ",")
421}
422
423var wireTypes = map[protoreflect.Kind]string{
424 protoreflect.BoolKind: "varint",
425 protoreflect.EnumKind: "varint",
426 protoreflect.Int32Kind: "varint",
427 protoreflect.Sint32Kind: "zigzag32",
428 protoreflect.Uint32Kind: "varint",
429 protoreflect.Int64Kind: "varint",
430 protoreflect.Sint64Kind: "zigzag64",
431 protoreflect.Uint64Kind: "varint",
432 protoreflect.Sfixed32Kind: "fixed32",
433 protoreflect.Fixed32Kind: "fixed32",
434 protoreflect.FloatKind: "fixed32",
435 protoreflect.Sfixed64Kind: "fixed64",
436 protoreflect.Fixed64Kind: "fixed64",
437 protoreflect.DoubleKind: "fixed64",
438 protoreflect.StringKind: "bytes",
439 protoreflect.BytesKind: "bytes",
440 protoreflect.MessageKind: "bytes",
441 protoreflect.GroupKind: "group",
442}
443
444func fieldJSONTag(field *protogen.Field) string {
445 return string(field.Desc.Name()) + ",omitempty"
446}
447
Damien Neilcab8dfe2018-09-06 14:51:28 -0700448func genComment(g *protogen.GeneratedFile, f *File, path []int32) {
449 for _, loc := range f.locationMap[pathKey(path)] {
450 if loc.LeadingComments == nil {
451 continue
452 }
453 for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") {
454 g.P("//", line)
455 }
456 return
457 }
458}
459
460// pathKey converts a location path to a string suitable for use as a map key.
461func pathKey(path []int32) string {
462 var buf []byte
463 for i, x := range path {
464 if i != 0 {
465 buf = append(buf, ',')
466 }
467 buf = strconv.AppendInt(buf, int64(x), 10)
468 }
469 return string(buf)
470}
Damien Neil46abb572018-09-07 12:45:37 -0700471
472func genWellKnownType(g *protogen.GeneratedFile, ident protogen.GoIdent, desc protoreflect.Descriptor) {
473 if wellKnownTypes[desc.FullName()] {
474 g.P("func (", ident, `) XXX_WellKnownType() string { return "`, desc.Name(), `" }`)
475 g.P()
476 }
477}
478
479// Names of messages and enums for which we will generate XXX_WellKnownType methods.
480var wellKnownTypes = map[protoreflect.FullName]bool{
481 "google.protobuf.NullValue": true,
482}