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;
52 Encoding responseFileEncoding;
53 MessageImportance standardErrorLoggingImportance;
54 MessageImportance standardOutputLoggingImportance;
55 StringBuilder toolOutput;
56 bool typeLoadException;
63 this.standardErrorLoggingImportance = MessageImportance.High;
64 this.standardOutputLoggingImportance = MessageImportance.Normal;
67 protected ToolTask (ResourceManager taskResources)
68 : this (taskResources, null)
72 protected ToolTask (ResourceManager taskResources,
73 string helpKeywordPrefix)
75 this.TaskResources = taskResources;
76 this.HelpKeywordPrefix = helpKeywordPrefix;
77 this.toolPath = MonoLocationHelper.GetBinDir ();
78 this.responseFileEncoding = Encoding.UTF8;
79 this.timeout = Int32.MaxValue;
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 return HandleTaskExecutionErrors ();
113 protected virtual string GetWorkingDirectory ()
118 protected virtual int ExecuteTool (string pathToTool,
119 string responseFileCommands,
120 string commandLineCommands)
123 if (pathToTool == null)
124 throw new ArgumentNullException ("pathToTool");
126 string output, error, responseFileName;
127 StreamWriter outwr, errwr;
129 outwr = errwr = null;
130 responseFileName = output = error = null;
131 toolOutput = new StringBuilder ();
134 string arguments = commandLineCommands;
135 if (!String.IsNullOrEmpty (responseFileCommands)) {
136 responseFileName = Path.GetTempFileName ();
137 File.WriteAllText (responseFileName, responseFileCommands);
138 arguments = arguments + " " + GetResponseFileSwitch (responseFileName);
141 LogToolCommand (String.Format ("Tool {0} execution started with arguments: {1} {2}",
142 pathToTool, commandLineCommands, responseFileCommands));
144 output = Path.GetTempFileName ();
145 error = Path.GetTempFileName ();
146 outwr = new StreamWriter (output);
147 errwr = new StreamWriter (error);
149 ProcessStartInfo pinfo = new ProcessStartInfo (pathToTool, arguments);
150 pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory;
152 pinfo.UseShellExecute = false;
153 pinfo.RedirectStandardOutput = true;
154 pinfo.RedirectStandardError = true;
157 ProcessWrapper pw = ProcessService.StartProcess (pinfo, outwr, errwr, null, environmentOverride);
158 pw.WaitForOutput (timeout == Int32.MaxValue ? -1 : timeout);
159 exitCode = pw.ExitCode;
163 } catch (System.ComponentModel.Win32Exception e) {
164 Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message);
168 ProcessOutputFile (output, standardOutputLoggingImportance);
169 ProcessOutputFile (error, standardErrorLoggingImportance);
171 if (!Log.HasLoggedErrors && exitCode != 0)
172 Log.LogError ("Tool crashed: " + toolOutput.ToString ());
174 Log.LogMessage (MessageImportance.Low, "Tool {0} execution finished.", pathToTool);
177 DeleteTempFile (responseFileName);
183 DeleteTempFile (output);
184 DeleteTempFile (error);
189 void ProcessOutputFile (string filename, MessageImportance importance)
191 using (StreamReader sr = File.OpenText (filename)) {
193 while ((line = sr.ReadLine ()) != null) {
194 if (typeLoadException) {
195 toolOutput.Append (sr.ReadToEnd ());
196 string output_str = toolOutput.ToString ();
197 Regex reg = new Regex (@".*WARNING.*used in (mscorlib|System),.*",
198 RegexOptions.Multiline);
200 if (reg.Match (output_str).Success)
202 "Error: A referenced assembly may be built with an incompatible " +
203 "CLR version. See the compilation output for more details.");
206 "Error: A dependency of a referenced assembly may be missing, or " +
207 "you may be referencing an assembly created with a newer CLR " +
208 "version. See the compilation output for more details.");
210 Log.LogError (output_str);
213 toolOutput.AppendLine (line);
214 LogEventsFromTextOutput (line, importance);
219 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
221 singleLine = singleLine.Trim ();
222 if (singleLine.Length == 0)
225 if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
226 singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
227 typeLoadException = true;
230 // When IncludeDebugInformation is true, prevents the debug symbols stats from braeking this.
231 if (singleLine.StartsWith ("WROTE SYMFILE") ||
232 singleLine.StartsWith ("OffsetTable") ||
233 singleLine.StartsWith ("Compilation succeeded") ||
234 singleLine.StartsWith ("Compilation failed"))
237 string filename, origin, category, code, subcategory, text;
238 int lineNumber, columnNumber, endLineNumber, endColumnNumber;
240 Match m = regex.Match (singleLine);
241 origin = m.Groups [regex.GroupNumberFromName ("ORIGIN")].Value;
242 category = m.Groups [regex.GroupNumberFromName ("CATEGORY")].Value;
243 code = m.Groups [regex.GroupNumberFromName ("CODE")].Value;
244 subcategory = m.Groups [regex.GroupNumberFromName ("SUBCATEGORY")].Value;
245 text = m.Groups [regex.GroupNumberFromName ("TEXT")].Value;
247 ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber);
249 if (category == "warning") {
250 Log.LogWarning (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
251 endColumnNumber, text, null);
252 } else if (category == "error") {
253 Log.LogError (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
254 endColumnNumber, text, null);
256 Log.LogMessage (singleLine);
260 private void ParseOrigin (string origin, out string filename,
261 out int lineNumber, out int columnNumber,
262 out int endLineNumber, out int endColumnNumber)
266 string[] left, right;
268 if (origin.IndexOf ('(') != -1 ) {
269 lParen = origin.IndexOf ('(');
270 filename = origin.Substring (0, lParen);
271 temp = origin.Substring (lParen + 1, origin.Length - lParen - 2).Split (',');
272 if (temp.Length == 1) {
273 left = temp [0].Split ('-');
274 if (left.Length == 1) {
275 lineNumber = Int32.Parse (left [0]);
279 } else if (left.Length == 2) {
280 lineNumber = Int32.Parse (left [0]);
282 endLineNumber = Int32.Parse (left [1]);
285 throw new Exception ("Invalid line/column format.");
286 } else if (temp.Length == 2) {
287 right = temp [1].Split ('-');
288 lineNumber = Int32.Parse (temp [0]);
290 if (right.Length == 1) {
291 columnNumber = Int32.Parse (right [0]);
293 } else if (right.Length == 2) {
294 columnNumber = Int32.Parse (right [0]);
295 endColumnNumber = Int32.Parse (right [0]);
297 throw new Exception ("Invalid line/column format.");
298 } else if (temp.Length == 4) {
299 lineNumber = Int32.Parse (temp [0]);
300 endLineNumber = Int32.Parse (temp [2]);
301 columnNumber = Int32.Parse (temp [1]);
302 endColumnNumber = Int32.Parse (temp [3]);
304 throw new Exception ("Invalid line/column format.");
315 protected virtual string GenerateCommandLineCommands ()
320 protected abstract string GenerateFullPathToTool ();
323 protected virtual string GenerateResponseFileCommands ()
329 protected virtual string GetResponseFileSwitch (string responseFilePath)
331 return String.Format ("@{0}", responseFilePath);
335 protected virtual bool HandleTaskExecutionErrors ()
337 return ExitCode == 0 && !Log.HasLoggedErrors;
340 protected virtual HostObjectInitializationStatus InitializeHostObject ()
342 return HostObjectInitializationStatus.NoActionReturnSuccess;
346 protected virtual void LogToolCommand (string message)
348 Log.LogMessage (MessageImportance.Normal, message);
352 protected virtual void LogPathToTool (string toolName,
357 protected virtual bool SkipTaskExecution()
362 protected virtual bool ValidateParameters()
367 protected void DeleteTempFile (string fileName)
369 if (String.IsNullOrEmpty (fileName))
373 File.Delete (fileName);
374 } catch (IOException ioe) {
375 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", ioe.Message);
376 } catch (UnauthorizedAccessException uae) {
377 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", uae.Message);
381 protected virtual StringDictionary EnvironmentOverride
383 get { return environmentOverride; }
387 public int ExitCode {
388 get { return exitCode; }
391 protected virtual Encoding ResponseFileEncoding
393 get { return responseFileEncoding; }
396 protected virtual Encoding StandardErrorEncoding
398 get { return Console.Error.Encoding; }
401 protected virtual MessageImportance StandardErrorLoggingImportance {
402 get { return standardErrorLoggingImportance; }
405 protected virtual Encoding StandardOutputEncoding
407 get { return Console.Out.Encoding; }
410 protected virtual MessageImportance StandardOutputLoggingImportance {
411 get { return standardOutputLoggingImportance; }
414 protected virtual bool HasLoggedErrors {
415 get { return Log.HasLoggedErrors; }
418 public virtual int Timeout
420 get { return timeout; }
421 set { timeout = value; }
424 protected abstract string ToolName
429 public string ToolPath
431 get { return toolPath; }
432 set { toolPath = value; }