2 // ToolTask.cs: Base class for command line tool tasks.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
6 // Ankit Jain (jankit@novell.com)
8 // (C) 2005 Marek Sieradzki
9 // Copyright 2009 Novell, Inc (http://www.novell.com)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
33 using System.Diagnostics;
34 using System.Collections;
35 using System.Collections.Specialized;
37 using System.Resources;
39 using System.Text.RegularExpressions;
40 using Microsoft.Build.Framework;
41 using Mono.XBuild.Utilities;
43 using SCS = System.Collections.Specialized;
45 namespace Microsoft.Build.Utilities
47 public abstract class ToolTask : Task
49 SCS.ProcessStringDictionary environmentOverride;
52 string toolPath, toolExe;
53 Encoding responseFileEncoding;
54 MessageImportance standardErrorLoggingImportance;
55 MessageImportance standardOutputLoggingImportance;
56 StringBuilder toolOutput;
57 bool typeLoadException;
62 this.standardErrorLoggingImportance = MessageImportance.High;
63 this.standardOutputLoggingImportance = MessageImportance.Normal;
66 protected ToolTask (ResourceManager taskResources)
67 : this (taskResources, null)
71 protected ToolTask (ResourceManager taskResources,
72 string helpKeywordPrefix)
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 ();
83 protected virtual bool CallHostObjectToExecute ()
88 public override bool Execute ()
90 if (SkipTaskExecution ())
93 exitCode = ExecuteTool (GenerateFullPathToTool (), GenerateResponseFileCommands (),
94 GenerateCommandLineCommands ());
96 // HandleTaskExecutionErrors is called only if exitCode != 0
97 return exitCode == 0 || HandleTaskExecutionErrors ();
101 protected virtual string GetWorkingDirectory ()
106 protected virtual int ExecuteTool (string pathToTool,
107 string responseFileCommands,
108 string commandLineCommands)
111 if (pathToTool == null)
112 throw new ArgumentNullException ("pathToTool");
114 if (!File.Exists (pathToTool)) {
115 Log.LogError ("Tool not found at {0}", pathToTool);
119 string output, error, responseFileName;
120 StreamWriter outwr, errwr;
122 outwr = errwr = null;
123 responseFileName = output = error = null;
124 toolOutput = new StringBuilder ();
127 string arguments = commandLineCommands;
128 if (!String.IsNullOrEmpty (responseFileCommands)) {
129 responseFileName = Path.GetTempFileName ();
130 File.WriteAllText (responseFileName, responseFileCommands);
131 arguments = arguments + " " + GetResponseFileSwitch (responseFileName);
134 LogToolCommand (String.Format ("Tool {0} execution started with arguments: {1} {2}",
135 pathToTool, commandLineCommands, responseFileCommands));
137 output = Path.GetTempFileName ();
138 error = Path.GetTempFileName ();
139 outwr = new StreamWriter (output);
140 errwr = new StreamWriter (error);
142 ProcessStartInfo pinfo = new ProcessStartInfo (pathToTool, arguments);
143 pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory;
145 pinfo.UseShellExecute = false;
146 pinfo.RedirectStandardOutput = true;
147 pinfo.RedirectStandardError = true;
150 ProcessWrapper pw = ProcessService.StartProcess (pinfo, outwr, errwr, null, environmentOverride);
151 pw.WaitForOutput (timeout == Int32.MaxValue ? -1 : timeout);
152 exitCode = pw.ExitCode;
156 } catch (System.ComponentModel.Win32Exception e) {
157 Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message);
161 ProcessOutputFile (output, StandardOutputLoggingImportance);
162 ProcessOutputFile (error, StandardErrorLoggingImportance);
164 Log.LogMessage (MessageImportance.Low, "Tool {0} execution finished.", pathToTool);
167 DeleteTempFile (responseFileName);
173 DeleteTempFile (output);
174 DeleteTempFile (error);
178 void ProcessOutputFile (string filename, MessageImportance importance)
180 using (StreamReader sr = File.OpenText (filename)) {
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);
189 if (reg.Match (output_str).Success)
191 "Error: A referenced assembly may be built with an incompatible " +
192 "CLR version. See the compilation output for more details.");
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.");
199 Log.LogError (output_str);
202 toolOutput.AppendLine (line);
203 LogEventsFromTextOutput (line, importance);
208 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
210 singleLine = singleLine.Trim ();
211 if (singleLine.Length == 0)
214 if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
215 singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
216 typeLoadException = true;
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"))
226 Match match = CscErrorRegex.Match (singleLine);
227 if (!match.Success) {
228 Log.LogMessage (importance, singleLine);
232 string filename = match.Result ("${file}") ?? "";
233 string line = match.Result ("${line}");
234 int lineNumber = !string.IsNullOrEmpty (line) ? Int32.Parse (line) : 0;
236 string col = match.Result ("${column}");
237 int columnNumber = 0;
238 if (!string.IsNullOrEmpty (col))
239 columnNumber = col == "255+" ? -1 : Int32.Parse (col);
241 string category = match.Result ("${level}");
242 string code = match.Result ("${number}");
243 string text = match.Result ("${message}");
245 if (String.Compare (category, "warning", StringComparison.OrdinalIgnoreCase) == 0) {
246 Log.LogWarning (null, code, null, filename, lineNumber, columnNumber, -1,
248 } else if (String.Compare (category, "error", StringComparison.OrdinalIgnoreCase) == 0) {
249 Log.LogError (null, code, null, filename, lineNumber, columnNumber, -1,
252 Log.LogMessage (importance, singleLine);
256 protected virtual string GenerateCommandLineCommands ()
261 protected abstract string GenerateFullPathToTool ();
263 protected virtual string GenerateResponseFileCommands ()
268 protected virtual string GetResponseFileSwitch (string responseFilePath)
270 return String.Format ("@{0}", responseFilePath);
273 protected virtual bool HandleTaskExecutionErrors ()
275 if (!Log.HasLoggedErrors && exitCode != 0)
276 Log.LogError ("Tool exited with code: {0}. Output: {1}", exitCode,
277 toolOutput != null ? toolOutput.ToString () : String.Empty);
280 return ExitCode == 0 && !Log.HasLoggedErrors;
283 protected virtual HostObjectInitializationStatus InitializeHostObject ()
285 return HostObjectInitializationStatus.NoActionReturnSuccess;
288 protected virtual void LogToolCommand (string message)
290 Log.LogMessage (MessageImportance.Normal, message);
294 protected virtual void LogPathToTool (string toolName,
299 protected virtual bool SkipTaskExecution()
301 return !ValidateParameters ();
304 protected virtual bool ValidateParameters()
309 protected void DeleteTempFile (string fileName)
311 if (String.IsNullOrEmpty (fileName))
315 File.Delete (fileName);
316 } catch (IOException ioe) {
317 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", ioe.Message);
318 } catch (UnauthorizedAccessException uae) {
319 Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", uae.Message);
323 protected virtual StringDictionary EnvironmentOverride
325 get { return environmentOverride; }
329 public int ExitCode {
330 get { return exitCode; }
333 protected virtual Encoding ResponseFileEncoding
335 get { return responseFileEncoding; }
338 protected virtual Encoding StandardErrorEncoding
340 get { return Console.Error.Encoding; }
343 protected virtual MessageImportance StandardErrorLoggingImportance {
344 get { return standardErrorLoggingImportance; }
347 protected virtual Encoding StandardOutputEncoding
349 get { return Console.Out.Encoding; }
352 protected virtual MessageImportance StandardOutputLoggingImportance {
353 get { return standardOutputLoggingImportance; }
356 protected virtual bool HasLoggedErrors {
357 get { return Log.HasLoggedErrors; }
360 public virtual int Timeout
362 get { return timeout; }
363 set { timeout = value; }
366 public virtual string ToolExe
375 if (!String.IsNullOrEmpty (value))
380 protected abstract string ToolName
385 public string ToolPath
387 get { return toolPath; }
389 if (!String.IsNullOrEmpty (value))
394 // Snatched from our codedom code, with some changes to make it compatible with csc
395 // (the line+column group is optional is csc)
396 static Regex errorRegex;
397 static Regex CscErrorRegex {
399 if (errorRegex == null)
400 errorRegex = new Regex (@"^(\s*(?<file>[^\(]+)(\((?<line>\d*)(,(?<column>\d*[\+]*))?\))?:\s+)*(?<level>\w+)\s+(?<number>.*\d):\s*(?<message>.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture);