From a93e3d275335f3a95f9d2a69da26281d282d46f9 Mon Sep 17 00:00:00 2001 From: kkm Date: Sun, 4 Feb 2018 17:50:39 -0800 Subject: Add Grpc.Tools MsBuild taks assembly, test and scripting This is a complete set of tooling to build .proto files, with or without gRPC services, in .csproj, both "classic" and SDK flavors, and a bare minimum support for C++ projects. Highlights and omissions: * By default, generated files are placed into project's intermediate directory under obj/, and treated as temporary generated sources. * The projects are highly customizabe thorugh item metadata on Protobuf items. * SDK projects only use Visual Studio new build system, and automatically import XAML property sheets that allow setting per-file properties, such as generated file access, and whether to expect gRPC outputs, from VS properties windows. This possibly requires VS restart after the package is added to solution. Classic projects cannot be extended this way, and only show Protobuf as the possible item; settings are modified by editing the project only. * For C++ projects, only the tool and standard proto import paths are provided, no custom targets yet. This is in the works. * gRPC and Protobuf scripts are separate, and everything is programmed to easily split the Tools package into one for Google.Protobuf and another for Grpc.Tools. This requires tighter coordination between the teams. * The tasks DLL knows about gRPC. I tried to use it to support gRPC in a script-only fashion, but using the tasks results in much cleaner scripts. This is probably how it should remain. * In multitarget projects (multiple frameworks) protoc files are compiled for each target, and also for Debug/Release configuration sepatately. A possible fix is in the works, but requries some MsBuild tooling fixes, so it will take a while. * There are 4 tasks. The "smart" task predicts protoc outputs, and knows things about protoc naming conventions. This supports only C# and C++. The "dumb" task simply invokes protoc in a language-independent way, and supports all languages known to protoc. In the (not very likely) case protoc is used with MsBuild for these languages, instructions for extending the build is provided in build script comments. The other 2 tasks are one to detect current platform and therefore tools paths, and another to read protoc generated dependency file. We use it for C#, but custom project may opt not to use the dependecy files. * 64-bit tools for Windows (protoc and grpc plugin exe) have been removed from package, as Windows is alsways able to run 32-bit executable (and they are smaller and faster, and always preferred when 2G address space is enough). --- src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs | 134 +++++++ src/csharp/Grpc.Tools.Tests/GeneratorTests.cs | 165 +++++++++ .../Grpc.Tools.Tests/Grpc.Tools.Tests.csproj | 27 ++ src/csharp/Grpc.Tools.Tests/NUnitMain.cs | 31 ++ .../Grpc.Tools.Tests/ProtoCompileTaskTest.cs | 232 ++++++++++++ .../Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs | 53 +++ src/csharp/Grpc.Tools.Tests/Utils.cs | 43 +++ src/csharp/Grpc.Tools.nuspec | 33 -- src/csharp/Grpc.Tools/Common.cs | 105 ++++++ src/csharp/Grpc.Tools/DepFileUtil.cs | 198 ++++++++++ src/csharp/Grpc.Tools/GeneratorServices.cs | 168 +++++++++ src/csharp/Grpc.Tools/Grpc.Tools.csproj | 95 +++++ src/csharp/Grpc.Tools/ProtoCompile.cs | 409 +++++++++++++++++++++ src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs | 80 ++++ src/csharp/Grpc.Tools/ProtoReadDependencies.cs | 70 ++++ src/csharp/Grpc.Tools/ProtoToolsPlatform.cs | 58 +++ src/csharp/Grpc.Tools/build/Grpc.Tools.props | 11 + src/csharp/Grpc.Tools/build/Grpc.Tools.targets | 11 + src/csharp/Grpc.Tools/build/_grpc/Grpc.CSharp.xml | 30 ++ src/csharp/Grpc.Tools/build/_grpc/README | 3 + .../Grpc.Tools/build/_grpc/_Grpc.Tools.props | 6 + .../Grpc.Tools/build/_grpc/_Grpc.Tools.targets | 46 +++ .../build/_protobuf/Google.Protobuf.Tools.props | 23 ++ .../build/_protobuf/Google.Protobuf.Tools.targets | 383 +++++++++++++++++++ .../Grpc.Tools/build/_protobuf/Protobuf.CSharp.xml | 99 +++++ src/csharp/Grpc.Tools/build/_protobuf/README | 1 + .../Grpc.Tools/build/native/Grpc.Tools.props | 15 + src/csharp/Grpc.sln | 12 + src/csharp/build_packages_dotnetcli.bat | 2 +- src/csharp/build_packages_dotnetcli.sh | 2 +- src/csharp/tests.json | 10 + 31 files changed, 2520 insertions(+), 35 deletions(-) create mode 100644 src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/GeneratorTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj create mode 100644 src/csharp/Grpc.Tools.Tests/NUnitMain.cs create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileTaskTest.cs create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs create mode 100644 src/csharp/Grpc.Tools.Tests/Utils.cs delete mode 100644 src/csharp/Grpc.Tools.nuspec create mode 100644 src/csharp/Grpc.Tools/Common.cs create mode 100644 src/csharp/Grpc.Tools/DepFileUtil.cs create mode 100644 src/csharp/Grpc.Tools/GeneratorServices.cs create mode 100644 src/csharp/Grpc.Tools/Grpc.Tools.csproj create mode 100644 src/csharp/Grpc.Tools/ProtoCompile.cs create mode 100644 src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs create mode 100644 src/csharp/Grpc.Tools/ProtoReadDependencies.cs create mode 100644 src/csharp/Grpc.Tools/ProtoToolsPlatform.cs create mode 100644 src/csharp/Grpc.Tools/build/Grpc.Tools.props create mode 100644 src/csharp/Grpc.Tools/build/Grpc.Tools.targets create mode 100644 src/csharp/Grpc.Tools/build/_grpc/Grpc.CSharp.xml create mode 100644 src/csharp/Grpc.Tools/build/_grpc/README create mode 100644 src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.props create mode 100644 src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets create mode 100644 src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props create mode 100644 src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets create mode 100644 src/csharp/Grpc.Tools/build/_protobuf/Protobuf.CSharp.xml create mode 100644 src/csharp/Grpc.Tools/build/_protobuf/README create mode 100644 src/csharp/Grpc.Tools/build/native/Grpc.Tools.props (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs b/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs new file mode 100644 index 0000000000..0ea621adea --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs @@ -0,0 +1,134 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using System.IO; +using Grpc.Tools; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NUnit.Framework; + +namespace Grps.Tools.Tests { + public class DepFileUtilTests { + + [Test] + public void HashString64Hex_IsSane() { + string hashFoo1 = DepFileUtil.HashString64Hex("foo"); + string hashEmpty = DepFileUtil.HashString64Hex(""); + string hashFoo2 = DepFileUtil.HashString64Hex("foo"); + + StringAssert.IsMatch("^[a-f0-9]{16}$", hashFoo1); + Assert.AreEqual(hashFoo1, hashFoo2); + Assert.AreNotEqual(hashFoo1, hashEmpty); + } + + [Test] + public void GetDepFilenameForProto_IsSane() { + StringAssert.IsMatch(@"^out[\\/][a-f0-9]{16}_foo.protodep$", + DepFileUtil.GetDepFilenameForProto("out", "foo.proto")); + StringAssert.IsMatch(@"^[a-f0-9]{16}_foo.protodep$", + DepFileUtil.GetDepFilenameForProto("", "foo.proto")); + } + + [Test] + public void GetDepFilenameForProto_HashesDir() { + string PickHash(string fname) => + DepFileUtil.GetDepFilenameForProto("", fname).Substring(0, 16); + + string same1 = PickHash("dir1/dir2/foo.proto"); + string same2 = PickHash("dir1/dir2/proto.foo"); + string same3 = PickHash("dir1/dir2/proto"); + string same4 = PickHash("dir1/dir2/.proto"); + string unsame1 = PickHash("dir2/foo.proto"); + string unsame2 = PickHash("/dir2/foo.proto"); + + Assert.AreEqual(same1, same2); + Assert.AreEqual(same1, same3); + Assert.AreEqual(same1, same4); + Assert.AreNotEqual(same1, unsame1); + Assert.AreNotEqual(unsame1, unsame2); + } + + ////////////////////////////////////////////////////////////////////////// + // Full file reading tests + + // Generated by protoc on Windows. Slashes vary. + const string depFile1 = +@"C:\projects\foo\src\./foo.grpc.pb.cc \ +C:\projects\foo\src\./foo.grpc.pb.h \ +C:\projects\foo\src\./foo.pb.cc \ + C:\projects\foo\src\./foo.pb.h: C:/usr/include/google/protobuf/wrappers.proto\ + C:/usr/include/google/protobuf/any.proto\ +C:/usr/include/google/protobuf/source_context.proto\ + C:/usr/include/google/protobuf/type.proto\ + foo.proto"; + + // This has a nasty output directory with a space. + const string depFile2 = +@"obj\Release x64\net45\/Foo.cs \ +obj\Release x64\net45\/FooGrpc.cs: C:/usr/include/google/protobuf/wrappers.proto\ + C:/projects/foo/src//foo.proto"; + + [Test] + public void ReadDependencyInput_FullFile1() { + string[] deps = ReadDependencyInputFromFileData(depFile1, "foo.proto"); + + Assert.NotNull(deps); + Assert.That(deps, Has.Length.InRange(4, 5)); // foo.proto may or may not be listed. + Assert.That(deps, Has.One.EndsWith("wrappers.proto")); + Assert.That(deps, Has.One.EndsWith("type.proto")); + Assert.That(deps, Has.None.StartWith(" ")); + } + + [Test] + public void ReadDependencyInput_FullFile2() { + string[] deps = ReadDependencyInputFromFileData(depFile2, "C:/projects/foo/src/foo.proto"); + + Assert.NotNull(deps); + Assert.That(deps, Has.Length.InRange(1, 2)); + Assert.That(deps, Has.One.EndsWith("wrappers.proto")); + Assert.That(deps, Has.None.StartWith(" ")); + } + + [Test] + public void ReadDependencyInput_FullFileUnparsable() { + string[] deps = ReadDependencyInputFromFileData("a:/foo.proto", "/foo.proto"); + Assert.NotNull(deps); + Assert.Zero(deps.Length); + } + + // NB in our tests files are put into the temp directory but all have + // different names. Avoid adding files with the same directory path and + // name, or add reasonable handling for it if required. Tests are run in + // parallel and will collide otherwise. + private string[] ReadDependencyInputFromFileData(string fileData, string protoName) { + string tempPath = Path.GetTempPath(); + string tempfile = DepFileUtil.GetDepFilenameForProto(tempPath, protoName); + try { + File.WriteAllText(tempfile, fileData); + var mockEng = new Moq.Mock(); + var log = new TaskLoggingHelper(mockEng.Object, "x"); + return DepFileUtil.ReadDependencyInputs(tempPath, protoName, log); + } finally { + try { + File.Delete(tempfile); + } catch { } + } + } + } +} diff --git a/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs new file mode 100644 index 0000000000..0a273380b9 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs @@ -0,0 +1,165 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + public class GeneratorTests { + protected Mock _mockEngine; + protected TaskLoggingHelper _log; + + [SetUp] + public void SetUp() { + _mockEngine = new Mock(); + _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()), Times.Never); + } + + [TestCase("")] + [TestCase("COBOL")] + public void InvalidLanguages(string lang) { + Assert.IsNull(GeneratorServices.GetForLanguage(lang, _log)); + _mockEngine.Verify(me => me.LogErrorEvent(It.IsAny()), Times.Once); + } + }; + + public class CSharpGeneratorTests : GeneratorTests { + GeneratorServices _generator; + + [SetUp] + public new void SetUp() { + _generator = GeneratorServices.GetForLanguage("CSharp", _log); + } + + [TestCase("foo.proto", "Foo.cs", "FooGrpc.cs")] + [TestCase("sub/foo.proto", "Foo.cs", "FooGrpc.cs")] + [TestCase("one_two.proto", "OneTwo.cs", "OneTwoGrpc.cs")] + [TestCase("__one_two!.proto", "OneTwo!.cs", "OneTwo!Grpc.cs")] + [TestCase("one(two).proto", "One(two).cs", "One(two)Grpc.cs")] + [TestCase("one_(two).proto", "One(two).cs", "One(two)Grpc.cs")] + [TestCase("one two.proto", "One two.cs", "One twoGrpc.cs")] + [TestCase("one_ two.proto", "One two.cs", "One twoGrpc.cs")] + [TestCase("one .proto", "One .cs", "One Grpc.cs")] + public void NameMangling(string proto, string expectCs, string expectGrpcCs) { + var poss = _generator.GetPossibleOutputs(Utils.MakeItem(proto, "grpcservices", "both")); + Assert.AreEqual(2, poss.Length); + Assert.Contains(expectCs, poss); + Assert.Contains(expectGrpcCs, poss); + } + + [Test] + public void NoGrpcOneOutput() { + var poss = _generator.GetPossibleOutputs(Utils.MakeItem("foo.proto")); + Assert.AreEqual(1, poss.Length); + } + + [TestCase("none")] + [TestCase("")] + public void GrpcNoneOneOutput(string grpc) { + var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(1, poss.Length); + } + + [TestCase("client")] + [TestCase("server")] + [TestCase("both")] + public void GrpcEnabledTwoOutputs(string grpc) { + var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(2, poss.Length); + } + + [Test] + public void OutputDirMetadataRecognized() { + var item = Utils.MakeItem("foo.proto", "OutputDir", "out"); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(1, poss.Length); + Assert.That(poss[0], Is.EqualTo("out/Foo.cs") | Is.EqualTo("out\\Foo.cs")); + } + }; + + public class CppGeneratorTests : GeneratorTests { + GeneratorServices _generator; + + [SetUp] + public new void SetUp() { + _generator = GeneratorServices.GetForLanguage("Cpp", _log); + } + + [TestCase("foo.proto", "", "foo")] + [TestCase("foo.proto", ".", "foo")] + [TestCase("foo.proto", "./", "foo")] + [TestCase("sub/foo.proto", "", "sub/foo")] + [TestCase("root/sub/foo.proto", "root", "sub/foo")] + [TestCase("root/sub/foo.proto", "root", "sub/foo")] + [TestCase("/root/sub/foo.proto", "/root", "sub/foo")] + public void RelativeDirectoryCompute(string proto, string root, string expectStem) { + if (Path.DirectorySeparatorChar == '\\') + expectStem = expectStem.Replace('/', '\\'); + var poss = _generator.GetPossibleOutputs(Utils.MakeItem(proto, "ProtoRoot", root)); + Assert.AreEqual(2, poss.Length); + Assert.Contains(expectStem + ".pb.cc", poss); + Assert.Contains(expectStem + ".pb.h", poss); + } + + [Test] + public void NoGrpcTwoOutputs() { + var poss = _generator.GetPossibleOutputs(Utils.MakeItem("foo.proto")); + Assert.AreEqual(2, poss.Length); + } + + [TestCase("false")] + [TestCase("")] + public void GrpcDisabledTwoOutput(string grpc) { + var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(2, poss.Length); + } + + [TestCase("true")] + public void GrpcEnabledFourOutputs(string grpc) { + var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(4, poss.Length); + Assert.Contains("foo.pb.cc", poss); + Assert.Contains("foo.pb.h", poss); + Assert.Contains("foo_grpc.pb.cc", poss); + Assert.Contains("foo_grpc.pb.h", poss); + } + + [Test] + public void OutputDirMetadataRecognized() { + var item = Utils.MakeItem("foo.proto", "OutputDir", "out"); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(2, poss.Length); + Assert.That(Path.GetDirectoryName(poss[0]), Is.EqualTo("out")); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj new file mode 100644 index 0000000000..585e4518b5 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj @@ -0,0 +1,27 @@ + + + + netcoreapp1.0;net45 + Exe + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/csharp/Grpc.Tools.Tests/NUnitMain.cs b/src/csharp/Grpc.Tools.Tests/NUnitMain.cs new file mode 100644 index 0000000000..c4452c50d2 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/NUnitMain.cs @@ -0,0 +1,31 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Reflection; +using NUnitLite; + +namespace Grps.Tools.Tests { + static class NUnitMain { + public static int Main(string[] args) => +#if NETCOREAPP1_0 + new AutoRun(typeof(NUnitMain).GetTypeInfo().Assembly).Execute(args); +#else + new AutoRun().Execute(args); +#endif + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileTaskTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileTaskTest.cs new file mode 100644 index 0000000000..86c78289b2 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileTaskTest.cs @@ -0,0 +1,232 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Reflection; +using Microsoft.Build.Framework; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + public class ProtoCompileBasicTests { + // Mock task class that stops right before invoking protoc. + public class ProtoCompileTestable : ProtoCompile { + public string LastPathToTool { get; private set; } + public string[] LastResponseFile { get; private set; } + + protected override int ExecuteTool(string pathToTool, + string response, + string commandLine) { + // We should never be using command line commands. + Assert.That(commandLine, Is.Null | Is.Empty); + + // Must receive a path to tool + Assert.That(pathToTool, Is.Not.Null & Is.Not.Empty); + Assert.That(response, Is.Not.Null & Does.EndWith("\n")); + + LastPathToTool = pathToTool; + LastResponseFile = response.Remove(response.Length - 1).Split('\n'); + + // Do not run the tool, but pretend it ran successfully. + return 0; + } + }; + + protected Mock _mockEngine; + protected ProtoCompileTestable _task; + + [SetUp] + public void SetUp() { + _mockEngine = new Mock(); + _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()); + } + }; + + internal class ProtoCompileCommandLineGeneratorTests : ProtoCompileBasicTests { + [SetUp] + public new void SetUp() { + _task.Generator = "csharp"; + _task.OutputDir = "outdir"; + _task.ProtoBuf = Utils.MakeSimpleItems("a.proto"); + } + + void ExecuteExpectSuccess() { + _mockEngine + .Setup(me => me.LogErrorEvent(It.IsAny())) + .Callback((BuildErrorEventArgs e) => + Assert.Fail($"Error logged by build engine:\n{e.Message}")); + bool result = _task.Execute(); + Assert.IsTrue(result); + } + + [Test] + public void MinimalCompile() { + ExecuteExpectSuccess(); + Assert.That(_task.LastPathToTool, Does.Match(@"protoc(.exe)?$")); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + "--csharp_out=outdir", "a.proto" })); + } + + [Test] + public void CompileTwoFiles() { + _task.ProtoBuf = Utils.MakeSimpleItems("a.proto", "foo/b.proto"); + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + "--csharp_out=outdir", "a.proto", "foo/b.proto" })); + } + + [Test] + public void CompileWithProtoPaths() { + _task.ProtoPath = new[] { "/path1", "/path2" }; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + "--csharp_out=outdir", "--proto_path=/path1", + "--proto_path=/path2", "a.proto" })); + } + + [TestCase("Cpp")] + [TestCase("CSharp")] + [TestCase("Java")] + [TestCase("Javanano")] + [TestCase("Js")] + [TestCase("Objc")] + [TestCase("Php")] + [TestCase("Python")] + [TestCase("Ruby")] + public void CompileWithOptions(string gen) { + _task.Generator = gen; + _task.OutputOptions = new[] { "foo", "bar" }; + ExecuteExpectSuccess(); + gen = gen.ToLowerInvariant(); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + $"--{gen}_out=outdir", $"--{gen}_opt=foo,bar", "a.proto" })); + } + + [Test] + public void OutputDependencyFile() { + _task.DependencyOut = "foo/my.protodep"; + // Task fails trying to read the non-generated file; we ignore that. + _task.Execute(); + Assert.That(_task.LastResponseFile, + Does.Contain("--dependency_out=foo/my.protodep")); + } + + [Test] + public void OutputDependencyWithProtoDepDir() { + _task.ProtoDepDir = "foo"; + // Task fails trying to read the non-generated file; we ignore that. + _task.Execute(); + Assert.That(_task.LastResponseFile, + Has.One.Match(@"^--dependency_out=foo[/\\].+_a.protodep$")); + } + + [Test] + public void GenerateGrpc() { + _task.GrpcPluginExe = "/foo/grpcgen"; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { + "--csharp_out=outdir", "--grpc_out=outdir", + "--plugin=protoc-gen-grpc=/foo/grpcgen" })); + } + + [Test] + public void GenerateGrpcWithOutDir() { + _task.GrpcPluginExe = "/foo/grpcgen"; + _task.GrpcOutputDir = "gen-out"; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { + "--csharp_out=outdir", "--grpc_out=gen-out" })); + } + + [Test] + public void GenerateGrpcWithOptions() { + _task.GrpcPluginExe = "/foo/grpcgen"; + _task.GrpcOutputOptions = new[] { "baz", "quux" }; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, + Does.Contain("--grpc_opt=baz,quux")); + } + + [Test] + public void DirectoryArgumentsSlashTrimmed() { + _task.GrpcPluginExe = "/foo/grpcgen"; + _task.GrpcOutputDir = "gen-out/"; + _task.OutputDir = "outdir/"; + _task.ProtoPath = new[] { "/path1/", "/path2/" }; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { + "--proto_path=/path1", "--proto_path=/path2", + "--csharp_out=outdir", "--grpc_out=gen-out" })); + } + + [TestCase("." , ".")] + [TestCase("/" , "/")] + [TestCase("//" , "/")] + [TestCase("/foo/" , "/foo")] + [TestCase("/foo" , "/foo")] + [TestCase("foo/" , "foo")] + [TestCase("foo//" , "foo")] + [TestCase("foo/\\" , "foo")] + [TestCase("foo\\/" , "foo")] + [TestCase("C:\\foo", "C:\\foo")] + [TestCase("C:" , "C:")] + [TestCase("C:\\" , "C:\\")] + [TestCase("C:\\\\" , "C:\\")] + public void DirectorySlashTrimmingCases(string given, string expect) { + _task.OutputDir = given; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, + Does.Contain("--csharp_out=" + expect)); + } + }; + + internal class ProtoCompileCommandLinePrinterTests : ProtoCompileBasicTests { + [SetUp] + public new void SetUp() { + _task.Generator = "csharp"; + _task.OutputDir = "outdir"; + _task.ProtoBuf = Utils.MakeSimpleItems("a.proto"); + + _mockEngine + .Setup(me => me.LogMessageEvent(It.IsAny())) + .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())) + .Callback((BuildErrorEventArgs e) => + Assert.Fail($"Error logged by build engine:\n{e.Message}")); + bool result = _task.Execute(); + Assert.IsTrue(result); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs new file mode 100644 index 0000000000..3ba0bdfbf6 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs @@ -0,0 +1,53 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using Microsoft.Build.Framework; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + // This test requires that environment variables be set to the exected + // output of the task in its external test harness: + // PROTOTOOLS_TEST_CPU = { x64 | x86 } + // PROTOTOOLS_TEST_OS = { linux | macosx | windows } + public class ProtoToolsPlatformTaskTests { + static string s_expectOs; + static string s_expectCpu; + + [OneTimeSetUp] + public static void Init() { + s_expectCpu = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_CPU"); + s_expectOs = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_OS"); + if (s_expectCpu == null || s_expectOs == null) + Assert.Inconclusive("This test requires PROTOTOOLS_TEST_CPU and " + + "PROTOTOOLS_TEST_OS set in the environment to match the OS it runs on."); + } + + [Test] + public void CpuAndOsMatchExpected() { + var mockEng = new Mock(); + var task = new ProtoToolsPlatform() { + BuildEngine = mockEng.Object + }; + task.Execute(); + Assert.AreEqual(s_expectCpu, task.Cpu); + Assert.AreEqual(s_expectOs, task.Os); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/Utils.cs b/src/csharp/Grpc.Tools.Tests/Utils.cs new file mode 100644 index 0000000000..618e335452 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/Utils.cs @@ -0,0 +1,43 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Grpc.Tools.Tests { + static class Utils { + // Build an item with a name from args[0] and metadata key-value pairs + // from the rest of args, interleaved. + // This does not do any checking, and expects an odd number of args. + public static ITaskItem MakeItem(params string[] args) { + var item = new TaskItem(args[0]); + for (int i = 1; i < args.Length; i += 2) + item.SetMetadata(args[i], args[i + 1]); + return item; + } + + // Return an array of items from given itemspecs. + public static ITaskItem[] MakeSimpleItems(params string[] specs) { + return specs.Select(s => new TaskItem(s)).ToArray(); + } + }; +} diff --git a/src/csharp/Grpc.Tools.nuspec b/src/csharp/Grpc.Tools.nuspec deleted file mode 100644 index 0cae5572fd..0000000000 --- a/src/csharp/Grpc.Tools.nuspec +++ /dev/null @@ -1,33 +0,0 @@ - - - - Grpc.Tools - gRPC C# Tools - Tools for C# implementation of gRPC - an RPC library and framework - Precompiled protobuf compiler and gRPC protobuf compiler plugin for generating gRPC client/server C# code. Binaries are available for Windows, Linux and MacOS. - $version$ - Google Inc. - grpc-packages - https://github.com/grpc/grpc/blob/master/LICENSE - https://github.com/grpc/grpc - false - Release $version$ - Copyright 2015, Google Inc. - gRPC RPC Protocol HTTP/2 - - - - - - - - - - - - - - - - - diff --git a/src/csharp/Grpc.Tools/Common.cs b/src/csharp/Grpc.Tools/Common.cs new file mode 100644 index 0000000000..df539f8c4f --- /dev/null +++ b/src/csharp/Grpc.Tools/Common.cs @@ -0,0 +1,105 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +[assembly: InternalsVisibleTo("Grpc.Tools.Tests")] + +namespace Grpc.Tools { + // Metadata names that we refer to often. + static class Metadata { + // On output dependency lists. + public static string kSource = "Source"; + // On ProtoBuf items. + public static string kProtoRoot = "ProtoRoot"; + public static string kOutputDir = "OutputDir"; + public static string kGrpcServices = "GrpcServices"; + public static string kGrpcOutputDir = "GrpcOutputDir"; + }; + + // A few flags used to control the behavior under various platforms. + internal static class Platform { + public enum OsKind { Unknown, Windows, Linux, MacOsX }; + public static readonly OsKind Os; + + public enum CpuKind { Unknown, X86, X64 }; + public static readonly CpuKind Cpu; + + // This is not necessarily true, but good enough. BCL lacks a per-FS + // API to determine file case sensitivity. + public static bool IsFsCaseInsensitive => Os == OsKind.Windows; + public static bool IsWindows => Os == OsKind.Windows; + + static Platform() { +#if NETSTANDARD + Os = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? OsKind.Windows + : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? OsKind.Linux + : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? OsKind.MacOsX + : OsKind.Unknown; + + switch (RuntimeInformation.OSArchitecture) { + case Architecture.X86: Cpu = CpuKind.X86; break; + case Architecture.X64: Cpu = CpuKind.X64; break; + // We do not have build tools for other architectures. + default: Cpu = CpuKind.Unknown; break; + } +#else + // Running under either Mono or full MS framework. + Os = OsKind.Windows; + if (Type.GetType("Mono.Runtime", throwOnError: false) != null) { + // Congratulations. We are running under Mono. + var plat = Environment.OSVersion.Platform; + if (plat == PlatformID.MacOSX) { + Os = OsKind.MacOsX; + } else if (plat == PlatformID.Unix || (int)plat == 128) { + // TODO(kkm): This is how Mono detects OSX internally. Looks cheesy + // to me. Would not testing for /proc absence be more reliable? OSX + // did never have it, AFAIK. + Os = File.Exists("/usr/lib/libc.dylib") ? OsKind.MacOsX : OsKind.Linux; + } + } + + // Hope we are not building on ARM under Xamarin! + Cpu = Environment.Is64BitOperatingSystem ? CpuKind.X64 : CpuKind.X86; +#endif + } + }; + + // Exception handling helpers. + static class Exceptions { + // Returns true iff the exception indicates an error from an I/O call. See + // https://github.com/Microsoft/msbuild/blob/v15.4.8.50001/src/Shared/ExceptionHandling.cs#L101 + static public bool IsIoRelated(Exception ex) => + ex is IOException || + (ex is ArgumentException && !(ex is ArgumentNullException)) || + ex is SecurityException || + ex is UnauthorizedAccessException || + ex is NotSupportedException; + }; + + // String helpers. + static class Strings { + // Compare string to argument using OrdinalIgnoreCase comparison. + public static bool EqualNoCase(this string a, string b) => + string.Equals(a, b, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/src/csharp/Grpc.Tools/DepFileUtil.cs b/src/csharp/Grpc.Tools/DepFileUtil.cs new file mode 100644 index 0000000000..2a931b7295 --- /dev/null +++ b/src/csharp/Grpc.Tools/DepFileUtil.cs @@ -0,0 +1,198 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Grpc.Tools { + internal static class DepFileUtil { +/* + Sample dependency files. Notable features we have to deal with: + * Slash doubling, must normalize them. + * Spaces in file names. Cannot just "unwrap" the line on backslash at eof; + rather, treat every line as containing one file name except for one with + the ':' separator, as containing exactly two. + * Deal with ':' also being drive letter separator (second example). + +obj\Release\net45\/Foo.cs \ +obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\ + C:/projects/foo/src//foo.proto + +C:\projects\foo\src\./foo.grpc.pb.cc \ +C:\projects\foo\src\./foo.grpc.pb.h \ +C:\projects\foo\src\./foo.pb.cc \ +C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ + C:/foo/include/google/protobuf/any.proto\ + C:/foo/include/google/protobuf/source_context.proto\ + C:/foo/include/google/protobuf/type.proto\ + foo.proto +*/ + + // Read file names from the dependency file to the right of ':'. + public static string[] ReadDependencyInputs(string protoDepDir, string proto, + TaskLoggingHelper log) { + string depFilename = GetDepFilenameForProto(protoDepDir, proto); + string[] lines = ReadDepFileLines(depFilename, false, log); + if (lines.Length == 0) { + return lines; + } + + var result = new List(); + bool skip = true; + foreach (string line in lines) { + // Start at the only line separating dependency outputs from inputs. + int ix = skip ? FindLineSeparator(line) : -1; + skip = skip && ix < 0; + if (skip) continue; + string file = ExtractFilenameFromLine(line, ix + 1, line.Length); + if (file == "") { + log.LogMessage(MessageImportance.Low, + $"Skipping unparsable dependency file {depFilename}.\nLine with error: '{line}'"); + return new string[0]; + } + + // Do not bend over backwards trying not to include a proto into its + // own list of dependencies. Since a file is not older than self, + // it is safe to add; this is purely a memory optimization. + if (file != proto) { + result.Add(file); + } + } + return result.ToArray(); + } + + // Read file names from the dependency file to the left of ':'. + public static string[] ReadDependencyOutputs(string depFilename, + TaskLoggingHelper log) { + string[] lines = ReadDepFileLines(depFilename, true, log); + if (lines.Length == 0) { + return lines; + } + + var result = new List(); + foreach (string line in lines) { + int ix = FindLineSeparator(line); + string file = ExtractFilenameFromLine(line, 0, ix >= 0 ? ix : line.Length); + if (file == "") { + log.LogError("Unable to parse generated dependency file {0}.\n" + + "Line with error: '{1}'", depFilename, line); + return new string[0]; + } + result.Add(file); + + // If this is the line with the separator, do not read further. + if (ix >= 0) + break; + } + return result.ToArray(); + } + + // Get complete dependency file name from directory hash and file name, + // tucked onto protoDepDir, e. g. + // ("out", "foo/file.proto") => "out/deadbeef12345678_file.protodep". + // This way, the filenames are unique but still possible to make sense of. + public static string GetDepFilenameForProto(string protoDepDir, string proto) { + string dirname = Path.GetDirectoryName(proto); + if (Platform.IsFsCaseInsensitive) { + dirname = dirname.ToLowerInvariant(); + } + string dirhash = HashString64Hex(dirname); + string filename = Path.GetFileNameWithoutExtension(proto); + return Path.Combine(protoDepDir, $"{dirhash}_{filename}.protodep"); + } + + // Get a 64-bit hash for a directory string. We treat it as if it were + // unique, since there are not so many distinct proto paths in a project. + // We take the first 64 bit of the string SHA1. + // Internal for tests access only. + internal static string HashString64Hex(string str) { + using (var sha1 = System.Security.Cryptography.SHA1.Create()) { + byte[] hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(str)); + var hashstr = new StringBuilder(16); + for (int i = 0; i < 8; i++) { + hashstr.Append(hash[i].ToString("x2")); + } + return hashstr.ToString(); + } + } + + // Extract filename between 'beg' (inclusive) and 'end' (exclusive) from + // line 'line', skipping over trailing and leading whitespace, and, when + // 'end' is immediately past end of line 'line', also final '\' (used + // as a line continuation token in the dep file). + // Returns an empty string if the filename cannot be extracted. + static string ExtractFilenameFromLine(string line, int beg, int end) { + while (beg < end && char.IsWhiteSpace(line[beg])) beg++; + if (beg < end && end == line.Length && line[end - 1] == '\\') end--; + while (beg < end && char.IsWhiteSpace(line[end - 1])) end--; + if (beg == end) return ""; + + string filename = line.Substring(beg, end - beg); + try { + // Normalize file name. + return Path.Combine( + Path.GetDirectoryName(filename), + Path.GetFileName(filename)); + } catch (Exception ex) when (Exceptions.IsIoRelated(ex)) { + return ""; + } + } + + // Finds the index of the ':' separating dependency clauses in the line, + // not taking Windows drive spec into account. Returns the index of the + // separating ':', or -1 if no separator found. + static int FindLineSeparator(string line) { + // Mind this case where the first ':' is not separator: + // C:\foo\bar\.pb.h: C:/protobuf/wrappers.proto\ + int ix = line.IndexOf(':'); + if (ix <= 0 || ix == line.Length - 1 + || (line[ix + 1] != '/' && line[ix + 1] != '\\') + || !char.IsLetter(line[ix - 1])) + return ix; // Not a windows drive: no letter before ':', or no '\' after. + for (int j = ix - 1; --j >= 0;) { + if (!char.IsWhiteSpace(line[j])) + return ix; // Not space or BOL only before "X:/". + } + return line.IndexOf(':', ix + 1); + } + + // Read entire dependency file. The 'required' parameter controls error + // logging behavior in case the file not found. We require this file when + // compiling, but reading it is optional when computing depnedencies. + static string[] ReadDepFileLines(string filename, bool required, + TaskLoggingHelper log) { + try { + var result = File.ReadAllLines(filename); + if (!required) + log.LogMessage(MessageImportance.Low, $"Using dependency file {filename}"); + return result; + } catch (Exception ex) when (Exceptions.IsIoRelated(ex)) { + if (required) { + log.LogError($"Unable to load {filename}: {ex.GetType().Name}: {ex.Message}"); + } else { + log.LogMessage(MessageImportance.Low, $"Skippping {filename}: {ex.Message}"); + } + return new string[0]; + } + } + }; +} diff --git a/src/csharp/Grpc.Tools/GeneratorServices.cs b/src/csharp/Grpc.Tools/GeneratorServices.cs new file mode 100644 index 0000000000..e1f266aa16 --- /dev/null +++ b/src/csharp/Grpc.Tools/GeneratorServices.cs @@ -0,0 +1,168 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using System.IO; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Grpc.Tools { + // Abstract class for language-specific analysis behavior, such + // as guessing the generated files the same way protoc does. + internal abstract class GeneratorServices { + protected readonly TaskLoggingHelper Log; + protected GeneratorServices(TaskLoggingHelper log) { + Log = log; + } + + // Obtain a service for the given language (csharp, cpp). + public static GeneratorServices GetForLanguage(string lang, TaskLoggingHelper log) { + if (lang.EqualNoCase("csharp")) + return new CSharpGeneratorServices(log); + if (lang.EqualNoCase("cpp")) + return new CppGeneratorServices(log); + log.LogError("Invalid value '{0}' for task property 'Generator'. " + + "Supported generator languages: CSharp, Cpp.", lang); + return null; + } + + // Guess whether item's metadata suggests gRPC stub generation. + // When "gRPCServices" is not defined, assume gRPC is not used. + // When defined, C# uses "none" to skip gRPC, C++ uses "false", so + // recognize both. Since the value is tightly coupled to the scripts, + // we do not try to validate the value; scripts take care of that. + // It is safe to assume that gRPC is requested for any other value. + protected bool GrpcOutputPossible(ITaskItem proto) { + string gsm = proto.GetMetadata(Metadata.kGrpcServices); + return !gsm.EqualNoCase("") && !gsm.EqualNoCase("none") + && !gsm.EqualNoCase("false"); + } + + public abstract string[] GetPossibleOutputs(ITaskItem proto); + }; + + // C# generator services. + internal class CSharpGeneratorServices : GeneratorServices { + public CSharpGeneratorServices(TaskLoggingHelper log) : base(log) {} + + public override string[] GetPossibleOutputs(ITaskItem protoItem) { + bool doGrpc = GrpcOutputPossible(protoItem); + string filename = LowerUnderscoreToUpperCamel( + Path.GetFileNameWithoutExtension(protoItem.ItemSpec)); + + var outputs = new string[doGrpc ? 2 : 1]; + string outdir = protoItem.GetMetadata(Metadata.kOutputDir); + string fileStem = Path.Combine(outdir, filename); + outputs[0] = fileStem + ".cs"; + if (doGrpc) { + // Override outdir if kGrpcOutputDir present, default to proto output. + outdir = protoItem.GetMetadata(Metadata.kGrpcOutputDir); + if (outdir != "") { + fileStem = Path.Combine(outdir, filename); + } + outputs[1] = fileStem + "Grpc.cs"; + } + return outputs; + } + + string LowerUnderscoreToUpperCamel(string str) { + // See src/compiler/generator_helpers.h:118 + var result = new StringBuilder(str.Length, str.Length); + bool cap = true; + foreach (char c in str) { + if (c == '_') { + cap = true; + } else if (cap) { + result.Append(char.ToUpperInvariant(c)); + cap = false; + } else { + result.Append(c); + } + } + return result.ToString(); + } + }; + + // C++ generator services. + internal class CppGeneratorServices : GeneratorServices { + public CppGeneratorServices(TaskLoggingHelper log) : base(log) { } + + public override string[] GetPossibleOutputs(ITaskItem protoItem) { + bool doGrpc = GrpcOutputPossible(protoItem); + string root = protoItem.GetMetadata(Metadata.kProtoRoot); + string proto = protoItem.ItemSpec; + string filename = Path.GetFileNameWithoutExtension(proto); + // E. g., ("foo/", "foo/bar/x.proto") => "bar" + string relative = GetRelativeDir(root, proto); + + var outputs = new string[doGrpc ? 4 : 2]; + string outdir = protoItem.GetMetadata(Metadata.kOutputDir); + string fileStem = Path.Combine(outdir, relative, filename); + outputs[0] = fileStem + ".pb.cc"; + outputs[1] = fileStem + ".pb.h"; + if (doGrpc) { + // Override outdir if kGrpcOutputDir present, default to proto output. + outdir = protoItem.GetMetadata(Metadata.kGrpcOutputDir); + if (outdir != "") { + fileStem = Path.Combine(outdir, relative, filename); + } + outputs[2] = fileStem + "_grpc.pb.cc"; + outputs[3] = fileStem + "_grpc.pb.h"; + } + return outputs; + } + + // Calculate part of proto path relative to root. Protoc is very picky + // about them matching exactly, so can be we. Expect root be exact prefix + // to proto, minus some slash normalization. + string GetRelativeDir(string root, string proto) { + string protoDir = Path.GetDirectoryName(proto); + string rootDir = EndWithSlash(Path.GetDirectoryName(EndWithSlash(root))); + if (rootDir == s_dotSlash) { + // Special case, otherwise we can return "./" instead of "" below! + return protoDir; + } + if (Platform.IsFsCaseInsensitive) { + protoDir = protoDir.ToLowerInvariant(); + rootDir = rootDir.ToLowerInvariant(); + } + protoDir = EndWithSlash(protoDir); + if (!protoDir.StartsWith(rootDir)) { + Log.LogWarning("ProtoBuf item '{0}' has the ProtoRoot metadata '{1}' " + + "which is not prefix to its path. Cannot compute relative path.", + proto, root); + return ""; + } + return protoDir.Substring(rootDir.Length); + } + + // './' or '.\', normalized per system. + static string s_dotSlash = "." + Path.DirectorySeparatorChar; + + static string EndWithSlash(string str) { + if (str == "") { + return s_dotSlash; + } else if (str[str.Length - 1] != '\\' && str[str.Length - 1] != '/') { + return str + Path.DirectorySeparatorChar; + } else { + return str; + } + } + }; +} diff --git a/src/csharp/Grpc.Tools/Grpc.Tools.csproj b/src/csharp/Grpc.Tools/Grpc.Tools.csproj new file mode 100644 index 0000000000..46a6d4670d --- /dev/null +++ b/src/csharp/Grpc.Tools/Grpc.Tools.csproj @@ -0,0 +1,95 @@ + + + + + + Protobuf.MSBuild + $(GrpcCsharpVersion) + + netstandard1.3;net40 + + + + + + + ../../../third_party/protobuf/src/google/protobuf/ + + + + ../protoc_plugins/protoc_ + + + ../protoc_plugins/ + + + + <_NetStandard>False + <_NetStandard Condition=" $(TargetFramework.StartsWith('netstandard')) or $(TargetFramework.StartsWith('netcore')) ">True + + + $(DefineConstants);NETSTANDARD + + + + true + ../../../artifacts + + + build\_protobuf\ + true + true + Grpc.Tools + 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. + Copyright 2018 gRPC authors + gRPC authors + https://github.com/grpc/grpc/blob/master/LICENSE + https://github.com/grpc/grpc + gRPC RPC protocol HTTP/2 + + + + + + + <_ProtoTemp Include="any.proto;api.proto;descriptor.proto;duration.proto;" /> + <_ProtoTemp Include="empty.proto;field_mask.proto;source_context.proto;" /> + <_ProtoTemp Include="struct.proto;timestamp.proto;type.proto;wrappers.proto" /> + <_Asset PackagePath="build/native/include/google/protobuf/" Include="@(_ProtoTemp->'$(Assets_ProtoInclude)%(Identity)')" /> + + + <_Asset PackagePath="build/native/bin/windows/protoc.exe" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" /> + <_Asset PackagePath="build/native/bin/linux_x86/protoc" Include="$(Assets_ProtoCompiler)linux_x86/protoc" /> + <_Asset PackagePath="build/native/bin/linux_x64/protoc" Include="$(Assets_ProtoCompiler)linux_x64/protoc" /> + <_Asset PackagePath="build/native/bin/macosx_x86/protoc" Include="$(Assets_ProtoCompiler)macos_x86/protoc" /> + <_Asset PackagePath="build/native/bin/macosx_x64/protoc" Include="$(Assets_ProtoCompiler)macos_x64/protoc" /> + + + <_Asset PackagePath="build/native/bin/windows/grpc_csharp_plugin.exe" Include="$(Assets_GrpcPlugins)protoc_windows_x86/grpc_csharp_plugin.exe" /> + <_Asset PackagePath="build/native/bin/linux_x86/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_linux_x86/grpc_csharp_plugin" /> + <_Asset PackagePath="build/native/bin/linux_x64/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_linux_x64/grpc_csharp_plugin" /> + <_Asset PackagePath="build/native/bin/macosx_x86/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_macos_x86/grpc_csharp_plugin" /> + <_Asset PackagePath="build/native/bin/macosx_x64/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_macos_x64/grpc_csharp_plugin" /> + + + + + + + + + + + + + + + + + diff --git a/src/csharp/Grpc.Tools/ProtoCompile.cs b/src/csharp/Grpc.Tools/ProtoCompile.cs new file mode 100644 index 0000000000..76c2338ef9 --- /dev/null +++ b/src/csharp/Grpc.Tools/ProtoCompile.cs @@ -0,0 +1,409 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Grpc.Tools { + /// + /// 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. + /// + 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. + @ 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 + @ 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", + }; + + /// + /// Code generator. + /// + [Required] + public string Generator { get; set; } + + /// + /// Protobuf files to compile. + /// + [Required] + public ITaskItem[] ProtoBuf { get; set; } + + /// + /// 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). + /// + public string ProtoDepDir { get; set; } + + /// + /// Dependency file full name. Mutually exclusive with ProtoDepDir. + /// Autogenerated file name is available in this property after execution. + /// Switch: --dependency_out. + /// + [Output] + public string DependencyOut { get; set; } + + /// + /// The directories to search for imports. Directories will be searched + /// in order. If not given, the current working directory is used. + /// Switch: --proto_path. + /// + public string[] ProtoPath { get; set; } + + /// + /// Generated code directory. The generator property determines the language. + /// Switch: --GEN-out= (for different generators GEN). + /// + [Required] + public string OutputDir { get; set; } + + /// + /// Codegen options. See also OptionsFromMetadata. + /// Switch: --GEN_out= (for different generators GEN). + /// + public string[] OutputOptions { get; set; } + + /// + /// Full path to the gRPC plugin executable. If specified, gRPC generation + /// is enabled for the files. + /// Switch: --plugin=protoc-gen-grpc= + /// + public string GrpcPluginExe { get; set; } + + /// + /// Generated gRPC directory. The generator property determines the + /// language. If gRPC is enabled but this is not given, OutputDir is used. + /// Switch: --grpc_out= + /// + public string GrpcOutputDir { get; set; } + + /// + /// gRPC Codegen options. See also OptionsFromMetadata. + /// --grpc_opt=opt1,opt2=val (comma-separated). + /// + public string[] GrpcOutputOptions { get; set; } + + /// + /// List of files written in addition to generated outputs. Includes a + /// single item for the dependency file if written. + /// + [Output] + public ITaskItem[] AdditionalFileWrites { get; private set; } + + /// + /// 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. + /// + [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; + } + }; +} diff --git a/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs b/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs new file mode 100644 index 0000000000..9afea9255e --- /dev/null +++ b/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs @@ -0,0 +1,80 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Collections.Generic; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Grpc.Tools { + public class ProtoCompilerOutputs : Task { + /// + /// Code generator. Currently supported are "csharp", "cpp". + /// + [Required] + public string Generator { get; set; } + + /// + /// 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. + /// + [Required] + public ITaskItem[] ProtoBuf { get; set; } + + /// + /// 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: + /// + /// + [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(); + foreach (var proto in ProtoBuf) { + var outputs = generator.GetPossibleOutputs(proto); + foreach (string output in outputs) { + var ti = new TaskItem(output); + ti.SetMetadata(Metadata.kSource, proto.ItemSpec); + possible.Add(ti); + } + } + PossibleOutputs = possible.ToArray(); + + return !Log.HasLoggedErrors; + } + }; +} diff --git a/src/csharp/Grpc.Tools/ProtoReadDependencies.cs b/src/csharp/Grpc.Tools/ProtoReadDependencies.cs new file mode 100644 index 0000000000..2ee0389146 --- /dev/null +++ b/src/csharp/Grpc.Tools/ProtoReadDependencies.cs @@ -0,0 +1,70 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Collections.Generic; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Grpc.Tools { + public class ProtoReadDependencies : Task { + /// + /// The collection is used to collect possible additional dependencies + /// of proto files cached under ProtoDepDir. + /// + [Required] + public ITaskItem[] ProtoBuf { get; set; } + + /// + /// Directory where protoc dependency files are cached. + /// + [Required] + public string ProtoDepDir { get; set; } + + /// + /// 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 + /// + /// + [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(); + foreach (var proto in ProtoBuf) { + string[] deps = DepFileUtil.ReadDependencyInputs(ProtoDepDir, proto.ItemSpec, Log); + foreach (string dep in deps) { + var ti = new TaskItem(dep); + ti.SetMetadata(Metadata.kSource, proto.ItemSpec); + dependencies.Add(ti); + } + } + Dependencies = dependencies.ToArray(); + } else { + Dependencies = new ITaskItem[0]; + } + + return !Log.HasLoggedErrors; + } + }; +} diff --git a/src/csharp/Grpc.Tools/ProtoToolsPlatform.cs b/src/csharp/Grpc.Tools/ProtoToolsPlatform.cs new file mode 100644 index 0000000000..f505b86fe4 --- /dev/null +++ b/src/csharp/Grpc.Tools/ProtoToolsPlatform.cs @@ -0,0 +1,58 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Grpc.Tools { + /// + /// A helper task to resolve actual OS type and bitness. + /// + public class ProtoToolsPlatform : Task { + /// + /// Return one of 'linux', 'macosx' or 'windows'. + /// If the OS is unknown, the property is not set. + /// + [Output] + public string Os { get; set; } + + /// + /// Return one of 'x64' or 'x86'. + /// If the CPU is unknown, the property is not set. + /// + [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; + } + }; +} diff --git a/src/csharp/Grpc.Tools/build/Grpc.Tools.props b/src/csharp/Grpc.Tools/build/Grpc.Tools.props new file mode 100644 index 0000000000..dbcd8bf494 --- /dev/null +++ b/src/csharp/Grpc.Tools/build/Grpc.Tools.props @@ -0,0 +1,11 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + + diff --git a/src/csharp/Grpc.Tools/build/Grpc.Tools.targets b/src/csharp/Grpc.Tools/build/Grpc.Tools.targets new file mode 100644 index 0000000000..c0a5b1e2c5 --- /dev/null +++ b/src/csharp/Grpc.Tools/build/Grpc.Tools.targets @@ -0,0 +1,11 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + + diff --git a/src/csharp/Grpc.Tools/build/_grpc/Grpc.CSharp.xml b/src/csharp/Grpc.Tools/build/_grpc/Grpc.CSharp.xml new file mode 100644 index 0000000000..54468eb5ef --- /dev/null +++ b/src/csharp/Grpc.Tools/build/_grpc/Grpc.CSharp.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/csharp/Grpc.Tools/build/_grpc/README b/src/csharp/Grpc.Tools/build/_grpc/README new file mode 100644 index 0000000000..4a7204b9ff --- /dev/null +++ b/src/csharp/Grpc.Tools/build/_grpc/README @@ -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. diff --git a/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.props b/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.props new file mode 100644 index 0000000000..8ce07c48ab --- /dev/null +++ b/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.props @@ -0,0 +1,6 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + diff --git a/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets b/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets new file mode 100644 index 0000000000..0042bf2bfa --- /dev/null +++ b/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets @@ -0,0 +1,46 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + grpc_csharp_plugin + + + + + + File;BrowseObject + + + + + + Both + + + + + + + $(Protobuf_PackagedToolsPath)bin\$(Protobuf_ToolsOs)\$(gRPC_PluginFileName).exe + $(Protobuf_PackagedToolsPath)bin/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/$(gRPC_PluginFileName) + + + + + + + $(gRPC_PluginFullPath) + %(Protobuf_Compile.OutputDir) + <_GrpcOutputOptions Condition=" '%(Protobuf_Compile.Access)' == 'Internal' ">%(Protobuf_Compile._GrpcOutputOptions);internal_access + + + <_GrpcOutputOptions>%(Protobuf_Compile._GrpcOutputOptions);no_server + + + <_GrpcOutputOptions>%(Protobuf_Compile._GrpcOutputOptions);no_client + + + + diff --git a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props new file mode 100644 index 0000000000..06ee9bcda8 --- /dev/null +++ b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props @@ -0,0 +1,23 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + 1 + + + $( [System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)../native/) ) + $(Protobuf_PackagedToolsPath)include + + + + + + + diff --git a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets new file mode 100644 index 0000000000..5a8d3f2027 --- /dev/null +++ b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets @@ -0,0 +1,383 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + CSharp + + true + <_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' == 'Core' ">netstandard1.3\Protobuf.MSBuild.dll + <_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net40\Protobuf.MSBuild.dll + + + + + + + + + $(IntermediateOutputPath) + $(Protobuf_IntermediatePath) + $(Protobuf_IntermediatePath) + + + + + Public + True + + True + $(Protobuf_OutputPath) + + + + + + File;BrowseObject + + + + + + + false + + + + + + + + + + + + + + + + + + + $(PROTOBUF_TOOLS_OS) + $(PROTOBUF_TOOLS_CPU) + $(PROTOBUF_PROTOC) + + + $(_Protobuf_ToolsOs) + $(_Protobuf_ToolsCpu) + $(Protobuf_PackagedToolsPath)bin\$(Protobuf_ToolsOs)\protoc.exe + $(Protobuf_PackagedToolsPath)bin/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/protoc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + + + %(RelativeDir) + + + + + + %(Identity) + + + + + + + + + + + + + + + + + + + + <_Protobuf_CodeCompile Include="@(Protobuf_ExpectedOutputs->Distinct())" + Condition=" '%(Source)' != '' and '@(Protobuf_Compile->WithMetadataValue('CompileOutputs', 'true'))' != '' " /> + + + + + + + + + + %(Identity) + + + + + + + + + + + + + + + + + <_Protobuf_OutOfDateProto Remove="@(_Protobuf_OutOfDateProto->WithMetadataValue('_Exec',''))" /> + + + + + + + <_Protobuf_OutOfDateProto Include="@(Protobuf_Compile)" + Condition = " '%(Source)' != '' and '@(Protobuf_ExpectedOutputs)' == '' "> + <_Exec>true + + + + + + + + + + + + + + + + + + + <_OutputOptions Condition=" '%(Protobuf_Compile.Access)' == 'Internal' ">%(Protobuf_Compile._OutputOptions);internal_access + + + + + + + + + + + + + <_Protobuf_OutOfDateProto> + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_Protobuf_Protodep Include="$(Protobuf_DepFilesPath)*.protodep" /> + + + + + + + + + + + + + diff --git a/src/csharp/Grpc.Tools/build/_protobuf/Protobuf.CSharp.xml b/src/csharp/Grpc.Tools/build/_protobuf/Protobuf.CSharp.xml new file mode 100644 index 0000000000..2c41fbcbd0 --- /dev/null +++ b/src/csharp/Grpc.Tools/build/_protobuf/Protobuf.CSharp.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/csharp/Grpc.Tools/build/_protobuf/README b/src/csharp/Grpc.Tools/build/_protobuf/README new file mode 100644 index 0000000000..e6e358a218 --- /dev/null +++ b/src/csharp/Grpc.Tools/build/_protobuf/README @@ -0,0 +1 @@ +TODO(kkm): These file will go into Google.Protobuf.Tools/build after package split. diff --git a/src/csharp/Grpc.Tools/build/native/Grpc.Tools.props b/src/csharp/Grpc.Tools/build/native/Grpc.Tools.props new file mode 100644 index 0000000000..7f64ae9165 --- /dev/null +++ b/src/csharp/Grpc.Tools/build/native/Grpc.Tools.props @@ -0,0 +1,15 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + 1 + + + $(MSBuildThisFileDirectory)bin\windows\protoc.exe + $(MSBuildThisFileDirectory)bin\include\ + grpc_cpp_plugin + $(MSBuildThisFileDirectory)bin\windows\grpc_cpp_plugin.exe + + diff --git a/src/csharp/Grpc.sln b/src/csharp/Grpc.sln index d9a7b8d556..6c1b2e9998 100644 --- a/src/csharp/Grpc.sln +++ b/src/csharp/Grpc.sln @@ -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 diff --git a/src/csharp/build_packages_dotnetcli.bat b/src/csharp/build_packages_dotnetcli.bat index 394b859e0b..cae253bc7b 100755 --- a/src/csharp/build_packages_dotnetcli.bat +++ b/src/csharp/build_packages_dotnetcli.bat @@ -45,10 +45,10 @@ xcopy /Y /I nativelibs\csharp_ext_windows_x64\grpc_csharp_ext.dll ..\..\cmake\bu %DOTNET% pack --configuration Release Grpc.Auth --output ..\..\..\artifacts || goto :error %DOTNET% pack --configuration Release Grpc.HealthCheck --output ..\..\..\artifacts || goto :error %DOTNET% pack --configuration Release Grpc.Reflection --output ..\..\..\artifacts || goto :error +%DOTNET% pack --configuration Release Grpc.Tools --output ..\..\..\artifacts || goto :error %NUGET% pack Grpc.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts || goto :error %NUGET% pack Grpc.Core.NativeDebug.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts -%NUGET% pack Grpc.Tools.nuspec -Version %VERSION% -OutputDirectory ..\..\artifacts @rem copy resulting nuget packages to artifacts directory xcopy /Y /I *.nupkg ..\..\artifacts\ || goto :error diff --git a/src/csharp/build_packages_dotnetcli.sh b/src/csharp/build_packages_dotnetcli.sh index 273d745f17..8b81eba3aa 100755 --- a/src/csharp/build_packages_dotnetcli.sh +++ b/src/csharp/build_packages_dotnetcli.sh @@ -44,9 +44,9 @@ dotnet pack --configuration Release Grpc.Core.Testing --output ../../../artifact dotnet pack --configuration Release Grpc.Auth --output ../../../artifacts dotnet pack --configuration Release Grpc.HealthCheck --output ../../../artifacts dotnet pack --configuration Release Grpc.Reflection --output ../../../artifacts +dotnet pack --configuration Release Grpc.Tools --output ../../../artifacts nuget pack Grpc.nuspec -Version "1.14.0-dev" -OutputDirectory ../../artifacts nuget pack Grpc.Core.NativeDebug.nuspec -Version "1.14.0-dev" -OutputDirectory ../../artifacts -nuget pack Grpc.Tools.nuspec -Version "1.14.0-dev" -OutputDirectory ../../artifacts (cd ../../artifacts && zip csharp_nugets_dotnetcli.zip *.nupkg) diff --git a/src/csharp/tests.json b/src/csharp/tests.json index c2f243fe0a..483d1d7aad 100644 --- a/src/csharp/tests.json +++ b/src/csharp/tests.json @@ -62,5 +62,15 @@ "Grpc.Reflection.Tests": [ "Grpc.Reflection.Tests.ReflectionClientServerTest", "Grpc.Reflection.Tests.SymbolRegistryTest" + ], + "Grpc.Tools.Tests": [ + "Grpc.Tools.Tests.CppGeneratorTests", + "Grpc.Tools.Tests.CSharpGeneratorTests", + "Grpc.Tools.Tests.GeneratorTests", + "Grpc.Tools.Tests.ProtoCompileBasicTests", + "Grpc.Tools.Tests.ProtoCompileCommandLineGeneratorTests", + "Grpc.Tools.Tests.ProtoCompileCommandLinePrinterTests", + "Grpc.Tools.Tests.ProtoToolsPlatformTaskTests", + "Grps.Tools.Tests.DepFileUtilTests" ] } -- cgit v1.2.3 From 17df1f8cf59b88ca6df78ebc736ae038dd3bbfbe Mon Sep 17 00:00:00 2001 From: kkm Date: Wed, 13 Jun 2018 18:27:07 -0700 Subject: fixup! Add Grpc.Tools MsBuild taks assembly, test and scripting --- .../Grpc.Tools.Tests/Grpc.Tools.Tests.csproj | 14 +- src/csharp/Grpc.Tools/Common.cs | 12 +- src/csharp/Grpc.Tools/DepFileUtil.cs | 103 +++++++++---- src/csharp/Grpc.Tools/GeneratorServices.cs | 12 +- src/csharp/Grpc.Tools/Grpc.Tools.csproj | 31 ++-- src/csharp/Grpc.Tools/ProtoCompile.cs | 170 ++++++++++----------- src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs | 2 +- src/csharp/Grpc.Tools/ProtoReadDependencies.cs | 2 +- .../build/_protobuf/Google.Protobuf.Tools.targets | 4 +- 9 files changed, 201 insertions(+), 149 deletions(-) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj index 585e4518b5..661d97d81b 100644 --- a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj +++ b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj @@ -1,23 +1,25 @@ + + - netcoreapp1.0;net45 + net45;netcoreapp1.0 Exe + + - - - + + - - + diff --git a/src/csharp/Grpc.Tools/Common.cs b/src/csharp/Grpc.Tools/Common.cs index df539f8c4f..1ebd386bd1 100644 --- a/src/csharp/Grpc.Tools/Common.cs +++ b/src/csharp/Grpc.Tools/Common.cs @@ -25,15 +25,15 @@ using System.Security; [assembly: InternalsVisibleTo("Grpc.Tools.Tests")] namespace Grpc.Tools { - // Metadata names that we refer to often. + // Metadata names (MSBuild item attributes) that we refer to often. static class Metadata { // On output dependency lists. - public static string kSource = "Source"; + public static string Source = "Source"; // On ProtoBuf items. - public static string kProtoRoot = "ProtoRoot"; - public static string kOutputDir = "OutputDir"; - public static string kGrpcServices = "GrpcServices"; - public static string kGrpcOutputDir = "GrpcOutputDir"; + 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. diff --git a/src/csharp/Grpc.Tools/DepFileUtil.cs b/src/csharp/Grpc.Tools/DepFileUtil.cs index 2a931b7295..e635ad1e85 100644 --- a/src/csharp/Grpc.Tools/DepFileUtil.cs +++ b/src/csharp/Grpc.Tools/DepFileUtil.cs @@ -25,29 +25,38 @@ using Microsoft.Build.Utilities; namespace Grpc.Tools { internal static class DepFileUtil { -/* - Sample dependency files. Notable features we have to deal with: - * Slash doubling, must normalize them. - * Spaces in file names. Cannot just "unwrap" the line on backslash at eof; - rather, treat every line as containing one file name except for one with - the ':' separator, as containing exactly two. - * Deal with ':' also being drive letter separator (second example). - -obj\Release\net45\/Foo.cs \ -obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\ - C:/projects/foo/src//foo.proto - -C:\projects\foo\src\./foo.grpc.pb.cc \ -C:\projects\foo\src\./foo.grpc.pb.h \ -C:\projects\foo\src\./foo.pb.cc \ -C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ - C:/foo/include/google/protobuf/any.proto\ - C:/foo/include/google/protobuf/source_context.proto\ - C:/foo/include/google/protobuf/type.proto\ - foo.proto -*/ - - // Read file names from the dependency file to the right of ':'. + /* + Sample dependency files. Notable features we have to deal with: + * Slash doubling, must normalize them. + * Spaces in file names. Cannot just "unwrap" the line on backslash at eof; + rather, treat every line as containing one file name except for one with + the ':' separator, as containing exactly two. + * Deal with ':' also being drive letter separator (second example). + + obj\Release\net45\/Foo.cs \ + obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\ + C:/projects/foo/src//foo.proto + + C:\projects\foo\src\./foo.grpc.pb.cc \ + C:\projects\foo\src\./foo.grpc.pb.h \ + C:\projects\foo\src\./foo.pb.cc \ + C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ + C:/foo/include/google/protobuf/any.proto\ + C:/foo/include/google/protobuf/source_context.proto\ + C:/foo/include/google/protobuf/type.proto\ + foo.proto + */ + + /// + /// Read file names from the dependency file to the right of ':' + /// + /// Relative path to the dependency cache, e. g. "out" + /// Relative path to the proto item, e. g. "foo/file.proto" + /// A for logging + /// + /// Array of the proto file input dependencies as written by protoc, or empty + /// array if the dependency file does not exist or cannot be parsed. + /// public static string[] ReadDependencyInputs(string protoDepDir, string proto, TaskLoggingHelper log) { string depFilename = GetDepFilenameForProto(protoDepDir, proto); @@ -80,7 +89,20 @@ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ return result.ToArray(); } - // Read file names from the dependency file to the left of ':'. + /// + /// Read file names from the dependency file to the left of ':' + /// + /// Path to dependency file written by protoc + /// A for logging + /// + /// 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. + /// + /// + /// Since this is called after a protoc invocation, an unparsable or missing + /// file causes an error-level message to be logged. + /// public static string[] ReadDependencyOutputs(string depFilename, TaskLoggingHelper log) { string[] lines = ReadDepFileLines(depFilename, true, log); @@ -106,10 +128,31 @@ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ return result.ToArray(); } - // Get complete dependency file name from directory hash and file name, - // tucked onto protoDepDir, e. g. - // ("out", "foo/file.proto") => "out/deadbeef12345678_file.protodep". - // This way, the filenames are unique but still possible to make sense of. + /// + /// Construct relative dependency file name from directory hash and file name + /// + /// Relative path to the dependency cache, e. g. "out" + /// Relative path to the proto item, e. g. "foo/file.proto" + /// + /// Full relative path to the dependency file, e. g. + /// "out/deadbeef12345678_file.protodep" + /// + /// + /// 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. + /// public static string GetDepFilenameForProto(string protoDepDir, string proto) { string dirname = Path.GetDirectoryName(proto); if (Platform.IsFsCaseInsensitive) { @@ -177,7 +220,7 @@ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ // Read entire dependency file. The 'required' parameter controls error // logging behavior in case the file not found. We require this file when - // compiling, but reading it is optional when computing depnedencies. + // compiling, but reading it is optional when computing dependencies. static string[] ReadDepFileLines(string filename, bool required, TaskLoggingHelper log) { try { @@ -189,7 +232,7 @@ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ if (required) { log.LogError($"Unable to load {filename}: {ex.GetType().Name}: {ex.Message}"); } else { - log.LogMessage(MessageImportance.Low, $"Skippping {filename}: {ex.Message}"); + log.LogMessage(MessageImportance.Low, $"Skipping {filename}: {ex.Message}"); } return new string[0]; } diff --git a/src/csharp/Grpc.Tools/GeneratorServices.cs b/src/csharp/Grpc.Tools/GeneratorServices.cs index e1f266aa16..52bd29a678 100644 --- a/src/csharp/Grpc.Tools/GeneratorServices.cs +++ b/src/csharp/Grpc.Tools/GeneratorServices.cs @@ -49,7 +49,7 @@ namespace Grpc.Tools { // we do not try to validate the value; scripts take care of that. // It is safe to assume that gRPC is requested for any other value. protected bool GrpcOutputPossible(ITaskItem proto) { - string gsm = proto.GetMetadata(Metadata.kGrpcServices); + string gsm = proto.GetMetadata(Metadata.GrpcServices); return !gsm.EqualNoCase("") && !gsm.EqualNoCase("none") && !gsm.EqualNoCase("false"); } @@ -67,12 +67,12 @@ namespace Grpc.Tools { Path.GetFileNameWithoutExtension(protoItem.ItemSpec)); var outputs = new string[doGrpc ? 2 : 1]; - string outdir = protoItem.GetMetadata(Metadata.kOutputDir); + 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.kGrpcOutputDir); + outdir = protoItem.GetMetadata(Metadata.GrpcOutputDir); if (outdir != "") { fileStem = Path.Combine(outdir, filename); } @@ -105,20 +105,20 @@ namespace Grpc.Tools { public override string[] GetPossibleOutputs(ITaskItem protoItem) { bool doGrpc = GrpcOutputPossible(protoItem); - string root = protoItem.GetMetadata(Metadata.kProtoRoot); + 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.kOutputDir); + 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.kGrpcOutputDir); + outdir = protoItem.GetMetadata(Metadata.GrpcOutputDir); if (outdir != "") { fileStem = Path.Combine(outdir, relative, filename); } diff --git a/src/csharp/Grpc.Tools/Grpc.Tools.csproj b/src/csharp/Grpc.Tools/Grpc.Tools.csproj index 46a6d4670d..8edfb848d7 100644 --- a/src/csharp/Grpc.Tools/Grpc.Tools.csproj +++ b/src/csharp/Grpc.Tools/Grpc.Tools.csproj @@ -6,7 +6,19 @@ Protobuf.MSBuild $(GrpcCsharpVersion) - netstandard1.3;net40 + net45;netstandard1.3 + + + + + + /usr/lib/mono/4.5-api + /usr/local/lib/mono/4.5-api + /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api @@ -32,9 +44,6 @@ - true - ../../../artifacts - build\_protobuf\ true @@ -57,10 +66,9 @@ Linux and MacOS. Managed runtime is supplied separately in the Grpc.Core package - <_ProtoTemp Include="any.proto;api.proto;descriptor.proto;duration.proto;" /> - <_ProtoTemp Include="empty.proto;field_mask.proto;source_context.proto;" /> - <_ProtoTemp Include="struct.proto;timestamp.proto;type.proto;wrappers.proto" /> - <_Asset PackagePath="build/native/include/google/protobuf/" Include="@(_ProtoTemp->'$(Assets_ProtoInclude)%(Identity)')" /> + <_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')" /> <_Asset PackagePath="build/native/bin/windows/protoc.exe" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" /> @@ -85,10 +93,9 @@ Linux and MacOS. Managed runtime is supplied separately in the Grpc.Core package - - - + + diff --git a/src/csharp/Grpc.Tools/ProtoCompile.cs b/src/csharp/Grpc.Tools/ProtoCompile.cs index 76c2338ef9..e77084b1ef 100644 --- a/src/csharp/Grpc.Tools/ProtoCompile.cs +++ b/src/csharp/Grpc.Tools/ProtoCompile.cs @@ -32,91 +32,91 @@ namespace Grpc.Tools { /// any language outputs. /// 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. - @ 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 - @ 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. -*/ + /* + + 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. + @ 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 + @ 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", diff --git a/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs b/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs index 9afea9255e..74aaa8bd3d 100644 --- a/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs +++ b/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs @@ -68,7 +68,7 @@ namespace Grpc.Tools { var outputs = generator.GetPossibleOutputs(proto); foreach (string output in outputs) { var ti = new TaskItem(output); - ti.SetMetadata(Metadata.kSource, proto.ItemSpec); + ti.SetMetadata(Metadata.Source, proto.ItemSpec); possible.Add(ti); } } diff --git a/src/csharp/Grpc.Tools/ProtoReadDependencies.cs b/src/csharp/Grpc.Tools/ProtoReadDependencies.cs index 2ee0389146..ea931b0826 100644 --- a/src/csharp/Grpc.Tools/ProtoReadDependencies.cs +++ b/src/csharp/Grpc.Tools/ProtoReadDependencies.cs @@ -55,7 +55,7 @@ namespace Grpc.Tools { string[] deps = DepFileUtil.ReadDependencyInputs(ProtoDepDir, proto.ItemSpec, Log); foreach (string dep in deps) { var ti = new TaskItem(dep); - ti.SetMetadata(Metadata.kSource, proto.ItemSpec); + ti.SetMetadata(Metadata.Source, proto.ItemSpec); dependencies.Add(ti); } } diff --git a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets index 5a8d3f2027..2ed515c8db 100644 --- a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets +++ b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets @@ -7,7 +7,7 @@ true <_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' == 'Core' ">netstandard1.3\Protobuf.MSBuild.dll - <_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net40\Protobuf.MSBuild.dll + <_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net45\Protobuf.MSBuild.dll @@ -370,7 +370,7 @@ Design-time support =================================================================================--> - Date: Mon, 13 Aug 2018 14:16:31 +0200 Subject: add linux/mac net45 compilation fix to Grcp.Tools.Tests.csproj --- src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj index 661d97d81b..7c1a6afe34 100644 --- a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj +++ b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj @@ -9,6 +9,18 @@ + + + + /usr/lib/mono/4.5-api + /usr/local/lib/mono/4.5-api + /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api + + -- cgit v1.2.3 From 67c2d99189620ea930aba4b8b3cbca18e21bc495 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 13 Aug 2018 14:48:01 +0200 Subject: add Grpc.Tools.Tests to SanityTest --- src/csharp/Grpc.Core.Tests/SanityTest.cs | 1 + 1 file changed, 1 insertion(+) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Core.Tests/SanityTest.cs b/src/csharp/Grpc.Core.Tests/SanityTest.cs index 73efad1f84..13c8c0d143 100644 --- a/src/csharp/Grpc.Core.Tests/SanityTest.cs +++ b/src/csharp/Grpc.Core.Tests/SanityTest.cs @@ -102,6 +102,7 @@ namespace Grpc.Core.Tests "Grpc.HealthCheck.Tests", "Grpc.IntegrationTesting", "Grpc.Reflection.Tests", + "Grpc.Tools.Tests", }; foreach (var assemblyName in otherAssemblies) { -- cgit v1.2.3 From 62901c1631e21657554a17dba9bfee550342edfd Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 13 Aug 2018 14:55:22 +0200 Subject: C# sanity test accepts [TestCase] attribute too --- src/csharp/Grpc.Core.Tests/SanityTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Core.Tests/SanityTest.cs b/src/csharp/Grpc.Core.Tests/SanityTest.cs index 13c8c0d143..0904453b6e 100644 --- a/src/csharp/Grpc.Core.Tests/SanityTest.cs +++ b/src/csharp/Grpc.Core.Tests/SanityTest.cs @@ -65,13 +65,13 @@ namespace Grpc.Core.Tests { foreach (var m in t.GetMethods()) { - var attributes = m.GetCustomAttributes(typeof(NUnit.Framework.TestAttribute), true); - if (attributes.Length > 0) + var testAttributes = m.GetCustomAttributes(typeof(NUnit.Framework.TestAttribute), true); + var testCaseAttributes = m.GetCustomAttributes(typeof(NUnit.Framework.TestCaseAttribute), true); + if (testAttributes.Length > 0 || testCaseAttributes.Length > 0) { testClasses.Add(t.FullName); break; } - } } testClasses.Sort(); -- cgit v1.2.3 From 7a9a9f6d1a79bdb4858b2eb0f41e9badc55ae8db Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 13 Aug 2018 14:56:08 +0200 Subject: fix tests.json --- src/csharp/tests.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/csharp') diff --git a/src/csharp/tests.json b/src/csharp/tests.json index 483d1d7aad..d06fbb7869 100644 --- a/src/csharp/tests.json +++ b/src/csharp/tests.json @@ -66,11 +66,11 @@ "Grpc.Tools.Tests": [ "Grpc.Tools.Tests.CppGeneratorTests", "Grpc.Tools.Tests.CSharpGeneratorTests", + "Grpc.Tools.Tests.DepFileUtilTests", "Grpc.Tools.Tests.GeneratorTests", "Grpc.Tools.Tests.ProtoCompileBasicTests", "Grpc.Tools.Tests.ProtoCompileCommandLineGeneratorTests", "Grpc.Tools.Tests.ProtoCompileCommandLinePrinterTests", - "Grpc.Tools.Tests.ProtoToolsPlatformTaskTests", - "Grps.Tools.Tests.DepFileUtilTests" + "Grpc.Tools.Tests.ProtoToolsPlatformTaskTests" ] } -- cgit v1.2.3 From 86df93d4e1e4b3d8a6450bf9a19a77c8e7fbaee9 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 13 Aug 2018 15:00:44 +0200 Subject: split up ProtoCompile tests to individual files --- .../Grpc.Tools.Tests/ProtoCompileBasicTests.cs | 70 +++++++ .../ProtoCompileCommandLineGeneratorTests.cs | 162 ++++++++++++++ .../ProtoCompileCommandLinePrinterTests.cs | 48 +++++ .../Grpc.Tools.Tests/ProtoCompileTaskTest.cs | 232 --------------------- 4 files changed, 280 insertions(+), 232 deletions(-) create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs delete mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileTaskTest.cs (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs new file mode 100644 index 0000000000..0660b3e33d --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs @@ -0,0 +1,70 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Reflection; +using Microsoft.Build.Framework; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + public class ProtoCompileBasicTests { + // Mock task class that stops right before invoking protoc. + public class ProtoCompileTestable : ProtoCompile { + public string LastPathToTool { get; private set; } + public string[] LastResponseFile { get; private set; } + + protected override int ExecuteTool(string pathToTool, + string response, + string commandLine) { + // We should never be using command line commands. + Assert.That(commandLine, Is.Null | Is.Empty); + + // Must receive a path to tool + Assert.That(pathToTool, Is.Not.Null & Is.Not.Empty); + Assert.That(response, Is.Not.Null & Does.EndWith("\n")); + + LastPathToTool = pathToTool; + LastResponseFile = response.Remove(response.Length - 1).Split('\n'); + + // Do not run the tool, but pretend it ran successfully. + return 0; + } + }; + + protected Mock _mockEngine; + protected ProtoCompileTestable _task; + + [SetUp] + public void SetUp() { + _mockEngine = new Mock(); + _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()); + } + } +} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs new file mode 100644 index 0000000000..fcdff11da4 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs @@ -0,0 +1,162 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Reflection; +using Microsoft.Build.Framework; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + internal class ProtoCompileCommandLineGeneratorTests : ProtoCompileBasicTests { + [SetUp] + public new void SetUp() { + _task.Generator = "csharp"; + _task.OutputDir = "outdir"; + _task.ProtoBuf = Utils.MakeSimpleItems("a.proto"); + } + + void ExecuteExpectSuccess() { + _mockEngine + .Setup(me => me.LogErrorEvent(It.IsAny())) + .Callback((BuildErrorEventArgs e) => + Assert.Fail($"Error logged by build engine:\n{e.Message}")); + bool result = _task.Execute(); + Assert.IsTrue(result); + } + + [Test] + public void MinimalCompile() { + ExecuteExpectSuccess(); + Assert.That(_task.LastPathToTool, Does.Match(@"protoc(.exe)?$")); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + "--csharp_out=outdir", "a.proto" })); + } + + [Test] + public void CompileTwoFiles() { + _task.ProtoBuf = Utils.MakeSimpleItems("a.proto", "foo/b.proto"); + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + "--csharp_out=outdir", "a.proto", "foo/b.proto" })); + } + + [Test] + public void CompileWithProtoPaths() { + _task.ProtoPath = new[] { "/path1", "/path2" }; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + "--csharp_out=outdir", "--proto_path=/path1", + "--proto_path=/path2", "a.proto" })); + } + + [TestCase("Cpp")] + [TestCase("CSharp")] + [TestCase("Java")] + [TestCase("Javanano")] + [TestCase("Js")] + [TestCase("Objc")] + [TestCase("Php")] + [TestCase("Python")] + [TestCase("Ruby")] + public void CompileWithOptions(string gen) { + _task.Generator = gen; + _task.OutputOptions = new[] { "foo", "bar" }; + ExecuteExpectSuccess(); + gen = gen.ToLowerInvariant(); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + $"--{gen}_out=outdir", $"--{gen}_opt=foo,bar", "a.proto" })); + } + + [Test] + public void OutputDependencyFile() { + _task.DependencyOut = "foo/my.protodep"; + // Task fails trying to read the non-generated file; we ignore that. + _task.Execute(); + Assert.That(_task.LastResponseFile, + Does.Contain("--dependency_out=foo/my.protodep")); + } + + [Test] + public void OutputDependencyWithProtoDepDir() { + _task.ProtoDepDir = "foo"; + // Task fails trying to read the non-generated file; we ignore that. + _task.Execute(); + Assert.That(_task.LastResponseFile, + Has.One.Match(@"^--dependency_out=foo[/\\].+_a.protodep$")); + } + + [Test] + public void GenerateGrpc() { + _task.GrpcPluginExe = "/foo/grpcgen"; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { + "--csharp_out=outdir", "--grpc_out=outdir", + "--plugin=protoc-gen-grpc=/foo/grpcgen" })); + } + + [Test] + public void GenerateGrpcWithOutDir() { + _task.GrpcPluginExe = "/foo/grpcgen"; + _task.GrpcOutputDir = "gen-out"; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { + "--csharp_out=outdir", "--grpc_out=gen-out" })); + } + + [Test] + public void GenerateGrpcWithOptions() { + _task.GrpcPluginExe = "/foo/grpcgen"; + _task.GrpcOutputOptions = new[] { "baz", "quux" }; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, + Does.Contain("--grpc_opt=baz,quux")); + } + + [Test] + public void DirectoryArgumentsSlashTrimmed() { + _task.GrpcPluginExe = "/foo/grpcgen"; + _task.GrpcOutputDir = "gen-out/"; + _task.OutputDir = "outdir/"; + _task.ProtoPath = new[] { "/path1/", "/path2/" }; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { + "--proto_path=/path1", "--proto_path=/path2", + "--csharp_out=outdir", "--grpc_out=gen-out" })); + } + + [TestCase("." , ".")] + [TestCase("/" , "/")] + [TestCase("//" , "/")] + [TestCase("/foo/" , "/foo")] + [TestCase("/foo" , "/foo")] + [TestCase("foo/" , "foo")] + [TestCase("foo//" , "foo")] + [TestCase("foo/\\" , "foo")] + [TestCase("foo\\/" , "foo")] + [TestCase("C:\\foo", "C:\\foo")] + [TestCase("C:" , "C:")] + [TestCase("C:\\" , "C:\\")] + [TestCase("C:\\\\" , "C:\\")] + public void DirectorySlashTrimmingCases(string given, string expect) { + _task.OutputDir = given; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, + Does.Contain("--csharp_out=" + expect)); + } + } +} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs new file mode 100644 index 0000000000..91cfceaf56 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs @@ -0,0 +1,48 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.Reflection; +using Microsoft.Build.Framework; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + internal class ProtoCompileCommandLinePrinterTests : ProtoCompileBasicTests { + [SetUp] + public new void SetUp() { + _task.Generator = "csharp"; + _task.OutputDir = "outdir"; + _task.ProtoBuf = Utils.MakeSimpleItems("a.proto"); + + _mockEngine + .Setup(me => me.LogMessageEvent(It.IsAny())) + .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())) + .Callback((BuildErrorEventArgs e) => + Assert.Fail($"Error logged by build engine:\n{e.Message}")); + bool result = _task.Execute(); + Assert.IsTrue(result); + } + } +} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileTaskTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileTaskTest.cs deleted file mode 100644 index 86c78289b2..0000000000 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileTaskTest.cs +++ /dev/null @@ -1,232 +0,0 @@ -#region Copyright notice and license - -// Copyright 2018 gRPC authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#endregion - -using System.Reflection; -using Microsoft.Build.Framework; -using Moq; -using NUnit.Framework; - -namespace Grpc.Tools.Tests { - public class ProtoCompileBasicTests { - // Mock task class that stops right before invoking protoc. - public class ProtoCompileTestable : ProtoCompile { - public string LastPathToTool { get; private set; } - public string[] LastResponseFile { get; private set; } - - protected override int ExecuteTool(string pathToTool, - string response, - string commandLine) { - // We should never be using command line commands. - Assert.That(commandLine, Is.Null | Is.Empty); - - // Must receive a path to tool - Assert.That(pathToTool, Is.Not.Null & Is.Not.Empty); - Assert.That(response, Is.Not.Null & Does.EndWith("\n")); - - LastPathToTool = pathToTool; - LastResponseFile = response.Remove(response.Length - 1).Split('\n'); - - // Do not run the tool, but pretend it ran successfully. - return 0; - } - }; - - protected Mock _mockEngine; - protected ProtoCompileTestable _task; - - [SetUp] - public void SetUp() { - _mockEngine = new Mock(); - _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()); - } - }; - - internal class ProtoCompileCommandLineGeneratorTests : ProtoCompileBasicTests { - [SetUp] - public new void SetUp() { - _task.Generator = "csharp"; - _task.OutputDir = "outdir"; - _task.ProtoBuf = Utils.MakeSimpleItems("a.proto"); - } - - void ExecuteExpectSuccess() { - _mockEngine - .Setup(me => me.LogErrorEvent(It.IsAny())) - .Callback((BuildErrorEventArgs e) => - Assert.Fail($"Error logged by build engine:\n{e.Message}")); - bool result = _task.Execute(); - Assert.IsTrue(result); - } - - [Test] - public void MinimalCompile() { - ExecuteExpectSuccess(); - Assert.That(_task.LastPathToTool, Does.Match(@"protoc(.exe)?$")); - Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { - "--csharp_out=outdir", "a.proto" })); - } - - [Test] - public void CompileTwoFiles() { - _task.ProtoBuf = Utils.MakeSimpleItems("a.proto", "foo/b.proto"); - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { - "--csharp_out=outdir", "a.proto", "foo/b.proto" })); - } - - [Test] - public void CompileWithProtoPaths() { - _task.ProtoPath = new[] { "/path1", "/path2" }; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { - "--csharp_out=outdir", "--proto_path=/path1", - "--proto_path=/path2", "a.proto" })); - } - - [TestCase("Cpp")] - [TestCase("CSharp")] - [TestCase("Java")] - [TestCase("Javanano")] - [TestCase("Js")] - [TestCase("Objc")] - [TestCase("Php")] - [TestCase("Python")] - [TestCase("Ruby")] - public void CompileWithOptions(string gen) { - _task.Generator = gen; - _task.OutputOptions = new[] { "foo", "bar" }; - ExecuteExpectSuccess(); - gen = gen.ToLowerInvariant(); - Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { - $"--{gen}_out=outdir", $"--{gen}_opt=foo,bar", "a.proto" })); - } - - [Test] - public void OutputDependencyFile() { - _task.DependencyOut = "foo/my.protodep"; - // Task fails trying to read the non-generated file; we ignore that. - _task.Execute(); - Assert.That(_task.LastResponseFile, - Does.Contain("--dependency_out=foo/my.protodep")); - } - - [Test] - public void OutputDependencyWithProtoDepDir() { - _task.ProtoDepDir = "foo"; - // Task fails trying to read the non-generated file; we ignore that. - _task.Execute(); - Assert.That(_task.LastResponseFile, - Has.One.Match(@"^--dependency_out=foo[/\\].+_a.protodep$")); - } - - [Test] - public void GenerateGrpc() { - _task.GrpcPluginExe = "/foo/grpcgen"; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { - "--csharp_out=outdir", "--grpc_out=outdir", - "--plugin=protoc-gen-grpc=/foo/grpcgen" })); - } - - [Test] - public void GenerateGrpcWithOutDir() { - _task.GrpcPluginExe = "/foo/grpcgen"; - _task.GrpcOutputDir = "gen-out"; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { - "--csharp_out=outdir", "--grpc_out=gen-out" })); - } - - [Test] - public void GenerateGrpcWithOptions() { - _task.GrpcPluginExe = "/foo/grpcgen"; - _task.GrpcOutputOptions = new[] { "baz", "quux" }; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, - Does.Contain("--grpc_opt=baz,quux")); - } - - [Test] - public void DirectoryArgumentsSlashTrimmed() { - _task.GrpcPluginExe = "/foo/grpcgen"; - _task.GrpcOutputDir = "gen-out/"; - _task.OutputDir = "outdir/"; - _task.ProtoPath = new[] { "/path1/", "/path2/" }; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { - "--proto_path=/path1", "--proto_path=/path2", - "--csharp_out=outdir", "--grpc_out=gen-out" })); - } - - [TestCase("." , ".")] - [TestCase("/" , "/")] - [TestCase("//" , "/")] - [TestCase("/foo/" , "/foo")] - [TestCase("/foo" , "/foo")] - [TestCase("foo/" , "foo")] - [TestCase("foo//" , "foo")] - [TestCase("foo/\\" , "foo")] - [TestCase("foo\\/" , "foo")] - [TestCase("C:\\foo", "C:\\foo")] - [TestCase("C:" , "C:")] - [TestCase("C:\\" , "C:\\")] - [TestCase("C:\\\\" , "C:\\")] - public void DirectorySlashTrimmingCases(string given, string expect) { - _task.OutputDir = given; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, - Does.Contain("--csharp_out=" + expect)); - } - }; - - internal class ProtoCompileCommandLinePrinterTests : ProtoCompileBasicTests { - [SetUp] - public new void SetUp() { - _task.Generator = "csharp"; - _task.OutputDir = "outdir"; - _task.ProtoBuf = Utils.MakeSimpleItems("a.proto"); - - _mockEngine - .Setup(me => me.LogMessageEvent(It.IsAny())) - .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())) - .Callback((BuildErrorEventArgs e) => - Assert.Fail($"Error logged by build engine:\n{e.Message}")); - bool result = _task.Execute(); - Assert.IsTrue(result); - } - }; -} -- cgit v1.2.3 From 8a98ab97acaa831e9e4dc257ac4dbd0ef10b3203 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 13 Aug 2018 15:01:31 +0200 Subject: fix typo in DepFileUtilTests --- src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs b/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs index 0ea621adea..42d0ae5998 100644 --- a/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs +++ b/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs @@ -23,7 +23,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using NUnit.Framework; -namespace Grps.Tools.Tests { +namespace Grpc.Tools.Tests { public class DepFileUtilTests { [Test] -- cgit v1.2.3 From 5f4dfaac9e8ad175cbd5ef074b08f087cc17a610 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 13 Aug 2018 15:02:40 +0200 Subject: split up GeneratorTests into individual files --- .../Grpc.Tools.Tests/CSharpGeneratorTests.cs | 82 +++++++++++++++ src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs | 83 +++++++++++++++ src/csharp/Grpc.Tools.Tests/GeneratorTests.cs | 116 +-------------------- 3 files changed, 166 insertions(+), 115 deletions(-) create mode 100644 src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs new file mode 100644 index 0000000000..dbffa65d8c --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs @@ -0,0 +1,82 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + + public class CSharpGeneratorTests : GeneratorTests { + GeneratorServices _generator; + + [SetUp] + public new void SetUp() { + _generator = GeneratorServices.GetForLanguage("CSharp", _log); + } + + [TestCase("foo.proto", "Foo.cs", "FooGrpc.cs")] + [TestCase("sub/foo.proto", "Foo.cs", "FooGrpc.cs")] + [TestCase("one_two.proto", "OneTwo.cs", "OneTwoGrpc.cs")] + [TestCase("__one_two!.proto", "OneTwo!.cs", "OneTwo!Grpc.cs")] + [TestCase("one(two).proto", "One(two).cs", "One(two)Grpc.cs")] + [TestCase("one_(two).proto", "One(two).cs", "One(two)Grpc.cs")] + [TestCase("one two.proto", "One two.cs", "One twoGrpc.cs")] + [TestCase("one_ two.proto", "One two.cs", "One twoGrpc.cs")] + [TestCase("one .proto", "One .cs", "One Grpc.cs")] + public void NameMangling(string proto, string expectCs, string expectGrpcCs) { + var poss = _generator.GetPossibleOutputs(Utils.MakeItem(proto, "grpcservices", "both")); + Assert.AreEqual(2, poss.Length); + Assert.Contains(expectCs, poss); + Assert.Contains(expectGrpcCs, poss); + } + + [Test] + public void NoGrpcOneOutput() { + var poss = _generator.GetPossibleOutputs(Utils.MakeItem("foo.proto")); + Assert.AreEqual(1, poss.Length); + } + + [TestCase("none")] + [TestCase("")] + public void GrpcNoneOneOutput(string grpc) { + var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(1, poss.Length); + } + + [TestCase("client")] + [TestCase("server")] + [TestCase("both")] + public void GrpcEnabledTwoOutputs(string grpc) { + var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(2, poss.Length); + } + + [Test] + public void OutputDirMetadataRecognized() { + var item = Utils.MakeItem("foo.proto", "OutputDir", "out"); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(1, poss.Length); + Assert.That(poss[0], Is.EqualTo("out/Foo.cs") | Is.EqualTo("out\\Foo.cs")); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs new file mode 100644 index 0000000000..e25a6498cd --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs @@ -0,0 +1,83 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + public class CppGeneratorTests : GeneratorTests { + GeneratorServices _generator; + + [SetUp] + public new void SetUp() { + _generator = GeneratorServices.GetForLanguage("Cpp", _log); + } + + [TestCase("foo.proto", "", "foo")] + [TestCase("foo.proto", ".", "foo")] + [TestCase("foo.proto", "./", "foo")] + [TestCase("sub/foo.proto", "", "sub/foo")] + [TestCase("root/sub/foo.proto", "root", "sub/foo")] + [TestCase("root/sub/foo.proto", "root", "sub/foo")] + [TestCase("/root/sub/foo.proto", "/root", "sub/foo")] + public void RelativeDirectoryCompute(string proto, string root, string expectStem) { + if (Path.DirectorySeparatorChar == '\\') + expectStem = expectStem.Replace('/', '\\'); + var poss = _generator.GetPossibleOutputs(Utils.MakeItem(proto, "ProtoRoot", root)); + Assert.AreEqual(2, poss.Length); + Assert.Contains(expectStem + ".pb.cc", poss); + Assert.Contains(expectStem + ".pb.h", poss); + } + + [Test] + public void NoGrpcTwoOutputs() { + var poss = _generator.GetPossibleOutputs(Utils.MakeItem("foo.proto")); + Assert.AreEqual(2, poss.Length); + } + + [TestCase("false")] + [TestCase("")] + public void GrpcDisabledTwoOutput(string grpc) { + var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(2, poss.Length); + } + + [TestCase("true")] + public void GrpcEnabledFourOutputs(string grpc) { + var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(4, poss.Length); + Assert.Contains("foo.pb.cc", poss); + Assert.Contains("foo.pb.h", poss); + Assert.Contains("foo_grpc.pb.cc", poss); + Assert.Contains("foo_grpc.pb.h", poss); + } + + [Test] + public void OutputDirMetadataRecognized() { + var item = Utils.MakeItem("foo.proto", "OutputDir", "out"); + var poss = _generator.GetPossibleOutputs(item); + Assert.AreEqual(2, poss.Length); + Assert.That(Path.GetDirectoryName(poss[0]), Is.EqualTo("out")); + } + } +} diff --git a/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs index 0a273380b9..93df9d6cab 100644 --- a/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs +++ b/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs @@ -47,119 +47,5 @@ namespace Grpc.Tools.Tests { Assert.IsNull(GeneratorServices.GetForLanguage(lang, _log)); _mockEngine.Verify(me => me.LogErrorEvent(It.IsAny()), Times.Once); } - }; - - public class CSharpGeneratorTests : GeneratorTests { - GeneratorServices _generator; - - [SetUp] - public new void SetUp() { - _generator = GeneratorServices.GetForLanguage("CSharp", _log); - } - - [TestCase("foo.proto", "Foo.cs", "FooGrpc.cs")] - [TestCase("sub/foo.proto", "Foo.cs", "FooGrpc.cs")] - [TestCase("one_two.proto", "OneTwo.cs", "OneTwoGrpc.cs")] - [TestCase("__one_two!.proto", "OneTwo!.cs", "OneTwo!Grpc.cs")] - [TestCase("one(two).proto", "One(two).cs", "One(two)Grpc.cs")] - [TestCase("one_(two).proto", "One(two).cs", "One(two)Grpc.cs")] - [TestCase("one two.proto", "One two.cs", "One twoGrpc.cs")] - [TestCase("one_ two.proto", "One two.cs", "One twoGrpc.cs")] - [TestCase("one .proto", "One .cs", "One Grpc.cs")] - public void NameMangling(string proto, string expectCs, string expectGrpcCs) { - var poss = _generator.GetPossibleOutputs(Utils.MakeItem(proto, "grpcservices", "both")); - Assert.AreEqual(2, poss.Length); - Assert.Contains(expectCs, poss); - Assert.Contains(expectGrpcCs, poss); - } - - [Test] - public void NoGrpcOneOutput() { - var poss = _generator.GetPossibleOutputs(Utils.MakeItem("foo.proto")); - Assert.AreEqual(1, poss.Length); - } - - [TestCase("none")] - [TestCase("")] - public void GrpcNoneOneOutput(string grpc) { - var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); - var poss = _generator.GetPossibleOutputs(item); - Assert.AreEqual(1, poss.Length); - } - - [TestCase("client")] - [TestCase("server")] - [TestCase("both")] - public void GrpcEnabledTwoOutputs(string grpc) { - var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); - var poss = _generator.GetPossibleOutputs(item); - Assert.AreEqual(2, poss.Length); - } - - [Test] - public void OutputDirMetadataRecognized() { - var item = Utils.MakeItem("foo.proto", "OutputDir", "out"); - var poss = _generator.GetPossibleOutputs(item); - Assert.AreEqual(1, poss.Length); - Assert.That(poss[0], Is.EqualTo("out/Foo.cs") | Is.EqualTo("out\\Foo.cs")); - } - }; - - public class CppGeneratorTests : GeneratorTests { - GeneratorServices _generator; - - [SetUp] - public new void SetUp() { - _generator = GeneratorServices.GetForLanguage("Cpp", _log); - } - - [TestCase("foo.proto", "", "foo")] - [TestCase("foo.proto", ".", "foo")] - [TestCase("foo.proto", "./", "foo")] - [TestCase("sub/foo.proto", "", "sub/foo")] - [TestCase("root/sub/foo.proto", "root", "sub/foo")] - [TestCase("root/sub/foo.proto", "root", "sub/foo")] - [TestCase("/root/sub/foo.proto", "/root", "sub/foo")] - public void RelativeDirectoryCompute(string proto, string root, string expectStem) { - if (Path.DirectorySeparatorChar == '\\') - expectStem = expectStem.Replace('/', '\\'); - var poss = _generator.GetPossibleOutputs(Utils.MakeItem(proto, "ProtoRoot", root)); - Assert.AreEqual(2, poss.Length); - Assert.Contains(expectStem + ".pb.cc", poss); - Assert.Contains(expectStem + ".pb.h", poss); - } - - [Test] - public void NoGrpcTwoOutputs() { - var poss = _generator.GetPossibleOutputs(Utils.MakeItem("foo.proto")); - Assert.AreEqual(2, poss.Length); - } - - [TestCase("false")] - [TestCase("")] - public void GrpcDisabledTwoOutput(string grpc) { - var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); - var poss = _generator.GetPossibleOutputs(item); - Assert.AreEqual(2, poss.Length); - } - - [TestCase("true")] - public void GrpcEnabledFourOutputs(string grpc) { - var item = Utils.MakeItem("foo.proto", "grpcservices", grpc); - var poss = _generator.GetPossibleOutputs(item); - Assert.AreEqual(4, poss.Length); - Assert.Contains("foo.pb.cc", poss); - Assert.Contains("foo.pb.h", poss); - Assert.Contains("foo_grpc.pb.cc", poss); - Assert.Contains("foo_grpc.pb.h", poss); - } - - [Test] - public void OutputDirMetadataRecognized() { - var item = Utils.MakeItem("foo.proto", "OutputDir", "out"); - var poss = _generator.GetPossibleOutputs(item); - Assert.AreEqual(2, poss.Length); - Assert.That(Path.GetDirectoryName(poss[0]), Is.EqualTo("out")); - } - }; + } } -- cgit v1.2.3 From e3e7e32a7e1698c7937e5bf0bde4914e13131362 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Mon, 13 Aug 2018 15:55:54 +0200 Subject: sync nunit version for all test projects --- src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj | 4 ++-- src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj | 4 ++-- src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj | 4 ++-- src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj | 4 ++-- src/csharp/Grpc.Reflection.Tests/Grpc.Reflection.Tests.csproj | 4 ++-- src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj | 3 ++- 6 files changed, 12 insertions(+), 11 deletions(-) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj index 18993a93e0..d58f046824 100755 --- a/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj +++ b/src/csharp/Grpc.Core.Tests/Grpc.Core.Tests.csproj @@ -17,8 +17,8 @@ - - + + diff --git a/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj b/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj index d2cc5bbc65..7493eb8051 100755 --- a/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj +++ b/src/csharp/Grpc.Examples.Tests/Grpc.Examples.Tests.csproj @@ -17,8 +17,8 @@ - - + + diff --git a/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj b/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj index 9da0539dcb..616e56df10 100755 --- a/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj +++ b/src/csharp/Grpc.HealthCheck.Tests/Grpc.HealthCheck.Tests.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj index e4f36d8810..ad7033b782 100755 --- a/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj +++ b/src/csharp/Grpc.IntegrationTesting/Grpc.IntegrationTesting.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/src/csharp/Grpc.Reflection.Tests/Grpc.Reflection.Tests.csproj b/src/csharp/Grpc.Reflection.Tests/Grpc.Reflection.Tests.csproj index d368697124..0c12f38f25 100755 --- a/src/csharp/Grpc.Reflection.Tests/Grpc.Reflection.Tests.csproj +++ b/src/csharp/Grpc.Reflection.Tests/Grpc.Reflection.Tests.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj index 7c1a6afe34..ab14f32afd 100644 --- a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj +++ b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj @@ -27,7 +27,8 @@ - + + -- cgit v1.2.3 From a844323c7e96d7a525b963769c2a8e7db5ea4b61 Mon Sep 17 00:00:00 2001 From: kkm Date: Sun, 7 Oct 2018 00:39:02 -0700 Subject: Rename test classes *Test; UWYU in Tools.Test project --- .../Grpc.Tools.Tests/CSharpGeneratorTests.cs | 7 +-- src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs | 7 +-- src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs | 6 +-- src/csharp/Grpc.Tools.Tests/GeneratorTests.cs | 5 +- src/csharp/Grpc.Tools.Tests/NUnitMain.cs | 2 +- .../Grpc.Tools.Tests/ProtoCompileBasicTests.cs | 6 +-- .../ProtoCompileCommandLineGeneratorTests.cs | 5 +- .../ProtoCompileCommandLinePrinterTests.cs | 5 +- .../Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs | 53 ---------------------- .../ProtoToolsPlatformTaskTests.cs | 53 ++++++++++++++++++++++ src/csharp/Grpc.Tools.Tests/Utils.cs | 3 -- src/csharp/tests.json | 16 +++---- 12 files changed, 76 insertions(+), 92 deletions(-) delete mode 100644 src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTests.cs (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs index dbffa65d8c..654500d53d 100644 --- a/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs +++ b/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs @@ -16,15 +16,10 @@ #endregion -using System.IO; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Moq; using NUnit.Framework; namespace Grpc.Tools.Tests { - - public class CSharpGeneratorTests : GeneratorTests { + public class CSharpGeneratorTest : GeneratorTest { GeneratorServices _generator; [SetUp] diff --git a/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs index e25a6498cd..a3450fae17 100644 --- a/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs +++ b/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs @@ -17,13 +17,10 @@ #endregion using System.IO; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using Moq; using NUnit.Framework; namespace Grpc.Tools.Tests { - public class CppGeneratorTests : GeneratorTests { + public class CppGeneratorTest : GeneratorTest { GeneratorServices _generator; [SetUp] @@ -79,5 +76,5 @@ namespace Grpc.Tools.Tests { Assert.AreEqual(2, poss.Length); Assert.That(Path.GetDirectoryName(poss[0]), Is.EqualTo("out")); } - } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs b/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs index 42d0ae5998..ea34c89921 100644 --- a/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs +++ b/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs @@ -16,15 +16,13 @@ #endregion -using System; using System.IO; -using Grpc.Tools; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using NUnit.Framework; namespace Grpc.Tools.Tests { - public class DepFileUtilTests { + public class DepFileUtilTest { [Test] public void HashString64Hex_IsSane() { @@ -130,5 +128,5 @@ obj\Release x64\net45\/FooGrpc.cs: C:/usr/include/google/protobuf/wrappers.proto } catch { } } } - } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs index 93df9d6cab..52fab1d8ca 100644 --- a/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs +++ b/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs @@ -16,14 +16,13 @@ #endregion -using System.IO; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Moq; using NUnit.Framework; namespace Grpc.Tools.Tests { - public class GeneratorTests { + public class GeneratorTest { protected Mock _mockEngine; protected TaskLoggingHelper _log; @@ -47,5 +46,5 @@ namespace Grpc.Tools.Tests { Assert.IsNull(GeneratorServices.GetForLanguage(lang, _log)); _mockEngine.Verify(me => me.LogErrorEvent(It.IsAny()), Times.Once); } - } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/NUnitMain.cs b/src/csharp/Grpc.Tools.Tests/NUnitMain.cs index c4452c50d2..5a8ae8cf87 100644 --- a/src/csharp/Grpc.Tools.Tests/NUnitMain.cs +++ b/src/csharp/Grpc.Tools.Tests/NUnitMain.cs @@ -19,7 +19,7 @@ using System.Reflection; using NUnitLite; -namespace Grps.Tools.Tests { +namespace Grpc.Tools.Tests { static class NUnitMain { public static int Main(string[] args) => #if NETCOREAPP1_0 diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs index 0660b3e33d..cf9d210424 100644 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs @@ -16,13 +16,13 @@ #endregion -using System.Reflection; +using System.Reflection; // UWYU: Object.GetType() extension. using Microsoft.Build.Framework; using Moq; using NUnit.Framework; namespace Grpc.Tools.Tests { - public class ProtoCompileBasicTests { + public class ProtoCompileBasicTest { // Mock task class that stops right before invoking protoc. public class ProtoCompileTestable : ProtoCompile { public string LastPathToTool { get; private set; } @@ -66,5 +66,5 @@ namespace Grpc.Tools.Tests { Assert.NotNull(pinfo); Assert.That(pinfo, Has.Attribute()); } - } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs index fcdff11da4..c5d43f08d9 100644 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs @@ -16,13 +16,12 @@ #endregion -using System.Reflection; using Microsoft.Build.Framework; using Moq; using NUnit.Framework; namespace Grpc.Tools.Tests { - internal class ProtoCompileCommandLineGeneratorTests : ProtoCompileBasicTests { + public class ProtoCompileCommandLineGeneratorTest : ProtoCompileBasicTest { [SetUp] public new void SetUp() { _task.Generator = "csharp"; @@ -158,5 +157,5 @@ namespace Grpc.Tools.Tests { Assert.That(_task.LastResponseFile, Does.Contain("--csharp_out=" + expect)); } - } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs index 91cfceaf56..a0406371dc 100644 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs @@ -16,13 +16,12 @@ #endregion -using System.Reflection; using Microsoft.Build.Framework; using Moq; using NUnit.Framework; namespace Grpc.Tools.Tests { - internal class ProtoCompileCommandLinePrinterTests : ProtoCompileBasicTests { + public class ProtoCompileCommandLinePrinterTest : ProtoCompileBasicTest { [SetUp] public new void SetUp() { _task.Generator = "csharp"; @@ -44,5 +43,5 @@ namespace Grpc.Tools.Tests { bool result = _task.Execute(); Assert.IsTrue(result); } - } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs deleted file mode 100644 index 3ba0bdfbf6..0000000000 --- a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs +++ /dev/null @@ -1,53 +0,0 @@ -#region Copyright notice and license - -// Copyright 2018 gRPC authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#endregion - -using System; -using Microsoft.Build.Framework; -using Moq; -using NUnit.Framework; - -namespace Grpc.Tools.Tests { - // This test requires that environment variables be set to the exected - // output of the task in its external test harness: - // PROTOTOOLS_TEST_CPU = { x64 | x86 } - // PROTOTOOLS_TEST_OS = { linux | macosx | windows } - public class ProtoToolsPlatformTaskTests { - static string s_expectOs; - static string s_expectCpu; - - [OneTimeSetUp] - public static void Init() { - s_expectCpu = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_CPU"); - s_expectOs = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_OS"); - if (s_expectCpu == null || s_expectOs == null) - Assert.Inconclusive("This test requires PROTOTOOLS_TEST_CPU and " + - "PROTOTOOLS_TEST_OS set in the environment to match the OS it runs on."); - } - - [Test] - public void CpuAndOsMatchExpected() { - var mockEng = new Mock(); - var task = new ProtoToolsPlatform() { - BuildEngine = mockEng.Object - }; - task.Execute(); - Assert.AreEqual(s_expectCpu, task.Cpu); - Assert.AreEqual(s_expectOs, task.Os); - } - }; -} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTests.cs new file mode 100644 index 0000000000..235f8de527 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTests.cs @@ -0,0 +1,53 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using Microsoft.Build.Framework; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + // This test requires that environment variables be set to the expected + // output of the task in its external test harness: + // PROTOTOOLS_TEST_CPU = { x64 | x86 } + // PROTOTOOLS_TEST_OS = { linux | macosx | windows } + public class ProtoToolsPlatformTaskTest { + static string s_expectOs; + static string s_expectCpu; + + [OneTimeSetUp] + public static void Init() { + s_expectCpu = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_CPU"); + s_expectOs = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_OS"); + if (s_expectCpu == null || s_expectOs == null) + Assert.Inconclusive("This test requires PROTOTOOLS_TEST_CPU and " + + "PROTOTOOLS_TEST_OS set in the environment to match the OS it runs on."); + } + + [Test] + public void CpuAndOsMatchExpected() { + var mockEng = new Mock(); + var task = new ProtoToolsPlatform() { + BuildEngine = mockEng.Object + }; + task.Execute(); + Assert.AreEqual(s_expectCpu, task.Cpu); + Assert.AreEqual(s_expectOs, task.Os); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/Utils.cs b/src/csharp/Grpc.Tools.Tests/Utils.cs index 618e335452..bb051a4873 100644 --- a/src/csharp/Grpc.Tools.Tests/Utils.cs +++ b/src/csharp/Grpc.Tools.Tests/Utils.cs @@ -16,10 +16,7 @@ #endregion -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; diff --git a/src/csharp/tests.json b/src/csharp/tests.json index 761308b33f..760776f9e7 100644 --- a/src/csharp/tests.json +++ b/src/csharp/tests.json @@ -66,13 +66,13 @@ "Grpc.Reflection.Tests.SymbolRegistryTest" ], "Grpc.Tools.Tests": [ - "Grpc.Tools.Tests.CppGeneratorTests", - "Grpc.Tools.Tests.CSharpGeneratorTests", - "Grpc.Tools.Tests.DepFileUtilTests", - "Grpc.Tools.Tests.GeneratorTests", - "Grpc.Tools.Tests.ProtoCompileBasicTests", - "Grpc.Tools.Tests.ProtoCompileCommandLineGeneratorTests", - "Grpc.Tools.Tests.ProtoCompileCommandLinePrinterTests", - "Grpc.Tools.Tests.ProtoToolsPlatformTaskTests" + "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" ] } -- cgit v1.2.3 From 716b5577fc7c744bb85730d134366f17f71ea00e Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Tue, 9 Oct 2018 13:09:41 +0200 Subject: fixup: rename *Tests files to *Test --- src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs | 77 ++++++++++ .../Grpc.Tools.Tests/CSharpGeneratorTests.cs | 77 ---------- src/csharp/Grpc.Tools.Tests/CppGeneratorTest.cs | 80 ++++++++++ src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs | 80 ---------- src/csharp/Grpc.Tools.Tests/DepFileUtilTest.cs | 132 +++++++++++++++++ src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs | 132 ----------------- src/csharp/Grpc.Tools.Tests/GeneratorTest.cs | 50 +++++++ src/csharp/Grpc.Tools.Tests/GeneratorTests.cs | 50 ------- .../Grpc.Tools.Tests/ProtoCompileBasicTest.cs | 70 +++++++++ .../Grpc.Tools.Tests/ProtoCompileBasicTests.cs | 70 --------- .../ProtoCompileCommandLineGeneratorTest.cs | 161 +++++++++++++++++++++ .../ProtoCompileCommandLineGeneratorTests.cs | 161 --------------------- .../ProtoCompileCommandLinePrinterTest.cs | 47 ++++++ .../ProtoCompileCommandLinePrinterTests.cs | 47 ------ .../Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs | 53 +++++++ .../ProtoToolsPlatformTaskTests.cs | 53 ------- 16 files changed, 670 insertions(+), 670 deletions(-) create mode 100644 src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs delete mode 100644 src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/CppGeneratorTest.cs delete mode 100644 src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/DepFileUtilTest.cs delete mode 100644 src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/GeneratorTest.cs delete mode 100644 src/csharp/Grpc.Tools.Tests/GeneratorTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTest.cs delete mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs delete mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTest.cs delete mode 100644 src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs create mode 100644 src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs delete mode 100644 src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTests.cs (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs b/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs new file mode 100644 index 0000000000..654500d53d --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs @@ -0,0 +1,77 @@ +#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")); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs deleted file mode 100644 index 654500d53d..0000000000 --- a/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -#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")); - } - }; -} diff --git a/src/csharp/Grpc.Tools.Tests/CppGeneratorTest.cs b/src/csharp/Grpc.Tools.Tests/CppGeneratorTest.cs new file mode 100644 index 0000000000..a3450fae17 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/CppGeneratorTest.cs @@ -0,0 +1,80 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.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")); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs deleted file mode 100644 index a3450fae17..0000000000 --- a/src/csharp/Grpc.Tools.Tests/CppGeneratorTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -#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")); - } - }; -} diff --git a/src/csharp/Grpc.Tools.Tests/DepFileUtilTest.cs b/src/csharp/Grpc.Tools.Tests/DepFileUtilTest.cs new file mode 100644 index 0000000000..ea34c89921 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/DepFileUtilTest.cs @@ -0,0 +1,132 @@ +#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(); + var log = new TaskLoggingHelper(mockEng.Object, "x"); + return DepFileUtil.ReadDependencyInputs(tempPath, protoName, log); + } finally { + try { + File.Delete(tempfile); + } catch { } + } + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs b/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs deleted file mode 100644 index ea34c89921..0000000000 --- a/src/csharp/Grpc.Tools.Tests/DepFileUtilTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -#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(); - var log = new TaskLoggingHelper(mockEng.Object, "x"); - return DepFileUtil.ReadDependencyInputs(tempPath, protoName, log); - } finally { - try { - File.Delete(tempfile); - } catch { } - } - } - }; -} diff --git a/src/csharp/Grpc.Tools.Tests/GeneratorTest.cs b/src/csharp/Grpc.Tools.Tests/GeneratorTest.cs new file mode 100644 index 0000000000..52fab1d8ca --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/GeneratorTest.cs @@ -0,0 +1,50 @@ +#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 _mockEngine; + protected TaskLoggingHelper _log; + + [SetUp] + public void SetUp() { + _mockEngine = new Mock(); + _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()), Times.Never); + } + + [TestCase("")] + [TestCase("COBOL")] + public void InvalidLanguages(string lang) { + Assert.IsNull(GeneratorServices.GetForLanguage(lang, _log)); + _mockEngine.Verify(me => me.LogErrorEvent(It.IsAny()), Times.Once); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs deleted file mode 100644 index 52fab1d8ca..0000000000 --- a/src/csharp/Grpc.Tools.Tests/GeneratorTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -#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 _mockEngine; - protected TaskLoggingHelper _log; - - [SetUp] - public void SetUp() { - _mockEngine = new Mock(); - _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()), Times.Never); - } - - [TestCase("")] - [TestCase("COBOL")] - public void InvalidLanguages(string lang) { - Assert.IsNull(GeneratorServices.GetForLanguage(lang, _log)); - _mockEngine.Verify(me => me.LogErrorEvent(It.IsAny()), Times.Once); - } - }; -} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTest.cs new file mode 100644 index 0000000000..cf9d210424 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTest.cs @@ -0,0 +1,70 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System.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 _mockEngine; + protected ProtoCompileTestable _task; + + [SetUp] + public void SetUp() { + _mockEngine = new Mock(); + _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()); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs deleted file mode 100644 index cf9d210424..0000000000 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -#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 _mockEngine; - protected ProtoCompileTestable _task; - - [SetUp] - public void SetUp() { - _mockEngine = new Mock(); - _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()); - } - }; -} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs new file mode 100644 index 0000000000..c5d43f08d9 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs @@ -0,0 +1,161 @@ +#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 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())) + .Callback((BuildErrorEventArgs e) => + Assert.Fail($"Error logged by build engine:\n{e.Message}")); + bool result = _task.Execute(); + Assert.IsTrue(result); + } + + [Test] + public void MinimalCompile() { + ExecuteExpectSuccess(); + Assert.That(_task.LastPathToTool, Does.Match(@"protoc(.exe)?$")); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + "--csharp_out=outdir", "a.proto" })); + } + + [Test] + public void CompileTwoFiles() { + _task.ProtoBuf = Utils.MakeSimpleItems("a.proto", "foo/b.proto"); + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + "--csharp_out=outdir", "a.proto", "foo/b.proto" })); + } + + [Test] + public void CompileWithProtoPaths() { + _task.ProtoPath = new[] { "/path1", "/path2" }; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + "--csharp_out=outdir", "--proto_path=/path1", + "--proto_path=/path2", "a.proto" })); + } + + [TestCase("Cpp")] + [TestCase("CSharp")] + [TestCase("Java")] + [TestCase("Javanano")] + [TestCase("Js")] + [TestCase("Objc")] + [TestCase("Php")] + [TestCase("Python")] + [TestCase("Ruby")] + public void CompileWithOptions(string gen) { + _task.Generator = gen; + _task.OutputOptions = new[] { "foo", "bar" }; + ExecuteExpectSuccess(); + gen = gen.ToLowerInvariant(); + Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { + $"--{gen}_out=outdir", $"--{gen}_opt=foo,bar", "a.proto" })); + } + + [Test] + public void OutputDependencyFile() { + _task.DependencyOut = "foo/my.protodep"; + // Task fails trying to read the non-generated file; we ignore that. + _task.Execute(); + Assert.That(_task.LastResponseFile, + Does.Contain("--dependency_out=foo/my.protodep")); + } + + [Test] + public void OutputDependencyWithProtoDepDir() { + _task.ProtoDepDir = "foo"; + // Task fails trying to read the non-generated file; we ignore that. + _task.Execute(); + Assert.That(_task.LastResponseFile, + Has.One.Match(@"^--dependency_out=foo[/\\].+_a.protodep$")); + } + + [Test] + public void GenerateGrpc() { + _task.GrpcPluginExe = "/foo/grpcgen"; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { + "--csharp_out=outdir", "--grpc_out=outdir", + "--plugin=protoc-gen-grpc=/foo/grpcgen" })); + } + + [Test] + public void GenerateGrpcWithOutDir() { + _task.GrpcPluginExe = "/foo/grpcgen"; + _task.GrpcOutputDir = "gen-out"; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { + "--csharp_out=outdir", "--grpc_out=gen-out" })); + } + + [Test] + public void GenerateGrpcWithOptions() { + _task.GrpcPluginExe = "/foo/grpcgen"; + _task.GrpcOutputOptions = new[] { "baz", "quux" }; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, + Does.Contain("--grpc_opt=baz,quux")); + } + + [Test] + public void DirectoryArgumentsSlashTrimmed() { + _task.GrpcPluginExe = "/foo/grpcgen"; + _task.GrpcOutputDir = "gen-out/"; + _task.OutputDir = "outdir/"; + _task.ProtoPath = new[] { "/path1/", "/path2/" }; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { + "--proto_path=/path1", "--proto_path=/path2", + "--csharp_out=outdir", "--grpc_out=gen-out" })); + } + + [TestCase("." , ".")] + [TestCase("/" , "/")] + [TestCase("//" , "/")] + [TestCase("/foo/" , "/foo")] + [TestCase("/foo" , "/foo")] + [TestCase("foo/" , "foo")] + [TestCase("foo//" , "foo")] + [TestCase("foo/\\" , "foo")] + [TestCase("foo\\/" , "foo")] + [TestCase("C:\\foo", "C:\\foo")] + [TestCase("C:" , "C:")] + [TestCase("C:\\" , "C:\\")] + [TestCase("C:\\\\" , "C:\\")] + public void DirectorySlashTrimmingCases(string given, string expect) { + _task.OutputDir = given; + ExecuteExpectSuccess(); + Assert.That(_task.LastResponseFile, + Does.Contain("--csharp_out=" + expect)); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs deleted file mode 100644 index c5d43f08d9..0000000000 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTests.cs +++ /dev/null @@ -1,161 +0,0 @@ -#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 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())) - .Callback((BuildErrorEventArgs e) => - Assert.Fail($"Error logged by build engine:\n{e.Message}")); - bool result = _task.Execute(); - Assert.IsTrue(result); - } - - [Test] - public void MinimalCompile() { - ExecuteExpectSuccess(); - Assert.That(_task.LastPathToTool, Does.Match(@"protoc(.exe)?$")); - Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { - "--csharp_out=outdir", "a.proto" })); - } - - [Test] - public void CompileTwoFiles() { - _task.ProtoBuf = Utils.MakeSimpleItems("a.proto", "foo/b.proto"); - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { - "--csharp_out=outdir", "a.proto", "foo/b.proto" })); - } - - [Test] - public void CompileWithProtoPaths() { - _task.ProtoPath = new[] { "/path1", "/path2" }; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { - "--csharp_out=outdir", "--proto_path=/path1", - "--proto_path=/path2", "a.proto" })); - } - - [TestCase("Cpp")] - [TestCase("CSharp")] - [TestCase("Java")] - [TestCase("Javanano")] - [TestCase("Js")] - [TestCase("Objc")] - [TestCase("Php")] - [TestCase("Python")] - [TestCase("Ruby")] - public void CompileWithOptions(string gen) { - _task.Generator = gen; - _task.OutputOptions = new[] { "foo", "bar" }; - ExecuteExpectSuccess(); - gen = gen.ToLowerInvariant(); - Assert.That(_task.LastResponseFile, Is.EqualTo(new[] { - $"--{gen}_out=outdir", $"--{gen}_opt=foo,bar", "a.proto" })); - } - - [Test] - public void OutputDependencyFile() { - _task.DependencyOut = "foo/my.protodep"; - // Task fails trying to read the non-generated file; we ignore that. - _task.Execute(); - Assert.That(_task.LastResponseFile, - Does.Contain("--dependency_out=foo/my.protodep")); - } - - [Test] - public void OutputDependencyWithProtoDepDir() { - _task.ProtoDepDir = "foo"; - // Task fails trying to read the non-generated file; we ignore that. - _task.Execute(); - Assert.That(_task.LastResponseFile, - Has.One.Match(@"^--dependency_out=foo[/\\].+_a.protodep$")); - } - - [Test] - public void GenerateGrpc() { - _task.GrpcPluginExe = "/foo/grpcgen"; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { - "--csharp_out=outdir", "--grpc_out=outdir", - "--plugin=protoc-gen-grpc=/foo/grpcgen" })); - } - - [Test] - public void GenerateGrpcWithOutDir() { - _task.GrpcPluginExe = "/foo/grpcgen"; - _task.GrpcOutputDir = "gen-out"; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { - "--csharp_out=outdir", "--grpc_out=gen-out" })); - } - - [Test] - public void GenerateGrpcWithOptions() { - _task.GrpcPluginExe = "/foo/grpcgen"; - _task.GrpcOutputOptions = new[] { "baz", "quux" }; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, - Does.Contain("--grpc_opt=baz,quux")); - } - - [Test] - public void DirectoryArgumentsSlashTrimmed() { - _task.GrpcPluginExe = "/foo/grpcgen"; - _task.GrpcOutputDir = "gen-out/"; - _task.OutputDir = "outdir/"; - _task.ProtoPath = new[] { "/path1/", "/path2/" }; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, Is.SupersetOf(new[] { - "--proto_path=/path1", "--proto_path=/path2", - "--csharp_out=outdir", "--grpc_out=gen-out" })); - } - - [TestCase("." , ".")] - [TestCase("/" , "/")] - [TestCase("//" , "/")] - [TestCase("/foo/" , "/foo")] - [TestCase("/foo" , "/foo")] - [TestCase("foo/" , "foo")] - [TestCase("foo//" , "foo")] - [TestCase("foo/\\" , "foo")] - [TestCase("foo\\/" , "foo")] - [TestCase("C:\\foo", "C:\\foo")] - [TestCase("C:" , "C:")] - [TestCase("C:\\" , "C:\\")] - [TestCase("C:\\\\" , "C:\\")] - public void DirectorySlashTrimmingCases(string given, string expect) { - _task.OutputDir = given; - ExecuteExpectSuccess(); - Assert.That(_task.LastResponseFile, - Does.Contain("--csharp_out=" + expect)); - } - }; -} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTest.cs new file mode 100644 index 0000000000..a0406371dc --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTest.cs @@ -0,0 +1,47 @@ +#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())) + .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())) + .Callback((BuildErrorEventArgs e) => + Assert.Fail($"Error logged by build engine:\n{e.Message}")); + bool result = _task.Execute(); + Assert.IsTrue(result); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs deleted file mode 100644 index a0406371dc..0000000000 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTests.cs +++ /dev/null @@ -1,47 +0,0 @@ -#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())) - .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())) - .Callback((BuildErrorEventArgs e) => - Assert.Fail($"Error logged by build engine:\n{e.Message}")); - bool result = _task.Execute(); - Assert.IsTrue(result); - } - }; -} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs new file mode 100644 index 0000000000..235f8de527 --- /dev/null +++ b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs @@ -0,0 +1,53 @@ +#region Copyright notice and license + +// Copyright 2018 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +using System; +using Microsoft.Build.Framework; +using Moq; +using NUnit.Framework; + +namespace Grpc.Tools.Tests { + // This test requires that environment variables be set to the expected + // output of the task in its external test harness: + // PROTOTOOLS_TEST_CPU = { x64 | x86 } + // PROTOTOOLS_TEST_OS = { linux | macosx | windows } + public class ProtoToolsPlatformTaskTest { + static string s_expectOs; + static string s_expectCpu; + + [OneTimeSetUp] + public static void Init() { + s_expectCpu = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_CPU"); + s_expectOs = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_OS"); + if (s_expectCpu == null || s_expectOs == null) + Assert.Inconclusive("This test requires PROTOTOOLS_TEST_CPU and " + + "PROTOTOOLS_TEST_OS set in the environment to match the OS it runs on."); + } + + [Test] + public void CpuAndOsMatchExpected() { + var mockEng = new Mock(); + var task = new ProtoToolsPlatform() { + BuildEngine = mockEng.Object + }; + task.Execute(); + Assert.AreEqual(s_expectCpu, task.Cpu); + Assert.AreEqual(s_expectOs, task.Os); + } + }; +} diff --git a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTests.cs b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTests.cs deleted file mode 100644 index 235f8de527..0000000000 --- a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTests.cs +++ /dev/null @@ -1,53 +0,0 @@ -#region Copyright notice and license - -// Copyright 2018 gRPC authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#endregion - -using System; -using Microsoft.Build.Framework; -using Moq; -using NUnit.Framework; - -namespace Grpc.Tools.Tests { - // This test requires that environment variables be set to the expected - // output of the task in its external test harness: - // PROTOTOOLS_TEST_CPU = { x64 | x86 } - // PROTOTOOLS_TEST_OS = { linux | macosx | windows } - public class ProtoToolsPlatformTaskTest { - static string s_expectOs; - static string s_expectCpu; - - [OneTimeSetUp] - public static void Init() { - s_expectCpu = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_CPU"); - s_expectOs = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_OS"); - if (s_expectCpu == null || s_expectOs == null) - Assert.Inconclusive("This test requires PROTOTOOLS_TEST_CPU and " + - "PROTOTOOLS_TEST_OS set in the environment to match the OS it runs on."); - } - - [Test] - public void CpuAndOsMatchExpected() { - var mockEng = new Mock(); - var task = new ProtoToolsPlatform() { - BuildEngine = mockEng.Object - }; - task.Execute(); - Assert.AreEqual(s_expectCpu, task.Cpu); - Assert.AreEqual(s_expectOs, task.Os); - } - }; -} -- cgit v1.2.3 From ccacf24fb077e9de1f1bfb3af9ad33fb5de71870 Mon Sep 17 00:00:00 2001 From: kkm Date: Thu, 11 Oct 2018 13:12:01 -0700 Subject: Fix dotnet/Mono build and testing under Linux --- .../Grpc.Tools.Tests/Grpc.Tools.Tests.csproj | 49 ++++++++++++++++++---- src/csharp/Grpc.Tools.Tests/NUnitMain.cs | 2 +- .../ProtoCompileCommandLineGeneratorTest.cs | 3 ++ src/csharp/Grpc.Tools/Common.cs | 2 +- src/csharp/Grpc.Tools/Grpc.Tools.csproj | 47 ++++++++++----------- 5 files changed, 66 insertions(+), 37 deletions(-) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj index ab14f32afd..70c0474047 100644 --- a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj +++ b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj @@ -3,7 +3,7 @@ - net45;netcoreapp1.0 + net45;netcoreapp1.0;netcoreapp2.1 Exe @@ -14,8 +14,8 @@ and may not compile Grpc.Core/Version.cs, as that file references constants in Grpc.Core.dll. TODO(kkm): Refactor imports. --> - - + + /usr/lib/mono/4.5-api /usr/local/lib/mono/4.5-api /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api @@ -27,16 +27,47 @@ - - - - - - + + + + + + + + + + + + + + {HintPathFromItem};{TargetFrameworkDirectory};{RawFileName} + + <_MSBuildAssemblyPath Condition=" '$(MSBuildRuntimeType)' == 'Core' " + >$(FrameworkPathOverride)/../msbuild/$(MSBuildToolsVersion)/bin + + <_MSBuildAssemblyPath Condition=" '$(MSBuildRuntimeType)' != 'Core' " + >$(MSBuildToolsPath) + + diff --git a/src/csharp/Grpc.Tools.Tests/NUnitMain.cs b/src/csharp/Grpc.Tools.Tests/NUnitMain.cs index 5a8ae8cf87..5784cdeac2 100644 --- a/src/csharp/Grpc.Tools.Tests/NUnitMain.cs +++ b/src/csharp/Grpc.Tools.Tests/NUnitMain.cs @@ -22,7 +22,7 @@ using NUnitLite; namespace Grpc.Tools.Tests { static class NUnitMain { public static int Main(string[] args) => -#if NETCOREAPP1_0 +#if NETCOREAPP1_0 || NETCOREAPP1_1 new AutoRun(typeof(NUnitMain).GetTypeInfo().Assembly).Execute(args); #else new AutoRun().Execute(args); diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs index c5d43f08d9..06376f8ef4 100644 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs @@ -16,6 +16,7 @@ #endregion +using System.IO; using Microsoft.Build.Framework; using Moq; using NUnit.Framework; @@ -152,6 +153,8 @@ namespace Grpc.Tools.Tests { [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, diff --git a/src/csharp/Grpc.Tools/Common.cs b/src/csharp/Grpc.Tools/Common.cs index 1ebd386bd1..9f8600bad0 100644 --- a/src/csharp/Grpc.Tools/Common.cs +++ b/src/csharp/Grpc.Tools/Common.cs @@ -50,7 +50,7 @@ namespace Grpc.Tools { public static bool IsWindows => Os == OsKind.Windows; static Platform() { -#if NETSTANDARD +#if NETCORE Os = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? OsKind.Windows : RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? OsKind.Linux : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? OsKind.MacOsX diff --git a/src/csharp/Grpc.Tools/Grpc.Tools.csproj b/src/csharp/Grpc.Tools/Grpc.Tools.csproj index 8edfb848d7..d93947b8ff 100644 --- a/src/csharp/Grpc.Tools/Grpc.Tools.csproj +++ b/src/csharp/Grpc.Tools/Grpc.Tools.csproj @@ -14,8 +14,8 @@ and may not compile Grpc.Core/Version.cs, as that file references constants in Grpc.Core.dll. TODO(kkm): Refactor imports. --> - - + + /usr/lib/mono/4.5-api /usr/local/lib/mono/4.5-api /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api @@ -27,20 +27,16 @@ ../../../third_party/protobuf/src/google/protobuf/ - - + + ../protoc_plugins/protoc_ - + ../protoc_plugins/ - - <_NetStandard>False - <_NetStandard Condition=" $(TargetFramework.StartsWith('netstandard')) or $(TargetFramework.StartsWith('netcore')) ">True - - - $(DefineConstants);NETSTANDARD + + $(DefineConstants);NETCORE @@ -71,29 +67,28 @@ Linux and MacOS. Managed runtime is supplied separately in the Grpc.Core package <_Asset PackagePath="build/native/include/google/protobuf/" Include="@(_ProtoAssetName->'$(Assets_ProtoInclude)%(Identity).proto')" /> - <_Asset PackagePath="build/native/bin/windows/protoc.exe" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" /> - <_Asset PackagePath="build/native/bin/linux_x86/protoc" Include="$(Assets_ProtoCompiler)linux_x86/protoc" /> - <_Asset PackagePath="build/native/bin/linux_x64/protoc" Include="$(Assets_ProtoCompiler)linux_x64/protoc" /> - <_Asset PackagePath="build/native/bin/macosx_x86/protoc" Include="$(Assets_ProtoCompiler)macos_x86/protoc" /> - <_Asset PackagePath="build/native/bin/macosx_x64/protoc" Include="$(Assets_ProtoCompiler)macos_x64/protoc" /> + <_Asset PackagePath="build/native/bin/windows/" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" /> + <_Asset PackagePath="build/native/bin/linux_x86/" Include="$(Assets_ProtoCompiler)linux_x86/protoc" /> + <_Asset PackagePath="build/native/bin/linux_x64/" Include="$(Assets_ProtoCompiler)linux_x64/protoc" /> + <_Asset PackagePath="build/native/bin/macosx_x86/" Include="$(Assets_ProtoCompiler)macos_x86/protoc" /> + <_Asset PackagePath="build/native/bin/macosx_x64/" Include="$(Assets_ProtoCompiler)macos_x64/protoc" /> - <_Asset PackagePath="build/native/bin/windows/grpc_csharp_plugin.exe" Include="$(Assets_GrpcPlugins)protoc_windows_x86/grpc_csharp_plugin.exe" /> - <_Asset PackagePath="build/native/bin/linux_x86/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_linux_x86/grpc_csharp_plugin" /> - <_Asset PackagePath="build/native/bin/linux_x64/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_linux_x64/grpc_csharp_plugin" /> - <_Asset PackagePath="build/native/bin/macosx_x86/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_macos_x86/grpc_csharp_plugin" /> - <_Asset PackagePath="build/native/bin/macosx_x64/grpc_csharp_plugin" Include="$(Assets_GrpcPlugins)protoc_macos_x64/grpc_csharp_plugin" /> + <_Asset PackagePath="build/native/bin/windows/" Include="$(Assets_GrpcPlugins)protoc_windows_x86/grpc_csharp_plugin.exe" /> + <_Asset PackagePath="build/native/bin/linux_x86/" Include="$(Assets_GrpcPlugins)protoc_linux_x86/grpc_csharp_plugin" /> + <_Asset PackagePath="build/native/bin/linux_x64/" Include="$(Assets_GrpcPlugins)protoc_linux_x64/grpc_csharp_plugin" /> + <_Asset PackagePath="build/native/bin/macosx_x86/" Include="$(Assets_GrpcPlugins)protoc_macos_x86/grpc_csharp_plugin" /> + <_Asset PackagePath="build/native/bin/macosx_x64/" Include="$(Assets_GrpcPlugins)protoc_macos_x64/grpc_csharp_plugin" /> - - - + + - - + + -- cgit v1.2.3 From 9158493c41f4d47f5409e40760afe6babf17bb2e Mon Sep 17 00:00:00 2001 From: kkm Date: Sun, 14 Oct 2018 04:17:08 -0700 Subject: Improve ProtoToolsPlatformTask tests --- .../Grpc.Tools.Tests/Grpc.Tools.Tests.csproj | 13 ++- .../Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs | 111 +++++++++++++++++---- 2 files changed, 99 insertions(+), 25 deletions(-) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj index 70c0474047..cb065d3c36 100644 --- a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj +++ b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj @@ -28,8 +28,13 @@ + + + $(DefineConstants);NETCORE + + @@ -62,12 +67,12 @@ 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 --> {HintPathFromItem};{TargetFrameworkDirectory};{RawFileName} - - <_MSBuildAssemblyPath Condition=" '$(MSBuildRuntimeType)' == 'Core' " - >$(FrameworkPathOverride)/../msbuild/$(MSBuildToolsVersion)/bin - + <_MSBuildAssemblyPath Condition=" '$(MSBuildRuntimeType)' != 'Core' " >$(MSBuildToolsPath) + + <_MSBuildAssemblyPath Condition=" '$(MSBuildRuntimeType)' == 'Core' " + >$(FrameworkPathOverride)/../msbuild/$(MSBuildToolsVersion)/bin diff --git a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs index 235f8de527..2380ae8a37 100644 --- a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs +++ b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs @@ -16,38 +16,107 @@ #endregion -using System; +using System.Runtime.InteropServices; // For NETCORE tests. using Microsoft.Build.Framework; using Moq; using NUnit.Framework; namespace Grpc.Tools.Tests { - // This test requires that environment variables be set to the expected - // output of the task in its external test harness: - // PROTOTOOLS_TEST_CPU = { x64 | x86 } - // PROTOTOOLS_TEST_OS = { linux | macosx | windows } public class ProtoToolsPlatformTaskTest { - static string s_expectOs; - static string s_expectCpu; + ProtoToolsPlatform _task; + int _cpuMatched, _osMatched; [OneTimeSetUp] - public static void Init() { - s_expectCpu = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_CPU"); - s_expectOs = Environment.GetEnvironmentVariable("PROTOTOOLS_TEST_OS"); - if (s_expectCpu == null || s_expectOs == null) - Assert.Inconclusive("This test requires PROTOTOOLS_TEST_CPU and " + - "PROTOTOOLS_TEST_OS set in the environment to match the OS it runs on."); - } - - [Test] - public void CpuAndOsMatchExpected() { + public void SetUp() { var mockEng = new Mock(); - var task = new ProtoToolsPlatform() { + _task = new ProtoToolsPlatform() { BuildEngine = mockEng.Object }; - task.Execute(); - Assert.AreEqual(s_expectCpu, task.Cpu); - Assert.AreEqual(s_expectOs, task.Os); + _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 }; } -- cgit v1.2.3 From f626d4618d8ed15853e763e96a23e3635be0c339 Mon Sep 17 00:00:00 2001 From: kkm Date: Sun, 14 Oct 2018 06:59:49 -0700 Subject: Restore packaging of binary tools under tools/ Unfixes #13098, hopefully temporarily. --- src/csharp/Grpc.Tools/Grpc.Tools.csproj | 24 +++++++++++++--------- .../Grpc.Tools/build/_grpc/_Grpc.Tools.targets | 8 +++++--- .../build/_protobuf/Google.Protobuf.Tools.props | 7 ++++--- .../build/_protobuf/Google.Protobuf.Tools.targets | 5 +++-- .../Grpc.Tools/build/native/Grpc.Tools.props | 8 +++++--- 5 files changed, 31 insertions(+), 21 deletions(-) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools/Grpc.Tools.csproj b/src/csharp/Grpc.Tools/Grpc.Tools.csproj index d93947b8ff..f2cefc9826 100644 --- a/src/csharp/Grpc.Tools/Grpc.Tools.csproj +++ b/src/csharp/Grpc.Tools/Grpc.Tools.csproj @@ -67,18 +67,22 @@ Linux and MacOS. Managed runtime is supplied separately in the Grpc.Core package <_Asset PackagePath="build/native/include/google/protobuf/" Include="@(_ProtoAssetName->'$(Assets_ProtoInclude)%(Identity).proto')" /> - <_Asset PackagePath="build/native/bin/windows/" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" /> - <_Asset PackagePath="build/native/bin/linux_x86/" Include="$(Assets_ProtoCompiler)linux_x86/protoc" /> - <_Asset PackagePath="build/native/bin/linux_x64/" Include="$(Assets_ProtoCompiler)linux_x64/protoc" /> - <_Asset PackagePath="build/native/bin/macosx_x86/" Include="$(Assets_ProtoCompiler)macos_x86/protoc" /> - <_Asset PackagePath="build/native/bin/macosx_x64/" Include="$(Assets_ProtoCompiler)macos_x64/protoc" /> + + + <_Asset PackagePath="tools/windows_x86/" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" /> + <_Asset PackagePath="tools/windows_x64/" Include="$(Assets_ProtoCompiler)windows_x64/protoc.exe" /> + <_Asset PackagePath="tools/linux_x86/" Include="$(Assets_ProtoCompiler)linux_x86/protoc" /> + <_Asset PackagePath="tools/linux_x64/" Include="$(Assets_ProtoCompiler)linux_x64/protoc" /> + <_Asset PackagePath="tools/macosx_x86/" Include="$(Assets_ProtoCompiler)macos_x86/protoc" /> + <_Asset PackagePath="tools/macosx_x64/" Include="$(Assets_ProtoCompiler)macos_x64/protoc" /> - <_Asset PackagePath="build/native/bin/windows/" Include="$(Assets_GrpcPlugins)protoc_windows_x86/grpc_csharp_plugin.exe" /> - <_Asset PackagePath="build/native/bin/linux_x86/" Include="$(Assets_GrpcPlugins)protoc_linux_x86/grpc_csharp_plugin" /> - <_Asset PackagePath="build/native/bin/linux_x64/" Include="$(Assets_GrpcPlugins)protoc_linux_x64/grpc_csharp_plugin" /> - <_Asset PackagePath="build/native/bin/macosx_x86/" Include="$(Assets_GrpcPlugins)protoc_macos_x86/grpc_csharp_plugin" /> - <_Asset PackagePath="build/native/bin/macosx_x64/" Include="$(Assets_GrpcPlugins)protoc_macos_x64/grpc_csharp_plugin" /> + <_Asset PackagePath="tools/windows_x86/" Include="$(Assets_GrpcPlugins)protoc_windows_x86/grpc_csharp_plugin.exe" /> + <_Asset PackagePath="tools/windows_x64/" Include="$(Assets_GrpcPlugins)protoc_windows_x64/grpc_csharp_plugin.exe" /> + <_Asset PackagePath="tools/linux_x86/" Include="$(Assets_GrpcPlugins)protoc_linux_x86/grpc_csharp_plugin" /> + <_Asset PackagePath="tools/linux_x64/" Include="$(Assets_GrpcPlugins)protoc_linux_x64/grpc_csharp_plugin" /> + <_Asset PackagePath="tools/macosx_x86/" Include="$(Assets_GrpcPlugins)protoc_macos_x86/grpc_csharp_plugin" /> + <_Asset PackagePath="tools/macosx_x64/" Include="$(Assets_GrpcPlugins)protoc_macos_x64/grpc_csharp_plugin" /> diff --git a/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets b/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets index 0042bf2bfa..5f76c03ce5 100644 --- a/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets +++ b/src/csharp/Grpc.Tools/build/_grpc/_Grpc.Tools.targets @@ -14,17 +14,19 @@ - Both + Both + + $(Protobuf_PackagedToolsPath)bin\$(Protobuf_ToolsOs)\$(gRPC_PluginFileName).exe + >$(Protobuf_PackagedToolsPath)\$(Protobuf_ToolsOs)_x86\$(gRPC_PluginFileName).exe $(Protobuf_PackagedToolsPath)bin/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/$(gRPC_PluginFileName) + >$(Protobuf_PackagedToolsPath)/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/$(gRPC_PluginFileName) diff --git a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props index 06ee9bcda8..9f2d8bb4b5 100644 --- a/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props +++ b/src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.props @@ -6,9 +6,10 @@ 1 - - $( [System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)../native/) ) - $(Protobuf_PackagedToolsPath)include + + + $( [System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)../../tools) ) + $( [System.IO.Path]::GetFullPath($(MSBuildThisFileDirectory)../native/include) ) $(_Protobuf_ToolsOs) $(_Protobuf_ToolsCpu) + $(Protobuf_PackagedToolsPath)bin\$(Protobuf_ToolsOs)\protoc.exe + >$(Protobuf_PackagedToolsPath)\$(Protobuf_ToolsOs)_x86\protoc.exe $(Protobuf_PackagedToolsPath)bin/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/protoc + >$(Protobuf_PackagedToolsPath)/$(Protobuf_ToolsOs)_$(Protobuf_ToolsCpu)/protoc - 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 \ +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\ @@ -76,57 +81,66 @@ 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 \ + // 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(); - var log = new TaskLoggingHelper(mockEng.Object, "x"); - return DepFileUtil.ReadDependencyInputs(tempPath, protoName, log); - } finally { - try { - File.Delete(tempfile); - } catch { } - } - } - }; + [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(); + var log = new TaskLoggingHelper(mockEng.Object, "x"); + return DepFileUtil.ReadDependencyInputs(tempPath, protoName, log); + } + finally + { + try + { + File.Delete(tempfile); + } + catch { } + } + } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/GeneratorTest.cs b/src/csharp/Grpc.Tools.Tests/GeneratorTest.cs index 52fab1d8ca..8a8fc81aba 100644 --- a/src/csharp/Grpc.Tools.Tests/GeneratorTest.cs +++ b/src/csharp/Grpc.Tools.Tests/GeneratorTest.cs @@ -21,30 +21,35 @@ using Microsoft.Build.Utilities; using Moq; using NUnit.Framework; -namespace Grpc.Tools.Tests { - public class GeneratorTest { - protected Mock _mockEngine; - protected TaskLoggingHelper _log; +namespace Grpc.Tools.Tests +{ + public class GeneratorTest + { + protected Mock _mockEngine; + protected TaskLoggingHelper _log; - [SetUp] - public void SetUp() { - _mockEngine = new Mock(); - _log = new TaskLoggingHelper(_mockEngine.Object, "dummy"); - } + [SetUp] + public void SetUp() + { + _mockEngine = new Mock(); + _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()), Times.Never); - } + [TestCase("csharp")] + [TestCase("CSharp")] + [TestCase("cpp")] + public void ValidLanguages(string lang) + { + Assert.IsNotNull(GeneratorServices.GetForLanguage(lang, _log)); + _mockEngine.Verify(me => me.LogErrorEvent(It.IsAny()), Times.Never); + } - [TestCase("")] - [TestCase("COBOL")] - public void InvalidLanguages(string lang) { - Assert.IsNull(GeneratorServices.GetForLanguage(lang, _log)); - _mockEngine.Verify(me => me.LogErrorEvent(It.IsAny()), Times.Once); - } - }; + [TestCase("")] + [TestCase("COBOL")] + public void InvalidLanguages(string lang) + { + Assert.IsNull(GeneratorServices.GetForLanguage(lang, _log)); + _mockEngine.Verify(me => me.LogErrorEvent(It.IsAny()), Times.Once); + } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/NUnitMain.cs b/src/csharp/Grpc.Tools.Tests/NUnitMain.cs index 5784cdeac2..418c33820e 100644 --- a/src/csharp/Grpc.Tools.Tests/NUnitMain.cs +++ b/src/csharp/Grpc.Tools.Tests/NUnitMain.cs @@ -19,13 +19,15 @@ using System.Reflection; using NUnitLite; -namespace Grpc.Tools.Tests { - static class NUnitMain { - public static int Main(string[] args) => +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); + new AutoRun(typeof(NUnitMain).GetTypeInfo().Assembly).Execute(args); #else - new AutoRun().Execute(args); + new AutoRun().Execute(args); #endif - }; + }; } diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTest.cs index cf9d210424..ea763f4e40 100644 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTest.cs +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileBasicTest.cs @@ -21,50 +21,56 @@ 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; } +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); + 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")); + // 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'); + LastPathToTool = pathToTool; + LastResponseFile = response.Remove(response.Length - 1).Split('\n'); - // Do not run the tool, but pretend it ran successfully. - return 0; - } - }; + // Do not run the tool, but pretend it ran successfully. + return 0; + } + }; - protected Mock _mockEngine; - protected ProtoCompileTestable _task; + protected Mock _mockEngine; + protected ProtoCompileTestable _task; - [SetUp] - public void SetUp() { - _mockEngine = new Mock(); - _task = new ProtoCompileTestable { - BuildEngine = _mockEngine.Object - }; - } + [SetUp] + public void SetUp() + { + _mockEngine = new Mock(); + _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()); - } - }; + [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()); + } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs index 06376f8ef4..cac7146634 100644 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLineGeneratorTest.cs @@ -21,144 +21,159 @@ 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())) - .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[] { +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())) + .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)); - } - }; + } + + [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)); + } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTest.cs index a0406371dc..1773dcb875 100644 --- a/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTest.cs +++ b/src/csharp/Grpc.Tools.Tests/ProtoCompileCommandLinePrinterTest.cs @@ -20,28 +20,32 @@ 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"); +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())) - .Callback((BuildMessageEventArgs e) => - Assert.Fail($"Error logged by build engine:\n{e.Message}")) - .Verifiable("Command line was not output by the task."); - } + _mockEngine + .Setup(me => me.LogMessageEvent(It.IsAny())) + .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())) - .Callback((BuildErrorEventArgs e) => - Assert.Fail($"Error logged by build engine:\n{e.Message}")); - bool result = _task.Execute(); - Assert.IsTrue(result); - } - }; + void ExecuteExpectSuccess() + { + _mockEngine + .Setup(me => me.LogErrorEvent(It.IsAny())) + .Callback((BuildErrorEventArgs e) => + Assert.Fail($"Error logged by build engine:\n{e.Message}")); + bool result = _task.Execute(); + Assert.IsTrue(result); + } + }; } diff --git a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs index 2380ae8a37..54723b74fc 100644 --- a/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs +++ b/src/csharp/Grpc.Tools.Tests/ProtoToolsPlatformTaskTest.cs @@ -21,102 +21,119 @@ 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(); - _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"); - } +namespace Grpc.Tools.Tests +{ + public class ProtoToolsPlatformTaskTest + { + ProtoToolsPlatform _task; + int _cpuMatched, _osMatched; + + [OneTimeSetUp] + public void SetUp() + { + var mockEng = new Mock(); + _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); - } - } + // 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); - } + [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 - }; + }; } diff --git a/src/csharp/Grpc.Tools.Tests/Utils.cs b/src/csharp/Grpc.Tools.Tests/Utils.cs index bb051a4873..6e0f1cffd5 100644 --- a/src/csharp/Grpc.Tools.Tests/Utils.cs +++ b/src/csharp/Grpc.Tools.Tests/Utils.cs @@ -20,21 +20,27 @@ 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; - } +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(); - } - }; + // Return an array of items from given itemspecs. + public static ITaskItem[] MakeSimpleItems(params string[] specs) + { + return specs.Select(s => new TaskItem(s)).ToArray(); + } + }; } diff --git a/src/csharp/Grpc.Tools/Common.cs b/src/csharp/Grpc.Tools/Common.cs index 9f8600bad0..e6acdd6393 100644 --- a/src/csharp/Grpc.Tools/Common.cs +++ b/src/csharp/Grpc.Tools/Common.cs @@ -24,82 +24,91 @@ 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"; - }; +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; + // 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; + 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; + // 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() { + 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; + 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; - } + switch (RuntimeInformation.OSArchitecture) + { + case Architecture.X86: Cpu = CpuKind.X86; break; + case Architecture.X64: Cpu = CpuKind.X64; break; + // We do not have build tools for other architectures. + default: Cpu = CpuKind.Unknown; break; + } #else - // Running under either Mono or full MS framework. - Os = OsKind.Windows; - if (Type.GetType("Mono.Runtime", throwOnError: false) != null) { - // Congratulations. We are running under Mono. - var plat = Environment.OSVersion.Platform; - if (plat == PlatformID.MacOSX) { - Os = OsKind.MacOsX; - } else if (plat == PlatformID.Unix || (int)plat == 128) { - // TODO(kkm): This is how Mono detects OSX internally. Looks cheesy - // to me. Would not testing for /proc absence be more reliable? OSX - // did never have it, AFAIK. - Os = File.Exists("/usr/lib/libc.dylib") ? OsKind.MacOsX : OsKind.Linux; - } - } + // 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; + // 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; - }; + // 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); - } + // 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); + } } diff --git a/src/csharp/Grpc.Tools/DepFileUtil.cs b/src/csharp/Grpc.Tools/DepFileUtil.cs index e635ad1e85..440d3d535c 100644 --- a/src/csharp/Grpc.Tools/DepFileUtil.cs +++ b/src/csharp/Grpc.Tools/DepFileUtil.cs @@ -23,219 +23,251 @@ using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Grpc.Tools { - internal static class DepFileUtil { - /* - Sample dependency files. Notable features we have to deal with: - * Slash doubling, must normalize them. - * Spaces in file names. Cannot just "unwrap" the line on backslash at eof; - rather, treat every line as containing one file name except for one with - the ':' separator, as containing exactly two. - * Deal with ':' also being drive letter separator (second example). - - obj\Release\net45\/Foo.cs \ - obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\ - C:/projects/foo/src//foo.proto - - C:\projects\foo\src\./foo.grpc.pb.cc \ - C:\projects\foo\src\./foo.grpc.pb.h \ - C:\projects\foo\src\./foo.pb.cc \ - C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ - C:/foo/include/google/protobuf/any.proto\ - C:/foo/include/google/protobuf/source_context.proto\ - C:/foo/include/google/protobuf/type.proto\ - foo.proto - */ - - /// - /// Read file names from the dependency file to the right of ':' - /// - /// Relative path to the dependency cache, e. g. "out" - /// Relative path to the proto item, e. g. "foo/file.proto" - /// A for logging - /// - /// Array of the proto file input dependencies as written by protoc, or empty - /// array if the dependency file does not exist or cannot be parsed. - /// - 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(); - 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]; +namespace Grpc.Tools +{ + internal static class DepFileUtil + { + /* + Sample dependency files. Notable features we have to deal with: + * Slash doubling, must normalize them. + * Spaces in file names. Cannot just "unwrap" the line on backslash at eof; + rather, treat every line as containing one file name except for one with + the ':' separator, as containing exactly two. + * Deal with ':' also being drive letter separator (second example). + + obj\Release\net45\/Foo.cs \ + obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\ + C:/projects/foo/src//foo.proto + + C:\projects\foo\src\./foo.grpc.pb.cc \ + C:\projects\foo\src\./foo.grpc.pb.h \ + C:\projects\foo\src\./foo.pb.cc \ + C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ + C:/foo/include/google/protobuf/any.proto\ + C:/foo/include/google/protobuf/source_context.proto\ + C:/foo/include/google/protobuf/type.proto\ + foo.proto + */ + + /// + /// Read file names from the dependency file to the right of ':' + /// + /// Relative path to the dependency cache, e. g. "out" + /// Relative path to the proto item, e. g. "foo/file.proto" + /// A for logging + /// + /// Array of the proto file input dependencies as written by protoc, or empty + /// array if the dependency file does not exist or cannot be parsed. + /// + 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(); + 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(); } - // 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); + /// + /// Read file names from the dependency file to the left of ':' + /// + /// Path to dependency file written by protoc + /// A for logging + /// + /// 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. + /// + /// + /// Since this is called after a protoc invocation, an unparsable or missing + /// file causes an error-level message to be logged. + /// + public static string[] ReadDependencyOutputs(string depFilename, + TaskLoggingHelper log) + { + string[] lines = ReadDepFileLines(depFilename, true, log); + if (lines.Length == 0) + { + return lines; + } + + var result = new List(); + 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(); } - } - return result.ToArray(); - } - - /// - /// Read file names from the dependency file to the left of ':' - /// - /// Path to dependency file written by protoc - /// A for logging - /// - /// 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. - /// - /// - /// Since this is called after a protoc invocation, an unparsable or missing - /// file causes an error-level message to be logged. - /// - public static string[] ReadDependencyOutputs(string depFilename, - TaskLoggingHelper log) { - string[] lines = ReadDepFileLines(depFilename, true, log); - if (lines.Length == 0) { - return lines; - } - - var result = new List(); - 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]; + + /// + /// Construct relative dependency file name from directory hash and file name + /// + /// Relative path to the dependency cache, e. g. "out" + /// Relative path to the proto item, e. g. "foo/file.proto" + /// + /// Full relative path to the dependency file, e. g. + /// "out/deadbeef12345678_file.protodep" + /// + /// + /// 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. + /// + 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"); } - result.Add(file); - - // If this is the line with the separator, do not read further. - if (ix >= 0) - break; - } - return result.ToArray(); - } - - /// - /// Construct relative dependency file name from directory hash and file name - /// - /// Relative path to the dependency cache, e. g. "out" - /// Relative path to the proto item, e. g. "foo/file.proto" - /// - /// Full relative path to the dependency file, e. g. - /// "out/deadbeef12345678_file.protodep" - /// - /// - /// 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. - /// - 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")); + + // 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 ""; + } } - 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}"); + + // 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]; + } } - return new string[0]; - } - } - }; + }; } diff --git a/src/csharp/Grpc.Tools/GeneratorServices.cs b/src/csharp/Grpc.Tools/GeneratorServices.cs index 52bd29a678..536ec43c83 100644 --- a/src/csharp/Grpc.Tools/GeneratorServices.cs +++ b/src/csharp/Grpc.Tools/GeneratorServices.cs @@ -22,147 +22,173 @@ 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); +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; } - 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); + + // 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(); } - } - 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); + }; + + // 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; + } } - 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; - } - } - }; + }; } diff --git a/src/csharp/Grpc.Tools/ProtoCompile.cs b/src/csharp/Grpc.Tools/ProtoCompile.cs index e77084b1ef..93608e1ac0 100644 --- a/src/csharp/Grpc.Tools/ProtoCompile.cs +++ b/src/csharp/Grpc.Tools/ProtoCompile.cs @@ -20,390 +20,422 @@ using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Grpc.Tools { - /// - /// 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. - /// - 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. - @ 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 - @ 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", - }; - - /// - /// Code generator. - /// - [Required] - public string Generator { get; set; } - - /// - /// Protobuf files to compile. - /// - [Required] - public ITaskItem[] ProtoBuf { get; set; } - - /// - /// 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). - /// - public string ProtoDepDir { get; set; } - - /// - /// Dependency file full name. Mutually exclusive with ProtoDepDir. - /// Autogenerated file name is available in this property after execution. - /// Switch: --dependency_out. - /// - [Output] - public string DependencyOut { get; set; } - - /// - /// The directories to search for imports. Directories will be searched - /// in order. If not given, the current working directory is used. - /// Switch: --proto_path. - /// - public string[] ProtoPath { get; set; } - - /// - /// Generated code directory. The generator property determines the language. - /// Switch: --GEN-out= (for different generators GEN). - /// - [Required] - public string OutputDir { get; set; } - - /// - /// Codegen options. See also OptionsFromMetadata. - /// Switch: --GEN_out= (for different generators GEN). - /// - public string[] OutputOptions { get; set; } - - /// - /// Full path to the gRPC plugin executable. If specified, gRPC generation - /// is enabled for the files. - /// Switch: --plugin=protoc-gen-grpc= - /// - public string GrpcPluginExe { get; set; } - +namespace Grpc.Tools +{ /// - /// Generated gRPC directory. The generator property determines the - /// language. If gRPC is enabled but this is not given, OutputDir is used. - /// Switch: --grpc_out= + /// 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. /// - public string GrpcOutputDir { get; set; } - - /// - /// gRPC Codegen options. See also OptionsFromMetadata. - /// --grpc_opt=opt1,opt2=val (comma-separated). - /// - public string[] GrpcOutputOptions { get; set; } + 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. + @ 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 + @ 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" }; + + /// + /// Code generator. + /// + [Required] + public string Generator { get; set; } + + /// + /// Protobuf files to compile. + /// + [Required] + public ITaskItem[] ProtoBuf { get; set; } + + /// + /// 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). + /// + public string ProtoDepDir { get; set; } + + /// + /// Dependency file full name. Mutually exclusive with ProtoDepDir. + /// Autogenerated file name is available in this property after execution. + /// Switch: --dependency_out. + /// + [Output] + public string DependencyOut { get; set; } + + /// + /// The directories to search for imports. Directories will be searched + /// in order. If not given, the current working directory is used. + /// Switch: --proto_path. + /// + public string[] ProtoPath { get; set; } + + /// + /// Generated code directory. The generator property determines the language. + /// Switch: --GEN-out= (for different generators GEN). + /// + [Required] + public string OutputDir { get; set; } + + /// + /// Codegen options. See also OptionsFromMetadata. + /// Switch: --GEN_out= (for different generators GEN). + /// + public string[] OutputOptions { get; set; } + + /// + /// Full path to the gRPC plugin executable. If specified, gRPC generation + /// is enabled for the files. + /// Switch: --plugin=protoc-gen-grpc= + /// + public string GrpcPluginExe { get; set; } + + /// + /// Generated gRPC directory. The generator property determines the + /// language. If gRPC is enabled but this is not given, OutputDir is used. + /// Switch: --grpc_out= + /// + public string GrpcOutputDir { get; set; } + + /// + /// gRPC Codegen options. See also OptionsFromMetadata. + /// --grpc_opt=opt1,opt2=val (comma-separated). + /// + public string[] GrpcOutputOptions { get; set; } + + /// + /// List of files written in addition to generated outputs. Includes a + /// single item for the dependency file if written. + /// + [Output] + public ITaskItem[] AdditionalFileWrites { get; private set; } + + /// + /// 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. + /// + [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(); + } - /// - /// List of files written in addition to generated outputs. Includes a - /// single item for the dependency file if written. - /// - [Output] - public ITaskItem[] AdditionalFileWrites { get; private set; } + // 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'); + } + }; - /// - /// 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. - /// - [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'); + // 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(); } - } - // 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'); + // 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; } - } - - // 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; + // 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()); } - GeneratedFiles = new ITaskItem[outputs.Length]; - for (int i = 0; i < outputs.Length; i++) { - GeneratedFiles[i] = new TaskItem(outputs[i]); + // 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; } - AdditionalFileWrites = new ITaskItem[] { - new TaskItem(DependencyOut) - }; - } - - return true; - } - }; + }; } diff --git a/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs b/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs index 74aaa8bd3d..915be3421e 100644 --- a/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs +++ b/src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2018 gRPC authors. // @@ -20,61 +20,67 @@ using System.Collections.Generic; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Grpc.Tools { - public class ProtoCompilerOutputs : Task { - /// - /// Code generator. Currently supported are "csharp", "cpp". - /// - [Required] - public string Generator { get; set; } +namespace Grpc.Tools +{ + public class ProtoCompilerOutputs : Task + { + /// + /// Code generator. Currently supported are "csharp", "cpp". + /// + [Required] + public string Generator { get; set; } - /// - /// 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. - /// - [Required] - public ITaskItem[] ProtoBuf { get; set; } + /// + /// 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. + /// + [Required] + public ITaskItem[] ProtoBuf { get; set; } - /// - /// 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: - /// - /// - [Output] - public ITaskItem[] PossibleOutputs { get; private set; } + /// + /// 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: + /// + /// + [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; - } + 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(); - 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(); + // Get language-specific possible output. The generator expects certain + // metadata be set on the proto item. + var possible = new List(); + 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; - } - }; + return !Log.HasLoggedErrors; + } + }; } diff --git a/src/csharp/Grpc.Tools/ProtoReadDependencies.cs b/src/csharp/Grpc.Tools/ProtoReadDependencies.cs index ea931b0826..963837e8b7 100644 --- a/src/csharp/Grpc.Tools/ProtoReadDependencies.cs +++ b/src/csharp/Grpc.Tools/ProtoReadDependencies.cs @@ -20,51 +20,59 @@ using System.Collections.Generic; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Grpc.Tools { - public class ProtoReadDependencies : Task { - /// - /// The collection is used to collect possible additional dependencies - /// of proto files cached under ProtoDepDir. - /// - [Required] - public ITaskItem[] ProtoBuf { get; set; } +namespace Grpc.Tools +{ + public class ProtoReadDependencies : Task + { + /// + /// The collection is used to collect possible additional dependencies + /// of proto files cached under ProtoDepDir. + /// + [Required] + public ITaskItem[] ProtoBuf { get; set; } - /// - /// Directory where protoc dependency files are cached. - /// - [Required] - public string ProtoDepDir { get; set; } + /// + /// Directory where protoc dependency files are cached. + /// + [Required] + public string ProtoDepDir { get; set; } - /// - /// 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 - /// - /// - [Output] - public ITaskItem[] Dependencies { get; private set; } + /// + /// 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 + /// + /// + [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(); - 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]; - } + 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(); + 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; - } - }; + return !Log.HasLoggedErrors; + } + }; } diff --git a/src/csharp/Grpc.Tools/ProtoToolsPlatform.cs b/src/csharp/Grpc.Tools/ProtoToolsPlatform.cs index f505b86fe4..aed8a66339 100644 --- a/src/csharp/Grpc.Tools/ProtoToolsPlatform.cs +++ b/src/csharp/Grpc.Tools/ProtoToolsPlatform.cs @@ -19,40 +19,45 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Grpc.Tools { - /// - /// A helper task to resolve actual OS type and bitness. - /// - public class ProtoToolsPlatform : Task { +namespace Grpc.Tools +{ /// - /// Return one of 'linux', 'macosx' or 'windows'. - /// If the OS is unknown, the property is not set. + /// A helper task to resolve actual OS type and bitness. /// - [Output] - public string Os { get; set; } - - /// - /// Return one of 'x64' or 'x86'. - /// If the CPU is unknown, the property is not set. - /// - [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; - } - }; + public class ProtoToolsPlatform : Task + { + /// + /// Return one of 'linux', 'macosx' or 'windows'. + /// If the OS is unknown, the property is not set. + /// + [Output] + public string Os { get; set; } + + /// + /// Return one of 'x64' or 'x86'. + /// If the CPU is unknown, the property is not set. + /// + [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; + } + }; } -- cgit v1.2.3 From 3b7c4082620590d1d0d9419f13f36f2e59291dff Mon Sep 17 00:00:00 2001 From: kkm Date: Tue, 16 Oct 2018 15:27:04 -0700 Subject: Remove target netcoreapp2.1 from Tools.Tests csproj --- src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj index cb065d3c36..a2d4874eec 100644 --- a/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj +++ b/src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj @@ -3,7 +3,7 @@ - net45;netcoreapp1.0;netcoreapp2.1 + net45;netcoreapp1.0 Exe -- cgit v1.2.3 From ee4cb5ce65f935b2de6e3c2a043dca3edc86cbf0 Mon Sep 17 00:00:00 2001 From: Jan Tattermusch Date: Wed, 17 Oct 2018 12:49:01 +0200 Subject: fix Grpc.Tools nuget layout on Linux and Mac For files without known extension (e.g. Unix binaries) , NuGet can't tell files from directories, so mention protoc and grpc_csharp_ext explicitly to avoid breaking nuget's directory layout. --- src/csharp/Grpc.Tools/Grpc.Tools.csproj | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src/csharp') diff --git a/src/csharp/Grpc.Tools/Grpc.Tools.csproj b/src/csharp/Grpc.Tools/Grpc.Tools.csproj index f2cefc9826..61fa75a4ec 100644 --- a/src/csharp/Grpc.Tools/Grpc.Tools.csproj +++ b/src/csharp/Grpc.Tools/Grpc.Tools.csproj @@ -69,20 +69,20 @@ Linux and MacOS. Managed runtime is supplied separately in the Grpc.Core package - <_Asset PackagePath="tools/windows_x86/" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" /> - <_Asset PackagePath="tools/windows_x64/" Include="$(Assets_ProtoCompiler)windows_x64/protoc.exe" /> - <_Asset PackagePath="tools/linux_x86/" Include="$(Assets_ProtoCompiler)linux_x86/protoc" /> - <_Asset PackagePath="tools/linux_x64/" Include="$(Assets_ProtoCompiler)linux_x64/protoc" /> - <_Asset PackagePath="tools/macosx_x86/" Include="$(Assets_ProtoCompiler)macos_x86/protoc" /> - <_Asset PackagePath="tools/macosx_x64/" Include="$(Assets_ProtoCompiler)macos_x64/protoc" /> + <_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" /> + <_Asset PackagePath="tools/macosx_x64/protoc" Include="$(Assets_ProtoCompiler)macos_x64/protoc" /> - <_Asset PackagePath="tools/windows_x86/" Include="$(Assets_GrpcPlugins)protoc_windows_x86/grpc_csharp_plugin.exe" /> - <_Asset PackagePath="tools/windows_x64/" Include="$(Assets_GrpcPlugins)protoc_windows_x64/grpc_csharp_plugin.exe" /> - <_Asset PackagePath="tools/linux_x86/" Include="$(Assets_GrpcPlugins)protoc_linux_x86/grpc_csharp_plugin" /> - <_Asset PackagePath="tools/linux_x64/" Include="$(Assets_GrpcPlugins)protoc_linux_x64/grpc_csharp_plugin" /> - <_Asset PackagePath="tools/macosx_x86/" Include="$(Assets_GrpcPlugins)protoc_macos_x86/grpc_csharp_plugin" /> - <_Asset PackagePath="tools/macosx_x64/" Include="$(Assets_GrpcPlugins)protoc_macos_x64/grpc_csharp_plugin" /> + <_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" /> -- cgit v1.2.3