Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
### liverpcgen
### v1.0.1
- 加入CallOption
### v1.0.0
- Use gogoproto to generate.

View File

@@ -0,0 +1,9 @@
# Owner
liugang
liuzhen
# Author
liugang
# Reviewer
liugang

11
app/tool/liverpc/OWNERS Normal file
View File

@@ -0,0 +1,11 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- liugang
- liuzhen
labels:
- tool
options:
no_parent_owners: true
reviewers:
- liugang

View File

@@ -0,0 +1,39 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "liverpcgen",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "go-common/app/tool/liverpc/liverpcgen",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/siddontang/go/ioutil2:go_default_library",
"//vendor/github.com/urfave/cli: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,300 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/siddontang/go/ioutil2"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Name = "liverpcgen"
app.Usage = "Genproto generate files to call liverpc, include *.pb.go and *.liverpc.go\n" +
"EXAMPLE:\n liverpcgen APP_NAME"
app.Version = "1.0.0"
app.Commands = []cli.Command{
{
Name: "update",
Usage: "update the tool its self",
Action: actionUpdate,
},
}
app.Action = actionGenerate
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
func autoUpdate(ctx *cli.Context) (err error) {
tfile, e := ioutil.ReadFile("/tmp/liverpcgentime")
if e != nil {
tfile = []byte{'0'}
}
ts, _ := strconv.ParseInt(string(tfile), 0, 64)
current := time.Now().Unix()
if (current - int64(ts)) > 3600*12 { // 12 hours old
ioutil.WriteFile("/tmp/liverpcgentime", []byte(strconv.FormatInt(current, 10)), 0777)
err = actionUpdate(ctx)
if err != nil {
return
}
}
return
}
// actionGenerate invokes protoc to generate files
func actionGenerate(ctx *cli.Context) (err error) {
if err = autoUpdate(ctx); err != nil {
return
}
appName := ctx.Args().Get(0)
if appName == "" {
cli.ShowAppHelpAndExit(ctx, 1)
}
here := ctx.Args().Get(1)
goPath := initGopath()
goCommonPath := goPath + "/src/go-common"
if !fileExist(goCommonPath) {
return cli.NewExitError("go-common not exist : "+goCommonPath, 1)
}
var rpcPath = goCommonPath + "/app/service/live/" + appName + "/api/liverpc"
if here == "here" {
rpcPath, err = os.Getwd()
if err != nil {
return cli.NewExitError(err, 1)
}
}
if !fileExist(rpcPath) {
os.MkdirAll(rpcPath, 0755)
}
log.Println("Generate for path: " + rpcPath)
// find protos
hasProto := fileExistWithSuffix(rpcPath, ".proto")
if !hasProto {
return cli.NewExitError("No proto files found in path : "+rpcPath, 1)
}
files, err := ioutil.ReadDir(rpcPath)
if err != nil {
return cli.NewExitError("Cannot read dir : "+rpcPath+" error: "+err.Error(), 1)
}
for _, file := range files {
if file.IsDir() {
if strings.HasPrefix(file.Name(), "v") {
//if !file
p := rpcPath + "/" + file.Name()
if fileExistWithSuffix(p, ".proto") {
err = runCmd(fmt.Sprintf("cd %s && protoc -I%s -I%s -I. --gogofaster_out=. --liverpc_out=. %s/*.proto",
rpcPath, goCommonPath+"/vendor", goCommonPath, file.Name()))
if err != nil {
return cli.NewExitError("run protoc err: "+err.Error(), 1)
}
log.Println("generated for path: ", p)
} else {
log.Println("no protofiles found in " + p + " skip")
}
}
}
}
// generate client code
log.Println("generating client.go ...")
resetPrint()
var outputCliDeclares []string
var outputCliAssigns []string
var outputImports []string
split := strings.Split(rpcPath, "/")
clientPkg := split[len(split)-1]
ap("// Code generated by liverpcgen, DO NOT EDIT.")
ap("// source: *.proto files under this directory")
ap("// If you want to change this file, Please see README in go-common/app/tool/liverpc/protoc-gen-liverpc/")
ap("package " + clientPkg)
ap("")
ap("import (")
ap(` "go-common/library/net/rpc/liverpc"`)
pkgPrefix := strings.Replace(rpcPath, goPath+"/src/", "", 1)
files, err = ioutil.ReadDir(rpcPath)
if err != nil {
return cli.NewExitError("Cannot read dir : "+rpcPath+" error: "+err.Error(), 1)
}
for _, file := range files {
if strings.HasPrefix(file.Name(), "client.v") {
pkg := strings.Split(file.Name(), ".")[1]
pkgUpper := strings.ToUpper(pkg)
var b []byte
b, err = ioutil.ReadFile(rpcPath + "/" + file.Name())
fileContent := string(b)
if err != nil {
return cli.NewExitError("fail to read file: "+rpcPath+"/"+file.Name(), 1)
}
fileContent = strings.TrimSuffix(fileContent, "\n")
if fileContent != "" {
names := strings.Split(fileContent, "\n")
for _, name := range names {
apVar(&outputCliDeclares, fmt.Sprintf(" // %s%s presents the controller in liverpc",
pkgUpper, name))
apVar(&outputCliDeclares, fmt.Sprintf(" %s%s %s.%sRPCClient", pkgUpper, name, pkg, name))
apVar(&outputCliAssigns, fmt.Sprintf(" cli.%s%s = %s.New%sRPCClient(realCli)", pkgUpper, name, pkg, name))
}
log.Println("merge " + rpcPath + "/" + file.Name())
apVar(&outputImports, fmt.Sprintf(` "%s/%s"`, pkgPrefix, pkg))
}
os.Remove(rpcPath + "/" + file.Name())
}
}
ap(outputImports...)
ap(")")
ap("")
ap("// Client that represents a liverpc " + appName + " service api")
ap("type Client struct {")
ap(" cli *liverpc.Client")
ap(outputCliDeclares...)
ap("}")
ap("")
discoveryId := strings.Replace(appName, "-", "", -1)
discoveryId = strings.Replace(discoveryId, "_", "", -1)
discoveryId = "live." + discoveryId
ap("// DiscoveryAppId the discovery id is not the tree name")
ap(`var DiscoveryAppId = "` + discoveryId + `"`)
ap("")
ap("// New a Client that represents a liverpc " + discoveryId + " service api")
ap("// conf can be empty, and it will use discovery to find service by default")
ap("// conf.AppID will be overwrite by a fixed value DiscoveryAppId")
ap("// therefore is no need to set")
ap("func New(conf *liverpc.ClientConfig) *Client {")
ap(" if conf == nil {")
ap(" conf = &liverpc.ClientConfig{}")
ap(" }")
ap(" conf.AppID = DiscoveryAppId")
ap(" var realCli = liverpc.NewClient(conf)")
ap(" cli := &Client{cli:realCli}")
ap(" cli.clientInit(realCli)")
ap(" return cli")
ap("}")
ap("")
ap("")
ap("func (cli *Client)GetRawCli() *liverpc.Client {")
ap(" return cli.cli")
ap("}")
ap("")
ap("func (cli *Client)clientInit(realCli *liverpc.Client) {")
ap(outputCliAssigns...)
ap("}")
output := getOutput()
err = ioutil.WriteFile(rpcPath+"/client.go", []byte(output), 0755)
if err != nil {
return cli.NewExitError("write file err : "+err.Error()+"; path: "+rpcPath+"/client.go", 1)
}
runCmd("gofmt -s -w " + rpcPath + "/client.go")
return
}
var buf []string
func resetPrint() {
buf = make([]string, 0)
}
// append to default buf
func ap(args ...string) {
buf = append(buf, args...)
}
// append to var "b"
func apVar(b *[]string, str ...string) {
*b = append(*b, str...)
}
func getOutput() string {
return strings.Join(buf, "\n")
}
// equals to `ls dir/*ext` has result
func fileExistWithSuffix(dir string, ext string) bool {
hasFile := false
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
if strings.HasSuffix(info.Name(), ext) {
hasFile = true
return fmt.Errorf("stop")
}
}
return nil
})
return hasFile
}
// actionUpdate update the tools its self
func actionUpdate(ctx *cli.Context) (err error) {
log.Printf("Updating liverpcgen.....")
goPath := initGopath()
goCommonPath := goPath + "/src/go-common"
if !fileExist(goCommonPath) {
return cli.NewExitError("go-common not exist : "+goCommonPath, 1)
}
cmd1 := fmt.Sprintf("cd %s/app/tool/liverpc/protoc-gen-liverpc && go install", goCommonPath)
cmd2 := fmt.Sprintf("cd %s/app/tool/liverpc/liverpcgen && go install", goCommonPath)
err = runCmd(cmd1)
if err != nil {
return cli.NewExitError("update fail: "+err.Error(), 1)
}
err = runCmd(cmd2)
if err != nil {
return cli.NewExitError("update fail: "+err.Error(), 1)
}
log.Printf("Updated!")
return
}
// runCmd runs the cmd & print output (both stdout & stderr)
func runCmd(cmd string) (err error) {
fmt.Printf("CMD: %s\n", cmd)
out, err := exec.Command("/bin/bash", "-c", cmd).CombinedOutput()
fmt.Print(string(out))
return
}
func fileExist(file string) bool {
return ioutil2.FileExists(file)
}
func initGopath() string {
root, err := goPath()
if err != nil || root == "" {
log.Printf("can not read GOPATH, use ~/go as default GOPATH")
root = path.Join(os.Getenv("HOME"), "go")
}
return root
}
func goPath() (string, error) {
gopaths := strings.Split(os.Getenv("GOPATH"), ":")
if len(gopaths) == 1 {
return gopaths[0], nil
}
for _, gp := range gopaths {
absgp, err := filepath.Abs(gp)
if err != nil {
return "", err
}
if fileExist(absgp + "/src/go-common") {
return absgp, nil
}
}
return "", fmt.Errorf("can't found current gopath")
}

