blob: dc4ded3ae06e2d4fce2226ca4d4d48d3733a1e58 [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 Neilcab8dfe2018-09-06 14:51:28 -0700270 for _, nested := range message.Messages {
271 genMessage(gen, g, f, nested)
Damien Neilc7d07d92018-08-22 13:46:02 -0700272 }
273}
Damien Neilcab8dfe2018-09-06 14:51:28 -0700274
Damien Neil658051b2018-09-10 12:26:21 -0700275func fieldGoType(g *protogen.GeneratedFile, field *protogen.Field) string {
276 // TODO: map types
277 var typ string
278 switch field.Desc.Kind() {
279 case protoreflect.BoolKind:
280 typ = "bool"
281 case protoreflect.EnumKind:
282 typ = g.QualifiedGoIdent(field.EnumType.GoIdent)
283 case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
284 typ = "int32"
285 case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
286 typ = "uint32"
287 case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
288 typ = "int64"
289 case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
290 typ = "uint64"
291 case protoreflect.FloatKind:
292 typ = "float32"
293 case protoreflect.DoubleKind:
294 typ = "float64"
295 case protoreflect.StringKind:
296 typ = "string"
297 case protoreflect.BytesKind:
298 typ = "[]byte"
299 case protoreflect.MessageKind, protoreflect.GroupKind:
300 typ = "*" + g.QualifiedGoIdent(field.MessageType.GoIdent)
301 }
302 if field.Desc.Cardinality() == protoreflect.Repeated {
303 return "[]" + typ
304 }
305 if field.Desc.Syntax() == protoreflect.Proto3 {
306 return typ
307 }
308 if field.Desc.OneofType() != nil {
309 return typ
310 }
311 nonPointerKinds := map[protoreflect.Kind]bool{
312 protoreflect.GroupKind: true,
313 protoreflect.MessageKind: true,
314 protoreflect.BytesKind: true,
315 }
316 if !nonPointerKinds[field.Desc.Kind()] {
317 return "*" + typ
318 }
319 return typ
320}
321
322func fieldProtobufTag(field *protogen.Field) string {
323 var tag []string
324 // wire type
325 tag = append(tag, wireTypes[field.Desc.Kind()])
326 // field number
327 tag = append(tag, strconv.Itoa(int(field.Desc.Number())))
328 // cardinality
329 switch field.Desc.Cardinality() {
330 case protoreflect.Optional:
331 tag = append(tag, "opt")
332 case protoreflect.Required:
333 tag = append(tag, "req")
334 case protoreflect.Repeated:
335 tag = append(tag, "rep")
336 }
337 // TODO: default values
338 // TODO: packed
339 // name
340 name := string(field.Desc.Name())
341 if field.Desc.Kind() == protoreflect.GroupKind {
342 // The name of the FieldDescriptor for a group field is
343 // lowercased. To find the original capitalization, we
344 // look in the field's MessageType.
345 name = string(field.MessageType.Desc.Name())
346 }
347 tag = append(tag, "name="+name)
348 // JSON name
349 if jsonName := field.Desc.JSONName(); jsonName != "" && jsonName != name {
350 tag = append(tag, "json="+jsonName)
351 }
352 // proto3
353 if field.Desc.Syntax() == protoreflect.Proto3 {
354 tag = append(tag, "proto3")
355 }
356 // enum
357 if field.Desc.Kind() == protoreflect.EnumKind {
358 tag = append(tag, "enum="+enumRegistryName(field.EnumType))
359 }
360 // oneof
361 if field.Desc.OneofType() != nil {
362 tag = append(tag, "oneof")
363 }
364 return strings.Join(tag, ",")
365}
366
367var wireTypes = map[protoreflect.Kind]string{
368 protoreflect.BoolKind: "varint",
369 protoreflect.EnumKind: "varint",
370 protoreflect.Int32Kind: "varint",
371 protoreflect.Sint32Kind: "zigzag32",
372 protoreflect.Uint32Kind: "varint",
373 protoreflect.Int64Kind: "varint",
374 protoreflect.Sint64Kind: "zigzag64",
375 protoreflect.Uint64Kind: "varint",
376 protoreflect.Sfixed32Kind: "fixed32",
377 protoreflect.Fixed32Kind: "fixed32",
378 protoreflect.FloatKind: "fixed32",
379 protoreflect.Sfixed64Kind: "fixed64",
380 protoreflect.Fixed64Kind: "fixed64",
381 protoreflect.DoubleKind: "fixed64",
382 protoreflect.StringKind: "bytes",
383 protoreflect.BytesKind: "bytes",
384 protoreflect.MessageKind: "bytes",
385 protoreflect.GroupKind: "group",
386}
387
388func fieldJSONTag(field *protogen.Field) string {
389 return string(field.Desc.Name()) + ",omitempty"
390}
391
Damien Neilcab8dfe2018-09-06 14:51:28 -0700392func genComment(g *protogen.GeneratedFile, f *File, path []int32) {
393 for _, loc := range f.locationMap[pathKey(path)] {
394 if loc.LeadingComments == nil {
395 continue
396 }
397 for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") {
398 g.P("//", line)
399 }
400 return
401 }
402}
403
404// pathKey converts a location path to a string suitable for use as a map key.
405func pathKey(path []int32) string {
406 var buf []byte
407 for i, x := range path {
408 if i != 0 {
409 buf = append(buf, ',')
410 }
411 buf = strconv.AppendInt(buf, int64(x), 10)
412 }
413 return string(buf)
414}
Damien Neil46abb572018-09-07 12:45:37 -0700415
416func genWellKnownType(g *protogen.GeneratedFile, ident protogen.GoIdent, desc protoreflect.Descriptor) {
417 if wellKnownTypes[desc.FullName()] {
418 g.P("func (", ident, `) XXX_WellKnownType() string { return "`, desc.Name(), `" }`)
419 g.P()
420 }
421}
422
423// Names of messages and enums for which we will generate XXX_WellKnownType methods.
424var wellKnownTypes = map[protoreflect.FullName]bool{
425 "google.protobuf.NullValue": true,
426}