* Vbc.cs (LogEventsFromTextOutput):
[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 using SCS = System.Collections.Specialized;
44
45 namespace Microsoft.Build.Utilities
46 {
47         public abstract class ToolTask : Task
48         {
49                 SCS.ProcessStringDictionary     environmentOverride;
50                 int                     exitCode;
51                 int                     timeout;
52                 string                  toolPath, toolExe;
53                 Encoding                responseFileEncoding;
54                 MessageImportance       standardErrorLoggingImportance;
55                 MessageImportance       standardOutputLoggingImportance;
56                 StringBuilder toolOutput;
57                 bool typeLoadException;
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 SCS.ProcessStringDictionary ();
80                 }
81
82                 [MonoTODO]
83                 protected virtual bool CallHostObjectToExecute ()
84                 {
85                         return true;
86                 }
87
88                 public override bool Execute ()
89                 {
90                         if (SkipTaskExecution ())
91                                 return true;
92
93                         exitCode = ExecuteTool (GenerateFullPathToTool (), GenerateResponseFileCommands (),
94                                 GenerateCommandLineCommands ());
95
96                         // HandleTaskExecutionErrors is called only if exitCode != 0
97                         return exitCode == 0 || HandleTaskExecutionErrors ();
98                 }
99                 
100                 [MonoTODO]
101                 protected virtual string GetWorkingDirectory ()
102                 {
103                         return null;
104                 }
105                 
106                 protected virtual int ExecuteTool (string pathToTool,
107                                                    string responseFileCommands,
108                                                    string commandLineCommands)
109
110                 {
111                         if (pathToTool == null)
112                                 throw new ArgumentNullException ("pathToTool");
113
114                         if (!File.Exists (pathToTool)) {
115                                 Log.LogError ("Tool not found at {0}", pathToTool);
116                                 return -1;
117                         }
118
119                         string output, error, responseFileName;
120                         StreamWriter outwr, errwr;
121
122                         outwr = errwr = null;
123                         responseFileName = output = error = null;
124                         toolOutput = new StringBuilder ();
125
126                         try {
127                                 string arguments = commandLineCommands;
128                                 if (!String.IsNullOrEmpty (responseFileCommands)) {
129                                         responseFileName = Path.GetTempFileName ();
130                                         File.WriteAllText (responseFileName, responseFileCommands);
131                                         arguments = arguments + " " + GetResponseFileSwitch (responseFileName);
132                                 }
133
134                                 LogToolCommand (String.Format ("Tool {0} execution started with arguments: {1} {2}",
135                                                 pathToTool, commandLineCommands, responseFileCommands));
136
137                                 output = Path.GetTempFileName ();
138                                 error = Path.GetTempFileName ();
139                                 outwr = new StreamWriter (output);
140                                 errwr = new StreamWriter (error);
141
142                                 ProcessStartInfo pinfo = new ProcessStartInfo (pathToTool, arguments);
143                                 pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory;
144
145                                 pinfo.UseShellExecute = false;
146                                 pinfo.RedirectStandardOutput = true;
147                                 pinfo.RedirectStandardError = true;
148
149                                 try {
150                                         ProcessWrapper pw = ProcessService.StartProcess (pinfo, outwr, errwr, null, environmentOverride);
151                                         pw.WaitForOutput (timeout == Int32.MaxValue ? -1 : timeout);
152                                         exitCode = pw.ExitCode;
153                                         outwr.Close();
154                                         errwr.Close();
155                                         pw.Dispose ();
156                                 } catch (System.ComponentModel.Win32Exception e) {
157                                         Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message);
158                                         return -1;
159                                 }
160
161                                 ProcessOutputFile (output, StandardOutputLoggingImportance);
162                                 ProcessOutputFile (error, StandardErrorLoggingImportance);
163
164                                 Log.LogMessage (MessageImportance.Low, "Tool {0} execution finished.", pathToTool);
165                                 return exitCode;
166                         } finally {
167                                 DeleteTempFile (responseFileName);
168                                 if (outwr != null)
169                                         outwr.Dispose ();
170                                 if (errwr != null)
171                                         errwr.Dispose ();
172
173                                 DeleteTempFile (output);
174                                 DeleteTempFile (error);
175                         }
176                 }
177
178                 void ProcessOutputFile (string filename, MessageImportance importance)
179                 {
180                         using (StreamReader sr = File.OpenText (filename)) {
181                                 string line;
182                                 while ((line = sr.ReadLine ()) != null) {
183                                         if (typeLoadException) {
184                                                 toolOutput.Append (sr.ReadToEnd ());
185                                                 string output_str = toolOutput.ToString ();
186                                                 Regex reg  = new Regex (@".*WARNING.*used in (mscorlib|System),.*",
187                                                                 RegexOptions.Multiline);
188
189                                                 if (reg.Match (output_str).Success)
190                                                         Log.LogError (
191                                                                 "Error: A referenced assembly may be built with an incompatible " + 
192                                                                 "CLR version. See the compilation output for more details.");
193                                                 else
194                                                         Log.LogError (
195                                                                 "Error: A dependency of a referenced assembly may be missing, or " +
196                                                                 "you may be referencing an assembly created with a newer CLR " +
197                                                                 "version. See the compilation output for more details.");
198
199                                                 Log.LogError (output_str);
200                                         }
201
202                                         toolOutput.AppendLine (line);
203                                         LogEventsFromTextOutput (line, importance);
204                                 }
205                         }
206                 }
207
208                 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
209                 {
210                         singleLine = singleLine.Trim ();
211                         if (singleLine.Length == 0)
212                                 return;
213
214                         if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
215                             singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
216                                 typeLoadException = true;
217                         }
218
219                         // When IncludeDebugInformation is true, prevents the debug symbols stats from braeking this.
220                         if (singleLine.StartsWith ("WROTE SYMFILE") ||
221                                 singleLine.StartsWith ("OffsetTable") ||
222                                 singleLine.StartsWith ("Compilation succeeded") ||
223                                 singleLine.StartsWith ("Compilation failed"))
224                                 return;
225
226                         Match match = CscErrorRegex.Match (singleLine);
227                         if (!match.Success) {
228                                 Log.LogMessage (importance, singleLine);
229                                 return;
230                         }
231
232                         string filename = match.Result ("${file}") ?? "";
233                         string line = match.Result ("${line}");
234                         int lineNumber = !string.IsNullOrEmpty (line) ? Int32.Parse (line) : 0;
235
236                         string col = match.Result ("${column}");
237                         int columnNumber = 0;
238                         if (!string.IsNullOrEmpty (col))
239                                 columnNumber = col == "255+" ? -1 : Int32.Parse (col);
240
241                         string category = match.Result ("${level}");
242                         string code = match.Result ("${number}");
243                         string text = match.Result ("${message}");
244
245                         if (String.Compare (category, "warning", StringComparison.OrdinalIgnoreCase) == 0) {
246                                 Log.LogWarning (null, code, null, filename, lineNumber, columnNumber, -1,
247                                         -1, text, null);
248                         } else if (String.Compare (category, "error", StringComparison.OrdinalIgnoreCase) == 0) {
249                                 Log.LogError (null, code, null, filename, lineNumber, columnNumber, -1,
250                                         -1, text, null);
251                         } else {
252                                 Log.LogMessage (importance, singleLine);
253                         }
254                 }
255
256                 protected virtual string GenerateCommandLineCommands ()
257                 {
258                         return null;
259                 }
260
261                 protected abstract string GenerateFullPathToTool ();
262
263                 protected virtual string GenerateResponseFileCommands ()
264                 {
265                         return null;
266                 }
267
268                 protected virtual string GetResponseFileSwitch (string responseFilePath)
269                 {
270                         return String.Format ("@{0}", responseFilePath);
271                 }
272
273                 protected virtual bool HandleTaskExecutionErrors ()
274                 {
275                         if (!Log.HasLoggedErrors && exitCode != 0)
276                                 Log.LogError ("Tool exited with code: {0}. Output: {1}", exitCode,
277                                                 toolOutput != null ? toolOutput.ToString () : String.Empty);
278                         toolOutput = null;
279
280                         return ExitCode == 0 && !Log.HasLoggedErrors;
281                 }
282
283                 protected virtual HostObjectInitializationStatus InitializeHostObject ()
284                 {
285                         return HostObjectInitializationStatus.NoActionReturnSuccess;
286                 }
287
288                 protected virtual void LogToolCommand (string message)
289                 {
290                         Log.LogMessage (MessageImportance.Normal, message);
291                 }
292                 
293                 [MonoTODO]
294                 protected virtual void LogPathToTool (string toolName,
295                                                       string pathToTool)
296                 {
297                 }
298
299                 protected virtual bool SkipTaskExecution()
300                 {
301                         return !ValidateParameters ();
302                 }
303
304                 protected virtual bool ValidateParameters()
305                 {
306                         return true;
307                 }
308
309                 protected void DeleteTempFile (string fileName)
310                 {
311                         if (String.IsNullOrEmpty (fileName))
312                                 return;
313
314                         try {
315                                 File.Delete (fileName);
316                         } catch (IOException ioe) {
317                                 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", ioe.Message);
318                         } catch (UnauthorizedAccessException uae) {
319                                 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", uae.Message);
320                         }
321                 }
322
323                 protected virtual StringDictionary EnvironmentOverride
324                 {
325                         get { return environmentOverride; }
326                 }
327                 
328                 [Output]
329                 public int ExitCode {
330                         get { return exitCode; }
331                 }
332
333                 protected virtual Encoding ResponseFileEncoding
334                 {
335                         get { return responseFileEncoding; }
336                 }
337
338                 protected virtual Encoding StandardErrorEncoding
339                 {
340                         get { return Console.Error.Encoding; }
341                 }
342
343                 protected virtual MessageImportance StandardErrorLoggingImportance {
344                         get { return standardErrorLoggingImportance; }
345                 }
346
347                 protected virtual Encoding StandardOutputEncoding
348                 {
349                         get { return Console.Out.Encoding; }
350                 }
351
352                 protected virtual MessageImportance StandardOutputLoggingImportance {
353                         get { return standardOutputLoggingImportance; }
354                 }
355
356                 protected virtual bool HasLoggedErrors {
357                         get { return Log.HasLoggedErrors; }
358                 }
359
360                 public virtual int Timeout
361                 {
362                         get { return timeout; }
363                         set { timeout = value; }
364                 }
365
366                 public virtual string ToolExe
367                 {
368                         get {
369                                 if (toolExe == null)
370                                         return ToolName;
371                                 else
372                                         return toolExe;
373                         }
374                         set {
375                                 if (!String.IsNullOrEmpty (value))
376                                         toolExe = value;
377                         }
378                 }
379
380                 protected abstract string ToolName
381                 {
382                         get;
383                 }
384
385                 public string ToolPath
386                 {
387                         get { return toolPath; }
388                         set {
389                                 if (!String.IsNullOrEmpty (value))
390                                         toolPath  = value;
391                         }
392                 }
393
394                 // Snatched from our codedom code, with some changes to make it compatible with csc
395                 // (the line+column group is optional is csc)
396                 static Regex errorRegex;
397                 static Regex CscErrorRegex {
398                         get {
399                                 if (errorRegex == null)
400                                         errorRegex = new Regex (@"^(\s*(?<file>[^\(]+)(\((?<line>\d*)(,(?<column>\d*[\+]*))?\))?:\s+)*(?<level>\w+)\s+(?<number>.*\d):\s*(?<message>.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
401                                 return errorRegex;
402                         }
403                 }
404         }
405 }
406
407 #endif