Add Grpc.Tools MsBuild taks assembly, test and scripting

This is a complete set of tooling to build .proto files, with or without
gRPC services, in .csproj, both "classic" and SDK flavors, and a bare
minimum support for C++ projects.

Highlights and omissions:
 * By default, generated files are placed into project's intermediate
   directory under obj/, and treated as temporary generated sources.
 * The projects are highly customizabe thorugh item metadata on Protobuf
   items.
 * SDK projects only use Visual Studio new build system, and automatically
   import XAML property sheets that allow setting per-file properties,
   such as generated file access, and whether to expect gRPC outputs, from
   VS properties windows. This possibly requires VS restart after the
   package is added to solution. Classic projects cannot be extended this
   way, and only show Protobuf as the possible item; settings are modified
   by editing the project only.
 * For C++ projects, only the tool and standard proto import paths are
   provided, no custom targets yet. This is in the works.
 * gRPC and Protobuf scripts are separate, and everything is programmed to
   easily split the Tools package into one for Google.Protobuf and another
   for Grpc.Tools. This requires tighter coordination between the teams.
 * The tasks DLL knows about gRPC. I tried to use it to support gRPC in a
   script-only fashion, but using the tasks results in much cleaner
   scripts. This is probably how it should remain.
 * In multitarget projects (multiple frameworks) protoc files are compiled
   for each target, and also for Debug/Release configuration sepatately. A
   possible fix is in the works, but requries some MsBuild tooling fixes,
   so it will take a while.
 * There are 4 tasks. The "smart" task predicts protoc outputs, and knows
   things about protoc naming conventions. This supports only C# and C++.
   The "dumb" task simply invokes protoc in a language-independent way,
   and supports all languages known to protoc. In the (not very likely)
   case protoc is used with MsBuild for these languages, instructions for
   extending the build is provided in build script comments. The other 2
   tasks are one to detect current platform and therefore tools paths, and
   another to read protoc generated dependency file. We use it for C#, but
   custom project may opt not to use the dependecy files.
 * 64-bit tools for Windows (protoc and grpc plugin exe) have been removed
   from package, as Windows is alsways able to run 32-bit executable (and
   they are smaller and faster, and always preferred when 2G address space
   is enough).
pull/13207/head
kkm 7 years ago
parent 7a2a8ca4ba
commit a93e3d2753
  1. 134
      src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs
  2. 165
      src/csharp/Grpc.Tools.Tests/GeneratorTests.cs
  3. 27
      src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj
  4. 31
      src/csharp/Grpc.Tools.Tests/NUnitMain.cs
  5. 232
      src/csharp/Grpc.Tools.Tests/ProtoCompileTaskTest.cs
  6. 53
      src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs
  7. 43
      src/csharp/Grpc.Tools.Tests/Utils.cs
  8. 33
      src/csharp/Grpc.Tools.nuspec
  9. 105
      src/csharp/Grpc.Tools/Common.cs
  10. 198
      src/csharp/Grpc.Tools/DepFileUtil.cs
  11. 168
      src/csharp/Grpc.Tools/GeneratorServices.cs
  12. 95
      src/csharp/Grpc.Tools/Grpc.Tools.csproj
  13. 409
      src/csharp/Grpc.Tools/ProtoCompile.cs
  14. 80
      src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs
  15. 70
      src/csharp/Grpc.Tools/ProtoReadDependencies.cs
  16. 58
      src/csharp/Grpc.Tools/ProtoToolsPlatform.cs
  17. 11
      src/csharp/Grpc.Tools/build/Grpc.Tools.props
  18. 11
      src/csharp/Grpc.Tools/build/Grpc.Tools.targets
  19. 30
      src/csharp/Grpc.Tools/build/_grpc/Grpc.CSharp.xml
  20. 3
      src/csharp/Grpc.Tools/build/_grpc/README
  21. 6
      src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.props
  22. 46
      src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets
  23. 23
      src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props
  24. 383
      src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets
  25. 99
      src/csharp/Grpc.Tools/build/_protobuf/Protobuf.CSharp.xml
  26. 1
      src/csharp/Grpc.Tools/build/_protobuf/README
  27. 15
      src/csharp/Grpc.Tools/build/native/Grpc.Tools.props
  28. 12
      src/csharp/Grpc.sln
  29. 2
      src/csharp/build_packages_dotnetcli.bat
  30. 2
      src/csharp/build_packages_dotnetcli.sh
  31. 10
      src/csharp/tests.json
  32. 2
      templates/src/csharp/build_packages_dotnetcli.bat.template
  33. 2
      templates/src/csharp/build_packages_dotnetcli.sh.template

@ -0,0 +1,134 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
using System.IO;
using Grpc.Tools;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using NUnit.Framework;
namespace Grps.Tools.Tests {
public class DepFileUtilTests {
[Test]
public void HashString64Hex_IsSane() {
string hashFoo1 = DepFileUtil.HashString64Hex("foo");
string hashEmpty = DepFileUtil.HashString64Hex("");
string hashFoo2 = DepFileUtil.HashString64Hex("foo");
StringAssert.IsMatch("^[a-f0-9]{16}$", hashFoo1);
Assert.AreEqual(hashFoo1, hashFoo2);
Assert.AreNotEqual(hashFoo1, hashEmpty);
}
[Test]
public void GetDepFilenameForProto_IsSane() {
StringAssert.IsMatch(@"^out[\\/][a-f0-9]{16}_foo.protodep$",
DepFileUtil.GetDepFilenameForProto("out", "foo.proto"));
StringAssert.IsMatch(@"^[a-f0-9]{16}_foo.protodep$",
DepFileUtil.GetDepFilenameForProto("", "foo.proto"));
}
[Test]
public void GetDepFilenameForProto_HashesDir() {
string PickHash(string fname) =>
DepFileUtil.GetDepFilenameForProto("", fname).Substring(0, 16);
string same1 = PickHash("dir1/dir2/foo.proto");
string same2 = PickHash("dir1/dir2/proto.foo");
string same3 = PickHash("dir1/dir2/proto");
string same4 = PickHash("dir1/dir2/.proto");
string unsame1 = PickHash("dir2/foo.proto");
string unsame2 = PickHash("/dir2/foo.proto");
Assert.AreEqual(same1, same2);
Assert.AreEqual(same1, same3);
Assert.AreEqual(same1, same4);
Assert.AreNotEqual(same1, unsame1);
Assert.AreNotEqual(unsame1, unsame2);
}
//////////////////////////////////////////////////////////////////////////
// Full file reading tests
// Generated by protoc on Windows. Slashes vary.
const string depFile1 =
@"C:\projects\foo\src\./foo.grpc.pb.cc \
C:\projects\foo\src\./foo.grpc.pb.h \
C:\projects\foo\src\./foo.pb.cc \
C:\projects\foo\src\./foo.pb.h: C:/usr/include/google/protobuf/wrappers.proto\
C:/usr/include/google/protobuf/any.proto\
C:/usr/include/google/protobuf/source_context.proto\
C:/usr/include/google/protobuf/type.proto\
foo.proto";
// This has a nasty output directory with a space.
const string depFile2 =
@"obj\Release x64\net45\/Foo.cs \
obj\Release x64\net45\/FooGrpc.cs: C:/usr/include/google/protobuf/wrappers.proto\
C:/projects/foo/src//foo.proto";
[Test]
public void ReadDependencyInput_FullFile1() {
string[] deps = ReadDependencyInputFromFileData(depFile1, "foo.proto");
Assert.NotNull(deps);
Assert.That(deps, Has.Length.InRange(4, 5)); // foo.proto may or may not be listed.
Assert.That(deps, Has.One.EndsWith("wrappers.proto"));
Assert.That(deps, Has.One.EndsWith("type.proto"));
Assert.That(deps, Has.None.StartWith(" "));
}
[Test]
public void ReadDependencyInput_FullFile2() {
string[] deps = ReadDependencyInputFromFileData(depFile2, "C:/projects/foo/src/foo.proto");
Assert.NotNull(deps);
Assert.That(deps, Has.Length.InRange(1, 2));
Assert.That(deps, Has.One.EndsWith("wrappers.proto"));
Assert.That(deps, Has.None.StartWith(" "));
}
[Test]
public void ReadDependencyInput_FullFileUnparsable() {
string[] deps = ReadDependencyInputFromFileData("a:/foo.proto", "/foo.proto");
Assert.NotNull(deps);
Assert.Zero(deps.Length);
}
// NB in our tests files are put into the temp directory but all have
// different names. Avoid adding files with the same directory path and
// name, or add reasonable handling for it if required. Tests are run in
// parallel and will collide otherwise.
private string[] ReadDependencyInputFromFileData(string fileData, string protoName) {
string tempPath = Path.GetTempPath();
string tempfile = DepFileUtil.GetDepFilenameForProto(tempPath, protoName);
try {
File.WriteAllText(tempfile, fileData);
var mockEng = new Moq.Mock<IBuildEngine>();
var log = new TaskLoggingHelper(mockEng.Object, "x");
return DepFileUtil.ReadDependencyInputs(tempPath, protoName, log);
} finally {
try {
File.Delete(tempfile);
} catch { }
}
}
}
}

@ -0,0 +1,165 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Moq;
using NUnit.Framework;
namespace Grpc.Tools.Tests {
public class GeneratorTests {
protected Mock<IBuildEngine> _mockEngine;
protected TaskLoggingHelper _log;
[SetUp]
public void SetUp() {
_mockEngine = new Mock<IBuildEngine>();
_log = new TaskLoggingHelper(_mockEngine.Object, "dummy");
}
[TestCase("csharp")]
[TestCase("CSharp")]
[TestCase("cpp")]
public void ValidLanguages(string lang) {
Assert.IsNotNull(GeneratorServices.GetForLanguage(lang, _log));
_mockEngine.Verify(me => me.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()), Times.Never);
}
[TestCase("")]
[TestCase("COBOL")]
public void InvalidLanguages(string lang) {
Assert.IsNull(GeneratorServices.GetForLanguage(lang, _log));
_mockEngine.Verify(me => me.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()), Times.Once);
}
};
public class CSharpGeneratorTests : GeneratorTests {
GeneratorServices _generator;
[SetUp]
public new void SetUp() {
_generator = GeneratorServices.GetForLanguage("CSharp", _log);
}
[TestCase("foo.proto", "Foo.cs", "FooGrpc.cs")]
[TestCase("sub/foo.proto", "Foo.cs", "FooGrpc.cs")]
[TestCase("one_two.proto", "OneTwo.cs", "OneTwoGrpc.cs")]
[TestCase("__one_two!.proto", "OneTwo!.cs", "OneTwo!Grpc.cs")]
[TestCase("one(two).proto", "One(two).cs", "One(two)Grpc.cs")]
[TestCase("one_(two).proto", "One(two).cs", "One(two)Grpc.cs")]
[TestCase("one two.proto", "One two.cs", "One twoGrpc.cs")]
[TestCase("one_ two.proto", "One two.cs", "One twoGrpc.cs")]
[TestCase("one .proto", "One .cs", "One Grpc.cs")]
public void NameMangling(string proto, string expectCs, string expectGrpcCs) {
var poss = _generator.GetPossibleOutputs(Utils.MakeItem(proto, "grpcservices", "both"));
Assert.AreEqual(2, poss.Length);
Assert.Contains(expectCs, poss);
Assert.Contains(expectGrpcCs, poss);
}
[Test]
public void NoGrpcOneOutput() {
var poss = _generator.GetPossibleOutputs(Utils.MakeItem("foo.proto"));
Assert.AreEqual(1, poss.Length);
}
[TestCase("none")]
[TestCase("")]
public void GrpcNoneOneOutput(string grpc) {
var item = Utils.MakeItem("foo.proto", "grpcservices", grpc);
var poss = _generator.GetPossibleOutputs(item);
Assert.AreEqual(1, poss.Length);
}
[TestCase("client")]
[TestCase("server")]
[TestCase("both")]
public void GrpcEnabledTwoOutputs(string grpc) {
var item = Utils.MakeItem("foo.proto", "grpcservices", grpc);
var poss = _generator.GetPossibleOutputs(item);
Assert.AreEqual(2, poss.Length);
}
[Test]
public void OutputDirMetadataRecognized() {
var item = Utils.MakeItem("foo.proto", "OutputDir", "out");
var poss = _generator.GetPossibleOutputs(item);
Assert.AreEqual(1, poss.Length);
Assert.That(poss[0], Is.EqualTo("out/Foo.cs") | Is.EqualTo("out\\Foo.cs"));
}
};
public class CppGeneratorTests : GeneratorTests {
GeneratorServices _generator;
[SetUp]
public new void SetUp() {
_generator = GeneratorServices.GetForLanguage("Cpp", _log);
}
[TestCase("foo.proto", "", "foo")]
[TestCase("foo.proto", ".", "foo")]
[TestCase("foo.proto", "./", "foo")]
[TestCase("sub/foo.proto", "", "sub/foo")]
[TestCase("root/sub/foo.proto", "root", "sub/foo")]
[TestCase("root/sub/foo.proto", "root", "sub/foo")]
[TestCase("/root/sub/foo.proto", "/root", "sub/foo")]
public void RelativeDirectoryCompute(string proto, string root, string expectStem) {
if (Path.DirectorySeparatorChar == '\\')
expectStem = expectStem.Replace('/', '\\');
var poss = _generator.GetPossibleOutputs(Utils.MakeItem(proto, "ProtoRoot", root));
Assert.AreEqual(2, poss.Length);
Assert.Contains(expectStem + ".pb.cc", poss);
Assert.Contains(expectStem + ".pb.h", poss);
}
[Test]
public void NoGrpcTwoOutputs() {
var poss = _generator.GetPossibleOutputs(Utils.MakeItem("foo.proto"));
Assert.AreEqual(2, poss.Length);
}
[TestCase("false")]
[TestCase("")]
public void GrpcDisabledTwoOutput(string grpc) {
var item = Utils.MakeItem("foo.proto", "grpcservices", grpc);
var poss = _generator.GetPossibleOutputs(item);
Assert.AreEqual(2, poss.Length);
}
[TestCase("true")]
public void GrpcEnabledFourOutputs(string grpc) {
var item = Utils.MakeItem("foo.proto", "grpcservices", grpc);
var poss = _generator.GetPossibleOutputs(item);
Assert.AreEqual(4, poss.Length);
Assert.Contains("foo.pb.cc", poss);
Assert.Contains("foo.pb.h", poss);
Assert.Contains("foo_grpc.pb.cc", poss);
Assert.Contains("foo_grpc.pb.h", poss);
}
[Test]
public void OutputDirMetadataRecognized() {
var item = Utils.MakeItem("foo.proto", "OutputDir", "out");
var poss = _generator.GetPossibleOutputs(item);
Assert.AreEqual(2, poss.Length);
Assert.That(Path.GetDirectoryName(poss[0]), Is.EqualTo("out"));
}
};
}

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetFrameworks>netcoreapp1.0;net45</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Grpc.Tools\Grpc.Tools.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="4.7.145" />
<PackageReference Include="NUnit" Version="3.9.0" />
<PackageReference Include="NUnitLite" Version="3.9.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">
<PackageReference Include="Microsoft.Build.Framework" Version="15.5.180" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.5.180" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="Microsoft.Build.Framework; Microsoft.Build.Utilities.v4.0" />
</ItemGroup>
</Project>

