* ToolTask.cs (ExecuteTool): Use the virtual method
[mono.git] / mcs / class / Microsoft.Build.Utilities / Microsoft.Build.Utilities / ToolTask.cs
1 //
2 // ToolTask.cs: Base class for command line tool tasks. 
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 //   Ankit Jain (jankit@novell.com)
7 //
8 // (C) 2005 Marek Sieradzki
9 // Copyright 2009 Novell, Inc (http://www.novell.com)
10 //
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:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
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.
29
30 #if NET_2_0
31
32 using System;
33 using System.Diagnostics;
34 using System.Collections;
35 using System.Collections.Specialized;
36 using System.IO;
37 using System.Resources;
38 using System.Text;
39 using System.Text.RegularExpressions;
40 using Microsoft.Build.Framework;
41 using Mono.XBuild.Utilities;
42
43 namespace Microsoft.Build.Utilities
44 {
45         public abstract class ToolTask : Task
46         {
47                 StringDictionary        environmentOverride;
48                 int                     exitCode;
49                 int                     timeout;
50                 string                  toolPath, toolExe;
51                 Encoding                responseFileEncoding;
52                 MessageImportance       standardErrorLoggingImportance;
53                 MessageImportance       standardOutputLoggingImportance;
54                 StringBuilder toolOutput;
55                 bool typeLoadException;
56
57                 static Regex            regex;
58                 
59                 protected ToolTask ()
60                         : this (null, null)
61                 {
62                         this.standardErrorLoggingImportance = MessageImportance.High;
63                         this.standardOutputLoggingImportance = MessageImportance.Normal;
64                 }
65
66                 protected ToolTask (ResourceManager taskResources)
67                         : this (taskResources, null)
68                 {
69                 }
70
71                 protected ToolTask (ResourceManager taskResources,
72                                    string helpKeywordPrefix)
73                 {
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 ();
80                 }
81
82                 static ToolTask ()
83                 {
84                         regex = new Regex (
85                                 @"^\s*"
86                                 + @"(((?<ORIGIN>(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)"
87                                 + "|())"
88                                 + "(?<SUBCATEGORY>(()|([^:]*? )))"
89                                 + "(?<CATEGORY>(error|warning)) "
90                                 + "(?<CODE>[^:]*):"
91                                 + "(?<TEXT>.*)$",
92                                 RegexOptions.IgnoreCase);
93                 }
94
95                 [MonoTODO]
96                 protected virtual bool CallHostObjectToExecute ()
97                 {
98                         return true;
99                 }
100
101                 public override bool Execute ()
102                 {
103                         if (SkipTaskExecution ())
104                                 return true;
105
106                         exitCode = ExecuteTool (GenerateFullPathToTool (), GenerateResponseFileCommands (),
107                                 GenerateCommandLineCommands ());
108
109                         // HandleTaskExecutionErrors is called only if exitCode != 0
110                         return exitCode == 0 || HandleTaskExecutionErrors ();
111                 }
112                 
113                 [MonoTODO]
114                 protected virtual string GetWorkingDirectory ()
115                 {
116                         return null;
117                 }
118                 
119                 protected virtual int ExecuteTool (string pathToTool,
120                                                    string responseFileCommands,
121                                                    string commandLineCommands)
122
123                 {
124                         if (pathToTool == null)
125                                 throw new ArgumentNullException ("pathToTool");
126
127                         string output, error, responseFileName;
128                         StreamWriter outwr, errwr;
129
130                         outwr = errwr = null;
131                         responseFileName = output = error = null;
132                         toolOutput = new StringBuilder ();
133
134                         try {
135                                 string arguments = commandLineCommands;
136                                 if (!String.IsNullOrEmpty (responseFileCommands)) {
137                                         responseFileName = Path.GetTempFileName ();
138                                         File.WriteAllText (responseFileName, responseFileCommands);
139                                         arguments = arguments + " " + GetResponseFileSwitch (responseFileName);
140                                 }
141
142                                 LogToolCommand (String.Format ("Tool {0} execution started with arguments: {1} {2}",
143                                                 pathToTool, commandLineCommands, responseFileCommands));
144
145                                 output = Path.GetTempFileName ();
146                                 error = Path.GetTempFileName ();
147                                 outwr = new StreamWriter (output);
148                                 errwr = new StreamWriter (error);
149
150                                 ProcessStartInfo pinfo = new ProcessStartInfo (pathToTool, arguments);
151                                 pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory;
152
153                                 pinfo.UseShellExecute = false;
154                                 pinfo.RedirectStandardOutput = true;
155                                 pinfo.RedirectStandardError = true;
156
157                                 try {
158                                         ProcessWrapper pw = ProcessService.StartProcess (pinfo, outwr, errwr, null, environmentOverride);
159                                         pw.WaitForOutput (timeout == Int32.MaxValue ? -1 : timeout);
160                                         exitCode = pw.ExitCode;
161                                         outwr.Close();
162                                         errwr.Close();
163                                         pw.Dispose ();
164                                 } catch (System.ComponentModel.Win32Exception e) {
165                                         Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message);
166                                         return -1;
167                                 }
168
169                                 ProcessOutputFile (output, StandardOutputLoggingImportance);
170                                 ProcessOutputFile (error, StandardErrorLoggingImportance);
171
172                                 Log.LogMessage (MessageImportance.Low, "Tool {0} execution finished.", pathToTool);
173                                 return exitCode;
174                         } finally {
175                                 DeleteTempFile (responseFileName);
176                                 if (outwr != null)
177                                         outwr.Dispose ();
178                                 if (errwr != null)
179                                         errwr.Dispose ();
180
181                                 DeleteTempFile (output);
182                                 DeleteTempFile (error);
183                         }
184                 }
185
186                 void ProcessOutputFile (string filename, MessageImportance importance)
187                 {
188                         using (StreamReader sr = File.OpenText (filename)) {
189                                 string line;
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);
196
197                                                 if (reg.Match (output_str).Success)
198                                                         Log.LogError (
199                                                                 "Error: A referenced assembly may be built with an incompatible " + 
200                                                                 "CLR version. See the compilation output for more details.");
201                                                 else
202                                                         Log.LogError (
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.");
206
207                                                 Log.LogError (output_str);
208                                         }
209
210                                         toolOutput.AppendLine (line);
211                                         LogEventsFromTextOutput (line, importance);
212                                 }
213                         }
214                 }
215
216                 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
217                 {
218                         singleLine = singleLine.Trim ();
219                         if (singleLine.Length == 0)
220                                 return;
221
222                         if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
223                             singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
224                                 typeLoadException = true;
225                         }
226
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"))
232                                 return;
233
234                         string filename, origin, category, code, subcategory, text;
235                         int lineNumber, columnNumber, endLineNumber, endColumnNumber;
236                 
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;
243                         
244                         ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber);
245
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);
252                         } else {
253                                 Log.LogMessage (importance, singleLine);
254                         }
255                 }
256                 
257                 private void ParseOrigin (string origin, out string filename,
258                                      out int lineNumber, out int columnNumber,
259                                      out int endLineNumber, out int endColumnNumber)
260                 {
261                         int lParen;
262                         string[] temp;
263                         string[] left, right;
264                         
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]);
273                                                 columnNumber = 0;
274                                                 endLineNumber = 0;
275                                                 endColumnNumber = 0;
276                                         } else if (left.Length == 2) {
277                                                 lineNumber = Int32.Parse (left [0]);
278                                                 columnNumber = 0;
279                                                 endLineNumber = Int32.Parse (left [1]);
280                                                 endColumnNumber = 0;
281                                         } else
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]);
286                                         endLineNumber = 0;
287                                         if (right.Length == 1) {
288                                                 columnNumber = Int32.Parse (right [0]);
289                                                 endColumnNumber = 0;
290                                         } else if (right.Length == 2) {
291                                                 columnNumber = Int32.Parse (right [0]);
292                                                 endColumnNumber = Int32.Parse (right [0]);
293                                         } else
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]);
300                                 } else
301                                         throw new Exception ("Invalid line/column format.");
302                         } else {
303                                 filename = origin;
304                                 lineNumber = 0;
305                                 columnNumber = 0;
306                                 endLineNumber = 0;
307                                 endColumnNumber = 0;
308                         }
309                 }
310
311                 protected virtual string GenerateCommandLineCommands ()
312                 {
313                         return null;
314                 }
315
316                 protected abstract string GenerateFullPathToTool ();
317
318                 protected virtual string GenerateResponseFileCommands ()
319                 {
320                         return null;
321                 }
322
323                 protected virtual string GetResponseFileSwitch (string responseFilePath)
324                 {
325                         return String.Format ("@{0}", responseFilePath);
326                 }
327
328                 protected virtual bool HandleTaskExecutionErrors ()
329                 {
330                         if (!Log.HasLoggedErrors && exitCode != 0)
331                                 Log.LogError ("Tool exited with code: {0}. Output: {1}", exitCode,
332                                                 toolOutput != null ? toolOutput.ToString () : String.Empty);
333                         toolOutput = null;
334
335                         return ExitCode == 0 && !Log.HasLoggedErrors;
336                 }
337
338                 protected virtual HostObjectInitializationStatus InitializeHostObject ()
339                 {
340                         return HostObjectInitializationStatus.NoActionReturnSuccess;
341                 }
342
343                 protected virtual void LogToolCommand (string message)
344                 {
345                         Log.LogMessage (MessageImportance.Normal, message);
346                 }
347                 
348                 [MonoTODO]
349                 protected virtual void LogPathToTool (string toolName,
350                                                       string pathToTool)
351                 {
352                 }
353
354                 protected virtual bool SkipTaskExecution()
355                 {
356                         return !ValidateParameters ();
357                 }
358
359                 protected virtual bool ValidateParameters()
360                 {
361                         return true;
362                 }
363
364                 protected void DeleteTempFile (string fileName)
365                 {
366                         if (String.IsNullOrEmpty (fileName))
367                                 return;
368
369                         try {
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);
375                         }
376                 }
377
378                 protected virtual StringDictionary EnvironmentOverride
379                 {
380                         get { return environmentOverride; }
381                 }
382                 
383                 [Output]
384                 public int ExitCode {
385                         get { return exitCode; }
386                 }
387
388                 protected virtual Encoding ResponseFileEncoding
389                 {
390                         get { return responseFileEncoding; }
391                 }
392
393                 protected virtual Encoding StandardErrorEncoding
394                 {
395                         get { return Console.Error.Encoding; }
396                 }
397
398                 protected virtual MessageImportance StandardErrorLoggingImportance {
399                         get { return standardErrorLoggingImportance; }
400                 }
401
402                 protected virtual Encoding StandardOutputEncoding
403                 {
404                         get { return Console.Out.Encoding; }
405                 }
406
407                 protected virtual MessageImportance StandardOutputLoggingImportance {
408                         get { return standardOutputLoggingImportance; }
409                 }
410
411                 protected virtual bool HasLoggedErrors {
412                         get { return Log.HasLoggedErrors; }
413                 }
414
415                 public virtual int Timeout
416                 {
417                         get { return timeout; }
418                         set { timeout = value; }
419                 }
420
421                 public virtual string ToolExe
422                 {
423                         get {
424                                 if (toolExe == null)
425                                         return ToolName;
426                                 else
427                                         return toolExe;
428                         }
429                         set {
430                                 if (!String.IsNullOrEmpty (value))
431                                         toolExe = value;
432                         }
433                 }
434
435                 protected abstract string ToolName
436                 {
437                         get;
438                 }
439
440                 public string ToolPath
441                 {
442                         get { return toolPath; }
443                         set {
444                                 if (!String.IsNullOrEmpty (value))
445                                         toolPath  = value;
446                         }
447                 }
448         }
449 }
450
451 #endif