* ToolTask.cs: Use regex to parse output. Regex is from monodevelop.
[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                                 return;
229
230                         string filename = match.Result ("${file}") ?? "";
231                         string line = match.Result ("${line}");
232                         int lineNumber = !string.IsNullOrEmpty (line) ? Int32.Parse (line) : 0;
233
234                         string col = match.Result ("${column}");
235                         int columnNumber = 0;
236                         if (!string.IsNullOrEmpty (col))
237                                 columnNumber = col == "255+" ? -1 : Int32.Parse (col);
238
239                         string category = match.Result ("${level}");
240                         string code = match.Result ("${number}");
241                         string text = match.Result ("${message}");
242
243                         if (String.Compare (category, "warning", StringComparison.OrdinalIgnoreCase) == 0) {
244                                 Log.LogWarning (null, code, null, filename, lineNumber, columnNumber, -1,
245                                         -1, text, null);
246                         } else if (String.Compare (category, "error", StringComparison.OrdinalIgnoreCase) == 0) {
247                                 Log.LogError (null, code, null, filename, lineNumber, columnNumber, -1,
248                                         -1, text, null);
249                         } else {
250                                 Log.LogMessage (importance, singleLine);
251                         }
252                 }
253
254                 protected virtual string GenerateCommandLineCommands ()
255                 {
256                         return null;
257                 }
258
259                 protected abstract string GenerateFullPathToTool ();
260
261                 protected virtual string GenerateResponseFileCommands ()
262                 {
263                         return null;
264                 }
265
266                 protected virtual string GetResponseFileSwitch (string responseFilePath)
267                 {
268                         return String.Format ("@{0}", responseFilePath);
269                 }
270
271                 protected virtual bool HandleTaskExecutionErrors ()
272                 {
273                         if (!Log.HasLoggedErrors && exitCode != 0)
274                                 Log.LogError ("Tool exited with code: {0}. Output: {1}", exitCode,
275                                                 toolOutput != null ? toolOutput.ToString () : String.Empty);
276                         toolOutput = null;
277
278                         return ExitCode == 0 && !Log.HasLoggedErrors;
279                 }
280
281                 protected virtual HostObjectInitializationStatus InitializeHostObject ()
282                 {
283                         return HostObjectInitializationStatus.NoActionReturnSuccess;
284                 }
285
286                 protected virtual void LogToolCommand (string message)
287                 {
288                         Log.LogMessage (MessageImportance.Normal, message);
289                 }
290                 
291                 [MonoTODO]
292                 protected virtual void LogPathToTool (string toolName,
293                                                       string pathToTool)
294                 {
295                 }
296
297                 protected virtual bool SkipTaskExecution()
298                 {
299                         return !ValidateParameters ();
300                 }
301
302                 protected virtual bool ValidateParameters()
303                 {
304                         return true;
305                 }
306
307                 protected void DeleteTempFile (string fileName)
308                 {
309                         if (String.IsNullOrEmpty (fileName))
310                                 return;
311
312                         try {
313                                 File.Delete (fileName);
314                         } catch (IOException ioe) {
315                                 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", ioe.Message);
316                         } catch (UnauthorizedAccessException uae) {
317                                 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", uae.Message);
318                         }
319                 }
320
321                 protected virtual StringDictionary EnvironmentOverride
322                 {
323                         get { return environmentOverride; }
324                 }
325                 
326                 [Output]
327                 public int ExitCode {
328                         get { return exitCode; }
329                 }
330
331                 protected virtual Encoding ResponseFileEncoding
332                 {
333                         get { return responseFileEncoding; }
334                 }
335
336                 protected virtual Encoding StandardErrorEncoding
337                 {
338                         get { return Console.Error.Encoding; }
339                 }
340
341                 protected virtual MessageImportance StandardErrorLoggingImportance {
342                         get { return standardErrorLoggingImportance; }
343                 }
344
345                 protected virtual Encoding StandardOutputEncoding
346                 {
347                         get { return Console.Out.Encoding; }
348                 }
349
350                 protected virtual MessageImportance StandardOutputLoggingImportance {
351                         get { return standardOutputLoggingImportance; }
352                 }
353
354                 protected virtual bool HasLoggedErrors {
355                         get { return Log.HasLoggedErrors; }
356                 }
357
358                 public virtual int Timeout
359                 {
360                         get { return timeout; }
361                         set { timeout = value; }
362                 }
363
364                 public virtual string ToolExe
365                 {
366                         get {
367                                 if (toolExe == null)
368                                         return ToolName;
369                                 else
370                                         return toolExe;
371                         }
372                         set {
373                                 if (!String.IsNullOrEmpty (value))
374                                         toolExe = value;
375                         }
376                 }
377
378                 protected abstract string ToolName
379                 {
380                         get;
381                 }
382
383                 public string ToolPath
384                 {
385                         get { return toolPath; }
386                         set {
387                                 if (!String.IsNullOrEmpty (value))
388                                         toolPath  = value;
389                         }
390                 }
391
392                 // Snatched from our codedom code, with some changes to make it compatible with csc
393                 // (the line+column group is optional is csc)
394                 static Regex errorRegex;
395                 static Regex CscErrorRegex {
396                         get {
397                                 if (errorRegex == null)
398                                         errorRegex = new Regex (@"^(\s*(?<file>[^\(]+)(\((?<line>\d*)(,(?<column>\d*[\+]*))?\))?:\s+)*(?<level>\w+)\s+(?<number>.*\d):\s*(?<message>.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
399                                 return errorRegex;
400                         }
401                 }
402         }
403 }
404
405 #endif