@ -0,0 +1,31 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System.Reflection;
using NUnitLite;
namespace Grps.Tools.Tests {
static class NUnitMain {
public static int Main(string[] args) =>
#if NETCOREAPP1_0
new AutoRun(typeof(NUnitMain).GetTypeInfo().Assembly).Execute(args);
#else
new AutoRun().Execute(args);
#endif
};
}

@ -0,0 +1,232 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System.Reflection;
using Microsoft.Build.Framework;
using Moq;
using NUnit.Framework;
namespace Grpc.Tools.Tests {
public class ProtoCompileBasicTests {
// Mock task class that stops right before invoking protoc.
public class ProtoCompileTestable : ProtoCompile {
public string LastPathToTool { get; private set; }
public string[] LastResponseFile { get; private set; }
protected override int ExecuteTool(string pathToTool,
string response,
string commandLine) {
// We should never be using command line commands.
Assert.That(commandLine, Is.Null | Is.Empty);
// Must receive a path to tool
Assert.That(pathToTool, Is.Not.Null & Is.Not.Empty);
Assert.That(response, Is.Not.Null & Does.EndWith("\n"));
LastPathToTool = pathToTool;
LastResponseFile = response.Remove(response.Length - 1).Split('\n');
// Do not run the tool, but pretend it ran successfully.
return 0;
}
};
protected Mock<IBuildEngine> _mockEngine;
protected ProtoCompileTestable _task;
[SetUp]
public void SetUp() {
_mockEngine = new Mock<IBuildEngine>();
_task = new ProtoCompileTestable {
BuildEngine = _mockEngine.Object
};
}
[TestCase("ProtoBuf")]
[TestCase("Generator")]
[TestCase("OutputDir")]
[Description("We trust MSBuild to initialize these properties.")]
public void RequiredAttributePresentOnProperty(string prop) {
var pinfo = _task.GetType()?.GetProperty(prop);
Assert.NotNull(pinfo);
Assert.That(pinfo, Has.Attribute<RequiredAttribute>());
}
};
internal class ProtoCompileCommandLineGeneratorTests : ProtoCompileBasicTests {
[SetUp]
public new void SetUp() {
_task.Generator = "csharp";
_task.OutputDir = "outdir";
_task.ProtoBuf = Utils.MakeSimpleItems("a.proto");
}
void ExecuteExpectSuccess() {
_mockEngine
.Setup(me => me.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback((BuildErrorEventArgs e) =>
Assert.Fail($"Error logged by build engine:\n{e.Message}"));
bool result = _task.Execute();
Assert.IsTrue(result);
}
[Test]
public void MinimalCompile() {
ExecuteExpectSuccess();
Assert.That(_task.LastPathToTool, Does.Match(@"protoc(.exe)?$"));
Assert.That(_task.LastResponseFile, Is.EqualTo(new[] {
"--csharp_out=outdir", "a.proto" }));
}
[Test]
public void CompileTwoFiles() {
_task.ProtoBuf = Utils.MakeSimpleItems("a.proto", "foo/b.proto");
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.EqualTo(new[] {
"--csharp_out=outdir", "a.proto", "foo/b.proto" }));
}
[Test]
public void CompileWithProtoPaths() {
_task.ProtoPath = new[] { "/path1", "/path2" };
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.EqualTo(new[] {
"--csharp_out=outdir", "--proto_path=/path1",
"--proto_path=/path2", "a.proto" }));
}
[TestCase("Cpp")]
[TestCase("CSharp")]
[TestCase("Java")]
[TestCase("Javanano")]
[TestCase("Js")]
[TestCase("Objc")]
[TestCase("Php")]
[TestCase("Python")]
[TestCase("Ruby")]
public void CompileWithOptions(string gen) {
_task.Generator = gen;
_task.OutputOptions = new[] { "foo", "bar" };
ExecuteExpectSuccess();
gen = gen.ToLowerInvariant();
Assert.That(_task.LastResponseFile, Is.EqualTo(new[] {
$"--{gen}_out=outdir", $"--{gen}_opt=foo,bar", "a.proto" }));
}
[Test]
public void OutputDependencyFile() {
_task.DependencyOut = "foo/my.protodep";
// Task fails trying to read the non-generated file; we ignore that.
_task.Execute();
Assert.That(_task.LastResponseFile,
Does.Contain("--dependency_out=foo/my.protodep"));
}
[Test]
public void OutputDependencyWithProtoDepDir() {
_task.ProtoDepDir = "foo";
// Task fails trying to read the non-generated file; we ignore that.
_task.Execute();
Assert.That(_task.LastResponseFile,
Has.One.Match(@"^--dependency_out=foo[/\\].+_a.protodep$"));
}
[Test]
public void GenerateGrpc() {
_task.GrpcPluginExe = "/foo/grpcgen";
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] {
"--csharp_out=outdir", "--grpc_out=outdir",
"--plugin=protoc-gen-grpc=/foo/grpcgen" }));
}
[Test]
public void GenerateGrpcWithOutDir() {
_task.GrpcPluginExe = "/foo/grpcgen";
_task.GrpcOutputDir = "gen-out";
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] {
"--csharp_out=outdir", "--grpc_out=gen-out" }));
}
[Test]
public void GenerateGrpcWithOptions() {
_task.GrpcPluginExe = "/foo/grpcgen";
_task.GrpcOutputOptions = new[] { "baz", "quux" };
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile,
Does.Contain("--grpc_opt=baz,quux"));
}
[Test]
public void DirectoryArgumentsSlashTrimmed() {
_task.GrpcPluginExe = "/foo/grpcgen";
_task.GrpcOutputDir = "gen-out/";
_task.OutputDir = "outdir/";
_task.ProtoPath = new[] { "/path1/", "/path2/" };
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] {
"--proto_path=/path1", "--proto_path=/path2",
"--csharp_out=outdir", "--grpc_out=gen-out" }));
}
[TestCase("." , ".")]
[TestCase("/" , "/")]
[TestCase("//" , "/")]
[TestCase("/foo/" , "/foo")]
[TestCase("/foo" , "/foo")]
[TestCase("foo/" , "foo")]
[TestCase("foo//" , "foo")]
[TestCase("foo/\\" , "foo")]
[TestCase("foo\\/" , "foo")]
[TestCase("C:\\foo", "C:\\foo")]
[TestCase("C:" , "C:")]
[TestCase("C:\\" , "C:\\")]
[TestCase("C:\\\\" , "C:\\")]
public void DirectorySlashTrimmingCases(string given, string expect) {
_task.OutputDir = given;
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile,
Does.Contain("--csharp_out=" + expect));
}
};
internal class ProtoCompileCommandLinePrinterTests : ProtoCompileBasicTests {
[SetUp]
public new void SetUp() {
_task.Generator = "csharp";
_task.OutputDir = "outdir";
_task.ProtoBuf = Utils.MakeSimpleItems("a.proto");
_mockEngine
.Setup(me => me.LogMessageEvent(It.IsAny<BuildMessageEventArgs>()))
.Callback((BuildMessageEventArgs e) =>
Assert.Fail($"Error logged by build engine:\n{e.Message}"))
.Verifiable("Command line was not output by the task.");
}
void ExecuteExpectSuccess() {
_mockEngine
.Setup(me => me.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
.Callback((BuildErrorEventArgs e) =>
Assert.Fail($"Error logged by build engine:\n{e.Message}"));
bool result = _task.Execute();
Assert.IsTrue(result);
}
};
}

@ -0,0 +1,53 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
using Microsoft.Build.Framework;
using Moq;
using NUnit.Framework;
namespace Grpc.Tools.Tests {
// This test requires that environment variables be set to the exected
// output of the task in its external test harness:
// PROTOTOOLS_TEST_CPU = { x64 | x86 }
// PROTOTOOLS_TEST_OS = { linux | macosx | windows }
public class ProtoToolsPlatformTaskTests {
static string s_expectOs;
static string s_expectCpu;
[OneTimeSetUp]
public static void Init() {
s_expectCpu = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_CPU");
s_expectOs = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_OS");
if (s_expectCpu == null || s_expectOs == null)
Assert.Inconclusive("This test requires PROTOTOOLS_TEST_CPU and " +
"PROTOTOOLS_TEST_OS set in the environment to match the OS it runs on.");
}
[Test]
public void CpuAndOsMatchExpected() {
var mockEng = new Mock<IBuildEngine>();
var task = new ProtoToolsPlatform() {
BuildEngine = mockEng.Object
};
task.Execute();
Assert.AreEqual(s_expectCpu, task.Cpu);
Assert.AreEqual(s_expectOs, task.Os);
}
};
}

@ -0,0 +1,43 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Grpc.Tools.Tests {
static class Utils {
// Build an item with a name from args[0] and metadata key-value pairs
// from the rest of args, interleaved.
// This does not do any checking, and expects an odd number of args.
public static ITaskItem MakeItem(params string[] args) {
var item = new TaskItem(args[0]);
for (int i = 1; i < args.Length; i += 2)
item.SetMetadata(args[i], args[i + 1]);
return item;
}
// Return an array of items from given itemspecs.
public static ITaskItem[] MakeSimpleItems(params string[] specs) {
return specs.Select(s => new TaskItem(s)).ToArray();
}
};
}

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<package>
<metadata>
<id>Grpc.Tools</id>
<title>gRPC C# Tools</title>
<summary>Tools for C# implementation of gRPC - an RPC library and framework</summary>
<description>Precompiled protobuf compiler and gRPC protobuf compiler plugin for generating gRPC client/server C# code. Binaries are available for Windows, Linux and MacOS.</description>
<version>$version$</version>
<authors>Google Inc.</authors>
<owners>grpc-packages</owners>
<licenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/grpc/grpc</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<releaseNotes>Release $version$</releaseNotes>
<copyright>Copyright 2015, Google Inc.</copyright>
<tags>gRPC RPC Protocol HTTP/2</tags>
</metadata>
<files>
<!-- forward slashes in src path enable building on Linux -->
<file src="protoc_plugins/protoc_windows_x86/protoc.exe" target="tools/windows_x86/protoc.exe" />
<file src="protoc_plugins/protoc_windows_x86/grpc_csharp_plugin.exe" target="tools/windows_x86/grpc_csharp_plugin.exe" />
<file src="protoc_plugins/protoc_windows_x64/protoc.exe" target="tools/windows_x64/protoc.exe" />
<file src="protoc_plugins/protoc_windows_x64/grpc_csharp_plugin.exe" target="tools/windows_x64/grpc_csharp_plugin.exe" />
<file src="protoc_plugins/protoc_linux_x86/protoc" target="tools/linux_x86/protoc" />
<file src="protoc_plugins/protoc_linux_x86/grpc_csharp_plugin" target="tools/linux_x86/grpc_csharp_plugin" />
<file src="protoc_plugins/protoc_linux_x64/protoc" target="tools/linux_x64/protoc" />
<file src="protoc_plugins/protoc_linux_x64/grpc_csharp_plugin" target="tools/linux_x64/grpc_csharp_plugin" />
<file src="protoc_plugins/protoc_macos_x86/protoc" target="tools/macosx_x86/protoc" />
<file src="protoc_plugins/protoc_macos_x86/grpc_csharp_plugin" target="tools/macosx_x86/grpc_csharp_plugin" />
<file src="protoc_plugins/protoc_macos_x64/protoc" target="tools/macosx_x64/protoc" />
<file src="protoc_plugins/protoc_macos_x64/grpc_csharp_plugin" target="tools/macosx_x64/grpc_csharp_plugin" />
</files>
</package>

