cmd/protoc-gen-go-grpc: add gRPC code generator

This is a straight translation of the v1 API gRPC "plugin" to protogen.

Add a protoc-gen-go-grpc command. The preferred way to generate gRPC
services is to invoke both plugins separately:

  protoc --go_out=. --go-grpc_out=. foo.proto

When invoked in this fashion, the generators will produce separate
foo.pb.go and foo_grpc.pb.go files.

Change-Id: Ie180385dab3da7063db96f7c2f9de3abbd749f63
Reviewed-on: https://go-review.googlesource.com/137037
Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
This commit is contained in:
Damien Neil 2018-09-21 15:03:34 -07:00
parent 0d1a064764
commit 2dc6718b59
13 changed files with 1096 additions and 125 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.cache
vendor
cmd/protoc-gen-go/protoc-gen-go
cmd/protoc-gen-go-grpc/protoc-gen-go-grpc

View File

@ -0,0 +1,25 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !race
package main
import (
"flag"
"testing"
"github.com/golang/protobuf/v2/internal/protogen/goldentest"
)
// Set --regenerate to regenerate the golden files.
var regenerate = flag.Bool("regenerate", false, "regenerate golden files")
func init() {
goldentest.Plugin(main)
}
func TestGolden(t *testing.T) {
goldentest.Run(t, *regenerate)
}

View File

