|  | @@ -16,7 +16,10 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #endregion
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +using System;
 | 
	
		
			
				|  |  | +using System.Collections.Generic;
 | 
	
		
			
				|  |  |  using System.Text;
 | 
	
		
			
				|  |  | +using System.Text.RegularExpressions;
 | 
	
		
			
				|  |  |  using Microsoft.Build.Framework;
 | 
	
		
			
				|  |  |  using Microsoft.Build.Utilities;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -123,6 +126,110 @@ namespace Grpc.Tools
 | 
	
		
			
				|  |  |                                                          "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 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>
 | 
	
	
		
			
				|  | @@ -406,6 +513,22 @@ namespace Grpc.Tools
 | 
	
		
			
				|  |  |              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()
 | 
	
		
			
				|  |  |          {
 | 
	
	
		
			
				|  | @@ -438,5 +561,11 @@ namespace Grpc.Tools
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              return true;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        class ErrorListFilter
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            public Regex Pattern { get; set; }
 | 
	
		
			
				|  |  | +            public Action<TaskLoggingHelper, Match> LogAction { get; set; }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  |  }
 |