@ -0,0 +1,105 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
[assembly: InternalsVisibleTo("Grpc.Tools.Tests")]
namespace Grpc.Tools {
// Metadata names that we refer to often.
static class Metadata {
// On output dependency lists.
public static string kSource = "Source";
// On ProtoBuf items.
public static string kProtoRoot = "ProtoRoot";
public static string kOutputDir = "OutputDir";
public static string kGrpcServices = "GrpcServices";
public static string kGrpcOutputDir = "GrpcOutputDir";
};
// A few flags used to control the behavior under various platforms.
internal static class Platform {
public enum OsKind { Unknown, Windows, Linux, MacOsX };
public static readonly OsKind Os;
public enum CpuKind { Unknown, X86, X64 };
public static readonly CpuKind Cpu;
// This is not necessarily true, but good enough. BCL lacks a per-FS
// API to determine file case sensitivity.
public static bool IsFsCaseInsensitive => Os == OsKind.Windows;
public static bool IsWindows => Os == OsKind.Windows;
static Platform() {
#if NETSTANDARD
Os = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? OsKind.Windows
: RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? OsKind.Linux
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? OsKind.MacOsX
: OsKind.Unknown;
switch (RuntimeInformation.OSArchitecture) {
case Architecture.X86: Cpu = CpuKind.X86; break;
case Architecture.X64: Cpu = CpuKind.X64; break;
// We do not have build tools for other architectures.
default: Cpu = CpuKind.Unknown; break;
}
#else
// Running under either Mono or full MS framework.
Os = OsKind.Windows;
if (Type.GetType("Mono.Runtime", throwOnError: false) != null) {
// Congratulations. We are running under Mono.
var plat = Environment.OSVersion.Platform;
if (plat == PlatformID.MacOSX) {
Os = OsKind.MacOsX;
} else if (plat == PlatformID.Unix || (int)plat == 128) {
// TODO(kkm): This is how Mono detects OSX internally. Looks cheesy
// to me. Would not testing for /proc absence be more reliable? OSX
// did never have it, AFAIK.
Os = File.Exists("/usr/lib/libc.dylib") ? OsKind.MacOsX : OsKind.Linux;
}
}
// Hope we are not building on ARM under Xamarin!
Cpu = Environment.Is64BitOperatingSystem ? CpuKind.X64 : CpuKind.X86;
#endif
}
};
// Exception handling helpers.
static class Exceptions {
// Returns true iff the exception indicates an error from an I/O call. See
// https://github.com/Microsoft/msbuild/blob/v15.4.8.50001/src/Shared/ExceptionHandling.cs#L101
static public bool IsIoRelated(Exception ex) =>
ex is IOException ||
(ex is ArgumentException && !(ex is ArgumentNullException)) ||
ex is SecurityException ||
ex is UnauthorizedAccessException ||
ex is NotSupportedException;
};
// String helpers.
static class Strings {
// Compare string to argument using OrdinalIgnoreCase comparison.
public static bool EqualNoCase(this string a, string b) =>
string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
}
}

@ -0,0 +1,198 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Grpc.Tools {
internal static class DepFileUtil {
/*
Sample dependency files. Notable features we have to deal with:
* Slash doubling, must normalize them.
* Spaces in file names. Cannot just "unwrap" the line on backslash at eof;
rather, treat every line as containing one file name except for one with
the ':' separator, as containing exactly two.
* Deal with ':' also being drive letter separator (second example).
obj\Release\net45\/Foo.cs \
obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\
C:/projects/foo/src//foo.proto
C:\projects\foo\src\./foo.grpc.pb.cc \
C:\projects\foo\src\./foo.grpc.pb.h \
C:\projects\foo\src\./foo.pb.cc \
C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\
C:/foo/include/google/protobuf/any.proto\
C:/foo/include/google/protobuf/source_context.proto\
C:/foo/include/google/protobuf/type.proto\
foo.proto
*/
// Read file names from the dependency file to the right of ':'.
public static string[] ReadDependencyInputs(string protoDepDir, string proto,
TaskLoggingHelper log) {
string depFilename = GetDepFilenameForProto(protoDepDir, proto);
string[] lines = ReadDepFileLines(depFilename, false, log);
if (lines.Length == 0) {
return lines;
}
var result = new List<string>();
bool skip = true;
foreach (string line in lines) {
// Start at the only line separating dependency outputs from inputs.
int ix = skip ? FindLineSeparator(line) : -1;
skip = skip && ix < 0;
if (skip) continue;
string file = ExtractFilenameFromLine(line, ix + 1, line.Length);
if (file == "") {
log.LogMessage(MessageImportance.Low,
$"Skipping unparsable dependency file {depFilename}.\nLine with error: '{line}'");
return new string[0];
}
// Do not bend over backwards trying not to include a proto into its
// own list of dependencies. Since a file is not older than self,
// it is safe to add; this is purely a memory optimization.
if (file != proto) {
result.Add(file);
}
}
return result.ToArray();
}
// Read file names from the dependency file to the left of ':'.
public static string[] ReadDependencyOutputs(string depFilename,
TaskLoggingHelper log) {
string[] lines = ReadDepFileLines(depFilename, true, log);
if (lines.Length == 0) {
return lines;
}
var result = new List<string>();
foreach (string line in lines) {
int ix = FindLineSeparator(line);
string file = ExtractFilenameFromLine(line, 0, ix >= 0 ? ix : line.Length);
if (file == "") {
log.LogError("Unable to parse generated dependency file {0}.\n" +
"Line with error: '{1}'", depFilename, line);
return new string[0];
}
result.Add(file);
// If this is the line with the separator, do not read further.
if (ix >= 0)
break;
}
return result.ToArray();
}
// Get complete dependency file name from directory hash and file name,
// tucked onto protoDepDir, e. g.
// ("out", "foo/file.proto") => "out/deadbeef12345678_file.protodep".
// This way, the filenames are unique but still possible to make sense of.
public static string GetDepFilenameForProto(string protoDepDir, string proto) {
string dirname = Path.GetDirectoryName(proto);
if (Platform.IsFsCaseInsensitive) {
dirname = dirname.ToLowerInvariant();
}
string dirhash = HashString64Hex(dirname);
string filename = Path.GetFileNameWithoutExtension(proto);
return Path.Combine(protoDepDir, $"{dirhash}_{filename}.protodep");
}
// Get a 64-bit hash for a directory string. We treat it as if it were
// unique, since there are not so many distinct proto paths in a project.
// We take the first 64 bit of the string SHA1.
// Internal for tests access only.
internal static string HashString64Hex(string str) {
using (var sha1 = System.Security.Cryptography.SHA1.Create()) {
byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(str));
var hashstr = new StringBuilder(16);
for (int i = 0; i < 8; i++) {
hashstr.Append(hash[i].ToString("x2"));
}
return hashstr.ToString();
}
}
// Extract filename between 'beg' (inclusive) and 'end' (exclusive) from
// line 'line', skipping over trailing and leading whitespace, and, when
// 'end' is immediately past end of line 'line', also final '\' (used
// as a line continuation token in the dep file).
// Returns an empty string if the filename cannot be extracted.
static string ExtractFilenameFromLine(string line, int beg, int end) {
while (beg < end && char.IsWhiteSpace(line[beg])) beg++;
if (beg < end && end == line.Length && line[end - 1] == '\\') end--;
while (beg < end && char.IsWhiteSpace(line[end - 1])) end--;
if (beg == end) return "";
string filename = line.Substring(beg, end - beg);
try {
// Normalize file name.
return Path.Combine(
Path.GetDirectoryName(filename),
Path.GetFileName(filename));
} catch (Exception ex) when (Exceptions.IsIoRelated(ex)) {
return "";
}
}
// Finds the index of the ':' separating dependency clauses in the line,
// not taking Windows drive spec into account. Returns the index of the
// separating ':', or -1 if no separator found.
static int FindLineSeparator(string line) {
// Mind this case where the first ':' is not separator:
// C:\foo\bar\.pb.h: C:/protobuf/wrappers.proto\
int ix = line.IndexOf(':');
if (ix <= 0 || ix == line.Length - 1
|| (line[ix + 1] != '/' && line[ix + 1] != '\\')
|| !char.IsLetter(line[ix - 1]))
return ix; // Not a windows drive: no letter before ':', or no '\' after.
for (int j = ix - 1; --j >= 0;) {
if (!char.IsWhiteSpace(line[j]))
return ix; // Not space or BOL only before "X:/".
}
return line.IndexOf(':', ix + 1);
}
// Read entire dependency file. The 'required' parameter controls error
// logging behavior in case the file not found. We require this file when
// compiling, but reading it is optional when computing depnedencies.
static string[] ReadDepFileLines(string filename, bool required,
TaskLoggingHelper log) {
try {
var result = File.ReadAllLines(filename);
if (!required)
log.LogMessage(MessageImportance.Low, $"Using dependency file {filename}");
return result;
} catch (Exception ex) when (Exceptions.IsIoRelated(ex)) {
if (required) {
log.LogError($"Unable to load {filename}: {ex.GetType().Name}: {ex.Message}");
} else {
log.LogMessage(MessageImportance.Low, $"Skippping {filename}: {ex.Message}");
}
return new string[0];
}
}
};
}

@ -0,0 +1,168 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System;
using System.IO;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Grpc.Tools {
// Abstract class for language-specific analysis behavior, such
// as guessing the generated files the same way protoc does.
internal abstract class GeneratorServices {
protected readonly TaskLoggingHelper Log;
protected GeneratorServices(TaskLoggingHelper log) {
Log = log;
}
// Obtain a service for the given language (csharp, cpp).
public static GeneratorServices GetForLanguage(string lang, TaskLoggingHelper log) {
if (lang.EqualNoCase("csharp"))
return new CSharpGeneratorServices(log);
if (lang.EqualNoCase("cpp"))
return new CppGeneratorServices(log);
log.LogError("Invalid value '{0}' for task property 'Generator'. " +
"Supported generator languages: CSharp, Cpp.", lang);
return null;
}
// Guess whether item's metadata suggests gRPC stub generation.
// When "gRPCServices" is not defined, assume gRPC is not used.
// When defined, C# uses "none" to skip gRPC, C++ uses "false", so
// recognize both. Since the value is tightly coupled to the scripts,
// we do not try to validate the value; scripts take care of that.
// It is safe to assume that gRPC is requested for any other value.
protected bool GrpcOutputPossible(ITaskItem proto) {
string gsm = proto.GetMetadata(Metadata.kGrpcServices);
return !gsm.EqualNoCase("") && !gsm.EqualNoCase("none")
&& !gsm.EqualNoCase("false");
}
public abstract string[] GetPossibleOutputs(ITaskItem proto);
};
// C# generator services.
internal class CSharpGeneratorServices : GeneratorServices {
public CSharpGeneratorServices(TaskLoggingHelper log) : base(log) {}
public override string[] GetPossibleOutputs(ITaskItem protoItem) {
bool doGrpc = GrpcOutputPossible(protoItem);
string filename = LowerUnderscoreToUpperCamel(
Path.GetFileNameWithoutExtension(protoItem.ItemSpec));
var outputs = new string[doGrpc ? 2 : 1];
string outdir = protoItem.GetMetadata(Metadata.kOutputDir);
string fileStem = Path.Combine(outdir, filename);
outputs[0] = fileStem + ".cs";
if (doGrpc) {
// Override outdir if kGrpcOutputDir present, default to proto output.
outdir = protoItem.GetMetadata(Metadata.kGrpcOutputDir);
if (outdir != "") {
fileStem = Path.Combine(outdir, filename);
}
outputs[1] = fileStem + "Grpc.cs";
}
return outputs;
}
string LowerUnderscoreToUpperCamel(string str) {
// See src/compiler/generator_helpers.h:118
var result = new StringBuilder(str.Length, str.Length);
bool cap = true;
foreach (char c in str) {
if (c == '_') {
cap = true;
} else if (cap) {
result.Append(char.ToUpperInvariant(c));
cap = false;
} else {
result.Append(c);
}
}
return result.ToString();
}
};
// C++ generator services.
internal class CppGeneratorServices : GeneratorServices {
public CppGeneratorServices(TaskLoggingHelper log) : base(log) { }
public override string[] GetPossibleOutputs(ITaskItem protoItem) {
bool doGrpc = GrpcOutputPossible(protoItem);
string root = protoItem.GetMetadata(Metadata.kProtoRoot);
string proto = protoItem.ItemSpec;
string filename = Path.GetFileNameWithoutExtension(proto);
// E. g., ("foo/", "foo/bar/x.proto") => "bar"
string relative = GetRelativeDir(root, proto);
var outputs = new string[doGrpc ? 4 : 2];
string outdir = protoItem.GetMetadata(Metadata.kOutputDir);
string fileStem = Path.Combine(outdir, relative, filename);
outputs[0] = fileStem + ".pb.cc";
outputs[1] = fileStem + ".pb.h";
if (doGrpc) {
// Override outdir if kGrpcOutputDir present, default to proto output.
outdir = protoItem.GetMetadata(Metadata.kGrpcOutputDir);
if (outdir != "") {
fileStem = Path.Combine(outdir, relative, filename);
}
outputs[2] = fileStem + "_grpc.pb.cc";
outputs[3] = fileStem + "_grpc.pb.h";
}
return outputs;
}
// Calculate part of proto path relative to root. Protoc is very picky
// about them matching exactly, so can be we. Expect root be exact prefix
// to proto, minus some slash normalization.
string GetRelativeDir(string root, string proto) {
string protoDir = Path.GetDirectoryName(proto);
string rootDir = EndWithSlash(Path.GetDirectoryName(EndWithSlash(root)));
if (rootDir == s_dotSlash) {
// Special case, otherwise we can return "./" instead of "" below!
return protoDir;
}
if (Platform.IsFsCaseInsensitive) {
protoDir = protoDir.ToLowerInvariant();
rootDir = rootDir.ToLowerInvariant();
}
protoDir = EndWithSlash(protoDir);
if (!protoDir.StartsWith(rootDir)) {
Log.LogWarning("ProtoBuf item '{0}' has the ProtoRoot metadata '{1}' " +
"which is not prefix to its path. Cannot compute relative path.",
proto, root);
return "";
}
return protoDir.Substring(rootDir.Length);
}
// './' or '.\', normalized per system.
static string s_dotSlash = "." + Path.DirectorySeparatorChar;
static string EndWithSlash(string str) {
if (str == "") {
return s_dotSlash;
} else if (str[str.Length - 1] != '\\' && str[str.Length - 1] != '/') {
return str + Path.DirectorySeparatorChar;
} else {
return str;
}
}
};
}

