|  | #region Copyright notice and license | 
|  |  | 
|  | // Copyright 2018 gRPC authors. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | #endregion | 
|  |  | 
|  | using System; | 
|  | using System.Collections.Generic; | 
|  | using System.Text; | 
|  | using System.Text.RegularExpressions; | 
|  | using Microsoft.Build.Framework; | 
|  | using Microsoft.Build.Utilities; | 
|  |  | 
|  | namespace Grpc.Tools | 
|  | { | 
|  | /// <summary> | 
|  | /// Run Google proto compiler (protoc). | 
|  | /// | 
|  | /// After a successful run, the task reads the dependency file if specified | 
|  | /// to be saved by the compiler, and returns its output files. | 
|  | /// | 
|  | /// This task (unlike PrepareProtoCompile) does not attempt to guess anything | 
|  | /// about language-specific behavior of protoc, and therefore can be used for | 
|  | /// any language outputs. | 
|  | /// </summary> | 
|  | public class ProtoCompile : ToolTask | 
|  | { | 
|  | /* | 
|  |  | 
|  | Usage: /home/kkm/work/protobuf/src/.libs/lt-protoc [OPTION] PROTO_FILES | 
|  | Parse PROTO_FILES and generate output based on the options given: | 
|  | -IPATH, --proto_path=PATH   Specify the directory in which to search for | 
|  | imports.  May be specified multiple times; | 
|  | directories will be searched in order.  If not | 
|  | given, the current working directory is used. | 
|  | --version                   Show version info and exit. | 
|  | -h, --help                  Show this text and exit. | 
|  | --encode=MESSAGE_TYPE       Read a text-format message of the given type | 
|  | from standard input and write it in binary | 
|  | to standard output.  The message type must | 
|  | be defined in PROTO_FILES or their imports. | 
|  | --decode=MESSAGE_TYPE       Read a binary message of the given type from | 
|  | standard input and write it in text format | 
|  | to standard output.  The message type must | 
|  | be defined in PROTO_FILES or their imports. | 
|  | --decode_raw                Read an arbitrary protocol message from | 
|  | standard input and write the raw tag/value | 
|  | pairs in text format to standard output.  No | 
|  | PROTO_FILES should be given when using this | 
|  | flag. | 
|  | --descriptor_set_in=FILES   Specifies a delimited list of FILES | 
|  | each containing a FileDescriptorSet (a | 
|  | protocol buffer defined in descriptor.proto). | 
|  | The FileDescriptor for each of the PROTO_FILES | 
|  | provided will be loaded from these | 
|  | FileDescriptorSets. If a FileDescriptor | 
|  | appears multiple times, the first occurrence | 
|  | will be used. | 
|  | -oFILE,                     Writes a FileDescriptorSet (a protocol buffer, | 
|  | --descriptor_set_out=FILE defined in descriptor.proto) containing all of | 
|  | the input files to FILE. | 
|  | --include_imports           When using --descriptor_set_out, also include | 
|  | all dependencies of the input files in the | 
|  | set, so that the set is self-contained. | 
|  | --include_source_info       When using --descriptor_set_out, do not strip | 
|  | SourceCodeInfo from the FileDescriptorProto. | 
|  | This results in vastly larger descriptors that | 
|  | include information about the original | 
|  | location of each decl in the source file as | 
|  | well as surrounding comments. | 
|  | --dependency_out=FILE       Write a dependency output file in the format | 
|  | expected by make. This writes the transitive | 
|  | set of input file paths to FILE | 
|  | --error_format=FORMAT       Set the format in which to print errors. | 
|  | FORMAT may be 'gcc' (the default) or 'msvs' | 
|  | (Microsoft Visual Studio format). | 
|  | --print_free_field_numbers  Print the free field numbers of the messages | 
|  | defined in the given proto files. Groups share | 
|  | the same field number space with the parent | 
|  | message. Extension ranges are counted as | 
|  | occupied fields numbers. | 
|  |  | 
|  | --plugin=EXECUTABLE         Specifies a plugin executable to use. | 
|  | Normally, protoc searches the PATH for | 
|  | plugins, but you may specify additional | 
|  | executables not in the path using this flag. | 
|  | Additionally, EXECUTABLE may be of the form | 
|  | NAME=PATH, in which case the given plugin name | 
|  | is mapped to the given executable even if | 
|  | the executable's own name differs. | 
|  | --cpp_out=OUT_DIR           Generate C++ header and source. | 
|  | --csharp_out=OUT_DIR        Generate C# source file. | 
|  | --java_out=OUT_DIR          Generate Java source file. | 
|  | --javanano_out=OUT_DIR      Generate Java Nano source file. | 
|  | --js_out=OUT_DIR            Generate JavaScript source. | 
|  | --objc_out=OUT_DIR          Generate Objective C header and source. | 
|  | --php_out=OUT_DIR           Generate PHP source file. | 
|  | --python_out=OUT_DIR        Generate Python source file. | 
|  | --ruby_out=OUT_DIR          Generate Ruby source file. | 
|  | @<filename>                 Read options and filenames from file. If a | 
|  | relative file path is specified, the file | 
|  | will be searched in the working directory. | 
|  | The --proto_path option will not affect how | 
|  | this argument file is searched. Content of | 
|  | the file will be expanded in the position of | 
|  | @<filename> as in the argument list. Note | 
|  | that shell expansion is not applied to the | 
|  | content of the file (i.e., you cannot use | 
|  | quotes, wildcards, escapes, commands, etc.). | 
|  | Each line corresponds to a single argument, | 
|  | even if it contains spaces. | 
|  | */ | 
|  | static string[] s_supportedGenerators = new[] { "cpp", "csharp", "java", | 
|  | "javanano", "js", "objc", | 
|  | "php", "python", "ruby" }; | 
|  |  | 
|  | static readonly TimeSpan s_regexTimeout = TimeSpan.FromMilliseconds(100); | 
|  |  | 
|  | static readonly List<ErrorListFilter> s_errorListFilters = new List<ErrorListFilter>() | 
|  | { | 
|  | // Example warning with location | 
|  | //../Protos/greet.proto(19) : warning in column=5 : warning : When enum name is stripped and label is PascalCased (Zero), | 
|  | // this value label conflicts with Zero. This will make the proto fail to compile for some languages, such as C#. | 
|  | new ErrorListFilter | 
|  | { | 
|  | Pattern = new Regex( | 
|  | pattern: "^(?'FILENAME'.+?)\\((?'LINE'\\d+)\\) ?: ?warning in column=(?'COLUMN'\\d+) ?: ?(?'TEXT'.*)", | 
|  | options: RegexOptions.Compiled | RegexOptions.IgnoreCase, | 
|  | matchTimeout: s_regexTimeout), | 
|  | LogAction = (log, match) => | 
|  | { | 
|  | int.TryParse(match.Groups["LINE"].Value, out var line); | 
|  | int.TryParse(match.Groups["COLUMN"].Value, out var column); | 
|  |  | 
|  | log.LogWarning( | 
|  | subcategory: null, | 
|  | warningCode: null, | 
|  | helpKeyword: null, | 
|  | file: match.Groups["FILENAME"].Value, | 
|  | lineNumber: line, | 
|  | columnNumber: column, | 
|  | endLineNumber: 0, | 
|  | endColumnNumber: 0, | 
|  | message: match.Groups["TEXT"].Value); | 
|  | } | 
|  | }, | 
|  |  | 
|  | // Example error with location | 
|  | //../Protos/greet.proto(14) : error in column=10: "name" is already defined in "Greet.HelloRequest". | 
|  | new ErrorListFilter | 
|  | { | 
|  | Pattern = new Regex( | 
|  | pattern: "^(?'FILENAME'.+?)\\((?'LINE'\\d+)\\) ?: ?error in column=(?'COLUMN'\\d+) ?: ?(?'TEXT'.*)", | 
|  | options: RegexOptions.Compiled | RegexOptions.IgnoreCase, | 
|  | matchTimeout: s_regexTimeout), | 
|  | LogAction = (log, match) => | 
|  | { | 
|  | int.TryParse(match.Groups["LINE"].Value, out var line); | 
|  | int.TryParse(match.Groups["COLUMN"].Value, out var column); | 
|  |  | 
|  | log.LogError( | 
|  | subcategory: null, | 
|  | errorCode: null, | 
|  | helpKeyword: null, | 
|  | file: match.Groups["FILENAME"].Value, | 
|  | lineNumber: line, | 
|  | columnNumber: column, | 
|  | endLineNumber: 0, | 
|  | endColumnNumber: 0, | 
|  | message: match.Groups["TEXT"].Value); | 
|  | } | 
|  | }, | 
|  |  | 
|  | // Example warning without location | 
|  | //../Protos/greet.proto: warning: Import google/protobuf/empty.proto but not used. | 
|  | new ErrorListFilter | 
|  | { | 
|  | Pattern = new Regex( | 
|  | pattern: "^(?'FILENAME'.+?): ?warning: ?(?'TEXT'.*)", | 
|  | options: RegexOptions.Compiled | RegexOptions.IgnoreCase, | 
|  | matchTimeout: s_regexTimeout), | 
|  | LogAction = (log, match) => | 
|  | { | 
|  | log.LogWarning( | 
|  | subcategory: null, | 
|  | warningCode: null, | 
|  | helpKeyword: null, | 
|  | file: match.Groups["FILENAME"].Value, | 
|  | lineNumber: 0, | 
|  | columnNumber: 0, | 
|  | endLineNumber: 0, | 
|  | endColumnNumber: 0, | 
|  | message: match.Groups["TEXT"].Value); | 
|  | } | 
|  | }, | 
|  |  | 
|  | // Example warning from plugins that use GOOGLE_LOG | 
|  | // [libprotobuf WARNING T:\altsrc\..\csharp_enum.cc:74] Duplicate enum value Work (originally Work) in PhoneType; adding underscore to distinguish | 
|  | new ErrorListFilter | 
|  | { | 
|  | Pattern = new Regex( | 
|  | pattern: "^\\[.+? WARNING (?'FILENAME'.+?):(?'LINE'\\d+?)\\] ?(?'TEXT'.*)", | 
|  | options: RegexOptions.Compiled | RegexOptions.IgnoreCase, | 
|  | matchTimeout: s_regexTimeout), | 
|  | LogAction = (log, match) => | 
|  | { | 
|  | // The filename and line logged by the plugins may not be useful to the | 
|  | // end user as they are not the location in the proto file but rather | 
|  | // in the source code for the plugin. Log them anyway as they may help in | 
|  | // diagnostics. | 
|  | int.TryParse(match.Groups["LINE"].Value, out var line); | 
|  | log.LogWarning( | 
|  | subcategory: null, | 
|  | warningCode: null, | 
|  | helpKeyword: null, | 
|  | file: match.Groups["FILENAME"].Value, | 
|  | lineNumber: line, | 
|  | columnNumber: 0, | 
|  | endLineNumber: 0, | 
|  | endColumnNumber: 0, | 
|  | message: match.Groups["TEXT"].Value); | 
|  | } | 
|  | }, | 
|  |  | 
|  | // Example error from plugins that use GOOGLE_LOG | 
|  | // [libprotobuf ERROR T:\path\...\filename:23] Some message | 
|  | // [libprotobuf FATAL T:\path\...\filename:23] Some message | 
|  | new ErrorListFilter | 
|  | { | 
|  | Pattern = new Regex( | 
|  | pattern: "^\\[.+? (?'LEVEL'ERROR|FATAL) (?'FILENAME'.+?):(?'LINE'\\d+?)\\] ?(?'TEXT'.*)", | 
|  | options: RegexOptions.Compiled | RegexOptions.IgnoreCase, | 
|  | matchTimeout: s_regexTimeout), | 
|  | LogAction = (log, match) => | 
|  | { | 
|  | // The filename and line logged by the plugins may not be useful to the | 
|  | // end user as they are not the location in the proto file but rather | 
|  | // in the source code for the plugin. Log them anyway as they may help in | 
|  | // diagnostics. | 
|  | int.TryParse(match.Groups["LINE"].Value, out var line); | 
|  | log.LogError( | 
|  | subcategory: null, | 
|  | errorCode: null, | 
|  | helpKeyword: null, | 
|  | file: match.Groups["FILENAME"].Value, | 
|  | lineNumber: line, | 
|  | columnNumber: 0, | 
|  | endLineNumber: 0, | 
|  | endColumnNumber: 0, | 
|  | message: match.Groups["LEVEL"].Value + " " + match.Groups["TEXT"].Value); | 
|  | } | 
|  | }, | 
|  |  | 
|  | // Example error without location | 
|  | //../Protos/greet.proto: Import "google/protobuf/empty.proto" was listed twice. | 
|  | new ErrorListFilter | 
|  | { | 
|  | Pattern = new Regex( | 
|  | pattern: "^(?'FILENAME'.+?): ?(?'TEXT'.*)", | 
|  | options: RegexOptions.Compiled | RegexOptions.IgnoreCase, | 
|  | matchTimeout: s_regexTimeout), | 
|  | LogAction = (log, match) => | 
|  | { | 
|  | log.LogError( | 
|  | subcategory: null, | 
|  | errorCode: null, | 
|  | helpKeyword: null, | 
|  | file: match.Groups["FILENAME"].Value, | 
|  | lineNumber: 0, | 
|  | columnNumber: 0, | 
|  | endLineNumber: 0, | 
|  | endColumnNumber: 0, | 
|  | message: match.Groups["TEXT"].Value); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | /// <summary> | 
|  | /// Code generator. | 
|  | /// </summary> | 
|  | [Required] | 
|  | public string Generator { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// Protobuf files to compile. | 
|  | /// </summary> | 
|  | [Required] | 
|  | public ITaskItem[] Protobuf { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// Directory where protoc dependency files are cached. If provided, dependency | 
|  | /// output filename is autogenerated from source directory hash and file name. | 
|  | /// Mutually exclusive with DependencyOut. | 
|  | /// Switch: --dependency_out (with autogenerated file name). | 
|  | /// </summary> | 
|  | public string ProtoDepDir { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// Dependency file full name. Mutually exclusive with ProtoDepDir. | 
|  | /// Autogenerated file name is available in this property after execution. | 
|  | /// Switch: --dependency_out. | 
|  | /// </summary> | 
|  | [Output] | 
|  | public string DependencyOut { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// The directories to search for imports. Directories will be searched | 
|  | /// in order. If not given, the current working directory is used. | 
|  | /// Switch: --proto_path. | 
|  | /// </summary> | 
|  | public string[] ProtoPath { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// Generated code directory. The generator property determines the language. | 
|  | /// Switch: --GEN_out= (for different generators GEN, e.g. --csharp_out). | 
|  | /// </summary> | 
|  | [Required] | 
|  | public string OutputDir { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// Codegen options. See also OptionsFromMetadata. | 
|  | /// Switch: --GEN_opt= (for different generators GEN, e.g. --csharp_opt). | 
|  | /// </summary> | 
|  | public string[] OutputOptions { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// Additional arguments that will be passed unmodified to protoc (and before any file names). | 
|  | /// For example, "--experimental_allow_proto3_optional" | 
|  | /// </summary> | 
|  | public string[] AdditionalProtocArguments { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// Full path to the gRPC plugin executable. If specified, gRPC generation | 
|  | /// is enabled for the files. | 
|  | /// Switch: --plugin=protoc-gen-grpc= | 
|  | /// </summary> | 
|  | public string GrpcPluginExe { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// Generated gRPC  directory. The generator property determines the | 
|  | /// language. If gRPC is enabled but this is not given, OutputDir is used. | 
|  | /// Switch: --grpc_out= | 
|  | /// </summary> | 
|  | public string GrpcOutputDir { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// gRPC Codegen options. See also OptionsFromMetadata. | 
|  | /// --grpc_opt=opt1,opt2=val (comma-separated). | 
|  | /// </summary> | 
|  | public string[] GrpcOutputOptions { get; set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// List of files written in addition to generated outputs. Includes a | 
|  | /// single item for the dependency file if written. | 
|  | /// </summary> | 
|  | [Output] | 
|  | public ITaskItem[] AdditionalFileWrites { get; private set; } | 
|  |  | 
|  | /// <summary> | 
|  | /// List of language files generated by protoc. Empty unless DependencyOut | 
|  | /// or ProtoDepDir is set, since the file writes are extracted from protoc | 
|  | /// dependency output file. | 
|  | /// </summary> | 
|  | [Output] | 
|  | public ITaskItem[] GeneratedFiles { get; private set; } | 
|  |  | 
|  | // Hide this property from MSBuild, we should never use a shell script. | 
|  | private new bool UseCommandProcessor { get; set; } | 
|  |  | 
|  | protected override string ToolName => Platform.IsWindows ? "protoc.exe" : "protoc"; | 
|  |  | 
|  | // Since we never try to really locate protoc.exe somehow, just try ToolExe | 
|  | // as the full tool location. It will be either just protoc[.exe] from | 
|  | // ToolName above if not set by the user, or a user-supplied full path. The | 
|  | // base class will then resolve the former using system PATH. | 
|  | protected override string GenerateFullPathToTool() => ToolExe; | 
|  |  | 
|  | // Log protoc errors with the High priority (bold white in MsBuild, | 
|  | // printed with -v:n, and shown in the Output windows in VS). | 
|  | protected override MessageImportance StandardErrorLoggingImportance => MessageImportance.High; | 
|  |  | 
|  | // Called by base class to validate arguments and make them consistent. | 
|  | protected override bool ValidateParameters() | 
|  | { | 
|  | // Part of proto command line switches, must be lowercased. | 
|  | Generator = Generator.ToLowerInvariant(); | 
|  | if (!System.Array.Exists(s_supportedGenerators, g => g == Generator)) | 
|  | { | 
|  | Log.LogError("Invalid value for Generator='{0}'. Supported generators: {1}", | 
|  | Generator, string.Join(", ", s_supportedGenerators)); | 
|  | } | 
|  |  | 
|  | if (ProtoDepDir != null && DependencyOut != null) | 
|  | { | 
|  | Log.LogError("Properties ProtoDepDir and DependencyOut may not be both specified"); | 
|  | } | 
|  |  | 
|  | if (Protobuf.Length > 1 && (ProtoDepDir != null || DependencyOut != null)) | 
|  | { | 
|  | Log.LogError("Proto compiler currently allows only one input when " + | 
|  | "--dependency_out is specified (via ProtoDepDir or DependencyOut). " + | 
|  | "Tracking issue: https://github.com/protocolbuffers/protobuf/pull/3959"); | 
|  | } | 
|  |  | 
|  | // Use ProtoDepDir to autogenerate DependencyOut | 
|  | if (ProtoDepDir != null) | 
|  | { | 
|  | DependencyOut = DepFileUtil.GetDepFilenameForProto(ProtoDepDir, Protobuf[0].ItemSpec); | 
|  | } | 
|  |  | 
|  | if (GrpcPluginExe == null) | 
|  | { | 
|  | GrpcOutputOptions = null; | 
|  | GrpcOutputDir = null; | 
|  | } | 
|  | else if (GrpcOutputDir == null) | 
|  | { | 
|  | // Use OutputDir for gRPC output if not specified otherwise by user. | 
|  | GrpcOutputDir = OutputDir; | 
|  | } | 
|  |  | 
|  | return !Log.HasLoggedErrors && base.ValidateParameters(); | 
|  | } | 
|  |  | 
|  | // Protoc chokes on BOM, naturally. I would! | 
|  | static readonly Encoding s_utf8WithoutBom = new UTF8Encoding(false); | 
|  | protected override Encoding ResponseFileEncoding => s_utf8WithoutBom; | 
|  |  | 
|  | // Protoc takes one argument per line from the response file, and does not | 
|  | // require any quoting whatsoever. Otherwise, this is similar to the | 
|  | // standard CommandLineBuilder | 
|  | class ProtocResponseFileBuilder | 
|  | { | 
|  | StringBuilder _data = new StringBuilder(1000); | 
|  | public override string ToString() => _data.ToString(); | 
|  |  | 
|  | // If 'value' is not empty, append '--name=value\n'. | 
|  | public void AddSwitchMaybe(string name, string value) | 
|  | { | 
|  | if (!string.IsNullOrEmpty(value)) | 
|  | { | 
|  | _data.Append("--").Append(name).Append("=") | 
|  | .Append(value).Append('\n'); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Add switch with the 'values' separated by commas, for options. | 
|  | public void AddSwitchMaybe(string name, string[] values) | 
|  | { | 
|  | if (values?.Length > 0) | 
|  | { | 
|  | _data.Append("--").Append(name).Append("=") | 
|  | .Append(string.Join(",", values)).Append('\n'); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Add a positional argument to the file data. | 
|  | public void AddArg(string arg) | 
|  | { | 
|  | _data.Append(arg).Append('\n'); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Called by the base ToolTask to get response file contents. | 
|  | protected override string GenerateResponseFileCommands() | 
|  | { | 
|  | var cmd = new ProtocResponseFileBuilder(); | 
|  | cmd.AddSwitchMaybe(Generator + "_out", TrimEndSlash(OutputDir)); | 
|  | cmd.AddSwitchMaybe(Generator + "_opt", OutputOptions); | 
|  | cmd.AddSwitchMaybe("plugin=protoc-gen-grpc", GrpcPluginExe); | 
|  | cmd.AddSwitchMaybe("grpc_out", TrimEndSlash(GrpcOutputDir)); | 
|  | cmd.AddSwitchMaybe("grpc_opt", GrpcOutputOptions); | 
|  | if (ProtoPath != null) | 
|  | { | 
|  | foreach (string path in ProtoPath) | 
|  | { | 
|  | cmd.AddSwitchMaybe("proto_path", TrimEndSlash(path)); | 
|  | } | 
|  | } | 
|  | cmd.AddSwitchMaybe("dependency_out", DependencyOut); | 
|  | cmd.AddSwitchMaybe("error_format", "msvs"); | 
|  |  | 
|  | if (AdditionalProtocArguments != null) | 
|  | { | 
|  | foreach (var additionalProtocOption in AdditionalProtocArguments) | 
|  | { | 
|  | cmd.AddArg(additionalProtocOption); | 
|  | } | 
|  | } | 
|  |  | 
|  | foreach (var proto in Protobuf) | 
|  | { | 
|  | cmd.AddArg(proto.ItemSpec); | 
|  | } | 
|  | return cmd.ToString(); | 
|  | } | 
|  |  | 
|  | // Protoc cannot digest trailing slashes in directory names, | 
|  | // curiously under Linux, but not in Windows. | 
|  | static string TrimEndSlash(string dir) | 
|  | { | 
|  | if (dir == null || dir.Length <= 1) | 
|  | { | 
|  | return dir; | 
|  | } | 
|  | string trim = dir.TrimEnd('/', '\\'); | 
|  | // Do not trim the root slash, drive letter possible. | 
|  | if (trim.Length == 0) | 
|  | { | 
|  | // Slashes all the way down. | 
|  | return dir.Substring(0, 1); | 
|  | } | 
|  | if (trim.Length == 2 && dir.Length > 2 && trim[1] == ':') | 
|  | { | 
|  | // We have a drive letter and root, e. g. 'C:\' | 
|  | return dir.Substring(0, 3); | 
|  | } | 
|  | return trim; | 
|  | } | 
|  |  | 
|  | // Called by the base class to log tool's command line. | 
|  | // | 
|  | // Protoc command file is peculiar, with one argument per line, separated | 
|  | // by newlines. Unwrap it for log readability into a single line, and also | 
|  | // quote arguments, lest it look weird and so it may be copied and pasted | 
|  | // into shell. Since this is for logging only, correct enough is correct. | 
|  | protected override void LogToolCommand(string cmd) | 
|  | { | 
|  | var printer = new StringBuilder(1024); | 
|  |  | 
|  | // Print 'str' slice into 'printer', wrapping in quotes if contains some | 
|  | // interesting characters in file names, or if empty string. The list of | 
|  | // characters requiring quoting is not by any means exhaustive; we are | 
|  | // just striving to be nice, not guaranteeing to be nice. | 
|  | var quotable = new[] { ' ', '!', '$', '&', '\'', '^' }; | 
|  | void PrintQuoting(string str, int start, int count) | 
|  | { | 
|  | bool wrap = count == 0 || str.IndexOfAny(quotable, start, count) >= 0; | 
|  | if (wrap) printer.Append('"'); | 
|  | printer.Append(str, start, count); | 
|  | if (wrap) printer.Append('"'); | 
|  | } | 
|  |  | 
|  | for (int ib = 0, ie; (ie = cmd.IndexOf('\n', ib)) >= 0; ib = ie + 1) | 
|  | { | 
|  | // First line only contains both the program name and the first switch. | 
|  | // We can rely on at least the '--out_dir' switch being always present. | 
|  | if (ib == 0) | 
|  | { | 
|  | int iep = cmd.IndexOf(" --"); | 
|  | if (iep > 0) | 
|  | { | 
|  | PrintQuoting(cmd, 0, iep); | 
|  | ib = iep + 1; | 
|  | } | 
|  | } | 
|  | printer.Append(' '); | 
|  | if (cmd[ib] == '-') | 
|  | { | 
|  | // Print switch unquoted, including '=' if any. | 
|  | int iarg = cmd.IndexOf('=', ib, ie - ib); | 
|  | if (iarg < 0) | 
|  | { | 
|  | // Bare switch without a '='. | 
|  | printer.Append(cmd, ib, ie - ib); | 
|  | continue; | 
|  | } | 
|  | printer.Append(cmd, ib, iarg + 1 - ib); | 
|  | ib = iarg + 1; | 
|  | } | 
|  | // A positional argument or switch value. | 
|  | PrintQuoting(cmd, ib, ie - ib); | 
|  | } | 
|  |  | 
|  | base.LogToolCommand(printer.ToString()); | 
|  | } | 
|  |  | 
|  | protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance) | 
|  | { | 
|  | foreach (ErrorListFilter filter in s_errorListFilters) | 
|  | { | 
|  | Match match = filter.Pattern.Match(singleLine); | 
|  |  | 
|  | if (match.Success) | 
|  | { | 
|  | filter.LogAction(Log, match); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | base.LogEventsFromTextOutput(singleLine, messageImportance); | 
|  | } | 
|  |  | 
|  | // Main task entry point. | 
|  | public override bool Execute() | 
|  | { | 
|  | base.UseCommandProcessor = false; | 
|  |  | 
|  | bool ok = base.Execute(); | 
|  | if (!ok) | 
|  | { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Read dependency output file from the compiler to retrieve the | 
|  | // definitive list of created files. Report the dependency file | 
|  | // itself as having been written to. | 
|  | if (DependencyOut != null) | 
|  | { | 
|  | string[] outputs = DepFileUtil.ReadDependencyOutputs(DependencyOut, Log); | 
|  | if (HasLoggedErrors) | 
|  | { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | GeneratedFiles = new ITaskItem[outputs.Length]; | 
|  | for (int i = 0; i < outputs.Length; i++) | 
|  | { | 
|  | GeneratedFiles[i] = new TaskItem(outputs[i]); | 
|  | } | 
|  | AdditionalFileWrites = new ITaskItem[] { new TaskItem(DependencyOut) }; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | class ErrorListFilter | 
|  | { | 
|  | public Regex Pattern { get; set; } | 
|  | public Action<TaskLoggingHelper, Match> LogAction { get; set; } | 
|  | } | 
|  | }; | 
|  | } |