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 ./...