{
public abstract class ToolTask : Task
{
- SCS.ProcessStringDictionary environmentOverride;
int exitCode;
int timeout;
string toolPath, toolExe;
this.toolPath = MonoLocationHelper.GetBinDir ();
this.responseFileEncoding = Encoding.UTF8;
this.timeout = Int32.MaxValue;
- this.environmentOverride = new SCS.ProcessStringDictionary ();
}
[MonoTODO]
if (pathToTool == null)
throw new ArgumentNullException ("pathToTool");
- string output, error, responseFileName;
- StreamWriter outwr, errwr;
-
- outwr = errwr = null;
- responseFileName = output = error = null;
+ string responseFileName;
+ responseFileName = null;
toolOutput = new StringBuilder ();
try {
- string arguments = commandLineCommands;
+ string responseFileSwitch = String.Empty;
if (!String.IsNullOrEmpty (responseFileCommands)) {
responseFileName = Path.GetTempFileName ();
File.WriteAllText (responseFileName, responseFileCommands);
- arguments = arguments + " " + GetResponseFileSwitch (responseFileName);
+ responseFileSwitch = GetResponseFileSwitch (responseFileName);
}
+ var pinfo = GetProcessStartInfo (pathToTool, commandLineCommands, responseFileSwitch);
LogToolCommand (String.Format ("Tool {0} execution started with arguments: {1} {2}",
- pathToTool, commandLineCommands, responseFileCommands));
-
- output = Path.GetTempFileName ();
- error = Path.GetTempFileName ();
- outwr = new StreamWriter (output);
- errwr = new StreamWriter (error);
-
- ProcessStartInfo pinfo = new ProcessStartInfo (pathToTool, arguments);
- pinfo.WorkingDirectory = GetWorkingDirectory () ?? Environment.CurrentDirectory;
-
- pinfo.UseShellExecute = false;
- pinfo.RedirectStandardOutput = true;
- pinfo.RedirectStandardError = true;
+ pinfo.FileName, commandLineCommands, responseFileCommands));
+ var pendingLineFragmentOutput = new StringBuilder ();
+ var pendingLineFragmentError = new StringBuilder ();
+ var environmentOverride = GetAndLogEnvironmentVariables ();
try {
- ProcessWrapper pw = ProcessService.StartProcess (pinfo, outwr, errwr, null, environmentOverride);
- pw.OutputStreamChanged += delegate (object o, string msg) { ProcessLine (msg, StandardOutputLoggingImportance); };
- pw.ErrorStreamChanged += delegate (object o, string msg) { ProcessLine (msg, StandardErrorLoggingImportance); };
+ // 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);
- pw.WaitForOutput (timeout == Int32.MaxValue ? -1 : timeout);
exitCode = pw.ExitCode;
- outwr.Close();
- errwr.Close();
pw.Dispose ();
} catch (System.ComponentModel.Win32Exception e) {
Log.LogError ("Error executing tool '{0}': {1}", pathToTool, e.Message);
if (typeLoadException)
ProcessTypeLoadException ();
+ pendingLineFragmentOutput.Length = 0;
+ pendingLineFragmentError.Length = 0;
+
Log.LogMessage (MessageImportance.Low, "Tool {0} execution finished.", pathToTool);
return exitCode;
} finally {
DeleteTempFile (responseFileName);
- if (outwr != null)
- outwr.Dispose ();
- if (errwr != null)
- errwr.Dispose ();
-
- DeleteTempFile (output);
- DeleteTempFile (error);
}
}
Log.LogError (output_str);
}
- void ProcessLine (string line, MessageImportance importance)
+ void ProcessLine (StringBuilder outputBuilder, MessageImportance importance, bool isLastLine)
{
- foreach (string singleLine in line.Split (new string [] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)) {
+ 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
protected virtual void LogEventsFromTextOutput (string singleLine, MessageImportance importance)
{
- singleLine = singleLine.Trim ();
- if (singleLine.Length == 0)
+ if (singleLine.Length == 0) {
+ Log.LogMessage (singleLine, importance);
return;
+ }
if (singleLine.StartsWith ("Unhandled Exception: System.TypeLoadException") ||
singleLine.StartsWith ("Unhandled Exception: System.IO.FileNotFoundException")) {
string col = match.Result ("${column}");
int columnNumber = 0;
if (!string.IsNullOrEmpty (col))
- columnNumber = col == "255+" ? -1 : Int32.Parse (col);
+ columnNumber = col.IndexOf ("+") >= 0 ? -1 : Int32.Parse (col);
string category = match.Result ("${level}");
string code = match.Result ("${number}");
return String.Format ("@{0}", responseFilePath);
}
+ 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 ()
{
if (!Log.HasLoggedErrors && exitCode != 0)
}
}
+ // 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; }
}
-
+
+ // Ignore EnvironmentOverride if this is set
+ public string[] EnvironmentVariables { get; set; }
+
[Output]
public int ExitCode {
get { return exitCode; }