* ToolTask.cs (LogEventsFromTextOutput): Log message even if its not
[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;
51                 Process                 process;
52                 Encoding                responseFileEncoding;
53                 MessageImportance       standardErrorLoggingImportance;
54                 MessageImportance       standardOutputLoggingImportance;
55                 StringBuilder toolOutput;
56                 bool typeLoadException;
57
58                 static Regex            regex;
59                 
60                 protected ToolTask ()
61                         : this (null, null)
62                 {
63                         this.standardErrorLoggingImportance = MessageImportance.High;
64                         this.standardOutputLoggingImportance = MessageImportance.Normal;
65                 }
66
67                 protected ToolTask (ResourceManager taskResources)
68                         : this (taskResources, null)
69                 {
70                 }
71
72                 protected ToolTask (ResourceManager taskResources,
73                                    string helpKeywordPrefix)
74                 {
75                         this.TaskResources = taskResources;
76                         this.HelpKeywordPrefix = helpKeywordPrefix;
77                         this.toolPath = MonoLocationHelper.GetBinDir ();
78                         this.responseFileEncoding = Encoding.UTF8;
79                         this.timeout = Int32.MaxValue;
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                         return HandleTaskExecutionErrors ();
110                 }
111                 
112                 [MonoTODO]
113                 protected virtual string GetWorkingDirectory ()
114                 {
115                         return null;
116                 }
117                 
118                 protected virtual int ExecuteTool (string pathToTool,
119                                                    string responseFileCommands,
120                                                    string commandLineCommands)
121
122                 {
123                         if (pathToTool == null)
124                                 throw new ArgumentNullException ("pathToTool");
125
126                         string output, error, responseFileName;
127                         StreamWriter outwr, errwr;
128
129                         outwr = errwr = null;
130                         responseFileName = output = error = null;
131                         toolOutput = new StringBuilder ();
132
133                         try {
134                                 string arguments = commandLineCommands;
135                                 if (!String.IsNullOrEmpty (responseFileCommands)) {
136                                         responseFileName = Path.GetTempFileName ();
137                                         File.WriteAllText (responseFileName, responseFileCommands);
138                                         arguments = arguments + " " + GetResponseFileSwitch (responseFileName);
139                                 }
140
141                                 Log.LogMessage (MessageImportance.Normal, "Tool {0} execution started with arguments: {1} {2}",
142                                                 pathToTool, commandLineCommands, responseFileCommands);
143
144                                 output = Path.GetTempFileName ();
145                                 error = Path.GetTempFileName ();
146                                 outwr = new StreamWriter (output);
147                                 errwr = new StreamWriter (error);
148
149                                 ProcessStartInfo pinfo = new ProcessStartInfo (pathToTool, arguments);
150                                 pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory;
151
152                                 pinfo.UseShellExecute = false;
153                                 pinfo.RedirectStandardOutput = true;
154                                 pinfo.RedirectStandardError = true;
155
156                                 try {
157                                         ProcessWrapper pw = ProcessService.StartProcess (pinfo, outwr, errwr, null, environmentOverride);
158                                         pw.WaitForOutput (timeout == Int32.MaxValue ? -1 : timeout);
159                                         exitCode = pw.ExitCode;
160                                         outwr.Close();
161                                         errwr.Close();
162                                         pw.Dispose ();
163                                 } catch (System.ComponentModel.Win32Exception e) {
164                                         Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message);
165                                         return -1;
166                                 }
167
168                                 ProcessOutputFile (output, standardOutputLoggingImportance);
169                                 ProcessOutputFile (error, standardErrorLoggingImportance);
170
171                                 if (!Log.HasLoggedErrors && exitCode != 0)
172                                         Log.LogError ("Tool crashed: " + toolOutput.ToString ());
173
174                                 Log.LogMessage (MessageImportance.Low, "Tool {0} execution finished.", pathToTool);
175                                 return exitCode;
176                         } finally {
177                                 DeleteTempFile (responseFileName);
178                                 if (outwr != null)
179                                         outwr.Dispose ();
180                                 if (errwr != null)
181                                         errwr.Dispose ();
182
183                                 DeleteTempFile (output);
184                                 DeleteTempFile (error);
185                                 toolOutput = null;
186                         }
187                 }
188
189                 void ProcessOutputFile (string filename, MessageImportance importance)
190                 {
191                         using (StreamReader sr = File.OpenText (filename)) {
192                                 string line;
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);
199
200                                                 if (reg.Match (output_str).Success)
201                                                         Log.LogError (
202                                                                 "Error: A referenced assembly may be built with an incompatible " + 
203                                                                 "CLR version. See the compilation output for more details.");
204                                                 else
205                                                         Log.LogError (
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.");
209
210                                                 Log.LogError (output_str);
211                                         }
212
213                                         LogEventsFromTextOutput (line, importance);
214                                 }
215                         }
216                 }
217
218                 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
219                 {
220                         toolOutput.AppendLine (singleLine);
221
222                         singleLine = singleLine.Trim ();
223                         if (singleLine.Length == 0)
224                                 return;
225
226                         if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
227                             singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
228                                 typeLoadException = true;
229                         }
230
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"))
236                                 return;
237
238                         string filename, origin, category, code, subcategory, text;
239                         int lineNumber, columnNumber, endLineNumber, endColumnNumber;
240                 
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;
247                         
248                         ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber);
249
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);
256                         } else {
257                                 Log.LogMessage (singleLine);
258                         }
259                 }
260                 
261                 private void ParseOrigin (string origin, out string filename,
262                                      out int lineNumber, out int columnNumber,
263                                      out int endLineNumber, out int endColumnNumber)
264                 {
265                         int lParen;
266                         string[] temp;
267                         string[] left, right;
268                         
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]);
277                                                 columnNumber = 0;
278                                                 endLineNumber = 0;
279                                                 endColumnNumber = 0;
280                                         } else if (left.Length == 2) {
281                                                 lineNumber = Int32.Parse (left [0]);
282                                                 columnNumber = 0;
283                                                 endLineNumber = Int32.Parse (left [1]);
284                                                 endColumnNumber = 0;
285                                         } else
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]);
290                                         endLineNumber = 0;
291                                         if (right.Length == 1) {
292                                                 columnNumber = Int32.Parse (right [0]);
293                                                 endColumnNumber = 0;
294                                         } else if (right.Length == 2) {
295                                                 columnNumber = Int32.Parse (right [0]);
296                                                 endColumnNumber = Int32.Parse (right [0]);
297                                         } else
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]);
304                                 } else
305                                         throw new Exception ("Invalid line/column format.");
306                         } else {
307                                 filename = origin;
308                                 lineNumber = 0;
309                                 columnNumber = 0;
310                                 endLineNumber = 0;
311                                 endColumnNumber = 0;
312                         }
313                 }
314
315                 [MonoTODO]
316                 protected virtual string GenerateCommandLineCommands ()
317                 {
318                         return null;
319                 }
320
321                 protected abstract string GenerateFullPathToTool ();
322
323                 [MonoTODO]
324                 protected virtual string GenerateResponseFileCommands ()
325                 {
326                         return null;
327                 }
328
329                 [MonoTODO]
330                 protected virtual string GetResponseFileSwitch (string responseFilePath)
331                 {
332                         return String.Format ("@{0}", responseFilePath);
333                 }
334
335                 [MonoTODO]
336                 protected virtual bool HandleTaskExecutionErrors ()
337                 {
338                         return ExitCode == 0 && !Log.HasLoggedErrors;
339                 }
340
341                 protected virtual HostObjectInitializationStatus InitializeHostObject ()
342                 {
343                         return HostObjectInitializationStatus.NoActionReturnSuccess;
344                 }
345
346                 [MonoTODO]
347                 protected virtual void LogToolCommand (string message)
348                 {
349                 }
350                 
351                 [MonoTODO]
352                 protected virtual void LogPathToTool (string toolName,
353                                                       string pathToTool)
354                 {
355                 }
356
357                 protected virtual bool SkipTaskExecution()
358                 {
359                         return false;
360                 }
361
362                 protected virtual bool ValidateParameters()
363                 {
364                         return true;
365                 }
366
367                 protected void DeleteTempFile (string fileName)
368                 {
369                         if (String.IsNullOrEmpty (fileName))
370                                 return;
371
372                         try {
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);
378                         }
379                 }
380
381                 protected virtual StringDictionary EnvironmentOverride
382                 {
383                         get { return environmentOverride; }
384                 }
385                 
386                 [Output]
387                 public int ExitCode {
388                         get { return exitCode; }
389                 }
390
391                 protected virtual Encoding ResponseFileEncoding
392                 {
393                         get { return responseFileEncoding; }
394                 }
395
396                 protected virtual Encoding StandardErrorEncoding
397                 {
398                         get { return Console.Error.Encoding; }
399                 }
400
401                 protected virtual MessageImportance StandardErrorLoggingImportance {
402                         get { return standardErrorLoggingImportance; }
403                 }
404
405                 protected virtual Encoding StandardOutputEncoding
406                 {
407                         get { return Console.Out.Encoding; }
408                 }
409
410                 protected virtual MessageImportance StandardOutputLoggingImportance {
411                         get { return standardOutputLoggingImportance; }
412                 }
413
414                 public virtual int Timeout
415                 {
416                         get { return timeout; }
417                         set { timeout = value; }
418                 }
419
420                 protected abstract string ToolName
421                 {
422                         get;
423                 }
424
425                 public string ToolPath
426                 {
427                         get { return toolPath; }
428                         set { toolPath  = value; }
429                 }
430         }
431 }
432
433 #endif