Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"logging.go",
"main.go",
"version.go",
"wrappers.go",
],
importpath = "go-common/app/tool/liverpc/protoc-gen-liverpc/gen",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/liverpc/protoc-gen-liverpc/gen/stringutils:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@com_github_golang_protobuf//protoc-gen-go/descriptor:go_default_library",
"@com_github_golang_protobuf//protoc-gen-go/plugin:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/tool/liverpc/protoc-gen-liverpc/gen/stringutils:all-srcs",
"//app/tool/liverpc/protoc-gen-liverpc/gen/typemap:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,34 @@
// 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.
package gen
import (
"log"
"os"
"strings"
)
// Fail log and exit
func Fail(msgs ...string) {
s := strings.Join(msgs, " ")
log.Print("error:", s)
os.Exit(1)
}
// Error log and exit
func Error(err error, msgs ...string) {
s := strings.Join(msgs, " ") + ":" + err.Error()
log.Print("error:", s)
os.Exit(1)
}

View File

@@ -0,0 +1,82 @@
// 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.
package gen
import (
"io"
"io/ioutil"
"os"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
)
// Generator ...
type Generator interface {
Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse
}
// Main ...
func Main(g Generator) {
req := readGenRequest()
resp := g.Generate(req)
writeResponse(os.Stdout, resp)
}
// FilesToGenerate ...
func FilesToGenerate(req *plugin.CodeGeneratorRequest) []*descriptor.FileDescriptorProto {
genFiles := make([]*descriptor.FileDescriptorProto, 0)
Outer:
for _, name := range req.FileToGenerate {
for _, f := range req.ProtoFile {
if f.GetName() == name {
genFiles = append(genFiles, f)
continue Outer
}
}
Fail("could not find file named", name)
}
return genFiles
}
func readGenRequest() *plugin.CodeGeneratorRequest {
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
Error(err, "reading input")
}
req := new(plugin.CodeGeneratorRequest)
if err = proto.Unmarshal(data, req); err != nil {
Error(err, "parsing input proto")
}
if len(req.FileToGenerate) == 0 {
Fail("no files to generate")
}
return req
}
func writeResponse(w io.Writer, resp *plugin.CodeGeneratorResponse) {
data, err := proto.Marshal(resp)
if err != nil {
Error(err, "marshaling response")
}
_, err = w.Write(data)
if err != nil {
Error(err, "writing response")
}
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["stringutils.go"],
importpath = "go-common/app/tool/liverpc/protoc-gen-liverpc/gen/stringutils",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,158 @@
// 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 stringutils
import (
"bytes"
"fmt"
"strings"
"unicode"
)
// Is c an ASCII lower-case letter?
func isASCIILower(c byte) bool {
return 'a' <= c && c <= 'z'
}
// Is c an ASCII digit?
func isASCIIDigit(c byte) bool {
return '0' <= c && c <= '9'
}
// CamelCase converts a string from snake_case to CamelCased.
//
// If there is an interior underscore followed by a lower case letter, drop the
// underscore and convert the letter to upper case. There is a remote
// possibility of this rewrite causing a name collision, but it's so remote
// we're prepared to pretend it's nonexistent - since the C++ generator
// lowercases names, it's extremely unlikely to have two fields with different
// capitalizations. In short, _my_field_name_2 becomes XMyFieldName_2.
func CamelCase(s string) string {
if s == "" {
return ""
}
t := make([]byte, 0, 32)
i := 0
if s[0] == '_' {
// Need a capital letter; drop the '_'.
t = append(t, 'X')
i++
}
// Invariant: if the next letter is lower case, it must be converted
// to upper case.
//
// That is, we process a word at a time, where words are marked by _ or upper
// case letter. Digits are treated as words.
for ; i < len(s); i++ {
c := s[i]
if c == '_' && i+1 < len(s) && isASCIILower(s[i+1]) {
continue // Skip the underscore in s.
}
if isASCIIDigit(c) {
t = append(t, c)
continue
}
// Assume we have a letter now - if not, it's a bogus identifier. The next
// word is a sequence of characters that must start upper case.
if isASCIILower(c) {
c ^= ' ' // Make it a capital letter.
}
t = append(t, c) // Guaranteed not lower case.
// Accept lower case sequence that follows.
for i+1 < len(s) && isASCIILower(s[i+1]) {
i++
t = append(t, s[i])
}
}
return string(t)
}
// CamelCaseSlice is like CamelCase, but the argument is a slice of strings to
// be joined with "_" and then camelcased.
func CamelCaseSlice(elem []string) string { return CamelCase(strings.Join(elem, "_")) }
// DotJoin joins a slice of strings with '.'
func DotJoin(elem []string) string { return strings.Join(elem, ".") }
// AlphaDigitize replaces non-letter, non-digit, non-underscore characters with
// underscore.
func AlphaDigitize(r rune) rune {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
return r
}
return '_'
}
// CleanIdentifier makes sure s is a valid 'identifier' string: it contains only
// letters, numbers, and underscore.
func CleanIdentifier(s string) string {
return strings.Map(AlphaDigitize, s)
}
// BaseName the last path element of a slash-delimited name, with the last
// dotted suffix removed.
func BaseName(name string) string {
// First, find the last element
if i := strings.LastIndex(name, "/"); i >= 0 {
name = name[i+1:]
}
// Now drop the suffix
if i := strings.LastIndex(name, "."); i >= 0 {
name = name[0:i]
}
return name
}
// SnakeCase converts a string from CamelCase to snake_case.
func SnakeCase(s string) string {
var buf bytes.Buffer
for i, r := range s {
if unicode.IsUpper(r) && i > 0 {
fmt.Fprintf(&buf, "_")
}
r = unicode.ToLower(r)
fmt.Fprintf(&buf, "%c", r)
}
return buf.String()
}