View File

@@ -0,0 +1,68 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_test",
"go_library",
)
go_binary(
name = "protoc-gen-liverpc",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = [
"command_line_test.go",
"generator_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"@com_github_golang_protobuf//proto:go_default_library",
"@com_github_golang_protobuf//protoc-gen-go/plugin:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"command_line.go",
"generator.go",
"go_naming.go",
"main.go",
],
importpath = "go-common/app/tool/liverpc/protoc-gen-liverpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/liverpc/protoc-gen-liverpc/gen:go_default_library",
"//app/tool/liverpc/protoc-gen-liverpc/gen/stringutils:go_default_library",
"//app/tool/liverpc/protoc-gen-liverpc/gen/typemap:go_default_library",
"//vendor/github.com/pkg/errors: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:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,3 @@
install:
go install
cd ../liverpcgen && go install

View File

@@ -0,0 +1,10 @@
# Install
make install
# Usage
`liverpcgen APP_NAME`
will generate protos inside go-common/app/service/live/APP_NAME/api/liverpc
# generate proto files from php projects
see http://git.bilibili.co/live-dev/brpc

View File

@@ -0,0 +1,64 @@
// 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 main
import (
"fmt"
"strings"
)
type commandLineParams struct {
importPrefix string // String to prefix to imported package file names.
importMap map[string]string // Mapping from .proto file name to import path.
}
// parseCommandLineParams breaks the comma-separated list of key=value pairs
// in the parameter (a member of the request protobuf) into a key/value map.
// It then sets command line parameter mappings defined by those entries.
func parseCommandLineParams(parameter string) (*commandLineParams, error) {
ps := make(map[string]string)
for _, p := range strings.Split(parameter, ",") {
if p == "" {
continue
}
i := strings.Index(p, "=")
if i < 0 {
return nil, fmt.Errorf("invalid parameter %q: expected format of parameter to be k=v", p)
}
k := p[0:i]
v := p[i+1:]
if v == "" {
return nil, fmt.Errorf("invalid parameter %q: expected format of parameter to be k=v", k)
}
ps[k] = v
}
clp := &commandLineParams{
importMap: make(map[string]string),
}
for k, v := range ps {
switch {
case k == "import_prefix":
clp.importPrefix = v
// Support import map 'M' prefix per https://github.com/golang/protobuf/blob/6fb5325/protoc-gen-go/generator/generator.go#L497.
case len(k) > 0 && k[0] == 'M':
clp.importMap[k[1:]] = v // 1 is the length of 'M'.
case len(k) > 0 && strings.HasPrefix(k, "go_import_mapping@"):
clp.importMap[k[18:]] = v // 18 is the length of 'go_import_mapping@'.
default:
return nil, fmt.Errorf("unknown parameter %q", k)
}
}
return clp, nil
}

View File

@@ -0,0 +1,128 @@
// 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 main
import (
"errors"
"reflect"
"testing"
)
func TestParseCommandLineParams(t *testing.T) {
tests := []struct {
name string
parameter string
params *commandLineParams
err error
}{
{
"no parameters",
"",
&commandLineParams{
importMap: map[string]string{},
},
nil,
},
{
"unknown parameter",
"k=v",
nil,
errors.New(`unknown parameter "k"`),
},
{
"empty parameter value - no equals sign",
"import_prefix",
nil,
errors.New(`invalid parameter "import_prefix": expected format of parameter to be k=v`),
},
{
"empty parameter value - no value",
"import_prefix=",
nil,
errors.New(`invalid parameter "import_prefix": expected format of parameter to be k=v`),
},
{
"import_prefix parameter",
"import_prefix=github.com/example/repo",
&commandLineParams{
importMap: map[string]string{},
importPrefix: "github.com/example/repo",
},
nil,
},
{
"single import parameter starting with 'M'",
"Mrpcutil/empty.proto=github.com/example/rpcutil",
&commandLineParams{
importMap: map[string]string{
"rpcutil/empty.proto": "github.com/example/rpcutil",
},
},
nil,
},
{
"multiple import parameters starting with 'M'",
"Mrpcutil/empty.proto=github.com/example/rpcutil,Mrpc/haberdasher/service.proto=github.com/example/rpc/haberdasher",
&commandLineParams{
importMap: map[string]string{
"rpcutil/empty.proto": "github.com/example/rpcutil",
"rpc/haberdasher/service.proto": "github.com/example/rpc/haberdasher",
},
},
nil,
},
{
"single import parameter starting with 'go_import_mapping@'",
"go_import_mapping@rpcutil/empty.proto=github.com/example/rpcutil",
&commandLineParams{
importMap: map[string]string{
"rpcutil/empty.proto": "github.com/example/rpcutil",
},
},
nil,
},
{
"multiple import parameters starting with 'go_import_mapping@'",
"go_import_mapping@rpcutil/empty.proto=github.com/example/rpcutil,go_import_mapping@rpc/haberdasher/service.proto=github.com/example/rpc/haberdasher",
&commandLineParams{
importMap: map[string]string{
"rpcutil/empty.proto": "github.com/example/rpcutil",
"rpc/haberdasher/service.proto": "github.com/example/rpc/haberdasher",
},
},
nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
params, err := parseCommandLineParams(tt.parameter)
switch {
case err != nil:
if tt.err == nil {
t.Fatal(err)
}
if err.Error() != tt.err.Error() {
t.Errorf("got error = %v, want %v", err, tt.err)
}
case err == nil:
if tt.err != nil {
t.Errorf("got error = %v, want %v", err, tt.err)
}
}
if !reflect.DeepEqual(params, tt.params) {
t.Errorf("got params = %v, want %v", params, tt.params)
}
})
}
}

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
)

