Damien Neil has uploaded this change for review.
cmd/protoc-gen-grpc_go: add gRPC code generator
This is a straight translation of the v1-api gRPC "plugin" to protogen.
Add a protoc-gen-grpc_go command. The preferred way to generate gRPC
services is to invoke both plugins separately:
protoc --go_out=. --grpc_go_out=. foo.proto
When invoked in this fashion, the generators will produce separate
foo.pb.go and foo_grpc.pb.go files.
The old "--go_out=plugins=grpc:." form is also supported for the
moment, in which case we still generate just one combined foo.pb.go.
Change-Id: Ie180385dab3da7063db96f7c2f9de3abbd749f63
---
M cmd/protoc-gen-go/internal_gengo/main.go
A cmd/protoc-gen-go/testdata/grpc/grpc.pb.go
A cmd/protoc-gen-go/testdata/grpc/grpc.proto
A cmd/protoc-gen-grpc_go/main.go
A internal/protogen/gengrpcgo/grpc.go
M protogen/protogen.go
M regenerate.bash
7 files changed, 931 insertions(+), 6 deletions(-)
diff --git a/cmd/protoc-gen-go/internal_gengo/main.go b/cmd/protoc-gen-go/internal_gengo/main.go
index 518f427..07643aa 100644
--- a/cmd/protoc-gen-go/internal_gengo/main.go
+++ b/cmd/protoc-gen-go/internal_gengo/main.go
@@ -19,6 +19,7 @@
"github.com/golang/protobuf/proto"
descpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
+ "github.com/golang/protobuf/v2/internal/protogen/gengrpcgo"
"github.com/golang/protobuf/v2/protogen"
"github.com/golang/protobuf/v2/reflect/protoreflect"
)
@@ -33,17 +34,20 @@
func Main() {
var flags flag.FlagSet
- // TODO: Decide what to do for backwards compatibility with plugins=grpc.
- flags.String("plugins", "", "")
+ plugins := flags.String("plugins", "", "list of plugins to invoke")
opts := &protogen.Options{
ParamFunc: flags.Set,
}
protogen.Run(opts, func(gen *protogen.Plugin) error {
+ enabledPlugins := make(map[string]bool)
+ for _, p := range strings.Split(*plugins, "+") {
+ enabledPlugins[p] = true
+ }
for _, f := range gen.Files {
if !f.Generate {
continue
}
- genFile(gen, f)
+ genFile(gen, f, enabledPlugins)
}
return nil
})
@@ -58,7 +62,7 @@
allExtensions []*protogen.Extension
}
-func genFile(gen *protogen.Plugin, file *protogen.File) {
+func genFile(gen *protogen.Plugin, file *protogen.File, plugins map[string]bool) {
f := &fileInfo{
File: file,
locationMap: make(map[string][]*descpb.SourceCodeInfo_Location),
@@ -138,7 +142,9 @@
}
genInitFunction(gen, g, f)
-
+ if plugins["grpc"] {
+ gengrpcgo.GenerateFileContent(gen, f.File, g)
+ }
genFileDescriptor(gen, g, f)
}
diff --git a/cmd/protoc-gen-go/testdata/grpc/grpc.pb.go b/cmd/protoc-gen-go/testdata/grpc/grpc.pb.go
new file mode 100644
index 0000000..c759239
--- /dev/null
+++ b/cmd/protoc-gen-go/testdata/grpc/grpc.pb.go
@@ -0,0 +1,381 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: grpc/grpc.proto
+
+package grpc
+
+import (
+ context "context"
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ grpc "grpc"
+ 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")
+}
+
+// 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",
+}
+
+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/testdata/grpc/grpc.proto b/cmd/protoc-gen-go/testdata/grpc/grpc.proto
new file mode 100644
index 0000000..c484ebe
--- /dev/null
+++ b/cmd/protoc-gen-go/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-grpc_go/main.go b/cmd/protoc-gen-grpc_go/main.go
new file mode 100644
index 0000000..7bd1e29
--- /dev/null
+++ b/cmd/protoc-gen-grpc_go/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 binary is a protoc plugin to generate Go gRPC
+// service definitions.
+package main
+
+import (
+ "github.com/golang/protobuf/v2/internal/protogen/gengrpcgo"
+ "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
+ }
+ gengrpcgo.GenerateFile(gen, file)
+ }
+ return nil
+ })
+}
diff --git a/internal/protogen/gengrpcgo/grpc.go b/internal/protogen/gengrpcgo/grpc.go
new file mode 100644
index 0000000..0c90ac9
--- /dev/null
+++ b/internal/protogen/gengrpcgo/grpc.go
@@ -0,0 +1,412 @@
+// 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 gengrpc generates Go gRPC services.
+package gengrpcgo
+
+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-grpc_go. 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 _ ", protogen.GoIdent{GoImportPath: "context", GoName: "Context"})
+ g.P("var _ ", protogen.GoIdent{GoImportPath: "grpc", GoName: "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 _ = 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 (
+ contextContext = protogen.GoIdent{
+ GoImportPath: "context",
+ GoName: "Context",
+ }
+ grpcUnaryServerInterceptor = protogen.GoIdent{}
+)
+
+func ident(name string) protogen.GoIdent {
+ idx := strings.LastIndex(name, ".")
+ return protogen.GoIdent{
+ GoImportPath: protogen.GoImportPath(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/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..d22de1d 100755
--- a/regenerate.bash
+++ b/regenerate.bash
@@ -29,6 +29,6 @@
continue;
fi
echo "# $p"
- protoc -I$dir --go_out=paths=source_relative:$dir $p
+ protoc -I$dir --go_out=paths=source_relative,plugins=grpc:$dir $p
done
done
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.
10 comments:
Patch Set #1, Line 7: add gRPC code generator
More broadly speaking...
Do we want to expose this binary in the v2 API? Doing so will make it hard to remove. Perhaps we should get the gRPC repo to own this pretty soon after the stable release of v2? For some period of time, we can continue to support the "--go_out=plugins=grpc" approach (using the same technique for how the v1 protoc-gen-go forwards to use the v2 protoc-gen-go), but print a scary warning that it is being deprecated soon.
Also, do we want to support "--go_out=plugins=grpc:." in the v2 API at all? Not saying this is the best idea, but perhaps that feature in only supported in the v1 protoc-gen-go, but not in the v2 protoc-gen-go.
Patch Set #1, Line 7: protoc-gen-grpc_go
Eww... mixing both "-" and "_"...
The idiomatic name in the gRPC ecosystem seems to either be:
https://github.com/grpc/grpc/blob/e7fab938f4e0d4448a597d14a29f80aa4af7392b/examples/cpp/helloworld/Makefile#L32
https://github.com/grpc/grpc/blob/e7fab938f4e0d4448a597d14a29f80aa4af7392b/examples/csharp/Helloworld/generate_protos.bat#L24
https://github.com/grpc/grpc/blame/5253720853c0bf999149b5fa2cbe122030ec6ed8/examples/php/greeter_proto_gen.sh#L16
https://github.com/grpc/grpc/blob/ccbad108e45afb7c4fb361202cc0bae5ad7e5da2/examples/objective-c/helloworld/HelloWorld.podspec#L25
Personally, I like protoc-gen-go-grpc more.
Patch Set #1, Line 9: v1-api
nit: v1 API
Patch Set #1, Line 14: protoc --go_out=. --grpc_go_out=. foo.proto
Looking around it seems like idiomatic usage is something like:
protoc --go_out=. -grpc_out=. --plugin=protoc-gen-grpc=grpc_go_plugin
protoc --go_out=. -grpc_out=. --plugin=protoc-gen-grpc=protoc-gen-go-grpc
File cmd/protoc-gen-go/internal_gengo/main.go:
Patch Set #1, Line 44: enabledPlugins[p] = true
Isn't it better to error on unknown plugins?
File cmd/protoc-gen-go/testdata/grpc/grpc.pb.go:
Patch Set #1, Line 10: grpc "grpc"
That looks wrong.
File cmd/protoc-gen-grpc_go/main.go:
Patch Set #1, Line 5: protoc-gen-go
This does not match the name of directory.
File internal/protogen/gengrpcgo/grpc.go:
Patch Set #1, Line 29: protoc-gen-grpc_go
Update this if the binary is renamed.
Patch Set #1, Line 58: grpc.SupportPackageIsVersion4
ident(...)
Patch Set #1, Line 371: idx := strings.LastIndex(name, ".")
The import path for the grpc package is "google.golang.org/grpc", but that import path does not appear in this file.
It seems that we need to convert the package name in name[:idx] to an actual import path.
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.
Damien Neil uploaded patch set #2 to this change.
cmd/protoc-gen-grpc_go: add gRPC code generator
This is a straight translation of the v1 API gRPC "plugin" to protogen.
Add a protoc-gen-grpc_go command. The preferred way to generate gRPC
services is to invoke both plugins separately:
protoc --go_out=. --grpc_go_out=. foo.proto
When invoked in this fashion, the generators will produce separate
foo.pb.go and foo_grpc.pb.go files.
The old "--go_out=plugins=grpc:." form is also supported for the
moment, in which case we still generate just one combined foo.pb.go.
Change-Id: Ie180385dab3da7063db96f7c2f9de3abbd749f63
---
A cmd/protoc-gen-go-grpc/golden_test.go
A cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go
A cmd/protoc-gen-go-grpc/main.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.pb.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.proto
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc_grpc.pb.go
M cmd/protoc-gen-go/golden_test.go
M cmd/protoc-gen-go/internal_gengo/main.go
A internal/protogen/goldentest/goldentest.go
M protogen/protogen.go
M regenerate.bash
11 files changed, 1,090 insertions(+), 125 deletions(-)
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.
Damien Neil uploaded patch set #3 to this change.
cmd/protoc-gen-grpc_go: add gRPC code generator
This is a straight translation of the v1 API gRPC "plugin" to protogen.
Add a protoc-gen-grpc_go command. The preferred way to generate gRPC
services is to invoke both plugins separately:
protoc --go_out=. --grpc_go_out=. foo.proto
When invoked in this fashion, the generators will produce separate
foo.pb.go and foo_grpc.pb.go files.
The old "--go_out=plugins=grpc:." form is also supported for the
moment, in which case we still generate just one combined foo.pb.go.
Change-Id: Ie180385dab3da7063db96f7c2f9de3abbd749f63
---
A cmd/protoc-gen-go-grpc/golden_test.go
A cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go
A cmd/protoc-gen-go-grpc/main.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.pb.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.proto
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc_grpc.pb.go
M cmd/protoc-gen-go/golden_test.go
M cmd/protoc-gen-go/internal_gengo/main.go
A internal/protogen/goldentest/goldentest.go
M protogen/protogen.go
M regenerate.bash
11 files changed, 1,094 insertions(+), 125 deletions(-)
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.
10 comments:
Patch Set #1, Line 7: protoc-gen-grpc_go
Eww... mixing both "-" and "_"... […]
Weird. I have no idea why they aren't following the protoc plugin naming convention (--FOO_out=... invokes protoc-gen-FOO).
To make things even more confusing (which is why I was confused), the instructions for some languages obscure this. For example, the Objective C tutorial tells you to run "protoc --objc_out=... --objcgrpc_out=..." without mentioning the need for an additional --plugin=protoc-gen-objcgrpc=... flag.
I'll go with protoc-gen-go-grpc. It's only consistent with Java, but at least it's consistent with *something* and I think it's clearly superior to something that requires an additional --plugin flag to protoc. Do note that it means the protoc flag is --go-grpc_out; we either get mixed -/_ in the binary name or the flag name.
Patch Set #1, Line 7: add gRPC code generator
More broadly speaking... […]
How does this sound?
Patch Set #1, Line 9: v1-api
nit: v1 API
Done
Patch Set #1, Line 14: protoc --go_out=. --grpc_go_out=. foo.proto
Looking around it seems like idiomatic usage is something like: […]
Ew.
Several langauges (Python, ObjC) have tutorials that recommend --lang_out=. --gprc_lang_out=.; I think it's okay to default to that approach for Go unless the Go gRPC team asks otherwise.
File cmd/protoc-gen-go/internal_gengo/main.go:
Patch Set #1, Line 44: enabledPlugins[p] = true
Isn't it better to error on unknown plugins?
Dropped plugin support here.
File cmd/protoc-gen-go/testdata/grpc/grpc.pb.go:
Patch Set #1, Line 10: grpc "grpc"
That looks wrong.
Patch Set #1, Line 5: protoc-gen-go
This does not match the name of directory.
Patch Set #1, Line 29: protoc-gen-grpc_go
Update this if the binary is renamed.
Done
Patch Set #1, Line 58: grpc.SupportPackageIsVersion4
ident(... […]
Done
Patch Set #1, Line 371: idx := strings.LastIndex(name, ".")
The import path for the grpc package is "google.golang. […]
Done
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.
Do we need to update test.bash to run the golden tests in non-race mode?
Do we need to add protoc-gen-go-grpc to .gitignore?
Patch set 3:Code-Review +1
5 comments:
Patch Set #1, Line 7: add gRPC code generator
How does this sound? […]
SGTM. I'm happy to hear plugin support is entirely dropped.
Patch Set #3, Line 7: protoc-gen-grpc_go
protoc-gen-go-grpc
Patch Set #3, Line 11: protoc-gen-grpc_go
protoc-gen-go-grpc
Patch Set #3, Line 14: grpc_go_out
go-grpc_out
The old "--go_out=plugins=grpc:." form is also supported for the
moment, in which case we still generate just one combined foo.pb.go.
Stale?
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.
Damien Neil uploaded patch set #4 to this change.
cmd/protoc-gen-grpc_go: add gRPC code generator
This is a straight translation of the v1 API gRPC "plugin" to protogen.
Add a protoc-gen-grpc_go command. The preferred way to generate gRPC
services is to invoke both plugins separately:
protoc --go_out=. --grpc_go_out=. foo.proto
When invoked in this fashion, the generators will produce separate
foo.pb.go and foo_grpc.pb.go files.
The old "--go_out=plugins=grpc:." form is also supported for the
moment, in which case we still generate just one combined foo.pb.go.
Change-Id: Ie180385dab3da7063db96f7c2f9de3abbd749f63
---
M .gitignore
A cmd/protoc-gen-go-grpc/golden_test.go
A cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go
A cmd/protoc-gen-go-grpc/main.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.pb.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.proto
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc_grpc.pb.go
M cmd/protoc-gen-go/golden_test.go
M cmd/protoc-gen-go/internal_gengo/main.go
A internal/protogen/goldentest/goldentest.go
M protogen/protogen.go
M regenerate.bash
12 files changed, 1,095 insertions(+), 125 deletions(-)
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.
Do we need to update test.bash to run the golden tests in non-race mode?
The golden tests have a "// +build !race" constraint, so no.
Do we need to add protoc-gen-go-grpc to .gitignore?
Added.
Patch Set 3:
Do we need to update test.bash to run the golden tests in non-race mode?
The golden tests have a "// +build !race" constraint, so no.
Do we need to add protoc-gen-go-grpc to .gitignore?
Added.
Maybe I'm missing something, but test.bash currently only runs the tests with "-race" enabled. So wouldn't that imply that we're not running the golden tests?
Damien Neil uploaded patch set #5 to this change.
cmd/protoc-gen-grpc_go: add gRPC code generator
This is a straight translation of the v1 API gRPC "plugin" to protogen.
Add a protoc-gen-grpc_go command. The preferred way to generate gRPC
services is to invoke both plugins separately:
protoc --go_out=. --grpc_go_out=. foo.proto
When invoked in this fashion, the generators will produce separate
foo.pb.go and foo_grpc.pb.go files.
The old "--go_out=plugins=grpc:." form is also supported for the
moment, in which case we still generate just one combined foo.pb.go.
Change-Id: Ie180385dab3da7063db96f7c2f9de3abbd749f63
---
M .gitignore
A cmd/protoc-gen-go-grpc/golden_test.go
A cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go
A cmd/protoc-gen-go-grpc/main.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.pb.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.proto
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc_grpc.pb.go
M cmd/protoc-gen-go/golden_test.go
M cmd/protoc-gen-go/internal_gengo/main.go
A internal/protogen/goldentest/goldentest.go
M protogen/protogen.go
M regenerate.bash
M test.bash
13 files changed, 1,096 insertions(+), 125 deletions(-)
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.
The golden tests have a "// +build !race" constraint, so no.
Oh, wait, I didn't realize test.bash didn't run anything in non-race mode. Added.
Don't forget to fix the CL description before submission.
Patch set 5:Code-Review +2
Damien Neil uploaded patch set #6 to this change.
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
---
M .gitignore
A cmd/protoc-gen-go-grpc/golden_test.go
A cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go
A cmd/protoc-gen-go-grpc/main.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.pb.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.proto
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc_grpc.pb.go
M cmd/protoc-gen-go/golden_test.go
M cmd/protoc-gen-go/internal_gengo/main.go
A internal/protogen/goldentest/goldentest.go
M protogen/protogen.go
M regenerate.bash
M test.bash
13 files changed, 1,096 insertions(+), 125 deletions(-)
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.
Damien Neil merged this change.
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 <thebroke...@gmail.com>
---
M .gitignore
A cmd/protoc-gen-go-grpc/golden_test.go
A cmd/protoc-gen-go-grpc/internal_gengogrpc/grpc.go
A cmd/protoc-gen-go-grpc/main.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.pb.go
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc.proto
A cmd/protoc-gen-go-grpc/testdata/grpc/grpc_grpc.pb.go
M cmd/protoc-gen-go/golden_test.go
M cmd/protoc-gen-go/internal_gengo/main.go
A internal/protogen/goldentest/goldentest.go
M protogen/protogen.go
M regenerate.bash
M test.bash
13 files changed, 1,096 insertions(+), 125 deletions(-)
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"))
+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]],
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"
+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
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.
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)
+ }
+}
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 ./...
To view, visit change 137037. To unsubscribe, or for help writing mail filters, visit settings.