View File

@@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["typemap.go"],
importpath = "go-common/app/tool/liverpc/protoc-gen-liverpc/gen/typemap",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/pkg/errors:go_default_library",
"@com_github_golang_protobuf//protoc-gen-go/descriptor:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,332 @@
// 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.
package typemap
import (
"strings"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/pkg/errors"
)
// Registry is the place of descriptors resolving
type Registry struct {
allFiles []*descriptor.FileDescriptorProto
filesByName map[string]*descriptor.FileDescriptorProto
// Mapping of fully-qualified names to their definitions
messagesByProtoName map[string]*MessageDefinition
}
// New Registry
func New(files []*descriptor.FileDescriptorProto) *Registry {
r := &Registry{
allFiles: files,
filesByName: make(map[string]*descriptor.FileDescriptorProto),
messagesByProtoName: make(map[string]*MessageDefinition),
}
// First, index the file descriptors by name. We need this so
// messageDefsForFile can correctly scan imports.
for _, f := range files {
r.filesByName[f.GetName()] = f
}
// Next, index all the message definitions by their fully-qualified proto
// names.
for _, f := range files {
defs := messageDefsForFile(f, r.filesByName)
for name, def := range defs {
r.messagesByProtoName[name] = def
}
}
return r
}
// FileComments comment of file
func (r *Registry) FileComments(file *descriptor.FileDescriptorProto) (DefinitionComments, error) {
return commentsAtPath([]int32{packagePath}, file), nil
}
// ServiceComments comments of service
func (r *Registry) ServiceComments(file *descriptor.FileDescriptorProto, svc *descriptor.ServiceDescriptorProto) (DefinitionComments, error) {
for i, s := range file.Service {
if s == svc {
path := []int32{servicePath, int32(i)}
return commentsAtPath(path, file), nil
}
}
return DefinitionComments{}, errors.Errorf("service not found in file")
}
func (r *Registry) FieldComments(file *descriptor.FileDescriptorProto,
message *MessageDefinition, field *descriptor.FieldDescriptorProto) (DefinitionComments, error) {
mpath := message.path
for i, f := range message.Descriptor.Field {
if f == field {
path := append(mpath, messageFieldPath, int32(i))
return commentsAtPath(path, file), nil
}
}
return DefinitionComments{}, errors.Errorf("field not found in msg")
}
// MethodComments comment of method
func (r *Registry) MethodComments(file *descriptor.FileDescriptorProto, svc *descriptor.ServiceDescriptorProto, method *descriptor.MethodDescriptorProto) (DefinitionComments, error) {
for i, s := range file.Service {
if s == svc {
path := []int32{servicePath, int32(i)}
for j, m := range s.Method {
if m == method {
path = append(path, serviceMethodPath, int32(j))
return commentsAtPath(path, file), nil
}
}
}
}
return DefinitionComments{}, errors.Errorf("service not found in file")
}
// MethodInputDefinition returns MethodInputDefinition
func (r *Registry) MethodInputDefinition(method *descriptor.MethodDescriptorProto) *MessageDefinition {
return r.messagesByProtoName[method.GetInputType()]
}
// MethodOutputDefinition returns MethodOutputDefinition
func (r *Registry) MethodOutputDefinition(method *descriptor.MethodDescriptorProto) *MessageDefinition {
return r.messagesByProtoName[method.GetOutputType()]
}
// MessageDefinition by name
func (r *Registry) MessageDefinition(name string) *MessageDefinition {
return r.messagesByProtoName[name]
}
// MessageDefinition msg info
type MessageDefinition struct {
// Descriptor is is the DescriptorProto defining the message.
Descriptor *descriptor.DescriptorProto
// File is the File that the message was defined in. Or, if it has been
// publicly imported, what File was that import performed in?
File *descriptor.FileDescriptorProto
// Parent is the parent message, if this was defined as a nested message. If
// this was defiend at the top level, parent is nil.
Parent *MessageDefinition
// Comments describes the comments surrounding a message's definition. If it
// was publicly imported, then these comments are from the actual source file,
// not the file that the import was performed in.
Comments DefinitionComments
// path is the 'SourceCodeInfo' path. See the documentation for
// github.com/golang/protobuf/protoc-gen-go/descriptor.SourceCodeInfo for an
// explanation of its format.
path []int32
}
// ProtoName returns the dot-delimited, fully-qualified protobuf name of the
// message.
func (m *MessageDefinition) ProtoName() string {
prefix := "."
if pkg := m.File.GetPackage(); pkg != "" {
prefix += pkg + "."
}
if lineage := m.Lineage(); len(lineage) > 0 {
for _, parent := range lineage {
prefix += parent.Descriptor.GetName() + "."
}
}
return prefix + m.Descriptor.GetName()
}
// Lineage returns m's parental chain all the way back up to a top-level message
// definition. The first element of the returned slice is the highest-level
// parent.
func (m *MessageDefinition) Lineage() []*MessageDefinition {
var parents []*MessageDefinition
for p := m.Parent; p != nil; p = p.Parent {
parents = append([]*MessageDefinition{p}, parents...)
}
return parents
}
// descendants returns all the submessages defined within m, and all the
// descendants of those, recursively.
func (m *MessageDefinition) descendants() []*MessageDefinition {
descendants := make([]*MessageDefinition, 0)
for i, child := range m.Descriptor.NestedType {
path := append(m.path, []int32{messageMessagePath, int32(i)}...)
childDef := &MessageDefinition{
Descriptor: child,
File: m.File,
Parent: m,
Comments: commentsAtPath(path, m.File),
path: path,
}
descendants = append(descendants, childDef)
descendants = append(descendants, childDef.descendants()...)
}
return descendants
}
// messageDefsForFile gathers a mapping of fully-qualified protobuf names to
// their definitions. It scans a singles file at a time. It requires a mapping
// of .proto file names to their definitions in order to correctly handle
// 'import public' declarations; this mapping should include all files
// transitively imported by f.
func messageDefsForFile(f *descriptor.FileDescriptorProto, filesByName map[string]*descriptor.FileDescriptorProto) map[string]*MessageDefinition {
byProtoName := make(map[string]*MessageDefinition)
// First, gather all the messages defined at the top level.
for i, d := range f.MessageType {
path := []int32{messagePath, int32(i)}
def := &MessageDefinition{
Descriptor: d,
File: f,
Parent: nil,
Comments: commentsAtPath(path, f),
path: path,
}
byProtoName[def.ProtoName()] = def
// Next, all nested message definitions.
for _, child := range def.descendants() {
byProtoName[child.ProtoName()] = child
}
}
// Finally, all messages imported publicly.
for _, depIdx := range f.PublicDependency {
depFileName := f.Dependency[depIdx]
depFile := filesByName[depFileName]
depDefs := messageDefsForFile(depFile, filesByName)
for _, def := range depDefs {
imported := &MessageDefinition{
Descriptor: def.Descriptor,
File: f,
Parent: def.Parent,
Comments: commentsAtPath(def.path, depFile),
path: def.path,
}
byProtoName[imported.ProtoName()] = imported
}
}
return byProtoName
}
// DefinitionComments contains the comments surrounding a definition in a
// protobuf file.
//
// These follow the rules described by protobuf:
//
// A series of line comments appearing on consecutive lines, with no other
// tokens appearing on those lines, will be treated as a single comment.
//
// leading_detached_comments will keep paragraphs of comments that appear
// before (but not connected to) the current element. Each paragraph,
// separated by empty lines, will be one comment element in the repeated
// field.
//
// Only the comment content is provided; comment markers (e.g. //) are
// stripped out. For block comments, leading whitespace and an asterisk
// will be stripped from the beginning of each line other than the first.
// Newlines are included in the output.
//
// Examples:
//
// optional int32 foo = 1; // Comment attached to foo.
// // Comment attached to bar.
// optional int32 bar = 2;
//
// optional string baz = 3;
// // Comment attached to baz.
// // Another line attached to baz.
//
// // Comment attached to qux.
// //
// // Another line attached to qux.
// optional double qux = 4;
//
// // Detached comment for corge. This is not leading or trailing comments
// // to qux or corge because there are blank lines separating it from
// // both.
//
// // Detached comment for corge paragraph 2.
//
// optional string corge = 5;
// /* Block comment attached
// * to corge. Leading asterisks
// * will be removed. */
// /* Block comment attached to
// * grault. */
// optional int32 grault = 6;
//
// // ignored detached comments.
type DefinitionComments struct {
Leading string
Trailing string
LeadingDetached []string
}
func commentsAtPath(path []int32, sourceFile *descriptor.FileDescriptorProto) DefinitionComments {
if sourceFile.SourceCodeInfo == nil {
// The compiler didn't provide us with comments.
return DefinitionComments{}
}
for _, loc := range sourceFile.SourceCodeInfo.Location {
if pathEqual(path, loc.Path) {
return DefinitionComments{
Leading: strings.TrimSuffix(loc.GetLeadingComments(), "\n"),
LeadingDetached: loc.GetLeadingDetachedComments(),
Trailing: loc.GetTrailingComments(),
}
}
}
return DefinitionComments{}
}
func pathEqual(path1, path2 []int32) bool {
if len(path1) != len(path2) {
return false
}
for i, v := range path1 {
if path2[i] != v {
return false
}
}
return true
}
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
)

View File

@@ -0,0 +1,17 @@
// 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.
package gen
// Version plugin version
const Version = "v0.1"

View File

@@ -0,0 +1,522 @@
// 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
)