xref: /aosp_15_r20/external/grpc-grpc/src/csharp/Grpc.Tools/GeneratorServices.cs (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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