blob: c243e00907ff9030aae906252aceef4f3897aa98 [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
203 // The name registered is, confusingly, <proto_package>.<go_ident>.
204 // This probably should have been the full name of the proto enum
205 // type instead, but changing it at this point would require thought.
206 regName := string(f.Desc.Package()) + "." + enum.GoIdent.GoName
207 f.init = append(f.init, fmt.Sprintf("%s(%q, %s, %s)",
208 g.QualifiedGoIdent(protogen.GoIdent{
209 GoImportPath: protoPackage,
210 GoName: "RegisterEnum",
211 }),
212 regName, nameMap, valueMap,
213 ))
214}
215
Damien Neilcab8dfe2018-09-06 14:51:28 -0700216func genMessage(gen *protogen.Plugin, g *protogen.GeneratedFile, f *File, message *protogen.Message) {
Damien Neil46abb572018-09-07 12:45:37 -0700217 for _, enum := range message.Enums {
218 genEnum(gen, g, f, enum)
219 }
220
Damien Neilcab8dfe2018-09-06 14:51:28 -0700221 genComment(g, f, message.Path)
222 g.P("type ", message.GoIdent, " struct {")
Damien Neilc7d07d92018-08-22 13:46:02 -0700223 g.P("}")
224 g.P()
225
Damien Neilcab8dfe2018-09-06 14:51:28 -0700226 for _, nested := range message.Messages {
227 genMessage(gen, g, f, nested)
Damien Neilc7d07d92018-08-22 13:46:02 -0700228 }
229}
Damien Neilcab8dfe2018-09-06 14:51:28 -0700230
231func genComment(g *protogen.GeneratedFile, f *File, path []int32) {
232 for _, loc := range f.locationMap[pathKey(path)] {
233 if loc.LeadingComments == nil {
234 continue
235 }
236 for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") {
237 g.P("//", line)
238 }
239 return
240 }
241}
242
243// pathKey converts a location path to a string suitable for use as a map key.
244func pathKey(path []int32) string {
245 var buf []byte
246 for i, x := range path {
247 if i != 0 {
248 buf = append(buf, ',')
249 }
250 buf = strconv.AppendInt(buf, int64(x), 10)
251 }
252 return string(buf)
253}
Damien Neil46abb572018-09-07 12:45:37 -0700254
255func genWellKnownType(g *protogen.GeneratedFile, ident protogen.GoIdent, desc protoreflect.Descriptor) {
256 if wellKnownTypes[desc.FullName()] {
257 g.P("func (", ident, `) XXX_WellKnownType() string { return "`, desc.Name(), `" }`)
258 g.P()
259 }
260}
261
262// Names of messages and enums for which we will generate XXX_WellKnownType methods.
263var wellKnownTypes = map[protoreflect.FullName]bool{
264 "google.protobuf.NullValue": true,
265}