[csharp] Grpc.Tools documentation for sharing proto files between projects and nuget packages (#34521)

Added documented for Grpc.Tools for sharing proto files

- between projects in a solution
- in NuGet packages to be used by other projects

This addresses the questions raised in:
- https://github.com/grpc/grpc-dotnet/issues/1458
- https://github.com/grpc/grpc-dotnet/issues/183

@jtattermusch @JamesNK

---------

Co-authored-by: James Newton-King <james@newtonking.com>
pull/34727/head
tony 1 year ago committed by GitHub
parent a935d7c190
commit 301636dae0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 184
      src/csharp/BUILD-INTEGRATION.md

@ -79,6 +79,63 @@ For `.proto` files that are outside of the project directory a link can be added
For more examples see the example project files in GitHub: https://github.com/grpc/grpc-dotnet/tree/master/examples
## Sharing `.proto` files between multiple projects (in the same VS solution)
It's common to want to share `.proto` files between projects. For example, a gRPC client
and gRPC server share the same contract. It is preferable to share contracts without
copying `.proto` files because copies can go out of sync over time.
There are a couple of ways to use `.proto` files in multiple projects without duplication:
* Sharing `.proto` files between projects with MSBuild links.
* Generating code in a class library and sharing the library.
### Sharing `.proto` with MSBuild links
`.proto` files can be placed in a shared location and referenced by multiple projects
using MSBuild's [`Link` or `LinkBase` settings](https://learn.microsoft.com/visualstudio/msbuild/common-msbuild-item-metadata).
```xml
<ItemGroup>
<Protobuf Include="..\Protos\greet.proto" GrpcServices="None" Link="Protos\greet.proto"/>
</ItemGroup>
```
In the example above, `greet.proto` is in a shared `Protos` directory outside
the project directory. Multiple projects can reference the proto file.
### Generating code in a class library
Create a class library that references `.proto` files and contains generated code. The other
projects in the solution can then reference this shared class library instead of each project
having to compile the same `.proto` files.
The advantages of this are:
- The `.proto` only need to be compiled once.
- It prevents some projects getting multiple definitions of the same generated code, which can in turn break the build.
There are a couple of examples in GitHub:
- The [Liber example](https://github.com/grpc/grpc-dotnet/tree/master/examples#liber)
demonstrates how common protocol buffers messages can be compiled once and used in other projects:
- The *Common* project creates a class library that includes the generates messages contained in `common.proto`
- The *Client* and *Server* projects reference the *Common* project.
- They do not need to recompile `common.proto` as those .NET types are already in
the *Common* class library.
- They do however each generate their own gRPC client or server code as both have a
`<Protobuf>` reference for `greet.proto`. The *Client* and *Server* projects each having their own version of `greet.proto` is OK since they don't reference each other - they only reference the shared *Common* class library.
- The [RouteGuide example](https://github.com/grpc/grpc/tree/v1.46.x/examples/csharp/RouteGuide)
demonstrates how the gRPC client and server code can be generated once and used in other
projects:
- **Note:** this example uses the *legacy c-core C#* packages, but the principles are the same
for *gRPC for .NET* projects.
- The *RouteGuide* project has a `<Protobuf>` reference to `route_guide.proto` and
generates both the gRPC client and server code.
- The *RouteGuideClient* and *RouteGuideServer* projects reference the *RouteGuide* project.
- They do not need any `<Protobuf>` references since the code has already been
generated in the *RouteGuide* project.
# Reference
## Protobuf item metadata reference
@ -252,6 +309,7 @@ Quick links to the examples below:
* [Visual Studio: setting per-file `.proto` file options](#visualstudio)
* [Bypassing Grpc.Tools to run the protocol buffers compiler explicitly](#compiler)
* [Using Grpc.Tools with unsupported architectures](#unsupported-arch)
* [Including `.proto` files in NuGet packages](#proto-only-nuget)
---
## <a name="ProtoRoot"></a>ProtoRoot - Common root for one or more `.proto` files
@ -565,6 +623,132 @@ dotnet build
---
## <a name="proto-only-nuget"></a>Including `.proto` files in NuGet packages
There might be occassions when you are given a NuGet package that contains
`.proto` files that you wish to use in your own project, or you may wish to
package your own `.proto` files in a NuGet package for others to use.
There is no automatic way for `Grpc.Tools` to locate and include `.proto`
files from other NuGet packages. Below is a suggested convention to use
when creating NuGet packages that contain `.proto` files.
__Note:__ This is not the same as a NuGet package providing a library built from
the code generated from the `.proto` files. Below just describes how to provide
the uncompiled `.proto` files in a NuGet package and have `Grpc.Tools` automatically
use them.
### Creating the NuGet package
The NuGet package should:
- provide the `.proto` files in a `content\protos` subdirectory in the package
- provide a `packagename.targets` file in the `build` subdirectory in the package that:
- defines an MSBuild property giving the path to the `.proto` files in the
installed package
- conditionally updates the `Protobuf_StandardImportsPath` property with the
above path so that the files can be found by the protocol buffers compiler
- it should be made optional forcing users to *opt in* to including
the `.proto` files
For example, for the package `My.Example.Protos`:
My.Example.Protos.nuspec:
```xml
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>My.Example.Protos</id>
<version>1.0.0</version>
<title>Example package containing proto files</title>
<authors>author</authors>
<owners>owner</owners>
<licenseUrl>license url</licenseUrl>
<projectUrl>project url</projectUrl>
<description>See project site for more info.</description>
<summary>Example package containing proto files.</summary>
<releaseNotes>Example package containing proto files</releaseNotes>
<copyright>Copyright 2023, My Company.</copyright>
</metadata>
<files>
<!-- copy the My.Example.Protos.targets file for MSBuild integration -->
<file src="build\**" target="build" />
<!-- copy the .proto files into the package -->
<file src="proto\**" target="content\protos" />
</files>
</package>
```
My.Example.Protos.targets:
```xml
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This targets file allows .proto files bundled in package,
to be included in Grpc.Tools compilation. -->
<PropertyGroup>
<!-- Define a property containing the path of the proto files.
Content from the nupkg. -->
<MyExampleProtos_ProtosPath>$( [System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)../content/protos) )</MyExampleProtos_ProtosPath>
</PropertyGroup>
<!-- Run immediately before the Protobuf_BeforeCompile extension point. -->
<!-- Only include protos if project has set <IncludeMyExampleProtosProtos>
property to true. -->
<Target Name="MyExampleProtos_BeforeCompile"
BeforeTargets="Protobuf_BeforeCompile"
Condition=" '$(IncludeMyExampleProtosProtos)' == 'true' ">
<PropertyGroup>
<!-- Add proto files by including path in Protobuf_StandardImportsPath.
This path is passed to protoc via the -I option -->
<Protobuf_StandardImportsPath>$(Protobuf_StandardImportsPath);$(MyPackage_ProtosPath)</Protobuf_StandardImportsPath>
</PropertyGroup>
<!-- These message are not required but included here for diagnostics -->
<Message Text="Included proto files at $(MyExampleProtos_ProtosPath) in import path." Importance="high" />
<Message Text="Updated proto imports path: $(Protobuf_StandardImportsPath)" Importance="high" />
</Target>
</Project>
```
### Using the NuGet package
The project needs to add the package containing the `.proto` files:
```xml
<PackageReference Include="My.Example.Protos" Version="1.0.0" />
```
If the project only wants to compile the `.proto` files included in the package
then all it needs to do is add the `<Protobuf>` items using the property defined
in the package for the path to the files. For example, if the NuGet package contained
the file `greet.proto`, then the project should add:
```xml
<Protobuf Include="$(MyExampleProtos_ProtosPath)/greet.proto" />
```
However if the provided `.proto` files are to be *imported* by the projects own `.proto`
files then the `Protobuf_StandardImportsPath` needs updated to add the directory
containing the package's files. This is done by setting to `true` the property
used in the package. For example, if the project has the local `.proto` file
`my_services.proto` and it imported a file from the package `common_message.proto`,
then:
```xml
<PropertyGroup>
<!-- Update the Protobuf_StandardImportsPath -->
<IncludeMyExampleProtosProtos>true</IncludeMyExampleProtosProtos>
</PropertyGroup>
<ItemGroup>
<!-- my_services.proto imports common_message.proto from the package
My.Example.Protos -->
<Protobuf Include="my_services.proto" />
</ItemGroup>
```
---
## See also
gRPC project documentation:

Loading…
Cancel
Save