PageRenderTime 49ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/GendarmeMsBuild/Gendarme.cs

https://github.com/anderslm/GendarmeMsBuild
C# | 288 lines | 222 code | 14 blank | 52 comment | 22 complexity | 737e386c82659e868d73555050499084 MD5 | raw file
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using System.Xml.Linq;
  7. using System.Diagnostics;
  8. using Microsoft.Build.Framework;
  9. using Microsoft.Build.Utilities;
  10. using System.Collections.Generic;
  11. namespace GendarmeMsBuild
  12. {
  13. public class Gendarme : Task
  14. {
  15. private string _gendarmeExeFilename = Path.Combine(Path.Combine(ProgramFilesx86(), "Gendarme"), "gendarme.exe");
  16. private static readonly Regex LineRegex = new Regex(@"(?<file>.*)\(≈?((?<line>\d+)|unavailable)(,(?<column>\d+))?\)", RegexOptions.Compiled);
  17. #region Task Properties
  18. /// <summary>
  19. /// The path to Gendarme.exe. Defaults to C:\program Files\gendarme\gendarme.exe (or C:\program files (x86)\gendarme\gendarme.exe on 64bit systems) if no value is supplied.
  20. /// </summary>
  21. public string GendarmeExeFilename
  22. {
  23. get { return _gendarmeExeFilename; }
  24. set { _gendarmeExeFilename = value; }
  25. }
  26. /// <summary>
  27. /// The assemblies to inspect. Multiple files and masks ('?', '*') are supported. Required.
  28. /// </summary>
  29. [Required]
  30. public ITaskItem[] Assemblies { get; set; }
  31. /// <summary>
  32. /// The path to the Gendarme config file. Maps to --config [filename] (optional)
  33. /// </summary>
  34. public string GendarmeConfigFilename { get; set; }
  35. /// <summary>
  36. /// The name of the ruleset to be used. Maps to --ruleset [set] (optional)
  37. /// </summary>
  38. public string Ruleset { get; set; }
  39. /// <summary>
  40. /// The path to the Gendarme ignore file. Maps to --ignore [filename] (optional)
  41. /// </summary>
  42. public string GendarmeIgnoreFilename { get; set; }
  43. /// <summary>
  44. /// The inspection severity. Maps to --severity [all | audit[+] | low[+|-] | medium[+|-] | high[+|-] | critical[-]] (optional)
  45. /// </summary>
  46. public string Severity { get; set; }
  47. /// <summary>
  48. /// The confidence level defects are filtered by. Maps to --confidence [all | low[+] | normal[+|-] | high[+|-] | total[-]] (optional)
  49. /// </summary>
  50. public string Confidence { get; set; }
  51. private int? limit = null;
  52. /// <summary>
  53. /// Limit the amount of defects found. Maps to --limit [value] (optional)
  54. /// </summary>
  55. public int Limit
  56. {
  57. get { return limit.HasValue ? limit.Value : -1; }
  58. set { limit = value >= 0 ? new int?(value) : null; }
  59. }
  60. /// <summary>
  61. /// The path to save Gendarme's output XML (optional)
  62. /// </summary>
  63. public string OutputXmlFilename { get; set; }
  64. /// <summary>
  65. /// Output minimal info. Maps to --quiet. Also causes the MSBuild task to output no info (optional). Ignored when Visual Studio integration is enabled.
  66. /// </summary>
  67. public bool Quiet { get; set; }
  68. /// <summary>
  69. /// Output verbose info. Maps to --verbose (optional). Ignored when Visual Studio integration is enabled.
  70. /// </summary>
  71. public bool Verbose { get; set; }
  72. /// <summary>
  73. /// Whether or not to fail the build if defects are found. Defaults to false. Useful when only the
  74. /// output XML is required. Ignored when Visual Studio integration is enabled.
  75. /// </summary>
  76. [Obsolete("use WarningsAsErrors instead")]
  77. public bool DefectsCauseFailure
  78. {
  79. get { return WarningsAsErrors; }
  80. set { WarningsAsErrors = value; }
  81. }
  82. /// <summary>
  83. /// Whether to consider defects (which are normally treated as warnings) as errors. When set to true,
  84. /// the build will fail if any defects are found. Optional.
  85. /// </summary>
  86. public bool WarningsAsErrors { get; set; }
  87. /// <summary>
  88. /// Whether or not to format the output in a format Visual Studio can understand. Defaults to false (optional)
  89. /// </summary>
  90. public bool IntegrateWithVisualStudio { get; set; }
  91. /// <summary>
  92. /// Whether to display gendarme defects in msbuild output.
  93. /// </summary>
  94. public bool Silent { get; set; }
  95. #endregion
  96. /// <summary>
  97. /// Execute the MSBuild task
  98. /// </summary>
  99. /// <returns>True if no defects are found, false otherwise.</returns>
  100. public override bool Execute()
  101. {
  102. if (!VerifyProperties()) return false;
  103. var thisOutputFile = OutputXmlFilename;
  104. var isUsingTempFile = false;
  105. if (string.IsNullOrEmpty(thisOutputFile))
  106. {
  107. thisOutputFile = Path.GetTempFileName();
  108. isUsingTempFile = true;
  109. }
  110. else
  111. {
  112. if(File.Exists(thisOutputFile))
  113. try
  114. {
  115. File.Delete(thisOutputFile);
  116. } catch(Exception e)
  117. {
  118. Log.LogErrorFromException(new Exception("Couldn't recreate output file " + thisOutputFile, e));
  119. return false;
  120. }
  121. }
  122. MaybeLogMessage("output file: " + thisOutputFile);
  123. try
  124. {
  125. var commandLineArguments = BuildCommandLineArguments(thisOutputFile);
  126. MaybeLogMessage("GendarmeMsBuild - command line arguments to Gendarme: " + commandLineArguments);
  127. var processInfo = new ProcessStartInfo(_gendarmeExeFilename, commandLineArguments) { CreateNoWindow = true, UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true };
  128. var sw = new Stopwatch();
  129. sw.Start();
  130. var proc = Process.Start(processInfo);
  131. var stdErr = proc.StandardError.ReadToEnd();
  132. var stdOut = proc.StandardOutput.ReadToEnd();
  133. proc.WaitForExit();
  134. var exitCode = proc.ExitCode;
  135. sw.Stop();
  136. MaybeLogMessage(String.Format("GendarmeMsBuild - finished running Gendarme in {0}ms", sw.ElapsedMilliseconds));
  137. if (exitCode != 0)
  138. {
  139. if (stdErr.Length > 0)
  140. {
  141. // problem with the call out to Gendarme
  142. Log.LogError(stdErr);
  143. return false;
  144. }
  145. else
  146. {
  147. if (!IntegrateWithVisualStudio && !string.IsNullOrEmpty(stdOut))
  148. Log.LogMessage(stdOut);
  149. if (!Silent)
  150. CreateVisualStudioOutput(thisOutputFile);
  151. return !WarningsAsErrors;
  152. }
  153. }
  154. if (!IntegrateWithVisualStudio && !string.IsNullOrEmpty(stdOut))
  155. Log.LogMessage(stdOut);
  156. return true;
  157. }
  158. finally
  159. {
  160. if (isUsingTempFile)
  161. try { File.Delete(thisOutputFile); }
  162. catch { /* do nothing */}
  163. }
  164. }
  165. #region helper methods/classes
  166. private string BuildCommandLineArguments(string thisOutputFile)
  167. {
  168. var sb = new StringBuilder();
  169. if (GendarmeConfigFilename != null)
  170. sb.Append(" --config ").Append('"').Append(GendarmeConfigFilename).Append('"');
  171. if (Ruleset != null)
  172. sb.Append(" --set ").Append('"').Append(Ruleset).Append('"');
  173. if (Severity != null)
  174. sb.Append(" --severity ").Append('"').Append(Severity).Append('"');
  175. if (Confidence != null)
  176. sb.Append(" --confidence ").Append('"').Append(Confidence).Append('"');
  177. if (GendarmeIgnoreFilename != null)
  178. sb.Append(" --ignore \"").Append(GendarmeIgnoreFilename).Append('"');
  179. if (limit.HasValue)
  180. sb.Append(" --limit ").Append(limit.Value.ToString());
  181. if (Quiet)
  182. sb.Append(" --quiet");
  183. if (Verbose)
  184. sb.Append(" --verbose");
  185. sb.Append(" --xml \"").Append(thisOutputFile).Append('"');
  186. foreach (var assembly in Assemblies)
  187. sb.Append(" \"").Append(assembly.ItemSpec).Append('"');
  188. return sb.ToString();
  189. }
  190. private bool VerifyProperties()
  191. {
  192. if (!File.Exists(GendarmeExeFilename))
  193. {
  194. Log.LogError("Couldn't find gendarme.exe at " + GendarmeExeFilename);
  195. return false;
  196. }
  197. if (!string.IsNullOrEmpty(GendarmeIgnoreFilename) && !File.Exists(GendarmeIgnoreFilename))
  198. {
  199. Log.LogError("Couldn't find the Gendarme ignore file at " + GendarmeExeFilename);
  200. return false;
  201. }
  202. return true;
  203. }
  204. private void CreateVisualStudioOutput(string outputFile)
  205. {
  206. var xdoc = XDocument.Load(outputFile);
  207. var q = from defect in xdoc.Root.Descendants("defect")
  208. let rule = defect.Parent.Parent
  209. let target = defect.Parent
  210. select new
  211. {
  212. RuleName = rule.Attribute("Uri").Value.Substring(rule.Attribute("Uri").Value.LastIndexOf('/') + 1).Replace('#', '.'),
  213. Problem = rule.Element("problem").Value,
  214. Solution = rule.Element("solution").Value,
  215. Source = LineRegex.IsMatch(defect.Attribute("Source").Value) ? defect.Attribute("Source").Value : null,
  216. Target = target.Attribute("Name").Value,
  217. Description = defect.Value
  218. };
  219. foreach (var defect in q)
  220. {
  221. if (defect.Source != null)
  222. {
  223. var groups = LineRegex.Match(defect.Source).Groups;
  224. int line = SafeConvert(groups["line"].Value), column = SafeConvert(groups["column"].Value);
  225. LogDefect("[gendarme]", defect.RuleName, null, groups["file"].Value, line, column, line, column, String.Format("{0}: {1}{2}{3}", defect.RuleName, defect.Problem, Environment.NewLine, defect.Description));
  226. }
  227. else
  228. {
  229. LogDefect("[gendarme]", defect.RuleName, null, null, 0, 0, 0, 0, String.Format("{0}: {1}: {2}{3}{4}", defect.RuleName, defect.Target, defect.Problem, Environment.NewLine, defect.Description));
  230. }
  231. }
  232. }
  233. private static int SafeConvert(string number)
  234. {
  235. try { return Convert.ToInt32(number); }
  236. catch { return 0; }
  237. }
  238. /// <summary>
  239. /// Log a message to MSBuild if Visual Studio integration isn't enabled, and the Quiet option isn't set.
  240. /// </summary>
  241. /// <param name="message"></param>
  242. private void MaybeLogMessage(string message)
  243. {
  244. if (!IntegrateWithVisualStudio && !Quiet)
  245. Log.LogMessage(message);
  246. }
  247. private void LogDefect(string subcategory, string errorCode, string helpKeyword, string file, int lineNumber,
  248. int columnNumber, int endLineNumber, int endColumnNumber, string message, params string[] messageArgs)
  249. {
  250. if (WarningsAsErrors)
  251. Log.LogError(subcategory, errorCode, helpKeyword, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, message, messageArgs);
  252. else
  253. Log.LogWarning(subcategory, errorCode, helpKeyword, file, lineNumber, columnNumber, endLineNumber, endColumnNumber, message, messageArgs);
  254. }
  255. static string ProgramFilesx86()
  256. {
  257. if (8 == IntPtr.Size
  258. || (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432"))))
  259. {
  260. return Environment.GetEnvironmentVariable("ProgramFiles(x86)");
  261. }
  262. return Environment.GetEnvironmentVariable("ProgramFiles");
  263. }
  264. #endregion
  265. }
  266. }