@ -0,0 +1,95 @@
<Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\Grpc.Core\Version.csproj.include" />
<PropertyGroup>
<AssemblyName>Protobuf.MSBuild</AssemblyName>
<Version>$(GrpcCsharpVersion)</Version>
<!-- If changing targets, change also paths in Google.Protobuf.Tools.targets. -->
<TargetFrameworks>netstandard1.3;net40</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Label="Asset root folders. TODO(kkm): Change with package separation.">
<!-- TODO(kkm): Rework whole section when splitting packages. -->
<!-- GRPC: ../../third_party/protobuf/src/google/protobuf/ -->
<!-- GPB: ../src/google/protobuf/ -->
<Assets_ProtoInclude>../../../third_party/protobuf/src/google/protobuf/</Assets_ProtoInclude>
<!-- GPB: protoc\ -->
<!-- GRPC: protoc_plugins\protoc_ -->
<Assets_ProtoCompiler>../protoc_plugins/protoc_</Assets_ProtoCompiler>
<!-- GRPC: protoc_plugins\ -->
<Assets_GrpcPlugins>../protoc_plugins/</Assets_GrpcPlugins>
</PropertyGroup>
<PropertyGroup>
<_NetStandard>False</_NetStandard>
<_NetStandard Condition=" $(TargetFramework.StartsWith('netstandard')) or $(TargetFramework.StartsWith('netcore')) ">True</_NetStandard>
<!-- So we do not hardcode an exact version into #if's. -->
<DefineConstants Condition="$(_NetStandard)">$(DefineConstants);NETSTANDARD</DefineConstants>
</PropertyGroup>
<PropertyGroup Label="NuGet package definition" Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageOutputPath>../../../artifacts</PackageOutputPath>
<!-- TODO(kkm): Change to "build\" after splitting. -->
<BuildOutputTargetFolder>build\_protobuf\</BuildOutputTargetFolder>
<DevelopmentDependency>true</DevelopmentDependency>
<NoPackageAnalysis>true</NoPackageAnalysis>
<PackageId>Grpc.Tools</PackageId>
<Description>gRPC and Protocol Buffer compiler for managed C# and native C++ projects.
Add this package to a project that contains .proto files to be compiled to code.
It contains the compilers, include files and project system integration for gRPC
and Protocol buffer service description files necessary to build them on Windows,
Linux and MacOS. Managed runtime is supplied separately in the Grpc.Core package.</Description>
<Copyright>Copyright 2018 gRPC authors</Copyright>
<Authors>gRPC authors</Authors>
<PackageLicenseUrl>https://github.com/grpc/grpc/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/grpc/grpc</PackageProjectUrl>
<PackageTags>gRPC RPC protocol HTTP/2</PackageTags>
</PropertyGroup>
<ItemGroup Label="NuGet package assets">
<None Pack="true" PackagePath="build\" Include="build\**\*.xml; build\**\*.props; build\**\*.targets;" />
<!-- Protobuf assets (for Google.Protobuf.Tools) -->
<_ProtoTemp Include="any.proto;api.proto;descriptor.proto;duration.proto;" />
<_ProtoTemp Include="empty.proto;field_mask.proto;source_context.proto;" />
<_ProtoTemp Include="struct.proto;timestamp.proto;type.proto;wrappers.proto" />
<_Asset PackagePath="build/native/include/google/protobuf/" Include="@(_ProtoTemp->'$(Assets_ProtoInclude)%(Identity)')" />
<!-- TODO(kkm): GPB builds assets into "macosx", GRPC into "macos". -->
<_Asset PackagePath="build/native/bin/windows/protoc.exe" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" />
<_Asset PackagePath="build/native/bin/linux_x86/protoc" Include="$(Assets_ProtoCompiler)linux_x86/protoc" />
<_Asset PackagePath="build/native/bin/linux_x64/protoc" Include="$(Assets_ProtoCompiler)linux_x64/protoc" />
<_Asset PackagePath="build/native/bin/macosx_x86/protoc" Include="$(Assets_ProtoCompiler)macos_x86/protoc" /> <!-- GPB: macosx-->
<_Asset PackagePath="build/native/bin/macosx_x64/protoc" Include="$(Assets_ProtoCompiler)macos_x64/protoc" /> <!-- GPB: macosx-->
<!-- gRPC assets (for Grpc.Tools) -->
<_Asset PackagePath="build/native/bin/windows/grpc_csharp_plugin.exe" Include="$(Assets_GrpcPlugins)protoc_windows_x86/grpc_csharp_plugin.exe" />
<_Asset PackagePath="build/native/bin/linux_x86/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_linux_x86/grpc_csharp_plugin" />
<_Asset PackagePath="build/native/bin/linux_x64/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_linux_x64/grpc_csharp_plugin" />
<_Asset PackagePath="build/native/bin/macosx_x86/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_macos_x86/grpc_csharp_plugin" />
<_Asset PackagePath="build/native/bin/macosx_x64/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_macos_x64/grpc_csharp_plugin" />
<None Include="@(_Asset)" Pack="true" Visible="false" />
</ItemGroup>
<ItemGroup Condition="!$(_NetStandard)">
<Reference Include="Microsoft.Build.Framework" Pack="false" />
<Reference Include="Microsoft.Build.Utilities.v4.0" Pack="false" />
</ItemGroup>
<ItemGroup Condition="$(_NetStandard)">
<PackageReference Include="Microsoft.Build.Framework" Version="15.5.180" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.5.180" />
<!-- Set PrivateAssets="All" on all items, so that even implicit package
dependencies do not become dependencies of this package. -->
<PackageReference Update="@(PackageReference)" PrivateAssets="All" />
</ItemGroup>
</Project>

