1*cc02d7e2SAndroid Build Coastguard Worker #region Copyright notice and license 2*cc02d7e2SAndroid Build Coastguard Worker 3*cc02d7e2SAndroid Build Coastguard Worker // Copyright 2018 gRPC authors. 4*cc02d7e2SAndroid Build Coastguard Worker // 5*cc02d7e2SAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License"); 6*cc02d7e2SAndroid Build Coastguard Worker // you may not use this file except in compliance with the License. 7*cc02d7e2SAndroid Build Coastguard Worker // You may obtain a copy of the License at 8*cc02d7e2SAndroid Build Coastguard Worker // 9*cc02d7e2SAndroid Build Coastguard Worker // http://www.apache.org/licenses/LICENSE-2.0 10*cc02d7e2SAndroid Build Coastguard Worker // 11*cc02d7e2SAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software 12*cc02d7e2SAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS, 13*cc02d7e2SAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*cc02d7e2SAndroid Build Coastguard Worker // See the License for the specific language governing permissions and 15*cc02d7e2SAndroid Build Coastguard Worker // limitations under the License. 16*cc02d7e2SAndroid Build Coastguard Worker 17*cc02d7e2SAndroid Build Coastguard Worker #endregion 18*cc02d7e2SAndroid Build Coastguard Worker 19*cc02d7e2SAndroid Build Coastguard Worker using System.IO; 20*cc02d7e2SAndroid Build Coastguard Worker using System.Text; 21*cc02d7e2SAndroid Build Coastguard Worker using Microsoft.Build.Framework; 22*cc02d7e2SAndroid Build Coastguard Worker using Microsoft.Build.Utilities; 23*cc02d7e2SAndroid Build Coastguard Worker 24*cc02d7e2SAndroid Build Coastguard Worker namespace Grpc.Tools 25*cc02d7e2SAndroid Build Coastguard Worker { 26*cc02d7e2SAndroid Build Coastguard Worker // Abstract class for language-specific analysis behavior, such 27*cc02d7e2SAndroid Build Coastguard Worker // as guessing the generated files the same way protoc does. 28*cc02d7e2SAndroid Build Coastguard Worker internal abstract class GeneratorServices 29*cc02d7e2SAndroid Build Coastguard Worker { 30*cc02d7e2SAndroid Build Coastguard Worker protected readonly TaskLoggingHelper Log; GeneratorServices(TaskLoggingHelper log)31*cc02d7e2SAndroid Build Coastguard Worker protected GeneratorServices(TaskLoggingHelper log) { Log = log; } 32*cc02d7e2SAndroid Build Coastguard Worker 33*cc02d7e2SAndroid Build Coastguard Worker // Obtain a service for the given language (csharp, cpp). GetForLanguage(string lang, TaskLoggingHelper log)34*cc02d7e2SAndroid Build Coastguard Worker public static GeneratorServices GetForLanguage(string lang, TaskLoggingHelper log) 35*cc02d7e2SAndroid Build Coastguard Worker { 36*cc02d7e2SAndroid Build Coastguard Worker if (lang.EqualNoCase("csharp")) { return new CSharpGeneratorServices(log); } 37*cc02d7e2SAndroid Build Coastguard Worker if (lang.EqualNoCase("cpp")) { return new CppGeneratorServices(log); } 38*cc02d7e2SAndroid Build Coastguard Worker 39*cc02d7e2SAndroid Build Coastguard Worker log.LogError("Invalid value '{0}' for task property 'Generator'. " + 40*cc02d7e2SAndroid Build Coastguard Worker "Supported generator languages: CSharp, Cpp.", lang); 41*cc02d7e2SAndroid Build Coastguard Worker return null; 42*cc02d7e2SAndroid Build Coastguard Worker } 43*cc02d7e2SAndroid Build Coastguard Worker 44*cc02d7e2SAndroid Build Coastguard Worker // Guess whether item's metadata suggests gRPC stub generation. 45*cc02d7e2SAndroid Build Coastguard Worker // When "gRPCServices" is not defined, assume gRPC is not used. 46*cc02d7e2SAndroid Build Coastguard Worker // When defined, C# uses "none" to skip gRPC, C++ uses "false", so 47*cc02d7e2SAndroid Build Coastguard Worker // recognize both. Since the value is tightly coupled to the scripts, 48*cc02d7e2SAndroid Build Coastguard Worker // we do not try to validate the value; scripts take care of that. 49*cc02d7e2SAndroid Build Coastguard Worker // It is safe to assume that gRPC is requested for any other value. GrpcOutputPossible(ITaskItem proto)50*cc02d7e2SAndroid Build Coastguard Worker protected bool GrpcOutputPossible(ITaskItem proto) 51*cc02d7e2SAndroid Build Coastguard Worker { 52*cc02d7e2SAndroid Build Coastguard Worker string gsm = proto.GetMetadata(Metadata.GrpcServices); 53*cc02d7e2SAndroid Build Coastguard Worker return !gsm.EqualNoCase("") && !gsm.EqualNoCase("none") 54*cc02d7e2SAndroid Build Coastguard Worker && !gsm.EqualNoCase("false"); 55*cc02d7e2SAndroid Build Coastguard Worker } 56*cc02d7e2SAndroid Build Coastguard Worker 57*cc02d7e2SAndroid Build Coastguard Worker // Update OutputDir and GrpcOutputDir for the item and all subsequent 58*cc02d7e2SAndroid Build Coastguard Worker // targets using this item. This should only be done if the real 59*cc02d7e2SAndroid Build Coastguard Worker // output directories for protoc should be modified. PatchOutputDirectory(ITaskItem protoItem)60*cc02d7e2SAndroid Build Coastguard Worker public virtual ITaskItem PatchOutputDirectory(ITaskItem protoItem) 61*cc02d7e2SAndroid Build Coastguard Worker { 62*cc02d7e2SAndroid Build Coastguard Worker // Nothing to do 63*cc02d7e2SAndroid Build Coastguard Worker return protoItem; 64*cc02d7e2SAndroid Build Coastguard Worker } 65*cc02d7e2SAndroid Build Coastguard Worker GetPossibleOutputs(ITaskItem protoItem)66*cc02d7e2SAndroid Build Coastguard Worker public abstract string[] GetPossibleOutputs(ITaskItem protoItem); 67*cc02d7e2SAndroid Build Coastguard Worker 68*cc02d7e2SAndroid Build Coastguard Worker // Calculate part of proto path relative to root. Protoc is very picky 69*cc02d7e2SAndroid Build Coastguard Worker // about them matching exactly, so can be we. Expect root be exact prefix 70*cc02d7e2SAndroid Build Coastguard Worker // to proto, minus some slash normalization. GetRelativeDir(string root, string proto, TaskLoggingHelper log)71*cc02d7e2SAndroid Build Coastguard Worker protected static string GetRelativeDir(string root, string proto, TaskLoggingHelper log) 72*cc02d7e2SAndroid Build Coastguard Worker { 73*cc02d7e2SAndroid Build Coastguard Worker string protoDir = Path.GetDirectoryName(proto); 74*cc02d7e2SAndroid Build Coastguard Worker string rootDir = EndWithSlash(Path.GetDirectoryName(EndWithSlash(root))); 75*cc02d7e2SAndroid Build Coastguard Worker if (rootDir == s_dotSlash) 76*cc02d7e2SAndroid Build Coastguard Worker { 77*cc02d7e2SAndroid Build Coastguard Worker // Special case, otherwise we can return "./" instead of "" below! 78*cc02d7e2SAndroid Build Coastguard Worker return protoDir; 79*cc02d7e2SAndroid Build Coastguard Worker } 80*cc02d7e2SAndroid Build Coastguard Worker if (Platform.IsFsCaseInsensitive) 81*cc02d7e2SAndroid Build Coastguard Worker { 82*cc02d7e2SAndroid Build Coastguard Worker protoDir = protoDir.ToLowerInvariant(); 83*cc02d7e2SAndroid Build Coastguard Worker rootDir = rootDir.ToLowerInvariant(); 84*cc02d7e2SAndroid Build Coastguard Worker } 85*cc02d7e2SAndroid Build Coastguard Worker protoDir = EndWithSlash(protoDir); 86*cc02d7e2SAndroid Build Coastguard Worker if (!protoDir.StartsWith(rootDir)) 87*cc02d7e2SAndroid Build Coastguard Worker { 88*cc02d7e2SAndroid Build Coastguard Worker log.LogWarning("Protobuf item '{0}' has the ProtoRoot metadata '{1}' " + 89*cc02d7e2SAndroid Build Coastguard Worker "which is not prefix to its path. Cannot compute relative path.", 90*cc02d7e2SAndroid Build Coastguard Worker proto, root); 91*cc02d7e2SAndroid Build Coastguard Worker return ""; 92*cc02d7e2SAndroid Build Coastguard Worker } 93*cc02d7e2SAndroid Build Coastguard Worker return protoDir.Substring(rootDir.Length); 94*cc02d7e2SAndroid Build Coastguard Worker } 95*cc02d7e2SAndroid Build Coastguard Worker 96*cc02d7e2SAndroid Build Coastguard Worker // './' or '.\', normalized per system. 97*cc02d7e2SAndroid Build Coastguard Worker protected static string s_dotSlash = "." + Path.DirectorySeparatorChar; 98*cc02d7e2SAndroid Build Coastguard Worker EndWithSlash(string str)99*cc02d7e2SAndroid Build Coastguard Worker protected static string EndWithSlash(string str) 100*cc02d7e2SAndroid Build Coastguard Worker { 101*cc02d7e2SAndroid Build Coastguard Worker if (str == "") 102*cc02d7e2SAndroid Build Coastguard Worker { 103*cc02d7e2SAndroid Build Coastguard Worker return s_dotSlash; 104*cc02d7e2SAndroid Build Coastguard Worker } 105*cc02d7e2SAndroid Build Coastguard Worker 106*cc02d7e2SAndroid Build Coastguard Worker if (str[str.Length - 1] != '\\' && str[str.Length - 1] != '/') 107*cc02d7e2SAndroid Build Coastguard Worker { 108*cc02d7e2SAndroid Build Coastguard Worker return str + Path.DirectorySeparatorChar; 109*cc02d7e2SAndroid Build Coastguard Worker } 110*cc02d7e2SAndroid Build Coastguard Worker 111*cc02d7e2SAndroid Build Coastguard Worker return str; 112*cc02d7e2SAndroid Build Coastguard Worker } 113*cc02d7e2SAndroid Build Coastguard Worker }; 114*cc02d7e2SAndroid Build Coastguard Worker 115*cc02d7e2SAndroid Build Coastguard Worker // C# generator services. 116*cc02d7e2SAndroid Build Coastguard Worker internal class CSharpGeneratorServices : GeneratorServices 117*cc02d7e2SAndroid Build Coastguard Worker { CSharpGeneratorServices(TaskLoggingHelper log)118*cc02d7e2SAndroid Build Coastguard Worker public CSharpGeneratorServices(TaskLoggingHelper log) : base(log) { } 119*cc02d7e2SAndroid Build Coastguard Worker PatchOutputDirectory(ITaskItem protoItem)120*cc02d7e2SAndroid Build Coastguard Worker public override ITaskItem PatchOutputDirectory(ITaskItem protoItem) 121*cc02d7e2SAndroid Build Coastguard Worker { 122*cc02d7e2SAndroid Build Coastguard Worker var outputItem = new TaskItem(protoItem); 123*cc02d7e2SAndroid Build Coastguard Worker string root = outputItem.GetMetadata(Metadata.ProtoRoot); 124*cc02d7e2SAndroid Build Coastguard Worker string proto = outputItem.ItemSpec; 125*cc02d7e2SAndroid Build Coastguard Worker string relative = GetRelativeDir(root, proto, Log); 126*cc02d7e2SAndroid Build Coastguard Worker 127*cc02d7e2SAndroid Build Coastguard Worker string outdir = outputItem.GetMetadata(Metadata.OutputDir); 128*cc02d7e2SAndroid Build Coastguard Worker string pathStem = Path.Combine(outdir, relative); 129*cc02d7e2SAndroid Build Coastguard Worker outputItem.SetMetadata(Metadata.OutputDir, pathStem); 130*cc02d7e2SAndroid Build Coastguard Worker 131*cc02d7e2SAndroid Build Coastguard Worker // Override outdir if GrpcOutputDir present, default to proto output. 132*cc02d7e2SAndroid Build Coastguard Worker string grpcdir = outputItem.GetMetadata(Metadata.GrpcOutputDir); 133*cc02d7e2SAndroid Build Coastguard Worker if (grpcdir != "") 134*cc02d7e2SAndroid Build Coastguard Worker { 135*cc02d7e2SAndroid Build Coastguard Worker pathStem = Path.Combine(grpcdir, relative); 136*cc02d7e2SAndroid Build Coastguard Worker } 137*cc02d7e2SAndroid Build Coastguard Worker outputItem.SetMetadata(Metadata.GrpcOutputDir, pathStem); 138*cc02d7e2SAndroid Build Coastguard Worker return outputItem; 139*cc02d7e2SAndroid Build Coastguard Worker } 140*cc02d7e2SAndroid Build Coastguard Worker GetPossibleOutputs(ITaskItem protoItem)141*cc02d7e2SAndroid Build Coastguard Worker public override string[] GetPossibleOutputs(ITaskItem protoItem) 142*cc02d7e2SAndroid Build Coastguard Worker { 143*cc02d7e2SAndroid Build Coastguard Worker bool doGrpc = GrpcOutputPossible(protoItem); 144*cc02d7e2SAndroid Build Coastguard Worker var outputs = new string[doGrpc ? 2 : 1]; 145*cc02d7e2SAndroid Build Coastguard Worker string proto = protoItem.ItemSpec; 146*cc02d7e2SAndroid Build Coastguard Worker string basename = Path.GetFileNameWithoutExtension(proto); 147*cc02d7e2SAndroid Build Coastguard Worker string outdir = protoItem.GetMetadata(Metadata.OutputDir); 148*cc02d7e2SAndroid Build Coastguard Worker string filename = LowerUnderscoreToUpperCamelProtocWay(basename); 149*cc02d7e2SAndroid Build Coastguard Worker outputs[0] = Path.Combine(outdir, filename) + ".cs"; 150*cc02d7e2SAndroid Build Coastguard Worker 151*cc02d7e2SAndroid Build Coastguard Worker if (doGrpc) 152*cc02d7e2SAndroid Build Coastguard Worker { 153*cc02d7e2SAndroid Build Coastguard Worker string grpcdir = protoItem.GetMetadata(Metadata.GrpcOutputDir); 154*cc02d7e2SAndroid Build Coastguard Worker filename = LowerUnderscoreToUpperCamelGrpcWay(basename); 155*cc02d7e2SAndroid Build Coastguard Worker outputs[1] = Path.Combine(grpcdir, filename) + "Grpc.cs"; 156*cc02d7e2SAndroid Build Coastguard Worker } 157*cc02d7e2SAndroid Build Coastguard Worker return outputs; 158*cc02d7e2SAndroid Build Coastguard Worker } 159*cc02d7e2SAndroid Build Coastguard Worker 160*cc02d7e2SAndroid Build Coastguard Worker // This is how the gRPC codegen currently construct its output filename. 161*cc02d7e2SAndroid Build Coastguard Worker // See src/compiler/generator_helpers.h:118. LowerUnderscoreToUpperCamelGrpcWay(string str)162*cc02d7e2SAndroid Build Coastguard Worker string LowerUnderscoreToUpperCamelGrpcWay(string str) 163*cc02d7e2SAndroid Build Coastguard Worker { 164*cc02d7e2SAndroid Build Coastguard Worker var result = new StringBuilder(str.Length, str.Length); 165*cc02d7e2SAndroid Build Coastguard Worker bool cap = true; 166*cc02d7e2SAndroid Build Coastguard Worker foreach (char c in str) 167*cc02d7e2SAndroid Build Coastguard Worker { 168*cc02d7e2SAndroid Build Coastguard Worker if (c == '_') 169*cc02d7e2SAndroid Build Coastguard Worker { 170*cc02d7e2SAndroid Build Coastguard Worker cap = true; 171*cc02d7e2SAndroid Build Coastguard Worker } 172*cc02d7e2SAndroid Build Coastguard Worker else if (cap) 173*cc02d7e2SAndroid Build Coastguard Worker { 174*cc02d7e2SAndroid Build Coastguard Worker result.Append(char.ToUpperInvariant(c)); 175*cc02d7e2SAndroid Build Coastguard Worker cap = false; 176*cc02d7e2SAndroid Build Coastguard Worker } 177*cc02d7e2SAndroid Build Coastguard Worker else 178*cc02d7e2SAndroid Build Coastguard Worker { 179*cc02d7e2SAndroid Build Coastguard Worker result.Append(c); 180*cc02d7e2SAndroid Build Coastguard Worker } 181*cc02d7e2SAndroid Build Coastguard Worker } 182*cc02d7e2SAndroid Build Coastguard Worker return result.ToString(); 183*cc02d7e2SAndroid Build Coastguard Worker } 184*cc02d7e2SAndroid Build Coastguard Worker 185*cc02d7e2SAndroid Build Coastguard Worker // This is how the protoc codegen constructs its output filename. 186*cc02d7e2SAndroid Build Coastguard Worker // See protobuf/compiler/csharp/csharp_helpers.cc:137. 187*cc02d7e2SAndroid Build Coastguard Worker // Note that protoc explicitly discards non-ASCII letters. LowerUnderscoreToUpperCamelProtocWay(string str)188*cc02d7e2SAndroid Build Coastguard Worker string LowerUnderscoreToUpperCamelProtocWay(string str) 189*cc02d7e2SAndroid Build Coastguard Worker { 190*cc02d7e2SAndroid Build Coastguard Worker var result = new StringBuilder(str.Length, str.Length); 191*cc02d7e2SAndroid Build Coastguard Worker bool cap = true; 192*cc02d7e2SAndroid Build Coastguard Worker foreach (char c in str) 193*cc02d7e2SAndroid Build Coastguard Worker { 194*cc02d7e2SAndroid Build Coastguard Worker char upperC = char.ToUpperInvariant(c); 195*cc02d7e2SAndroid Build Coastguard Worker bool isAsciiLetter = 'A' <= upperC && upperC <= 'Z'; 196*cc02d7e2SAndroid Build Coastguard Worker if (isAsciiLetter || ('0' <= c && c <= '9')) 197*cc02d7e2SAndroid Build Coastguard Worker { 198*cc02d7e2SAndroid Build Coastguard Worker result.Append(cap ? upperC : c); 199*cc02d7e2SAndroid Build Coastguard Worker } 200*cc02d7e2SAndroid Build Coastguard Worker cap = !isAsciiLetter; 201*cc02d7e2SAndroid Build Coastguard Worker } 202*cc02d7e2SAndroid Build Coastguard Worker return result.ToString(); 203*cc02d7e2SAndroid Build Coastguard Worker } 204*cc02d7e2SAndroid Build Coastguard Worker }; 205*cc02d7e2SAndroid Build Coastguard Worker 206*cc02d7e2SAndroid Build Coastguard Worker // C++ generator services. 207*cc02d7e2SAndroid Build Coastguard Worker internal class CppGeneratorServices : GeneratorServices 208*cc02d7e2SAndroid Build Coastguard Worker { CppGeneratorServices(TaskLoggingHelper log)209*cc02d7e2SAndroid Build Coastguard Worker public CppGeneratorServices(TaskLoggingHelper log) : base(log) { } 210*cc02d7e2SAndroid Build Coastguard Worker GetPossibleOutputs(ITaskItem protoItem)211*cc02d7e2SAndroid Build Coastguard Worker public override string[] GetPossibleOutputs(ITaskItem protoItem) 212*cc02d7e2SAndroid Build Coastguard Worker { 213*cc02d7e2SAndroid Build Coastguard Worker bool doGrpc = GrpcOutputPossible(protoItem); 214*cc02d7e2SAndroid Build Coastguard Worker string root = protoItem.GetMetadata(Metadata.ProtoRoot); 215*cc02d7e2SAndroid Build Coastguard Worker string proto = protoItem.ItemSpec; 216*cc02d7e2SAndroid Build Coastguard Worker string filename = Path.GetFileNameWithoutExtension(proto); 217*cc02d7e2SAndroid Build Coastguard Worker // E. g., ("foo/", "foo/bar/x.proto") => "bar" 218*cc02d7e2SAndroid Build Coastguard Worker string relative = GetRelativeDir(root, proto, Log); 219*cc02d7e2SAndroid Build Coastguard Worker 220*cc02d7e2SAndroid Build Coastguard Worker var outputs = new string[doGrpc ? 4 : 2]; 221*cc02d7e2SAndroid Build Coastguard Worker string outdir = protoItem.GetMetadata(Metadata.OutputDir); 222*cc02d7e2SAndroid Build Coastguard Worker string fileStem = Path.Combine(outdir, relative, filename); 223*cc02d7e2SAndroid Build Coastguard Worker outputs[0] = fileStem + ".pb.cc"; 224*cc02d7e2SAndroid Build Coastguard Worker outputs[1] = fileStem + ".pb.h"; 225*cc02d7e2SAndroid Build Coastguard Worker if (doGrpc) 226*cc02d7e2SAndroid Build Coastguard Worker { 227*cc02d7e2SAndroid Build Coastguard Worker // Override outdir if GrpcOutputDir present, default to proto output. 228*cc02d7e2SAndroid Build Coastguard Worker outdir = protoItem.GetMetadata(Metadata.GrpcOutputDir); 229*cc02d7e2SAndroid Build Coastguard Worker if (outdir != "") 230*cc02d7e2SAndroid Build Coastguard Worker { 231*cc02d7e2SAndroid Build Coastguard Worker fileStem = Path.Combine(outdir, relative, filename); 232*cc02d7e2SAndroid Build Coastguard Worker } 233*cc02d7e2SAndroid Build Coastguard Worker outputs[2] = fileStem + ".grpc.pb.cc"; 234*cc02d7e2SAndroid Build Coastguard Worker outputs[3] = fileStem + ".grpc.pb.h"; 235*cc02d7e2SAndroid Build Coastguard Worker } 236*cc02d7e2SAndroid Build Coastguard Worker return outputs; 237*cc02d7e2SAndroid Build Coastguard Worker } 238*cc02d7e2SAndroid Build Coastguard Worker } 239*cc02d7e2SAndroid Build Coastguard Worker } 240