xref: /aosp_15_r20/external/grpc-grpc/src/csharp/Grpc.Tools/ProtoCompile.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;
20*cc02d7e2SAndroid Build Coastguard Worker using System.Collections.Generic;
21*cc02d7e2SAndroid Build Coastguard Worker using System.Text;
22*cc02d7e2SAndroid Build Coastguard Worker using System.Text.RegularExpressions;
23*cc02d7e2SAndroid Build Coastguard Worker using Microsoft.Build.Framework;
24*cc02d7e2SAndroid Build Coastguard Worker using Microsoft.Build.Utilities;
25*cc02d7e2SAndroid Build Coastguard Worker 
26*cc02d7e2SAndroid Build Coastguard Worker namespace Grpc.Tools
27*cc02d7e2SAndroid Build Coastguard Worker {
28*cc02d7e2SAndroid Build Coastguard Worker     /// <summary>
29*cc02d7e2SAndroid Build Coastguard Worker     /// Run Google proto compiler (protoc).
30*cc02d7e2SAndroid Build Coastguard Worker     ///
31*cc02d7e2SAndroid Build Coastguard Worker     /// After a successful run, the task reads the dependency file if specified
32*cc02d7e2SAndroid Build Coastguard Worker     /// to be saved by the compiler, and returns its output files.
33*cc02d7e2SAndroid Build Coastguard Worker     ///
34*cc02d7e2SAndroid Build Coastguard Worker     /// This task (unlike PrepareProtoCompile) does not attempt to guess anything
35*cc02d7e2SAndroid Build Coastguard Worker     /// about language-specific behavior of protoc, and therefore can be used for
36*cc02d7e2SAndroid Build Coastguard Worker     /// any language outputs.
37*cc02d7e2SAndroid Build Coastguard Worker     /// </summary>
38*cc02d7e2SAndroid Build Coastguard Worker     public class ProtoCompile : ToolTask
39*cc02d7e2SAndroid Build Coastguard Worker     {
40*cc02d7e2SAndroid Build Coastguard Worker         /*
41*cc02d7e2SAndroid Build Coastguard Worker 
42*cc02d7e2SAndroid Build Coastguard Worker         Usage: /home/kkm/work/protobuf/src/.libs/lt-protoc [OPTION] PROTO_FILES
43*cc02d7e2SAndroid Build Coastguard Worker         Parse PROTO_FILES and generate output based on the options given:
44*cc02d7e2SAndroid Build Coastguard Worker           -IPATH, --proto_path=PATH   Specify the directory in which to search for
45*cc02d7e2SAndroid Build Coastguard Worker                                       imports.  May be specified multiple times;
46*cc02d7e2SAndroid Build Coastguard Worker                                       directories will be searched in order.  If not
47*cc02d7e2SAndroid Build Coastguard Worker                                       given, the current working directory is used.
48*cc02d7e2SAndroid Build Coastguard Worker           --version                   Show version info and exit.
49*cc02d7e2SAndroid Build Coastguard Worker           -h, --help                  Show this text and exit.
50*cc02d7e2SAndroid Build Coastguard Worker           --encode=MESSAGE_TYPE       Read a text-format message of the given type
51*cc02d7e2SAndroid Build Coastguard Worker                                       from standard input and write it in binary
52*cc02d7e2SAndroid Build Coastguard Worker                                       to standard output.  The message type must
53*cc02d7e2SAndroid Build Coastguard Worker                                       be defined in PROTO_FILES or their imports.
54*cc02d7e2SAndroid Build Coastguard Worker           --decode=MESSAGE_TYPE       Read a binary message of the given type from
55*cc02d7e2SAndroid Build Coastguard Worker                                       standard input and write it in text format
56*cc02d7e2SAndroid Build Coastguard Worker                                       to standard output.  The message type must
57*cc02d7e2SAndroid Build Coastguard Worker                                       be defined in PROTO_FILES or their imports.
58*cc02d7e2SAndroid Build Coastguard Worker           --decode_raw                Read an arbitrary protocol message from
59*cc02d7e2SAndroid Build Coastguard Worker                                       standard input and write the raw tag/value
60*cc02d7e2SAndroid Build Coastguard Worker                                       pairs in text format to standard output.  No
61*cc02d7e2SAndroid Build Coastguard Worker                                       PROTO_FILES should be given when using this
62*cc02d7e2SAndroid Build Coastguard Worker                                       flag.
63*cc02d7e2SAndroid Build Coastguard Worker           --descriptor_set_in=FILES   Specifies a delimited list of FILES
64*cc02d7e2SAndroid Build Coastguard Worker                                       each containing a FileDescriptorSet (a
65*cc02d7e2SAndroid Build Coastguard Worker                                       protocol buffer defined in descriptor.proto).
66*cc02d7e2SAndroid Build Coastguard Worker                                       The FileDescriptor for each of the PROTO_FILES
67*cc02d7e2SAndroid Build Coastguard Worker                                       provided will be loaded from these
68*cc02d7e2SAndroid Build Coastguard Worker                                       FileDescriptorSets. If a FileDescriptor
69*cc02d7e2SAndroid Build Coastguard Worker                                       appears multiple times, the first occurrence
70*cc02d7e2SAndroid Build Coastguard Worker                                       will be used.
71*cc02d7e2SAndroid Build Coastguard Worker           -oFILE,                     Writes a FileDescriptorSet (a protocol buffer,
72*cc02d7e2SAndroid Build Coastguard Worker             --descriptor_set_out=FILE defined in descriptor.proto) containing all of
73*cc02d7e2SAndroid Build Coastguard Worker                                       the input files to FILE.
74*cc02d7e2SAndroid Build Coastguard Worker           --include_imports           When using --descriptor_set_out, also include
75*cc02d7e2SAndroid Build Coastguard Worker                                       all dependencies of the input files in the
76*cc02d7e2SAndroid Build Coastguard Worker                                       set, so that the set is self-contained.
77*cc02d7e2SAndroid Build Coastguard Worker           --include_source_info       When using --descriptor_set_out, do not strip
78*cc02d7e2SAndroid Build Coastguard Worker                                       SourceCodeInfo from the FileDescriptorProto.
79*cc02d7e2SAndroid Build Coastguard Worker                                       This results in vastly larger descriptors that
80*cc02d7e2SAndroid Build Coastguard Worker                                       include information about the original
81*cc02d7e2SAndroid Build Coastguard Worker                                       location of each decl in the source file as
82*cc02d7e2SAndroid Build Coastguard Worker                                       well as surrounding comments.
83*cc02d7e2SAndroid Build Coastguard Worker           --dependency_out=FILE       Write a dependency output file in the format
84*cc02d7e2SAndroid Build Coastguard Worker                                       expected by make. This writes the transitive
85*cc02d7e2SAndroid Build Coastguard Worker                                       set of input file paths to FILE
86*cc02d7e2SAndroid Build Coastguard Worker           --error_format=FORMAT       Set the format in which to print errors.
87*cc02d7e2SAndroid Build Coastguard Worker                                       FORMAT may be 'gcc' (the default) or 'msvs'
88*cc02d7e2SAndroid Build Coastguard Worker                                       (Microsoft Visual Studio format).
89*cc02d7e2SAndroid Build Coastguard Worker           --print_free_field_numbers  Print the free field numbers of the messages
90*cc02d7e2SAndroid Build Coastguard Worker                                       defined in the given proto files. Groups share
91*cc02d7e2SAndroid Build Coastguard Worker                                       the same field number space with the parent
92*cc02d7e2SAndroid Build Coastguard Worker                                       message. Extension ranges are counted as
93*cc02d7e2SAndroid Build Coastguard Worker                                       occupied fields numbers.
94*cc02d7e2SAndroid Build Coastguard Worker 
95*cc02d7e2SAndroid Build Coastguard Worker           --plugin=EXECUTABLE         Specifies a plugin executable to use.
96*cc02d7e2SAndroid Build Coastguard Worker                                       Normally, protoc searches the PATH for
97*cc02d7e2SAndroid Build Coastguard Worker                                       plugins, but you may specify additional
98*cc02d7e2SAndroid Build Coastguard Worker                                       executables not in the path using this flag.
99*cc02d7e2SAndroid Build Coastguard Worker                                       Additionally, EXECUTABLE may be of the form
100*cc02d7e2SAndroid Build Coastguard Worker                                       NAME=PATH, in which case the given plugin name
101*cc02d7e2SAndroid Build Coastguard Worker                                       is mapped to the given executable even if
102*cc02d7e2SAndroid Build Coastguard Worker                                       the executable's own name differs.
103*cc02d7e2SAndroid Build Coastguard Worker           --cpp_out=OUT_DIR           Generate C++ header and source.
104*cc02d7e2SAndroid Build Coastguard Worker           --csharp_out=OUT_DIR        Generate C# source file.
105*cc02d7e2SAndroid Build Coastguard Worker           --java_out=OUT_DIR          Generate Java source file.
106*cc02d7e2SAndroid Build Coastguard Worker           --javanano_out=OUT_DIR      Generate Java Nano source file.
107*cc02d7e2SAndroid Build Coastguard Worker           --js_out=OUT_DIR            Generate JavaScript source.
108*cc02d7e2SAndroid Build Coastguard Worker           --objc_out=OUT_DIR          Generate Objective C header and source.
109*cc02d7e2SAndroid Build Coastguard Worker           --php_out=OUT_DIR           Generate PHP source file.
110*cc02d7e2SAndroid Build Coastguard Worker           --python_out=OUT_DIR        Generate Python source file.
111*cc02d7e2SAndroid Build Coastguard Worker           --ruby_out=OUT_DIR          Generate Ruby source file.
112*cc02d7e2SAndroid Build Coastguard Worker           @<filename>                 Read options and filenames from file. If a
113*cc02d7e2SAndroid Build Coastguard Worker                                       relative file path is specified, the file
114*cc02d7e2SAndroid Build Coastguard Worker                                       will be searched in the working directory.
115*cc02d7e2SAndroid Build Coastguard Worker                                       The --proto_path option will not affect how
116*cc02d7e2SAndroid Build Coastguard Worker                                       this argument file is searched. Content of
117*cc02d7e2SAndroid Build Coastguard Worker                                       the file will be expanded in the position of
118*cc02d7e2SAndroid Build Coastguard Worker                                       @<filename> as in the argument list. Note
119*cc02d7e2SAndroid Build Coastguard Worker                                       that shell expansion is not applied to the
120*cc02d7e2SAndroid Build Coastguard Worker                                       content of the file (i.e., you cannot use
121*cc02d7e2SAndroid Build Coastguard Worker                                       quotes, wildcards, escapes, commands, etc.).
122*cc02d7e2SAndroid Build Coastguard Worker                                       Each line corresponds to a single argument,
123*cc02d7e2SAndroid Build Coastguard Worker                                       even if it contains spaces.
124*cc02d7e2SAndroid Build Coastguard Worker         */
125*cc02d7e2SAndroid Build Coastguard Worker         static string[] s_supportedGenerators = new[] { "cpp", "csharp", "java",
126*cc02d7e2SAndroid Build Coastguard Worker                                                         "javanano", "js", "objc",
127*cc02d7e2SAndroid Build Coastguard Worker                                                         "php", "python", "ruby" };
128*cc02d7e2SAndroid Build Coastguard Worker 
129*cc02d7e2SAndroid Build Coastguard Worker         static readonly TimeSpan s_regexTimeout = TimeSpan.FromSeconds(1);
130*cc02d7e2SAndroid Build Coastguard Worker 
131*cc02d7e2SAndroid Build Coastguard Worker         static readonly List<ErrorListFilter> s_errorListFilters = new List<ErrorListFilter>()
132*cc02d7e2SAndroid Build Coastguard Worker         {
133*cc02d7e2SAndroid Build Coastguard Worker             // Example warning with location
134*cc02d7e2SAndroid Build Coastguard Worker             //../Protos/greet.proto(19) : warning in column=5 : warning : When enum name is stripped and label is PascalCased (Zero),
135*cc02d7e2SAndroid Build Coastguard Worker             // this value label conflicts with Zero. This will make the proto fail to compile for some languages, such as C#.
136*cc02d7e2SAndroid Build Coastguard Worker             new ErrorListFilter
137*cc02d7e2SAndroid Build Coastguard Worker             {
138*cc02d7e2SAndroid Build Coastguard Worker                 Pattern = new Regex(
139*cc02d7e2SAndroid Build Coastguard Worker                     pattern: "^(?'FILENAME'.+?)\\((?'LINE'\\d+)\\) ?: ?warning in column=(?'COLUMN'\\d+) ?: ?(?'TEXT'.*)",
140*cc02d7e2SAndroid Build Coastguard Worker                     options: RegexOptions.Compiled | RegexOptions.IgnoreCase,
141*cc02d7e2SAndroid Build Coastguard Worker                     matchTimeout: s_regexTimeout),
142*cc02d7e2SAndroid Build Coastguard Worker                 LogAction = (log, match) =>
143*cc02d7e2SAndroid Build Coastguard Worker                 {
144*cc02d7e2SAndroid Build Coastguard Worker                     int.TryParse(match.Groups["LINE"].Value, out var line);
145*cc02d7e2SAndroid Build Coastguard Worker                     int.TryParse(match.Groups["COLUMN"].Value, out var column);
146*cc02d7e2SAndroid Build Coastguard Worker 
147*cc02d7e2SAndroid Build Coastguard Worker                     log.LogWarning(
148*cc02d7e2SAndroid Build Coastguard Worker                         subcategory: null,
149*cc02d7e2SAndroid Build Coastguard Worker                         warningCode: null,
150*cc02d7e2SAndroid Build Coastguard Worker                         helpKeyword: null,
151*cc02d7e2SAndroid Build Coastguard Worker                         file: match.Groups["FILENAME"].Value,
152*cc02d7e2SAndroid Build Coastguard Worker                         lineNumber: line,
153*cc02d7e2SAndroid Build Coastguard Worker                         columnNumber: column,
154*cc02d7e2SAndroid Build Coastguard Worker                         endLineNumber: 0,
155*cc02d7e2SAndroid Build Coastguard Worker                         endColumnNumber: 0,
156*cc02d7e2SAndroid Build Coastguard Worker                         message: match.Groups["TEXT"].Value);
157*cc02d7e2SAndroid Build Coastguard Worker                 }
158*cc02d7e2SAndroid Build Coastguard Worker             },
159*cc02d7e2SAndroid Build Coastguard Worker 
160*cc02d7e2SAndroid Build Coastguard Worker             // Example error with location
161*cc02d7e2SAndroid Build Coastguard Worker             //../Protos/greet.proto(14) : error in column=10: "name" is already defined in "Greet.HelloRequest".
162*cc02d7e2SAndroid Build Coastguard Worker             new ErrorListFilter
163*cc02d7e2SAndroid Build Coastguard Worker             {
164*cc02d7e2SAndroid Build Coastguard Worker                 Pattern = new Regex(
165*cc02d7e2SAndroid Build Coastguard Worker                     pattern: "^(?'FILENAME'.+?)\\((?'LINE'\\d+)\\) ?: ?error in column=(?'COLUMN'\\d+) ?: ?(?'TEXT'.*)",
166*cc02d7e2SAndroid Build Coastguard Worker                     options: RegexOptions.Compiled | RegexOptions.IgnoreCase,
167*cc02d7e2SAndroid Build Coastguard Worker                     matchTimeout: s_regexTimeout),
168*cc02d7e2SAndroid Build Coastguard Worker                 LogAction = (log, match) =>
169*cc02d7e2SAndroid Build Coastguard Worker                 {
170*cc02d7e2SAndroid Build Coastguard Worker                     int.TryParse(match.Groups["LINE"].Value, out var line);
171*cc02d7e2SAndroid Build Coastguard Worker                     int.TryParse(match.Groups["COLUMN"].Value, out var column);
172*cc02d7e2SAndroid Build Coastguard Worker 
173*cc02d7e2SAndroid Build Coastguard Worker                     log.LogError(
174*cc02d7e2SAndroid Build Coastguard Worker                         subcategory: null,
175*cc02d7e2SAndroid Build Coastguard Worker                         errorCode: null,
176*cc02d7e2SAndroid Build Coastguard Worker                         helpKeyword: null,
177*cc02d7e2SAndroid Build Coastguard Worker                         file: match.Groups["FILENAME"].Value,
178*cc02d7e2SAndroid Build Coastguard Worker                         lineNumber: line,
179*cc02d7e2SAndroid Build Coastguard Worker                         columnNumber: column,
180*cc02d7e2SAndroid Build Coastguard Worker                         endLineNumber: 0,
181*cc02d7e2SAndroid Build Coastguard Worker                         endColumnNumber: 0,
182*cc02d7e2SAndroid Build Coastguard Worker                         message: match.Groups["TEXT"].Value);
183*cc02d7e2SAndroid Build Coastguard Worker                 }
184*cc02d7e2SAndroid Build Coastguard Worker             },
185*cc02d7e2SAndroid Build Coastguard Worker 
186*cc02d7e2SAndroid Build Coastguard Worker             // Example warning without location
187*cc02d7e2SAndroid Build Coastguard Worker             //../Protos/greet.proto: warning: Import google/protobuf/empty.proto but not used.
188*cc02d7e2SAndroid Build Coastguard Worker             new ErrorListFilter
189*cc02d7e2SAndroid Build Coastguard Worker             {
190*cc02d7e2SAndroid Build Coastguard Worker                 Pattern = new Regex(
191*cc02d7e2SAndroid Build Coastguard Worker                     pattern: "^(?'FILENAME'.+?): ?warning: ?(?'TEXT'.*)",
192*cc02d7e2SAndroid Build Coastguard Worker                     options: RegexOptions.Compiled | RegexOptions.IgnoreCase,
193*cc02d7e2SAndroid Build Coastguard Worker                     matchTimeout: s_regexTimeout),
194*cc02d7e2SAndroid Build Coastguard Worker                 LogAction = (log, match) =>
195*cc02d7e2SAndroid Build Coastguard Worker                 {
196*cc02d7e2SAndroid Build Coastguard Worker                     log.LogWarning(
197*cc02d7e2SAndroid Build Coastguard Worker                         subcategory: null,
198*cc02d7e2SAndroid Build Coastguard Worker                         warningCode: null,
199*cc02d7e2SAndroid Build Coastguard Worker                         helpKeyword: null,
200*cc02d7e2SAndroid Build Coastguard Worker                         file: match.Groups["FILENAME"].Value,
201*cc02d7e2SAndroid Build Coastguard Worker                         lineNumber: 0,
202*cc02d7e2SAndroid Build Coastguard Worker                         columnNumber: 0,
203*cc02d7e2SAndroid Build Coastguard Worker                         endLineNumber: 0,
204*cc02d7e2SAndroid Build Coastguard Worker                         endColumnNumber: 0,
205*cc02d7e2SAndroid Build Coastguard Worker                         message: match.Groups["TEXT"].Value);
206*cc02d7e2SAndroid Build Coastguard Worker                 }
207*cc02d7e2SAndroid Build Coastguard Worker             },
208*cc02d7e2SAndroid Build Coastguard Worker 
209*cc02d7e2SAndroid Build Coastguard Worker             // Example warning from plugins that use GOOGLE_LOG
210*cc02d7e2SAndroid Build Coastguard Worker             // [libprotobuf WARNING T:\altsrc\..\csharp_enum.cc:74] Duplicate enum value Work (originally Work) in PhoneType; adding underscore to distinguish
211*cc02d7e2SAndroid Build Coastguard Worker             new ErrorListFilter
212*cc02d7e2SAndroid Build Coastguard Worker             {
213*cc02d7e2SAndroid Build Coastguard Worker                 Pattern = new Regex(
214*cc02d7e2SAndroid Build Coastguard Worker                     pattern: "^\\[.+? WARNING (?'FILENAME'.+?):(?'LINE'\\d+?)\\] ?(?'TEXT'.*)",
215*cc02d7e2SAndroid Build Coastguard Worker                     options: RegexOptions.Compiled | RegexOptions.IgnoreCase,
216*cc02d7e2SAndroid Build Coastguard Worker                     matchTimeout: s_regexTimeout),
217*cc02d7e2SAndroid Build Coastguard Worker                 LogAction = (log, match) =>
218*cc02d7e2SAndroid Build Coastguard Worker                 {
219*cc02d7e2SAndroid Build Coastguard Worker                     // The filename and line logged by the plugins may not be useful to the
220*cc02d7e2SAndroid Build Coastguard Worker                     // end user as they are not the location in the proto file but rather
221*cc02d7e2SAndroid Build Coastguard Worker                     // in the source code for the plugin. Log them anyway as they may help in
222*cc02d7e2SAndroid Build Coastguard Worker                     // diagnostics.
223*cc02d7e2SAndroid Build Coastguard Worker                     int.TryParse(match.Groups["LINE"].Value, out var line);
224*cc02d7e2SAndroid Build Coastguard Worker                     log.LogWarning(
225*cc02d7e2SAndroid Build Coastguard Worker                         subcategory: null,
226*cc02d7e2SAndroid Build Coastguard Worker                         warningCode: null,
227*cc02d7e2SAndroid Build Coastguard Worker                         helpKeyword: null,
228*cc02d7e2SAndroid Build Coastguard Worker                         file: match.Groups["FILENAME"].Value,
229*cc02d7e2SAndroid Build Coastguard Worker                         lineNumber: line,
230*cc02d7e2SAndroid Build Coastguard Worker                         columnNumber: 0,
231*cc02d7e2SAndroid Build Coastguard Worker                         endLineNumber: 0,
232*cc02d7e2SAndroid Build Coastguard Worker                         endColumnNumber: 0,
233*cc02d7e2SAndroid Build Coastguard Worker                         message: match.Groups["TEXT"].Value);
234*cc02d7e2SAndroid Build Coastguard Worker                 }
235*cc02d7e2SAndroid Build Coastguard Worker             },
236*cc02d7e2SAndroid Build Coastguard Worker 
237*cc02d7e2SAndroid Build Coastguard Worker             // Example error from plugins that use GOOGLE_LOG
238*cc02d7e2SAndroid Build Coastguard Worker             // [libprotobuf ERROR T:\path\...\filename:23] Some message
239*cc02d7e2SAndroid Build Coastguard Worker             // [libprotobuf FATAL T:\path\...\filename:23] Some message
240*cc02d7e2SAndroid Build Coastguard Worker             new ErrorListFilter
241*cc02d7e2SAndroid Build Coastguard Worker             {
242*cc02d7e2SAndroid Build Coastguard Worker                 Pattern = new Regex(
243*cc02d7e2SAndroid Build Coastguard Worker                     pattern: "^\\[.+? (?'LEVEL'ERROR|FATAL) (?'FILENAME'.+?):(?'LINE'\\d+?)\\] ?(?'TEXT'.*)",
244*cc02d7e2SAndroid Build Coastguard Worker                     options: RegexOptions.Compiled | RegexOptions.IgnoreCase,
245*cc02d7e2SAndroid Build Coastguard Worker                     matchTimeout: s_regexTimeout),
246*cc02d7e2SAndroid Build Coastguard Worker                 LogAction = (log, match) =>
247*cc02d7e2SAndroid Build Coastguard Worker                 {
248*cc02d7e2SAndroid Build Coastguard Worker                     // The filename and line logged by the plugins may not be useful to the
249*cc02d7e2SAndroid Build Coastguard Worker                     // end user as they are not the location in the proto file but rather
250*cc02d7e2SAndroid Build Coastguard Worker                     // in the source code for the plugin. Log them anyway as they may help in
251*cc02d7e2SAndroid Build Coastguard Worker                     // diagnostics.
252*cc02d7e2SAndroid Build Coastguard Worker                     int.TryParse(match.Groups["LINE"].Value, out var line);
253*cc02d7e2SAndroid Build Coastguard Worker                     log.LogError(
254*cc02d7e2SAndroid Build Coastguard Worker                         subcategory: null,
255*cc02d7e2SAndroid Build Coastguard Worker                         errorCode: null,
256*cc02d7e2SAndroid Build Coastguard Worker                         helpKeyword: null,
257*cc02d7e2SAndroid Build Coastguard Worker                         file: match.Groups["FILENAME"].Value,
258*cc02d7e2SAndroid Build Coastguard Worker                         lineNumber: line,
259*cc02d7e2SAndroid Build Coastguard Worker                         columnNumber: 0,
260*cc02d7e2SAndroid Build Coastguard Worker                         endLineNumber: 0,
261*cc02d7e2SAndroid Build Coastguard Worker                         endColumnNumber: 0,
262*cc02d7e2SAndroid Build Coastguard Worker                         message: match.Groups["LEVEL"].Value + " " + match.Groups["TEXT"].Value);
263*cc02d7e2SAndroid Build Coastguard Worker                 }
264*cc02d7e2SAndroid Build Coastguard Worker             },
265*cc02d7e2SAndroid Build Coastguard Worker 
266*cc02d7e2SAndroid Build Coastguard Worker             // Example error without location
267*cc02d7e2SAndroid Build Coastguard Worker             //../Protos/greet.proto: Import "google/protobuf/empty.proto" was listed twice.
268*cc02d7e2SAndroid Build Coastguard Worker             new ErrorListFilter
269*cc02d7e2SAndroid Build Coastguard Worker             {
270*cc02d7e2SAndroid Build Coastguard Worker                 Pattern = new Regex(
271*cc02d7e2SAndroid Build Coastguard Worker                     pattern: "^(?'FILENAME'.+?): ?(?'TEXT'.*)",
272*cc02d7e2SAndroid Build Coastguard Worker                     options: RegexOptions.Compiled | RegexOptions.IgnoreCase,
273*cc02d7e2SAndroid Build Coastguard Worker                     matchTimeout: s_regexTimeout),
274*cc02d7e2SAndroid Build Coastguard Worker                 LogAction = (log, match) =>
275*cc02d7e2SAndroid Build Coastguard Worker                 {
276*cc02d7e2SAndroid Build Coastguard Worker                     log.LogError(
277*cc02d7e2SAndroid Build Coastguard Worker                         subcategory: null,
278*cc02d7e2SAndroid Build Coastguard Worker                         errorCode: null,
279*cc02d7e2SAndroid Build Coastguard Worker                         helpKeyword: null,
280*cc02d7e2SAndroid Build Coastguard Worker                         file: match.Groups["FILENAME"].Value,
281*cc02d7e2SAndroid Build Coastguard Worker                         lineNumber: 0,
282*cc02d7e2SAndroid Build Coastguard Worker                         columnNumber: 0,
283*cc02d7e2SAndroid Build Coastguard Worker                         endLineNumber: 0,
284*cc02d7e2SAndroid Build Coastguard Worker                         endColumnNumber: 0,
285*cc02d7e2SAndroid Build Coastguard Worker                         message: match.Groups["TEXT"].Value);
286*cc02d7e2SAndroid Build Coastguard Worker                 }
287*cc02d7e2SAndroid Build Coastguard Worker             }
288*cc02d7e2SAndroid Build Coastguard Worker         };
289*cc02d7e2SAndroid Build Coastguard Worker 
290*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
291*cc02d7e2SAndroid Build Coastguard Worker         /// Code generator.
292*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
293*cc02d7e2SAndroid Build Coastguard Worker         [Required]
294*cc02d7e2SAndroid Build Coastguard Worker         public string Generator { get; set; }
295*cc02d7e2SAndroid Build Coastguard Worker 
296*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
297*cc02d7e2SAndroid Build Coastguard Worker         /// Protobuf files to compile.
298*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
299*cc02d7e2SAndroid Build Coastguard Worker         [Required]
300*cc02d7e2SAndroid Build Coastguard Worker         public ITaskItem[] Protobuf { get; set; }
301*cc02d7e2SAndroid Build Coastguard Worker 
302*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
303*cc02d7e2SAndroid Build Coastguard Worker         /// Directory where protoc dependency files are cached. If provided, dependency
304*cc02d7e2SAndroid Build Coastguard Worker         /// output filename is autogenerated from source directory hash and file name.
305*cc02d7e2SAndroid Build Coastguard Worker         /// Mutually exclusive with DependencyOut.
306*cc02d7e2SAndroid Build Coastguard Worker         /// Switch: --dependency_out (with autogenerated file name).
307*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
308*cc02d7e2SAndroid Build Coastguard Worker         public string ProtoDepDir { get; set; }
309*cc02d7e2SAndroid Build Coastguard Worker 
310*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
311*cc02d7e2SAndroid Build Coastguard Worker         /// Dependency file full name. Mutually exclusive with ProtoDepDir.
312*cc02d7e2SAndroid Build Coastguard Worker         /// Autogenerated file name is available in this property after execution.
313*cc02d7e2SAndroid Build Coastguard Worker         /// Switch: --dependency_out.
314*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
315*cc02d7e2SAndroid Build Coastguard Worker         [Output]
316*cc02d7e2SAndroid Build Coastguard Worker         public string DependencyOut { get; set; }
317*cc02d7e2SAndroid Build Coastguard Worker 
318*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
319*cc02d7e2SAndroid Build Coastguard Worker         /// The directories to search for imports. Directories will be searched
320*cc02d7e2SAndroid Build Coastguard Worker         /// in order. If not given, the current working directory is used.
321*cc02d7e2SAndroid Build Coastguard Worker         /// Switch: --proto_path.
322*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
323*cc02d7e2SAndroid Build Coastguard Worker         public string[] ProtoPath { get; set; }
324*cc02d7e2SAndroid Build Coastguard Worker 
325*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
326*cc02d7e2SAndroid Build Coastguard Worker         /// Generated code directory. The generator property determines the language.
327*cc02d7e2SAndroid Build Coastguard Worker         /// Switch: --GEN_out= (for different generators GEN, e.g. --csharp_out).
328*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
329*cc02d7e2SAndroid Build Coastguard Worker         [Required]
330*cc02d7e2SAndroid Build Coastguard Worker         public string OutputDir { get; set; }
331*cc02d7e2SAndroid Build Coastguard Worker 
332*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
333*cc02d7e2SAndroid Build Coastguard Worker         /// Codegen options. See also OptionsFromMetadata.
334*cc02d7e2SAndroid Build Coastguard Worker         /// Switch: --GEN_opt= (for different generators GEN, e.g. --csharp_opt).
335*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
336*cc02d7e2SAndroid Build Coastguard Worker         public string[] OutputOptions { get; set; }
337*cc02d7e2SAndroid Build Coastguard Worker 
338*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
339*cc02d7e2SAndroid Build Coastguard Worker         /// Additional arguments that will be passed unmodified to protoc (and before any file names).
340*cc02d7e2SAndroid Build Coastguard Worker         /// For example, "--experimental_allow_proto3_optional"
341*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
342*cc02d7e2SAndroid Build Coastguard Worker         public string[] AdditionalProtocArguments { get; set; }
343*cc02d7e2SAndroid Build Coastguard Worker 
344*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
345*cc02d7e2SAndroid Build Coastguard Worker         /// Full path to the gRPC plugin executable. If specified, gRPC generation
346*cc02d7e2SAndroid Build Coastguard Worker         /// is enabled for the files.
347*cc02d7e2SAndroid Build Coastguard Worker         /// Switch: --plugin=protoc-gen-grpc=
348*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
349*cc02d7e2SAndroid Build Coastguard Worker         public string GrpcPluginExe { get; set; }
350*cc02d7e2SAndroid Build Coastguard Worker 
351*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
352*cc02d7e2SAndroid Build Coastguard Worker         /// Generated gRPC  directory. The generator property determines the
353*cc02d7e2SAndroid Build Coastguard Worker         /// language. If gRPC is enabled but this is not given, OutputDir is used.
354*cc02d7e2SAndroid Build Coastguard Worker         /// Switch: --grpc_out=
355*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
356*cc02d7e2SAndroid Build Coastguard Worker         public string GrpcOutputDir { get; set; }
357*cc02d7e2SAndroid Build Coastguard Worker 
358*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
359*cc02d7e2SAndroid Build Coastguard Worker         /// gRPC Codegen options. See also OptionsFromMetadata.
360*cc02d7e2SAndroid Build Coastguard Worker         /// --grpc_opt=opt1,opt2=val (comma-separated).
361*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
362*cc02d7e2SAndroid Build Coastguard Worker         public string[] GrpcOutputOptions { get; set; }
363*cc02d7e2SAndroid Build Coastguard Worker 
364*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
365*cc02d7e2SAndroid Build Coastguard Worker         /// List of files written in addition to generated outputs. Includes a
366*cc02d7e2SAndroid Build Coastguard Worker         /// single item for the dependency file if written.
367*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
368*cc02d7e2SAndroid Build Coastguard Worker         [Output]
369*cc02d7e2SAndroid Build Coastguard Worker         public ITaskItem[] AdditionalFileWrites { get; private set; }
370*cc02d7e2SAndroid Build Coastguard Worker 
371*cc02d7e2SAndroid Build Coastguard Worker         /// <summary>
372*cc02d7e2SAndroid Build Coastguard Worker         /// List of language files generated by protoc. Empty unless DependencyOut
373*cc02d7e2SAndroid Build Coastguard Worker         /// or ProtoDepDir is set, since the file writes are extracted from protoc
374*cc02d7e2SAndroid Build Coastguard Worker         /// dependency output file.
375*cc02d7e2SAndroid Build Coastguard Worker         /// </summary>
376*cc02d7e2SAndroid Build Coastguard Worker         [Output]
377*cc02d7e2SAndroid Build Coastguard Worker         public ITaskItem[] GeneratedFiles { get; private set; }
378*cc02d7e2SAndroid Build Coastguard Worker 
379*cc02d7e2SAndroid Build Coastguard Worker         // Hide this property from MSBuild, we should never use a shell script.
380*cc02d7e2SAndroid Build Coastguard Worker         private new bool UseCommandProcessor { get; set; }
381*cc02d7e2SAndroid Build Coastguard Worker 
382*cc02d7e2SAndroid Build Coastguard Worker         protected override string ToolName => Platform.IsWindows ? "protoc.exe" : "protoc";
383*cc02d7e2SAndroid Build Coastguard Worker 
384*cc02d7e2SAndroid Build Coastguard Worker         // Since we never try to really locate protoc.exe somehow, just try ToolExe
385*cc02d7e2SAndroid Build Coastguard Worker         // as the full tool location. It will be either just protoc[.exe] from
386*cc02d7e2SAndroid Build Coastguard Worker         // ToolName above if not set by the user, or a user-supplied full path. The
387*cc02d7e2SAndroid Build Coastguard Worker         // base class will then resolve the former using system PATH.
GenerateFullPathToTool()388*cc02d7e2SAndroid Build Coastguard Worker         protected override string GenerateFullPathToTool() => ToolExe;
389*cc02d7e2SAndroid Build Coastguard Worker 
390*cc02d7e2SAndroid Build Coastguard Worker         // Log protoc errors with the High priority (bold white in MsBuild,
391*cc02d7e2SAndroid Build Coastguard Worker         // printed with -v:n, and shown in the Output windows in VS).
392*cc02d7e2SAndroid Build Coastguard Worker         protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High;
393*cc02d7e2SAndroid Build Coastguard Worker 
394*cc02d7e2SAndroid Build Coastguard Worker         // Called by base class to validate arguments and make them consistent.
ValidateParameters()395*cc02d7e2SAndroid Build Coastguard Worker         protected override bool ValidateParameters()
396*cc02d7e2SAndroid Build Coastguard Worker         {
397*cc02d7e2SAndroid Build Coastguard Worker             // Part of proto command line switches, must be lowercased.
398*cc02d7e2SAndroid Build Coastguard Worker             Generator = Generator.ToLowerInvariant();
399*cc02d7e2SAndroid Build Coastguard Worker             if (!System.Array.Exists(s_supportedGenerators, g => g == Generator))
400*cc02d7e2SAndroid Build Coastguard Worker             {
401*cc02d7e2SAndroid Build Coastguard Worker                 Log.LogError("Invalid value for Generator='{0}'. Supported generators: {1}",
402*cc02d7e2SAndroid Build Coastguard Worker                              Generator, string.Join(", ", s_supportedGenerators));
403*cc02d7e2SAndroid Build Coastguard Worker             }
404*cc02d7e2SAndroid Build Coastguard Worker 
405*cc02d7e2SAndroid Build Coastguard Worker             if (ProtoDepDir != null && DependencyOut != null)
406*cc02d7e2SAndroid Build Coastguard Worker             {
407*cc02d7e2SAndroid Build Coastguard Worker                 Log.LogError("Properties ProtoDepDir and DependencyOut may not be both specified");
408*cc02d7e2SAndroid Build Coastguard Worker             }
409*cc02d7e2SAndroid Build Coastguard Worker 
410*cc02d7e2SAndroid Build Coastguard Worker             if (Protobuf.Length > 1 && (ProtoDepDir != null || DependencyOut != null))
411*cc02d7e2SAndroid Build Coastguard Worker             {
412*cc02d7e2SAndroid Build Coastguard Worker                 Log.LogError("Proto compiler currently allows only one input when " +
413*cc02d7e2SAndroid Build Coastguard Worker                              "--dependency_out is specified (via ProtoDepDir or DependencyOut). " +
414*cc02d7e2SAndroid Build Coastguard Worker                              "Tracking issue: https://github.com/protocolbuffers/protobuf/pull/3959");
415*cc02d7e2SAndroid Build Coastguard Worker             }
416*cc02d7e2SAndroid Build Coastguard Worker 
417*cc02d7e2SAndroid Build Coastguard Worker             // Use ProtoDepDir to autogenerate DependencyOut
418*cc02d7e2SAndroid Build Coastguard Worker             if (ProtoDepDir != null)
419*cc02d7e2SAndroid Build Coastguard Worker             {
420*cc02d7e2SAndroid Build Coastguard Worker                 DependencyOut = DepFileUtil.GetDepFilenameForProto(ProtoDepDir, Protobuf[0].ItemSpec);
421*cc02d7e2SAndroid Build Coastguard Worker             }
422*cc02d7e2SAndroid Build Coastguard Worker 
423*cc02d7e2SAndroid Build Coastguard Worker             if (GrpcPluginExe == null)
424*cc02d7e2SAndroid Build Coastguard Worker             {
425*cc02d7e2SAndroid Build Coastguard Worker                 GrpcOutputOptions = null;
426*cc02d7e2SAndroid Build Coastguard Worker                 GrpcOutputDir = null;
427*cc02d7e2SAndroid Build Coastguard Worker             }
428*cc02d7e2SAndroid Build Coastguard Worker             else if (GrpcOutputDir == null)
429*cc02d7e2SAndroid Build Coastguard Worker             {
430*cc02d7e2SAndroid Build Coastguard Worker                 // Use OutputDir for gRPC output if not specified otherwise by user.
431*cc02d7e2SAndroid Build Coastguard Worker                 GrpcOutputDir = OutputDir;
432*cc02d7e2SAndroid Build Coastguard Worker             }
433*cc02d7e2SAndroid Build Coastguard Worker 
434*cc02d7e2SAndroid Build Coastguard Worker             return !Log.HasLoggedErrors && base.ValidateParameters();
435*cc02d7e2SAndroid Build Coastguard Worker         }
436*cc02d7e2SAndroid Build Coastguard Worker 
437*cc02d7e2SAndroid Build Coastguard Worker         // Protoc chokes on BOM, naturally. I would!
438*cc02d7e2SAndroid Build Coastguard Worker         static readonly Encoding s_utf8WithoutBom = new UTF8Encoding(false);
439*cc02d7e2SAndroid Build Coastguard Worker         protected override Encoding ResponseFileEncoding => s_utf8WithoutBom;
440*cc02d7e2SAndroid Build Coastguard Worker 
441*cc02d7e2SAndroid Build Coastguard Worker         // Protoc takes one argument per line from the response file, and does not
442*cc02d7e2SAndroid Build Coastguard Worker         // require any quoting whatsoever. Otherwise, this is similar to the
443*cc02d7e2SAndroid Build Coastguard Worker         // standard CommandLineBuilder
444*cc02d7e2SAndroid Build Coastguard Worker         class ProtocResponseFileBuilder
445*cc02d7e2SAndroid Build Coastguard Worker         {
446*cc02d7e2SAndroid Build Coastguard Worker             StringBuilder _data = new StringBuilder(1000);
ToString()447*cc02d7e2SAndroid Build Coastguard Worker             public override string ToString() => _data.ToString();
448*cc02d7e2SAndroid Build Coastguard Worker 
449*cc02d7e2SAndroid Build Coastguard Worker             // If 'value' is not empty, append '--name=value\n'.
AddSwitchMaybe(string name, string value)450*cc02d7e2SAndroid Build Coastguard Worker             public void AddSwitchMaybe(string name, string value)
451*cc02d7e2SAndroid Build Coastguard Worker             {
452*cc02d7e2SAndroid Build Coastguard Worker                 if (!string.IsNullOrEmpty(value))
453*cc02d7e2SAndroid Build Coastguard Worker                 {
454*cc02d7e2SAndroid Build Coastguard Worker                     _data.Append("--").Append(name).Append("=")
455*cc02d7e2SAndroid Build Coastguard Worker                          .Append(value).Append('\n');
456*cc02d7e2SAndroid Build Coastguard Worker                 }
457*cc02d7e2SAndroid Build Coastguard Worker             }
458*cc02d7e2SAndroid Build Coastguard Worker 
459*cc02d7e2SAndroid Build Coastguard Worker             // Add switch with the 'values' separated by commas, for options.
AddSwitchMaybe(string name, string[] values)460*cc02d7e2SAndroid Build Coastguard Worker             public void AddSwitchMaybe(string name, string[] values)
461*cc02d7e2SAndroid Build Coastguard Worker             {
462*cc02d7e2SAndroid Build Coastguard Worker                 if (values?.Length > 0)
463*cc02d7e2SAndroid Build Coastguard Worker                 {
464*cc02d7e2SAndroid Build Coastguard Worker                     _data.Append("--").Append(name).Append("=")
465*cc02d7e2SAndroid Build Coastguard Worker                          .Append(string.Join(",", values)).Append('\n');
466*cc02d7e2SAndroid Build Coastguard Worker                 }
467*cc02d7e2SAndroid Build Coastguard Worker             }
468*cc02d7e2SAndroid Build Coastguard Worker 
469*cc02d7e2SAndroid Build Coastguard Worker             // Add a positional argument to the file data.
AddArg(string arg)470*cc02d7e2SAndroid Build Coastguard Worker             public void AddArg(string arg)
471*cc02d7e2SAndroid Build Coastguard Worker             {
472*cc02d7e2SAndroid Build Coastguard Worker                 _data.Append(arg).Append('\n');
473*cc02d7e2SAndroid Build Coastguard Worker             }
474*cc02d7e2SAndroid Build Coastguard Worker         };
475*cc02d7e2SAndroid Build Coastguard Worker 
476*cc02d7e2SAndroid Build Coastguard Worker         // Called by the base ToolTask to get response file contents.
GenerateResponseFileCommands()477*cc02d7e2SAndroid Build Coastguard Worker         protected override string GenerateResponseFileCommands()
478*cc02d7e2SAndroid Build Coastguard Worker         {
479*cc02d7e2SAndroid Build Coastguard Worker             var cmd = new ProtocResponseFileBuilder();
480*cc02d7e2SAndroid Build Coastguard Worker             cmd.AddSwitchMaybe(Generator + "_out", TrimEndSlash(OutputDir));
481*cc02d7e2SAndroid Build Coastguard Worker             cmd.AddSwitchMaybe(Generator + "_opt", OutputOptions);
482*cc02d7e2SAndroid Build Coastguard Worker             cmd.AddSwitchMaybe("plugin=protoc-gen-grpc", GrpcPluginExe);
483*cc02d7e2SAndroid Build Coastguard Worker             cmd.AddSwitchMaybe("grpc_out", TrimEndSlash(GrpcOutputDir));
484*cc02d7e2SAndroid Build Coastguard Worker             cmd.AddSwitchMaybe("grpc_opt", GrpcOutputOptions);
485*cc02d7e2SAndroid Build Coastguard Worker             if (ProtoPath != null)
486*cc02d7e2SAndroid Build Coastguard Worker             {
487*cc02d7e2SAndroid Build Coastguard Worker                 foreach (string path in ProtoPath)
488*cc02d7e2SAndroid Build Coastguard Worker                 {
489*cc02d7e2SAndroid Build Coastguard Worker                     cmd.AddSwitchMaybe("proto_path", TrimEndSlash(path));
490*cc02d7e2SAndroid Build Coastguard Worker                 }
491*cc02d7e2SAndroid Build Coastguard Worker             }
492*cc02d7e2SAndroid Build Coastguard Worker             cmd.AddSwitchMaybe("dependency_out", DependencyOut);
493*cc02d7e2SAndroid Build Coastguard Worker             cmd.AddSwitchMaybe("error_format", "msvs");
494*cc02d7e2SAndroid Build Coastguard Worker 
495*cc02d7e2SAndroid Build Coastguard Worker             if (AdditionalProtocArguments != null)
496*cc02d7e2SAndroid Build Coastguard Worker             {
497*cc02d7e2SAndroid Build Coastguard Worker                 foreach (var additionalProtocOption in AdditionalProtocArguments)
498*cc02d7e2SAndroid Build Coastguard Worker                 {
499*cc02d7e2SAndroid Build Coastguard Worker                     cmd.AddArg(additionalProtocOption);
500*cc02d7e2SAndroid Build Coastguard Worker                 }
501*cc02d7e2SAndroid Build Coastguard Worker             }
502*cc02d7e2SAndroid Build Coastguard Worker 
503*cc02d7e2SAndroid Build Coastguard Worker             foreach (var proto in Protobuf)
504*cc02d7e2SAndroid Build Coastguard Worker             {
505*cc02d7e2SAndroid Build Coastguard Worker                 cmd.AddArg(proto.ItemSpec);
506*cc02d7e2SAndroid Build Coastguard Worker             }
507*cc02d7e2SAndroid Build Coastguard Worker             return cmd.ToString();
508*cc02d7e2SAndroid Build Coastguard Worker         }
509*cc02d7e2SAndroid Build Coastguard Worker 
510*cc02d7e2SAndroid Build Coastguard Worker         // Protoc cannot digest trailing slashes in directory names,
511*cc02d7e2SAndroid Build Coastguard Worker         // curiously under Linux, but not in Windows.
TrimEndSlash(string dir)512*cc02d7e2SAndroid Build Coastguard Worker         static string TrimEndSlash(string dir)
513*cc02d7e2SAndroid Build Coastguard Worker         {
514*cc02d7e2SAndroid Build Coastguard Worker             if (dir == null || dir.Length <= 1)
515*cc02d7e2SAndroid Build Coastguard Worker             {
516*cc02d7e2SAndroid Build Coastguard Worker                 return dir;
517*cc02d7e2SAndroid Build Coastguard Worker             }
518*cc02d7e2SAndroid Build Coastguard Worker             string trim = dir.TrimEnd('/', '\\');
519*cc02d7e2SAndroid Build Coastguard Worker             // Do not trim the root slash, drive letter possible.
520*cc02d7e2SAndroid Build Coastguard Worker             if (trim.Length == 0)
521*cc02d7e2SAndroid Build Coastguard Worker             {
522*cc02d7e2SAndroid Build Coastguard Worker                 // Slashes all the way down.
523*cc02d7e2SAndroid Build Coastguard Worker                 return dir.Substring(0, 1);
524*cc02d7e2SAndroid Build Coastguard Worker             }
525*cc02d7e2SAndroid Build Coastguard Worker             if (trim.Length == 2 && dir.Length > 2 && trim[1] == ':')
526*cc02d7e2SAndroid Build Coastguard Worker             {
527*cc02d7e2SAndroid Build Coastguard Worker                 // We have a drive letter and root, e. g. 'C:\'
528*cc02d7e2SAndroid Build Coastguard Worker                 return dir.Substring(0, 3);
529*cc02d7e2SAndroid Build Coastguard Worker             }
530*cc02d7e2SAndroid Build Coastguard Worker             return trim;
531*cc02d7e2SAndroid Build Coastguard Worker         }
532*cc02d7e2SAndroid Build Coastguard Worker 
533*cc02d7e2SAndroid Build Coastguard Worker         // Called by the base class to log tool's command line.
534*cc02d7e2SAndroid Build Coastguard Worker         //
535*cc02d7e2SAndroid Build Coastguard Worker         // Protoc command file is peculiar, with one argument per line, separated
536*cc02d7e2SAndroid Build Coastguard Worker         // by newlines. Unwrap it for log readability into a single line, and also
537*cc02d7e2SAndroid Build Coastguard Worker         // quote arguments, lest it look weird and so it may be copied and pasted
538*cc02d7e2SAndroid Build Coastguard Worker         // into shell. Since this is for logging only, correct enough is correct.
LogToolCommand(string cmd)539*cc02d7e2SAndroid Build Coastguard Worker         protected override void LogToolCommand(string cmd)
540*cc02d7e2SAndroid Build Coastguard Worker         {
541*cc02d7e2SAndroid Build Coastguard Worker             var printer = new StringBuilder(1024);
542*cc02d7e2SAndroid Build Coastguard Worker 
543*cc02d7e2SAndroid Build Coastguard Worker             // Print 'str' slice into 'printer', wrapping in quotes if contains some
544*cc02d7e2SAndroid Build Coastguard Worker             // interesting characters in file names, or if empty string. The list of
545*cc02d7e2SAndroid Build Coastguard Worker             // characters requiring quoting is not by any means exhaustive; we are
546*cc02d7e2SAndroid Build Coastguard Worker             // just striving to be nice, not guaranteeing to be nice.
547*cc02d7e2SAndroid Build Coastguard Worker             var quotable = new[] { ' ', '!', '$', '&', '\'', '^' };
548*cc02d7e2SAndroid Build Coastguard Worker             void PrintQuoting(string str, int start, int count)
549*cc02d7e2SAndroid Build Coastguard Worker             {
550*cc02d7e2SAndroid Build Coastguard Worker                 bool wrap = count == 0 || str.IndexOfAny(quotable, start, count) >= 0;
551*cc02d7e2SAndroid Build Coastguard Worker                 if (wrap) printer.Append('"');
552*cc02d7e2SAndroid Build Coastguard Worker                 printer.Append(str, start, count);
553*cc02d7e2SAndroid Build Coastguard Worker                 if (wrap) printer.Append('"');
554*cc02d7e2SAndroid Build Coastguard Worker             }
555*cc02d7e2SAndroid Build Coastguard Worker 
556*cc02d7e2SAndroid Build Coastguard Worker             for (int ib = 0, ie; (ie = cmd.IndexOf('\n', ib)) >= 0; ib = ie + 1)
557*cc02d7e2SAndroid Build Coastguard Worker             {
558*cc02d7e2SAndroid Build Coastguard Worker                 // First line only contains both the program name and the first switch.
559*cc02d7e2SAndroid Build Coastguard Worker                 // We can rely on at least the '--out_dir' switch being always present.
560*cc02d7e2SAndroid Build Coastguard Worker                 if (ib == 0)
561*cc02d7e2SAndroid Build Coastguard Worker                 {
562*cc02d7e2SAndroid Build Coastguard Worker                     int iep = cmd.IndexOf(" --");
563*cc02d7e2SAndroid Build Coastguard Worker                     if (iep > 0)
564*cc02d7e2SAndroid Build Coastguard Worker                     {
565*cc02d7e2SAndroid Build Coastguard Worker                         PrintQuoting(cmd, 0, iep);
566*cc02d7e2SAndroid Build Coastguard Worker                         ib = iep + 1;
567*cc02d7e2SAndroid Build Coastguard Worker                     }
568*cc02d7e2SAndroid Build Coastguard Worker                 }
569*cc02d7e2SAndroid Build Coastguard Worker                 printer.Append(' ');
570*cc02d7e2SAndroid Build Coastguard Worker                 if (cmd[ib] == '-')
571*cc02d7e2SAndroid Build Coastguard Worker                 {
572*cc02d7e2SAndroid Build Coastguard Worker                     // Print switch unquoted, including '=' if any.
573*cc02d7e2SAndroid Build Coastguard Worker                     int iarg = cmd.IndexOf('=', ib, ie - ib);
574*cc02d7e2SAndroid Build Coastguard Worker                     if (iarg < 0)
575*cc02d7e2SAndroid Build Coastguard Worker                     {
576*cc02d7e2SAndroid Build Coastguard Worker                         // Bare switch without a '='.
577*cc02d7e2SAndroid Build Coastguard Worker                         printer.Append(cmd, ib, ie - ib);
578*cc02d7e2SAndroid Build Coastguard Worker                         continue;
579*cc02d7e2SAndroid Build Coastguard Worker                     }
580*cc02d7e2SAndroid Build Coastguard Worker                     printer.Append(cmd, ib, iarg + 1 - ib);
581*cc02d7e2SAndroid Build Coastguard Worker                     ib = iarg + 1;
582*cc02d7e2SAndroid Build Coastguard Worker                 }
583*cc02d7e2SAndroid Build Coastguard Worker                 // A positional argument or switch value.
584*cc02d7e2SAndroid Build Coastguard Worker                 PrintQuoting(cmd, ib, ie - ib);
585*cc02d7e2SAndroid Build Coastguard Worker             }
586*cc02d7e2SAndroid Build Coastguard Worker 
587*cc02d7e2SAndroid Build Coastguard Worker             base.LogToolCommand(printer.ToString());
588*cc02d7e2SAndroid Build Coastguard Worker         }
589*cc02d7e2SAndroid Build Coastguard Worker 
LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)590*cc02d7e2SAndroid Build Coastguard Worker         protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)
591*cc02d7e2SAndroid Build Coastguard Worker         {
592*cc02d7e2SAndroid Build Coastguard Worker             foreach (ErrorListFilter filter in s_errorListFilters)
593*cc02d7e2SAndroid Build Coastguard Worker             {
594*cc02d7e2SAndroid Build Coastguard Worker                 try
595*cc02d7e2SAndroid Build Coastguard Worker                 {
596*cc02d7e2SAndroid Build Coastguard Worker                     Match match = filter.Pattern.Match(singleLine);
597*cc02d7e2SAndroid Build Coastguard Worker 
598*cc02d7e2SAndroid Build Coastguard Worker                     if (match.Success)
599*cc02d7e2SAndroid Build Coastguard Worker                     {
600*cc02d7e2SAndroid Build Coastguard Worker                         filter.LogAction(Log, match);
601*cc02d7e2SAndroid Build Coastguard Worker                         return;
602*cc02d7e2SAndroid Build Coastguard Worker                     }
603*cc02d7e2SAndroid Build Coastguard Worker                 } catch (RegexMatchTimeoutException)
604*cc02d7e2SAndroid Build Coastguard Worker                 {
605*cc02d7e2SAndroid Build Coastguard Worker                     Log.LogWarning("Unable to parse output from protoc. Regex timeout.");
606*cc02d7e2SAndroid Build Coastguard Worker                 }
607*cc02d7e2SAndroid Build Coastguard Worker             }
608*cc02d7e2SAndroid Build Coastguard Worker 
609*cc02d7e2SAndroid Build Coastguard Worker             base.LogEventsFromTextOutput(singleLine, messageImportance);
610*cc02d7e2SAndroid Build Coastguard Worker         }
611*cc02d7e2SAndroid Build Coastguard Worker 
612*cc02d7e2SAndroid Build Coastguard Worker         // Main task entry point.
Execute()613*cc02d7e2SAndroid Build Coastguard Worker         public override bool Execute()
614*cc02d7e2SAndroid Build Coastguard Worker         {
615*cc02d7e2SAndroid Build Coastguard Worker             base.UseCommandProcessor = false;
616*cc02d7e2SAndroid Build Coastguard Worker 
617*cc02d7e2SAndroid Build Coastguard Worker             bool ok = base.Execute();
618*cc02d7e2SAndroid Build Coastguard Worker             if (!ok)
619*cc02d7e2SAndroid Build Coastguard Worker             {
620*cc02d7e2SAndroid Build Coastguard Worker                 return false;
621*cc02d7e2SAndroid Build Coastguard Worker             }
622*cc02d7e2SAndroid Build Coastguard Worker 
623*cc02d7e2SAndroid Build Coastguard Worker             // Read dependency output file from the compiler to retrieve the
624*cc02d7e2SAndroid Build Coastguard Worker             // definitive list of created files. Report the dependency file
625*cc02d7e2SAndroid Build Coastguard Worker             // itself as having been written to.
626*cc02d7e2SAndroid Build Coastguard Worker             if (DependencyOut != null)
627*cc02d7e2SAndroid Build Coastguard Worker             {
628*cc02d7e2SAndroid Build Coastguard Worker                 string[] outputs = DepFileUtil.ReadDependencyOutputs(DependencyOut, Log);
629*cc02d7e2SAndroid Build Coastguard Worker                 if (HasLoggedErrors)
630*cc02d7e2SAndroid Build Coastguard Worker                 {
631*cc02d7e2SAndroid Build Coastguard Worker                     return false;
632*cc02d7e2SAndroid Build Coastguard Worker                 }
633*cc02d7e2SAndroid Build Coastguard Worker 
634*cc02d7e2SAndroid Build Coastguard Worker                 GeneratedFiles = new ITaskItem[outputs.Length];
635*cc02d7e2SAndroid Build Coastguard Worker                 for (int i = 0; i < outputs.Length; i++)
636*cc02d7e2SAndroid Build Coastguard Worker                 {
637*cc02d7e2SAndroid Build Coastguard Worker                     GeneratedFiles[i] = new TaskItem(outputs[i]);
638*cc02d7e2SAndroid Build Coastguard Worker                 }
639*cc02d7e2SAndroid Build Coastguard Worker                 AdditionalFileWrites = new ITaskItem[] { new TaskItem(DependencyOut) };
640*cc02d7e2SAndroid Build Coastguard Worker             }
641*cc02d7e2SAndroid Build Coastguard Worker 
642*cc02d7e2SAndroid Build Coastguard Worker             return true;
643*cc02d7e2SAndroid Build Coastguard Worker         }
644*cc02d7e2SAndroid Build Coastguard Worker 
645*cc02d7e2SAndroid Build Coastguard Worker         class ErrorListFilter
646*cc02d7e2SAndroid Build Coastguard Worker         {
647*cc02d7e2SAndroid Build Coastguard Worker             public Regex Pattern { get; set; }
648*cc02d7e2SAndroid Build Coastguard Worker             public Action<TaskLoggingHelper, Match> LogAction { get; set; }
649*cc02d7e2SAndroid Build Coastguard Worker         }
650*cc02d7e2SAndroid Build Coastguard Worker     };
651*cc02d7e2SAndroid Build Coastguard Worker }
652