@ -0,0 +1,409 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Grpc.Tools {
/// <summary>
/// Run Google proto compiler (protoc).
///
/// After a successful run, the task reads the dependency file if specified
/// to be saved by the compiler, and returns its output files.
///
/// This task (unlike PrepareProtoCompile) does not attempt to guess anything
/// about language-specific behavior of protoc, and therefore can be used for
/// any language outputs.
/// </summary>
public class ProtoCompile : ToolTask {
/*
Usage: /home/kkm/work/protobuf/src/.libs/lt-protoc [OPTION] PROTO_FILES
Parse PROTO_FILES and generate output based on the options given:
-IPATH, --proto_path=PATH Specify the directory in which to search for
imports. May be specified multiple times;
directories will be searched in order. If not
given, the current working directory is used.
--version Show version info and exit.
-h, --help Show this text and exit.
--encode=MESSAGE_TYPE Read a text-format message of the given type
from standard input and write it in binary
to standard output. The message type must
be defined in PROTO_FILES or their imports.
--decode=MESSAGE_TYPE Read a binary message of the given type from
standard input and write it in text format
to standard output. The message type must
be defined in PROTO_FILES or their imports.
--decode_raw Read an arbitrary protocol message from
standard input and write the raw tag/value
pairs in text format to standard output. No
PROTO_FILES should be given when using this
flag.
--descriptor_set_in=FILES Specifies a delimited list of FILES
each containing a FileDescriptorSet (a
protocol buffer defined in descriptor.proto).
The FileDescriptor for each of the PROTO_FILES
provided will be loaded from these
FileDescriptorSets. If a FileDescriptor
appears multiple times, the first occurrence
will be used.
-oFILE, Writes a FileDescriptorSet (a protocol buffer,
--descriptor_set_out=FILE defined in descriptor.proto) containing all of
the input files to FILE.
--include_imports When using --descriptor_set_out, also include
all dependencies of the input files in the
set, so that the set is self-contained.
--include_source_info When using --descriptor_set_out, do not strip
SourceCodeInfo from the FileDescriptorProto.
This results in vastly larger descriptors that
include information about the original
location of each decl in the source file as
well as surrounding comments.
--dependency_out=FILE Write a dependency output file in the format
expected by make. This writes the transitive
set of input file paths to FILE
--error_format=FORMAT Set the format in which to print errors.
FORMAT may be 'gcc' (the default) or 'msvs'
(Microsoft Visual Studio format).
--print_free_field_numbers Print the free field numbers of the messages
defined in the given proto files. Groups share
the same field number space with the parent
message. Extension ranges are counted as
occupied fields numbers.
--plugin=EXECUTABLE Specifies a plugin executable to use.
Normally, protoc searches the PATH for
plugins, but you may specify additional
executables not in the path using this flag.
Additionally, EXECUTABLE may be of the form
NAME=PATH, in which case the given plugin name
is mapped to the given executable even if
the executable's own name differs.
--cpp_out=OUT_DIR Generate C++ header and source.
--csharp_out=OUT_DIR Generate C# source file.
--java_out=OUT_DIR Generate Java source file.
--javanano_out=OUT_DIR Generate Java Nano source file.
--js_out=OUT_DIR Generate JavaScript source.
--objc_out=OUT_DIR Generate Objective C header and source.
--php_out=OUT_DIR Generate PHP source file.
--python_out=OUT_DIR Generate Python source file.
--ruby_out=OUT_DIR Generate Ruby source file.
@<filename> Read options and filenames from file. If a
relative file path is specified, the file
will be searched in the working directory.
The --proto_path option will not affect how
this argument file is searched. Content of
the file will be expanded in the position of
@<filename> as in the argument list. Note
that shell expansion is not applied to the
content of the file (i.e., you cannot use
quotes, wildcards, escapes, commands, etc.).
Each line corresponds to a single argument,
even if it contains spaces.
*/
static string[] s_supportedGenerators = new[] {
"cpp", "csharp", "java",
"javanano", "js", "objc",
"php", "python", "ruby",
};
/// <summary>
/// Code generator.
/// </summary>
[Required]
public string Generator { get; set; }
/// <summary>
/// Protobuf files to compile.
/// </summary>
[Required]
public ITaskItem[] ProtoBuf { get; set; }
/// <summary>
/// Directory where protoc dependency files are cached. If provided, dependency
/// output filename is autogenerated from source directory hash and file name.
/// Mutually exclusive with DependencyOut.
/// Switch: --dependency_out (with autogenerated file name).
/// </summary>
public string ProtoDepDir { get; set; }
/// <summary>
/// Dependency file full name. Mutually exclusive with ProtoDepDir.
/// Autogenerated file name is available in this property after execution.
/// Switch: --dependency_out.
/// </summary>
[Output]
public string DependencyOut { get; set; }
/// <summary>
/// The directories to search for imports. Directories will be searched
/// in order. If not given, the current working directory is used.
/// Switch: --proto_path.
/// </summary>
public string[] ProtoPath { get; set; }
/// <summary>
/// Generated code directory. The generator property determines the language.
/// Switch: --GEN-out= (for different generators GEN).
/// </summary>
[Required]
public string OutputDir { get; set; }
/// <summary>
/// Codegen options. See also OptionsFromMetadata.
/// Switch: --GEN_out= (for different generators GEN).
/// </summary>
public string[] OutputOptions { get; set; }
/// <summary>
/// Full path to the gRPC plugin executable. If specified, gRPC generation
/// is enabled for the files.
/// Switch: --plugin=protoc-gen-grpc=
/// </summary>
public string GrpcPluginExe { get; set; }
/// <summary>
/// Generated gRPC directory. The generator property determines the
/// language. If gRPC is enabled but this is not given, OutputDir is used.
/// Switch: --grpc_out=
/// </summary>
public string GrpcOutputDir { get; set; }
/// <summary>
/// gRPC Codegen options. See also OptionsFromMetadata.
/// --grpc_opt=opt1,opt2=val (comma-separated).
/// </summary>
public string[] GrpcOutputOptions { get; set; }
/// <summary>
/// List of files written in addition to generated outputs. Includes a
/// single item for the dependency file if written.
/// </summary>
[Output]
public ITaskItem[] AdditionalFileWrites { get; private set; }
/// <summary>
/// List of language files generated by protoc. Empty unless DependencyOut
/// or ProtoDepDir is set, since the file writes are extracted from protoc
/// dependency output file.
/// </summary>
[Output]
public ITaskItem[] GeneratedFiles { get; private set; }
// Hide this property from MSBuild, we should never use a shell script.
private new bool UseCommandProcessor { get; set; }
protected override string ToolName =>
Platform.IsWindows ? "protoc.exe" : "protoc";
// Since we never try to really locate protoc.exe somehow, just try ToolExe
// as the full tool location. It will be either just protoc[.exe] from
// ToolName above if not set by the user, or a user-supplied full path. The
// base class will then resolve the former using system PATH.
protected override string GenerateFullPathToTool() => ToolExe;
// Log protoc errors with the High priority (bold white in MsBuild,
// printed with -v:n, and shown in the Output windows in VS).
protected override MessageImportance StandardErrorLoggingImportance =>
MessageImportance.High;
// Called by base class to validate arguments and make them consistent.
protected override bool ValidateParameters() {
// Part of proto command line switches, must be lowercased.
Generator = Generator.ToLowerInvariant();
if (!System.Array.Exists(s_supportedGenerators, g => g == Generator))
Log.LogError("Invalid value for Generator='{0}'. Supported generators: {1}",
Generator, string.Join(", ", s_supportedGenerators));
if (ProtoDepDir != null && DependencyOut != null)
Log.LogError("Properties ProtoDepDir and DependencyOut may not be both specified");
if (ProtoBuf.Length > 1 && (ProtoDepDir != null || DependencyOut != null))
Log.LogError("Proto compiler currently allows only one input when " +
"--dependency_out is specified (via ProtoDepDir or DependencyOut). " +
"Tracking issue: https://github.com/google/protobuf/pull/3959");
// Use ProtoDepDir to autogenerate DependencyOut
if (ProtoDepDir != null) {
DependencyOut = DepFileUtil.GetDepFilenameForProto(ProtoDepDir, ProtoBuf[0].ItemSpec);
}
if (GrpcPluginExe == null) {
GrpcOutputOptions = null;
GrpcOutputDir = null;
} else if (GrpcOutputDir == null) {
// Use OutputDir for gRPC output if not specified otherwise by user.
GrpcOutputDir = OutputDir;
}
return !Log.HasLoggedErrors && base.ValidateParameters();
}
// Protoc chokes on BOM, naturally. I would!
static readonly Encoding s_utf8WithoutBom = new UTF8Encoding(false);
protected override Encoding ResponseFileEncoding => s_utf8WithoutBom;
// Protoc takes one argument per line from the response file, and does not
// require any quoting whatsoever. Otherwise, this is similar to the
// standard CommandLineBuilder
class ProtocResponseFileBuilder {
StringBuilder _data = new StringBuilder(1000);
public override string ToString() => _data.ToString();
// If 'value' is not empty, append '--name=value\n'.
public void AddSwitchMaybe(string name, string value) {
if (!string.IsNullOrEmpty(value)) {
_data.Append("--").Append(name).Append("=")
.Append(value).Append('\n');
}
}
// Add switch with the 'values' separated by commas, for options.
public void AddSwitchMaybe(string name, string[] values) {
if (values?.Length > 0) {
_data.Append("--").Append(name).Append("=")
.Append(string.Join(",", values)).Append('\n');
}
}
// Add a positional argument to the file data.
public void AddArg(string arg) {
_data.Append(arg).Append('\n');
}
};
// Called by the base ToolTask to get response file contents.
protected override string GenerateResponseFileCommands() {
var cmd = new ProtocResponseFileBuilder();
cmd.AddSwitchMaybe(Generator + "_out", TrimEndSlash(OutputDir));
cmd.AddSwitchMaybe(Generator + "_opt", OutputOptions);
cmd.AddSwitchMaybe("plugin=protoc-gen-grpc", GrpcPluginExe);
cmd.AddSwitchMaybe("grpc_out", TrimEndSlash(GrpcOutputDir));
cmd.AddSwitchMaybe("grpc_opt", GrpcOutputOptions);
if (ProtoPath != null) {
foreach (string path in ProtoPath)
cmd.AddSwitchMaybe("proto_path", TrimEndSlash(path));
}
cmd.AddSwitchMaybe("dependency_out", DependencyOut);
foreach (var proto in ProtoBuf) {
cmd.AddArg(proto.ItemSpec);
}
return cmd.ToString();
}
// Protoc cannot digest trailing slashes in directory names,
// curiously under Linux, but not in Windows.
static string TrimEndSlash(string dir) {
if (dir == null || dir.Length <= 1) {
return dir;
}
string trim = dir.TrimEnd('/', '\\');
// Do not trim the root slash, drive letter possible.
if (trim.Length == 0) {
// Slashes all the way down.
return dir.Substring(0, 1);
}
if (trim.Length == 2 && dir.Length > 2 && trim[1] == ':') {
// We have a drive letter and root, e. g. 'C:\'
return dir.Substring(0, 3);
}
return trim;
}
// Called by the base class to log tool's command line.
//
// Protoc command file is peculiar, with one argument per line, separated
// by newlines. Unwrap it for log readability into a single line, and also
// quote arguments, lest it look weird and so it may be copied and pasted
// into shell. Since this is for logging only, correct enough is correct.
protected override void LogToolCommand(string cmd) {
var printer = new StringBuilder(1024);
// Print 'str' slice into 'printer', wrapping in quotes if contains some
// interesting characters in file names, or if empty string. The list of
// characters requiring quoting is not by any means exhaustive; we are
// just striving to be nice, not guaranteeing to be nice.
var quotable = new[] { ' ', '!', '$', '&', '\'', '^' };
void PrintQuoting(string str, int start, int count) {
bool wrap = count == 0 || str.IndexOfAny(quotable, start, count) >= 0;
if (wrap) printer.Append('"');
printer.Append(str, start, count);
if (wrap) printer.Append('"');
}
for (int ib = 0, ie; (ie = cmd.IndexOf('\n', ib)) >= 0; ib = ie + 1) {
// First line only contains both the program name and the first switch.
// We can rely on at least the '--out_dir' switch being always present.
if (ib == 0) {
int iep = cmd.IndexOf(" --");
if (iep > 0) {
PrintQuoting(cmd, 0, iep);
ib = iep + 1;
}
}
printer.Append(' ');
if (cmd[ib] == '-') {
// Print switch unquoted, including '=' if any.
int iarg = cmd.IndexOf('=', ib, ie - ib);
if (iarg < 0) {
// Bare switch without a '='.
printer.Append(cmd, ib, ie - ib);
continue;
}
printer.Append(cmd, ib, iarg + 1 - ib);
ib = iarg + 1;
}
// A positional argument or switch value.
PrintQuoting(cmd, ib, ie - ib);
}
base.LogToolCommand(printer.ToString());
}
// Main task entry point.
public override bool Execute() {
base.UseCommandProcessor = false;
bool ok = base.Execute();
if (!ok) {
return false;
}
// Read dependency output file from the compiler to retrieve the
// definitive list of created files. Report the dependency file
// itself as having been written to.
if (DependencyOut != null) {
string[] outputs = DepFileUtil.ReadDependencyOutputs(DependencyOut, Log);
if (HasLoggedErrors) {
return false;
}
GeneratedFiles = new ITaskItem[outputs.Length];
for (int i = 0; i < outputs.Length; i++) {
GeneratedFiles[i] = new TaskItem(outputs[i]);
}
AdditionalFileWrites = new ITaskItem[] {
new TaskItem(DependencyOut)
};
}
return true;
}
};
}

@ -0,0 +1,80 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Grpc.Tools {
public class ProtoCompilerOutputs : Task {
/// <summary>
/// Code generator. Currently supported are "csharp", "cpp".
/// </summary>
[Required]
public string Generator { get; set; }
/// <summary>
/// All Proto files in the project. The task computes possible outputs
/// from these proto files, and returns them in the PossibleOutputs list.
/// Not all of these might be actually produced by protoc; this is dealt
/// with later in the ProtoCompile task which returns the list of
/// files actually produced by the compiler.
/// </summary>
[Required]
public ITaskItem[] ProtoBuf { get; set; }
/// <summary>
/// Output items per each potential output. We do not look at existing
/// cached dependency even if they exist, since file may be refactored,
/// affecting whether or not gRPC code file is generated from a given proto.
/// Instead, all potentially possible generated sources are collected.
/// It is a wise idea to generate empty files later for those potentials
/// that are not actually created by protoc, so the dependency checks
/// result in a minimal recompilation. The Protoc task can output the
/// list of files it actually produces, given right combination of its
/// properties.
/// Output items will have the Source metadata set on them:
/// <ItemName Include="MyProto.cs" Source="my_proto.proto" />
/// </summary>
[Output]
public ITaskItem[] PossibleOutputs { get; private set; }
public override bool Execute() {
var generator = GeneratorServices.GetForLanguage(Generator, Log);
if (generator == null) {
// Error already logged, just return.
return false;
}
// Get language-specific possible output. The generator expects certain
// metadata be set on the proto item.
var possible = new List<ITaskItem>();
foreach (var proto in ProtoBuf) {
var outputs = generator.GetPossibleOutputs(proto);
foreach (string output in outputs) {
var ti = new TaskItem(output);
ti.SetMetadata(Metadata.kSource, proto.ItemSpec);
possible.Add(ti);
}
}
PossibleOutputs = possible.ToArray();
return !Log.HasLoggedErrors;
}
};
}

@ -0,0 +1,70 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Grpc.Tools {
public class ProtoReadDependencies : Task {
/// <summary>
/// The collection is used to collect possible additional dependencies
/// of proto files cached under ProtoDepDir.
/// </summary>
[Required]
public ITaskItem[] ProtoBuf { get; set; }
/// <summary>
/// Directory where protoc dependency files are cached.
/// </summary>
[Required]
public string ProtoDepDir { get; set; }
/// <summary>
/// Additional items that a proto file depends on. This list may include
/// extra dependencies; we do our best to include as few extra positives
/// as reasonable to avoid missing any. The collection item is the
/// dependency, and its Source metadatum is the dependent proto file, like
/// <ItemName Include="/usr/include/proto/wrapper.proto"
/// Source="my_proto.proto" />
/// </summary>
[Output]
public ITaskItem[] Dependencies { get; private set; }
public override bool Execute() {
// Read dependency files, where available. There might be none,
// just use a best effort.
if (ProtoDepDir != null) {
var dependencies = new List<ITaskItem>();
foreach (var proto in ProtoBuf) {
string[] deps = DepFileUtil.ReadDependencyInputs(ProtoDepDir, proto.ItemSpec, Log);
foreach (string dep in deps) {
var ti = new TaskItem(dep);
ti.SetMetadata(Metadata.kSource, proto.ItemSpec);
dependencies.Add(ti);
}
}
Dependencies = dependencies.ToArray();
} else {
Dependencies = new ITaskItem[0];
}
return !Log.HasLoggedErrors;
}
};
}

@ -0,0 +1,58 @@
#region Copyright notice and license
// Copyright 2018 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Grpc.Tools {
/// <summary>
/// A helper task to resolve actual OS type and bitness.
/// </summary>
public class ProtoToolsPlatform : Task {
/// <summary>
/// Return one of 'linux', 'macosx' or 'windows'.
/// If the OS is unknown, the property is not set.
/// </summary>
[Output]
public string Os { get; set; }
/// <summary>
/// Return one of 'x64' or 'x86'.
/// If the CPU is unknown, the property is not set.
/// </summary>
[Output]
public string Cpu { get; set; }
public override bool Execute() {
switch (Platform.Os) {
case Platform.OsKind.Linux: Os = "linux"; break;
case Platform.OsKind.MacOsX: Os = "macosx"; break;
case Platform.OsKind.Windows: Os = "windows"; break;
default: Os = ""; break;
}
switch (Platform.Cpu) {
case Platform.CpuKind.X86: Cpu = "x86"; break;
case Platform.CpuKind.X64: Cpu = "x64"; break;
default: Cpu = ""; break;
}
return true;
}
};
}

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<!-- Name of this file must match package ID. -->
<!-- Packages will be split later. -->
<Import Project="_grpc/_Grpc.Tools.props"/>
<Import Project="_protobuf/Google.Protobuf.Tools.props"/>
</Project>

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
<!-- Name of this file must match package ID. -->
<!-- Packages will be split later. -->
<Import Project="_grpc/_Grpc.Tools.targets"/>
<Import Project="_protobuf/Google.Protobuf.Tools.targets"/>
</Project>