@ -0,0 +1,409 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package internal_gengogrpc is internal to the protobuf module.
package internal_gengogrpc
import (
"fmt"
"strconv"
"strings"
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/golang/protobuf/v2/protogen"
)
type fileInfo struct {
*protogen.File
locationMap map[string][]*descpb.SourceCodeInfo_Location
}
// GenerateFile generates a _grpc.pb.go file containing gRPC service definitions.
func GenerateFile(gen *protogen.Plugin, f *protogen.File) {
if len(f.Services) == 0 {
return
}
filename := f.GeneratedFilenamePrefix + "_grpc.pb.go"
g := gen.NewGeneratedFile(filename, f.GoImportPath)
g.P("// Code generated by protoc-gen-go-grpc. DO NOT EDIT.")
g.P()
g.P("package ", f.GoPackageName)
g.P()
GenerateFileContent(gen, f, g)
}
// GenerateFileContent generates the gRPC service definitions, excluding the package statement.
func GenerateFileContent(gen *protogen.Plugin, f *protogen.File, g *protogen.GeneratedFile) {
if len(f.Services) == 0 {
return
}
file := &fileInfo{
File: f,
locationMap: make(map[string][]*descpb.SourceCodeInfo_Location),
}
for _, loc := range file.Proto.GetSourceCodeInfo().GetLocation() {
key := pathKey(loc.Path)
file.locationMap[key] = append(file.locationMap[key], loc)
}
// TODO: Remove this. We don't need to include these references any more.
g.P("// Reference imports to suppress errors if they are not otherwise used.")
g.P("var _ ", ident("context.Context"))
g.P("var _ ", ident("grpc.ClientConn"))
g.P()
g.P("// This is a compile-time assertion to ensure that this generated file")
g.P("// is compatible with the grpc package it is being compiled against.")
g.P("const _ = ", ident("grpc.SupportPackageIsVersion4"))
g.P()
for _, service := range file.Services {
genService(gen, file, g, service)
}
}
func genService(gen *protogen.Plugin, file *fileInfo, g *protogen.GeneratedFile, service *protogen.Service) {
clientName := service.GoName + "Client"
g.P("// ", clientName, " is the client API for ", service.GoName, " service.")
g.P("//")
g.P("// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.")
// Client interface.
// TODO deprecation
g.P("type ", clientName, " interface {")
for _, method := range service.Methods {
genComment(g, file, method.Path)
g.P(clientSignature(g, method))
}
g.P("}")
g.P()
// Client structure.
g.P("type ", unexport(clientName), " struct {")
g.P("cc *", ident("grpc.ClientConn"))
g.P("}")
g.P()
// NewClient factory.
// TODO deprecation
g.P("func New", clientName, " (cc *", ident("grpc.ClientConn"), ") ", clientName, " {")
g.P("return &", unexport(clientName), "{cc}")
g.P("}")
g.P()
var methodIndex, streamIndex int
// Client method implementations.
for _, method := range service.Methods {
if !method.Desc.IsStreamingServer() && !method.Desc.IsStreamingClient() {
// Unary RPC method
genClientMethod(gen, file, g, method, methodIndex)
methodIndex++
} else {
// Streaming RPC method
genClientMethod(gen, file, g, method, streamIndex)
streamIndex++
}
}
// Server interface.
serverType := service.GoName + "Server"
g.P("// ", serverType, " is the server API for ", service.GoName, " service.")
// TODO deprecation
g.P("type ", serverType, " interface {")
for _, method := range service.Methods {
genComment(g, file, method.Path)
g.P(serverSignature(g, method))
}
g.P("}")
g.P()
// Server registration.
// TODO deprecation
serviceDescVar := "_" + service.GoName + "_serviceDesc"
g.P("func Register", service.GoName, "Server(s *", ident("grpc.Server"), ", srv ", serverType, ") {")
g.P("s.RegisterService(&", serviceDescVar, `, srv)`)
g.P("}")
g.P()
// Server handler implementations.
var handlerNames []string
for _, method := range service.Methods {
hname := genServerMethod(gen, file, g, method)
handlerNames = append(handlerNames, hname)
}
// Service descriptor.
g.P("var ", serviceDescVar, " = ", ident("grpc.ServiceDesc"), " {")
g.P("ServiceName: ", strconv.Quote(string(service.Desc.FullName())), ",")
g.P("HandlerType: (*", serverType, ")(nil),")
g.P("Methods: []", ident("grpc.MethodDesc"), "{")
for i, method := range service.Methods {
if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() {
continue
}
g.P("{")
g.P("MethodName: ", strconv.Quote(method.GoName), ",")
g.P("Handler: ", handlerNames[i], ",")
g.P("},")
}
g.P("},")
g.P("Streams: []", ident("grpc.StreamDesc"), "{")
for i, method := range service.Methods {
if !method.Desc.IsStreamingClient() && !method.Desc.IsStreamingServer() {
continue
}
g.P("{")
g.P("StreamName: ", strconv.Quote(method.GoName), ",")
g.P("Handler: ", handlerNames[i], ",")
if method.Desc.IsStreamingServer() {
g.P("ServerStreams: true,")
}
if method.Desc.IsStreamingClient() {
g.P("ClientStreams: true,")
}
g.P("},")
}
g.P("},")
g.P("Metadata: \"", file.Desc.Path(), "\",")
g.P("}")
g.P()
}
func clientSignature(g *protogen.GeneratedFile, method *protogen.Method) string {
s := method.GoName + "(ctx " + g.QualifiedGoIdent(ident("context.Context"))
if !method.Desc.IsStreamingClient() {
s += ", in *" + g.QualifiedGoIdent(method.InputType.GoIdent)
}
s += ", opts ..." + g.QualifiedGoIdent(ident("grpc.CallOption")) + ") ("
if !method.Desc.IsStreamingClient() && !method.Desc.IsStreamingServer() {
s += "*" + g.QualifiedGoIdent(method.OutputType.GoIdent)
} else {
s += method.ParentService.GoName + "_" + method.GoName + "Client"
}
s += ", error)"
return s
}
func genClientMethod(gen *protogen.Plugin, file *fileInfo, g *protogen.GeneratedFile, method *protogen.Method, index int) {
service := method.ParentService
sname := fmt.Sprintf("/%s/%s", service.Desc.FullName(), method.Desc.Name())
// TODO deprecation
g.P("func (c *", unexport(service.GoName), "Client) ", clientSignature(g, method), "{")
if !method.Desc.IsStreamingServer() && !method.Desc.IsStreamingClient() {
g.P("out := new(", method.OutputType.GoIdent, ")")
g.P(`err := c.cc.Invoke(ctx, "`, sname, `", in, out, opts...)`)
g.P("if err != nil { return nil, err }")
g.P("return out, nil")
g.P("}")
g.P()
return
}
streamType := unexport(service.GoName) + method.GoName + "Client"
serviceDescVar := "_" + service.GoName + "_serviceDesc"
g.P("stream, err := c.cc.NewStream(ctx, &", serviceDescVar, ".Streams[", index, `], "`, sname, `", opts...)`)
g.P("if err != nil { return nil, err }")
g.P("x := &", streamType, "{stream}")
if !method.Desc.IsStreamingClient() {
g.P("if err := x.ClientStream.SendMsg(in); err != nil { return nil, err }")
g.P("if err := x.ClientStream.CloseSend(); err != nil { return nil, err }")
}
g.P("return x, nil")
g.P("}")
g.P()
genSend := method.Desc.IsStreamingClient()
genRecv := method.Desc.IsStreamingServer()
genCloseAndRecv := !method.Desc.IsStreamingServer()
// Stream auxiliary types and methods.
g.P("type ", service.GoName, "_", method.GoName, "Client interface {")
if genSend {
g.P("Send(*", method.InputType.GoIdent, ") error")
}
if genRecv {
g.P("Recv() (*", method.OutputType.GoIdent, ", error)")
}
if genCloseAndRecv {
g.P("CloseAndRecv() (*", method.OutputType.GoIdent, ", error)")
}
g.P(ident("grpc.ClientStream"))
g.P("}")
g.P()
g.P("type ", streamType, " struct {")
g.P(ident("grpc.ClientStream"))
g.P("}")
g.P()
if genSend {
g.P("func (x *", streamType, ") Send(m *", method.InputType.GoIdent, ") error {")
g.P("return x.ClientStream.SendMsg(m)")
g.P("}")
g.P()
}
if genRecv {
g.P("func (x *", streamType, ") Recv() (*", method.OutputType.GoIdent, ", error) {")
g.P("m := new(", method.OutputType.GoIdent, ")")
g.P("if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err }")
g.P("return m, nil")
g.P("}")
g.P()
}
if genCloseAndRecv {
g.P("func (x *", streamType, ") CloseAndRecv() (*", method.OutputType.GoIdent, ", error) {")
g.P("if err := x.ClientStream.CloseSend(); err != nil { return nil, err }")
g.P("m := new(", method.OutputType.GoIdent, ")")
g.P("if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err }")
g.P("return m, nil")
g.P("}")
g.P()
}
}
func serverSignature(g *protogen.GeneratedFile, method *protogen.Method) string {
var reqArgs []string
ret := "error"
if !method.Desc.IsStreamingClient() && !method.Desc.IsStreamingServer() {
reqArgs = append(reqArgs, g.QualifiedGoIdent(ident("context.Context")))
ret = "(*" + g.QualifiedGoIdent(method.OutputType.GoIdent) + ", error)"
}
if !method.Desc.IsStreamingClient() {
reqArgs = append(reqArgs, "*"+g.QualifiedGoIdent(method.InputType.GoIdent))
}
if method.Desc.IsStreamingClient() || method.Desc.IsStreamingServer() {
reqArgs = append(reqArgs, method.ParentService.GoName+"_"+method.GoName+"Server")
}
return method.GoName + "(" + strings.Join(reqArgs, ", ") + ") " + ret
}
func genServerMethod(gen *protogen.Plugin, file *fileInfo, g *protogen.GeneratedFile, method *protogen.Method) string {
service := method.ParentService
hname := fmt.Sprintf("_%s_%s_Handler", service.GoName, method.GoName)
if !method.Desc.IsStreamingClient() && !method.Desc.IsStreamingServer() {
g.P("func ", hname, "(srv interface{}, ctx ", ident("context.Context"), ", dec func(interface{}) error, interceptor ", ident("grpc.UnaryServerInterceptor"), ") (interface{}, error) {")
g.P("in := new(", method.InputType.GoIdent, ")")
g.P("if err := dec(in); err != nil { return nil, err }")
g.P("if interceptor == nil { return srv.(", service.GoName, "Server).", method.GoName, "(ctx, in) }")
g.P("info := &", ident("grpc.UnaryServerInfo"), "{")
g.P("Server: srv,")
g.P("FullMethod: ", strconv.Quote(fmt.Sprintf("/%s/%s", service.Desc.FullName(), method.Desc.Name())), ",")
g.P("}")
g.P("handler := func(ctx ", ident("context.Context"), ", req interface{}) (interface{}, error) {")
g.P("return srv.(", service.GoName, "Server).", method.GoName, "(ctx, req.(*", method.InputType.GoIdent, "))")
g.P("}")
g.P("return interceptor(ctx, in, info, handler)")
g.P("}")
g.P()
return hname
}
streamType := unexport(service.GoName) + method.GoName + "Server"
g.P("func ", hname, "(srv interface{}, stream ", ident("grpc.ServerStream"), ") error {")
if !method.Desc.IsStreamingClient() {
g.P("m := new(", method.InputType.GoIdent, ")")
g.P("if err := stream.RecvMsg(m); err != nil { return err }")
g.P("return srv.(", service.GoName, "Server).", method.GoName, "(m, &", streamType, "{stream})")
} else {
g.P("return srv.(", service.GoName, "Server).", method.GoName, "(&", streamType, "{stream})")
}
g.P("}")
g.P()
genSend := method.Desc.IsStreamingServer()
genSendAndClose := !method.Desc.IsStreamingServer()
genRecv := method.Desc.IsStreamingClient()
// Stream auxiliary types and methods.
g.P("type ", service.GoName, "_", method.GoName, "Server interface {")
if genSend {
g.P("Send(*", method.OutputType.GoIdent, ") error")
}
if genSendAndClose {
g.P("SendAndClose(*", method.OutputType.GoIdent, ") error")
}
if genRecv {
g.P("Recv() (*", method.InputType.GoIdent, ", error)")
}
g.P(ident("grpc.ServerStream"))
g.P("}")
g.P()
g.P("type ", streamType, " struct {")
g.P(ident("grpc.ServerStream"))
g.P("}")
g.P()
if genSend {
g.P("func (x *", streamType, ") Send(m *", method.OutputType.GoIdent, ") error {")
g.P("return x.ServerStream.SendMsg(m)")
g.P("}")
g.P()
}
if genSendAndClose {
g.P("func (x *", streamType, ") SendAndClose(m *", method.OutputType.GoIdent, ") error {")
g.P("return x.ServerStream.SendMsg(m)")
g.P("}")
g.P()
}
if genRecv {
g.P("func (x *", streamType, ") Recv() (*", method.InputType.GoIdent, ", error) {")
g.P("m := new(", method.InputType.GoIdent, ")")
g.P("if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err }")
g.P("return m, nil")
g.P("}")
g.P()
}
return hname
}
var packages = map[string]protogen.GoImportPath{
"context": "golang.org/x/net/context",
"grpc": "google.golang.org/grpc",
}
func ident(name string) protogen.GoIdent {
idx := strings.LastIndex(name, ".")
return protogen.GoIdent{
GoImportPath: packages[name[:idx]],
GoName: name[idx+1:],
}
}
func genComment(g *protogen.GeneratedFile, file *fileInfo, path []int32) (hasComment bool) {
for _, loc := range file.locationMap[pathKey(path)] {
if loc.LeadingComments == nil {
continue
}
for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") {
hasComment = true
g.P("//", line)
}
break
}
return hasComment
}
// deprecationComment returns a standard deprecation comment if deprecated is true.
func deprecationComment(deprecated bool) string {
if !deprecated {
return ""
}
return "// Deprecated: Do not use."
}
// pathKey converts a location path to a string suitable for use as a map key.
func pathKey(path []int32) string {
var buf []byte
for i, x := range path {
if i != 0 {
buf = append(buf, ',')
}
buf = strconv.AppendInt(buf, int64(x), 10)
}
return string(buf)
}
func unexport(s string) string { return strings.ToLower(s[:1]) + s[1:] }