View File

@@ -0,0 +1,530 @@
// 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 main
import (
"bufio"
"bytes"
"compress/gzip"
"fmt"
"go/parser"
"go/printer"
"go/token"
"path"
"strconv"
"strings"
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen"
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen/stringutils"
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen/typemap"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
"github.com/pkg/errors"
)
type liverpc struct {
filesHandled int
reg *typemap.Registry
// Map to record whether we've built each package
pkgs map[string]string
pkgNamesInUse map[string]bool
importPrefix string // String to prefix to imported package file names.
importMap map[string]string // Mapping from .proto file name to import path.
// Package naming:
genPkgName string // Name of the package that we're generating
fileToGoPackageName map[*descriptor.FileDescriptorProto]string
// List of files that were inputs to the generator. We need to hold this in
// the struct so we can write a header for the file that lists its inputs.
genFiles []*descriptor.FileDescriptorProto
// Output buffer that holds the bytes we want to write out for a single file.
// Gets reset after working on a file.
output *bytes.Buffer
}
func liveRPCGenerator() *liverpc {
t := &liverpc{
pkgs: make(map[string]string),
pkgNamesInUse: make(map[string]bool),
importMap: make(map[string]string),
fileToGoPackageName: make(map[*descriptor.FileDescriptorProto]string),
output: bytes.NewBuffer(nil),
}
return t
}
func (t *liverpc) Generate(in *plugin.CodeGeneratorRequest) *plugin.CodeGeneratorResponse {
params, err := parseCommandLineParams(in.GetParameter())
if err != nil {
gen.Fail("could not parse parameters passed to --liverpc_out", err.Error())
}
t.importPrefix = params.importPrefix
t.importMap = params.importMap
t.genFiles = gen.FilesToGenerate(in)
// Collect information on types.
t.reg = typemap.New(in.ProtoFile)
t.registerPackageName("context")
t.registerPackageName("ioutil")
t.registerPackageName("proto")
t.registerPackageName("liverpc")
// Time to figure out package names of objects defined in protobuf. First,
// we'll figure out the name for the package we're generating.
genPkgName, err := deduceGenPkgName(t.genFiles)
if err != nil {
gen.Fail(err.Error())
}
t.genPkgName = genPkgName
// Next, we need to pick names for all the files that are dependencies.
for _, f := range in.ProtoFile {
if fileDescSliceContains(t.genFiles, f) {
// This is a file we are generating. It gets the shared package name.
t.fileToGoPackageName[f] = t.genPkgName
} else {
// This is a dependency. Use its package name.
name := f.GetPackage()
if name == "" {
name = stringutils.BaseName(f.GetName())
}
name = stringutils.CleanIdentifier(name)
alias := t.registerPackageName(name)
t.fileToGoPackageName[f] = alias
}
}
// Showtime! Generate the response.
resp := new(plugin.CodeGeneratorResponse)
var servicesNames []string
for _, f := range t.genFiles {
respFile := t.generate(f)
for _, s := range f.Service {
servicesNames = append(servicesNames, *s.Name)
}
if respFile != nil {
resp.File = append(resp.File, respFile)
}
}
// generate a temp file of service names
// because a protobuf plugin can only generate for a single package
// therefore we generate these temp files for other script to combine
// a single client for all packages
var filename = "client." + genPkgName + ".txt"
var respFile = &plugin.CodeGeneratorResponse_File{}
respFile.Name = &filename
var content = strings.Join(servicesNames, "\n")
content += "\n"
respFile.Content = &content
resp.File = append(resp.File, respFile)
return resp
}
func (t *liverpc) registerPackageName(name string) (alias string) {
alias = name
i := 1
for t.pkgNamesInUse[alias] {
alias = name + strconv.Itoa(i)
i++
}
t.pkgNamesInUse[alias] = true
t.pkgs[name] = alias
return alias
}
func (t *liverpc) generate(file *descriptor.FileDescriptorProto) *plugin.CodeGeneratorResponse_File {
resp := new(plugin.CodeGeneratorResponse_File)
if len(file.Service) == 0 {
return nil
}
t.generateFileHeader(file)
t.generateImports(file)
if t.filesHandled == 0 {
t.generateUtilImports()
}
// For each service, generate client stubs and server
for i, service := range file.Service {
t.generateService(file, service, i)
}
// Util functions only generated once per package
if t.filesHandled == 0 {
t.generateUtils()
}
t.generateFileDescriptor(file)
resp.Name = proto.String(goFileName(file))
resp.Content = proto.String(t.formattedOutput())
t.output.Reset()
t.filesHandled++
return resp
}
func (t *liverpc) generateFileHeader(file *descriptor.FileDescriptorProto) {
t.P("// Code generated by protoc-gen-liverpc ", gen.Version, ", DO NOT EDIT.")
t.P("// source: ", file.GetName())
t.P()
if t.filesHandled == 0 {
t.P("/*")
t.P("Package ", t.genPkgName, " is a generated liverpc stub package.")
t.P("This code was generated with go-common/app/tool/liverpc/protoc-gen-liverpc ", gen.Version, ".")
t.P()
comment, err := t.reg.FileComments(file)
if err == nil && comment.Leading != "" {
for _, line := range strings.Split(comment.Leading, "\n") {
line = strings.TrimPrefix(line, " ")
// ensure we don't escape from the block comment
line = strings.Replace(line, "*/", "* /", -1)
t.P(line)
}
t.P()
}
t.P("It is generated from these files:")
for _, f := range t.genFiles {
t.P("\t", f.GetName())
}
t.P("*/")
}
t.P(`package `, t.genPkgName)
t.P()
}
func (t *liverpc) generateImports(file *descriptor.FileDescriptorProto) {
if len(file.Service) == 0 {
return
}
t.P(`import `, t.pkgs["context"], ` "context"`)
t.P()
t.P(`import `, t.pkgs["proto"], ` "github.com/golang/protobuf/proto"`)
t.P(`import "go-common/library/net/rpc/liverpc"`)
t.P()
// It's legal to import a message and use it as an input or output for a
// method. Make sure to import the package of any such message. First, dedupe
// them.
deps := make(map[string]string) // Map of package name to quoted import path.
ourImportPath := path.Dir(goFileName(file))
for _, s := range file.Service {
for _, m := range s.Method {
defs := []*typemap.MessageDefinition{
t.reg.MethodInputDefinition(m),
t.reg.MethodOutputDefinition(m),
}
for _, def := range defs {
// By default, import path is the dirname of the Go filename.
importPath := path.Dir(goFileName(def.File))
if importPath == ourImportPath {
continue
}
if substitution, ok := t.importMap[def.File.GetName()]; ok {
importPath = substitution
}
importPath = t.importPrefix + importPath
pkg := t.goPackageName(def.File)
deps[pkg] = strconv.Quote(importPath)
}
}
}
for pkg, importPath := range deps {
t.P(`import `, pkg, ` `, importPath)
}
if len(deps) > 0 {
t.P()
}
t.P(`var _ proto.Message // generate to suppress unused imports`)
}
func (t *liverpc) generateUtilImports() {
t.P("// Imports only used by utility functions:")
//t.P(`import `, t.pkgs["io"], ` "io"`)
//t.P(`import `, t.pkgs["strconv"], ` "strconv"`)
//t.P(`import `, t.pkgs["json"], ` "encoding/json"`)
//t.P(`import `, t.pkgs["url"], ` "net/url"`)
}
// Generate utility functions used in LiveRpc code.
// These should be generated just once per package.
func (t *liverpc) generateUtils() {
t.sectionComment(`Utils`)
t.P(`func doRPCRequest(ctx `, t.pkgs["context"], `.Context, client *liverpc.Client, version int, method string, in, out `, t.pkgs["proto"], `.Message, opts []liverpc.CallOption) (err error) {`)
t.P(` err = client.Call(ctx, version, method, in, out, opts...)`)
t.P(` return`)
t.P(`}`)
t.P()
}
// P forwards to g.gen.P, which prints output.
func (t *liverpc) P(args ...string) {
for _, v := range args {
t.output.WriteString(v)
}
t.output.WriteByte('\n')
}
// Big header comments to makes it easier to visually parse a generated file.
func (t *liverpc) sectionComment(sectionTitle string) {
t.P()
t.P(`// `, strings.Repeat("=", len(sectionTitle)))
t.P(`// `, sectionTitle)
t.P(`// `, strings.Repeat("=", len(sectionTitle)))
t.P()
}
func (t *liverpc) generateService(file *descriptor.FileDescriptorProto, service *descriptor.ServiceDescriptorProto, index int) {
servName := serviceName(service)
t.sectionComment(servName + ` Interface`)
t.generateLiveRPCInterface(file, service)
t.sectionComment(servName + ` Live Rpc Client`)
t.generateClient(file, service)
}
func (t *liverpc) generateLiveRPCInterface(file *descriptor.FileDescriptorProto, service *descriptor.ServiceDescriptorProto) {
comments, err := t.reg.ServiceComments(file, service)
if err == nil {
t.printComments(comments)
}
t.P(`type `, clientName(service), ` interface {`)
for _, method := range service.Method {
comments, err = t.reg.MethodComments(file, service, method)
if err == nil {
t.printComments(comments)
}
t.P(t.generateSignature(method))
t.P()
}
t.P(`}`)
}
func (t *liverpc) generateSignature(method *descriptor.MethodDescriptorProto) string {
methName := methodName(method)
inputBodyType := t.goTypeName(method.GetInputType())
outputType := t.goTypeName(method.GetOutputType())
return fmt.Sprintf(` %s(ctx %s.Context, req *%s, opts ...liverpc.CallOption) (resp *%s, err error)`, methName, t.pkgs["context"], inputBodyType, outputType)
}
// valid names: 'JSON', 'Protobuf'
func (t *liverpc) generateClient(file *descriptor.FileDescriptorProto, service *descriptor.ServiceDescriptorProto) {
clientName := clientName(service)
structName := unexported(clientName)
newClientFunc := "New" + clientName
t.P(`type `, structName, ` struct {`)
t.P(` client *liverpc.Client`)
t.P(`}`)
t.P()
t.P(`// `, newClientFunc, ` creates a client that implements the `, clientName, ` interface.`)
t.P(`func `, newClientFunc, `(client *liverpc.Client) `, clientName, ` {`)
t.P(` return &`, structName, `{`)
t.P(` client: client,`)
t.P(` }`)
t.P(`}`)
t.P()
for _, method := range service.Method {
methName := methodName(method)
pkgName := pkgName(file)
inputType := t.goTypeName(method.GetInputType())
outputType := t.goTypeName(method.GetOutputType())
parts := strings.Split(pkgName, ".")
if len(parts) < 2 {
panic("package name must contain at least to parts, eg: service.v1, get " + pkgName + "!")
}
vStr := parts[len(parts)-1]
if len(vStr) < 2 {
panic("package name must contain a valid version, eg: service.v1")
}
_, err := strconv.Atoi(vStr[1:])
if err != nil {
panic("package name must contain a valid version, eg: service.v1, get " + vStr)
}
rpcMethod := method.GetName()
rpcCtrl := service.GetName()
rpcCmd := rpcCtrl + "." + rpcMethod
t.P(`func (c *`, structName, `) `, methName, `(ctx `, t.pkgs["context"], `.Context, in *`, inputType, `, opts ...liverpc.CallOption) (*`, outputType, `, error) {`)
t.P(` out := new(`, outputType, `)`)
t.P(` err := doRPCRequest(ctx,c.client, `, vStr[1:], `, "`, rpcCmd, `", in, out, opts)`)
t.P(` if err != nil {`)
t.P(` return nil, err`)
t.P(` }`)
t.P(` return out, nil`)
t.P(`}`)
t.P()
}
}
func (t *liverpc) generateFileDescriptor(file *descriptor.FileDescriptorProto) {
// Copied straight of of protoc-gen-go, which trims out comments.
pb := proto.Clone(file).(*descriptor.FileDescriptorProto)
pb.SourceCodeInfo = nil
b, err := proto.Marshal(pb)
if err != nil {
gen.Fail(err.Error())
}
var buf bytes.Buffer
w, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
w.Write(b)
w.Close()
buf.Bytes()
}
func (t *liverpc) printComments(comments typemap.DefinitionComments) bool {
text := strings.TrimSuffix(comments.Leading, "\n")
if len(strings.TrimSpace(text)) == 0 {
return false
}
split := strings.Split(text, "\n")
for _, line := range split {
t.P("// ", strings.TrimPrefix(line, " "))
}
return len(split) > 0
}
// Given a protobuf name for a Message, return the Go name we will use for that
// type, including its package prefix.
func (t *liverpc) goTypeName(protoName string) string {
def := t.reg.MessageDefinition(protoName)
if def == nil {
gen.Fail("could not find message for", protoName)
}
var prefix string
if pkg := t.goPackageName(def.File); pkg != t.genPkgName {
prefix = pkg + "."
}
var name string
for _, parent := range def.Lineage() {
name += parent.Descriptor.GetName() + "_"
}
name += def.Descriptor.GetName()
return prefix + name
}
func (t *liverpc) goPackageName(file *descriptor.FileDescriptorProto) string {
return t.fileToGoPackageName[file]
}
func (t *liverpc) formattedOutput() string {
// Reformat generated code.
fset := token.NewFileSet()
raw := t.output.Bytes()
ast, err := parser.ParseFile(fset, "", raw, parser.ParseComments)
if err != nil {
// Print out the bad code with line numbers.
// This should never happen in practice, but it can while changing generated code,
// so consider this a debugging aid.
var src bytes.Buffer
s := bufio.NewScanner(bytes.NewReader(raw))
for line := 1; s.Scan(); line++ {
fmt.Fprintf(&src, "%5d\t%s\n", line, s.Bytes())
}
gen.Fail("bad Go source code was generated:", err.Error(), "\n"+src.String())
}
out := bytes.NewBuffer(nil)
err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(out, fset, ast)
if err != nil {
gen.Fail("generated Go source code could not be reformatted:", err.Error())
}
return out.String()
}
func unexported(s string) string { return strings.ToLower(s[:1]) + s[1:] }
func pkgName(file *descriptor.FileDescriptorProto) string {
return file.GetPackage()
}
func serviceName(service *descriptor.ServiceDescriptorProto) string {
return stringutils.CamelCase(service.GetName())
}
func clientName(service *descriptor.ServiceDescriptorProto) string {
return serviceName(service) + "RPCClient"
}
func methodName(method *descriptor.MethodDescriptorProto) string {
return stringutils.CamelCase(method.GetName())
}
func fileDescSliceContains(slice []*descriptor.FileDescriptorProto, f *descriptor.FileDescriptorProto) bool {
for _, sf := range slice {
if f == sf {
return true
}
}
return false
}
// deduceGenPkgName figures out the go package name to use for generated code.
// Will try to use the explicit go_package setting in a file (if set, must be
// consistent in all files). If no files have go_package set, then use the
// protobuf package name (must be consistent in all files)
func deduceGenPkgName(genFiles []*descriptor.FileDescriptorProto) (string, error) {
var genPkgName string
for _, f := range genFiles {
name, explicit := goPackageName(f)
if explicit {
name = stringutils.CleanIdentifier(name)
if genPkgName != "" && genPkgName != name {
// Make sure they're all set consistently.
return "", errors.Errorf("files have conflicting go_package settings, must be the same: %q and %q", genPkgName, name)
}
genPkgName = name
}
}
if genPkgName != "" {
return genPkgName, nil
}
// If there is no explicit setting, then check the implicit package name
// (derived from the protobuf package name) of the files and make sure it's
// consistent.
for _, f := range genFiles {
name, _ := goPackageName(f)
name = stringutils.CleanIdentifier(name)
if genPkgName != "" && genPkgName != name {
return "", errors.Errorf("files have conflicting package names, must be the same or overridden with go_package: %q and %q", genPkgName, name)
}
genPkgName = name
}
// All the files have the same name, so we're good.
return genPkgName, nil
}