@ -0,0 +1,30 @@
<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties">
<Rule Name="ProtoBuf"
DisplayName="File Properties"
PageTemplate="generic"
Description="File Properties"
OverrideMode="Extend">
<Rule.DataSource>
<DataSource Persistence="ProjectFile" Label="Configuration" ItemType="ProtoBuf"
HasConfigurationCondition="false" SourceOfDefaultValue="AfterContext" />
</Rule.DataSource>
<Rule.Categories>
<Category Name="gRPC" DisplayName="gRPC" />
</Rule.Categories>
<EnumProperty Name="GrpcServices" DisplayName="gRPC Stub Classes"
Category="gRPC" Default="Both"
Description="Generate gRPC server and client stub classes.">
<EnumValue Name="Both" DisplayName="Client and Server" IsDefault="true" />
<EnumValue Name="Client" DisplayName="Client only" />
<EnumValue Name="Server" DisplayName="Server only" />
<EnumValue Name="None" DisplayName="Do not generate" />
<EnumProperty.DataSource>
<DataSource ItemType="ProtoBuf" SourceOfDefaultValue="AfterContext"
PersistenceStyle="Attribute" />
</EnumProperty.DataSource>
</EnumProperty>
</Rule>
</ProjectSchemaDefinitions>

@ -0,0 +1,3 @@
TODO(kkm): These file will go into Grpc.Tools/build after package split.
Remove leading underscores from file names; they are hiding the
files from some NuGet versions which pull them into project.

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
</PropertyGroup>
</Project>

@ -0,0 +1,46 @@
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<gRPC_PluginFileName Condition=" '$(gRPC_PluginFileName)' == '' and '$(Language)' == 'C#' ">grpc_csharp_plugin</gRPC_PluginFileName>
</PropertyGroup>
<ItemGroup Condition=" '$(Protobuf_ProjectSupported)' == 'true' and '$(Language)' == 'C#' ">
<!-- Extend property pages with gRPC properties. -->
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Grpc.CSharp.xml">
<Context>File;BrowseObject</Context>
</PropertyPageSchema>
</ItemGroup>
<ItemDefinitionGroup Condition=" '$(Protobuf_ProjectSupported)' == 'true' and '$(Language)' == 'C#' ">
<ProtoBuf>
<GrpcServices Condition="'%(ProtoBuf.GrpcServices)' == '' ">Both</GrpcServices>
</ProtoBuf>
</ItemDefinitionGroup>
<!-- This target is invoked in a C# project, or can be called in a customized project. -->
<Target Name="gRPC_ResolvePluginFullPath" AfterTargets="Protobuf_ResolvePlatform">
<PropertyGroup>
<gRPC_PluginFullPath Condition=" '$(gRPC_PluginFullPath)' == '' and '$(Protobuf_ToolsOs)' == 'windows' "
>$(Protobuf_PackagedToolsPath)bin\$(Protobuf_ToolsOs)\$(gRPC_PluginFileName).exe</gRPC_PluginFullPath>
<gRPC_PluginFullPath Condition=" '$(gRPC_PluginFullPath)' == '' "
>$(Protobuf_PackagedToolsPath)bin/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/$(gRPC_PluginFileName)</gRPC_PluginFullPath>
</PropertyGroup>
</Target>
<Target Name="_gRPC_PrepareCompileOptions" AfterTargets="Protobuf_PrepareCompileOptions">
<ItemGroup Condition=" '$(Language)' == 'C#' ">
<Protobuf_Compile Condition=" %(Protobuf_Compile.GrpcServices) != 'None' ">
<GrpcPluginExe Condition=" '%(Protobuf_Compile.GrpcPluginExe)' == '' ">$(gRPC_PluginFullPath)</GrpcPluginExe>
<GrpcOutputDir Condition=" '%(Protobuf_Compile.GrpcOutputDir)' == '' " >%(Protobuf_Compile.OutputDir)</GrpcOutputDir>
<_GrpcOutputOptions Condition=" '%(Protobuf_Compile.Access)' == 'Internal' ">%(Protobuf_Compile._GrpcOutputOptions);internal_access</_GrpcOutputOptions>
</Protobuf_Compile>
<Protobuf_Compile Condition=" '%(Protobuf_Compile.GrpcServices)' == 'Client' ">
<_GrpcOutputOptions>%(Protobuf_Compile._GrpcOutputOptions);no_server</_GrpcOutputOptions>
</Protobuf_Compile>
<Protobuf_Compile Condition=" '%(Protobuf_Compile.GrpcServices)' == 'Server' ">
<_GrpcOutputOptions>%(Protobuf_Compile._GrpcOutputOptions);no_client</_GrpcOutputOptions>
</Protobuf_Compile>
</ItemGroup>
</Target>
</Project>

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<!-- Revision number of this package conventions (as if "API" version). -->
<Protobuf_ToolingRevision>1</Protobuf_ToolingRevision>
<!-- TODO(kkm): Remove "../" when separating packages. -->
<Protobuf_PackagedToolsPath>$( [System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)../native/) )</Protobuf_PackagedToolsPath>
<Protobuf_StandardImportsPath>$(Protobuf_PackagedToolsPath)include</Protobuf_StandardImportsPath>
</PropertyGroup>
<!-- NET SDK projects only: include proto files by default. Other project
types are not setting or using $(EnableDefaultItems).
Note that MSBuild evaluates all ItemGroups and their conditions in the
final pass over the build script, so properties like EnableDefaultProtoBufItems
here can be changed later in the project. -->
<ItemGroup Condition=" '$(Protobuf_ProjectSupported)' == 'true' ">
<ProtoBuf Include="**/*.proto"
Condition=" '$(EnableDefaultItems)' == 'true' and '$(EnableDefaultProtoBufItems)' == 'true' " />
</ItemGroup>
</Project>

