[xbuild] ConsoleLogger - parse params only on Initialize.
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / ConsoleLogger.cs
index 02d0179fb3fdb2e8a08772a4951ad5ca7d91f70f..2381cff584c7d3271e2f871a4c56d42318682715 100644 (file)
@@ -32,6 +32,7 @@ using System.Runtime.InteropServices;
 using System.Collections;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Security;
 using System.Text;
 using Microsoft.Build.Framework;
@@ -54,11 +55,14 @@ namespace Microsoft.Build.BuildEngine {
                ConsoleColor errorColor, warningColor, eventColor, messageColor, highMessageColor;
                ColorSetter colorSet;
                ColorResetter colorReset;
+               IEventSource eventSource;
                bool no_message_color, use_colors;
+               bool noItemAndPropertyList;
 
-               List<BuildStatusEventArgs> events;
+               List<BuildEvent> events;
                Dictionary<string, List<string>> errorsTable;
                Dictionary<string, List<string>> warningsTable;
+               SortedDictionary<string, PerfInfo> targetPerfTable, tasksPerfTable;
                string current_events_string;
                
                public ConsoleLogger ()
@@ -92,9 +96,11 @@ namespace Microsoft.Build.BuildEngine {
                        this.colorSet = colorSet;
                        this.colorReset = colorReset;
 
-                       events = new List<BuildStatusEventArgs> ();
+                       events = new List<BuildEvent> ();
                        errorsTable = new Dictionary<string, List<string>> ();
                        warningsTable = new Dictionary<string, List<string>> ();
+                       targetPerfTable = new SortedDictionary<string, PerfInfo> ();
+                       tasksPerfTable = new SortedDictionary<string, PerfInfo> ();
 
                        //defaults
                        errorColor = ConsoleColor.DarkRed;
@@ -191,30 +197,101 @@ namespace Microsoft.Build.BuildEngine {
                public void ApplyParameter (string parameterName,
                                            string parameterValue)
                {
-                       // FIXME: what we should do here? in msbuild it isn't
-                       // changing "parameters" property
+                       switch (parameterName) {
+                               case "PerformanceSummary":
+                                       this.performanceSummary = true;
+                                       break;
+                               case "Summary":
+                                       this.showSummary = true;
+                                       break;
+                               case "NoSummary":
+                                       this.showSummary = false;
+                                       break;
+                               case "NoItemAndPropertyList":
+                                       this.noItemAndPropertyList = true;
+                                       break;
+                               default:
+                                       if (parameterName.StartsWith ("Verbosity="))
+                                               ParseVerbosity (parameterName);
+                                       break;
+                       }
+               }
+
+               void ParseVerbosity (string s)
+               {
+                       string key, value;
+                       if (!TrySplitKeyValuePair (s, out key, out value))
+                               throw new LoggerException ("Unknown Verbosity, should be set as 'Verbosity=<verbosity>'");
+
+                       switch (value) {
+                       case "q":
+                       case "quiet":
+                               Verbosity = LoggerVerbosity.Quiet;
+                               break;
+                       case "m":
+                       case "minimal":
+                               Verbosity = LoggerVerbosity.Minimal;
+                               break;
+                       case "n":
+                       case "normal":
+                               Verbosity = LoggerVerbosity.Normal;
+                               break;
+                       case "d":
+                       case "detailed":
+                               Verbosity = LoggerVerbosity.Detailed;
+                               break;
+                       case "diag":
+                       case "diagnostic":
+                               Verbosity = LoggerVerbosity.Diagnostic;
+                               break;
+                       default:
+                               throw new LoggerException (String.Format ("Unknown verbosity - '{0}'", s));
+                       }
+               }
+
+               bool TrySplitKeyValuePair (string pair, out string key, out string value)
+               {
+                       key = value = null;
+                       string[] parts = pair.Split ('=');
+                       if (parts.Length != 2)
+                               return false;
+
+                       key = parts [0];
+                       value = parts [1];
+                       return true;
                }
 
                public virtual void Initialize (IEventSource eventSource)
                {
-                        eventSource.BuildStarted +=  new BuildStartedEventHandler (BuildStartedHandler);
-                        eventSource.BuildFinished += new BuildFinishedEventHandler (BuildFinishedHandler);
-                        eventSource.ProjectStarted += new ProjectStartedEventHandler (ProjectStartedHandler);
-                        eventSource.ProjectFinished += new ProjectFinishedEventHandler (ProjectFinishedHandler);
-                        eventSource.TargetStarted += new TargetStartedEventHandler (TargetStartedHandler);
-                        eventSource.TargetFinished += new TargetFinishedEventHandler (TargetFinishedHandler);
-                        eventSource.TaskStarted += new TaskStartedEventHandler (TaskStartedHandler);
-                        eventSource.TaskFinished += new TaskFinishedEventHandler (TaskFinishedHandler);
-                        eventSource.MessageRaised += new BuildMessageEventHandler (MessageHandler);
-                        eventSource.WarningRaised += new BuildWarningEventHandler (WarningHandler);
-                        eventSource.ErrorRaised += new BuildErrorEventHandler (ErrorHandler);
+                       this.eventSource = eventSource;
+
+                       eventSource.BuildStarted += BuildStartedHandler;
+                       eventSource.BuildFinished += BuildFinishedHandler;
+
+                       eventSource.ProjectStarted += PushEvent;
+                       eventSource.ProjectFinished += PopEvent;
+
+                       eventSource.TargetStarted += PushEvent;
+                       eventSource.TargetFinished += PopEvent;
+
+                       eventSource.TaskStarted += PushEvent;
+                       eventSource.TaskFinished += PopEvent;
+
+                       eventSource.MessageRaised += MessageHandler;
+                       eventSource.WarningRaised += WarningHandler;
+                       eventSource.ErrorRaised += ErrorHandler;
+
+                       if (!String.IsNullOrEmpty (parameters))
+                               ParseParameters ();
                }
-               
+
                public void BuildStartedHandler (object sender, BuildStartedEventArgs args)
                {
-                       WriteLine (String.Empty);
-                       WriteLine (String.Format ("Build started {0}.", args.Timestamp));
-                       WriteLine ("__________________________________________________");
+                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
+                               WriteLine (String.Empty);
+                               WriteLine (String.Format ("Build started {0}.", args.Timestamp));
+                               WriteLine ("__________________________________________________");
+                       }
                        buildStart = args.Timestamp;
 
                        PushEvent (args);
@@ -222,14 +299,39 @@ namespace Microsoft.Build.BuildEngine {
                
                public void BuildFinishedHandler (object sender, BuildFinishedEventArgs args)
                {
+                       BuildFinishedHandlerActual (args);
+
+                       // Reset
+                       events.Clear ();
+                       errorsTable.Clear ();
+                       warningsTable.Clear ();
+                       targetPerfTable.Clear ();
+                       tasksPerfTable.Clear ();
+                       errors.Clear ();
+                       warnings.Clear ();
+
+                       indent = 0;
+                       errorCount = 0;
+                       warningCount = 0;
+                       projectFailed = false;
+               }
+
+               void BuildFinishedHandlerActual (BuildFinishedEventArgs args)
+               {
+                       if (!IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
+                               PopEvent (args);
+                               return;
+                       }
+
+                       TimeSpan timeElapsed = args.Timestamp - buildStart;
+                       if (performanceSummary || verbosity == LoggerVerbosity.Diagnostic)
+                               DumpPerformanceSummary ();
+
                        if (args.Succeeded == true && !projectFailed) {
                                WriteLine ("Build succeeded.");
                        } else {
                                WriteLine ("Build FAILED.");
                        }
-                       if (performanceSummary == true) {
-                       }
-
                        if (warnings.Count > 0) {
                                WriteLine (Environment.NewLine + "Warnings:");
                                SetColor (warningColor);
@@ -268,25 +370,25 @@ namespace Microsoft.Build.BuildEngine {
                        }
 
                        if (showSummary == true){
-                               TimeSpan timeElapsed = args.Timestamp - buildStart;
                                WriteLine (String.Format ("\t {0} Warning(s)", warningCount));
                                WriteLine (String.Format ("\t {0} Error(s)", errorCount));
                                WriteLine (String.Empty);
                                WriteLine (String.Format ("Time Elapsed {0}", timeElapsed));
-                       } 
-                       PopEvent ();
+                       }
+
+                       PopEvent (args);
                }
 
                public void ProjectStartedHandler (object sender, ProjectStartedEventArgs args)
                {
-                       SetColor (eventColor);
-                       WriteLine (String.Format ("Project \"{0}\" ({1} target(s)):", args.ProjectFile,
-                                               String.IsNullOrEmpty (args.TargetNames) ? "default" : args.TargetNames));
-                       ResetColor ();
-                       WriteLine (String.Empty);
-                       DumpProperties (args.Properties);
-                       DumpItems (args.Items);
-                       PushEvent (args);
+                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
+                               SetColor (eventColor);
+                               WriteLine (String.Format ("Project \"{0}\" ({1} target(s)):", args.ProjectFile,
+                                                       String.IsNullOrEmpty (args.TargetNames) ? "default" : args.TargetNames));
+                               ResetColor ();
+                               DumpProperties (args.Properties);
+                               DumpItems (args.Items);
+                       }
                }
                
                public void ProjectFinishedHandler (object sender, ProjectFinishedEventArgs args)
@@ -303,49 +405,48 @@ namespace Microsoft.Build.BuildEngine {
                        if (!projectFailed)
                                // no project has failed yet, so update the flag
                                projectFailed = !args.Succeeded;
-
-                       PopEvent ();
                }
                
                public void TargetStartedHandler (object sender, TargetStartedEventArgs args)
                {
-                       indent++;
-                       SetColor (eventColor);
-                       WriteLine (String.Format ("Target {0}:",args.TargetName));
-                       ResetColor ();
-                       PushEvent (args);
+                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
+                               indent++;
+                               SetColor (eventColor);
+                               WriteLine (String.Empty);
+                               WriteLine (String.Format ("Target {0}:",args.TargetName));
+                               ResetColor ();
+                       }
                }
                
                public void TargetFinishedHandler (object sender, TargetFinishedEventArgs args)
                {
-                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed) || !args.Succeeded) {
+                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed) ||
+                                       (!args.Succeeded && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal))) {
                                SetColor (eventColor);
                                WriteLine (String.Format ("Done building target \"{0}\" in project \"{1}\".{2}",
                                        args.TargetName, args.ProjectFile,
                                        args.Succeeded ? String.Empty : "-- FAILED"));
                                ResetColor ();
+                               WriteLine (String.Empty);
                        }
                        indent--;
