diff options
Diffstat (limited to 'src/csharp/Grpc.Tools/ProtoCompile.cs')
-rw-r--r-- | src/csharp/Grpc.Tools/ProtoCompile.cs | 782 |
1 files changed, 407 insertions, 375 deletions
diff --git a/src/csharp/Grpc.Tools/ProtoCompile.cs b/src/csharp/Grpc.Tools/ProtoCompile.cs index e77084b1ef..93608e1ac0 100644 --- a/src/csharp/Grpc.Tools/ProtoCompile.cs +++ b/src/csharp/Grpc.Tools/ProtoCompile.cs @@ -20,390 +20,422 @@ using System.Text; 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", - }; - - /// <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). - /// </summary> - [Required] - public string OutputDir { get; set; } - - /// <summary> - /// Codegen options. See also OptionsFromMetadata. - /// Switch: --GEN_out= (for different generators GEN). - /// </summary> - public string[] OutputOptions { 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; } - +namespace Grpc.Tools +{ /// <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= + /// 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 string GrpcOutputDir { get; set; } - - /// <summary> - /// gRPC Codegen options. See also OptionsFromMetadata. - /// --grpc_opt=opt1,opt2=val (comma-separated). - /// </summary> - public string[] GrpcOutputOptions { get; set; } + 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" }; + + /// <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). + /// </summary> + [Required] + public string OutputDir { get; set; } + + /// <summary> + /// Codegen options. See also OptionsFromMetadata. + /// Switch: --GEN_out= (for different generators GEN). + /// </summary> + public string[] OutputOptions { 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/google/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(); + } - /// <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; } + // 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'); + } + }; - /// <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/google/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'); + // 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); + foreach (var proto in ProtoBuf) + { + cmd.AddArg(proto.ItemSpec); + } + return cmd.ToString(); } - } - // 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'); + // 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; } - } - - // 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); - 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()); - } - - // 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; + // 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()); } - GeneratedFiles = new ITaskItem[outputs.Length]; - for (int i = 0; i < outputs.Length; i++) { - GeneratedFiles[i] = new TaskItem(outputs[i]); + // 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; } - AdditionalFileWrites = new ITaskItem[] { - new TaskItem(DependencyOut) - }; - } - - return true; - } - }; + }; } |