View File

@@ -0,0 +1,40 @@
// 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 main
import (
"os"
"os/exec"
"testing"
"github.com/golang/protobuf/proto"
plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
)
func TestGenerateParseCommandLineParamsError(t *testing.T) {
if os.Getenv("BE_CRASHER") == "1" {
g := &liverpc{}
g.Generate(&plugin.CodeGeneratorRequest{
Parameter: proto.String("invalid"),
})
return
}
cmd := exec.Command(os.Args[0], "-test.run=TestGenerateParseCommandLineParamsError")
cmd.Env = append(os.Environ(), "BE_CRASHER=1")
err := cmd.Run()
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
return
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}

View File

@@ -0,0 +1,86 @@
// 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 main
import (
"path"
"strings"
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen/stringutils"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
)
// goPackageOption interprets the file's go_package option.
// If there is no go_package, it returns ("", "", false).
// If there's a simple name, it returns ("", pkg, true).
// If the option implies an import path, it returns (impPath, pkg, true).
func goPackageOption(f *descriptor.FileDescriptorProto) (impPath, pkg string, ok bool) {
pkg = f.GetOptions().GetGoPackage()
if pkg == "" {
return
}
ok = true
// The presence of a slash implies there's an import path.
slash := strings.LastIndex(pkg, "/")
if slash < 0 {
return
}
impPath, pkg = pkg, pkg[slash+1:]
// A semicolon-delimited suffix overrides the package name.
sc := strings.IndexByte(impPath, ';')
if sc < 0 {
return
}
impPath, pkg = impPath[:sc], impPath[sc+1:]
return
}
// goPackageName returns the Go package name to use in the generated Go file.
// The result explicitly reports whether the name came from an option go_package
// statement. If explicit is false, the name was derived from the protocol
// buffer's package statement or the input file name.
func goPackageName(f *descriptor.FileDescriptorProto) (name string, explicit bool) {
// Does the file have a "go_package" option?
if _, pkg, ok := goPackageOption(f); ok {
return pkg, true
}
// Does the file have a package clause?
if pkg := f.GetPackage(); pkg != "" {
return pkg, false
}
// Use the file base name.
return stringutils.BaseName(f.GetName()), false
}
// goFileName returns the output name for the generated Go file.
func goFileName(f *descriptor.FileDescriptorProto) string {
name := *f.Name
if ext := path.Ext(name); ext == ".proto" || ext == ".protodevel" {
name = name[:len(name)-len(ext)]
}
name += ".liverpc.go"
// Does the file have a "go_package" option? If it does, it may override the
// filename.
if impPath, _, ok := goPackageOption(f); ok && impPath != "" {
// Replace the existing dirname with the declared import path.
_, name = path.Split(name)
name = path.Join(impPath, name)
return name
}
return name
}

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 main
import (
"flag"
"fmt"
"os"
"go-common/app/tool/liverpc/protoc-gen-liverpc/gen"
)
func main() {
versionFlag := flag.Bool("version", false, "print version and exit")
flag.Parse()
if *versionFlag {
fmt.Println(gen.Version)
os.Exit(0)
}
g := liveRPCGenerator()
gen.Main(g)
}