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 Log.LogMessage (MessageImportance.Normal, "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)
351 protected virtual void LogPathToTool (string toolName,
356 protected virtual bool SkipTaskExecution()
361 protected virtual bool ValidateParameters()
366 protected void DeleteTempFile (string fileName)
368 if (String.IsNullOrEmpty (fileName))
372 File.Delete (fileName);
373 } catch (IOException ioe) {
374 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", ioe.Message);
375 } catch (UnauthorizedAccessException uae) {
376 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", uae.Message);
380 protected virtual StringDictionary EnvironmentOverride
382 get { return environmentOverride; }
386 public int ExitCode {
387 get { return exitCode; }
390 protected virtual Encoding ResponseFileEncoding
392 get { return responseFileEncoding; }
395 protected virtual Encoding StandardErrorEncoding
397 get { return Console.Error.Encoding; }
400 protected virtual MessageImportance StandardErrorLoggingImportance {
401 get { return standardErrorLoggingImportance; }
404 protected virtual Encoding StandardOutputEncoding
406 get { return Console.Out.Encoding; }
409 protected virtual MessageImportance StandardOutputLoggingImportance {
410 get { return standardOutputLoggingImportance; }
413 protected virtual bool HasLoggedErrors {
414 get { return Log.HasLoggedErrors; }
417 public virtual int Timeout
419 get { return timeout; }
420 set { timeout = value; }
423 protected abstract string ToolName
428 public string ToolPath
430 get { return toolPath; }
431 set { toolPath = value; }