You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
522 lines
17 KiB
522 lines
17 KiB
// Copyright 2018 Twitch Interactive, Inc. All Rights Reserved. |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"). You may not |
|
// use this file except in compliance with the License. A copy of the License is |
|
// located at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// or in the "license" file accompanying this file. This file is distributed on |
|
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either |
|
// express or implied. See the License for the specific language governing |
|
// permissions and limitations under the License. |
|
// |
|
// |
|
// This file contains some code from https://github.com/golang/protobuf: |
|
// Copyright 2010 The Go Authors. All rights reserved. |
|
// https://github.com/golang/protobuf |
|
// |
|
// Redistribution and use in source and binary forms, with or without |
|
// modification, are permitted provided that the following conditions are |
|
// met: |
|
// |
|
// * Redistributions of source code must retain the above copyright |
|
// notice, this list of conditions and the following disclaimer. |
|
// * Redistributions in binary form must reproduce the above |
|
// copyright notice, this list of conditions and the following disclaimer |
|
// in the documentation and/or other materials provided with the |
|
// distribution. |
|
// * Neither the name of Google Inc. nor the names of its |
|
// contributors may be used to endorse or promote products derived from |
|
// this software without specific prior written permission. |
|
// |
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
package gen |
|
|
|
import ( |
|
"fmt" |
|
"path" |
|
"strconv" |
|
"strings" |
|
|
|
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen/stringutils" |
|
|
|
"github.com/golang/protobuf/protoc-gen-go/descriptor" |
|
plugin "github.com/golang/protobuf/protoc-gen-go/plugin" |
|
) |
|
|
|
// Each type we import as a protocol buffer (other than FileDescriptorProto) needs |
|
// a pointer to the FileDescriptorProto that represents it. These types achieve that |
|
// wrapping by placing each Proto inside a struct with the pointer to its File. The |
|
// structs have the same names as their contents, with "Proto" removed. |
|
// FileDescriptor is used to store the things that it points to. |
|
|
|
// WrapTypes walks the incoming data, wrapping DescriptorProtos, EnumDescriptorProtos |
|
// and FileDescriptorProtos into file-referenced objects within the Generator. |
|
// It also creates the list of files to generate and so should be called before GenerateAllFiles. |
|
func WrapTypes(req *plugin.CodeGeneratorRequest) (genFiles, allFiles []*FileDescriptor, allFilesByName map[string]*FileDescriptor) { |
|
allFiles = make([]*FileDescriptor, 0, len(req.ProtoFile)) |
|
allFilesByName = make(map[string]*FileDescriptor, len(allFiles)) |
|
|
|
for _, f := range req.ProtoFile { |
|
// We must wrap the descriptors before we wrap the enums |
|
descs := wrapDescriptors(f) |
|
buildNestedDescriptors(descs) |
|
enums := wrapEnumDescriptors(f, descs) |
|
buildNestedEnums(descs, enums) |
|
exts := wrapExtensions(f) |
|
svcs := wrapServices(f) |
|
fd := &FileDescriptor{ |
|
FileDescriptorProto: f, |
|
Services: svcs, |
|
Descriptors: descs, |
|
Enums: enums, |
|
Extensions: exts, |
|
proto3: fileIsProto3(f), |
|
} |
|
extractComments(fd) |
|
|
|
allFiles = append(allFiles, fd) |
|
allFilesByName[f.GetName()] = fd |
|
} |
|
|
|
for _, fd := range allFiles { |
|
fd.Imported = wrapImported(fd.FileDescriptorProto, allFilesByName) |
|
} |
|
|
|
genFiles = make([]*FileDescriptor, 0, len(req.FileToGenerate)) |
|
for _, fileName := range req.FileToGenerate { |
|
fd := allFilesByName[fileName] |
|
if fd == nil { |
|
Fail("could not find file named", fileName) |
|
} |
|
fd.Index = len(genFiles) |
|
genFiles = append(genFiles, fd) |
|
} |
|
|
|
return genFiles, allFiles, allFilesByName |
|
} |
|
|
|
// The file and package name method are common to messages and enums. |
|
type common struct { |
|
file *descriptor.FileDescriptorProto // File this object comes from. |
|
} |
|
|
|
func (c *common) File() *descriptor.FileDescriptorProto { return c.file } |
|
|
|
func fileIsProto3(file *descriptor.FileDescriptorProto) bool { |
|
return file.GetSyntax() == "proto3" |
|
} |
|
|
|
// Descriptor represents a protocol buffer message. |
|
type Descriptor struct { |
|
common |
|
*descriptor.DescriptorProto |
|
Parent *Descriptor // The containing message, if any. |
|
nested []*Descriptor // Inner messages, if any. |
|
enums []*EnumDescriptor // Inner enums, if any. |
|
ext []*ExtensionDescriptor // Extensions, if any. |
|
typename []string // Cached typename vector. |
|
index int // The index into the container, whether the file or another message. |
|
path string // The SourceCodeInfo path as comma-separated integers. |
|
group bool |
|
} |
|
|
|
func newDescriptor(desc *descriptor.DescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto, index int) *Descriptor { |
|
d := &Descriptor{ |
|
common: common{file}, |
|
DescriptorProto: desc, |
|
Parent: parent, |
|
index: index, |
|
} |
|
if parent == nil { |
|
d.path = fmt.Sprintf("%d,%d", messagePath, index) |
|
} else { |
|
d.path = fmt.Sprintf("%s,%d,%d", parent.path, messageMessagePath, index) |
|
} |
|
|
|
// The only way to distinguish a group from a message is whether |
|
// the containing message has a TYPE_GROUP field that matches. |
|
if parent != nil { |
|
parts := d.TypeName() |
|
if file.Package != nil { |
|
parts = append([]string{*file.Package}, parts...) |
|
} |
|
exp := "." + strings.Join(parts, ".") |
|
for _, field := range parent.Field { |
|
if field.GetType() == descriptor.FieldDescriptorProto_TYPE_GROUP && field.GetTypeName() == exp { |
|
d.group = true |
|
break |
|
} |
|
} |
|
} |
|
|
|
for _, field := range desc.Extension { |
|
d.ext = append(d.ext, &ExtensionDescriptor{common{file}, field, d}) |
|
} |
|
|
|
return d |
|
} |
|
|
|
// Return a slice of all the Descriptors defined within this file |
|
func wrapDescriptors(file *descriptor.FileDescriptorProto) []*Descriptor { |
|
sl := make([]*Descriptor, 0, len(file.MessageType)+10) |
|
for i, desc := range file.MessageType { |
|
sl = wrapThisDescriptor(sl, desc, nil, file, i) |
|
} |
|
return sl |
|
} |
|
|
|
// Wrap this Descriptor, recursively |
|
func wrapThisDescriptor(sl []*Descriptor, desc *descriptor.DescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto, index int) []*Descriptor { |
|
sl = append(sl, newDescriptor(desc, parent, file, index)) |
|
me := sl[len(sl)-1] |
|
for i, nested := range desc.NestedType { |
|
sl = wrapThisDescriptor(sl, nested, me, file, i) |
|
} |
|
return sl |
|
} |
|
|
|
func buildNestedDescriptors(descs []*Descriptor) { |
|
for _, desc := range descs { |
|
if len(desc.NestedType) != 0 { |
|
for _, nest := range descs { |
|
if nest.Parent == desc { |
|
desc.nested = append(desc.nested, nest) |
|
} |
|
} |
|
if len(desc.nested) != len(desc.NestedType) { |
|
Fail("internal error: nesting failure for", desc.GetName()) |
|
} |
|
} |
|
} |
|
} |
|
|
|
// TypeName returns the elements of the dotted type name. |
|
// The package name is not part of this name. |
|
func (d *Descriptor) TypeName() []string { |
|
if d.typename != nil { |
|
return d.typename |
|
} |
|
n := 0 |
|
for parent := d; parent != nil; parent = parent.Parent { |
|
n++ |
|
} |
|
s := make([]string, n) |
|
for parent := d; parent != nil; parent = parent.Parent { |
|
n-- |
|
s[n] = parent.GetName() |
|
} |
|
d.typename = s |
|
return s |
|
} |
|
|
|
// EnumDescriptor describes an enum. If it's at top level, its parent will be nil. |
|
// Otherwise it will be the descriptor of the message in which it is defined. |
|
type EnumDescriptor struct { |
|
common |
|
*descriptor.EnumDescriptorProto |
|
parent *Descriptor // The containing message, if any. |
|
typename []string // Cached typename vector. |
|
index int // The index into the container, whether the file or a message. |
|
path string // The SourceCodeInfo path as comma-separated integers. |
|
} |
|
|
|
// Construct a new EnumDescriptor |
|
func newEnumDescriptor(desc *descriptor.EnumDescriptorProto, parent *Descriptor, file *descriptor.FileDescriptorProto, index int) *EnumDescriptor { |
|
ed := &EnumDescriptor{ |
|
common: common{file}, |
|
EnumDescriptorProto: desc, |
|
parent: parent, |
|
index: index, |
|
} |
|
if parent == nil { |
|
ed.path = fmt.Sprintf("%d,%d", enumPath, index) |
|
} else { |
|
ed.path = fmt.Sprintf("%s,%d,%d", parent.path, messageEnumPath, index) |
|
} |
|
return ed |
|
} |
|
|
|
// Return a slice of all the EnumDescriptors defined within this file |
|
func wrapEnumDescriptors(file *descriptor.FileDescriptorProto, descs []*Descriptor) []*EnumDescriptor { |
|
sl := make([]*EnumDescriptor, 0, len(file.EnumType)+10) |
|
// Top-level enums. |
|
for i, enum := range file.EnumType { |
|
sl = append(sl, newEnumDescriptor(enum, nil, file, i)) |
|
} |
|
// Enums within messages. Enums within embedded messages appear in the outer-most message. |
|
for _, nested := range descs { |
|
for i, enum := range nested.EnumType { |
|
sl = append(sl, newEnumDescriptor(enum, nested, file, i)) |
|
} |
|
} |
|
return sl |
|
} |
|
|
|
func buildNestedEnums(descs []*Descriptor, enums []*EnumDescriptor) { |
|
for _, desc := range descs { |
|
if len(desc.EnumType) != 0 { |
|
for _, enum := range enums { |
|
if enum.parent == desc { |
|
desc.enums = append(desc.enums, enum) |
|
} |
|
} |
|
if len(desc.enums) != len(desc.EnumType) { |
|
Fail("internal error: enum nesting failure for", desc.GetName()) |
|
} |
|
} |
|
} |
|
} |
|
|
|
// TypeName returns the elements of the dotted type name. |
|
// The package name is not part of this name. |
|
func (e *EnumDescriptor) TypeName() (s []string) { |
|
if e.typename != nil { |
|
return e.typename |
|
} |
|
name := e.GetName() |
|
if e.parent == nil { |
|
s = make([]string, 1) |
|
} else { |
|
pname := e.parent.TypeName() |
|
s = make([]string, len(pname)+1) |
|
copy(s, pname) |
|
} |
|
s[len(s)-1] = name |
|
e.typename = s |
|
return s |
|
} |
|
|
|
// ExtensionDescriptor describes an extension. If it's at top level, its parent will be nil. |
|
// Otherwise it will be the descriptor of the message in which it is defined. |
|
type ExtensionDescriptor struct { |
|
common |
|
*descriptor.FieldDescriptorProto |
|
parent *Descriptor // The containing message, if any. |
|
} |
|
|
|
// Return a slice of all the top-level ExtensionDescriptors defined within this file. |
|
func wrapExtensions(file *descriptor.FileDescriptorProto) []*ExtensionDescriptor { |
|
var sl []*ExtensionDescriptor |
|
for _, field := range file.Extension { |
|
sl = append(sl, &ExtensionDescriptor{common{file}, field, nil}) |
|
} |
|
return sl |
|
} |
|
|
|
// TypeName returns the elements of the dotted type name. |
|
// The package name is not part of this name. |
|
func (e *ExtensionDescriptor) TypeName() (s []string) { |
|
name := e.GetName() |
|
if e.parent == nil { |
|
// top-level extension |
|
s = make([]string, 1) |
|
} else { |
|
pname := e.parent.TypeName() |
|
s = make([]string, len(pname)+1) |
|
copy(s, pname) |
|
} |
|
s[len(s)-1] = name |
|
return s |
|
} |
|
|
|
// DescName returns the variable name used for the generated descriptor. |
|
func (e *ExtensionDescriptor) DescName() string { |
|
// The full type name. |
|
typeName := e.TypeName() |
|
// Each scope of the extension is individually CamelCased, and all are joined with "_" with an "E_" prefix. |
|
for i, s := range typeName { |
|
typeName[i] = stringutils.CamelCase(s) |
|
} |
|
return "E_" + strings.Join(typeName, "_") |
|
} |
|
|
|
// ImportedDescriptor describes a type that has been publicly imported from another file. |
|
type ImportedDescriptor struct { |
|
common |
|
Object Object |
|
} |
|
|
|
// Return a slice of all the types that are publicly imported into this file. |
|
func wrapImported(file *descriptor.FileDescriptorProto, fileMap map[string]*FileDescriptor) (sl []*ImportedDescriptor) { |
|
for _, index := range file.PublicDependency { |
|
df := fileMap[file.Dependency[index]] |
|
for _, d := range df.Descriptors { |
|
if d.GetOptions().GetMapEntry() { |
|
continue |
|
} |
|
sl = append(sl, &ImportedDescriptor{common{file}, d}) |
|
} |
|
for _, e := range df.Enums { |
|
sl = append(sl, &ImportedDescriptor{common{file}, e}) |
|
} |
|
for _, ext := range df.Extensions { |
|
sl = append(sl, &ImportedDescriptor{common{file}, ext}) |
|
} |
|
} |
|
return |
|
} |
|
|
|
// TypeName ... |
|
func (id *ImportedDescriptor) TypeName() []string { return id.Object.TypeName() } |
|
|
|
// ServiceDescriptor represents a protocol buffer service. |
|
type ServiceDescriptor struct { |
|
common |
|
*descriptor.ServiceDescriptorProto |
|
Methods []*MethodDescriptor |
|
Index int // index of the ServiceDescriptorProto in its parent FileDescriptorProto |
|
|
|
Path string // The SourceCodeInfo path as comma-separated integers. |
|
} |
|
|
|
// TypeName ... |
|
func (sd *ServiceDescriptor) TypeName() []string { |
|
return []string{sd.GetName()} |
|
} |
|
|
|
func wrapServices(file *descriptor.FileDescriptorProto) (sl []*ServiceDescriptor) { |
|
for i, svc := range file.Service { |
|
sd := &ServiceDescriptor{ |
|
common: common{file}, |
|
ServiceDescriptorProto: svc, |
|
Index: i, |
|
Path: fmt.Sprintf("%d,%d", servicePath, i), |
|
} |
|
for j, method := range svc.Method { |
|
md := &MethodDescriptor{ |
|
common: common{file}, |
|
MethodDescriptorProto: method, |
|
service: sd, |
|
Path: fmt.Sprintf("%d,%d,%d,%d", servicePath, i, serviceMethodPath, j), |
|
} |
|
sd.Methods = append(sd.Methods, md) |
|
} |
|
sl = append(sl, sd) |
|
} |
|
return sl |
|
} |
|
|
|
// MethodDescriptor represents an RPC method on a protocol buffer |
|
// service. |
|
type MethodDescriptor struct { |
|
common |
|
*descriptor.MethodDescriptorProto |
|
service *ServiceDescriptor |
|
|
|
Path string // The SourceCodeInfo path as comma-separated integers. |
|
} |
|
|
|
// TypeName ... |
|
func (md *MethodDescriptor) TypeName() []string { |
|
return []string{md.service.GetName(), md.GetName()} |
|
} |
|
|
|
// FileDescriptor describes an protocol buffer descriptor file (.proto). |
|
// It includes slices of all the messages and enums defined within it. |
|
// Those slices are constructed by WrapTypes. |
|
type FileDescriptor struct { |
|
*descriptor.FileDescriptorProto |
|
Descriptors []*Descriptor // All the messages defined in this file. |
|
Enums []*EnumDescriptor // All the enums defined in this file. |
|
Extensions []*ExtensionDescriptor // All the top-level extensions defined in this file. |
|
Imported []*ImportedDescriptor // All types defined in files publicly imported by this file. |
|
Services []*ServiceDescriptor // All the services defined in this file. |
|
|
|
// Comments, stored as a map of path (comma-separated integers) to the comment. |
|
Comments map[string]*descriptor.SourceCodeInfo_Location |
|
|
|
Index int // The index of this file in the list of files to generate code for |
|
|
|
proto3 bool // whether to generate proto3 code for this file |
|
} |
|
|
|
// VarName is the variable name used in generated code to refer to the |
|
// compressed bytes of this descriptor. It is not exported, so it is only valid |
|
// inside the generated package. |
|
// |
|
// protoc-gen-go writes its own version of this file, but so does |
|
// protoc-gen-gogo - with a different name! Twirp aims to be compatible with |
|
// both; the simplest way forward is to write the file descriptor again as |
|
// another variable that we control. |
|
func (d *FileDescriptor) VarName() string { return fmt.Sprintf("twirpFileDescriptor%d", d.Index) } |
|
|
|
// PackageComments get pkg comments |
|
func (d *FileDescriptor) PackageComments() string { |
|
if loc, ok := d.Comments[strconv.Itoa(packagePath)]; ok { |
|
text := strings.TrimSuffix(loc.GetLeadingComments(), "\n") |
|
return text |
|
} |
|
return "" |
|
} |
|
|
|
// BaseFileName name strip extension |
|
func (d *FileDescriptor) BaseFileName() string { |
|
name := *d.Name |
|
if ext := path.Ext(name); ext == ".proto" || ext == ".protodevel" { |
|
name = name[:len(name)-len(ext)] |
|
} |
|
|
|
return name |
|
} |
|
|
|
func extractComments(file *FileDescriptor) { |
|
file.Comments = make(map[string]*descriptor.SourceCodeInfo_Location) |
|
for _, loc := range file.GetSourceCodeInfo().GetLocation() { |
|
if loc.LeadingComments == nil { |
|
continue |
|
} |
|
var p []string |
|
for _, n := range loc.Path { |
|
p = append(p, strconv.Itoa(int(n))) |
|
} |
|
file.Comments[strings.Join(p, ",")] = loc |
|
} |
|
} |
|
|
|
// Object is an interface abstracting the abilities shared by enums, messages, extensions and imported objects. |
|
type Object interface { |
|
TypeName() []string |
|
File() *descriptor.FileDescriptorProto |
|
} |
|
|
|
// The SourceCodeInfo message describes the location of elements of a parsed |
|
// .proto file by way of a "path", which is a sequence of integers that |
|
// describe the route from a FileDescriptorProto to the relevant submessage. |
|
// The path alternates between a field number of a repeated field, and an index |
|
// into that repeated field. The constants below define the field numbers that |
|
// are used. |
|
// |
|
// See descriptor.proto for more information about this. |
|
const ( |
|
// tag numbers in FileDescriptorProto |
|
packagePath = 2 // package |
|
messagePath = 4 // message_type |
|
enumPath = 5 // enum_type |
|
servicePath = 6 // service |
|
// tag numbers in DescriptorProto |
|
//messageFieldPath = 2 // field |
|
messageMessagePath = 3 // nested_type |
|
messageEnumPath = 4 // enum_type |
|
//messageOneofPath = 8 // oneof_decl |
|
// tag numbers in ServiceDescriptorProto |
|
//serviceNamePath = 1 // name |
|
serviceMethodPath = 2 // method |
|
//serviceOptionsPath = 3 // options |
|
// tag numbers in MethodDescriptorProto |
|
//methodNamePath = 1 // name |
|
//methodInputPath = 2 // input_type |
|
//methodOutputPath = 3 // output_type |
|
)
|
|
|