2 // ConsoleLogger.cs: Outputs to the console
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Runtime.InteropServices;
32 using System.Collections;
33 using System.Collections.Generic;
35 using System.Security;
37 using Microsoft.Build.Framework;
39 namespace Microsoft.Build.BuildEngine {
40 public class ConsoleLogger : ILogger {
44 LoggerVerbosity verbosity;
45 WriteHandler writeHandler;
49 bool performanceSummary;
51 bool skipProjectStartedText;
52 List<string> errors, warnings;
54 ConsoleColor errorColor, warningColor, eventColor, messageColor, highMessageColor;
56 ColorResetter colorReset;
57 bool no_message_color, use_colors;
58 bool noItemAndPropertyList;
60 List<BuildStatusEventArgs> events;
61 Dictionary<string, List<string>> errorsTable;
62 Dictionary<string, List<string>> warningsTable;
63 string current_events_string;
65 public ConsoleLogger ()
66 : this (LoggerVerbosity.Normal, null, null, null)
70 public ConsoleLogger (LoggerVerbosity verbosity)
71 : this (LoggerVerbosity.Normal, null, null, null)
75 public ConsoleLogger (LoggerVerbosity verbosity,
78 ColorResetter colorReset)
80 this.verbosity = verbosity;
83 this.warningCount = 0;
85 this.writeHandler += new WriteHandler (WriteHandlerFunction);
87 this.writeHandler += write;
88 this.performanceSummary = false;
89 this.showSummary = true;
90 this.skipProjectStartedText = false;
91 errors = new List<string> ();
92 warnings = new List<string> ();
93 this.colorSet = colorSet;
94 this.colorReset = colorReset;
96 events = new List<BuildStatusEventArgs> ();
97 errorsTable = new Dictionary<string, List<string>> ();
98 warningsTable = new Dictionary<string, List<string>> ();
101 errorColor = ConsoleColor.DarkRed;
102 warningColor = ConsoleColor.DarkYellow;
103 eventColor = ConsoleColor.DarkCyan;
104 messageColor = ConsoleColor.DarkGray;
105 highMessageColor = ConsoleColor.White;
107 // if message color is not set via the env var,
108 // then don't use any color for it.
109 no_message_color = true;
112 if (colorSet == null || colorReset == null)
116 string config = Environment.GetEnvironmentVariable ("XBUILD_COLORS");
117 if (config == null) {
122 if (config == "disable")
126 string [] pairs = config.Split (new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
127 foreach (string pair in pairs) {
128 string [] parts = pair.Split (new char[] {'='}, StringSplitOptions.RemoveEmptyEntries);
129 if (parts.Length != 2)
132 if (parts [0] == "errors")
133 TryParseConsoleColor (parts [1], ref errorColor);
134 else if (parts [0] == "warnings")
135 TryParseConsoleColor (parts [1], ref warningColor);
136 else if (parts [0] == "events")
137 TryParseConsoleColor (parts [1], ref eventColor);
138 else if (parts [0] == "messages") {
139 if (TryParseConsoleColor (parts [1], ref messageColor)) {
140 highMessageColor = GetBrightColorFor (messageColor);
141 no_message_color = false;
147 bool TryParseConsoleColor (string color_str, ref ConsoleColor color)
149 switch (color_str.ToLower ()) {
150 case "black": color = ConsoleColor.Black; break;
152 case "blue": color = ConsoleColor.DarkBlue; break;
153 case "green": color = ConsoleColor.DarkGreen; break;
154 case "cyan": color = ConsoleColor.DarkCyan; break;
155 case "red": color = ConsoleColor.DarkRed; break;
156 case "magenta": color = ConsoleColor.DarkMagenta; break;
157 case "yellow": color = ConsoleColor.DarkYellow; break;
158 case "grey": color = ConsoleColor.DarkGray; break;
160 case "brightgrey": color = ConsoleColor.Gray; break;
161 case "brightblue": color = ConsoleColor.Blue; break;
162 case "brightgreen": color = ConsoleColor.Green; break;
163 case "brightcyan": color = ConsoleColor.Cyan; break;
164 case "brightred": color = ConsoleColor.Red; break;
165 case "brightmagenta": color = ConsoleColor.Magenta; break;
166 case "brightyellow": color = ConsoleColor.Yellow; break;
169 case "brightwhite": color = ConsoleColor.White; break;
170 default: return false;
176 ConsoleColor GetBrightColorFor (ConsoleColor color)
179 case ConsoleColor.DarkBlue: return ConsoleColor.Blue;
180 case ConsoleColor.DarkGreen: return ConsoleColor.Green;
181 case ConsoleColor.DarkCyan: return ConsoleColor.Cyan;
182 case ConsoleColor.DarkRed: return ConsoleColor.Red;
183 case ConsoleColor.DarkMagenta: return ConsoleColor.Magenta;
184 case ConsoleColor.DarkYellow: return ConsoleColor.Yellow;
185 case ConsoleColor.DarkGray: return ConsoleColor.Gray;
186 case ConsoleColor.Gray: return ConsoleColor.White;
188 default: return color;
192 public void ApplyParameter (string parameterName,
193 string parameterValue)
195 // FIXME: what we should do here? in msbuild it isn't
196 // changing "parameters" property
199 public virtual void Initialize (IEventSource eventSource)
201 eventSource.BuildStarted += new BuildStartedEventHandler (BuildStartedHandler);
202 eventSource.BuildFinished += new BuildFinishedEventHandler (BuildFinishedHandler);
203 eventSource.ProjectStarted += new ProjectStartedEventHandler (ProjectStartedHandler);
204 eventSource.ProjectFinished += new ProjectFinishedEventHandler (ProjectFinishedHandler);
205 eventSource.TargetStarted += new TargetStartedEventHandler (TargetStartedHandler);
206 eventSource.TargetFinished += new TargetFinishedEventHandler (TargetFinishedHandler);
207 eventSource.TaskStarted += new TaskStartedEventHandler (TaskStartedHandler);
208 eventSource.TaskFinished += new TaskFinishedEventHandler (TaskFinishedHandler);
209 eventSource.MessageRaised += new BuildMessageEventHandler (MessageHandler);
210 eventSource.WarningRaised += new BuildWarningEventHandler (WarningHandler);
211 eventSource.ErrorRaised += new BuildErrorEventHandler (ErrorHandler);
214 public void BuildStartedHandler (object sender, BuildStartedEventArgs args)
216 WriteLine (String.Empty);
217 WriteLine (String.Format ("Build started {0}.", args.Timestamp));
218 WriteLine ("__________________________________________________");
219 buildStart = args.Timestamp;
224 public void BuildFinishedHandler (object sender, BuildFinishedEventArgs args)
226 if (args.Succeeded == true && !projectFailed) {
227 WriteLine ("Build succeeded.");
229 WriteLine ("Build FAILED.");
231 if (performanceSummary == true) {
234 if (warnings.Count > 0) {
235 WriteLine (Environment.NewLine + "Warnings:");
236 SetColor (warningColor);
238 WriteLine (String.Empty);
239 foreach (KeyValuePair<string, List<string>> pair in warningsTable) {
240 if (!String.IsNullOrEmpty (pair.Key))
241 WriteLine (pair.Key);
243 string indent_str = String.IsNullOrEmpty (pair.Key) ? String.Empty : "\t";
244 foreach (string msg in pair.Value)
245 WriteLine (String.Format ("{0}{1}", indent_str, msg));
247 WriteLine (String.Empty);
253 if (errors.Count > 0) {
254 WriteLine ("Errors:");
255 SetColor (errorColor);
257 WriteLine (String.Empty);
258 foreach (KeyValuePair<string, List<string>> pair in errorsTable) {
259 if (!String.IsNullOrEmpty (pair.Key))
260 WriteLine (pair.Key);
262 string indent_str = String.IsNullOrEmpty (pair.Key) ? String.Empty : "\t";
263 foreach (string msg in pair.Value)
264 WriteLine (String.Format ("{0}{1}", indent_str, msg));
266 WriteLine (String.Empty);
271 if (showSummary == true){
272 TimeSpan timeElapsed = args.Timestamp - buildStart;
273 WriteLine (String.Format ("\t {0} Warning(s)", warningCount));
274 WriteLine (String.Format ("\t {0} Error(s)", errorCount));
275 WriteLine (String.Empty);
276 WriteLine (String.Format ("Time Elapsed {0}", timeElapsed));
281 public void ProjectStartedHandler (object sender, ProjectStartedEventArgs args)
283 SetColor (eventColor);
284 WriteLine (String.Format ("Project \"{0}\" ({1} target(s)):", args.ProjectFile,
285 String.IsNullOrEmpty (args.TargetNames) ? "default" : args.TargetNames));
287 WriteLine (String.Empty);
288 DumpProperties (args.Properties);
289 DumpItems (args.Items);
293 public void ProjectFinishedHandler (object sender, ProjectFinishedEventArgs args)
295 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
298 SetColor (eventColor);
299 WriteLine (String.Format ("Done building project \"{0}\".{1}", args.ProjectFile,
300 args.Succeeded ? String.Empty : "-- FAILED"));
302 WriteLine (String.Empty);
305 // no project has failed yet, so update the flag
306 projectFailed = !args.Succeeded;
311 public void TargetStartedHandler (object sender, TargetStartedEventArgs args)
314 SetColor (eventColor);
315 WriteLine (String.Format ("Target {0}:",args.TargetName));
320 public void TargetFinishedHandler (object sender, TargetFinishedEventArgs args)
322 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed) || !args.Succeeded) {
323 SetColor (eventColor);
324 WriteLine (String.Format ("Done building target \"{0}\" in project \"{1}\".{2}",
325 args.TargetName, args.ProjectFile,
326 args.Succeeded ? String.Empty : "-- FAILED"));
331 WriteLine (String.Empty);
335 public void TaskStartedHandler (object sender, TaskStartedEventArgs args)
337 if (this.verbosity == LoggerVerbosity.Detailed) {
338 SetColor (eventColor);
339 WriteLine (String.Format ("Task \"{0}\"",args.TaskName));
346 public void TaskFinishedHandler (object sender, TaskFinishedEventArgs args)
349 if (this.verbosity == LoggerVerbosity.Detailed || !args.Succeeded) {
350 SetColor (eventColor);
352 WriteLine (String.Format ("Done executing task \"{0}\"", args.TaskName));
354 WriteLine (String.Format ("Task \"{0}\" execution -- FAILED", args.TaskName));
360 public void MessageHandler (object sender, BuildMessageEventArgs args)
362 if (IsMessageOk (args)) {
363 if (no_message_color) {
364 WriteLine (args.Message);
366 SetColor (args.Importance == MessageImportance.High ? highMessageColor : messageColor);
367 WriteLine (args.Message);
373 public void WarningHandler (object sender, BuildWarningEventArgs args)
375 string msg = FormatWarningEvent (args);
376 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
377 SetColor (warningColor);
378 WriteLineWithoutIndent (msg);
383 List<string> list = null;
384 if (!warningsTable.TryGetValue (EventsAsString, out list))
385 warningsTable [EventsAsString] = list = new List<string> ();
391 public void ErrorHandler (object sender, BuildErrorEventArgs args)
393 string msg = FormatErrorEvent (args);
394 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Minimal)) {
395 SetColor (errorColor);
396 WriteLineWithoutIndent (msg);
401 List<string> list = null;
402 if (!errorsTable.TryGetValue (EventsAsString, out list))
403 errorsTable [EventsAsString] = list = new List<string> ();
409 public void CustomEventHandler (object sender, CustomBuildEventArgs args)
413 private void WriteLine (string message)
416 StringBuilder sb = new StringBuilder ();
417 for (int i = 0; i < indent; i++)
421 writeHandler (sb.ToString ());
423 writeHandler (message);
427 void PushEvent (BuildStatusEventArgs args)
430 current_events_string = null;
435 events.RemoveAt (events.Count - 1);
436 current_events_string = null;
439 string EventsToString ()
441 StringBuilder sb = new StringBuilder ();
443 string last_imported_target_file = String.Empty;
444 for (int i = 0; i < events.Count; i ++) {
445 BuildStatusEventArgs args = events [i];
446 ProjectStartedEventArgs pargs = args as ProjectStartedEventArgs;
448 sb.AppendFormat ("{0} ({1}) ->\n", pargs.ProjectFile,
449 String.IsNullOrEmpty (pargs.TargetNames) ?
452 last_imported_target_file = String.Empty;
456 TargetStartedEventArgs targs = args as TargetStartedEventArgs;
458 if (targs.TargetFile != targs.ProjectFile && targs.TargetFile != last_imported_target_file)
459 // target from an imported file,
460 // and it hasn't been mentioned as yet
461 sb.AppendFormat ("{0} ", targs.TargetFile);
463 last_imported_target_file = targs.TargetFile;
464 sb.AppendFormat ("({0} target) ->\n", targs.TargetName);
468 return sb.ToString ();
471 private void WriteLineWithoutIndent (string message)
473 writeHandler (message);
476 private void WriteHandlerFunction (string message)
478 Console.WriteLine (message);
481 void SetColor (ConsoleColor color)
493 private void ParseParameters ()
495 string[] splittedParameters = parameters.Split (';');
496 foreach (string s in splittedParameters ) {
498 case "PerformanceSummary":
499 this.performanceSummary = true;
502 this.showSummary = false;
504 case "NoItemAndPropertyList":
505 this.noItemAndPropertyList = true;
508 throw new ArgumentException ("Invalid parameter : " + s);
513 public virtual void Shutdown ()
517 static bool InEmacs = Environment.GetEnvironmentVariable ("EMACS") == "t";
519 private string FormatErrorEvent (BuildErrorEventArgs args)
521 // For some reason we get an 1-char empty string as Subcategory somtimes.
522 string subprefix = args.Subcategory == null || args.Subcategory == "" || args.Subcategory == " " ? "" : " ";
523 string subcat = subprefix == "" ? "" : args.Subcategory;
525 if (args.LineNumber != 0){
526 if (args.ColumnNumber != 0 && !InEmacs)
527 return String.Format ("{0}({1},{2}): {3}{4}error {5}: {6}",
528 args.File, args.LineNumber, args.ColumnNumber,
529 subprefix, subcat, args.Code, args.Message);
531 return String.Format ("{0}({1}): {2}{3}error {4}: {5}",
532 args.File, args.LineNumber,
533 subprefix, subcat, args.Code, args.Message);
535 return String.Format ("{0}: {1}{2}error {3}: {4}", args.File, subprefix, subcat, args.Code,
540 private string FormatWarningEvent (BuildWarningEventArgs args)
542 // For some reason we get an 1-char empty string as Subcategory somtimes.
543 string subprefix = args.Subcategory == null || args.Subcategory == "" || args.Subcategory == " " ? "" : " ";
544 string subcat = subprefix == "" ? "" : args.Subcategory;
546 // FIXME: show more complicated args
547 if (args.LineNumber != 0){
549 if (args.ColumnNumber != 0 && !InEmacs) {
550 return String.Format ("{0}({1},{2}): {3}{4}warning {5}: {6}",
551 args.File, args.LineNumber, args.ColumnNumber,
552 subprefix, subcat, args.Code, args.Message);
554 return String.Format ("{0}({1}): {2}{3}warning {4}: {5}",
555 args.File, args.LineNumber,
556 subprefix, subcat, args.Code, args.Message);
558 return String.Format ("{0}: {1} warning {2}: {3}", args.File, args.Subcategory, args.Code,
563 private bool IsMessageOk (BuildMessageEventArgs bsea)
565 if (bsea.Importance == MessageImportance.High && IsVerbosityGreaterOrEqual (LoggerVerbosity.Minimal)) {
567 } else if (bsea.Importance == MessageImportance.Normal && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
569 } else if (bsea.Importance == MessageImportance.Low && IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed)) {
575 private bool IsVerbosityGreaterOrEqual (LoggerVerbosity v)
577 if (v == LoggerVerbosity.Diagnostic) {
578 return LoggerVerbosity.Diagnostic <= verbosity;
579 } else if (v == LoggerVerbosity.Detailed) {
580 return LoggerVerbosity.Detailed <= verbosity;
581 } else if (v == LoggerVerbosity.Normal) {
582 return LoggerVerbosity.Normal <= verbosity;
583 } else if (v == LoggerVerbosity.Minimal) {
584 return LoggerVerbosity.Minimal <= verbosity;
585 } else if (v == LoggerVerbosity.Quiet) {
591 void DumpProperties (IEnumerable properties)
593 if (noItemAndPropertyList || !IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic))
596 SetColor (eventColor);
598 WriteLine ("Initial Properties:");
601 if (properties == null)
604 var dict = new SortedDictionary<string, string> ();
605 foreach (DictionaryEntry de in properties)
606 dict [(string)de.Key] = (string)de.Value;
608 foreach (KeyValuePair<string, string> pair in dict)
609 WriteLine (String.Format ("{0} = {1}", pair.Key, pair.Value));
613 void DumpItems (IEnumerable items)
615 if (noItemAndPropertyList || !IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic) || items == null)
618 SetColor (eventColor);
620 WriteLine ("Initial Items:");
625 var items_table = new SortedDictionary<string, List<ITaskItem>> ();
626 foreach (DictionaryEntry de in items) {
627 string key = (string)de.Key;
628 if (!items_table.ContainsKey (key))
629 items_table [key] = new List<ITaskItem> ();
631 items_table [key].Add ((ITaskItem) de.Value);
634 foreach (string name in items_table.Keys) {
637 foreach (ITaskItem item in items_table [name])
638 WriteLine (item.ItemSpec);
644 public string Parameters {
650 throw new ArgumentNullException ();
652 if (parameters != String.Empty)
657 string EventsAsString {
659 if (current_events_string == null)
660 current_events_string = EventsToString ();
661 return current_events_string;
665 public bool ShowSummary {
666 get { return showSummary; }
667 set { showSummary = value; }
670 public bool SkipProjectStartedText {
671 get { return skipProjectStartedText; }
672 set { skipProjectStartedText = value; }
675 public LoggerVerbosity Verbosity {
676 get { return verbosity; }
677 set { verbosity = value; }
680 protected WriteHandler WriteHandler {
681 get { return writeHandler; }
682 set { writeHandler = value; }