15

I've read the tutorial and I'm able to generate the .cs file but it doesn't include any of my service or rpc definitions.

I've added protoc to my PATH and from inside the project directory.

protoc project1.proto --csharp_out="C:\output" --plugin=protoc-gen-grpc="c:\Users\me\.nuget\packages\grpc.tools\1.8.0\tools\windows_x64\grpc_csharp_plugin.exe"

No errors output in console

user3953989
  • 1,844
  • 3
  • 25
  • 56
  • As an aside, I'd suggest using a more recent version of gRPC. Version 1.12 is out now. – Jon Skeet Jun 04 '18 at 19:58
  • 1.12 is what shows in my solution under the NuGet manager `--version` shows 3.5.0 – user3953989 Jun 04 '18 at 20:02
  • But you're using the 1.8.0 protoc plugin. You should use the 1.12.0 version of that as well. (I'm not sure what you're running to show 3.5.0, but I'd suggest using 3.5.1 of protoc, as well.) – Jon Skeet Jun 04 '18 at 20:08

3 Answers3

16

You need to add the --grpc_out command line option, e.g. add

--grpc_out="C:\output\"

Note that it won't write any files if you don't have any services.

Here's a complete example. From a root directory, create:

  • An empty output directory
  • A tools directory with protoc.exe and grpc_csharp_plugin.exe
  • A protos directory with test.proto as shown below:

test.proto:

syntax = "proto3";

service StackOverflowService {
  rpc GetAnswer(Question) returns (Answer);
}

message Question {
  string text = 1;
  string user = 2;
  repeated string tags = 3;
}

message Answer {
  string text = 1;
  string user = 2;
}

Then run (all on one line; I've broken it just for readability here):

tools\protoc.exe -I protos protos\test.proto --csharp_out=output
    --grpc_out=output --plugin=protoc-gen-grpc=tools\grpc_csharp_plugin.exe 

In the output directory, you'll find Test.cs and TestGrpc.cs

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    @user3953989: I've just tried it myself, and it's fine. Are you sure you didn't combine two command line arguments? What's the complete command line you used? – Jon Skeet Jun 04 '18 at 19:42
  • It's in the OP. I've added protoc to PATH to and running it from inside the directory where the proto is to make it cleaner `protoc project1.proto --csharp_out="C:\output"` works but `protoc project1.proto --csharp_out="C:\output" --plugin=protoc-gen-grpc=.\grpc_csharp_plugin.exe` doesn't. Both produce the same output and no errors – user3953989 Jun 04 '18 at 19:44
  • @user3953989: Is `grpc_csharp_plugin.exe` actually in the current directory? If not, that's probably the problem. (But you still need to specify --grpc_out) – Jon Skeet Jun 04 '18 at 19:48
  • Yes. I can run it directly. – user3953989 Jun 04 '18 at 19:48
  • 1
    @user3953989: What do you mean by "run it directly"? Putting it in your path is *not* the same as it being in the current directory. (I'm asking because it would be quite unusual to have that in the same directory as your proto file.) – Jon Skeet Jun 04 '18 at 19:49
  • I meant just running the exe at the command line, but good point that it won't run the same as find it. I also tried to update the `--plugin` param to use the full path to the exe and still not working. I've updated the OP with the new command – user3953989 Jun 04 '18 at 19:51
  • 1
    @user3953989: Except you still haven't specified `--grpc_out`, as per this answer. It really *will* work at that point... (Assuming you *have* some services, of course.) – Jon Skeet Jun 04 '18 at 19:54
  • @Dasiy Shipton You were correct. It was generating a second file with grpc in the name. I didn't realize additional files would be generated with that additional parameter. I kept looking at the other file for changes. – user3953989 Jun 05 '18 at 00:59
  • My classes are not being generated and I can't find an error in proto file. How to see the generator output, where is it? – Hrvoje Batrnek Nov 21 '20 at 01:43
  • @HrvojeBatrnek: If you're running the generator yourself, it's wherever you've told it to be - you specify the output directory. It sounds like it's probably worth you asking a new question with more details. – Jon Skeet Nov 21 '20 at 07:36
  • @JonSkeet It's an automatic generator in VS 2019 gRPC project, automatically generating when I make changes to the .proto file. It would be of much help if I knew where he is output exception, even better if they are accessible through VS UI. – Hrvoje Batrnek Nov 21 '20 at 13:49
  • @HrvojeBatrnek: That definitely sounds like you should ask it as a new question then, with all the relevant details. – Jon Skeet Nov 21 '20 at 15:40
  • @JonSkeet I can't, stack's algorithm blocked me because I did not have enough likes on my questions. :) – Hrvoje Batrnek Nov 21 '20 at 17:06
  • Haha, I did not notice that .proto errors appear in Error List because of all of the errors that I currently have in my solution. Problem solved. :) – Hrvoje Batrnek Nov 21 '20 at 17:24
