New tests, updates
[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 //
7 // (C) 2005 Marek Sieradzki
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
28 #if NET_2_0
29
30 using System;
31 using System.Diagnostics;
32 using System.Collections;
33 using System.Collections.Specialized;
34 using System.IO;
35 using System.Resources;
36 using System.Text;
37 using System.Text.RegularExpressions;
38 using Microsoft.Build.Framework;
39 using Mono.XBuild.Utilities;
40
41 namespace Microsoft.Build.Utilities
42 {
43         public abstract class ToolTask : Task
44         {
45                 StringDictionary        environmentOverride;
46                 int                     exitCode;
47                 int                     timeout;
48                 string                  toolPath;
49                 Process                 process;
50                 Encoding                responseFileEncoding;
51                 MessageImportance       standardErrorLoggingImportance;
52                 MessageImportance       standardOutputLoggingImportance;
53                 
54                 static Regex            regex;
55                 
56                 protected ToolTask ()
57                         : this (null, null)
58                 {
59                         this.standardErrorLoggingImportance = MessageImportance.High;
60                         this.standardOutputLoggingImportance = MessageImportance.Normal;
61                 }
62
63                 protected ToolTask (ResourceManager taskResources)
64                         : this (taskResources, null)
65                 {
66                 }
67
68                 protected ToolTask (ResourceManager taskResources,
69                                    string helpKeywordPrefix)
70                 {
71                         this.TaskResources = taskResources;
72                         this.HelpKeywordPrefix = helpKeywordPrefix;
73                         this.toolPath = MonoLocationHelper.GetBinDir ();
74                         this.responseFileEncoding = Encoding.UTF8;
75                 }
76
77                 static ToolTask ()
78                 {
79                         regex = new Regex (
80                                 @"^\s*"
81                                 + @"(((?<ORIGIN>(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)"
82                                 + "|())"
83                                 + "(?<SUBCATEGORY>(()|([^:]*? )))"
84                                 + "(?<CATEGORY>(error|warning)) "
85                                 + "(?<CODE>[^:]*):"
86                                 + "(?<TEXT>.*)$",
87                                 RegexOptions.IgnoreCase);
88                 }
89
90                 [MonoTODO]
91                 protected virtual bool CallHostObjectToExecute ()
92                 {
93                         return true;
94                 }
95
96                 [MonoTODO]
97                 // FIXME: it should write responseFileCommands to temporary response file
98                 protected virtual int ExecuteTool (string pathToTool,
99                                                    string responseFileCommands,
100                                                    string commandLineCommands)
101                 {
102                         return RealExecute (pathToTool, responseFileCommands, commandLineCommands) ? 0 : -1;
103                 }
104                 
105                 public override bool Execute ()
106                 {
107                         if (SkipTaskExecution ())
108                                 return true;
109
110                         int result = ExecuteTool (GenerateFullPathToTool (), GenerateResponseFileCommands (),
111                                 GenerateCommandLineCommands ());
112                         
113                         return result == 0;
114                 }
115                 
116                 [MonoTODO]
117                 protected virtual string GetWorkingDirectory ()
118                 {
119                         return null;
120                 }
121                 
122                 private bool RealExecute (string pathToTool,
123                                            string responseFileCommands,
124                                            string commandLineCommands)
125
126                 {
127                         if (pathToTool == null)
128                                 throw new ArgumentNullException ("pathToTool");
129
130                         if (!File.Exists (pathToTool)) {
131                                 Log.LogError ("Unable to find tool {0} at '{1}'", ToolName, pathToTool);
132                                 return false;
133                         }
134
135                         string responseFileName = Path.GetTempFileName ();
136                         File.WriteAllText (responseFileName, responseFileCommands);
137
138                         string arguments = String.Concat (commandLineCommands, " ", GetResponseFileSwitch (responseFileName));
139
140                         Log.LogMessage (MessageImportance.Normal, String.Format ("Tool {0} execution started with arguments: {1} {2}",
141                                 pathToTool, commandLineCommands, responseFileCommands));
142
143                         string output = Path.GetTempFileName ();
144                         string error = Path.GetTempFileName ();
145                         StreamWriter outwr = new StreamWriter (output);
146                         StreamWriter errwr = new StreamWriter (error);
147
148                         ProcessStartInfo pinfo = new ProcessStartInfo (pathToTool, arguments);
149                         pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory;
150
151                         pinfo.UseShellExecute = false;
152                         pinfo.RedirectStandardOutput = true;
153                         pinfo.RedirectStandardError = true;
154
155                         try {
156                                 ProcessWrapper pw = ProcessService.StartProcess (pinfo, outwr, errwr, null, environmentOverride);
157                                 pw.WaitForOutput();
158                                 exitCode = pw.ExitCode;
159                                 outwr.Close();
160                                 errwr.Close();
161                                 pw.Dispose ();
162                         } catch (System.ComponentModel.Win32Exception e) {
163                                 Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message);
164                                 return false;
165                         }
166
167                         bool typeLoadException = false;
168                         StringBuilder compilerOutput = new StringBuilder ();
169                         foreach (string s in new string[] { output, error }) {
170                                 using (StreamReader sr = File.OpenText (s)) {
171                                         string line;
172                                         while ((line = sr.ReadLine ()) != null) {
173                                                 if (typeLoadException) {
174                                                         compilerOutput.Append (sr.ReadToEnd ());
175                                                         break;
176                                                 }
177
178                                                 compilerOutput.AppendLine (line);
179
180                                                 line = line.Trim ();
181                                                 if (line.Length == 0)
182                                                         continue;
183
184                                                 if (line.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
185                                                     line.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
186                                                         typeLoadException = true;
187                                                 }
188                                                 LogEventsFromTextOutput (line, MessageImportance.Low);
189                                         }
190                                 }
191                                 if (typeLoadException) {
192                                         string output_str = compilerOutput.ToString ();
193                                         Regex reg  = new Regex (@".*WARNING.*used in (mscorlib|System),.*", RegexOptions.Multiline);
194                                         if (reg.Match (output_str).Success)
195                                                 Log.LogError ("Error: A referenced assembly may be built with an incompatible CLR version. See the compilation output for more details.");
196                                         else
197                                                 Log.LogError ("Error: A dependency of a referenced assembly may be missing, or you may be referencing an assembly created with a newer CLR version. See the compilation output for more details.");
198                                         Log.LogError (output_str);
199                                 }
200                         }
201
202                         if (!Log.HasLoggedErrors && exitCode != 0)
203                                 Log.LogError ("Compiler crashed: " + compilerOutput.ToString ());
204                         
205                         Log.LogMessage (MessageImportance.Low, String.Format ("Tool {0} execution finished.", pathToTool));
206                         
207                         return !Log.HasLoggedErrors;
208                 }
209                 
210                 
211                 [MonoTODO]
212                 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
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                         string filename, origin, category, code, subcategory, text;
222                         int lineNumber, columnNumber, endLineNumber, endColumnNumber;
223                 
224                         Match m = regex.Match (singleLine);
225                         origin = m.Groups [regex.GroupNumberFromName ("ORIGIN")].Value;
226                         category = m.Groups [regex.GroupNumberFromName ("CATEGORY")].Value;
227                         code = m.Groups [regex.GroupNumberFromName ("CODE")].Value;
228                         subcategory = m.Groups [regex.GroupNumberFromName ("SUBCATEGORY")].Value;
229                         text = m.Groups [regex.GroupNumberFromName ("TEXT")].Value;
230                         
231                         ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber);
232
233                         if (category == "warning") {
234                                 Log.LogWarning (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
235                                         endColumnNumber, text, null);
236                         } else if (category == "error") {
237                                 Log.LogError (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
238                                         endColumnNumber, text, null);
239                         }
240                 }
241                 
242                 private void ParseOrigin (string origin, out string filename,
243                                      out int lineNumber, out int columnNumber,
244                                      out int endLineNumber, out int endColumnNumber)
245                 {
246                         int lParen;
247                         string[] temp;
248                         string[] left, right;
249                         
250                         if (origin.IndexOf ('(') != -1 ) {
251                                 lParen = origin.IndexOf ('(');
252                                 filename = origin.Substring (0, lParen);
253                                 temp = origin.Substring (lParen + 1, origin.Length - lParen - 2).Split (',');
254                                 if (temp.Length == 1) {
255                                         left = temp [0].Split ('-');
256                                         if (left.Length == 1) {
257                                                 lineNumber = Int32.Parse (left [0]);
258                                                 columnNumber = 0;
259                                                 endLineNumber = 0;
260                                                 endColumnNumber = 0;
261                                         } else if (left.Length == 2) {
262                                                 lineNumber = Int32.Parse (left [0]);
263                                                 columnNumber = 0;
264                                                 endLineNumber = Int32.Parse (left [1]);
265                                                 endColumnNumber = 0;
266                                         } else
267                                                 throw new Exception ("Invalid line/column format.");
268                                 } else if (temp.Length == 2) {
269                                         right = temp [1].Split ('-');
270                                         lineNumber = Int32.Parse (temp [0]);
271                                         endLineNumber = 0;
272                                         if (right.Length == 1) {
273                                                 columnNumber = Int32.Parse (right [0]);
274                                                 endColumnNumber = 0;
275                                         } else if (right.Length == 2) {
276                                                 columnNumber = Int32.Parse (right [0]);
277                                                 endColumnNumber = Int32.Parse (right [0]);
278                                         } else
279                                                 throw new Exception ("Invalid line/column format.");
280                                 } else if (temp.Length == 4) {
281                                         lineNumber = Int32.Parse (temp [0]);
282                                         endLineNumber = Int32.Parse (temp [2]);
283                                         columnNumber = Int32.Parse (temp [1]);
284                                         endColumnNumber = Int32.Parse (temp [3]);
285                                 } else
286                                         throw new Exception ("Invalid line/column format.");
287                         } else {
288                                 filename = origin;
289                                 lineNumber = 0;
290                                 columnNumber = 0;
291                                 endLineNumber = 0;
292                                 endColumnNumber = 0;
293                         }
294                 }
295
296                 [MonoTODO]
297                 protected virtual string GenerateCommandLineCommands ()
298                 {
299                         return null;
300                 }
301
302                 protected abstract string GenerateFullPathToTool ();
303
304                 [MonoTODO]
305                 protected virtual string GenerateResponseFileCommands ()
306                 {
307                         return null;
308                 }
309
310                 [MonoTODO]
311                 protected virtual string GetResponseFileSwitch (string responseFilePath)
312                 {
313                         return String.Format ("@{0}", responseFilePath);
314                 }
315
316                 [MonoTODO]
317                 protected virtual bool HandleTaskExecutionErrors ()
318                 {
319                         return true;
320                 }
321
322                 protected virtual HostObjectInitializationStatus InitializeHostObject ()
323                 {
324                         return HostObjectInitializationStatus.NoActionReturnSuccess;
325                 }
326
327                 [MonoTODO]
328                 protected virtual void LogToolCommand (string message)
329                 {
330                 }
331                 
332                 [MonoTODO]
333                 protected virtual void LogPathToTool (string toolName,
334                                                       string pathToTool)
335                 {
336                 }
337
338                 protected virtual bool SkipTaskExecution()
339                 {
340                         return false;
341                 }
342
343                 protected virtual bool ValidateParameters()
344                 {
345                         return true;
346                 }
347
348                 protected virtual StringDictionary EnvironmentOverride
349                 {
350                         get { return environmentOverride; }
351                 }
352                 
353                 [MonoTODO]
354                 [Output]
355                 public int ExitCode {
356                         get { return exitCode; }
357                 }
358
359                 protected virtual Encoding ResponseFileEncoding
360                 {
361                         get { return responseFileEncoding; }
362                 }
363
364                 protected virtual Encoding StandardErrorEncoding
365                 {
366                         get { return Console.Error.Encoding; }
367                 }
368
369                 protected virtual MessageImportance StandardErrorLoggingImportance {
370                         get { return standardErrorLoggingImportance; }
371                 }
372
373                 protected virtual Encoding StandardOutputEncoding
374                 {
375                         get { return Console.Out.Encoding; }
376                 }
377
378                 protected virtual MessageImportance StandardOutputLoggingImportance {
379                         get { return standardOutputLoggingImportance; }
380                 }
381
382                 public virtual int Timeout
383                 {
384                         get { return timeout; }
385                         set { timeout = value; }
386                 }
387
388                 protected abstract string ToolName
389                 {
390                         get;
391                 }
392
393                 public string ToolPath
394                 {
395                         get { return toolPath; }
396                         set { toolPath  = value; }
397                 }
398         }
399 }
400
401 #endif