Merge pull request #13207 from kkm000/package-grpc-tools

Integrate Grpc.Tools into msbuild system (C# only)
pull/16910/head
Jan Tattermusch 6 years ago committed by GitHub
commit e69a636f32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      src/csharp/.editorconfig
  2. 1
      src/csharp/Grpc.Core.Tests/SanityTest.cs
  3. 85
      src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs
  4. 88
      src/csharp/Grpc.Tools.Tests/CppGeneratorTest.cs
  5. 146
      src/csharp/Grpc.Tools.Tests/DepFileUtilTest.cs
  6. 55
      src/csharp/Grpc.Tools.Tests/GeneratorTest.cs
  7. 78
      src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj
  8. 33
      src/csharp/Grpc.Tools.Tests/NUnitMain.cs
  9. 76
      src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTest.cs
  10. 179
      src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs
  11. 51
      src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTest.cs
  12. 139
      src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs
  13. 46
      src/csharp/Grpc.Tools.Tests/Utils.cs
  14. 33
      src/csharp/Grpc.Tools.nuspec
  15. 114
      src/csharp/Grpc.Tools/Common.cs
  16. 273
      src/csharp/Grpc.Tools/DepFileUtil.cs
  17. 194
      src/csharp/Grpc.Tools/GeneratorServices.cs
  18. 101
      src/csharp/Grpc.Tools/Grpc.Tools.csproj
  19. 441
      src/csharp/Grpc.Tools/ProtoCompile.cs
  20. 86
      src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs
  21. 78
      src/csharp/Grpc.Tools/ProtoReadDependencies.cs
  22. 63
      src/csharp/Grpc.Tools/ProtoToolsPlatform.cs
  23. 11
      src/csharp/Grpc.Tools/build/Grpc.Tools.props
  24. 11
      src/csharp/Grpc.Tools/build/Grpc.Tools.targets
  25. 30
      src/csharp/Grpc.Tools/build/_grpc/Grpc.CSharp.xml
  26. 3
      src/csharp/Grpc.Tools/build/_grpc/README
  27. 6
      src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.props
  28. 48
      src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets
  29. 24
      src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props
  30. 384
      src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets
  31. 99
      src/csharp/Grpc.Tools/build/_protobuf/Protobuf.CSharp.xml
  32. 1
      src/csharp/Grpc.Tools/build/_protobuf/README
  33. 17
      src/csharp/Grpc.Tools/build/native/Grpc.Tools.props
  34. 12
      src/csharp/Grpc.sln
  35. 2
      src/csharp/build_packages_dotnetcli.bat
  36. 10
      src/csharp/tests.json
  37. 2
      templates/src/csharp/build_packages_dotnetcli.bat.template

@ -6,3 +6,26 @@ indent_style = space
indent_size = 4
insert_final_newline = true
tab_width = 4
; https://docs.microsoft.com/visualstudio/ide/editorconfig-code-style-settings-reference
[*.cs]
dotnet_sort_system_directives_first = true
csharp_new_line_before_open_brace = accessors, anonymous_methods, control_blocks, events, indexers, local_functions, methods, properties, types
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true

@ -102,6 +102,7 @@ namespace Grpc.Core.Tests
"Grpc.HealthCheck.Tests",
"Grpc.IntegrationTesting",
"Grpc.Reflection.Tests",
"Grpc.Tools.Tests",
};
foreach (var assemblyName in otherAssemblies)
{

@ -0,0 +1,85 @@
#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 NUnit.Framework;
namespace Grpc.Tools.Tests
{
public class CSharpGeneratorTest : GeneratorTest
{
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"));
}
};
}

