gRPC 遇上 .NET SDK 和 Visual Studio:构建时自动代码生成
作为 Microsoft 向其跨平台 .NET 产品迈进的一部分,他们大大简化了项目文件格式,并允许第三方代码生成器与 .NET 项目紧密集成。 我们正在倾听,现在很自豪地推出在 .NET C# 项目中集成编译 Protocol Buffer 和 gRPC 服务 .proto
文件,从 Nuget.org 提供的 Grpc.Tools NuGet 包的 1.17 版本开始。
您不再需要使用手写的脚本从 .proto
文件生成代码:.NET 构建魔法为您处理此问题。 集成工具定位 proto 编译器和 gRPC 插件、标准 Protocol Buffer 导入,并在调用代码生成器之前跟踪依赖项,以便生成的 C# 源文件永远不会过时,同时将重新生成保持在所需的最低限度。 本质上,.proto
文件在 .NET C# 项目中被视为一流的源文件。
演练
在这篇博文中,我们将演练使用跨平台 dotnet
命令从 .proto
文件创建库的最简单且可能是最常见的场景。 我们将实现本质上是 Greeter
库的克隆,该库在 C# Helloworld
示例目录 中的客户端和服务器项目之间共享。
创建一个新项目
让我们从创建一个新的库项目开始。
~/work$ dotnet new classlib -o MyGreeter
The template "Class library" was created successfully.
~/work$ cd MyGreeter
~/work/MyGreeter$ ls -lF
total 12
-rw-rw-r-- 1 kkm kkm 86 Nov 9 16:10 Class1.cs
-rw-rw-r-- 1 kkm kkm 145 Nov 9 16:10 MyGreeter.csproj
drwxrwxr-x 2 kkm kkm 4096 Nov 9 16:10 obj/
请注意,dotnet new
命令创建了我们不需要的文件 Class1.cs
,因此请将其删除。 此外,我们需要一些 .proto
文件来编译。 对于此练习,我们将从 gRPC 发行版中复制一个示例文件 examples/protos/helloworld.proto
。
~/work/MyGreeter$ rm Class1.cs
~/work/MyGreeter$ wget -q https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/helloworld.proto
(在 Windows 上,使用 del Class1.cs
,如果您没有 wget 命令,只需打开上面的 URL ,并使用 Web 浏览器中的另存为…命令)。
接下来,将所需的 NuGet 包添加到项目中
~/work/MyGreeter$ dotnet add package Grpc
info : PackageReference for package 'Grpc' version '1.17.0' added to file '/home/kkm/work/MyGreeter/MyGreeter.csproj'.
~/work/MyGreeter$ dotnet add package Grpc.Tools
info : PackageReference for package 'Grpc.Tools' version '1.17.0' added to file '/home/kkm/work/MyGreeter/MyGreeter.csproj'.
~/work/MyGreeter$ dotnet add package Google.Protobuf
info : PackageReference for package 'Google.Protobuf' version '3.6.1' added to file '/home/kkm/work/MyGreeter/MyGreeter.csproj'.
将 .proto
文件添加到项目
接下来是重要部分。 首先,默认情况下,.csproj
项目文件会自动查找其目录中的所有 .cs
文件,尽管 Microsoft 现在建议禁止这种 globbing 行为,因此我们也决定反对 globbing .proto
文件。 因此,必须将 .proto
文件显式添加到项目中。
其次,重要的是将属性 PrivateAssets="All"
添加到 Grpc.Tools 包引用中,这样它就不会被您的新库的使用者不必要地获取。 这是有道理的,因为该包仅包含编译器、代码生成器和导入文件,这些在编译 .proto
文件的项目之外是不需要的。 虽然在此简单的演练中并非严格要求,但必须始终这样做作为您的标准做法。
所以,编辑 `MyGreeter.csproj` 文件,添加 `helloworld.proto` 文件以便它能被编译,并将 `PrivateAssets` 属性添加到 Grpc.Tools 包引用中。你的项目文件现在应该看起来像这样:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
<PackageReference Include="Grpc" Version="1.17.0" />
<!-- The Grpc.Tools package generates C# sources from .proto files during
project build, but is not needed by projects using the built library.
It's IMPORTANT to add the 'PrivateAssets="All"' to this reference: -->
<PackageReference Include="Grpc.Tools" Version="1.17.0" PrivateAssets="All" />
<!-- Explicitly include our helloworld.proto file by adding this line: -->
<Protobuf Include="helloworld.proto" />
</ItemGroup>
</Project>
构建它!
此时,你可以使用 `dotnet build` 命令构建项目,以编译 `.proto` 文件和库程序集。在本演练中,我们将向命令添加一个日志开关 `-v:n`,这样我们就可以看到编译 `helloworld.proto` 文件的命令实际上已运行。您可能会发现在第一次编译项目时始终这样做是个好主意!
请注意,以下省略了许多输出行,因为构建输出非常冗长。
~/work/MyGreeter$ dotnet build -v:n
Build started 11/9/18 5:33:44 PM.
1:7>Project "/home/kkm/work/MyGreeter/MyGreeter.csproj" on node 1 (Build target(s)).
1>_Protobuf_CoreCompile:
/home/kkm/.nuget/packages/grpc.tools/1.17.0/tools/linux_x64/protoc
--csharp_out=obj/Debug/netstandard2.0
--plugin=protoc-gen-grpc=/home/kkm/.nuget/packages/grpc.tools/1.17.0/tools/linux_x64/grpc_csharp_plugin
--grpc_out=obj/Debug/netstandard2.0 --proto_path=/home/kkm/.nuget/packages/grpc.tools/1.17.0/build/native/include
--proto_path=. --dependency_out=obj/Debug/netstandard2.0/da39a3ee5e6b4b0d_helloworld.protodep helloworld.proto
CoreCompile:
[ ... skipping long output ... ]
MyGreeter -> /home/kkm/work/MyGreeter/bin/Debug/netstandard2.0/MyGreeter.dll
Build succeeded.
如果在此时再次调用 `dotnet build -v:n` 命令,则不会调用 `protoc`,也不会编译 C# 源代码。但是,如果更改 `helloworld.proto` 源代码,则其输出将在构建期间重新生成,然后由 C# 编译器重新编译。这是你期望从修改任何源文件得到的常规依赖跟踪行为。
当然,你也可以向同一项目添加 `.cs` 文件:毕竟,这是一个构建 .NET 库的常规 C# 项目。这在我们的 RouteGuide 示例中完成。
生成的 文件在哪里?
你可能想知道 proto 编译器和 gRPC 插件输出的 C# 文件在哪里。默认情况下,它们与对象等其他生成文件放在同一目录中(在 .NET 构建术语中称为“中间输出”目录),位于 `obj/` 目录下。这是 .NET 构建的常规做法,这样自动生成的文件就不会弄乱工作目录或意外地置于源代码控制之下。否则,它们可以被调试器等工具访问。你也可以在该目录中看到其他自动生成的源代码。
~/work/MyGreeter$ find obj -name '*.cs'
obj/Debug/netstandard2.0/MyGreeter.AssemblyInfo.cs
obj/Debug/netstandard2.0/Helloworld.cs
obj/Debug/netstandard2.0/HelloworldGrpc.cs
(如果你正在从 Windows 命令提示符执行此演练,请使用 `dir /s obj\*.cs` )。
还有更多
虽然最简单的默认行为在许多情况下都足够了,但在大型项目中,有很多方法可以微调你的 `.proto` 编译过程。如果发现默认安排不适合你的工作流程,我们建议你阅读 文档文件 BUILD-INTEGRATION.md ,了解可用的选项。该包还扩展了 Visual Studio 的“属性”窗口,因此你可以在 Visual Studio 界面中为每个文件设置一些选项。
“经典” ` .csproj` 项目和 Mono 也受支持。
分享您的经验
与任何复杂功能的初始版本一样,我们很高兴收到你的反馈。是否有任何未按预期工作的事情?是否有你认为新工具不容易涵盖的场景?你是否有关于如何改进总体工作流程的想法?请仔细阅读文档,然后在 GitHub 上的 gRPC 代码存储库中 打开一个问题。你的反馈对于确定我们构建集成工作的未来方向至关重要!