* ToolTask.cs (ExecuteTool): Don't set toolOutput to null.
[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                         // 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                 [MonoTODO]
312                 protected virtual string GenerateCommandLineCommands ()
313                 {
314                         return null;
315                 }
316
317                 protected abstract string GenerateFullPathToTool ();
318
319                 [MonoTODO]
320                 protected virtual string GenerateResponseFileCommands ()
321                 {
322                         return null;
323                 }
324
325                 protected virtual string GetResponseFileSwitch (string responseFilePath)
326                 {
327                         return String.Format ("@{0}", responseFilePath);
328                 }
329
330                 protected virtual bool HandleTaskExecutionErrors ()
331                 {
332                         if (!Log.HasLoggedErrors && exitCode != 0)
333                                 Log.LogError ("Tool exited with code: {0}. Output: {1}", exitCode,
334                                                 toolOutput != null ? toolOutput.ToString () : String.Empty);
335                         toolOutput = null;
336
337                         return ExitCode == 0 && !Log.HasLoggedErrors;
338                 }
339
340                 protected virtual HostObjectInitializationStatus InitializeHostObject ()
341                 {
342                         return HostObjectInitializationStatus.NoActionReturnSuccess;
343                 }
344
345                 [MonoTODO]
346                 protected virtual void LogToolCommand (string message)
347                 {
348                         Log.LogMessage (MessageImportance.Normal, message);
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                 protected virtual bool HasLoggedErrors {
415                         get { return Log.HasLoggedErrors; }
416                 }
417
418                 public virtual int Timeout
419                 {
420                         get { return timeout; }
421                         set { timeout = value; }
422                 }
423
424                 protected abstract string ToolName
425                 {
426                         get;
427                 }
428
429                 public string ToolPath
430                 {
431                         get { return toolPath; }
432                         set { toolPath  = value; }
433                 }
434         }
435 }
436
437 #endif