2010-02-08 Atsushi Enomoto <atsushi@ximian.com>
[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 (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                 [MonoTODO]
344                 protected virtual void LogToolCommand (string message)
345                 {
346                         Log.LogMessage (MessageImportance.Normal, message);
347                 }
348                 
349                 [MonoTODO]
350                 protected virtual void LogPathToTool (string toolName,
351                                                       string pathToTool)
352                 {
353                 }
354
355                 protected virtual bool SkipTaskExecution()
356                 {
357                         return !ValidateParameters ();
358                 }
359
360                 protected virtual bool ValidateParameters()
361                 {
362                         return true;
363                 }
364
365                 protected void DeleteTempFile (string fileName)
366                 {
367                         if (String.IsNullOrEmpty (fileName))
368                                 return;
369
370                         try {
371                                 File.Delete (fileName);
372                         } catch (IOException ioe) {
373                                 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", ioe.Message);
374                         } catch (UnauthorizedAccessException uae) {
375                                 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", uae.Message);
376                         }
377                 }
378
379                 protected virtual StringDictionary EnvironmentOverride
380                 {
381                         get { return environmentOverride; }
382                 }
383                 
384                 [Output]
385                 public int ExitCode {
386                         get { return exitCode; }
387                 }
388
389                 protected virtual Encoding ResponseFileEncoding
390                 {
391                         get { return responseFileEncoding; }
392                 }
393
394                 protected virtual Encoding StandardErrorEncoding
395                 {
396                         get { return Console.Error.Encoding; }
397                 }
398
399                 protected virtual MessageImportance StandardErrorLoggingImportance {
400                         get { return standardErrorLoggingImportance; }
401                 }
402
403                 protected virtual Encoding StandardOutputEncoding
404                 {
405                         get { return Console.Out.Encoding; }
406                 }
407
408                 protected virtual MessageImportance StandardOutputLoggingImportance {
409                         get { return standardOutputLoggingImportance; }
410                 }
411
412                 protected virtual bool HasLoggedErrors {
413                         get { return Log.HasLoggedErrors; }
414                 }
415
416                 public virtual int Timeout
417                 {
418                         get { return timeout; }
419                         set { timeout = value; }
420                 }
421
422                 public virtual string ToolExe
423                 {
424                         get {
425                                 if (toolExe == null)
426                                         return ToolName;
427                                 else
428                                         return toolExe;
429                         }
430                         set {
431                                 if (!String.IsNullOrEmpty (value))
432                                         toolExe = value;
433                         }
434                 }
435
436                 protected abstract string ToolName
437                 {
438                         get;
439                 }
440
441                 public string ToolPath
442                 {
443                         get { return toolPath; }
444                         set {
445                                 if (!String.IsNullOrEmpty (value))
446                                         toolPath  = value;
447                         }
448                 }
449         }
450 }
451
452 #endif