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 LogEventsFromTextOutput (line, importance);
218 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
220 toolOutput.AppendLine (singleLine);
222 singleLine = singleLine.Trim ();
223 if (singleLine.Length == 0)
226 if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
227 singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
228 typeLoadException = true;
231 // When IncludeDebugInformation is true, prevents the debug symbols stats from braeking this.
232 if (singleLine.StartsWith ("WROTE SYMFILE") ||
233 singleLine.StartsWith ("OffsetTable") ||
234 singleLine.StartsWith ("Compilation succeeded") ||
235 singleLine.StartsWith ("Compilation failed"))
238 string filename, origin, category, code, subcategory, text;
239 int lineNumber, columnNumber, endLineNumber, endColumnNumber;
241 Match m = regex.Match (singleLine);
242 origin = m.Groups [regex.GroupNumberFromName ("ORIGIN")].Value;
243 category = m.Groups [regex.GroupNumberFromName ("CATEGORY")].Value;
244 code = m.Groups [regex.GroupNumberFromName ("CODE")].Value;
245 subcategory = m.Groups [regex.GroupNumberFromName ("SUBCATEGORY")].Value;
246 text = m.Groups [regex.GroupNumberFromName ("TEXT")].Value;
248 ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber);
250 if (category == "warning") {
251 Log.LogWarning (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
252 endColumnNumber, text, null);
253 } else if (category == "error") {
254 Log.LogError (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
255 endColumnNumber, text, null);
257 Log.LogMessage (singleLine);
261 private void ParseOrigin (string origin, out string filename,
262 out int lineNumber, out int columnNumber,
263 out int endLineNumber, out int endColumnNumber)
267 string[] left, right;
269 if (origin.IndexOf ('(') != -1 ) {
270 lParen = origin.IndexOf ('(');
271 filename = origin.Substring (0, lParen);
272 temp = origin.Substring (lParen + 1, origin.Length - lParen - 2).Split (',');
273 if (temp.Length == 1) {
274 left = temp [0].Split ('-');
275 if (left.Length == 1) {
276 lineNumber = Int32.Parse (left [0]);
280 } else if (left.Length == 2) {
281 lineNumber = Int32.Parse (left [0]);
283 endLineNumber = Int32.Parse (left [1]);
286 throw new Exception ("Invalid line/column format.");
287 } else if (temp.Length == 2) {
288 right = temp [1].Split ('-');
289 lineNumber = Int32.Parse (temp [0]);
291 if (right.Length == 1) {
292 columnNumber = Int32.Parse (right [0]);
294 } else if (right.Length == 2) {
295 columnNumber = Int32.Parse (right [0]);
296 endColumnNumber = Int32.Parse (right [0]);
298 throw new Exception ("Invalid line/column format.");
299 } else if (temp.Length == 4) {
300 lineNumber = Int32.Parse (temp [0]);
301 endLineNumber = Int32.Parse (temp [2]);
302 columnNumber = Int32.Parse (temp [1]);
303 endColumnNumber = Int32.Parse (temp [3]);
305 throw new Exception ("Invalid line/column format.");
316 protected virtual string GenerateCommandLineCommands ()
321 protected abstract string GenerateFullPathToTool ();
324 protected virtual string GenerateResponseFileCommands ()
330 protected virtual string GetResponseFileSwitch (string responseFilePath)
332 return String.Format ("@{0}", responseFilePath);
336 protected virtual bool HandleTaskExecutionErrors ()
338 return ExitCode == 0 && !Log.HasLoggedErrors;
341 protected virtual HostObjectInitializationStatus InitializeHostObject ()
343 return HostObjectInitializationStatus.NoActionReturnSuccess;
347 protected virtual void LogToolCommand (string 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 public virtual int Timeout
416 get { return timeout; }
417 set { timeout = value; }
420 protected abstract string ToolName
425 public string ToolPath
427 get { return toolPath; }
428 set { toolPath = value; }