@ -0,0 +1,88 @@
#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 NUnit.Framework;
namespace Grpc.Tools.Tests
{
public class CppGeneratorTest : GeneratorTest
{
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,146 @@
#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 NUnit.Framework;
namespace Grpc.Tools.Tests
{
public class DepFileUtilTest
{
[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,55 @@
#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;
using Moq;
using NUnit.Framework;
namespace Grpc.Tools.Tests
{
public class GeneratorTest
{
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);
}
};
}

@ -0,0 +1,78 @@
<Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\Grpc.Core\Version.csproj.include" />
<PropertyGroup>
<TargetFrameworks>net45;netcoreapp1.0</TargetFrameworks>
<OutputType>Exe</OutputType>
</PropertyGroup>
<Import Project="..\Grpc.Core\SourceLink.csproj.include" />
<!-- This is copied verbatim from Grpc.Core/Common.csproj.include. Other settings
in that file conflict with the intent of this build, as it cannot be signed,
and may not compile Grpc.Core/Version.cs, as that file references constants
in Grpc.Core.dll.
TODO(kkm): Refactor imports. -->
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT' and '$(MSBuildRuntimeType)' == 'Core' ">
<!-- Use Mono reference assemblies in SDK build: https://github.com/dotnet/sdk/issues/335 -->
<FrameworkPathOverride Condition="Exists('/usr/lib/mono/4.5-api')">/usr/lib/mono/4.5-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="Exists('/usr/local/lib/mono/4.5-api')">/usr/local/lib/mono/4.5-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="Exists('/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api')">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api</FrameworkPathOverride>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Grpc.Tools\Grpc.Tools.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="4.8.3" />
<PackageReference Include="NUnit; NUnitLite" Version="3.10.1" />
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' != 'net45' ">
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="Microsoft.Build.Framework; Microsoft.Build.Utilities.v4.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'net45' ">
<PackageReference Include="Microsoft.Build.Framework; Microsoft.Build.Utilities.Core" Version="15.6.*" />
</ItemGroup>
<!-- Groups below is a hack to allow the test to run under Mono Framework build.
========================================================================== -->
<!-- Mono unfortunately comes with broken Microsoft.Build.* assemblies installed in
the GAC, but fortunately searches for runtime assemblies in a different order
than Windows CLR host: the GAC assemblies have the lowest search priority, (see
https://www.mono-project.com/docs/advanced/assemblies-and-the-gac/), not the
highest as is in Windows (documented at
https://docs.microsoft.com/dotnet/framework/deployment/how-the-runtime-locates-assemblies).
To run the tests under Mono, we need correct assemblies in the same directory as
the test executable. Correct versions are in the MSBuild directory under Mono. -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' and '$(OS)' != 'Windows_NT' ">
<None Include="$(_MSBuildAssemblyPath)/Microsoft.Build.Framework.dll;
$(_MSBuildAssemblyPath)/Microsoft.Build.Utilities.v4.0.dll;
$(_MSBuildAssemblyPath)/Microsoft.Build.Utilities.Core.dll"
CopyToOutputDirectory="Always" Visible="false" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net45' and '$(OS)' != 'Windows_NT' ">
<!-- The None items are included into assembly candidate resolution by default, and
we do not want that, as they are not valid as reference assemblies (the version of
Microsoft.Build.Utilities.v4.0 is a pure facade for Microsoft.Build.Utilities.Core,
and does not define any types at all). Exclude them from assembly resolution. See
https://github.com/Microsoft/msbuild/blob/50639058f/documentation/wiki/ResolveAssemblyReference.md -->
<AssemblySearchPaths>{HintPathFromItem};{TargetFrameworkDirectory};{RawFileName}</AssemblySearchPaths>
<!-- Mono knows better where its MSBuild is. -->
<_MSBuildAssemblyPath Condition=" '$(MSBuildRuntimeType)' != 'Core' "
>$(MSBuildToolsPath)</_MSBuildAssemblyPath>
<!-- Under dotnet, make the best guess we can. -->
<_MSBuildAssemblyPath Condition=" '$(MSBuildRuntimeType)' == 'Core' "
>$(FrameworkPathOverride)/../msbuild/$(MSBuildToolsVersion)/bin</_MSBuildAssemblyPath>
</PropertyGroup>
</Project>

@ -0,0 +1,33 @@
#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 Grpc.Tools.Tests
{
static class NUnitMain
{
public static int Main(string[] args) =>
#if NETCOREAPP1_0 || NETCOREAPP1_1
new AutoRun(typeof(NUnitMain).GetTypeInfo().Assembly).Execute(args);
#else
new AutoRun().Execute(args);
#endif
};
}

@ -0,0 +1,76 @@
#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; // UWYU: Object.GetType() extension.
using Microsoft.Build.Framework;
using Moq;
using NUnit.Framework;
namespace Grpc.Tools.Tests
{
public class ProtoCompileBasicTest
{
// 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>());
}
};
}

@ -0,0 +1,179 @@
#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 Moq;
using NUnit.Framework;
namespace Grpc.Tools.Tests
{
public class ProtoCompileCommandLineGeneratorTest : ProtoCompileBasicTest
{
[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)
{
if (Path.DirectorySeparatorChar == '/')
expect = expect.Replace('\\', '/');
_task.OutputDir = given;
ExecuteExpectSuccess();
Assert.That(_task.LastResponseFile,
Does.Contain("--csharp_out=" + expect));
}
};
}

@ -0,0 +1,51 @@
#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 Moq;
using NUnit.Framework;
namespace Grpc.Tools.Tests
{
public class ProtoCompileCommandLinePrinterTest : ProtoCompileBasicTest
{
[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,139 @@
#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.Runtime.InteropServices; // For NETCORE tests.
using Microsoft.Build.Framework;
using Moq;
using NUnit.Framework;
namespace Grpc.Tools.Tests
{
public class ProtoToolsPlatformTaskTest
{
ProtoToolsPlatform _task;
int _cpuMatched, _osMatched;
[OneTimeSetUp]
public void SetUp()
{
var mockEng = new Mock<IBuildEngine>();
_task = new ProtoToolsPlatform() { BuildEngine = mockEng.Object };
_task.Execute();
}
[OneTimeTearDown]
public void TearDown()
{
Assert.AreEqual(1, _cpuMatched, "CPU type detection failed");
Assert.AreEqual(1, _osMatched, "OS detection failed");
}
#if NETCORE
// PlatformAttribute not yet available in NUnit, coming soon:
// https://github.com/nunit/nunit/pull/3003.
// Use same test case names as under the full framework.
[Test]
public void CpuIsX86()
{
if (RuntimeInformation.OSArchitecture == Architecture.X86)
{
_cpuMatched++;
Assert.AreEqual("x86", _task.Cpu);
}
}
[Test]
public void CpuIsX64()
{
if (RuntimeInformation.OSArchitecture == Architecture.X64)
{
_cpuMatched++;
Assert.AreEqual("x64", _task.Cpu);
}
}
[Test]
public void OsIsWindows()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_osMatched++;
Assert.AreEqual("windows", _task.Os);
}
}
[Test]
public void OsIsLinux()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
_osMatched++;
Assert.AreEqual("linux", _task.Os);
}
}
[Test]
public void OsIsMacOsX()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_osMatched++;
Assert.AreEqual("macosx", _task.Os);
}
}
#else // !NETCORE, i.e. full framework.
[Test, Platform("32-Bit")]
public void CpuIsX86()
{
_cpuMatched++;
Assert.AreEqual("x86", _task.Cpu);
}
[Test, Platform("64-Bit")]
public void CpuIsX64()
{
_cpuMatched++;
Assert.AreEqual("x64", _task.Cpu);
}
[Test, Platform("Win")]
public void OsIsWindows()
{
_osMatched++;
Assert.AreEqual("windows", _task.Os);
}
[Test, Platform("Linux")]
public void OsIsLinux()
{
_osMatched++;
Assert.AreEqual("linux", _task.Os);
}
[Test, Platform("MacOSX")]
public void OsIsMacOsX()
{
_osMatched++;
Assert.AreEqual("macosx", _task.Os);
}
#endif // NETCORE
};
}

@ -0,0 +1,46 @@
#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.Linq;
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,114 @@
#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 (MSBuild item attributes) that we refer to often.
static class Metadata
{
// On output dependency lists.
public static string Source = "Source";
// On ProtoBuf items.
public static string ProtoRoot = "ProtoRoot";
public static string OutputDir = "OutputDir";
public static string GrpcServices = "GrpcServices";
public static string GrpcOutputDir = "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 NETCORE
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)
{
// This is how Mono detects OSX internally.
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,273 @@
#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
*/
/// <summary>
/// Read file names from the dependency file to the right of ':'
/// </summary>
/// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param>
/// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param>
/// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param>
/// <returns>
/// Array of the proto file <b>input</b> dependencies as written by protoc, or empty
/// array if the dependency file does not exist or cannot be parsed.
/// </returns>
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();
}
/// <summary>
/// Read file names from the dependency file to the left of ':'
/// </summary>
/// <param name="depFilename">Path to dependency file written by protoc</param>
/// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param>
/// <returns>
/// Array of the protoc-generated outputs from the given dependency file
/// written by protoc, or empty array if the file does not exist or cannot
/// be parsed.
/// </returns>
/// <remarks>
/// Since this is called after a protoc invocation, an unparsable or missing
/// file causes an error-level message to be logged.
/// </remarks>
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();
}
/// <summary>
/// Construct relative dependency file name from directory hash and file name
/// </summary>
/// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param>
/// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param>
/// <returns>
/// Full relative path to the dependency file, e. g.
/// "out/deadbeef12345678_file.protodep"
/// </returns>
/// <remarks>
/// Since a project may contain proto files with the same filename but in different
/// directories, a unique filename for the dependency file is constructed based on the
/// proto file name both name and directory. The directory path can be arbitrary,
/// for example, it can be outside of the project, or an absolute path including
/// a drive letter, or a UNC network path. A name constructed from such a path by,
/// for example, replacing disallowed name characters with an underscore, may well
/// be over filesystem's allowed path length, since it will be located under the
/// project and solution directories, which are also some level deep from the root.
/// Instead of creating long and unwieldy names for these proto sources, we cache
/// the full path of the name without the filename, and append the filename to it,
/// as in e. g. "foo/file.proto" will yield the name "deadbeef12345678_file", where
/// "deadbeef12345678" is a presumed hash value of the string "foo/". This allows
/// the file names be short, unique (up to a hash collision), and still allowing
/// the user to guess their provenance.
/// </remarks>
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 dependencies.
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, $"Skipping {filename}: {ex.Message}");
}
return new string[0];
}
}
};
}

