From cbb70f534b417d6826e3c501be8fdb8ad5b4ae4a Mon Sep 17 00:00:00 2001 From: kkm Date: Fri, 22 Mar 2019 00:46:52 -0700 Subject: [PATCH] C# tools: support generated filename corner cases protoc and gRPC codegens differently treat non-ASCII letter characters and symbols other than underscores when constructing their respective output filenames for generated .cs files. This change reproduces their respective behaviors exactly. Fixes #17661 --- .../Grpc.Tools.Tests/CSharpGeneratorTest.cs | 14 +++--- src/csharp/Grpc.Tools/GeneratorServices.cs | 45 +++++++++++++------ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs b/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs index e4c9b2fa843..782c63b7107 100644 --- a/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs +++ b/src/csharp/Grpc.Tools.Tests/CSharpGeneratorTest.cs @@ -33,12 +33,14 @@ namespace Grpc.Tools.Tests [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")] + [TestCase("ONE_TWO.proto", "ONETWO.cs", "ONETWOGrpc.cs")] + [TestCase("one.two.proto", "OneTwo.cs", "One.twoGrpc.cs")] + [TestCase("__one_two!.proto", "OneTwo.cs", "OneTwo!Grpc.cs")] + [TestCase("one(two).proto", "OneTwo.cs", "One(two)Grpc.cs")] + [TestCase("one_(two).proto", "OneTwo.cs", "One(two)Grpc.cs")] + [TestCase("one two.proto", "OneTwo.cs", "One twoGrpc.cs")] + [TestCase("one_ two.proto", "OneTwo.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")); diff --git a/src/csharp/Grpc.Tools/GeneratorServices.cs b/src/csharp/Grpc.Tools/GeneratorServices.cs index 536ec43c836..c956c89d6d8 100644 --- a/src/csharp/Grpc.Tools/GeneratorServices.cs +++ b/src/csharp/Grpc.Tools/GeneratorServices.cs @@ -66,29 +66,28 @@ namespace Grpc.Tools 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 basename = Path.GetFileNameWithoutExtension(protoItem.ItemSpec); + string outdir = protoItem.GetMetadata(Metadata.OutputDir); - string fileStem = Path.Combine(outdir, filename); - outputs[0] = fileStem + ".cs"; + string filename = LowerUnderscoreToUpperCamelProtocWay(basename); + outputs[0] = Path.Combine(outdir, filename) + ".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"; + string grpcdir = protoItem.GetMetadata(Metadata.GrpcOutputDir); + filename = LowerUnderscoreToUpperCamelGrpcWay(basename); + outputs[1] = Path.Combine( + grpcdir != "" ? grpcdir : outdir, filename) + "Grpc.cs"; } return outputs; } - string LowerUnderscoreToUpperCamel(string str) + // This is how the gRPC codegen currently construct its output filename. + // See src/compiler/generator_helpers.h:118. + string LowerUnderscoreToUpperCamelGrpcWay(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) @@ -109,6 +108,26 @@ namespace Grpc.Tools } return result.ToString(); } + + // This is how the protoc codegen constructs its output filename. + // See protobuf/compiler/csharp/csharp_helpers.cc:356. + // Note that protoc explicitly discards non-ASCII letters. + string LowerUnderscoreToUpperCamelProtocWay(string str) + { + var result = new StringBuilder(str.Length, str.Length); + bool cap = true; + foreach (char c in str) + { + char upperC = char.ToUpperInvariant(c); + bool isAsciiLetter = 'A' <= upperC && upperC <= 'Z'; + if (isAsciiLetter || ('0' <= c && c <= '9')) + { + result.Append(cap ? upperC : c); + } + cap = !isAsciiLetter; + } + return result.ToString(); + } }; // C++ generator services.