2 // ToolTask.cs: Base class for command line tool tasks.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 // Ankit Jain (jankit@novell.com)
8 // (C) 2005 Marek Sieradzki
9 // Copyright 2009 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Diagnostics;
34 using System.Collections;
35 using System.Collections.Specialized;
37 using System.Resources;
39 using System.Text.RegularExpressions;
40 using Microsoft.Build.Framework;
41 using Mono.XBuild.Utilities;
43 namespace Microsoft.Build.Utilities
45 public abstract class ToolTask : Task
47 StringDictionary environmentOverride;
50 string toolPath, toolExe;
51 Encoding responseFileEncoding;
52 MessageImportance standardErrorLoggingImportance;
53 MessageImportance standardOutputLoggingImportance;
54 StringBuilder toolOutput;
55 bool typeLoadException;
62 this.standardErrorLoggingImportance = MessageImportance.High;
63 this.standardOutputLoggingImportance = MessageImportance.Normal;
66 protected ToolTask (ResourceManager taskResources)
67 : this (taskResources, null)
71 protected ToolTask (ResourceManager taskResources,
72 string helpKeywordPrefix)
74 this.TaskResources = taskResources;
75 this.HelpKeywordPrefix = helpKeywordPrefix;
76 this.toolPath = MonoLocationHelper.GetBinDir ();
77 this.responseFileEncoding = Encoding.UTF8;
78 this.timeout = Int32.MaxValue;
79 this.environmentOverride = new StringDictionary ();
86 + @"(((?<ORIGIN>(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)"
88 + "(?<SUBCATEGORY>(()|([^:]*? )))"
89 + "(?<CATEGORY>(error|warning)) "
92 RegexOptions.IgnoreCase);
96 protected virtual bool CallHostObjectToExecute ()
101 public override bool Execute ()
103 if (SkipTaskExecution ())
106 exitCode = ExecuteTool (GenerateFullPathToTool (), GenerateResponseFileCommands (),
107 GenerateCommandLineCommands ());
109 // HandleTaskExecutionErrors is called only if exitCode != 0
110 return exitCode == 0 || HandleTaskExecutionErrors ();
114 protected virtual string GetWorkingDirectory ()
119 protected virtual int ExecuteTool (string pathToTool,
120 string responseFileCommands,
121 string commandLineCommands)
124 if (pathToTool == null)
125 throw new ArgumentNullException ("pathToTool");
127 string output, error, responseFileName;
128 StreamWriter outwr, errwr;
130 outwr = errwr = null;
131 responseFileName = output = error = null;
132 toolOutput = new StringBuilder ();
135 string arguments = commandLineCommands;
136 if (!String.IsNullOrEmpty (responseFileCommands)) {
137 responseFileName = Path.GetTempFileName ();
138 File.WriteAllText (responseFileName, responseFileCommands);
139 arguments = arguments + " " + GetResponseFileSwitch (responseFileName);
142 LogToolCommand (String.Format ("Tool {0} execution started with arguments: {1} {2}",
143 pathToTool, commandLineCommands, responseFileCommands));
145 output = Path.GetTempFileName ();
146 error = Path.GetTempFileName ();
147 outwr = new StreamWriter (output);
148 errwr = new StreamWriter (error);
150 ProcessStartInfo pinfo = new ProcessStartInfo (pathToTool, arguments);
151 pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory;
153 pinfo.UseShellExecute = false;
154 pinfo.RedirectStandardOutput = true;
155 pinfo.RedirectStandardError = true;
158 ProcessWrapper pw = ProcessService.StartProcess (pinfo, outwr, errwr, null, environmentOverride);
159 pw.WaitForOutput (timeout == Int32.MaxValue ? -1 : timeout);
160 exitCode = pw.ExitCode;
164 } catch (System.ComponentModel.Win32Exception e) {
165 Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message);
169 ProcessOutputFile (output, StandardOutputLoggingImportance);
170 ProcessOutputFile (error, StandardErrorLoggingImportance);
172 Log.LogMessage (MessageImportance.Low, "Tool {0} execution finished.", pathToTool);
175 DeleteTempFile (responseFileName);
181 DeleteTempFile (output);
182 DeleteTempFile (error);
186 void ProcessOutputFile (string filename, MessageImportance importance)
188 using (StreamReader sr = File.OpenText (filename)) {
190 while ((line = sr.ReadLine ()) != null) {
191 if (typeLoadException) {
192 toolOutput.Append (sr.ReadToEnd ());
193 string output_str = toolOutput.ToString ();
194 Regex reg = new Regex (@".*WARNING.*used in (mscorlib|System),.*",
195 RegexOptions.Multiline);
197 if (reg.Match (output_str).Success)
199 "Error: A referenced assembly may be built with an incompatible " +
200 "CLR version. See the compilation output for more details.");
203 "Error: A dependency of a referenced assembly may be missing, or " +
204 "you may be referencing an assembly created with a newer CLR " +
205 "version. See the compilation output for more details.");
207 Log.LogError (output_str);
210 toolOutput.AppendLine (line);
211 LogEventsFromTextOutput (line, importance);
216 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
218 singleLine = singleLine.Trim ();
219 if (singleLine.Length == 0)
222 if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
223 singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
224 typeLoadException = true;
227 // When IncludeDebugInformation is true, prevents the debug symbols stats from braeking this.
228 if (singleLine.StartsWith ("WROTE SYMFILE") ||
229 singleLine.StartsWith ("OffsetTable") ||
230 singleLine.StartsWith ("Compilation succeeded") ||
231 singleLine.StartsWith ("Compilation failed"))
234 string filename, origin, category, code, subcategory, text;
235 int lineNumber, columnNumber, endLineNumber, endColumnNumber;
237 Match m = regex.Match (singleLine);
238 origin = m.Groups [regex.GroupNumberFromName ("ORIGIN")].Value;
239 category = m.Groups [regex.GroupNumberFromName ("CATEGORY")].Value;
240 code = m.Groups [regex.GroupNumberFromName ("CODE")].Value;
241 subcategory = m.Groups [regex.GroupNumberFromName ("SUBCATEGORY")].Value;
242 text = m.Groups [regex.GroupNumberFromName ("TEXT")].Value;
244 ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber);
246 if (category == "warning") {
247 Log.LogWarning (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
248 endColumnNumber, text, null);
249 } else if (category == "error") {
250 Log.LogError (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
251 endColumnNumber, text, null);
253 Log.LogMessage (importance, singleLine);
257 private void ParseOrigin (string origin, out string filename,
258 out int lineNumber, out int columnNumber,
259 out int endLineNumber, out int endColumnNumber)
263 string[] left, right;
265 if (origin.IndexOf ('(') != -1 ) {
266 lParen = origin.IndexOf ('(');
267 filename = origin.Substring (0, lParen);
268 temp = origin.Substring (lParen + 1, origin.Length - lParen - 2).Split (',');
269 if (temp.Length == 1) {
270 left = temp [0].Split ('-');
271 if (left.Length == 1) {
272 lineNumber = Int32.Parse (left [0]);
276 } else if (left.Length == 2) {
277 lineNumber = Int32.Parse (left [0]);
279 endLineNumber = Int32.Parse (left [1]);
282 throw new Exception ("Invalid line/column format.");
283 } else if (temp.Length == 2) {
284 right = temp [1].Split ('-');
285 lineNumber = Int32.Parse (temp [0]);
287 if (right.Length == 1) {
288 columnNumber = Int32.Parse (right [0]);
290 } else if (right.Length == 2) {
291 columnNumber = Int32.Parse (right [0]);
292 endColumnNumber = Int32.Parse (right [0]);
294 throw new Exception ("Invalid line/column format.");
295 } else if (temp.Length == 4) {
296 lineNumber = Int32.Parse (temp [0]);
297 endLineNumber = Int32.Parse (temp [2]);
298 columnNumber = Int32.Parse (temp [1]);
299 endColumnNumber = Int32.Parse (temp [3]);
301 throw new Exception ("Invalid line/column format.");
311 protected virtual string GenerateCommandLineCommands ()
316 protected abstract string GenerateFullPathToTool ();
318 protected virtual string GenerateResponseFileCommands ()
323 protected virtual string GetResponseFileSwitch (string responseFilePath)
325 return String.Format ("@{0}", responseFilePath);
328 protected virtual bool HandleTaskExecutionErrors ()
330 if (!Log.HasLoggedErrors && exitCode != 0)
331 Log.LogError ("Tool exited with code: {0}. Output: {1}", exitCode,
332 toolOutput != null ? toolOutput.ToString () : String.Empty);
335 return ExitCode == 0 && !Log.HasLoggedErrors;
338 protected virtual HostObjectInitializationStatus InitializeHostObject ()
340 return HostObjectInitializationStatus.NoActionReturnSuccess;
343 protected virtual void LogToolCommand (string message)
345 Log.LogMessage (MessageImportance.Normal, message);
349 protected virtual void LogPathToTool (string toolName,
354 protected virtual bool SkipTaskExecution()
356 return !ValidateParameters ();
359 protected virtual bool ValidateParameters()
364 protected void DeleteTempFile (string fileName)
366 if (String.IsNullOrEmpty (fileName))
370 File.Delete (fileName);
371 } catch (IOException ioe) {
372 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", ioe.Message);
373 } catch (UnauthorizedAccessException uae) {
374 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", uae.Message);
378 protected virtual StringDictionary EnvironmentOverride
380 get { return environmentOverride; }
384 public int ExitCode {
385 get { return exitCode; }
388 protected virtual Encoding ResponseFileEncoding
390 get { return responseFileEncoding; }
393 protected virtual Encoding StandardErrorEncoding
395 get { return Console.Error.Encoding; }
398 protected virtual MessageImportance StandardErrorLoggingImportance {
399 get { return standardErrorLoggingImportance; }
402 protected virtual Encoding StandardOutputEncoding
404 get { return Console.Out.Encoding; }
407 protected virtual MessageImportance StandardOutputLoggingImportance {
408 get { return standardOutputLoggingImportance; }
411 protected virtual bool HasLoggedErrors {
412 get { return Log.HasLoggedErrors; }
415 public virtual int Timeout
417 get { return timeout; }
418 set { timeout = value; }
421 public virtual string ToolExe
430 if (!String.IsNullOrEmpty (value))
435 protected abstract string ToolName
440 public string ToolPath
442 get { return toolPath; }
444 if (!String.IsNullOrEmpty (value))