/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections; using System.Text; using System.Text.RegularExpressions; using Microsoft.Build.Framework; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; namespace JetBrains.Build { public class Peverify : ToolTask { #region Data protected static Regex myRegexErrorCode = new Regex(@"\(\s*Error\:\s*(?0x[0-9a-zA-z]+)\s*\)", RegexOptions.Compiled | RegexOptions.Singleline); protected static Regex myRegexErrorFile = new Regex(@"Error\:\s+\[\s*(?.+?)\s+\:\s+", RegexOptions.Compiled | RegexOptions.Singleline); protected static Regex myRegexInt32Hex = new Regex(@"^0x[0-9a-zA-z]{8}$", RegexOptions.Compiled | RegexOptions.Singleline); protected static Regex myRegexMessageCategory = new Regex(@"\[(?[A-Z]+)\]\:", RegexOptions.Compiled | RegexOptions.Singleline); protected static Regex myRegexParseErrorInstance = new Regex(@"\s+\:\s+(?.+?)\].*\[offset (?0x[0-9A-Za-z]{8})\].*\(\s*Error\:\s*(?0x[0-9a-zA-z]{8})\s*\)", RegexOptions.Compiled | RegexOptions.Singleline); protected static Regex myRegexWarningCode = new Regex(@"\(\s*Warning\:\s*(?0x[0-9a-zA-z]+)\s*\)", RegexOptions.Compiled | RegexOptions.Singleline); protected static Regex myRegexWarningFile = new Regex(@"Warning\:\s+\[\s*(?.+?)\s+\:\s+", RegexOptions.Compiled | RegexOptions.Singleline); protected Hashtable myBag = new Hashtable(); #endregion #region Attributes public bool HResult { get { return GetBoolParameterWithDefault("HResult", true); } set { Bag["HResult"] = value; } } /// /// Ignores a specifc instance of the error, ie in the specific member of the specific type, and at the specific offset. /// The item spec is up to you, but you must set the “Code”, “Member” and “Offset” metadata items. /// public ITaskItem[] IgnoreErrorInstances { get { return (ITaskItem[])Bag["IgnoreErrorInstances"]; } set { Bag["IgnoreErrorInstances"] = value; } } /// /// Ignores errors by their code. /// public ITaskItem[] IgnoreErrors { get { return (ITaskItem[])Bag["IgnoreErrors"]; } set { Bag["IgnoreErrors"] = value; } } public bool NoLogo { get { return GetBoolParameterWithDefault("NoLogo", true); } set { Bag["NoLogo"] = value; } } public bool Quiet { get { return GetBoolParameterWithDefault("Quiet", false); } set { Bag["Quiet"] = value; } } public ITaskItem[] Sources { get { return (ITaskItem[])Bag["Sources"]; } set { Bag["Sources"] = value; } } public bool Unique { get { return GetBoolParameterWithDefault("Unique", false); } set { Bag["Unique"] = value; } } public bool Verbose { get { return GetBoolParameterWithDefault("Verbose", true); } set { Bag["Verbose"] = value; } } #endregion #region Implementation /// Gets the collection of parameters used by the derived task class. /// The collection of parameters used by the derived task class. protected internal Hashtable Bag { get { return myBag; } } /// Gets the value of the specified Boolean parameter. /// The parameter value. /// The value to return if parameterName does not exist in the . /// The name of the parameter to return. protected internal bool GetBoolParameterWithDefault(string parameterName, bool defaultValue) { object obj1 = Bag[parameterName]; if(obj1 != null) return (bool)obj1; return defaultValue; } /// /// Checks whether the error instance has been specifically ignored. /// protected bool IsIgnoredErrorInstance(string text) { if((IgnoreErrorInstances == null) || (IgnoreErrorInstances.Length == 0)) return false; foreach(ITaskItem item in IgnoreErrorInstances) { if(IsIgnoredErrorInstance_Item(item, text)) return true; } return false; } /// /// for one specific item. /// protected bool IsIgnoredErrorInstance_Item(ITaskItem item, string text) { string sCode = item.GetMetadata("Code"); string sMember = item.GetMetadata("Member"); string sOffset = item.GetMetadata("Offset"); if(!myRegexInt32Hex.IsMatch(sCode)) { Log.LogError("The Code item metadata value “{0}” must be a prefixed 8-digit hex number, eg “0xDEADBEEF”.", sCode); return false; } if(!myRegexInt32Hex.IsMatch(sOffset)) { Log.LogError("The Offset item metadata value “{0}” must be a prefixed 8-digit hex number, eg “0xDEADBEEF”.", sOffset); return false; } // Parse components Match match = myRegexParseErrorInstance.Match(text); if(!match.Success) return false; // Check if(sCode != match.Groups["Code"].Value) return false; if(sMember != match.Groups["Member"].Value) return false; if(sOffset != match.Groups["Offset"].Value) return false; return true; } #endregion #region Overrides /// ///Creates a temporoary response (.rsp) file and runs the executable file. /// /// /// ///The returned exit code of the executable file. If the task logged errors, but the executable returned an exit code of 0, this method returns -1. /// /// ///The command line arguments to pass directly to the executable file. ///The command line arguments to place in the .rsp file. ///The path to the executable file. protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) { base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); return 0; } /// ///Returns a string value containing the command line arguments to pass directly to the executable file. /// /// /// ///A string value containing the command line arguments to pass directly to the executable file. /// /// protected override string GenerateCommandLineCommands() { var cmd = new CommandLineBuilderExtension(); cmd.AppendFileNamesIfNotNull(Sources, " "); if(NoLogo) cmd.AppendSwitch("/nologo"); if(Verbose) cmd.AppendSwitch("/verbose"); if(Quiet) cmd.AppendSwitch("/quiet"); if(Unique) cmd.AppendSwitch("/unique"); if(HResult) cmd.AppendSwitch("/hresult"); // Ignore list ITaskItem[] arIgnore = IgnoreErrors; if((arIgnore != null) && (arIgnore.Length > 0)) { var sb = new StringBuilder(); foreach(ITaskItem item in arIgnore) { if(sb.Length != 0) sb.Append(','); sb.Append(item.ItemSpec); } cmd.AppendSwitch("/ignore=" + sb); } return cmd.ToString(); } /// ///Returns the fully qualified path to the executable file. /// /// /// ///The fully qualified path to the executable file. /// /// protected override string GenerateFullPathToTool() { string path = ToolLocationHelper.GetPathToDotNetFrameworkSdkFile(ToolName, TargetDotNetFrameworkVersion.VersionLatest); if(path == null) Log.LogError("Could not locate the {0} compiler.", ToolName); return path; } /// ///Returns a string value containing the command line arguments to add to the response (.rsp) file before running the executable file. /// /// /// ///a string value containing the command line arguments to add to the response (.rsp) file before running the executable file. /// /// protected override string GenerateResponseFileCommands() { // NOP return ""; } /// ///Parses a single line of text to identify any errors or warnings in canonical format. /// /// ///A value of that indicates the importance level with which to log the message. ///A single line of text for the method to parse. protected override void LogEventsFromTextOutput(string text, MessageImportance messageImportance) { // Check if it's a warning or an error ErrorOrWarning type = ErrorOrWarning.None; if(text.Contains("Error:")) type = IsIgnoredErrorInstance(text) ? ErrorOrWarning.ErrorAsWarning : ErrorOrWarning.Error; else if(text.Contains("Warning:")) type = ErrorOrWarning.Warning; // Normal messages? if(type == ErrorOrWarning.None) { base.LogEventsFromTextOutput(text, messageImportance); return; } // Rip out data string sCode = ""; string sCategory = ""; string sFile = ""; Match match; switch(type) { case ErrorOrWarning.Warning: match = myRegexWarningCode.Match(text); if(match.Success) sCode = match.Groups["Code"].Value; match = myRegexMessageCategory.Match(text); if(match.Success) sCategory = match.Groups["Category"].Value; match = myRegexWarningFile.Match(text); if(match.Success) sFile = match.Groups["Name"].Value; break; case ErrorOrWarning.Error: match = myRegexErrorCode.Match(text); if(match.Success) sCode = match.Groups["Code"].Value; match = myRegexMessageCategory.Match(text); if(match.Success) sCategory = match.Groups["Category"].Value; match = myRegexErrorFile.Match(text); if(match.Success) sFile = match.Groups["Name"].Value; break; case ErrorOrWarning.ErrorAsWarning: goto case ErrorOrWarning.Error; default: throw new InvalidOperationException(string.Format("Unexpected type “{0}”.", type)); } // Log switch(type) { case ErrorOrWarning.Warning: Log.LogWarning(sCategory, sCode, "", sFile, 0, 0, 0, 0, text); break; case ErrorOrWarning.Error: Log.LogError(sCategory, sCode, "", sFile, 0, 0, 0, 0, text); break; case ErrorOrWarning.ErrorAsWarning: Log.LogWarning(sCategory, sCode, "", sFile, 0, 0, 0, 0, "[IgnoreErrorInstance] " + text); break; default: throw new InvalidOperationException(string.Format("Unexpected type “{0}”.", type)); } } /// ///Indicates whether all task paratmeters are valid. /// /// /// ///true if all task parameters are valid; otherwise, false. /// /// protected override bool ValidateParameters() { if((Sources == null) || (Sources.Length == 0)) { Log.LogError("There must be at least one source for the task."); return false; } if((IgnoreErrorInstances != null) && (IgnoreErrorInstances.Length != 0) && ((!Verbose) || (!HResult) || (Quiet))) { Log.LogError("In order to use IgnoreErrorInstances, you must turn on Verbose and HResult, and turn off Quiet."); return false; } return base.ValidateParameters(); } /// ///Gets the name of the executable file to run. /// /// /// ///The name of the executable file to run. /// /// protected override string ToolName { get { return "Peverify.exe"; } } #endregion #region ErrorOrWarning Type /// /// How to treat a message we're logging. /// protected enum ErrorOrWarning { /// /// Just text. /// None, /// /// A warning (go yellow). /// Warning, /// /// An error (go red). /// Error, /// /// Formally, an error, but it's in the personal ignore list and we must still go yellow. /// ErrorAsWarning, } #endregion } }