6

Just an idle comment here for other that find this, the documentation about this is terribly out of date and just flat out wrong.

Installing Grpc.Tools does not install anything in a packages folder; that is legacy behaviour which is no longer true even on windows.

When you install Grpc.Tools it will be hidden away in your local package cache, which you can see by calling:

$ dotnet nuget locals all --list
info : http-cache: /Users/doug/.local/share/NuGet/v3-cache
info : global-packages: /Users/doug/.nuget/packages/
info : temp: /var/folders/xx/s2hnzbrj3yn4hp1bg8q9gb_m0000gn/T/NuGetScratch

The binaries you want will be in one of these folders.

The easiest way to do this is to download the Grpc.Tools package directly from nuget, and install it locally.

I've hacked up this little helper script to do that, which works on windows/mac/linux, which may ease the difficulty of getting starting with this for others:

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Mono.Unix;

namespace BuildProtocol
{
  public class Program
  {
    private const string ToolsUrl = "https://www.nuget.org/api/v2/package/Grpc.Tools/";
    private const string Service = "Greeter";
    private static string ProtocolPath = Path.Combine("..", "protos");
    private static string Protocol = Path.Combine(ProtocolPath, "helloworld.proto");
    private static string Output = Path.Combine("..", "Greeter");

    public static void Main(string[] args)
    {
      RequireTools().Wait();

      var protoc = ProtocPath();
      var plugin = ProtocPluginPath();

      Console.WriteLine($"Using: {protoc}");
      Console.WriteLine($"Using: {plugin}");

      var command = new string[]
      {
        $"-I{ProtocolPath}",
        $"--csharp_out={Output}",
        $"--grpc_out={Output}",
        $"--plugin=protoc-gen-grpc=\"{plugin}\"",
        Protocol,
      };

      Console.WriteLine($"Exec: {protoc} {string.Join(' ', command)}");

      var process = new Process
      {
        StartInfo = new ProcessStartInfo
        {
          UseShellExecute = false,
          FileName = protoc,
          Arguments = string.Join(' ', command)
        }
      };

      process.Start();
      process.WaitForExit();

      Console.WriteLine($"Completed status: {process.ExitCode}");
    }

    public static async Task RequireTools()
    {
      if (!Directory.Exists("Tools"))
      {
        Console.WriteLine("No local tools found, downloading binaries from nuget...");
        Directory.CreateDirectory("Tools");
        await DownloadTools();
        ExtractTools();
      }
    }

    private static void ExtractTools()
    {
      ZipFile.ExtractToDirectory(Path.Combine("Tools", "tools.zip"), Path.Combine("Tools", "bin"));
    }

    private static async Task DownloadTools()
    {
      using (var client = new HttpClient())
      {
        Console.WriteLine($"Fetching: {ToolsUrl}");
        using (var result = await client.GetAsync(ToolsUrl))
        {
          if (!result.IsSuccessStatusCode) throw new Exception($"Unable to download tools ({result.StatusCode}), check URL");
          var localArchive = Path.Combine("Tools", "tools.zip");
          Console.WriteLine($"Saving to: {localArchive}");
          File.WriteAllBytes(localArchive, await result.Content.ReadAsByteArrayAsync());
        }
      }
    }

