///
/// 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
}
}