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>
diff --git a/.gitignore b/.gitignore
index 5c986d1..9c6aa35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.cache
vendor
cmd/protoc-gen-go/protoc-gen-go
+cmd/protoc-gen-go-grpc/protoc-gen-go-grpc
diff --git a/cmd/protoc-gen-go-grpc/golden_test.go b/cmd/protoc-gen-go-grpc/golden_test.go
new file mode 100644
index 0000000..e7efb3d
--- /dev/null
+++ b/cmd/protoc-gen-go-grpc/golden_test.go
@@ -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)
+}
diff --git a/cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go b/cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go
new file mode 100644
index 0000000..f8cf4b3
--- /dev/null
+++ b/cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go
@@ -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:] }
diff --git a/cmd/protoc-gen-go-grpc/main.go b/cmd/protoc-gen-go-grpc/main.go
new file mode 100644
index 0000000..238a63a
--- /dev/null
+++ b/cmd/protoc-gen-go-grpc/main.go
@@ -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
+ })
+}
diff --git a/cmd/protoc-gen-go-grpc/testdata/grpc/grpc.pb.go b/cmd/protoc-gen-go-grpc/testdata/grpc/grpc.pb.go
new file mode 100644
index 0000000..d18855e
--- /dev/null
+++ b/cmd/protoc-gen-go-grpc/testdata/grpc/grpc.pb.go
@@ -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,
+}
diff --git a/cmd/protoc-gen-go-grpc/testdata/grpc/grpc.proto b/cmd/protoc-gen-go-grpc/testdata/grpc/grpc.proto
new file mode 100644
index 0000000..c484ebe
--- /dev/null
+++ b/cmd/protoc-gen-go-grpc/testdata/grpc/grpc.proto
@@ -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);
+}
diff --git a/cmd/protoc-gen-go-grpc/testdata/grpc/grpc_grpc.pb.go b/cmd/protoc-gen-go-grpc/testdata/grpc/grpc_grpc.pb.go
new file mode 100644
index 0000000..9db0a3a
--- /dev/null
+++ b/cmd/protoc-gen-go-grpc/testdata/grpc/grpc_grpc.pb.go
@@ -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",
+}
diff --git a/cmd/protoc-gen-go/golden_test.go b/cmd/protoc-gen-go/golden_test.go
index 80e2d8c..e7efb3d 100644
--- a/cmd/protoc-gen-go/golden_test.go
+++ b/cmd/protoc-gen-go/golden_test.go
@@ -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)
}
diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index 518f427..a337c5f 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -10,6 +10,7 @@
"compress/gzip"
"crypto/sha256"
"encoding/hex"
+ "errors"
"flag"
"fmt"
"math"
@@ -33,12 +34,14 @@
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 @@
}
genInitFunction(gen, g, f)
-
genFileDescriptor(gen, g, f)
}
diff --git a/internal/protogen/goldentest/goldentest.go b/internal/protogen/goldentest/goldentest.go
new file mode 100644
index 0000000..82468b7
--- /dev/null
+++ b/internal/protogen/goldentest/goldentest.go
@@ -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)
+ }
+}
diff --git a/protogen/protogen.go b/protogen/protogen.go
index 03a2ff0..87e643c 100644
--- a/protogen/protogen.go
+++ b/protogen/protogen.go
@@ -349,6 +349,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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.
diff --git a/regenerate.bash b/regenerate.bash
index d6c8652..af3e487 100755
--- a/regenerate.bash
+++ b/regenerate.bash
@@ -11,24 +11,19 @@
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
diff --git a/test.bash b/test.bash
index 8d25906..7c625a5 100755
--- a/test.bash
+++ b/test.bash
@@ -85,6 +85,7 @@
}
go build ./...
+ go test ./...
go test -race ./...
go test -race -tags purego ./...
go test -race -tags proto1_legacy ./...