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