* ToolTask.cs (ExecuteTool): Use LogToolCommand, instead of
[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                                 LogToolCommand (String.Format ("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                                         toolOutput.AppendLine (line);
214                                         LogEventsFromTextOutput (line, importance);
215                                 }
216                         }
217                 }
218
219                 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
220                 {
221                         singleLine = singleLine.Trim ();
222                         if (singleLine.Length == 0)
223                                 return;
224
225                         if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
226                             singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
227                                 typeLoadException = true;
228                         }
229
230                         // When IncludeDebugInformation is true, prevents the debug symbols stats from braeking this.
231                         if (singleLine.StartsWith ("WROTE SYMFILE") ||
232                                 singleLine.StartsWith ("OffsetTable") ||
233                                 singleLine.StartsWith ("Compilation succeeded") ||
234                                 singleLine.StartsWith ("Compilation failed"))
235                                 return;
236
237                         string filename, origin, category, code, subcategory, text;
238                         int lineNumber, columnNumber, endLineNumber, endColumnNumber;
239                 
240                         Match m = regex.Match (singleLine);
241                         origin = m.Groups [regex.GroupNumberFromName ("ORIGIN")].Value;
242                         category = m.Groups [regex.GroupNumberFromName ("CATEGORY")].Value;
243                         code = m.Groups [regex.GroupNumberFromName ("CODE")].Value;
244                         subcategory = m.Groups [regex.GroupNumberFromName ("SUBCATEGORY")].Value;
245                         text = m.Groups [regex.GroupNumberFromName ("TEXT")].Value;
246                         
247                         ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber);
248
249                         if (category == "warning") {
250                                 Log.LogWarning (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
251                                         endColumnNumber, text, null);
252                         } else if (category == "error") {
253                                 Log.LogError (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
254                                         endColumnNumber, text, null);
255                         } else {
256                                 Log.LogMessage (singleLine);
257                         }
258                 }
259                 
260                 private void ParseOrigin (string origin, out string filename,
261                                      out int lineNumber, out int columnNumber,
262                                      out int endLineNumber, out int endColumnNumber)
263                 {
264                         int lParen;
265                         string[] temp;
266                         string[] left, right;
267                         
268                         if (origin.IndexOf ('(') != -1 ) {
269                                 lParen = origin.IndexOf ('(');
270                                 filename = origin.Substring (0, lParen);
271                                 temp = origin.Substring (lParen + 1, origin.Length - lParen - 2).Split (',');
272                                 if (temp.Length == 1) {
273                                         left = temp [0].Split ('-');
274                                         if (left.Length == 1) {
275                                                 lineNumber = Int32.Parse (left [0]);
276                                                 columnNumber = 0;
277                                                 endLineNumber = 0;
278                                                 endColumnNumber = 0;
279                                         } else if (left.Length == 2) {
280                                                 lineNumber = Int32.Parse (left [0]);
281                                                 columnNumber = 0;
282                                                 endLineNumber = Int32.Parse (left [1]);
283                                                 endColumnNumber = 0;
284                                         } else
285                                                 throw new Exception ("Invalid line/column format.");
286                                 } else if (temp.Length == 2) {
287                                         right = temp [1].Split ('-');
288                                         lineNumber = Int32.Parse (temp [0]);
289                                         endLineNumber = 0;
290                                         if (right.Length == 1) {
291                                                 columnNumber = Int32.Parse (right [0]);
292                                                 endColumnNumber = 0;
293                                         } else if (right.Length == 2) {
294                                                 columnNumber = Int32.Parse (right [0]);
295                                                 endColumnNumber = Int32.Parse (right [0]);
296                                         } else
297                                                 throw new Exception ("Invalid line/column format.");
298                                 } else if (temp.Length == 4) {
299                                         lineNumber = Int32.Parse (temp [0]);
300                                         endLineNumber = Int32.Parse (temp [2]);
301                                         columnNumber = Int32.Parse (temp [1]);
302                                         endColumnNumber = Int32.Parse (temp [3]);
303                                 } else
304                                         throw new Exception ("Invalid line/column format.");
305                         } else {
306                                 filename = origin;
307                                 lineNumber = 0;
308                                 columnNumber = 0;
309                                 endLineNumber = 0;
310                                 endColumnNumber = 0;
311                         }
312                 }
313
314                 [MonoTODO]
315                 protected virtual string GenerateCommandLineCommands ()
316                 {
317                         return null;
318                 }
319
320                 protected abstract string GenerateFullPathToTool ();
321
322                 [MonoTODO]
323                 protected virtual string GenerateResponseFileCommands ()
324                 {
325                         return null;
326                 }
327
328                 [MonoTODO]
329                 protected virtual string GetResponseFileSwitch (string responseFilePath)
330                 {
331                         return String.Format ("@{0}", responseFilePath);
332                 }
333
334                 [MonoTODO]
335                 protected virtual bool HandleTaskExecutionErrors ()
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