X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FMicrosoft.Build.Utilities%2FMicrosoft.Build.Utilities%2FToolTask.cs;h=4094c122e3b9e9afc86574ece4e1ea98afbd9cb9;hb=9f6fa83d53ec04dde5c07d21ca3a640024e00095;hp=9d4a75f2b96205bb67f9065fb36e98cf52599e90;hpb=496dfbf9ec0fd3143e5dd560a863d916e56a52b8;p=mono.git diff --git a/mcs/class/Microsoft.Build.Utilities/Microsoft.Build.Utilities/ToolTask.cs b/mcs/class/Microsoft.Build.Utilities/Microsoft.Build.Utilities/ToolTask.cs index 9d4a75f2b96..4094c122e3b 100644 --- a/mcs/class/Microsoft.Build.Utilities/Microsoft.Build.Utilities/ToolTask.cs +++ b/mcs/class/Microsoft.Build.Utilities/Microsoft.Build.Utilities/ToolTask.cs @@ -3,8 +3,10 @@ // // Author: // Marek Sieradzki (marek.sieradzki@gmail.com) +// Ankit Jain (jankit@novell.com) // // (C) 2005 Marek Sieradzki +// Copyright 2009 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -29,6 +31,7 @@ using System; using System.Diagnostics; +using System.Collections; using System.Collections.Specialized; using System.IO; using System.Resources; @@ -37,21 +40,21 @@ using System.Text.RegularExpressions; using Microsoft.Build.Framework; using Mono.XBuild.Utilities; +using SCS = System.Collections.Specialized; + namespace Microsoft.Build.Utilities { public abstract class ToolTask : Task { - StringDictionary environmentOverride; int exitCode; int timeout; - string toolPath; - Process process; + string toolPath, toolExe; Encoding responseFileEncoding; MessageImportance standardErrorLoggingImportance; MessageImportance standardOutputLoggingImportance; - - static Regex regex; - + StringBuilder toolOutput; + bool typeLoadException; + protected ToolTask () : this (null, null) { @@ -71,19 +74,7 @@ namespace Microsoft.Build.Utilities this.HelpKeywordPrefix = helpKeywordPrefix; this.toolPath = MonoLocationHelper.GetBinDir (); this.responseFileEncoding = Encoding.UTF8; - } - - static ToolTask () - { - regex = new Regex ( - @"^\s*" - + @"(((?(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)" - + "|())" - + "(?(()|([^:]*? )))" - + "(?(error|warning)) " - + "(?[^:]*):" - + "(?.*)$", - RegexOptions.IgnoreCase); + this.timeout = Int32.MaxValue; } [MonoTODO] @@ -92,36 +83,16 @@ namespace Microsoft.Build.Utilities return true; } - [MonoTODO] - // FIXME: it should write responseFileCommands to temporary response file - protected virtual int ExecuteTool (string pathToTool, - string responseFileCommands, - string commandLineCommands) - { - string arguments; - bool success; - - arguments = String.Concat (commandLineCommands, " ", responseFileCommands); - - success = RealExecute (pathToTool, arguments); - - if (success) - return 0; - else - return -1; - } - public override bool Execute () { - int result; - - result = ExecuteTool (GenerateFullPathToTool (), GenerateResponseFileCommands (), - GenerateCommandLineCommands ()); - - if (result == 0) + if (SkipTaskExecution ()) return true; - else - return false; + + exitCode = ExecuteTool (GenerateFullPathToTool (), GenerateResponseFileCommands (), + GenerateCommandLineCommands ()); + + // HandleTaskExecutionErrors is called only if exitCode != 0 + return exitCode == 0 || HandleTaskExecutionErrors (); } [MonoTODO] @@ -130,122 +101,180 @@ namespace Microsoft.Build.Utilities return null; } - private bool RealExecute (string filename, string arguments) + protected virtual int ExecuteTool (string pathToTool, + string responseFileCommands, + string commandLineCommands) + { - string line; - - if (filename == null) - throw new ArgumentNullException ("filename"); - if (arguments == null) - throw new ArgumentNullException ("arguments"); - - process = new Process (); - process.StartInfo.Arguments = arguments; - process.StartInfo.CreateNoWindow = true; - process.StartInfo.FileName = filename; - process.StartInfo.RedirectStandardError = true; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.UseShellExecute = false; - - Log.LogMessage (MessageImportance.Low, String.Format ("Tool {0} execution started with arguments: {1}", - filename, arguments)); - - process.Start (); - process.WaitForExit (); - - exitCode = process.ExitCode; - - while ((line = process.StandardError.ReadLine ()) != null) { - LogEventsFromTextOutput (line, MessageImportance.Normal); + if (pathToTool == null) + throw new ArgumentNullException ("pathToTool"); + + string responseFileName; + responseFileName = null; + toolOutput = new StringBuilder (); + + try { + string responseFileSwitch = String.Empty; + if (!String.IsNullOrEmpty (responseFileCommands)) { + responseFileName = Path.GetTempFileName (); + File.WriteAllText (responseFileName, responseFileCommands); + responseFileSwitch = GetResponseFileSwitch (responseFileName); + } + + var pinfo = GetProcessStartInfo (pathToTool, commandLineCommands, responseFileSwitch); + LogToolCommand (String.Format ("Tool {0} execution started with arguments: {1} {2}", + pinfo.FileName, commandLineCommands, responseFileCommands)); + + var pendingLineFragmentOutput = new StringBuilder (); + var pendingLineFragmentError = new StringBuilder (); + var environmentOverride = GetAndLogEnvironmentVariables (); + try { + // When StartProcess returns, the process has already .Start()'ed + // If we subscribe to the events after that, then for processes that + // finish executing before we can subscribe, we won't get the output/err + // events at all! + ProcessWrapper pw = ProcessService.StartProcess (pinfo, + (_, msg) => ProcessLine (pendingLineFragmentOutput, msg, StandardOutputLoggingImportance), + (_, msg) => ProcessLine (pendingLineFragmentError, msg, StandardErrorLoggingImportance), + null, + environmentOverride); + + pw.WaitForOutput (timeout == Int32.MaxValue ? Int32.MaxValue : timeout); + + // Process any remaining line + ProcessLine (pendingLineFragmentOutput, StandardOutputLoggingImportance, true); + ProcessLine (pendingLineFragmentError, StandardErrorLoggingImportance, true); + + exitCode = pw.ExitCode; + pw.Dispose (); + } catch (System.ComponentModel.Win32Exception e) { + Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message); + return -1; + } + + if (typeLoadException) + ProcessTypeLoadException (); + + pendingLineFragmentOutput.Length = 0; + pendingLineFragmentError.Length = 0; + + Log.LogMessage (MessageImportance.Low, "Tool {0} execution finished.", pathToTool); + return exitCode; + } finally { + DeleteTempFile (responseFileName); } - - Log.LogMessage (MessageImportance.Low, String.Format ("Tool {0} execution finished.", filename)); - - return !Log.HasLoggedErrors; } - - - // FIXME: use importance - [MonoTODO] - protected virtual void LogEventsFromTextOutput (string singleLine, - MessageImportance importance) + + void ProcessTypeLoadException () { - string filename, origin, category, code, subcategory, text; - int lineNumber, columnNumber, endLineNumber, endColumnNumber; - - Match m = regex.Match (singleLine); - origin = m.Groups [regex.GroupNumberFromName ("ORIGIN")].Value; - category = m.Groups [regex.GroupNumberFromName ("CATEGORY")].Value; - code = m.Groups [regex.GroupNumberFromName ("CODE")].Value; - subcategory = m.Groups [regex.GroupNumberFromName ("SUBCATEGORY")].Value; - text = m.Groups [regex.GroupNumberFromName ("TEXT")].Value; - - ParseOrigin (origin, out filename, out lineNumber, out columnNumber, out endLineNumber, out endColumnNumber); - - if (category == "warning") { - Log.LogWarning (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber, - endColumnNumber, text, null); - } else if (category == "error") { - Log.LogError (subcategory, code, null, filename, lineNumber, columnNumber, endLineNumber, - endColumnNumber, text, null); + string output_str = toolOutput.ToString (); + Regex reg = new Regex (@".*WARNING.*used in (mscorlib|System),.*", + RegexOptions.Multiline); + + if (reg.Match (output_str).Success) + Log.LogError ( + "Error: A referenced assembly may be built with an incompatible " + + "CLR version. See the compilation output for more details."); + else + 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."); + + Log.LogError (output_str); + } + + void ProcessLine (StringBuilder outputBuilder, MessageImportance importance, bool isLastLine) + { + if (outputBuilder.Length == 0) + return; + + if (isLastLine && !outputBuilder.ToString ().EndsWith (Environment.NewLine)) + // last line, but w/o an trailing newline, so add that + outputBuilder.Append (Environment.NewLine); + + ProcessLine (outputBuilder, null, importance); + } + + void ProcessLine (StringBuilder outputBuilder, string line, MessageImportance importance) + { + // Add to any line fragment from previous call + if (line != null) + outputBuilder.Append (line); + + // Don't remove empty lines! + var lines = outputBuilder.ToString ().Split (new string [] {Environment.NewLine}, StringSplitOptions.None); + + // Clear the builder. If any incomplete line is found, + // then it will get added back + outputBuilder.Length = 0; + for (int i = 0; i < lines.Length; i ++) { + string singleLine = lines [i]; + if (i == lines.Length - 1 && !singleLine.EndsWith (Environment.NewLine)) { + // Last line doesn't end in newline, could be part of + // a bigger line. Save for later processing + outputBuilder.Append (singleLine); + continue; + } + + toolOutput.AppendLine (singleLine); + + // in case of typeLoadException, collect all the output + // and then handle in ProcessTypeLoadException + if (!typeLoadException) + LogEventsFromTextOutput (singleLine, importance); } } - - private void ParseOrigin (string origin, out string filename, - out int lineNumber, out int columnNumber, - out int endLineNumber, out int endColumnNumber) - { - int lParen; - string[] temp; - string[] left, right; - - if (origin.IndexOf ('(') != -1 ) { - lParen = origin.IndexOf ('('); - filename = origin.Substring (0, lParen); - temp = origin.Substring (lParen + 1, origin.Length - lParen - 2).Split (','); - if (temp.Length == 1) { - left = temp [0].Split ('-'); - if (left.Length == 1) { - lineNumber = Int32.Parse (left [0]); - columnNumber = 0; - endLineNumber = 0; - endColumnNumber = 0; - } else if (left.Length == 2) { - lineNumber = Int32.Parse (left [0]); - columnNumber = 0; - endLineNumber = Int32.Parse (left [1]); - endColumnNumber = 0; - } else - throw new Exception ("Invalid line/column format."); - } else if (temp.Length == 2) { - right = temp [1].Split ('-'); - lineNumber = Int32.Parse (temp [0]); - endLineNumber = 0; - if (right.Length == 1) { - columnNumber = Int32.Parse (right [0]); - endColumnNumber = 0; - } else if (right.Length == 2) { - columnNumber = Int32.Parse (right [0]); - endColumnNumber = Int32.Parse (right [0]); - } else - throw new Exception ("Invalid line/column format."); - } else if (temp.Length == 4) { - lineNumber = Int32.Parse (temp [0]); - endLineNumber = Int32.Parse (temp [2]); - columnNumber = Int32.Parse (temp [1]); - endColumnNumber = Int32.Parse (temp [3]); - } else - throw new Exception ("Invalid line/column format."); + + protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance) + { + if (singleLine.Length == 0) { + Log.LogMessage (singleLine, importance); + return; + } + + if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") || + singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) { + typeLoadException = true; + } + + // When IncludeDebugInformation is true, prevents the debug symbols stats from braeking this. + if (singleLine.StartsWith ("WROTE SYMFILE") || + singleLine.StartsWith ("OffsetTable") || + singleLine.StartsWith ("Compilation succeeded") || + singleLine.StartsWith ("Compilation failed")) + return; + + Match match = CscErrorRegex.Match (singleLine); + if (!match.Success) { + Log.LogMessage (importance, singleLine); + return; + } + + string filename = match.Result ("${file}") ?? ""; + string line = match.Result ("${line}"); + int lineNumber = !string.IsNullOrEmpty (line) ? Int32.Parse (line) : 0; + + string col = match.Result ("${column}"); + int columnNumber = 0; + if (!string.IsNullOrEmpty (col)) + columnNumber = col.IndexOf ("+") >= 0 ? -1 : Int32.Parse (col); + + string category = match.Result ("${level}"); + string code = match.Result ("${number}"); + string text = match.Result ("${message}"); + + if (String.Compare (category, "warning", StringComparison.OrdinalIgnoreCase) == 0) { + Log.LogWarning (null, code, null, filename, lineNumber, columnNumber, -1, + -1, text, null); + } else if (String.Compare (category, "error", StringComparison.OrdinalIgnoreCase) == 0) { + Log.LogError (null, code, null, filename, lineNumber, columnNumber, -1, + -1, text, null); } else { - filename = origin; - lineNumber = 0; - columnNumber = 0; - endLineNumber = 0; - endColumnNumber = 0; + Log.LogMessage (importance, singleLine); } } - [MonoTODO] protected virtual string GenerateCommandLineCommands () { return null; @@ -253,22 +282,36 @@ namespace Microsoft.Build.Utilities protected abstract string GenerateFullPathToTool (); - [MonoTODO] protected virtual string GenerateResponseFileCommands () { return null; } - [MonoTODO] protected virtual string GetResponseFileSwitch (string responseFilePath) { return String.Format ("@{0}", responseFilePath); } - [MonoTODO] + protected virtual ProcessStartInfo GetProcessStartInfo (string pathToTool, string commandLineCommands, string responseFileSwitch) + { + var pinfo = new ProcessStartInfo (pathToTool, String.Format ("{0} {1}", commandLineCommands, responseFileSwitch)); + + pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory; + pinfo.UseShellExecute = false; + pinfo.RedirectStandardOutput = true; + pinfo.RedirectStandardError = true; + + return pinfo; + } + protected virtual bool HandleTaskExecutionErrors () { - return true; + if (!Log.HasLoggedErrors && exitCode != 0) + Log.LogError ("Tool exited with code: {0}. Output: {1}", exitCode, + toolOutput != null ? toolOutput.ToString () : String.Empty); + toolOutput = null; + + return ExitCode == 0 && !Log.HasLoggedErrors; } protected virtual HostObjectInitializationStatus InitializeHostObject () @@ -276,9 +319,9 @@ namespace Microsoft.Build.Utilities return HostObjectInitializationStatus.NoActionReturnSuccess; } - [MonoTODO] protected virtual void LogToolCommand (string message) { + Log.LogMessage (MessageImportance.Normal, message); } [MonoTODO] @@ -289,7 +332,7 @@ namespace Microsoft.Build.Utilities protected virtual bool SkipTaskExecution() { - return false; + return !ValidateParameters (); } protected virtual bool ValidateParameters() @@ -297,12 +340,64 @@ namespace Microsoft.Build.Utilities return true; } + protected void DeleteTempFile (string fileName) + { + if (String.IsNullOrEmpty (fileName)) + return; + + try { + File.Delete (fileName); + } catch (IOException ioe) { + Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", ioe.Message); + } catch (UnauthorizedAccessException uae) { + Log.LogWarning ("Unable to delete temporary file '{0}' : {1}", uae.Message); + } + } + + // If EnvironmentVariables is defined, then merge EnvironmentOverride + // EnvironmentOverride is Obsolete'd in 4.0 + // + // Returns the final set of environment variables and logs them + SCS.StringDictionary GetAndLogEnvironmentVariables () + { + var env_vars = GetEnvironmentVariables (); + if (env_vars == null) + return env_vars; + + Log.LogMessage (MessageImportance.Low, "Environment variables being passed to the tool:"); + foreach (DictionaryEntry entry in env_vars) + Log.LogMessage (MessageImportance.Low, "\t{0}={1}", (string)entry.Key, (string)entry.Value); + + return env_vars; + } + + SCS.StringDictionary GetEnvironmentVariables () + { + if (EnvironmentVariables == null || EnvironmentVariables.Length == 0) + return EnvironmentOverride; + + var env_vars = new SCS.ProcessStringDictionary (); + foreach (string pair in EnvironmentVariables) { + string [] key_value = pair.Split ('='); + if (!String.IsNullOrEmpty (key_value [0])) + env_vars [key_value [0]] = key_value.Length > 1 ? key_value [1] : String.Empty; + } + + if (EnvironmentOverride != null) + foreach (DictionaryEntry entry in EnvironmentOverride) + env_vars [(string)entry.Key] = (string)entry.Value; + + return env_vars; + } + protected virtual StringDictionary EnvironmentOverride { - get { return environmentOverride; } + get { return null; } } - - [MonoTODO] + + // Ignore EnvironmentOverride if this is set + public string[] EnvironmentVariables { get; set; } + [Output] public int ExitCode { get { return exitCode; } @@ -331,12 +426,30 @@ namespace Microsoft.Build.Utilities get { return standardOutputLoggingImportance; } } + protected virtual bool HasLoggedErrors { + get { return Log.HasLoggedErrors; } + } + public virtual int Timeout { get { return timeout; } set { timeout = value; } } + public virtual string ToolExe + { + get { + if (toolExe == null) + return ToolName; + else + return toolExe; + } + set { + if (!String.IsNullOrEmpty (value)) + toolExe = value; + } + } + protected abstract string ToolName { get; @@ -345,7 +458,21 @@ namespace Microsoft.Build.Utilities public string ToolPath { get { return toolPath; } - set { toolPath = value; } + set { + if (!String.IsNullOrEmpty (value)) + toolPath = value; + } + } + + // Snatched from our codedom code, with some changes to make it compatible with csc + // (the line+column group is optional is csc) + static Regex errorRegex; + static Regex CscErrorRegex { + get { + if (errorRegex == null) + errorRegex = new Regex (@"^(\s*(?[^\(]+)(\((?\d*)(,(?\d*[\+]*))?\))?:\s+)*(?\w+)\s+(?.*\d):\s*(?.*)", RegexOptions.Compiled | RegexOptions.ExplicitCapture); + return errorRegex; + } } } }