2 // ToolTask.cs: Base class for command line tool tasks.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
31 using System.Diagnostics;
32 using System.Collections;
33 using System.Collections.Specialized;
35 using System.Resources;
37 using System.Text.RegularExpressions;
38 using Microsoft.Build.Framework;
39 using Mono.XBuild.Utilities;
41 namespace Microsoft.Build.Utilities
43 public abstract class ToolTask : Task
45 StringDictionary environmentOverride;
50 Encoding responseFileEncoding;
51 MessageImportance standardErrorLoggingImportance;
52 MessageImportance standardOutputLoggingImportance;
59 this.standardErrorLoggingImportance = MessageImportance.High;
60 this.standardOutputLoggingImportance = MessageImportance.Normal;
63 protected ToolTask (ResourceManager taskResources)
64 : this (taskResources, null)
68 protected ToolTask (ResourceManager taskResources,
69 string helpKeywordPrefix)
71 this.TaskResources = taskResources;
72 this.HelpKeywordPrefix = helpKeywordPrefix;
73 this.toolPath = MonoLocationHelper.GetBinDir ();
74 this.responseFileEncoding = Encoding.UTF8;
81 + @"(((?<ORIGIN>(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)"
83 + "(?<SUBCATEGORY>(()|([^:]*? )))"
84 + "(?<CATEGORY>(error|warning)) "
87 RegexOptions.IgnoreCase);
91 protected virtual bool CallHostObjectToExecute ()
97 // FIXME: it should write responseFileCommands to temporary response file
98 protected virtual int ExecuteTool (string pathToTool,
99 string responseFileCommands,
100 string commandLineCommands)
102 return RealExecute (pathToTool, responseFileCommands, commandLineCommands) ? 0 : -1;
105 public override bool Execute ()
107 if (SkipTaskExecution ())
110 int result = ExecuteTool (GenerateFullPathToTool (), GenerateResponseFileCommands (),
111 GenerateCommandLineCommands ());
117 protected virtual string GetWorkingDirectory ()
122 private bool RealExecute (string pathToTool,
123 string responseFileCommands,
124 string commandLineCommands)
127 if (pathToTool == null)
128 throw new ArgumentNullException ("pathToTool");
130 if (!File.Exists (pathToTool)) {
131 Log.LogError ("Unable to find tool {0} at '{1}'", ToolName, pathToTool);
135 string responseFileName = Path.GetTempFileName ();
136 File.WriteAllText (responseFileName, responseFileCommands);
138 string arguments = String.Concat (commandLineCommands, " ", GetResponseFileSwitch (responseFileName));
140 Log.LogMessage (MessageImportance.Normal, String.Format ("Tool {0} execution started with arguments: {1} {2}",
141 pathToTool, commandLineCommands, responseFileCommands));
143 string output = Path.GetTempFileName ();
144 string error = Path.GetTempFileName ();
145 StreamWriter outwr = new StreamWriter (output);
146 StreamWriter errwr = new StreamWriter (error);
148 ProcessStartInfo pinfo = new ProcessStartInfo (pathToTool, arguments);
149 pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory;
151 pinfo.UseShellExecute = false;
152 pinfo.RedirectStandardOutput = true;
153 pinfo.RedirectStandardError = true;
156 ProcessWrapper pw = ProcessService.StartProcess (pinfo, outwr, errwr, null, environmentOverride);
158 exitCode = pw.ExitCode;
162 } catch (System.ComponentModel.Win32Exception e) {
163 Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message);
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)) {
172 while ((line = sr.ReadLine ()) != null) {
173 if (typeLoadException) {
174 compilerOutput.Append (sr.ReadToEnd ());
178 compilerOutput.AppendLine (line);
181 if (line.Length == 0)
184 if (line.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
185 line.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
186 typeLoadException = true;
188 LogEventsFromTextOutput (line, MessageImportance.Low);
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.");
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);
202 if (!Log.HasLoggedErrors && exitCode != 0)
203 Log.LogError ("Compiler crashed: " + compilerOutput.ToString ());
205 Log.LogMessage (MessageImportance.Low, String.Format ("Tool {0} execution finished.", pathToTool));
207 return !Log.HasLoggedErrors;
212 protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
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"))
221 string filename, origin, category, code, subcategory, text;
222 int lineNumber, columnNumber, endLineNumber, endColumnNumber;
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;
231 ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber);
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);
242 private void ParseOrigin (string origin, out string filename,
243 out int lineNumber, out int columnNumber,
244 out int endLineNumber, out int endColumnNumber)
248 string[] left, right;
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]);
261 } else if (left.Length == 2) {
262 lineNumber = Int32.Parse (left [0]);
264 endLineNumber = Int32.Parse (left [1]);
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]);
272 if (right.Length == 1) {
273 columnNumber = Int32.Parse (right [0]);
275 } else if (right.Length == 2) {
276 columnNumber = Int32.Parse (right [0]);
277 endColumnNumber = Int32.Parse (right [0]);
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]);
286 throw new Exception ("Invalid line/column format.");
297 protected virtual string GenerateCommandLineCommands ()
302 protected abstract string GenerateFullPathToTool ();
305 protected virtual string GenerateResponseFileCommands ()
311 protected virtual string GetResponseFileSwitch (string responseFilePath)
313 return String.Format ("@{0}", responseFilePath);
317 protected virtual bool HandleTaskExecutionErrors ()
322 protected virtual HostObjectInitializationStatus InitializeHostObject ()
324 return HostObjectInitializationStatus.NoActionReturnSuccess;
328 protected virtual void LogToolCommand (string message)
333 protected virtual void LogPathToTool (string toolName,
338 protected virtual bool SkipTaskExecution()
343 protected virtual bool ValidateParameters()
348 protected virtual StringDictionary EnvironmentOverride
350 get { return environmentOverride; }
355 public int ExitCode {
356 get { return exitCode; }
359 protected virtual Encoding ResponseFileEncoding
361 get { return responseFileEncoding; }
364 protected virtual Encoding StandardErrorEncoding
366 get { return Console.Error.Encoding; }
369 protected virtual MessageImportance StandardErrorLoggingImportance {
370 get { return standardErrorLoggingImportance; }
373 protected virtual Encoding StandardOutputEncoding
375 get { return Console.Out.Encoding; }
378 protected virtual MessageImportance StandardOutputLoggingImportance {
379 get { return standardOutputLoggingImportance; }
382 public virtual int Timeout
384 get { return timeout; }
385 set { timeout = value; }
388 protected abstract string ToolName
393 public string ToolPath
395 get { return toolPath; }
396 set { toolPath = value; }