2009-03-26 Sebastien Pouliot <sebastien@ximian.com>
[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                         foreach (string s in new string[] { output, error }) {
168                                 using (StreamReader sr = File.OpenText (s)) {
169                                         string line;
170                                         while ((line = sr.ReadLine ()) != null) {
171                                                 LogEventsFromTextOutput (line, MessageImportance.Low);
172                                         }
173                                 }
174                         }
175                         
176                         Log.LogMessage (MessageImportance.Low, String.Format ("Tool {0} execution finished.", pathToTool));
177                         
178                         return !Log.HasLoggedErrors;
179                 }
180                 
181                 
182                 [MonoTODO]
183                 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
184                 {
185                         if (String.IsNullOrEmpty (singleLine))
186                                 return;
187
188                         string filename, origin, category, code, subcategory, text;
189                         int lineNumber, columnNumber, endLineNumber, endColumnNumber;
190                 
191                         Match m = regex.Match (singleLine);
192                         origin = m.Groups [regex.GroupNumberFromName ("ORIGIN")].Value;
193                         category = m.Groups [regex.GroupNumberFromName ("CATEGORY")].Value;
194                         code = m.Groups [regex.GroupNumberFromName ("CODE")].Value;
195                         subcategory = m.Groups [regex.GroupNumberFromName ("SUBCATEGORY")].Value;
196                         text = m.Groups [regex.GroupNumberFromName ("TEXT")].Value;
197                         
198                         ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber);
199                         
200                         if (category == "warning") {
201                                 Log.LogWarning (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
202                                         endColumnNumber, text, null);
203                         } else if (category == "error") {
204                                 Log.LogError (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber,
205                                         endColumnNumber, text, null);
206                         }
207                 }
208                 
209                 private void ParseOrigin (string origin, out string filename,
210                                      out int lineNumber, out int columnNumber,
211                                      out int endLineNumber, out int endColumnNumber)
212                 {
213                         int lParen;
214                         string[] temp;
215                         string[] left, right;
216                         
217                         if (origin.IndexOf ('(') != -1 ) {
218                                 lParen = origin.IndexOf ('(');
219                                 filename = origin.Substring (0, lParen);
220                                 temp = origin.Substring (lParen + 1, origin.Length - lParen - 2).Split (',');
221                                 if (temp.Length == 1) {
222                                         left = temp [0].Split ('-');
223                                         if (left.Length == 1) {
224                                                 lineNumber = Int32.Parse (left [0]);
225                                                 columnNumber = 0;
226                                                 endLineNumber = 0;
227                                                 endColumnNumber = 0;
228                                         } else if (left.Length == 2) {
229                                                 lineNumber = Int32.Parse (left [0]);
230                                                 columnNumber = 0;
231                                                 endLineNumber = Int32.Parse (left [1]);
232                                                 endColumnNumber = 0;
233                                         } else
234                                                 throw new Exception ("Invalid line/column format.");
235                                 } else if (temp.Length == 2) {
236                                         right = temp [1].Split ('-');
237                                         lineNumber = Int32.Parse (temp [0]);
238                                         endLineNumber = 0;
239                                         if (right.Length == 1) {
240                                                 columnNumber = Int32.Parse (right [0]);
241                                                 endColumnNumber = 0;
242                                         } else if (right.Length == 2) {
243                                                 columnNumber = Int32.Parse (right [0]);
244                                                 endColumnNumber = Int32.Parse (right [0]);
245                                         } else
246                                                 throw new Exception ("Invalid line/column format.");
247                                 } else if (temp.Length == 4) {
248                                         lineNumber = Int32.Parse (temp [0]);
249                                         endLineNumber = Int32.Parse (temp [2]);
250                                         columnNumber = Int32.Parse (temp [1]);
251                                         endColumnNumber = Int32.Parse (temp [3]);
252                                 } else
253                                         throw new Exception ("Invalid line/column format.");
254                         } else {
255                                 filename = origin;
256                                 lineNumber = 0;
257                                 columnNumber = 0;
258                                 endLineNumber = 0;
259                                 endColumnNumber = 0;
260                         }
261                 }
262
263                 [MonoTODO]
264                 protected virtual string GenerateCommandLineCommands ()
265                 {
266                         return null;
267                 }
268
269                 protected abstract string GenerateFullPathToTool ();
270
271                 [MonoTODO]
272                 protected virtual string GenerateResponseFileCommands ()
273                 {
274                         return null;
275                 }
276
277                 [MonoTODO]
278                 protected virtual string GetResponseFileSwitch (string responseFilePath)
279                 {
280                         return String.Format ("@{0}", responseFilePath);
281                 }
282
283                 [MonoTODO]
284                 protected virtual bool HandleTaskExecutionErrors ()
285                 {
286                         return true;
287                 }
288
289                 protected virtual HostObjectInitializationStatus InitializeHostObject ()
290                 {
291                         return HostObjectInitializationStatus.NoActionReturnSuccess;
292                 }
293
294                 [MonoTODO]
295                 protected virtual void LogToolCommand (string message)
296                 {
297                 }
298                 
299                 [MonoTODO]
300                 protected virtual void LogPathToTool (string toolName,
301                                                       string pathToTool)
302                 {
303                 }
304
305                 protected virtual bool SkipTaskExecution()
306                 {
307                         return false;
308                 }
309
310                 protected virtual bool ValidateParameters()
311                 {
312                         return true;
313                 }
314
315                 protected virtual StringDictionary EnvironmentOverride
316                 {
317                         get { return environmentOverride; }
318                 }
319                 
320                 [MonoTODO]
321                 [Output]
322                 public int ExitCode {
323                         get { return exitCode; }
324                 }
325
326                 protected virtual Encoding ResponseFileEncoding
327                 {
328                         get { return responseFileEncoding; }
329                 }
330
331                 protected virtual Encoding StandardErrorEncoding
332                 {
333                         get { return Console.Error.Encoding; }
334                 }
335
336                 protected virtual MessageImportance StandardErrorLoggingImportance {
337                         get { return standardErrorLoggingImportance; }
338                 }
339
340                 protected virtual Encoding StandardOutputEncoding
341                 {
342                         get { return Console.Out.Encoding; }
343                 }
344
345                 protected virtual MessageImportance StandardOutputLoggingImportance {
346                         get { return standardOutputLoggingImportance; }
347                 }
348
349                 public virtual int Timeout
350                 {
351                         get { return timeout; }
352                         set { timeout = value; }
353                 }
354
355                 protected abstract string ToolName
356                 {
357                         get;
358                 }
359
360                 public string ToolPath
361                 {
362                         get { return toolPath; }
363                         set { toolPath  = value; }
364                 }
365         }
366 }
367
368 #endif