View File

@ -0,0 +1,24 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The protoc-gen-go-grpc binary is a protoc plugin to generate Go gRPC
// service definitions.
package main
import (
"github.com/golang/protobuf/v2/cmd/protoc-gen-go-grpc/internal_gengogrpc"
"github.com/golang/protobuf/v2/protogen"
)
func main() {
protogen.Run(nil, func(gen *protogen.Plugin) error {
for _, file := range gen.Files {
if !file.Generate {
continue
}
internal_gengogrpc.GenerateFile(gen, file)
}
return nil
})
}

View File

@ -0,0 +1,108 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: grpc/grpc.proto
package grpc
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Request struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_81ea47a3f88c2082, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
type Response struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_81ea47a3f88c2082, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func init() {
proto.RegisterType((*Request)(nil), "goproto.protoc.grpc.Request")
proto.RegisterType((*Response)(nil), "goproto.protoc.grpc.Response")
}
func init() { proto.RegisterFile("grpc/grpc.proto", fileDescriptor_81ea47a3f88c2082) }
var fileDescriptor_81ea47a3f88c2082 = []byte{
// 211 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4f, 0x2f, 0x2a, 0x48,
0xd6, 0x07, 0x11, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xc2, 0xe9, 0xf9, 0x60, 0x06, 0x84,
0x9b, 0xac, 0x07, 0x92, 0x52, 0xe2, 0xe4, 0x62, 0x0f, 0x4a, 0x2d, 0x2c, 0x4d, 0x2d, 0x2e, 0x51,
0xe2, 0xe2, 0xe2, 0x08, 0x4a, 0x2d, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x35, 0xda, 0xc8, 0xc4, 0xc5,
0x12, 0x92, 0x5a, 0x5c, 0x22, 0xe4, 0xc1, 0xc5, 0x19, 0x9a, 0x97, 0x58, 0x54, 0xe9, 0x9c, 0x98,
0x93, 0x23, 0x24, 0xa3, 0x87, 0xc5, 0x08, 0x3d, 0xa8, 0x7e, 0x29, 0x59, 0x1c, 0xb2, 0x10, 0x23,
0x85, 0xbc, 0xb9, 0xb8, 0x5c, 0xf2, 0xcb, 0xf3, 0x8a, 0x4b, 0x8a, 0x52, 0x13, 0x73, 0x29, 0x32,
0xca, 0x80, 0x51, 0xc8, 0x93, 0x8b, 0x23, 0xb4, 0x80, 0x0a, 0x46, 0x69, 0x30, 0x0a, 0xb9, 0x73,
0xb1, 0x38, 0x65, 0xa6, 0x64, 0x52, 0x68, 0x8c, 0x01, 0xa3, 0x93, 0x7d, 0x94, 0x6d, 0x7a, 0x66,
0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x7a, 0x7e, 0x4e, 0x62, 0x5e, 0xba, 0x3e,
0x58, 0x75, 0x52, 0x69, 0x9a, 0x7e, 0x99, 0x91, 0x7e, 0x72, 0x6e, 0x0a, 0x84, 0x9f, 0xac, 0x9b,
0x9e, 0x9a, 0xa7, 0x9b, 0x9e, 0xaf, 0x5f, 0x92, 0x5a, 0x5c, 0x92, 0x92, 0x58, 0x92, 0x08, 0x8e,
0xa6, 0x24, 0x36, 0xb0, 0xa4, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x29, 0xd5, 0xc4, 0xd0, 0xba,
0x01, 0x00, 0x00,
}