-
-                       WriteLine (String.Empty);
-                       PopEvent ();
                }
                
                public void TaskStartedHandler (object sender, TaskStartedEventArgs args)
                {
-                       if (this.verbosity == LoggerVerbosity.Detailed) {
+                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed)) {
                                SetColor (eventColor);
                                WriteLine (String.Format ("Task \"{0}\"",args.TaskName));
                                ResetColor ();
                        }
                        indent++;
-                       PushEvent (args);
                }
                
                public void TaskFinishedHandler (object sender, TaskFinishedEventArgs args)
                {
                        indent--;
-                       if (this.verbosity == LoggerVerbosity.Detailed || !args.Succeeded) {
+                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed) ||
+                                       (!args.Succeeded && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal))) {
                                SetColor (eventColor);
                                if (args.Succeeded)
                                        WriteLine (String.Format ("Done executing task \"{0}\"", args.TaskName));
@@ -353,15 +454,16 @@ namespace Microsoft.Build.BuildEngine {
                                        WriteLine (String.Format ("Task \"{0}\" execution -- FAILED", args.TaskName));
                                ResetColor ();
                        }
-                       PopEvent ();
                }
                
                public void MessageHandler (object sender, BuildMessageEventArgs args)
                {
                        if (IsMessageOk (args)) {
                                if (no_message_color) {
+                                       ExecutePendingEventHandlers ();
                                        WriteLine (args.Message);
                                } else {
+                                       ExecutePendingEventHandlers ();
                                        SetColor (args.Importance == MessageImportance.High ? highMessageColor : messageColor);
                                        WriteLine (args.Message);
                                        ResetColor ();
@@ -372,7 +474,8 @@ namespace Microsoft.Build.BuildEngine {
                public void WarningHandler (object sender, BuildWarningEventArgs args)
                {
                        string msg = FormatWarningEvent (args);
-                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
+                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Quiet)) {
+                               ExecutePendingEventHandlers ();
                                SetColor (warningColor);
                                WriteLineWithoutIndent (msg);
                                ResetColor ();
@@ -390,7 +493,8 @@ namespace Microsoft.Build.BuildEngine {
                public void ErrorHandler (object sender, BuildErrorEventArgs args)
                {
                        string msg = FormatErrorEvent (args);
-                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Minimal)) {
+                       if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Quiet)) {
+                               ExecutePendingEventHandlers ();
                                SetColor (errorColor);
                                WriteLineWithoutIndent (msg);
                                ResetColor ();
@@ -415,33 +519,74 @@ namespace Microsoft.Build.BuildEngine {
                                StringBuilder sb = new StringBuilder ();
                                for (int i = 0; i < indent; i++)
                                        sb.Append ('\t');
-                               sb.Append (message);
 
-                               writeHandler (sb.ToString ());
+                               string indent_str = sb.ToString ();
+
+                               foreach (string line in message.Split (new string[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries))
+                                       writeHandler (indent_str + line);
                        } else {
                                writeHandler (message);
                        }
                }
 
-               void PushEvent (BuildStatusEventArgs args)
+               void PushEvent<T> (object sender, T args) where T: BuildStatusEventArgs
+               {
+                       PushEvent (args);
+               }
+
+               void PushEvent<T> (T args) where T: BuildStatusEventArgs
                {
-                       events.Add (args);
+                       BuildEvent be = new BuildEvent {
+                               EventArgs = args,
+                               StartHandlerHasExecuted = false,
+                               ConsoleLogger = this
+                       };
+
+                       events.Add (be);
                        current_events_string = null;
                }
 
-               void PopEvent ()
+               void PopEvent<T> (object sender, T finished_args) where T: BuildStatusEventArgs
+               {
+                       PopEvent (finished_args);
+               }
+
+               void PopEvent<T> (T finished_args) where T: BuildStatusEventArgs
                {
+                       if (events.Count == 0)
+                               throw new InvalidOperationException ("INTERNAL ERROR: Trying to pop from an empty events stack");
+
+                       BuildEvent be = events [events.Count - 1];
+                       if (performanceSummary || verbosity == LoggerVerbosity.Diagnostic) {
+                               var args = be.EventArgs;
+                               TargetStartedEventArgs tgt_args = args as TargetStartedEventArgs;
+                               if (tgt_args != null) {
+                                       AddPerfInfo (tgt_args.TargetName, args.Timestamp, targetPerfTable);
+                               } else {
+                                       TaskStartedEventArgs tsk_args = args as TaskStartedEventArgs;
+                                       if (tsk_args != null)
+                                               AddPerfInfo (tsk_args.TaskName, args.Timestamp, tasksPerfTable);
+                               }
+                       }
+
+                       be.ExecuteFinishedHandler (finished_args);
                        events.RemoveAt (events.Count - 1);
                        current_events_string = null;
                }
 
+               void ExecutePendingEventHandlers ()
+               {
+                       foreach (var be in events)
+                               be.ExecuteStartedHandler ();
+               }
+
                string EventsToString ()
                {
                        StringBuilder sb = new StringBuilder ();
 
                        string last_imported_target_file = String.Empty;
                        for (int i = 0; i < events.Count; i ++) {
-                               BuildStatusEventArgs args = events [i];
+                               var args = events [i].EventArgs;
                                ProjectStartedEventArgs pargs = args as ProjectStartedEventArgs;
                                if (pargs != null) {
                                        sb.AppendFormat ("{0} ({1}) ->\n", pargs.ProjectFile,
@@ -466,6 +611,39 @@ namespace Microsoft.Build.BuildEngine {
 
                        return sb.ToString ();
                }
+
+               void AddPerfInfo (string name, DateTime start, IDictionary<string, PerfInfo> perf_table)
+               {
+                       PerfInfo pi;
+                       if (!perf_table.TryGetValue (name, out pi)) {
+                               pi = new PerfInfo ();
+                               perf_table [name] = pi;
+                       }
+
+                       pi.Time += DateTime.Now - start;
+                       pi.NumberOfCalls ++;
+               }
+
+               void DumpPerformanceSummary ()
+               {
+                       SetColor (eventColor);
+                       WriteLine ("Target perfomance summary:");
+                       ResetColor ();
+
+                       foreach (var pi in targetPerfTable.OrderBy (pair => pair.Value.Time))
+                               WriteLine (String.Format ("{0,10:0.000} ms  {1,-50}  {2,5} calls", pi.Value.Time.TotalMilliseconds, pi.Key, pi.Value.NumberOfCalls));
+
+                       WriteLine (String.Empty);
+
+                       SetColor (eventColor);
+                       WriteLine ("Tasks perfomance summary:");
+                       ResetColor ();
+
+                       foreach (var pi in tasksPerfTable.OrderBy (pair => pair.Value.Time))
+                               WriteLine (String.Format ("{0,10:0.000} ms  {1,-50}  {2,5} calls", pi.Value.Time.TotalMilliseconds, pi.Key, pi.Value.NumberOfCalls));
+
+                       WriteLine (String.Empty);
+               }
                
                private void WriteLineWithoutIndent (string message)
                {
@@ -492,22 +670,30 @@ namespace Microsoft.Build.BuildEngine {
                private void ParseParameters ()
                {
                        string[] splittedParameters = parameters.Split (';');
-                       foreach (string s in splittedParameters ) {
-                               switch (s) {
-                               case "PerformanceSummary":
-                                       this.performanceSummary = true;
-                                       break;
-                               case "NoSummary":
-                                       this.showSummary = false;
-                                       break;
-                               default:
-                                       throw new ArgumentException ("Invalid parameter.");
-                               }
-                       }
+                       foreach (string s in splittedParameters )
+                               ApplyParameter (s, null);
                }
                
                public virtual void Shutdown ()
                {
+                       if (eventSource == null)
+                               return;
+
+                       eventSource.BuildStarted -= BuildStartedHandler;
+                       eventSource.BuildFinished -= BuildFinishedHandler;
+
+                       eventSource.ProjectStarted -= PushEvent;
+                       eventSource.ProjectFinished -= PopEvent;
+
+                       eventSource.TargetStarted -= PushEvent;
+                       eventSource.TargetFinished -= PopEvent;
+
+                       eventSource.TaskStarted -= PushEvent;
+                       eventSource.TaskFinished -= PopEvent;
+
+                       eventSource.MessageRaised -= MessageHandler;
+                       eventSource.WarningRaised -= WarningHandler;
+                       eventSource.ErrorRaised -= ErrorHandler;
                }
 
                static bool InEmacs = Environment.GetEnvironmentVariable ("EMACS") == "t";
@@ -586,11 +772,11 @@ namespace Microsoft.Build.BuildEngine {
 
                void DumpProperties (IEnumerable properties)
                {
-                       if (!IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic))
+                       if (noItemAndPropertyList || !IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic))
                                return;
 
                        SetColor (eventColor);
-                       WriteLine ("\n");
+                       WriteLine (String.Empty);
                        WriteLine ("Initial Properties:");
                        ResetColor ();
 
@@ -603,16 +789,15 @@ namespace Microsoft.Build.BuildEngine {
 
                        foreach (KeyValuePair<string, string> pair in dict)
                                WriteLine (String.Format ("{0} = {1}", pair.Key, pair.Value));
-                       WriteLine ("\n");
                }
 
                void DumpItems (IEnumerable items)
                {
-                       if (!IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic) || items == null)
+                       if (noItemAndPropertyList || !IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic) || items == null)
                                return;
 
                        SetColor (eventColor);
-                       WriteLine ("\n");
+                       WriteLine (String.Empty);
                        WriteLine ("Initial Items:");
                        ResetColor ();
                        if (items == null)
@@ -634,7 +819,6 @@ namespace Microsoft.Build.BuildEngine {
                                        WriteLine (item.ItemSpec);
                                indent--;
                        }
-                       WriteLine ("\n");
                }
 
                public string Parameters {
@@ -645,8 +829,6 @@ namespace Microsoft.Build.BuildEngine {
                                if (value == null)
                                        throw new ArgumentNullException ();
                                parameters = value;
-                               if (parameters != String.Empty)
-                                       ParseParameters ();
                        }
                }
 
@@ -678,6 +860,49 @@ namespace Microsoft.Build.BuildEngine {
                        set { writeHandler = value; }
                }
        }
+
+       class BuildEvent {
+               public BuildStatusEventArgs EventArgs;
+               public bool StartHandlerHasExecuted;
+               public ConsoleLogger ConsoleLogger;
+
+               public void ExecuteStartedHandler ()
+               {
+                       if (StartHandlerHasExecuted)
+                               return;
+
+                       if (EventArgs is ProjectStartedEventArgs)
+                               ConsoleLogger.ProjectStartedHandler (null, (ProjectStartedEventArgs)EventArgs);
+                       else if (EventArgs is TargetStartedEventArgs)
+                               ConsoleLogger.TargetStartedHandler (null, (TargetStartedEventArgs)EventArgs);
+                       else if (EventArgs is TaskStartedEventArgs)
+                               ConsoleLogger.TaskStartedHandler (null, (TaskStartedEventArgs)EventArgs);
+                       else if (!(EventArgs is BuildStartedEventArgs))
+                               throw new InvalidOperationException ("Unexpected event on the stack, type: " + EventArgs.GetType ());
+
+                       StartHandlerHasExecuted = true;
+               }
+
+               public void ExecuteFinishedHandler (BuildStatusEventArgs finished_args)
+               {
+                       if (!StartHandlerHasExecuted)
+                               return;
+
+                       if (EventArgs is ProjectStartedEventArgs)
+                               ConsoleLogger.ProjectFinishedHandler (null, finished_args as ProjectFinishedEventArgs);
+                       else if (EventArgs is TargetStartedEventArgs)
+                               ConsoleLogger.TargetFinishedHandler (null, finished_args as TargetFinishedEventArgs);
+                       else if (EventArgs is TaskStartedEventArgs)
+                               ConsoleLogger.TaskFinishedHandler (null, finished_args as TaskFinishedEventArgs);
+                       else if (!(EventArgs is BuildStartedEventArgs))
+                               throw new InvalidOperationException ("Unexpected event on the stack, type: " + EventArgs.GetType ());
+               }
+       }
+
+       class PerfInfo {
+               public TimeSpan Time;
+               public int NumberOfCalls;
+       }
 }
 
 #endif