Merge pull request #901 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / ConsoleLogger.cs
1 //
2 // ConsoleLogger.cs: Outputs to the console
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 // 
7 // (C) 2005 Marek Sieradzki
8 //
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:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
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.
27
28 using System;
29 using System.Runtime.InteropServices;
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.IO;
33 using System.Linq;
34 using System.Security;
35 using System.Text;
36 using Microsoft.Build.Framework;
37
38 #if MICROSOFT_BUILD_DLL
39 namespace Microsoft.Build.Logging
40 #else
41 namespace Microsoft.Build.BuildEngine
42 #endif
43 {
44         public class ConsoleLogger : ILogger {
45         
46                 string          parameters;
47                 LoggerVerbosity verbosity;
48                 WriteHandler    writeHandler;
49                 bool            skipProjectStartedText;
50                 ConsoleColor errorColor, warningColor, eventColor, messageColor, highMessageColor;
51                 ColorSetter colorSet;
52                 ColorResetter colorReset;
53                 IEventSource eventSource;
54                 bool no_message_color, use_colors;
55                 ConsoleLoggerParameter config = new ConsoleLoggerParameter ();          
56                 
57                 public ConsoleLogger ()
58                         : this (LoggerVerbosity.Normal, null, null, null)
59                 {
60                 }
61
62                 public ConsoleLogger (LoggerVerbosity verbosity)
63                         : this (verbosity, null, null, null)
64                 {
65                 }
66                 
67                 public ConsoleLogger (LoggerVerbosity verbosity,
68                                       WriteHandler write,
69                                       ColorSetter colorSet,
70                                       ColorResetter colorReset)
71                 {
72                         this.verbosity = verbosity;
73                         if (write == null)
74                                 this.writeHandler += new WriteHandler (WriteHandlerFunction);
75                         else
76                                 this.writeHandler += write;
77                         this.skipProjectStartedText = false;
78                         this.colorSet = colorSet;
79                         this.colorReset = colorReset;
80
81                         //defaults
82                         errorColor = ConsoleColor.DarkRed;
83                         warningColor = ConsoleColor.DarkYellow;
84                         eventColor = ConsoleColor.DarkCyan;
85                         messageColor = ConsoleColor.DarkGray;
86                         highMessageColor = ConsoleColor.White;
87
88                         // if message color is not set via the env var,
89                         // then don't use any color for it.
90                         no_message_color = true;
91
92                         use_colors = false;
93                         if (colorSet == null || colorReset == null)
94                                 return;
95
96                         // color support
97                         string config = Environment.GetEnvironmentVariable ("XBUILD_COLORS");
98                         if (config == null) {
99                                 use_colors = true;
100                                 return;
101                         }
102
103                         if (config == "disable")
104                                 return;
105
106                         use_colors = true;
107                         string [] pairs = config.Split (new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
108                         foreach (string pair in pairs) {
109                                 string [] parts = pair.Split (new char[] {'='}, StringSplitOptions.RemoveEmptyEntries);
110                                 if (parts.Length != 2)
111                                         continue;
112
113                                 if (parts [0] == "errors")
114                                         TryParseConsoleColor (parts [1], ref errorColor);
115                                 else if (parts [0] == "warnings")
116                                         TryParseConsoleColor (parts [1], ref warningColor);
117                                 else if (parts [0] == "events")
118                                         TryParseConsoleColor (parts [1], ref eventColor);
119                                 else if (parts [0] == "messages") {
120                                         if (TryParseConsoleColor (parts [1], ref messageColor)) {
121                                                 highMessageColor = GetBrightColorFor (messageColor);
122                                                 no_message_color = false;
123                                         }
124                                 }
125                         }
126                 }
127                 
128                 private void WriteHandlerFunction (string message)
129                 {
130                         Console.WriteLine (message);
131                 }
132
133                 bool TryParseConsoleColor (string color_str, ref ConsoleColor color)
134                 {
135                         switch (color_str.ToLowerInvariant ()) {
136                         case "black": color = ConsoleColor.Black; break;
137
138                         case "blue": color = ConsoleColor.DarkBlue; break;
139                         case "green": color = ConsoleColor.DarkGreen; break;
140                         case "cyan": color = ConsoleColor.DarkCyan; break;
141                         case "red": color = ConsoleColor.DarkRed; break;
142                         case "magenta": color = ConsoleColor.DarkMagenta; break;
143                         case "yellow": color = ConsoleColor.DarkYellow; break;
144                         case "grey": color = ConsoleColor.DarkGray; break;
145
146                         case "brightgrey": color = ConsoleColor.Gray; break;
147                         case "brightblue": color = ConsoleColor.Blue; break;
148                         case "brightgreen": color = ConsoleColor.Green; break;
149                         case "brightcyan": color = ConsoleColor.Cyan; break;
150                         case "brightred": color = ConsoleColor.Red; break;
151                         case "brightmagenta": color = ConsoleColor.Magenta; break;
152                         case "brightyellow": color = ConsoleColor.Yellow; break;
153
154                         case "white":
155                         case "brightwhite": color = ConsoleColor.White; break;
156                         default: return false;
157                         }
158
159                         return true;
160                 }
161
162                 ConsoleColor GetBrightColorFor (ConsoleColor color)
163                 {
164                         switch (color) {
165                         case ConsoleColor.DarkBlue: return ConsoleColor.Blue;
166                         case ConsoleColor.DarkGreen: return ConsoleColor.Green;
167                         case ConsoleColor.DarkCyan: return ConsoleColor.Cyan;
168                         case ConsoleColor.DarkRed: return ConsoleColor.Red;
169                         case ConsoleColor.DarkMagenta: return ConsoleColor.Magenta;
170                         case ConsoleColor.DarkYellow: return ConsoleColor.Yellow;
171                         case ConsoleColor.DarkGray: return ConsoleColor.Gray;
172                         case ConsoleColor.Gray: return ConsoleColor.White;
173
174                         default: return color;
175                         }
176                 }
177                 
178                 class ConsoleLoggerParameter
179                 {
180                         public ConsoleLoggerParameter ()
181                         {
182                                 ShowSummary = true;
183                         }
184                         public bool PerformanceSummary { get; set; }
185                         public bool ShowSummary { get; set; }
186                         public bool NoItemAndPropertyList { get; set; }
187                 }
188                 
189                 public void ApplyParameter (string parameterName,
190                                             string parameterValue)
191                 {
192                         switch (parameterName) {
193                                 case "PerformanceSummary":
194                                         config.PerformanceSummary = true;
195                                         break;
196                                 case "Summary":
197                                         config.ShowSummary = true;
198                                         break;
199                                 case "NoSummary":
200                                         config.ShowSummary = false;
201                                         break;
202                                 case "NoItemAndPropertyList":
203                                         config.NoItemAndPropertyList = true;
204                                         break;
205                                 default:
206                                         if (parameterName.StartsWith ("Verbosity="))
207                                                 ParseVerbosity (parameterName);
208                                         break;
209                         }
210                 }
211
212                 void ParseVerbosity (string s)
213                 {
214                         string key, value;
215                         if (!TrySplitKeyValuePair (s, out key, out value))
216                                 throw new LoggerException ("Unknown Verbosity, should be set as 'Verbosity=<verbosity>'");
217
218                         switch (value) {
219                         case "q":
220                         case "quiet":
221                                 Verbosity = LoggerVerbosity.Quiet;
222                                 break;
223                         case "m":
224                         case "minimal":
225                                 Verbosity = LoggerVerbosity.Minimal;
226                                 break;
227                         case "n":
228                         case "normal":
229                                 Verbosity = LoggerVerbosity.Normal;
230                                 break;
231                         case "d":
232                         case "detailed":
233                                 Verbosity = LoggerVerbosity.Detailed;
234                                 break;
235                         case "diag":
236                         case "diagnostic":
237                                 Verbosity = LoggerVerbosity.Diagnostic;
238                                 break;
239                         default:
240                                 throw new LoggerException (String.Format ("Unknown verbosity - '{0}'", s));
241                         }
242                 }
243
244                 bool TrySplitKeyValuePair (string pair, out string key, out string value)
245                 {
246                         key = value = null;
247                         string[] parts = pair.Split ('=');
248                         if (parts.Length != 2)
249                                 return false;
250
251                         key = parts [0];
252                         value = parts [1];
253                         return true;
254                 }
255
256                 public virtual void Initialize (IEventSource eventSource)
257                 {
258                         this.eventSource = eventSource;
259
260                         eventSource.BuildStarted += BuildStartedHandler;
261                         eventSource.BuildFinished += BuildFinishedHandler;
262
263                         eventSource.ProjectStarted += PushEvent;
264                         eventSource.ProjectFinished += PopEvent;
265
266                         eventSource.TargetStarted += PushEvent;
267                         eventSource.TargetFinished += PopEvent;
268
269                         eventSource.TaskStarted += PushEvent;
270                         eventSource.TaskFinished += PopEvent;
271
272                         eventSource.MessageRaised += MessageHandler;
273                         eventSource.WarningRaised += WarningHandler;
274                         eventSource.ErrorRaised += ErrorHandler;
275
276                         if (!String.IsNullOrEmpty (parameters))
277                                 ParseParameters ();
278                 }
279                 
280                 Dictionary<object,BuildRecord> build_records = new Dictionary<object, BuildRecord> ();
281                 
282                 object dummy_key = new object ();
283                 
284                 BuildRecord GetBuildRecord (object sender)
285                 {
286                         BuildRecord r;
287                         // FIXME: our Microsoft.Build.Engine shouldn't give different "sender" object for each event
288                         // during the same build run. But it actually does.
289                         // It is problematic for parallel build because it is impossible to determine right "ongoing build"
290                         // record for the event without correct sender object.
291                         // Hence we expect sender as a valid object only if it is IBuildEngine4 -
292                         // only Microsoft.Build.Internal.BuildEngine4 implements it so far. 
293                         // (Used IBuildEngine3 because it needs to build for NET_4_0).
294 #if NET_4_0
295                         var key = sender as IBuildEngine3 ?? dummy_key;
296 #else
297                         var key = dummy_key;
298 #endif
299                         if (!build_records.TryGetValue (key, out r)) {
300                                 r = new BuildRecord (this);
301                                 build_records.Add (key, r);
302                         }
303                         return r;
304                 }
305
306                 public void BuildStartedHandler (object sender, BuildStartedEventArgs args)
307                 {
308                         GetBuildRecord (sender).BuildStartedHandler (sender, args);
309                 }
310                 
311                 public void BuildFinishedHandler (object sender, BuildFinishedEventArgs args)
312                 {
313                         GetBuildRecord (sender).BuildFinishedHandler (args);
314                         build_records.Remove (sender);
315                 }
316                 
317                 void PushEvent<T> (object sender, T args) where T: BuildStatusEventArgs
318                 {
319                         GetBuildRecord (sender).PushEvent (sender, args);
320                 }
321                 void PopEvent<T> (object sender, T args) where T: BuildStatusEventArgs
322                 {
323                         GetBuildRecord (sender).PopEvent (args);
324                 }
325                 public void ProjectStartedHandler (object sender, ProjectStartedEventArgs args)
326                 {
327                         GetBuildRecord (sender).ProjectStartedHandler (args);
328                 }
329                 public void ProjectFinishedHandler (object sender, ProjectFinishedEventArgs args)
330                 {
331                         GetBuildRecord (sender).ProjectFinishedHandler (args);
332                 }               
333                 public void TargetStartedHandler (object sender, TargetStartedEventArgs args)
334                 {
335                         GetBuildRecord (sender).TargetStartedHandler (args);
336                 }
337                 public void TargetFinishedHandler (object sender, TargetFinishedEventArgs args)
338                 {
339                         GetBuildRecord (sender).TargetFinishedHandler (args);
340                 }
341                 public void TaskStartedHandler (object sender, TaskStartedEventArgs args)
342                 {
343                         GetBuildRecord (sender).TaskStartedHandler (args);
344                 }
345                 public void TaskFinishedHandler (object sender, TaskFinishedEventArgs args)
346                 {
347                         GetBuildRecord (sender).TaskFinishedHandler (args);
348                 }
349                 public void MessageHandler (object sender, BuildMessageEventArgs args)
350                 {
351                         GetBuildRecord (sender).MessageHandler (args);
352                 }
353                 public void WarningHandler (object sender, BuildWarningEventArgs args)
354                 {
355                         GetBuildRecord (sender).WarningHandler (args);
356                 }
357                 public void ErrorHandler (object sender, BuildErrorEventArgs args)
358                 {
359                         GetBuildRecord (sender).ErrorHandler (args);
360                 }
361                 
362                 [MonoTODO]
363                 public void CustomEventHandler (object sender, CustomBuildEventArgs args)
364                 {
365                         build_records [sender].CustomHandler (args);
366                 }
367
368                 void SetColor (ConsoleColor color)
369                 {
370                         if (use_colors)
371                                 colorSet (color);
372                 }
373
374                 void ResetColor ()
375                 {
376                         if (use_colors)
377                                 colorReset ();
378                 }
379                 
380                 private void ParseParameters ()
381                 {
382                         string[] splittedParameters = parameters.Split (';');
383                         foreach (string s in splittedParameters )
384                                 ApplyParameter (s, null);
385                 }
386                 
387                 public virtual void Shutdown ()
388                 {
389                         if (eventSource == null)
390                                 return;
391
392                         eventSource.BuildStarted -= BuildStartedHandler;
393                         eventSource.BuildFinished -= BuildFinishedHandler;
394
395                         eventSource.ProjectStarted -= PushEvent;
396                         eventSource.ProjectFinished -= PopEvent;
397
398                         eventSource.TargetStarted -= PushEvent;
399                         eventSource.TargetFinished -= PopEvent;
400
401                         eventSource.TaskStarted -= PushEvent;
402                         eventSource.TaskFinished -= PopEvent;
403
404                         eventSource.MessageRaised -= MessageHandler;
405                         eventSource.WarningRaised -= WarningHandler;
406                         eventSource.ErrorRaised -= ErrorHandler;
407                 }
408
409                 public string Parameters {
410                         get {
411                                 return parameters;
412                         }
413                         set {
414                                 if (value == null)
415                                         throw new ArgumentNullException ();
416                                 parameters = value;
417                                 ParseParameters ();
418                         }
419                 }
420                 
421                 public bool ShowSummary {
422                         get { return config.ShowSummary; }
423                         set { config.ShowSummary = value; }
424                 }
425                 
426                 public bool SkipProjectStartedText {
427                         get { return skipProjectStartedText; }
428                         set { skipProjectStartedText = value; }
429                 }
430
431                 public LoggerVerbosity Verbosity {
432                         get { return verbosity; }
433                         set { verbosity = value; }
434                 }
435
436                 protected WriteHandler WriteHandler {
437                         get { return writeHandler; }
438                         set { writeHandler = value; }
439                 }
440                 
441                 class BuildRecord
442                 {
443                         readonly ConsoleLogger parent;
444                         
445                         readonly List<BuildEvent> events;
446                         readonly Dictionary<string, List<string>> errorsTable;
447                         readonly Dictionary<string, List<string>> warningsTable;
448                         readonly SortedDictionary<string, PerfInfo> targetPerfTable, tasksPerfTable;
449                         List<string> errors, warnings;
450                         int             indent;
451                         int             errorCount;
452                         int             warningCount;
453                         bool            projectFailed;
454                         DateTime                buildStart;
455         
456                         string current_events_string;
457                         
458                         ConsoleColor eventColor {
459                                 get { return parent.eventColor; }
460                         }
461                         LoggerVerbosity verbosity {
462                                 get { return parent.Verbosity; }
463                         }
464                         
465                         public BuildRecord (ConsoleLogger parent)
466                         {
467                                 this.parent = parent;
468                                 
469                                 this.indent = 0;
470                                 this.errorCount = 0;
471                                 this.warningCount = 0;
472                                 errors = new List<string> ();
473                                 warnings = new List<string> ();
474                                 events = new List<BuildEvent> ();
475                                 errorsTable = new Dictionary<string, List<string>> ();
476                                 warningsTable = new Dictionary<string, List<string>> ();
477                                 targetPerfTable = new SortedDictionary<string, PerfInfo> ();
478                                 tasksPerfTable = new SortedDictionary<string, PerfInfo> ();
479                         }
480         
481                         internal void PushEvent<T> (object sender, T args) where T: BuildStatusEventArgs
482                         {
483                                 BuildEvent be = new BuildEvent {
484                                         Sender = sender,
485                                         EventArgs = args,
486                                         StartHandlerHasExecuted = false,
487                                         ConsoleLogger = this.parent
488                                 };
489         
490                                 events.Add (be);
491                                 current_events_string = null;
492                         }
493         
494                         void PopEvent<T> (object sender, T finished_args) where T: BuildStatusEventArgs
495                         {
496                                 PopEvent (finished_args);
497                         }
498         
499                         internal void PopEvent<T> (T finished_args) where T: BuildStatusEventArgs
500                         {
501                                 if (events.Count == 0)
502                                         throw new InvalidOperationException ("INTERNAL ERROR: Trying to pop from an empty events stack");
503         
504                                 BuildEvent be = events [events.Count - 1];
505                                 if (parent.config.PerformanceSummary || verbosity == LoggerVerbosity.Diagnostic) {
506                                         var args = be.EventArgs;
507                                         TargetStartedEventArgs tgt_args = args as TargetStartedEventArgs;
508                                         if (tgt_args != null) {
509                                                 AddPerfInfo (tgt_args.TargetName, args.Timestamp, targetPerfTable);
510                                         } else {
511                                                 TaskStartedEventArgs tsk_args = args as TaskStartedEventArgs;
512                                                 if (tsk_args != null)
513                                                         AddPerfInfo (tsk_args.TaskName, args.Timestamp, tasksPerfTable);
514                                         }
515                                 }
516         
517                                 be.ExecuteFinishedHandler (finished_args);
518                                 events.RemoveAt (events.Count - 1);
519                                 current_events_string = null;
520                         }
521         
522                         public void ResetBuildState ()
523                         {
524                                 // Reset
525                                 events.Clear ();
526                                 errorsTable.Clear ();
527                                 warningsTable.Clear ();
528                                 targetPerfTable.Clear ();
529                                 tasksPerfTable.Clear ();
530                                 errors.Clear ();
531                                 warnings.Clear ();
532         
533                                 indent = 0;
534                                 errorCount = 0;
535                                 warningCount = 0;
536                                 projectFailed = false;
537                         }
538         
539                         void AddPerfInfo (string name, DateTime start, IDictionary<string, PerfInfo> perf_table)
540                         {
541                                 PerfInfo pi;
542                                 if (!perf_table.TryGetValue (name, out pi)) {
543                                         pi = new PerfInfo ();
544                                         perf_table [name] = pi;
545                                 }
546         
547                                 pi.Time += DateTime.Now - start;
548                                 pi.NumberOfCalls ++;
549                         }
550         
551                         public void BuildStartedHandler (object sender, BuildStartedEventArgs args)
552                         {
553                                 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
554                                         WriteLine (String.Empty);
555                                         WriteLine (String.Format ("Build started {0}.", args.Timestamp));
556                                         WriteLine ("__________________________________________________");
557                                 }
558                                 buildStart = args.Timestamp;
559         
560                                 PushEvent (sender, args);
561                         }
562                         
563                         public void BuildFinishedHandler (BuildFinishedEventArgs args)
564                         {
565                                 BuildFinishedHandlerActual (args);
566                                 
567                                 ResetBuildState ();
568                         }
569         
570                         void BuildFinishedHandlerActual (BuildFinishedEventArgs args)
571                         {
572                                 if (!IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
573                                         PopEvent (args);
574                                         return;
575                                 }
576         
577                                 TimeSpan timeElapsed = args.Timestamp - buildStart;
578                                 if (parent.config.PerformanceSummary || verbosity == LoggerVerbosity.Diagnostic)
579                                         DumpPerformanceSummary ();
580         
581                                 if (args.Succeeded == true && !projectFailed) {
582                                         WriteLine ("Build succeeded.");
583                                 } else {
584                                         WriteLine ("Build FAILED.");
585                                 }
586                                 if (warnings.Count > 0) {
587                                         WriteLine (Environment.NewLine + "Warnings:");
588                                         SetColor (parent.warningColor);
589         
590                                         WriteLine (String.Empty);
591                                         foreach (KeyValuePair<string, List<string>> pair in warningsTable) {
592                                                 if (!String.IsNullOrEmpty (pair.Key))
593                                                         WriteLine (pair.Key);
594         
595                                                 string indent_str = String.IsNullOrEmpty (pair.Key) ? String.Empty : "\t";
596                                                 foreach (string msg in pair.Value)
597                                                         WriteLine (String.Format ("{0}{1}", indent_str, msg));
598         
599                                                 WriteLine (String.Empty);
600                                         }
601         
602                                         ResetColor ();
603                                 }
604         
605                                 if (errors.Count > 0) {
606                                         WriteLine ("Errors:");
607                                         SetColor (parent.errorColor);
608         
609                                         WriteLine (String.Empty);
610                                         foreach (KeyValuePair<string, List<string>> pair in errorsTable) {
611                                                 if (!String.IsNullOrEmpty (pair.Key))
612                                                         WriteLine (pair.Key);
613         
614                                                 string indent_str = String.IsNullOrEmpty (pair.Key) ? String.Empty : "\t";
615                                                 foreach (string msg in pair.Value)
616                                                         WriteLine (String.Format ("{0}{1}", indent_str, msg));
617         
618                                                 WriteLine (String.Empty);
619                                         }
620                                         ResetColor ();
621                                 }
622         
623                                 if (parent.ShowSummary == true){
624                                         WriteLine (String.Format ("\t {0} Warning(s)", warningCount));
625                                         WriteLine (String.Format ("\t {0} Error(s)", errorCount));
626                                         WriteLine (String.Empty);
627                                         WriteLine (String.Format ("Time Elapsed {0}", timeElapsed));
628                                 }
629         
630                                 PopEvent (args);
631                         }
632         
633                         public void ProjectStartedHandler (ProjectStartedEventArgs args)
634                         {
635                                 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
636                                         SetColor (eventColor);
637                                         WriteLine (String.Format ("Project \"{0}\" ({1} target(s)):", args.ProjectFile,
638                                                                 String.IsNullOrEmpty (args.TargetNames) ? "default" : args.TargetNames));
639                                         ResetColor ();
640                                         DumpProperties (args.Properties);
641                                         DumpItems (args.Items);
642                                 }
643                         }
644                         
645                         public void ProjectFinishedHandler (ProjectFinishedEventArgs args)
646                         {
647                                 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
648                                         if (indent == 1)
649                                                 indent --;
650                                         SetColor (eventColor);
651                                         WriteLine (String.Format ("Done building project \"{0}\".{1}", args.ProjectFile,
652                                                                 args.Succeeded ? String.Empty : "-- FAILED"));
653                                         ResetColor ();
654                                         WriteLine (String.Empty);
655                                 }
656                                 if (!projectFailed)
657                                         // no project has failed yet, so update the flag
658                                         projectFailed = !args.Succeeded;
659                         }
660                         
661                         public void TargetStartedHandler (TargetStartedEventArgs args)
662                         {
663                                 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
664                                         indent++;
665                                         SetColor (eventColor);
666                                         WriteLine (String.Empty);
667                                         WriteLine (String.Format ("Target {0}:",args.TargetName));
668                                         ResetColor ();
669                                 }
670                         }
671                         
672                         public void TargetFinishedHandler (TargetFinishedEventArgs args)
673                         {
674                                 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed) ||
675                                                 (!args.Succeeded && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal))) {
676                                         SetColor (eventColor);
677                                         WriteLine (String.Format ("Done building target \"{0}\" in project \"{1}\".{2}",
678                                                 args.TargetName, args.ProjectFile,
679                                                 args.Succeeded ? String.Empty : "-- FAILED"));
680                                         ResetColor ();
681                                         WriteLine (String.Empty);
682                                 }
683                                 indent--;
684                         }
685                         
686                         public void TaskStartedHandler (TaskStartedEventArgs args)
687                         {
688                                 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed)) {
689                                         SetColor (eventColor);
690                                         WriteLine (String.Format ("Task \"{0}\"",args.TaskName));
691                                         ResetColor ();
692                                 }
693                                 indent++;
694                         }
695                         
696                         public void TaskFinishedHandler (TaskFinishedEventArgs args)
697                         {
698                                 indent--;
699                                 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed) ||
700                                                 (!args.Succeeded && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal))) {
701                                         SetColor (eventColor);
702                                         if (args.Succeeded)
703                                                 WriteLine (String.Format ("Done executing task \"{0}\"", args.TaskName));
704                                         else
705                                                 WriteLine (String.Format ("Task \"{0}\" execution -- FAILED", args.TaskName));
706                                         ResetColor ();
707                                 }
708                         }
709                         
710                         public void MessageHandler (BuildMessageEventArgs args)
711                         {
712                                 if (IsMessageOk (args)) {
713                                         if (parent.no_message_color) {
714                                                 ExecutePendingEventHandlers ();
715                                                 WriteLine (args.Message);
716                                         } else {
717                                                 ExecutePendingEventHandlers ();
718                                                 SetColor (args.Importance == MessageImportance.High ? parent.highMessageColor : parent.messageColor);
719                                                 WriteLine (args.Message);
720                                                 ResetColor ();
721                                         }
722                                 }
723                         }
724                         
725                         public void WarningHandler (BuildWarningEventArgs args)
726                         {
727                                 string msg = FormatWarningEvent (args);
728                                 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Quiet)) {
729                                         ExecutePendingEventHandlers ();
730                                         SetColor (parent.warningColor);
731                                         WriteLineWithoutIndent (msg);
732                                         ResetColor ();
733                                 }
734                                 warnings.Add (msg);
735         
736                                 List<string> list = null;
737                                 if (!warningsTable.TryGetValue (EventsAsString, out list))
738                                         warningsTable [EventsAsString] = list = new List<string> ();
739                                 list.Add (msg);
740         
741                                 warningCount++;
742                         }
743                         
744                         public void ErrorHandler (BuildErrorEventArgs args)
745                         {
746                                 string msg = FormatErrorEvent (args);
747                                 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Quiet)) {
748                                         ExecutePendingEventHandlers ();
749                                         SetColor (parent.errorColor);
750                                         WriteLineWithoutIndent (msg);
751                                         ResetColor ();
752                                 }
753                                 errors.Add (msg);
754         
755                                 List<string> list = null;
756                                 if (!errorsTable.TryGetValue (EventsAsString, out list))
757                                         errorsTable [EventsAsString] = list = new List<string> ();
758                                 list.Add (msg);
759                                 errorCount++;
760                         }
761                         
762                         public void CustomHandler (CustomBuildEventArgs args)
763                         {
764                         }
765                         
766                         private bool IsVerbosityGreaterOrEqual (LoggerVerbosity v)
767                         {
768                                 if (v == LoggerVerbosity.Diagnostic) {
769                                         return LoggerVerbosity.Diagnostic <= verbosity;
770                                 } else if (v == LoggerVerbosity.Detailed) {
771                                         return LoggerVerbosity.Detailed <= verbosity;
772                                 } else if (v == LoggerVerbosity.Normal) {
773                                         return LoggerVerbosity.Normal <= verbosity;
774                                 } else if (v == LoggerVerbosity.Minimal) {
775                                         return LoggerVerbosity.Minimal <= verbosity;
776                                 } else if (v == LoggerVerbosity.Quiet) {
777                                         return true;
778                                 } else
779                                         return false;
780                         }
781         
782                         void DumpItems (IEnumerable items)
783                         {
784                                 if (parent.config.NoItemAndPropertyList || !IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic) || items == null)
785                                         return;
786         
787                                 SetColor (eventColor);
788                                 WriteLine (String.Empty);
789                                 WriteLine ("Initial Items:");
790                                 ResetColor ();
791                                 if (items == null)
792                                         return;
793         
794                                 var items_table = new SortedDictionary<string, List<ITaskItem>> ();
795                                 foreach (DictionaryEntry de in items) {
796                                         string key = (string)de.Key;
797                                         if (!items_table.ContainsKey (key))
798                                                 items_table [key] = new List<ITaskItem> ();
799         
800                                         items_table [key].Add ((ITaskItem) de.Value);
801                                 }
802         
803                                 foreach (string name in items_table.Keys) {
804                                         WriteLine (name);
805                                         indent ++;
806                                         foreach (ITaskItem item in items_table [name])
807                                                 WriteLine (item.ItemSpec);
808                                         indent--;
809                                 }
810                         }
811         
812                         string EventsAsString {
813                                 get {
814                                         if (current_events_string == null)
815                                                 current_events_string = EventsToString ();
816                                         return current_events_string;
817                                 }
818                         }
819                         
820                         private bool IsMessageOk (BuildMessageEventArgs bsea)
821                         {
822                                 if (bsea.Importance == MessageImportance.High && IsVerbosityGreaterOrEqual (LoggerVerbosity.Minimal)) {
823                                         return true;
824                                 } else if (bsea.Importance == MessageImportance.Normal && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
825                                         return true;
826                                 } else if (bsea.Importance == MessageImportance.Low && IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed)) {
827                                         return true;
828                                 } else
829                                         return false;
830                         }
831         
832                         void DumpProperties (IEnumerable properties)
833                         {
834                                 if (parent.config.NoItemAndPropertyList || !IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic))
835                                         return;
836         
837                                 SetColor (eventColor);
838                                 WriteLine (String.Empty);
839                                 WriteLine ("Initial Properties:");
840                                 ResetColor ();
841         
842                                 if (properties == null)
843                                         return;
844         
845                                 var dict = new SortedDictionary<string, string> ();
846                                 foreach (DictionaryEntry de in properties)
847                                         dict [(string)de.Key] = (string)de.Value;
848         
849                                 foreach (KeyValuePair<string, string> pair in dict)
850                                         WriteLine (String.Format ("{0} = {1}", pair.Key, pair.Value));
851                         }
852         
853                         private void WriteLine (string message)
854                         {
855                                 if (indent > 0) {
856                                         StringBuilder sb = new StringBuilder ();
857                                         for (int i = 0; i < indent; i++)
858                                                 sb.Append ('\t');
859         
860                                         string indent_str = sb.ToString ();
861         
862                                         foreach (string line in message.Split (new string[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries))
863                                                 parent.writeHandler (indent_str + line);
864                                 } else {
865                                         parent.writeHandler (message);
866                                 }
867                         }
868         
869                         void ExecutePendingEventHandlers ()
870                         {
871                                 foreach (var be in events)
872                                         be.ExecuteStartedHandler ();
873                         }
874         
875                         string EventsToString ()
876                         {
877                                 StringBuilder sb = new StringBuilder ();
878         
879                                 string last_imported_target_file = String.Empty;
880                                 for (int i = 0; i < events.Count; i ++) {
881                                         var args = events [i].EventArgs;
882                                         ProjectStartedEventArgs pargs = args as ProjectStartedEventArgs;
883                                         if (pargs != null) {
884                                                 sb.AppendFormat ("{0} ({1}) ->\n", pargs.ProjectFile,
885                                                                 String.IsNullOrEmpty (pargs.TargetNames) ?
886                                                                         "default targets" :
887                                                                         pargs.TargetNames);
888                                                 last_imported_target_file = String.Empty;
889                                                 continue;
890                                         }
891         
892                                         TargetStartedEventArgs targs = args as TargetStartedEventArgs;
893                                         if (targs != null) {
894                                                 if (targs.TargetFile != targs.ProjectFile && targs.TargetFile != last_imported_target_file)
895                                                         // target from an imported file,
896                                                         // and it hasn't been mentioned as yet
897                                                         sb.AppendFormat ("{0} ", targs.TargetFile);
898         
899                                                 last_imported_target_file = targs.TargetFile;
900                                                 sb.AppendFormat ("({0} target) ->\n", targs.TargetName);
901                                         }
902                                 }
903         
904                                 return sb.ToString ();
905                         }
906         
907                         void DumpPerformanceSummary ()
908                         {
909                                 SetColor (eventColor);
910                                 WriteLine ("Target perfomance summary:");
911                                 ResetColor ();
912         
913                                 foreach (var pi in targetPerfTable.OrderBy (pair => pair.Value.Time))
914                                         WriteLine (String.Format ("{0,10:0.000} ms  {1,-50}  {2,5} calls", pi.Value.Time.TotalMilliseconds, pi.Key, pi.Value.NumberOfCalls));
915         
916                                 WriteLine (String.Empty);
917         
918                                 SetColor (eventColor);
919                                 WriteLine ("Tasks perfomance summary:");
920                                 ResetColor ();
921         
922                                 foreach (var pi in tasksPerfTable.OrderBy (pair => pair.Value.Time))
923                                         WriteLine (String.Format ("{0,10:0.000} ms  {1,-50}  {2,5} calls", pi.Value.Time.TotalMilliseconds, pi.Key, pi.Value.NumberOfCalls));
924         
925                                 WriteLine (String.Empty);
926                         }
927                         
928                         private string FormatErrorEvent (BuildErrorEventArgs args)
929                         {
930                                 // For some reason we get an 1-char empty string as Subcategory somtimes.
931                                 string subprefix = args.Subcategory == null || args.Subcategory == "" || args.Subcategory == " " ? "" : " ";
932                                 string subcat = subprefix == "" ? "" : args.Subcategory;
933                                         
934                                 if (args.LineNumber != 0){
935                                         if (args.ColumnNumber != 0 && !InEmacs) 
936                                                 return String.Format ("{0}({1},{2}): {3}{4}error {5}: {6}",
937                                                                       args.File, args.LineNumber, args.ColumnNumber,
938                                                                       subprefix, subcat, args.Code, args.Message);
939         
940                                         return String.Format ("{0}({1}): {2}{3}error {4}: {5}",
941                                                               args.File, args.LineNumber,
942                                                               subprefix, subcat, args.Code, args.Message);
943                                 } else {
944                                         return String.Format ("{0}: {1}{2}error {3}: {4}", args.File, subprefix, subcat, args.Code,
945                                                 args.Message);
946                                 }
947                         }
948         
949                         static bool InEmacs = Environment.GetEnvironmentVariable ("EMACS") == "t";
950         
951                         private string FormatWarningEvent (BuildWarningEventArgs args)
952                         {
953                                 // For some reason we get an 1-char empty string as Subcategory somtimes.
954                                 string subprefix = args.Subcategory == null || args.Subcategory == "" || args.Subcategory == " " ? "" : " ";
955                                 string subcat = subprefix == "" ? "" : args.Subcategory;
956         
957                                 // FIXME: show more complicated args
958                                 if (args.LineNumber != 0){
959         
960                                         if (args.ColumnNumber != 0 && !InEmacs) {
961                                                 return String.Format ("{0}({1},{2}): {3}{4}warning {5}: {6}",
962                                                                       args.File, args.LineNumber, args.ColumnNumber,
963                                                                       subprefix, subcat, args.Code, args.Message);
964                                         }
965                                         return String.Format ("{0}({1}): {2}{3}warning {4}: {5}",
966                                                               args.File, args.LineNumber,
967                                                               subprefix, subcat, args.Code, args.Message);
968                                 } else {
969                                         return String.Format ("{0}: {1} warning {2}: {3}", args.File, args.Subcategory, args.Code,
970                                                 args.Message);
971                                 }
972                         }
973         
974                         void SetColor (ConsoleColor color)
975                         {
976                                 if (parent.use_colors)
977                                         parent.colorSet (color);
978                         }
979         
980                         void ResetColor ()
981                         {
982                                 if (parent.use_colors)
983                                         parent.colorReset ();
984                         }
985                         
986                         private void WriteLineWithoutIndent (string message)
987                         {
988                                 parent.writeHandler (message);
989                         }
990                 }
991         }
992
993         class BuildEvent {
994                 public object Sender; 
995                 public BuildStatusEventArgs EventArgs;
996                 public bool StartHandlerHasExecuted;
997                 public ConsoleLogger ConsoleLogger;
998
999                 public void ExecuteStartedHandler ()
1000                 {
1001                         if (StartHandlerHasExecuted)
1002                                 return;
1003
1004                         if (EventArgs is ProjectStartedEventArgs)
1005                                 ConsoleLogger.ProjectStartedHandler (Sender, (ProjectStartedEventArgs)EventArgs);
1006                         else if (EventArgs is TargetStartedEventArgs)
1007                                 ConsoleLogger.TargetStartedHandler (Sender, (TargetStartedEventArgs)EventArgs);
1008                         else if (EventArgs is TaskStartedEventArgs)
1009                                 ConsoleLogger.TaskStartedHandler (Sender, (TaskStartedEventArgs)EventArgs);
1010                         else if (!(EventArgs is BuildStartedEventArgs))
1011                                 throw new InvalidOperationException ("Unexpected event on the stack, type: " + EventArgs.GetType ());
1012
1013                         StartHandlerHasExecuted = true;
1014                 }
1015
1016                 public void ExecuteFinishedHandler (BuildStatusEventArgs finished_args)
1017                 {
1018                         if (!StartHandlerHasExecuted)
1019                                 return;
1020
1021                         if (EventArgs is ProjectStartedEventArgs)
1022                                 ConsoleLogger.ProjectFinishedHandler (Sender, finished_args as ProjectFinishedEventArgs);
1023                         else if (EventArgs is TargetStartedEventArgs)
1024                                 ConsoleLogger.TargetFinishedHandler (Sender, finished_args as TargetFinishedEventArgs);
1025                         else if (EventArgs is TaskStartedEventArgs)
1026                                 ConsoleLogger.TaskFinishedHandler (Sender, finished_args as TaskFinishedEventArgs);
1027                         else if (!(EventArgs is BuildStartedEventArgs))
1028                                 throw new InvalidOperationException ("Unexpected event on the stack, type: " + EventArgs.GetType ());
1029                 }
1030         }
1031
1032         class PerfInfo {
1033                 public TimeSpan Time;
1034                 public int NumberOfCalls;
1035         }
1036 }