    private static string ProtocPath()
    {
      var path = Path.Combine("Tools", "bin", "tools", DetermineArch(), "protoc");
      RequireExecutablePermission(path);
      return WithExeExtensionIfRequired(path);
    }

    private static string ProtocPluginPath()
    {
      var path = Path.Combine("Tools", "bin", "tools", DetermineArch(), "grpc_csharp_plugin");
      RequireExecutablePermission(path);
      return WithExeExtensionIfRequired(path);
    }

    private static void RequireExecutablePermission(string path)
    {
      if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return;
      Console.WriteLine($"Ensuring +x on {path}");
      var unixFileInfo = new UnixFileInfo(path);
      unixFileInfo.FileAccessPermissions = FileAccessPermissions.UserRead | FileAccessPermissions.UserWrite | FileAccessPermissions.UserExecute;
    }

    private static string WithExeExtensionIfRequired(string path)
    {
      if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
      {
        path += ".exe";
      }

      return path;
    }

    private static string DetermineArch()
    {
      var arch = RuntimeInformation.OSArchitecture;
      if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
      {
        return WithArch("windows_", arch);
      }

      if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
      {
        return WithArch("macosx_", arch);
      }

      if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
      {
        return WithArch("linux_", arch);
      }

      throw new Exception("Unable to determine runtime");
    }

    private static string WithArch(string platform, Architecture arch)
    {
      switch (arch)
      {
        case Architecture.X64:
          return $"{platform}x86";
        case Architecture.X86:
          return $"{platform}x64";
        default:
          throw new ArgumentOutOfRangeException(nameof(arch), arch, null);
      }
    }
  }
}
Doug
  • 32,844
  • 38
  • 166
  • 222
  • "Installing Grpc.Tools does not install anything in a packages folder" - that depends how you install it. If you use `nuget install` then you can *absolutely* put it in whatever packages folder you want. I find that to be a very useful way of getting access to the files within packages. – Jon Skeet Aug 19 '18 at 07:02
  • 2
    `nuget` is not part of the .Net Core SDK, and problematic to install on other platforms. The standard way to install a package is `dotnet add package`, which is what most IDEs do now (VS depending on the project type). What you say is true, but it doesn't change the fact it is legacy behaviour (similar to hard coded versions of path to msbuild, instead of using `dotnet msbuild` to find the 'correct' installed copy of msbuild). – Doug Aug 19 '18 at 13:28
  • Somewhat. I think the overall tone of your first couple of paragraphs is unnecessarily hostile - and you should also bear in mind that there may well be many developers out there who are *not* using the .NET Core SDK, but still using gRPC. I agree that it would be better if the documentation could be improved, but there are more positive ways to express that. I'd also point out that soon there'll hopefully be MSBuilld integration to generate the files, which will make it all a lot easier. – Jon Skeet Aug 19 '18 at 13:52
  • 2
    @DaisyShipton I'm not sure what to say. I'm sorry if my tone is inappropriate, but the tutorial instructions are wrong, and do not work. I'll happily update this answer if that changes, but for now, it is what it is. – Doug Aug 20 '18 at 05:50
  • 2
    The gRPC C# quickstart seems out of date indeed. I created https://github.com/grpc/grpc.github.io/pull/728 to fix this. It contains updated instruction on how to do codegen when using the dotnet SDK. – Jan Tattermusch Aug 21 '18 at 08:05
0

the following approach helped me : Create a gRPC client and server in ASP.NET Core

in project, where .proto file located, edit the .csproj file

  <ItemGroup>
    ....
    <Protobuf Include="Shipping.proto" GrpcServices="Server" />
  </ItemGroup>

rebuild the project, the all necessary .cs files will be added automaticaly

\obj\Debug\[TARGET_FRAMEWORK]\Shipping.cs
\obj\Debug\[TARGET_FRAMEWORK]\ShippingGrpc.cs
Z.R.T.
  • 1,543
  • 12
  • 15