View File

@ -0,0 +1,25 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
syntax = "proto3";
package goproto.protoc.grpc;
option go_package = "github.com/golang/protobuf/v2/cmd/protoc-gen-go/testdata/grpc";
message Request {}
message Response {}
service Test {
rpc UnaryCall(Request) returns (Response);
// This RPC streams from the server only.
rpc Downstream(Request) returns (stream Response);
// This RPC streams from the client.
rpc Upstream(stream Request) returns (Response);
// This one streams in both directions.
rpc Bidi(stream Request) returns (stream Response);
}

View File

@ -0,0 +1,279 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package grpc
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// TestClient is the client API for Test service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type TestClient interface {
UnaryCall(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
// This RPC streams from the server only.
Downstream(ctx context.Context, in *Request, opts ...grpc.CallOption) (Test_DownstreamClient, error)
// This RPC streams from the client.
Upstream(ctx context.Context, opts ...grpc.CallOption) (Test_UpstreamClient, error)
// This one streams in both directions.
Bidi(ctx context.Context, opts ...grpc.CallOption) (Test_BidiClient, error)
}
type testClient struct {
cc *grpc.ClientConn
}
func NewTestClient(cc *grpc.ClientConn) TestClient {
return &testClient{cc}
}
func (c *testClient) UnaryCall(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/goproto.protoc.grpc.Test/UnaryCall", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *testClient) Downstream(ctx context.Context, in *Request, opts ...grpc.CallOption) (Test_DownstreamClient, error) {
stream, err := c.cc.NewStream(ctx, &_Test_serviceDesc.Streams[0], "/goproto.protoc.grpc.Test/Downstream", opts...)
if err != nil {
return nil, err
}
x := &testDownstreamClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type Test_DownstreamClient interface {
Recv() (*Response, error)
grpc.ClientStream
}
type testDownstreamClient struct {
grpc.ClientStream
}
func (x *testDownstreamClient) Recv() (*Response, error) {
m := new(Response)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *testClient) Upstream(ctx context.Context, opts ...grpc.CallOption) (Test_UpstreamClient, error) {
stream, err := c.cc.NewStream(ctx, &_Test_serviceDesc.Streams[1], "/goproto.protoc.grpc.Test/Upstream", opts...)
if err != nil {
return nil, err
}
x := &testUpstreamClient{stream}
return x, nil
}
type Test_UpstreamClient interface {
Send(*Request) error
CloseAndRecv() (*Response, error)
grpc.ClientStream
}
type testUpstreamClient struct {
grpc.ClientStream
}
func (x *testUpstreamClient) Send(m *Request) error {
return x.ClientStream.SendMsg(m)
}
func (x *testUpstreamClient) CloseAndRecv() (*Response, error) {
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
m := new(Response)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *testClient) Bidi(ctx context.Context, opts ...grpc.CallOption) (Test_BidiClient, error) {
stream, err := c.cc.NewStream(ctx, &_Test_serviceDesc.Streams[2], "/goproto.protoc.grpc.Test/Bidi", opts...)
if err != nil {
return nil, err
}
x := &testBidiClient{stream}
return x, nil
}
type Test_BidiClient interface {
Send(*Request) error
Recv() (*Response, error)
grpc.ClientStream
}
type testBidiClient struct {
grpc.ClientStream
}
func (x *testBidiClient) Send(m *Request) error {
return x.ClientStream.SendMsg(m)
}
func (x *testBidiClient) Recv() (*Response, error) {
m := new(Response)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// TestServer is the server API for Test service.
type TestServer interface {
UnaryCall(context.Context, *Request) (*Response, error)
// This RPC streams from the server only.
Downstream(*Request, Test_DownstreamServer) error
// This RPC streams from the client.
Upstream(Test_UpstreamServer) error
// This one streams in both directions.
Bidi(Test_BidiServer) error
}
func RegisterTestServer(s *grpc.Server, srv TestServer) {
s.RegisterService(&_Test_serviceDesc, srv)
}
func _Test_UnaryCall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TestServer).UnaryCall(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/goproto.protoc.grpc.Test/UnaryCall",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TestServer).UnaryCall(ctx, req.(*Request))
}
return interceptor(ctx, in, info, handler)
}
func _Test_Downstream_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(Request)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(TestServer).Downstream(m, &testDownstreamServer{stream})
}
type Test_DownstreamServer interface {
Send(*Response) error
grpc.ServerStream
}
type testDownstreamServer struct {
grpc.ServerStream
}
func (x *testDownstreamServer) Send(m *Response) error {
return x.ServerStream.SendMsg(m)
}
func _Test_Upstream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(TestServer).Upstream(&testUpstreamServer{stream})
}
type Test_UpstreamServer interface {
SendAndClose(*Response) error
Recv() (*Request, error)
grpc.ServerStream
}
type testUpstreamServer struct {
grpc.ServerStream
}
func (x *testUpstreamServer) SendAndClose(m *Response) error {
return x.ServerStream.SendMsg(m)
}
func (x *testUpstreamServer) Recv() (*Request, error) {
m := new(Request)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _Test_Bidi_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(TestServer).Bidi(&testBidiServer{stream})
}
type Test_BidiServer interface {
Send(*Response) error
Recv() (*Request, error)
grpc.ServerStream
}
type testBidiServer struct {
grpc.ServerStream
}
func (x *testBidiServer) Send(m *Response) error {
return x.ServerStream.SendMsg(m)
}
func (x *testBidiServer) Recv() (*Request, error) {
m := new(Request)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
var _Test_serviceDesc = grpc.ServiceDesc{
ServiceName: "goproto.protoc.grpc.Test",
HandlerType: (*TestServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "UnaryCall",
Handler: _Test_UnaryCall_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "Downstream",
Handler: _Test_Downstream_Handler,
ServerStreams: true,
},
{
StreamName: "Upstream",
Handler: _Test_Upstream_Handler,
ClientStreams: true,
},
{
StreamName: "Bidi",
Handler: _Test_Bidi_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "grpc/grpc.proto",
}

View File

@ -2,129 +2,24 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !race
package main
import (
"bytes"
"flag"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/golang/protobuf/v2/internal/protogen/goldentest"
)
// Set --regenerate to regenerate the golden files.
var regenerate = flag.Bool("regenerate", false, "regenerate golden files")
// When the environment variable RUN_AS_PROTOC_GEN_GO is set, we skip running
// tests and instead act as protoc-gen-go. This allows the test binary to
// pass itself to protoc.
func init() {
if os.Getenv("RUN_AS_PROTOC_GEN_GO") != "" {
main()
os.Exit(0)
}
goldentest.Plugin(main)
}
func TestGolden(t *testing.T) {
workdir, err := ioutil.TempDir("", "proto-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(workdir)
// Find all the proto files we need to compile. We assume that each directory
// contains the files for a single package.
packages := map[string][]string{}
err = filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
if !strings.HasSuffix(path, ".proto") {
return nil
}
dir := filepath.Dir(path)
packages[dir] = append(packages[dir], path)
return nil
})
if err != nil {
t.Fatal(err)
}
// Compile each package, using this binary as protoc-gen-go.
for _, sources := range packages {
args := []string{"-Itestdata", "--go_out=plugins=grpc,paths=source_relative:" + workdir}
args = append(args, sources...)
protoc(t, args)
}
// Compare each generated file to the golden version.
filepath.Walk(workdir, func(genPath string, info os.FileInfo, _ error) error {
if info.IsDir() {
return nil
}
// For each generated file, figure out the path to the corresponding
// golden file in the testdata directory.
relPath, err := filepath.Rel(workdir, genPath)
if err != nil {
t.Errorf("filepath.Rel(%q, %q): %v", workdir, genPath, err)
return nil
}
if filepath.SplitList(relPath)[0] == ".." {
t.Errorf("generated file %q is not relative to %q", genPath, workdir)
}
goldenPath := filepath.Join("testdata", relPath)
got, err := ioutil.ReadFile(genPath)
if err != nil {
t.Error(err)
return nil
}
if *regenerate {
// If --regenerate set, just rewrite the golden files.
err := ioutil.WriteFile(goldenPath, got, 0666)
if err != nil {
t.Error(err)
}
return nil
}
want, err := ioutil.ReadFile(goldenPath)
if err != nil {
t.Error(err)
return nil
}
want = fdescRE.ReplaceAll(want, nil)
got = fdescRE.ReplaceAll(got, nil)
if bytes.Equal(got, want) {
return nil
}
cmd := exec.Command("diff", "-u", goldenPath, genPath)
out, _ := cmd.CombinedOutput()
t.Errorf("golden file differs: %v\n%v", relPath, string(out))
return nil
})
}
var fdescRE = regexp.MustCompile(`(?ms)^var fileDescriptor.*}`)
func protoc(t *testing.T, args []string) {
cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
cmd.Args = append(cmd.Args, args...)
// We set the RUN_AS_PROTOC_GEN_GO environment variable to indicate that
// the subprocess should act as a proto compiler rather than a test.
cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_GEN_GO=1")
out, err := cmd.CombinedOutput()
if len(out) > 0 || err != nil {
t.Log("RUNNING: ", strings.Join(cmd.Args, " "))
}
if len(out) > 0 {
t.Log(string(out))
}
if err != nil {
t.Fatalf("protoc: %v", err)
}
goldentest.Run(t, *regenerate)
}

View File

@ -10,6 +10,7 @@ import (
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"errors"
"flag"
"fmt"
"math"
@ -33,12 +34,14 @@ const protoPackage = "github.com/golang/protobuf/proto"
func Main() {
var flags flag.FlagSet
// TODO: Decide what to do for backwards compatibility with plugins=grpc.
flags.String("plugins", "", "")
plugins := flags.String("plugins", "", "deprecated option")
opts := &protogen.Options{
ParamFunc: flags.Set,
}
protogen.Run(opts, func(gen *protogen.Plugin) error {
if *plugins != "" {
return errors.New("protoc-gen-go: plugins are not supported; use 'protoc --go-grpc_out=...' to generate gRPC")
}
for _, f := range gen.Files {
if !f.Generate {
continue
@ -138,7 +141,6 @@ func genFile(gen *protogen.Plugin, file *protogen.File) {
}
genInitFunction(gen, g, f)
genFileDescriptor(gen, g, f)
}

View File

@ -0,0 +1,130 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package goldentest compares the output of a protoc plugin to golden files.
package goldentest
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
)
// Plugin should be called at init time with a function that acts as a
// protoc plugin.
func Plugin(f func()) {
// When the environment variable RUN_AS_PROTOC_PLUGIN is set, we skip
// running tests and instead act as protoc-gen-go. This allows the
// test binary to pass itself to protoc.
if os.Getenv("RUN_AS_PROTOC_PLUGIN") != "" {
f()
os.Exit(0)
}
}
// Run executes golden tests.
func Run(t *testing.T, regenerate bool) {
workdir, err := ioutil.TempDir("", "proto-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(workdir)
// Find all the proto files we need to compile. We assume that each directory
// contains the files for a single package.
packages := map[string][]string{}
err = filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
if !strings.HasSuffix(path, ".proto") {
return nil
}
dir := filepath.Dir(path)
packages[dir] = append(packages[dir], path)
return nil
})
if err != nil {
t.Fatal(err)
}
// Compile each package, using this binary as protoc-gen-go.
for _, sources := range packages {
args := []string{"-Itestdata", "--go_out=paths=source_relative:" + workdir}
args = append(args, sources...)
protoc(t, args)
}
// Compare each generated file to the golden version.
filepath.Walk(workdir, func(genPath string, info os.FileInfo, _ error) error {
if info.IsDir() {
return nil
}
// For each generated file, figure out the path to the corresponding
// golden file in the testdata directory.
relPath, err := filepath.Rel(workdir, genPath)
if err != nil {
t.Errorf("filepath.Rel(%q, %q): %v", workdir, genPath, err)
return nil
}
if filepath.SplitList(relPath)[0] == ".." {
t.Errorf("generated file %q is not relative to %q", genPath, workdir)
}
goldenPath := filepath.Join("testdata", relPath)
got, err := ioutil.ReadFile(genPath)
if err != nil {
t.Error(err)
return nil
}
if regenerate {
// If --regenerate set, just rewrite the golden files.
err := ioutil.WriteFile(goldenPath, got, 0666)
if err != nil {
t.Error(err)
}
return nil
}
want, err := ioutil.ReadFile(goldenPath)
if err != nil {
t.Error(err)
return nil
}
want = fdescRE.ReplaceAll(want, nil)
got = fdescRE.ReplaceAll(got, nil)
if bytes.Equal(got, want) {
return nil
}
cmd := exec.Command("diff", "-u", goldenPath, genPath)
out, _ := cmd.CombinedOutput()
t.Errorf("golden file differs: %v\n%v", relPath, string(out))
return nil
})
}
var fdescRE = regexp.MustCompile(`(?ms)^var fileDescriptor.*}`)
func protoc(t *testing.T, args []string) {
cmd := exec.Command("protoc", "--plugin=protoc-gen-go="+os.Args[0])
cmd.Args = append(cmd.Args, args...)
// We set the RUN_AS_PROTOC_PLUGIN environment variable to indicate that
// the subprocess should act as a proto compiler rather than a test.
cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_PLUGIN=1")
out, err := cmd.CombinedOutput()
if len(out) > 0 || err != nil {
t.Log("RUNNING: ", strings.Join(cmd.Args, " "))
}
if len(out) > 0 {
t.Log(string(out))
}
if err != nil {
t.Fatalf("protoc: %v", err)
}
}

View File

@ -349,6 +349,7 @@ type File struct {
Messages []*Message // top-level message declarations
Enums []*Enum // top-level enum declarations
Extensions []*Extension // top-level extension declarations
Services []*Service // top-level service declarations
Generate bool // true if we should generate code for this file
// GeneratedFilenamePrefix is used to construct filenames for generated
@ -401,6 +402,9 @@ func newFile(gen *Plugin, p *descpb.FileDescriptorProto, packageName GoPackageNa
for i, extdescs := 0, desc.Extensions(); i < extdescs.Len(); i++ {
f.Extensions = append(f.Extensions, newField(gen, f, nil, extdescs.Get(i)))
}
for i, sdescs := 0, desc.Services(); i < sdescs.Len(); i++ {
f.Services = append(f.Services, newService(gen, f, sdescs.Get(i)))
}
for _, message := range f.Messages {
if err := message.init(gen); err != nil {
return nil, err
@ -411,6 +415,13 @@ func newFile(gen *Plugin, p *descpb.FileDescriptorProto, packageName GoPackageNa
return nil, err
}
}
for _, service := range f.Services {
for _, method := range service.Methods {
if err := method.init(gen); err != nil {
return nil, err
}
}
}
return f, nil
}
@ -723,6 +734,68 @@ func (gen *Plugin) NewGeneratedFile(filename string, goImportPath GoImportPath)
return g
}
// A Service describes a service.
type Service struct {
Desc protoreflect.ServiceDescriptor
GoName string
Path []int32 // location path of this service
Methods []*Method // service method definitions
}
func newService(gen *Plugin, f *File, desc protoreflect.ServiceDescriptor) *Service {
service := &Service{
Desc: desc,
GoName: camelCase(string(desc.Name())),
Path: []int32{fileServiceField, int32(desc.Index())},
}
for i, mdescs := 0, desc.Methods(); i < mdescs.Len(); i++ {
service.Methods = append(service.Methods, newMethod(gen, f, service, mdescs.Get(i)))
}
return service
}
// A Method describes a method in a service.
type Method struct {
Desc protoreflect.MethodDescriptor
GoName string
ParentService *Service
Path []int32 // location path of this method
InputType *Message
OutputType *Message
}
func newMethod(gen *Plugin, f *File, service *Service, desc protoreflect.MethodDescriptor) *Method {
method := &Method{
Desc: desc,
GoName: camelCase(string(desc.Name())),
ParentService: service,
Path: pathAppend(service.Path, serviceMethodField, int32(desc.Index())),
}
return method
}
func (method *Method) init(gen *Plugin) error {
desc := method.Desc
inName := desc.InputType().FullName()
in, ok := gen.messagesByName[inName]
if !ok {
return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), inName)
}
method.InputType = in
outName := desc.OutputType().FullName()
out, ok := gen.messagesByName[outName]
if !ok {
return fmt.Errorf("method %v: no descriptor for type %v", desc.FullName(), outName)
}
method.OutputType = out
return nil
}
// P prints a line to the generated output. It converts each parameter to a
// string following the same rules as fmt.Print. It never inserts spaces
// between parameters.
@ -843,6 +916,7 @@ const (
filePackageField = 2 // package
fileMessageField = 4 // message_type
fileEnumField = 5 // enum_type
fileServiceField = 6 // service
fileExtensionField = 7 // extension
// field numbers in DescriptorProto
messageFieldField = 2 // field
@ -852,6 +926,9 @@ const (
messageOneofField = 8 // oneof_decl
// field numbers in EnumDescriptorProto
enumValueField = 2 // value
// field numbers in ServiceDescriptorProto
serviceMethodField = 2 // method
serviceStreamField = 4 // stream
)
// pathAppend appends elements to a location path.

View File

@ -11,24 +11,19 @@ trap 'rm -rf $tmpdir' EXIT
mkdir -p $tmpdir/bin
PATH=$tmpdir/bin:$PATH
GOBIN=$tmpdir/bin go install ./cmd/protoc-gen-go
# Public imports require at least Go 1.9.
supportTypeAliases=""
if go list -f '{{context.ReleaseTags}}' runtime | grep -q go1.9; then
supportTypeAliases=1
fi
GOBIN=$tmpdir/bin go install ./cmd/protoc-gen-go-grpc
# Generate various test protos.
PROTO_DIRS=(
cmd/protoc-gen-go/testdata
cmd/protoc-gen-go-grpc/testdata
)
for dir in ${PROTO_DIRS[@]}; do
for p in `find $dir -name "*.proto"`; do
if [[ $p == */import_public/* && ! $supportTypeAliases ]]; then
echo "# $p (skipped)"
continue;
fi
echo "# $p"
protoc -I$dir --go_out=paths=source_relative:$dir $p
protoc -I$dir \
--go_out=paths=source_relative:$dir \
--go-grpc_out=paths=source_relative:$dir \
$p
done
done

View File

@ -85,6 +85,7 @@ for GO_VERSION in ${GO_VERSIONS[@]}; do
}
go build ./...
go test ./...
go test -race ./...
go test -race -tags purego ./...
go test -race -tags proto1_legacy ./...