@ -0,0 +1,194 @@
#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.GrpcServices);
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.OutputDir);
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.GrpcOutputDir);
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.ProtoRoot);
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.OutputDir);
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.GrpcOutputDir);
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,101 @@
<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>net45;netstandard1.3</TargetFrameworks>
</PropertyGroup>
<!-- This is copied verbatim from Grpc.Core/Common.csproj.include. Other settings
in that file conflict with the intent of this build, as it cannot be signed,
and may not compile Grpc.Core/Version.cs, as that file references constants
in Grpc.Core.dll.
TODO(kkm): Refactor imports. -->
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT' and '$(MSBuildRuntimeType)' == 'Core' ">
<!-- Use Mono reference assemblies in SDK build: https://github.com/dotnet/sdk/issues/335 -->
<FrameworkPathOverride Condition="Exists('/usr/lib/mono/4.5-api')">/usr/lib/mono/4.5-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="Exists('/usr/local/lib/mono/4.5-api')">/usr/local/lib/mono/4.5-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="Exists('/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api')">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api</FrameworkPathOverride>
</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 Condition=" '$(TargetFramework)' != 'net45' ">
<DefineConstants>$(DefineConstants);NETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup Label="NuGet package definition" Condition=" '$(Configuration)' == 'Release' ">
<!-- 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) -->
<_ProtoAssetName Include="any;api;descriptor;duration;empty;field_mask;
source_context;struct;timestamp;type;wrappers" />
<_Asset PackagePath="build/native/include/google/protobuf/" Include="@(_ProtoAssetName->'$(Assets_ProtoInclude)%(Identity).proto')" />
<!-- TODO(kkm): GPB builds assets into "macosx", GRPC into "macos". -->
<!-- TODO(kkm): Do not place non-tools under tools/, use build/native/bin/. -->
<!-- TODO(kkm): Do not package windows x64 builds (#13098). -->
<_Asset PackagePath="tools/windows_x86/protoc.exe" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" />
<_Asset PackagePath="tools/windows_x64/protoc.exe" Include="$(Assets_ProtoCompiler)windows_x64/protoc.exe" />
<_Asset PackagePath="tools/linux_x86/protoc" Include="$(Assets_ProtoCompiler)linux_x86/protoc" />
<_Asset PackagePath="tools/linux_x64/protoc" Include="$(Assets_ProtoCompiler)linux_x64/protoc" />
<_Asset PackagePath="tools/macosx_x86/protoc" Include="$(Assets_ProtoCompiler)macos_x86/protoc" /> <!-- GPB: macosx-->
<_Asset PackagePath="tools/macosx_x64/protoc" Include="$(Assets_ProtoCompiler)macos_x64/protoc" /> <!-- GPB: macosx-->
<!-- gRPC assets (for Grpc.Tools) -->
<_Asset PackagePath="tools/windows_x86/grpc_csharp_plugin.exe" Include="$(Assets_GrpcPlugins)protoc_windows_x86/grpc_csharp_plugin.exe" />
<_Asset PackagePath="tools/windows_x64/grpc_csharp_plugin.exe" Include="$(Assets_GrpcPlugins)protoc_windows_x64/grpc_csharp_plugin.exe" />
<_Asset PackagePath="tools/linux_x86/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_linux_x86/grpc_csharp_plugin" />
<_Asset PackagePath="tools/linux_x64/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_linux_x64/grpc_csharp_plugin" />
<_Asset PackagePath="tools/macosx_x86/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_macos_x86/grpc_csharp_plugin" />
<_Asset PackagePath="tools/macosx_x64/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_macos_x64/grpc_csharp_plugin" />
<None Include="@(_Asset)" Pack="true" Visible="false" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="Microsoft.Build.Framework; Microsoft.Build.Utilities.v4.0" Pack="false" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' != 'net45' ">
<PackageReference Include="Microsoft.Build.Framework; Microsoft.Build.Utilities.Core" Version="15.6.*" />
<!-- Set PrivateAssets="All" on all items, even those implicitly added,
so that they do not become dependencies of this package. -->
<PackageReference Update="@(PackageReference)" PrivateAssets="All" />
</ItemGroup>
</Project>

@ -0,0 +1,441 @@
#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,86 @@
#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.Source, proto.ItemSpec);
possible.Add(ti);
}
}
PossibleOutputs = possible.ToArray();
return !Log.HasLoggedErrors;
}
};
}

