diff --git a/BUILD b/BUILD index df082e47787dc909e98f7b7c8a343db13d343a7f..35e8f444963d2e59fd5f510e7e252c21684e33a3 100644 --- a/BUILD +++ b/BUILD @@ -737,6 +737,8 @@ cc_library( "src/compiler/config.h", "src/compiler/cpp_generator.h", "src/compiler/cpp_generator_helpers.h", + "src/compiler/csharp_generator.h", + "src/compiler/csharp_generator_helpers.h", "src/compiler/generator_helpers.h", "src/compiler/objective_c_generator.h", "src/compiler/objective_c_generator_helpers.h", @@ -746,6 +748,7 @@ cc_library( "src/compiler/ruby_generator_map-inl.h", "src/compiler/ruby_generator_string-inl.h", "src/compiler/cpp_generator.cc", + "src/compiler/csharp_generator.cc", "src/compiler/objective_c_generator.cc", "src/compiler/python_generator.cc", "src/compiler/ruby_generator.cc", @@ -793,6 +796,18 @@ cc_binary( ) +cc_binary( + name = "grpc_csharp_plugin", + srcs = [ + "src/compiler/csharp_plugin.cc", + ], + deps = [ + "//external:protobuf_compiler", + ":grpc_plugin_support", + ], +) + + cc_binary( name = "grpc_objective_c_plugin", srcs = [ diff --git a/Makefile b/Makefile index 244a211652cae2bb782951886712acf867cb791e..ecca8ab26ba8242d1de4edb7e51b8a8aa0158460 100644 --- a/Makefile +++ b/Makefile @@ -490,7 +490,7 @@ endif .SECONDARY = %.pb.h %.pb.cc -PROTOC_PLUGINS = $(BINDIR)/$(CONFIG)/grpc_cpp_plugin $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin $(BINDIR)/$(CONFIG)/grpc_python_plugin $(BINDIR)/$(CONFIG)/grpc_ruby_plugin +PROTOC_PLUGINS = $(BINDIR)/$(CONFIG)/grpc_cpp_plugin $(BINDIR)/$(CONFIG)/grpc_csharp_plugin $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin $(BINDIR)/$(CONFIG)/grpc_python_plugin $(BINDIR)/$(CONFIG)/grpc_ruby_plugin ifeq ($(DEP_MISSING),) all: static shared plugins dep_error: @@ -674,6 +674,7 @@ end2end_test: $(BINDIR)/$(CONFIG)/end2end_test generic_end2end_test: $(BINDIR)/$(CONFIG)/generic_end2end_test grpc_cli: $(BINDIR)/$(CONFIG)/grpc_cli grpc_cpp_plugin: $(BINDIR)/$(CONFIG)/grpc_cpp_plugin +grpc_csharp_plugin: $(BINDIR)/$(CONFIG)/grpc_csharp_plugin grpc_objective_c_plugin: $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin grpc_python_plugin: $(BINDIR)/$(CONFIG)/grpc_python_plugin grpc_ruby_plugin: $(BINDIR)/$(CONFIG)/grpc_ruby_plugin @@ -2210,6 +2211,8 @@ else $(Q) $(INSTALL) -d $(prefix)/bin $(Q) $(INSTALL) $(BINDIR)/$(CONFIG)/grpc_cpp_plugin $(prefix)/bin/grpc_cpp_plugin $(Q) $(INSTALL) -d $(prefix)/bin + $(Q) $(INSTALL) $(BINDIR)/$(CONFIG)/grpc_csharp_plugin $(prefix)/bin/grpc_csharp_plugin + $(Q) $(INSTALL) -d $(prefix)/bin $(Q) $(INSTALL) $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin $(prefix)/bin/grpc_objective_c_plugin $(Q) $(INSTALL) -d $(prefix)/bin $(Q) $(INSTALL) $(BINDIR)/$(CONFIG)/grpc_python_plugin $(prefix)/bin/grpc_python_plugin @@ -3135,6 +3138,7 @@ endif LIBGRPC_PLUGIN_SUPPORT_SRC = \ src/compiler/cpp_generator.cc \ + src/compiler/csharp_generator.cc \ src/compiler/objective_c_generator.cc \ src/compiler/python_generator.cc \ src/compiler/ruby_generator.cc \ @@ -6997,6 +7001,34 @@ ifneq ($(NO_DEPS),true) endif +GRPC_CSHARP_PLUGIN_SRC = \ + src/compiler/csharp_plugin.cc \ + +GRPC_CSHARP_PLUGIN_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GRPC_CSHARP_PLUGIN_SRC)))) + +ifeq ($(NO_PROTOBUF),true) + +# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.0.0+. + +$(BINDIR)/$(CONFIG)/grpc_csharp_plugin: protobuf_dep_error + +else + +$(BINDIR)/$(CONFIG)/grpc_csharp_plugin: $(PROTOBUF_DEP) $(GRPC_CSHARP_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a + $(E) "[HOSTLD] Linking $@" + $(Q) mkdir -p `dirname $@` + $(Q) $(HOST_LDXX) $(HOST_LDFLAGS) $(GRPC_CSHARP_PLUGIN_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a $(HOST_LDLIBSXX) $(HOST_LDLIBS_PROTOC) $(HOST_LDLIBS) $(HOST_LDLIBS_PROTOC) -o $(BINDIR)/$(CONFIG)/grpc_csharp_plugin + +endif + +$(OBJDIR)/$(CONFIG)/src/compiler/csharp_plugin.o: $(LIBDIR)/$(CONFIG)/libgrpc_plugin_support.a +deps_grpc_csharp_plugin: $(GRPC_CSHARP_PLUGIN_OBJS:.o=.dep) + +ifneq ($(NO_DEPS),true) +-include $(GRPC_CSHARP_PLUGIN_OBJS:.o=.dep) +endif + + GRPC_OBJECTIVE_C_PLUGIN_SRC = \ src/compiler/objective_c_plugin.cc \ diff --git a/build.json b/build.json index 10fd72d99e43b725d9cb6a10dc8c705c2f1002cc..6aeb2e466106db9f9415b954abf1712a15bbb07a 100644 --- a/build.json +++ b/build.json @@ -563,6 +563,8 @@ "src/compiler/config.h", "src/compiler/cpp_generator.h", "src/compiler/cpp_generator_helpers.h", + "src/compiler/csharp_generator.h", + "src/compiler/csharp_generator_helpers.h", "src/compiler/generator_helpers.h", "src/compiler/objective_c_generator.h", "src/compiler/objective_c_generator_helpers.h", @@ -574,6 +576,7 @@ ], "src": [ "src/compiler/cpp_generator.cc", + "src/compiler/csharp_generator.cc", "src/compiler/objective_c_generator.cc", "src/compiler/python_generator.cc", "src/compiler/ruby_generator.cc" @@ -1913,6 +1916,18 @@ ], "secure": "no" }, + { + "name": "grpc_csharp_plugin", + "build": "protoc", + "language": "c++", + "src": [ + "src/compiler/csharp_plugin.cc" + ], + "deps": [ + "grpc_plugin_support" + ], + "secure": "no" + }, { "name": "grpc_objective_c_plugin", "build": "protoc", diff --git a/src/compiler/csharp_generator.cc b/src/compiler/csharp_generator.cc new file mode 100644 index 0000000000000000000000000000000000000000..cc8f5bda71cbe88fd7f08159773b21758de256b1 --- /dev/null +++ b/src/compiler/csharp_generator.cc @@ -0,0 +1,496 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <cctype> +#include <map> +#include <vector> + +#include "src/compiler/config.h" +#include "src/compiler/csharp_generator.h" + +using grpc::protobuf::FileDescriptor; +using grpc::protobuf::Descriptor; +using grpc::protobuf::ServiceDescriptor; +using grpc::protobuf::MethodDescriptor; +using grpc::protobuf::io::Printer; +using grpc::protobuf::io::StringOutputStream; +using std::map; +using std::vector; + +namespace grpc_csharp_generator { +namespace { + +enum MethodType { + METHODTYPE_NO_STREAMING, + METHODTYPE_CLIENT_STREAMING, + METHODTYPE_SERVER_STREAMING, + METHODTYPE_BIDI_STREAMING +}; + +MethodType GetMethodType(const MethodDescriptor *method) { + if (method->client_streaming()) { + if (method->server_streaming()) { + return METHODTYPE_BIDI_STREAMING; + } else { + return METHODTYPE_CLIENT_STREAMING; + } + } else { + if (method->server_streaming()) { + return METHODTYPE_SERVER_STREAMING; + } else { + return METHODTYPE_NO_STREAMING; + } + } +} + +std::string GetCSharpNamespace(const FileDescriptor* file) { + // TODO(jtattermusch): this should be based on csharp_namespace option + return file->package(); +} + +std::string GetMessageType(const Descriptor* message) { + // TODO(jtattermusch): this has to match with C# protobuf generator + return message->name(); +} + +std::string GetServiceClassName(const ServiceDescriptor* service) { + return service->name(); +} + +std::string GetClientInterfaceName(const ServiceDescriptor* service) { + return "I" + service->name() + "Client"; +} + +std::string GetClientClassName(const ServiceDescriptor* service) { + return service->name() + "Client"; +} + +std::string GetServerInterfaceName(const ServiceDescriptor* service) { + return "I" + service->name(); +} + +std::string GetCSharpMethodType(MethodType method_type) { + switch (method_type) { + case METHODTYPE_NO_STREAMING: + return "MethodType.Unary"; + case METHODTYPE_CLIENT_STREAMING: + return "MethodType.ClientStreaming"; + case METHODTYPE_SERVER_STREAMING: + return "MethodType.ServerStreaming"; + case METHODTYPE_BIDI_STREAMING: + return "MethodType.DuplexStreaming"; + } + GOOGLE_LOG(FATAL)<< "Can't get here."; + return ""; +} + +std::string GetServiceNameFieldName() { + return "__ServiceName"; +} + +std::string GetMarshallerFieldName(const Descriptor *message) { + return "__Marshaller_" + message->name(); +} + +std::string GetMethodFieldName(const MethodDescriptor *method) { + return "__Method_" + method->name(); +} + +std::string GetMethodRequestParamMaybe(const MethodDescriptor *method) { + if (method->client_streaming()) { + return ""; + } + return GetMessageType(method->input_type()) + " request, "; +} + +std::string GetMethodReturnTypeClient(const MethodDescriptor *method) { + switch (GetMethodType(method)) { + case METHODTYPE_NO_STREAMING: + return "Task<" + GetMessageType(method->output_type()) + ">"; + case METHODTYPE_CLIENT_STREAMING: + return "AsyncClientStreamingCall<" + GetMessageType(method->input_type()) + + ", " + GetMessageType(method->output_type()) + ">"; + case METHODTYPE_SERVER_STREAMING: + return "AsyncServerStreamingCall<" + GetMessageType(method->output_type()) + + ">"; + case METHODTYPE_BIDI_STREAMING: + return "AsyncDuplexStreamingCall<" + GetMessageType(method->input_type()) + + ", " + GetMessageType(method->output_type()) + ">"; + } + GOOGLE_LOG(FATAL)<< "Can't get here."; + return ""; +} + +std::string GetMethodRequestParamServer(const MethodDescriptor *method) { + switch (GetMethodType(method)) { + case METHODTYPE_NO_STREAMING: + case METHODTYPE_SERVER_STREAMING: + return GetMessageType(method->input_type()) + " request"; + case METHODTYPE_CLIENT_STREAMING: + case METHODTYPE_BIDI_STREAMING: + return "IAsyncStreamReader<" + GetMessageType(method->input_type()) + + "> requestStream"; + } + GOOGLE_LOG(FATAL)<< "Can't get here."; + return ""; +} + +std::string GetMethodReturnTypeServer(const MethodDescriptor *method) { + switch (GetMethodType(method)) { + case METHODTYPE_NO_STREAMING: + case METHODTYPE_CLIENT_STREAMING: + return "Task<" + GetMessageType(method->output_type()) + ">"; + case METHODTYPE_SERVER_STREAMING: + case METHODTYPE_BIDI_STREAMING: + return "Task"; + } + GOOGLE_LOG(FATAL)<< "Can't get here."; + return ""; +} + +std::string GetMethodResponseStreamMaybe(const MethodDescriptor *method) { + switch (GetMethodType(method)) { + case METHODTYPE_NO_STREAMING: + case METHODTYPE_CLIENT_STREAMING: + return ""; + case METHODTYPE_SERVER_STREAMING: + case METHODTYPE_BIDI_STREAMING: + return ", IServerStreamWriter<" + GetMessageType(method->output_type()) + + "> responseStream"; + } + GOOGLE_LOG(FATAL)<< "Can't get here."; + return ""; +} + +// Gets vector of all messages used as input or output types. +std::vector<const Descriptor*> GetUsedMessages( + const ServiceDescriptor *service) { + std::set<const Descriptor*> descriptor_set; + std::vector<const Descriptor*> result; // vector is to maintain stable ordering + for (int i = 0; i < service->method_count(); i++) { + const MethodDescriptor *method = service->method(i); + if (descriptor_set.find(method->input_type()) == descriptor_set.end()) { + descriptor_set.insert(method->input_type()); + result.push_back(method->input_type()); + } + if (descriptor_set.find(method->output_type()) == descriptor_set.end()) { + descriptor_set.insert(method->output_type()); + result.push_back(method->output_type()); + } + } + return result; +} + +void GenerateMarshallerFields(Printer* out, const ServiceDescriptor *service) { + std::vector<const Descriptor*> used_messages = GetUsedMessages(service); + for (size_t i = 0; i < used_messages.size(); i++) { + const Descriptor *message = used_messages[i]; + out->Print( + "static readonly Marshaller<$type$> $fieldname$ = Marshallers.Create((arg) => arg.ToByteArray(), $type$.ParseFrom);\n", + "fieldname", GetMarshallerFieldName(message), "type", + GetMessageType(message)); + } + out->Print("\n"); +} + +void GenerateStaticMethodField(Printer* out, const MethodDescriptor *method) { + out->Print( + "static readonly Method<$request$, $response$> $fieldname$ = new Method<$request$, $response$>(\n", + "fieldname", GetMethodFieldName(method), "request", + GetMessageType(method->input_type()), "response", + GetMessageType(method->output_type())); + out->Indent(); + out->Indent(); + out->Print("$methodtype$,\n", "methodtype", + GetCSharpMethodType(GetMethodType(method))); + out->Print("\"$methodname$\",\n", "methodname", method->name()); + out->Print("$requestmarshaller$,\n", "requestmarshaller", + GetMarshallerFieldName(method->input_type())); + out->Print("$responsemarshaller$);\n", "responsemarshaller", + GetMarshallerFieldName(method->output_type())); + out->Print("\n"); + out->Outdent(); + out->Outdent(); +} + +void GenerateClientInterface(Printer* out, const ServiceDescriptor *service) { + out->Print("// client-side stub interface\n"); + out->Print("public interface $name$\n", "name", + GetClientInterfaceName(service)); + out->Print("{\n"); + out->Indent(); + for (int i = 0; i < service->method_count(); i++) { + const MethodDescriptor *method = service->method(i); + MethodType method_type = GetMethodType(method); + + if (method_type == METHODTYPE_NO_STREAMING) { + // unary calls have an extra synchronous stub method + out->Print( + "$response$ $methodname$($request$ request, CancellationToken token = default(CancellationToken));\n", + "methodname", method->name(), "request", + GetMessageType(method->input_type()), "response", + GetMessageType(method->output_type())); + } + + std::string method_name = method->name(); + if (method_type == METHODTYPE_NO_STREAMING) { + method_name += "Async"; // prevent name clash with synchronous method. + } + out->Print( + "$returntype$ $methodname$($request_maybe$CancellationToken token = default(CancellationToken));\n", + "methodname", method_name, "request_maybe", + GetMethodRequestParamMaybe(method), "returntype", + GetMethodReturnTypeClient(method)); + } + out->Outdent(); + out->Print("}\n"); + out->Print("\n"); +} + +void GenerateServerInterface(Printer* out, const ServiceDescriptor *service) { + out->Print("// server-side interface\n"); + out->Print("public interface $name$\n", "name", + GetServerInterfaceName(service)); + out->Print("{\n"); + out->Indent(); + for (int i = 0; i < service->method_count(); i++) { + const MethodDescriptor *method = service->method(i); + out->Print("$returntype$ $methodname$(ServerCallContext context, $request$$response_stream_maybe$);\n", + "methodname", method->name(), "returntype", + GetMethodReturnTypeServer(method), "request", + GetMethodRequestParamServer(method), "response_stream_maybe", + GetMethodResponseStreamMaybe(method)); + } + out->Outdent(); + out->Print("}\n"); + out->Print("\n"); +} + +void GenerateClientStub(Printer* out, const ServiceDescriptor *service) { + out->Print("// client stub\n"); + out->Print( + "public class $name$ : AbstractStub<$name$, StubConfiguration>, $interface$\n", + "name", GetClientClassName(service), "interface", + GetClientInterfaceName(service)); + out->Print("{\n"); + out->Indent(); + + // constructors + out->Print( + "public $name$(Channel channel) : this(channel, StubConfiguration.Default)\n", + "name", GetClientClassName(service)); + out->Print("{\n"); + out->Print("}\n"); + out->Print( + "public $name$(Channel channel, StubConfiguration config) : base(channel, config)\n", + "name", GetClientClassName(service)); + out->Print("{\n"); + out->Print("}\n"); + + for (int i = 0; i < service->method_count(); i++) { + const MethodDescriptor *method = service->method(i); + MethodType method_type = GetMethodType(method); + + if (method_type == METHODTYPE_NO_STREAMING) { + // unary calls have an extra synchronous stub method + out->Print( + "public $response$ $methodname$($request$ request, CancellationToken token = default(CancellationToken))\n", + "methodname", method->name(), "request", + GetMessageType(method->input_type()), "response", + GetMessageType(method->output_type())); + out->Print("{\n"); + out->Indent(); + out->Print("var call = CreateCall($servicenamefield$, $methodfield$);\n", + "servicenamefield", GetServiceNameFieldName(), "methodfield", + GetMethodFieldName(method)); + out->Print("return Calls.BlockingUnaryCall(call, request, token);\n"); + out->Outdent(); + out->Print("}\n"); + } + + std::string method_name = method->name(); + if (method_type == METHODTYPE_NO_STREAMING) { + method_name += "Async"; // prevent name clash with synchronous method. + } + out->Print( + "public $returntype$ $methodname$($request_maybe$CancellationToken token = default(CancellationToken))\n", + "methodname", method_name, "request_maybe", + GetMethodRequestParamMaybe(method), "returntype", + GetMethodReturnTypeClient(method)); + out->Print("{\n"); + out->Indent(); + out->Print("var call = CreateCall($servicenamefield$, $methodfield$);\n", + "servicenamefield", GetServiceNameFieldName(), "methodfield", + GetMethodFieldName(method)); + switch (GetMethodType(method)) { + case METHODTYPE_NO_STREAMING: + out->Print("return Calls.AsyncUnaryCall(call, request, token);\n"); + break; + case METHODTYPE_CLIENT_STREAMING: + out->Print("return Calls.AsyncClientStreamingCall(call, token);\n"); + break; + case METHODTYPE_SERVER_STREAMING: + out->Print( + "return Calls.AsyncServerStreamingCall(call, request, token);\n"); + break; + case METHODTYPE_BIDI_STREAMING: + out->Print("return Calls.AsyncDuplexStreamingCall(call, token);\n"); + break; + default: + GOOGLE_LOG(FATAL)<< "Can't get here."; + } + out->Outdent(); + out->Print("}\n"); + } + out->Outdent(); + out->Print("}\n"); + out->Print("\n"); +} + +void GenerateBindServiceMethod(Printer* out, const ServiceDescriptor *service) { + out->Print( + "// creates service definition that can be registered with a server\n"); + out->Print( + "public static ServerServiceDefinition BindService($interface$ serviceImpl)\n", + "interface", GetServerInterfaceName(service)); + out->Print("{\n"); + out->Indent(); + + out->Print( + "return ServerServiceDefinition.CreateBuilder($servicenamefield$)\n", + "servicenamefield", GetServiceNameFieldName()); + out->Indent(); + out->Indent(); + for (int i = 0; i < service->method_count(); i++) { + const MethodDescriptor *method = service->method(i); + out->Print(".AddMethod($methodfield$, serviceImpl.$methodname$)", + "methodfield", GetMethodFieldName(method), "methodname", + method->name()); + if (i == service->method_count() - 1) { + out->Print(".Build();"); + } + out->Print("\n"); + } + out->Outdent(); + out->Outdent(); + + out->Outdent(); + out->Print("}\n"); + out->Print("\n"); +} + +void GenerateNewStubMethods(Printer* out, const ServiceDescriptor *service) { + out->Print("// creates a new client stub\n"); + out->Print("public static $interface$ NewStub(Channel channel)\n", + "interface", GetClientInterfaceName(service)); + out->Print("{\n"); + out->Indent(); + out->Print("return new $classname$(channel);\n", "classname", + GetClientClassName(service)); + out->Outdent(); + out->Print("}\n"); + out->Print("\n"); + + out->Print("// creates a new client stub\n"); + out->Print( + "public static $interface$ NewStub(Channel channel, StubConfiguration config)\n", + "interface", GetClientInterfaceName(service)); + out->Print("{\n"); + out->Indent(); + out->Print("return new $classname$(channel, config);\n", "classname", + GetClientClassName(service)); + out->Outdent(); + out->Print("}\n"); +} + +void GenerateService(Printer* out, const ServiceDescriptor *service) { + out->Print("public static class $classname$\n", "classname", + GetServiceClassName(service)); + out->Print("{\n"); + out->Indent(); + out->Print("static readonly string $servicenamefield$ = \"$servicename$\";\n", + "servicenamefield", GetServiceNameFieldName(), "servicename", + service->full_name()); + out->Print("\n"); + + GenerateMarshallerFields(out, service); + for (int i = 0; i < service->method_count(); i++) { + GenerateStaticMethodField(out, service->method(i)); + } + GenerateClientInterface(out, service); + GenerateServerInterface(out, service); + GenerateClientStub(out, service); + GenerateBindServiceMethod(out, service); + GenerateNewStubMethods(out, service); + + out->Outdent(); + out->Print("}\n"); +} + +} // anonymous namespace + +grpc::string GetServices(const FileDescriptor *file) { + grpc::string output; + StringOutputStream output_stream(&output); + Printer out(&output_stream, '$'); + + // Don't write out any output if there no services, to avoid empty service + // files being generated for proto files that don't declare any. + if (file->service_count() == 0) { + return output; + } + + // Write out a file header. + out.Print("// Generated by the protocol buffer compiler. DO NOT EDIT!\n"); + out.Print("// source: $filename$\n", "filename", file->name()); + out.Print("#region Designer generated code\n"); + out.Print("\n"); + out.Print("using System;\n"); + out.Print("using System.Threading;\n"); + out.Print("using System.Threading.Tasks;\n"); + out.Print("using Grpc.Core;\n"); + // TODO(jtattermusch): add using for protobuf message classes + out.Print("\n"); + + out.Print("namespace $namespace$ {\n", "namespace", GetCSharpNamespace(file)); + out.Indent(); + for (int i = 0; i < file->service_count(); i++) { + GenerateService(&out, file->service(i)); + } + out.Outdent(); + out.Print("}\n"); + out.Print("#endregion"); + return output; +} + +} // namespace grpc_csharp_generator diff --git a/src/compiler/csharp_generator.h b/src/compiler/csharp_generator.h new file mode 100644 index 0000000000000000000000000000000000000000..ec537d3f1d2835c6bea91df798224129d72cf05e --- /dev/null +++ b/src/compiler/csharp_generator.h @@ -0,0 +1,45 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_H +#define GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_H + +#include "src/compiler/config.h" + +namespace grpc_csharp_generator { + +grpc::string GetServices(const grpc::protobuf::FileDescriptor *file); + +} // namespace grpc_csharp_generator + +#endif // GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_H diff --git a/src/compiler/csharp_generator_helpers.h b/src/compiler/csharp_generator_helpers.h new file mode 100644 index 0000000000000000000000000000000000000000..137062763374fcf59e44e62a355e2cbb2cee967d --- /dev/null +++ b/src/compiler/csharp_generator_helpers.h @@ -0,0 +1,50 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_HELPERS_H +#define GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_HELPERS_H + +#include "src/compiler/config.h" +#include "src/compiler/generator_helpers.h" + +namespace grpc_csharp_generator { + +inline bool ServicesFilename(const grpc::protobuf::FileDescriptor *file, + grpc::string *file_name_or_error) { + *file_name_or_error = grpc_generator::FileNameInUpperCamel(file) + "Grpc.cs"; + return true; +} + +} // namespace grpc_csharp_generator + +#endif // GRPC_INTERNAL_COMPILER_CSHARP_GENERATOR_HELPERS_H diff --git a/src/compiler/csharp_plugin.cc b/src/compiler/csharp_plugin.cc new file mode 100644 index 0000000000000000000000000000000000000000..8b9395f9e2b21af8c5af6e255e27724d14bbbff0 --- /dev/null +++ b/src/compiler/csharp_plugin.cc @@ -0,0 +1,72 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +// Generates C# gRPC service interface out of Protobuf IDL. + +#include <memory> + +#include "src/compiler/config.h" +#include "src/compiler/csharp_generator.h" +#include "src/compiler/csharp_generator_helpers.h" + +class CSharpGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { + public: + CSharpGrpcGenerator() {} + ~CSharpGrpcGenerator() {} + + bool Generate(const grpc::protobuf::FileDescriptor *file, + const grpc::string ¶meter, + grpc::protobuf::compiler::GeneratorContext *context, + grpc::string *error) const { + grpc::string code = grpc_csharp_generator::GetServices(file); + if (code.size() == 0) { + return true; // don't generate a file if there are no services + } + + // Get output file name. + grpc::string file_name; + if (!grpc_csharp_generator::ServicesFilename(file, &file_name)) { + return false; + } + std::unique_ptr<grpc::protobuf::io::ZeroCopyOutputStream> output( + context->Open(file_name)); + grpc::protobuf::io::CodedOutputStream coded_out(output.get()); + coded_out.WriteRaw(code.data(), code.size()); + return true; + } +}; + +int main(int argc, char *argv[]) { + CSharpGrpcGenerator generator; + return grpc::protobuf::compiler::PluginMain(argc, argv, &generator); +} diff --git a/src/csharp/Grpc.Examples/MathGrpc.cs b/src/csharp/Grpc.Examples/MathGrpc.cs index 03f5c31cb7acc77e7c846f93e08daa3c9cad1847..9ab60137f7d42ecbaf48c64dcdf3077e00972c5c 100644 --- a/src/csharp/Grpc.Examples/MathGrpc.cs +++ b/src/csharp/Grpc.Examples/MathGrpc.cs @@ -32,8 +32,6 @@ #endregion using System; -using System.Collections.Generic; -using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; using Grpc.Core; @@ -43,7 +41,7 @@ namespace math /// <summary> /// Math service definitions (this is handwritten version of code that will normally be generated). /// </summary> - public class MathGrpc + public static class MathGrpc { static readonly string ServiceName = "/math.Math";