@ -0,0 +1,383 @@
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<!-- We allow a non-C# generator be set by the user, but skip adding outputs to Compile in this case. -->
<Protobuf_Generator Condition=" '$(Protobuf_Generator)' == '' and '$(Language)' == 'C#' ">CSharp</Protobuf_Generator>
<!-- Configuration is passing the smoke test. -->
<Protobuf_ProjectSupported Condition=" '$(Protobuf_Generator)' != '' ">true</Protobuf_ProjectSupported>
<_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' == 'Core' ">netstandard1.3\Protobuf.MSBuild.dll</_Protobuf_MsBuildAssembly>
<_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net40\Protobuf.MSBuild.dll</_Protobuf_MsBuildAssembly>
</PropertyGroup>
<UsingTask AssemblyFile="$(_Protobuf_MsBuildAssembly)" TaskName="Grpc.Tools.ProtoToolsPlatform" />
<UsingTask AssemblyFile="$(_Protobuf_MsBuildAssembly)" TaskName="Grpc.Tools.ProtoCompilerOutputs" />
<UsingTask AssemblyFile="$(_Protobuf_MsBuildAssembly)" TaskName="Grpc.Tools.ProtoReadDependencies" />
<UsingTask AssemblyFile="$(_Protobuf_MsBuildAssembly)" TaskName="Grpc.Tools.ProtoCompile" />
<PropertyGroup Condition=" '$(Protobuf_ProjectSupported)' == 'true' ">
<Protobuf_IntermediatePath Condition=" '$(Protobuf_IntermediatePath)' == '' ">$(IntermediateOutputPath)</Protobuf_IntermediatePath>
<Protobuf_OutputPath Condition=" '$(Protobuf_OutputPath)' == '' ">$(Protobuf_IntermediatePath)</Protobuf_OutputPath>
<Protobuf_DepFilesPath Condition=" '$(Protobuf_DepFilesPath)' == '' ">$(Protobuf_IntermediatePath)</Protobuf_DepFilesPath>
</PropertyGroup>
<ItemDefinitionGroup Condition=" '$(Protobuf_ProjectSupported)' == 'true' and '$(Language)' == 'C#' ">
<ProtoBuf>
<Access Condition="'%(ProtoBuf.Access)' == '' ">Public</Access>
<ProtoCompile Condition="'%(ProtoBuf.ProtoCompile)' == '' ">True</ProtoCompile>
<ProtoRoot Condition="'%(ProtoBuf.ProtoRoot)' == '' " />
<CompileOutputs Condition="'%(ProtoBuf.CompileOutputs)' == ''">True</CompileOutputs>
<OutputDir Condition="'%(ProtoBuf.OutputDir)' == '' ">$(Protobuf_OutputPath)</OutputDir>
</ProtoBuf>
</ItemDefinitionGroup>
<ItemGroup Condition=" '$(Protobuf_ProjectSupported)' == 'true' and '$(Language)' == 'C#' ">
<PropertyPageSchema Include="$(MSBuildThisFileDirectory)Protobuf.CSharp.xml">
<Context>File;BrowseObject</Context>
</PropertyPageSchema>
<AvailableItemName Include="ProtoBuf" />
</ItemGroup>
<PropertyGroup>
<!-- NET SDK: by default, do not include proto files in the directory.
Current Microsoft's recommendation is against globbing:
https://docs.microsoft.com/en-us/dotnet/core/tools/csproj#recommendation -->
<EnableDefaultProtoBufItems Condition=" '$(EnableDefaultProtoBufItems)' == '' ">false</EnableDefaultProtoBufItems>
</PropertyGroup>
<!-- Check configuration sanity before build. -->
<Target Name="_Protobuf_SanityCheck" BeforeTargets="PrepareForBuild">
<Error
Condition=" '$(Protobuf_ProjectSupported)' != 'true' "
Text="Google.Protobuf.Tools proto compilation is only supported by default in a C# project (extension .csproj)" />
</Target>
<!--================================================================================
Tool path resolution
=================================================================================-->
<!-- Extension point for plugin packages: use Protobuf_ToolsOs and Protobuf_ToolsCpu
to resolve executable. Either or both may be blank, however, if resolution
fails; do check them before using. -->
<Target Name="Protobuf_ResolvePlatform">
<ProtoToolsPlatform>
<Output TaskParameter="Os" PropertyName="_Protobuf_ToolsOs"/>
<Output TaskParameter="Cpu" PropertyName="_Protobuf_ToolsCpu"/>
</ProtoToolsPlatform>
<PropertyGroup>
<!-- First try environment variable. -->
<Protobuf_ToolsOs>$(PROTOBUF_TOOLS_OS)</Protobuf_ToolsOs>
<Protobuf_ToolsCpu>$(PROTOBUF_TOOLS_CPU)</Protobuf_ToolsCpu>
<Protobuf_ProtocFullPath>$(PROTOBUF_PROTOC)</Protobuf_ProtocFullPath>
<!-- Next try OS and CPU resolved by ProtoToolsPlatform. -->
<Protobuf_ToolsOs Condition=" '$(Protobuf_ToolsOs)' == '' ">$(_Protobuf_ToolsOs)</Protobuf_ToolsOs>
<Protobuf_ToolsCpu Condition=" '$(Protobuf_ToolsCpu)' == '' ">$(_Protobuf_ToolsCpu)</Protobuf_ToolsCpu>
<Protobuf_ProtocFullPath Condition=" '$(Protobuf_ProtocFullPath)' == '' and '$(Protobuf_ToolsOs)' == 'windows' "
>$(Protobuf_PackagedToolsPath)bin\$(Protobuf_ToolsOs)\protoc.exe</Protobuf_ProtocFullPath>
<Protobuf_ProtocFullPath Condition=" '$(Protobuf_ProtocFullPath)' == '' "
>$(Protobuf_PackagedToolsPath)bin/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/protoc</Protobuf_ProtocFullPath>
</PropertyGroup>
<Error Condition=" '$(DesignTimeBuild)' != 'true' and '$(PROTOBUF_PROTOC)' == ''
and ( '$(Protobuf_ToolsOs)' == '' or '$(Protobuf_ToolsCpu)' == '' ) "
Text="Google.Protobuf.Tools cannot determine host OS and CPU.&#10;Use environment variables PROTOBUF_TOOLS_OS={linux|macosx|windows} and PROTOBUF_TOOLS_CPU={x86|x64} to try the closest match to your system.&#10;You may also set PROTOBUF_PROTOC to specify full path to the host-provided compiler (v3.5+ is required)." />
</Target>
<!--================================================================================
Proto compilation
=================================================================================-->
<!-- Extension points. -->
<Target Name="Protobuf_BeforeCompile" />
<Target Name="Protobuf_AfterCompile" />
<!-- Main compile sequence. Certain steps are gated by the value $(DesignTimeBuild),
so the sequence is good for either design time or build time. -->
<Target Name="Protobuf_Compile"
Condition=" '@(ProtoBuf)' != '' "
DependsOnTargets=" Protobuf_BeforeCompile;
Protobuf_ResolvePlatform;
_Protobuf_SelectFiles;
Protobuf_PrepareCompile;
_Protobuf_AugmentLanguageCompile;
_Protobuf_CoreCompile;
Protobuf_ReconcileOutputs;
Protobuf_AfterCompile" />
<!-- Do proto compilation by default in a C# project. In other types, the user invoke
Protobuf_Compile directly where required. -->
<!-- TODO(kkm): Do shared compile in outer multitarget project? -->
<Target Name="_Protobuf_Compile_BeforeCsCompile"
BeforeTargets="BeforeCompile"
DependsOnTargets="Protobuf_Compile"
Condition=" '$(Language)' == 'C#' " />
<Target Name="_Protobuf_SelectFiles">
<!-- Guess .proto root for the files. Whenever the root is set for a file explicitly,
leave it as is. Otherwise, for files under the project directory, set the root
to "." for the project's directory, as it is the current when compiling; for the
files outside of project directory, use each .proto file's directory as the root. -->
<FindUnderPath Path="$(MSBuildProjectDirectory)"
Files="@(ProtoBuf->WithMetadataValue('ProtoRoot',''))">
<Output TaskParameter="InPath" ItemName="_Protobuf_NoRootInProject"/>
<Output TaskParameter="OutOfPath" ItemName="_Protobuf_NoRootElsewhere"/>
</FindUnderPath>
<ItemGroup>
<!-- Files with explicit metadata. -->
<Protobuf_Compile Include="@(ProtoBuf->HasMetadata('ProtoRoot'))" />
<!-- In-project files will have ProtoRoot='.'. -->
<Protobuf_Compile Include="@(_Protobuf_NoRootInProject)">
<ProtoRoot>.</ProtoRoot>
</Protobuf_Compile>
<!-- Out-of-project files will have respective ProtoRoot='%(RelativeDir)'. -->
<Protobuf_Compile Include="@(_Protobuf_NoRootElsewhere)">
<ProtoRoot>%(RelativeDir)</ProtoRoot>
</Protobuf_Compile>
<!-- Remove files not for compile. -->
<Protobuf_Compile Remove="@(Protobuf_Compile)" Condition=" !%(ProtoCompile) " />
<!-- Ensure invariant Source=%(Identity). -->
<Protobuf_Compile>
<Source>%(Identity)</Source>
</Protobuf_Compile>
</ItemGroup>
</Target>
<!-- Extension point for non-C# project. Protobuf_Generator should be supported
by the ProtoCompile task, but we skip inferring expected outputs. All proto
files will be always recompiled with a warning, unless you add expectations
to the Protobuf_ExpectedOutputs collection.
All inferred ExpectedOutputs will be added to code compile (C#) in a C# project
by default. This is controlled per-proto by the CompileOutputs metadata. -->
<Target Name="Protobuf_PrepareCompile" Condition=" '@(Protobuf_Compile)' != '' ">
<!-- Predict expected names. -->
<ProtoCompilerOutputs Condition=" '$(Language)' == 'C#' "
ProtoBuf="@(Protobuf_Compile)"
Generator="$(Protobuf_Generator)">
<Output TaskParameter="PossibleOutputs" ItemName="Protobuf_ExpectedOutputs" />
</ProtoCompilerOutputs>
<!-- Read any dependency files from previous compiles. -->
<ProtoReadDependencies Condition=" '$(Protobuf_DepFilesPath)' != '' and '$(DesignTimeBuild)' != 'true' "
ProtoBuf="@(Protobuf_Compile)"
ProtoDepDir="$(Protobuf_DepFilesPath)" >
<Output TaskParameter="Dependencies" ItemName="Protobuf_Dependencies" />
</ProtoReadDependencies>
</Target>
<!-- Add all expected outputs, and only these, to language compile. -->
<Target Name="_Protobuf_AugmentLanguageCompile"
DependsOnTargets="_Protobuf_EnforceInvariants"
Condition=" '$(Language)' == 'C#' ">
<ItemGroup>
<_Protobuf_CodeCompile Include="@(Protobuf_ExpectedOutputs->Distinct())"
Condition=" '%(Source)' != '' and '@(Protobuf_Compile->WithMetadataValue('CompileOutputs', 'true'))' != '' " />
<Compile Include="@(_Protobuf_CodeCompile)" />
</ItemGroup>
</Target>
<!-- These invariants must be kept for compile up-to-date check to work. -->
<Target Name="_Protobuf_EnforceInvariants">
<!-- Enforce Source=Identity on proto files. The 'Source' metadata is used as a common
key to match dependencies/expected outputs in the lists for up-to-date checks. -->
<ItemGroup>
<Protobuf_Compile>
<Source>%(Identity)</Source>
</Protobuf_Compile>
</ItemGroup>
<!-- Remove possible output and dependency declarations that have no Source set, or those
not matching any proto marked for compilation. -->
<ItemGroup>
<Protobuf_ExpectedOutputs Remove="@(Protobuf_ExpectedOutputs)" Condition=" '%(Protobuf_ExpectedOutputs.Source)' == '' " />
<Protobuf_ExpectedOutputs Remove="@(Protobuf_ExpectedOutputs)" Condition=" '%(Source)' != '' and '@(Protobuf_Compile)' == '' " />
<Protobuf_Dependencies Remove="@(Protobuf_Dependencies)" Condition=" '%(Protobuf_Dependencies.Source)' == '' " />
<Protobuf_Dependencies Remove="@(Protobuf_Dependencies)" Condition=" '%(Source)' != '' and '@(Protobuf_Compile)' == '' " />
</ItemGroup>
</Target>
<!-- Gather files with and without known outputs, separately. -->
<Target Name="_Protobuf_GatherStaleFiles"
Condition=" '@(Protobuf_Compile)' != '' "
DependsOnTargets="_Protobuf_EnforceInvariants; _Protobuf_GatherStaleSimple; _Protobuf_GatherStaleBatched">
<ItemGroup>
<!-- Drop outputs from MSBuild inference (they won't have the '_Exec' metadata). -->
<_Protobuf_OutOfDateProto Remove="@(_Protobuf_OutOfDateProto->WithMetadataValue('_Exec',''))" />
</ItemGroup>
</Target>
<Target Name="_Protobuf_GatherStaleSimple">
<!-- Simple selection: always compile files that have no declared outputs (but warn below). -->
<ItemGroup>
<_Protobuf_OutOfDateProto Include="@(Protobuf_Compile)"
Condition = " '%(Source)' != '' and '@(Protobuf_ExpectedOutputs)' == '' ">
<_Exec>true</_Exec>
</_Protobuf_OutOfDateProto>
</ItemGroup>
<!-- You are seeing this warning because there was no Protobuf_ExpectedOutputs items with
their Source attribute pointing to the proto files listed in the warning. Such files
will be recompiled on every build, as there is nothing to run up-to-date check against.
Set Protobuf_NoOrphanWarning to 'true' to suppress if this is what you want. -->
<Warning Condition=" '@(_Protobuf_OutOfDateProto)' != '' and '$(Protobuf_NoOrphanWarning)' != 'true' "
Text="The following files have no known outputs, and will be always recompiled as if out-of-date:&#10;@(_Protobuf_Orphans->'&#10; %(Identity)', '')" />
</Target>
<Target Name="_Protobuf_GatherStaleBatched"
Inputs="@(Protobuf_Compile);%(Source);@(Protobuf_Dependencies);$(MSBuildAllProjects)"
Outputs="@(Protobuf_ExpectedOutputs)" >
<!-- The '_Exec' metadatum is set to distinguish really executed items from those MSBuild so
"helpfully" infers in a bucketed task. For the same reason, cannot use the intrinsic
ItemGroup task here. -->
<CreateItem Include="@(Protobuf_Compile)" AdditionalMetadata="_Exec=true">
<Output TaskParameter="Include" ItemName="_Protobuf_OutOfDateProto"/>
</CreateItem>
</Target>
<!-- Extension point: Plugins massage metadata into recognized metadata
values passed to the ProtoCompile task. -->
<Target Name="Protobuf_PrepareCompileOptions" Condition=" '@(Protobuf_Compile)' != '' ">
<ItemGroup>
<Protobuf_Compile>
<_OutputOptions Condition=" '%(Protobuf_Compile.Access)' == 'Internal' ">%(Protobuf_Compile._OutputOptions);internal_access</_OutputOptions>
</Protobuf_Compile>
</ItemGroup>
</Target>
<Target Name="_Protobuf_CoreCompile"
Condition=" '$(DesignTimeBuild)' != 'true' "
DependsOnTargets="Protobuf_PrepareCompileOptions;_Protobuf_GatherStaleFiles">
<!-- Ensure output directories. -->
<MakeDir Directories="%(_Protobuf_OutOfDateProto.OutputDir)" />
<MakeDir Directories="%(_Protobuf_OutOfDateProto.GrpcOutputDir)" />
<MakeDir Directories="$(Protobuf_DepFilesPath)" />
<!-- Force output to the current directory if the user has set it to empty. -->
<ItemGroup>
<_Protobuf_OutOfDateProto>
<OutputDir Condition=" '%(OutputDir)' == '' ">.</OutputDir>
</_Protobuf_OutOfDateProto>
</ItemGroup>
<ProtoCompile Condition=" '@(_Protobuf_OutOfDateProto)' != '' "
ToolExe="$(Protobuf_ProtocFullPath)"
Generator="$(Protobuf_Generator)"
ProtoBuf="%(_Protobuf_OutOfDateProto.Source)"
ProtoPath="%(_Protobuf_OutOfDateProto.AdditionalImportDirs);$(Protobuf_StandardImportsPath);%(_Protobuf_OutOfDateProto.ProtoRoot)"
ProtoDepDir="$(Protobuf_DepFilesPath)"
OutputDir="%(_Protobuf_OutOfDateProto.OutputDir)"
OutputOptions="%(_Protobuf_OutOfDateProto._OutputOptions)"
GrpcPluginExe="%(_Protobuf_OutOfDateProto.GrpcPluginExe)"
GrpcOutputDir="%(_Protobuf_OutOfDateProto.GrpcOutputDir)"
GrpcOutputOptions="%(_Protobuf_OutOfDateProto._GrpcOutputOptions)"
>
<Output TaskParameter="GeneratedFiles" ItemName="_Protobuf_GeneratedFiles"/>
</ProtoCompile>
<!-- Compute files expected but not in fact produced by protoc. -->
<ItemGroup Condition=" '@(_Protobuf_OutOfDateProto)' != '' ">
<Protobuf_ExpectedNotGenerated Include="@(Protobuf_ExpectedOutputs)"
Condition=" '%(Source)' != '' and '@(_Protobuf_OutOfDateProto)' != '' " />
<Protobuf_ExpectedNotGenerated Remove="@(_Protobuf_GeneratedFiles)" />
</ItemGroup>
</Target>
<!-- Extension point. Plugins and/or unsupported projects may take special care of the
Protobuf_ExpectedNotGenerated list in BeforeTargets. We just silently create the
missing outputs so that out-of-date checks work (we do not add them to language
compile though). You can empty this collection in your Before targets to do nothing.
The target is not executed if the proto compiler is not executed. -->
<Target Name="Protobuf_ReconcileOutputs"
Condition=" '$(DesignTimeBuild)' != 'true' ">
<!-- Warn about unexpected/missing files outside object file directory only.
This should have happened because build was incorrectly customized. -->
<FindUnderPath Path="$(BaseIntermediateOutputPath)" Files="@(Protobuf_ExpectedNotGenerated)">
<Output TaskParameter="InPath" ItemName="_Protobuf_ExpectedNotGeneratedInTemp"/>
<Output TaskParameter="OutOfPath" ItemName="_Protobuf_ExpectedNotGeneratedElsewhere"/>
</FindUnderPath>
<!-- Prevent unnecessary recompilation by silently creating empty files. This probably
has happened because a proto file with an rpc service was processed by the gRPC
plugin, and the user did not set GrpcOutput to None. When we treat outputs as
transient, we can do it permissively. -->
<Touch Files="@(_Protobuf_ExpectedNotGeneratedInTemp)" AlwaysCreate="true" />
<!-- Also create empty files outside of the intermediate directory, if the user wants so. -->
<Touch Files="@(_Protobuf_ExpectedNotGeneratedElsewhere)" AlwaysCreate="true"
Condition=" '$(Protobuf_TouchMissingExpected)' == 'true' "/>
<!-- You are seeing this warning because there were some Protobuf_ExpectedOutputs items
(outside of the transient directory under obj/) not in fact produced by protoc. -->
<Warning Condition=" '@(_Protobuf_ExpectedNotGeneratedElsewhere)' != '' and $(Protobuf_NoWarnMissingExpected) != 'true' "
Text="Some expected protoc outputs were not generated.&#10;@(_Protobuf_ExpectedNotGeneratedElsewhere->'&#10; %(Identity)', '')" />
</Target>
<!--================================================================================
Proto cleanup
=================================================================================-->
<!-- We fully support cleanup only in a C# project. If extending the build for other
generators/plugins, then mostly roll your own. -->
<!-- Extension points. -->
<Target Name="Protobuf_BeforeClean" />
<Target Name="Protobuf_AfterClean" />
<!-- Main cleanup sequence. -->
<Target Name="Protobuf_Clean"
Condition=" '@(ProtoBuf)' != '' "
DependsOnTargets=" Protobuf_BeforeClean;
Protobuf_PrepareClean;
_Protobuf_CoreClean;
Protobuf_AfterClean" />
<!-- Do proto cleanup by default in a C# project. In other types, the user should
invoke Protobuf_Clean directly if required. -->
<Target Name="_Protobuf_Clean_AfterCsClean"
AfterTargets="CoreClean"
DependsOnTargets="Protobuf_Clean"
Condition=" '$(Protobuf_ProjectSupported)' == 'true' and '$(Language)' == 'C#' " />
<!-- Extension point for non-C# project. ProtoCompilerOutputs is not invoked for
non-C# projects, since inferring protoc outputs is required, so this is a
no-op in other project types. In your extension target populate the
Protobuf_ExpectedOutputs with all possible output. An option is to include
all existing outputs using Include with a wildcard, if you know where to look.
Note this is like Protobuf_PrepareCompile, but uses @(Protobuf) regardless
of the Compile metadata, to remove all possible outputs. Plugins should err
on the side of overextending the Protobuf_ExpectedOutputs here.
All ExpectedOutputs will be removed. -->
<Target Name="Protobuf_PrepareClean" Condition=" '@(Protobuf)' != '' ">
<!-- Predict expected names. -->
<ProtoCompilerOutputs Condition=" '$(Language)' == 'C#' "
ProtoBuf="@(Protobuf)"
Generator="$(Protobuf_Generator)">
<Output TaskParameter="PossibleOutputs" ItemName="Protobuf_ExpectedOutputs" />
</ProtoCompilerOutputs>
</Target>
<Target Name="_Protobuf_CoreClean">
<ItemGroup>
<_Protobuf_Protodep Include="$(Protobuf_DepFilesPath)*.protodep" />
</ItemGroup>
<Delete Files="@(Protobuf_ExpectedOutputs);@(_Protobuf_Protodep)" TreatErrorsAsWarnings="true" />
</Target>
<!--================================================================================
Design-time support
=================================================================================-->
<!-- Add all .proto files to the SourceFilesProjectOutputGroupOutput, so that
* Visual Studio triggers a build when any of them changed;
* The Pack target includes .proto files into the source package. -->
<Target Name="_Protobuf_SourceFilesProjectOutputGroup"
BeforeTargets="SourceFilesProjectOutputGroup"
Condition=" '@(ProtoBuf)' != '' " >
<ItemGroup>
<SourceFilesProjectOutputGroupOutput Include="@(ProtoBuf->'%(FullPath)')" />
</ItemGroup>
</Target>
</Project>

@ -0,0 +1,99 @@
<ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties">
<FileExtension Name=".proto"
ContentType="ProtoFile" />
<ContentType Name="ProtoFile"
DisplayName="Protocol buffer definitions file"
ItemType="ProtoBuf" />
<ItemType Name="ProtoBuf"
DisplayName="Protobuf compiler" />
<Rule Name="ProtoBuf"
DisplayName="File Properties"
PageTemplate="generic"
Description="File Properties"
OverrideMode="Extend">
<Rule.DataSource>
<DataSource Persistence="ProjectFile" Label="Configuration" ItemType="ProtoBuf"
HasConfigurationCondition="false" SourceOfDefaultValue="AfterContext" />
</Rule.DataSource>
<Rule.Categories>
<Category Name="Advanced" DisplayName="Advanced" />
<Category Name="Protobuf" DisplayName="Protobuf" />
<Category Name="Misc" DisplayName="Misc" />
</Rule.Categories>
<DynamicEnumProperty Name="{}{ItemType}" DisplayName="Build Action" Category="Advanced"
Description="How the file relates to the build and deployment processes."
EnumProvider="ItemTypes" />
<StringProperty Name="Identity" Visible="false" ReadOnly="true">
<StringProperty.DataSource>
<DataSource Persistence="Intrinsic" ItemType="ProtoBuf"
PersistedName="Identity" SourceOfDefaultValue="AfterContext" />
</StringProperty.DataSource>
</StringProperty>
<StringProperty Name="FullPath"
DisplayName="Full Path"
ReadOnly="true"
Category="Misc"
Description="Location of the file.">
<StringProperty.DataSource>
<DataSource Persistence="Intrinsic" ItemType="ProtoBuf"
PersistedName="FullPath" SourceOfDefaultValue="AfterContext" />
</StringProperty.DataSource>
</StringProperty>
<StringProperty Name="FileNameAndExtension"
DisplayName="File Name"
ReadOnly="true"
Category="Misc"
Description="Name of the file or folder.">
<StringProperty.DataSource>
<DataSource Persistence="Intrinsic" ItemType="ProtoBuf"
PersistedName="FileNameAndExtension" SourceOfDefaultValue="AfterContext" />
</StringProperty.DataSource>
</StringProperty>
<BoolProperty Name="Visible" Visible="false" Default="true" />
<StringProperty Name="DependentUpon" Visible="false">
<StringProperty.Metadata>
<NameValuePair Name="DoNotCopyAcrossProjects" Value="true" />
</StringProperty.Metadata>
</StringProperty>
<StringProperty Name="Link" Visible="false">
<StringProperty.DataSource>
<DataSource SourceOfDefaultValue="AfterContext" />
</StringProperty.DataSource>
<StringProperty.Metadata>
<NameValuePair Name="DoNotCopyAcrossProjects" Value="true" />
</StringProperty.Metadata>
</StringProperty>
<EnumProperty Name="Access" DisplayName="Class Access"
Category="Protobuf"
Description="Public or internal access modifier on generated classes.">
<EnumValue Name="Public" DisplayName="Public" IsDefault="true" />
<EnumValue Name="Internal" DisplayName="Internal" />
<EnumProperty.DataSource>
<DataSource ItemType="ProtoBuf" SourceOfDefaultValue="AfterContext"
PersistenceStyle="Attribute" />
</EnumProperty.DataSource>
</EnumProperty>
<BoolProperty Name="ProtoCompile" DisplayName="Compile Protobuf"
Category="Protobuf" Default="true"
Description="Specifies if this file is compiled or only imported by other files.">
<BoolProperty.DataSource>
<DataSource ItemType="ProtoBuf" SourceOfDefaultValue="AfterContext"
PersistenceStyle="Attribute" />
</BoolProperty.DataSource>
</BoolProperty>
</Rule>
</ProjectSchemaDefinitions>

@ -0,0 +1 @@
TODO(kkm): These file will go into Google.Protobuf.Tools/build after package split.

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<!-- Revision number of this package conventions (as if "API" version). -->
<Protobuf_ToolingRevision>1</Protobuf_ToolingRevision>
<!-- For a Visual Studio C++ native project we currently only resolve tools and import paths. -->
<Protobuf_ProtocFullPath>$(MSBuildThisFileDirectory)bin\windows\protoc.exe</Protobuf_ProtocFullPath>
<Protobuf_StandardImportsPath>$(MSBuildThisFileDirectory)bin\include\</Protobuf_StandardImportsPath>
<gRPC_PluginFileName>grpc_cpp_plugin</gRPC_PluginFileName>
<gRPC_PluginFullPath>$(MSBuildThisFileDirectory)bin\windows\grpc_cpp_plugin.exe</gRPC_PluginFullPath>
</PropertyGroup>
</Project>

@ -39,6 +39,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Reflection.Tests", "Gr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Microbenchmarks", "Grpc.Microbenchmarks\Grpc.Microbenchmarks.csproj", "{84C17746-4727-4290-8E8B-A380793DAE1E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Tools", "Grpc.Tools\Grpc.Tools.csproj", "{8A643A1B-B85C-4E3D-BFD3-719FE04D7E91}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Tools.Tests", "Grpc.Tools.Tests\Grpc.Tools.Tests.csproj", "{AEBE9BD8-E433-45B7-8B3D-D458EDBBCFC4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -117,6 +121,14 @@ Global
{84C17746-4727-4290-8E8B-A380793DAE1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84C17746-4727-4290-8E8B-A380793DAE1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84C17746-4727-4290-8E8B-A380793DAE1E}.Release|Any CPU.Build.0 = Release|Any CPU
{8A643A1B-B85C-4E3D-BFD3-719FE04D7E91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A643A1B-B85C-4E3D-BFD3-719FE04D7E91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A643A1B-B85C-4E3D-BFD3-719FE04D7E91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A643A1B-B85C-4E3D-BFD3-719FE04D7E91}.Release|Any CPU.Build.0 = Release|Any CPU
{AEBE9BD8-E433-45B7-8B3D-D458EDBBCFC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEBE9BD8-E433-45B7-8B3D-D458EDBBCFC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEBE9BD8-E433-45B7-8B3D-D458EDBBCFC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEBE9BD8-E433-45B7-8B3D-D458EDBBCFC4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

@ -45,10 +45,10 @@ xcopy /Y /I nativelibs\csharp_ext_windows_x64\grpc_csharp_ext.dll ..\..\cmake\bu
%DOTNET% pack --configuration Release Grpc.Auth --output ..\..\..\artifacts || goto :error
%DOTNET% pack --configuration Release Grpc.HealthCheck --output ..\..\..\artifacts || goto :error
%DOTNET% pack --configuration Release Grpc.Reflection --output ..\..\..\artifacts || goto :error
%DOTNET% pack --configuration Release Grpc.Tools --output ..\..\..\artifacts || goto :error
%NUGET% pack Grpc.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts || goto :error
%NUGET% pack Grpc.Core.NativeDebug.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts
%NUGET% pack Grpc.Tools.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts
@rem copy resulting nuget packages to artifacts directory
xcopy /Y /I *.nupkg ..\..\artifacts\ || goto :error

@ -44,9 +44,9 @@ dotnet pack --configuration Release Grpc.Core.Testing --output ../../../artifact
dotnet pack --configuration Release Grpc.Auth --output ../../../artifacts
dotnet pack --configuration Release Grpc.HealthCheck --output ../../../artifacts
dotnet pack --configuration Release Grpc.Reflection --output ../../../artifacts
dotnet pack --configuration Release Grpc.Tools --output ../../../artifacts
nuget pack Grpc.nuspec -Version "1.14.0-dev" -OutputDirectory ../../artifacts
nuget pack Grpc.Core.NativeDebug.nuspec -Version "1.14.0-dev" -OutputDirectory ../../artifacts
nuget pack Grpc.Tools.nuspec -Version "1.14.0-dev" -OutputDirectory ../../artifacts
(cd ../../artifacts && zip csharp_nugets_dotnetcli.zip *.nupkg)

@ -62,5 +62,15 @@
"Grpc.Reflection.Tests": [
"Grpc.Reflection.Tests.ReflectionClientServerTest",
"Grpc.Reflection.Tests.SymbolRegistryTest"
],
"Grpc.Tools.Tests": [
"Grpc.Tools.Tests.CppGeneratorTests",
"Grpc.Tools.Tests.CSharpGeneratorTests",
"Grpc.Tools.Tests.GeneratorTests",
"Grpc.Tools.Tests.ProtoCompileBasicTests",
"Grpc.Tools.Tests.ProtoCompileCommandLineGeneratorTests",
"Grpc.Tools.Tests.ProtoCompileCommandLinePrinterTests",
"Grpc.Tools.Tests.ProtoToolsPlatformTaskTests",
"Grps.Tools.Tests.DepFileUtilTests"
]
}

@ -47,10 +47,10 @@
%%DOTNET% pack --configuration Release Grpc.Auth --output ..\..\..\artifacts || goto :error
%%DOTNET% pack --configuration Release Grpc.HealthCheck --output ..\..\..\artifacts || goto :error
%%DOTNET% pack --configuration Release Grpc.Reflection --output ..\..\..\artifacts || goto :error
%%DOTNET% pack --configuration Release Grpc.Tools --output ..\..\..\artifacts || goto :error
%%NUGET% pack Grpc.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts || goto :error
%%NUGET% pack Grpc.Core.NativeDebug.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts
%%NUGET% pack Grpc.Tools.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts
@rem copy resulting nuget packages to artifacts directory
xcopy /Y /I *.nupkg ..\..\artifacts\ || goto :error

@ -46,9 +46,9 @@
dotnet pack --configuration Release Grpc.Auth --output ../../../artifacts
dotnet pack --configuration Release Grpc.HealthCheck --output ../../../artifacts
dotnet pack --configuration Release Grpc.Reflection --output ../../../artifacts
dotnet pack --configuration Release Grpc.Tools --output ../../../artifacts
nuget pack Grpc.nuspec -Version "${settings.csharp_version}" -OutputDirectory ../../artifacts
nuget pack Grpc.Core.NativeDebug.nuspec -Version "${settings.csharp_version}" -OutputDirectory ../../artifacts
nuget pack Grpc.Tools.nuspec -Version "${settings.csharp_version}" -OutputDirectory ../../artifacts
(cd ../../artifacts && zip csharp_nugets_dotnetcli.zip *.nupkg)

Loading…
Cancel
Save