@ -0,0 +1,78 @@
#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.Source, proto.ItemSpec);
dependencies.Add(ti);
}
}
Dependencies = dependencies.ToArray();
}
else
{
Dependencies = new ITaskItem[0];
}
return !Log.HasLoggedErrors;
}
};
}

@ -0,0 +1,63 @@
#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,48 @@
<?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>
<!-- TODO(kkm): Do not use Protobuf_PackagedToolsPath, roll gRPC's own. -->
<!-- TODO(kkm): Do not package windows x64 builds (#13098). -->
<gRPC_PluginFullPath Condition=" '$(gRPC_PluginFullPath)' == '' and '$(Protobuf_ToolsOs)' == 'windows' "
>$(Protobuf_PackagedToolsPath)\$(Protobuf_ToolsOs)_x86\$(gRPC_PluginFileName).exe</gRPC_PluginFullPath>
<gRPC_PluginFullPath Condition=" '$(gRPC_PluginFullPath)' == '' "
>$(Protobuf_PackagedToolsPath)/$(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,24 @@
<?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 one "../" when separating packages. -->
<!-- TODO(kkm): Do not place non-tools under tools/, use build/native/bin/. -->
<Protobuf_PackagedToolsPath>$( [System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)../../tools) )</Protobuf_PackagedToolsPath>
<Protobuf_StandardImportsPath>$( [System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)../native/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,384 @@
<?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' ">net45\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>
<!-- TODO(kkm): Do not package windows x64 builds (#13098). -->
<Protobuf_ProtocFullPath Condition=" '$(Protobuf_ProtocFullPath)' == '' and '$(Protobuf_ToolsOs)' == 'windows' "
>$(Protobuf_PackagedToolsPath)\$(Protobuf_ToolsOs)_x86\protoc.exe</Protobuf_ProtocFullPath>
<Protobuf_ProtocFullPath Condition=" '$(Protobuf_ProtocFullPath)' == '' "
>$(Protobuf_PackagedToolsPath)/$(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,17 @@
<?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. -->
<!-- TODO(kkm): Do not place non-tools under tools/, use build/native/bin/. -->
<!-- TODO(kkm): Do not package windows x64 builds (#13098). -->
<Protobuf_ProtocFullPath>$(MSBuildThisFileDirectory)..\..\tools\windows_x86\protoc.exe</Protobuf_ProtocFullPath>
<Protobuf_StandardImportsPath>$(MSBuildThisFileDirectory)include\</Protobuf_StandardImportsPath>
<gRPC_PluginFileName>grpc_cpp_plugin</gRPC_PluginFileName>
<gRPC_PluginFullPath>$(MSBuildThisFileDirectory)..\..\tools\windows_x86\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

@ -39,10 +39,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

@ -64,5 +64,15 @@
"Grpc.Reflection.Tests": [
"Grpc.Reflection.Tests.ReflectionClientServerTest",
"Grpc.Reflection.Tests.SymbolRegistryTest"
],
"Grpc.Tools.Tests": [
"Grpc.Tools.Tests.CppGeneratorTest",
"Grpc.Tools.Tests.CSharpGeneratorTest",
"Grpc.Tools.Tests.DepFileUtilTest",
"Grpc.Tools.Tests.GeneratorTest",
"Grpc.Tools.Tests.ProtoCompileBasicTest",
"Grpc.Tools.Tests.ProtoCompileCommandLineGeneratorTest",
"Grpc.Tools.Tests.ProtoCompileCommandLinePrinterTest",
"Grpc.Tools.Tests.ProtoToolsPlatformTaskTest"
]
}

@ -41,10 +41,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

Loading…
Cancel
Save