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;
59 List<BuildStatusEventArgs> events;
60 Dictionary<string, List<string>> errorsTable;
61 Dictionary<string, List<string>> warningsTable;
62 string current_events_string;
64 public ConsoleLogger ()
65 : this (LoggerVerbosity.Normal, null, null, null)
69 public ConsoleLogger (LoggerVerbosity verbosity)
70 : this (LoggerVerbosity.Normal, null, null, null)
74 public ConsoleLogger (LoggerVerbosity verbosity,
77 ColorResetter colorReset)
79 this.verbosity = verbosity;
82 this.warningCount = 0;
84 this.writeHandler += new WriteHandler (WriteHandlerFunction);
86 this.writeHandler += write;
87 this.performanceSummary = false;
88 this.showSummary = true;
89 this.skipProjectStartedText = false;
90 errors = new List<string> ();
91 warnings = new List<string> ();
92 this.colorSet = colorSet;
93 this.colorReset = colorReset;
95 events = new List<BuildStatusEventArgs> ();
96 errorsTable = new Dictionary<string, List<string>> ();
97 warningsTable = new Dictionary<string, List<string>> ();
100 errorColor = ConsoleColor.DarkRed;
101 warningColor = ConsoleColor.DarkYellow;
102 eventColor = ConsoleColor.DarkCyan;
103 messageColor = ConsoleColor.DarkGray;
104 highMessageColor = ConsoleColor.White;
106 // if message color is not set via the env var,
107 // then don't use any color for it.
108 no_message_color = true;
111 if (colorSet == null || colorReset == null)
115 string config = Environment.GetEnvironmentVariable ("XBUILD_COLORS");
116 if (config == null) {
121 if (config == "disable")
125 string [] pairs = config.Split (new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
126 foreach (string pair in pairs) {
127 string [] parts = pair.Split (new char[] {'='}, StringSplitOptions.RemoveEmptyEntries);
128 if (parts.Length != 2)
131 if (parts [0] == "errors")
132 TryParseConsoleColor (parts [1], ref errorColor);
133 else if (parts [0] == "warnings")
134 TryParseConsoleColor (parts [1], ref warningColor);
135 else if (parts [0] == "events")
136 TryParseConsoleColor (parts [1], ref eventColor);
137 else if (parts [0] == "messages") {
138 if (TryParseConsoleColor (parts [1], ref messageColor)) {
139 highMessageColor = GetBrightColorFor (messageColor);
140 no_message_color = false;
146 bool TryParseConsoleColor (string color_str, ref ConsoleColor color)
148 switch (color_str.ToLower ()) {
149 case "black": color = ConsoleColor.Black; break;
151 case "blue": color = ConsoleColor.DarkBlue; break;
152 case "green": color = ConsoleColor.DarkGreen; break;
153 case "cyan": color = ConsoleColor.DarkCyan; break;
154 case "red": color = ConsoleColor.DarkRed; break;
155 case "magenta": color = ConsoleColor.DarkMagenta; break;
156 case "yellow": color = ConsoleColor.DarkYellow; break;
157 case "grey": color = ConsoleColor.DarkGray; break;
159 case "brightgrey": color = ConsoleColor.Gray; break;
160 case "brightblue": color = ConsoleColor.Blue; break;
161 case "brightgreen": color = ConsoleColor.Green; break;
162 case "brightcyan": color = ConsoleColor.Cyan; break;
163 case "brightred": color = ConsoleColor.Red; break;
164 case "brightmagenta": color = ConsoleColor.Magenta; break;
165 case "brightyellow": color = ConsoleColor.Yellow; break;
168 case "brightwhite": color = ConsoleColor.White; break;
169 default: return false;
175 ConsoleColor GetBrightColorFor (ConsoleColor color)
178 case ConsoleColor.DarkBlue: return ConsoleColor.Blue;
179 case ConsoleColor.DarkGreen: return ConsoleColor.Green;
180 case ConsoleColor.DarkCyan: return ConsoleColor.Cyan;
181 case ConsoleColor.DarkRed: return ConsoleColor.Red;
182 case ConsoleColor.DarkMagenta: return ConsoleColor.Magenta;
183 case ConsoleColor.DarkYellow: return ConsoleColor.Yellow;
184 case ConsoleColor.DarkGray: return ConsoleColor.Gray;
185 case ConsoleColor.Gray: return ConsoleColor.White;
187 default: return color;
191 public void ApplyParameter (string parameterName,
192 string parameterValue)
194 // FIXME: what we should do here? in msbuild it isn't
195 // changing "parameters" property
198 public virtual void Initialize (IEventSource eventSource)
200 eventSource.BuildStarted += new BuildStartedEventHandler (BuildStartedHandler);
201 eventSource.BuildFinished += new BuildFinishedEventHandler (BuildFinishedHandler);
202 eventSource.ProjectStarted += new ProjectStartedEventHandler (ProjectStartedHandler);
203 eventSource.ProjectFinished += new ProjectFinishedEventHandler (ProjectFinishedHandler);
204 eventSource.TargetStarted += new TargetStartedEventHandler (TargetStartedHandler);
205 eventSource.TargetFinished += new TargetFinishedEventHandler (TargetFinishedHandler);
206 eventSource.TaskStarted += new TaskStartedEventHandler (TaskStartedHandler);
207 eventSource.TaskFinished += new TaskFinishedEventHandler (TaskFinishedHandler);
208 eventSource.MessageRaised += new BuildMessageEventHandler (MessageHandler);
209 eventSource.WarningRaised += new BuildWarningEventHandler (WarningHandler);
210 eventSource.ErrorRaised += new BuildErrorEventHandler (ErrorHandler);
213 public void BuildStartedHandler (object sender, BuildStartedEventArgs args)
215 WriteLine (String.Empty);
216 WriteLine (String.Format ("Build started {0}.", args.Timestamp));
217 WriteLine ("__________________________________________________");
218 buildStart = args.Timestamp;
223 public void BuildFinishedHandler (object sender, BuildFinishedEventArgs args)
225 if (args.Succeeded == true && !projectFailed) {
226 WriteLine ("Build succeeded.");
228 WriteLine ("Build FAILED.");
230 if (performanceSummary == true) {
233 if (warnings.Count > 0) {
234 WriteLine (Environment.NewLine + "Warnings:");
235 SetColor (warningColor);
237 WriteLine (String.Empty);
238 foreach (KeyValuePair<string, List<string>> pair in warningsTable) {
239 if (!String.IsNullOrEmpty (pair.Key))
240 WriteLine (pair.Key);
242 string indent_str = String.IsNullOrEmpty (pair.Key) ? String.Empty : "\t";
243 foreach (string msg in pair.Value)
244 WriteLine (String.Format ("{0}{1}", indent_str, msg));
246 WriteLine (String.Empty);
252 if (errors.Count > 0) {
253 WriteLine ("Errors:");
254 SetColor (errorColor);
256 WriteLine (String.Empty);
257 foreach (KeyValuePair<string, List<string>> pair in errorsTable) {
258 if (!String.IsNullOrEmpty (pair.Key))
259 WriteLine (pair.Key);
261 string indent_str = String.IsNullOrEmpty (pair.Key) ? String.Empty : "\t";
262 foreach (string msg in pair.Value)
263 WriteLine (String.Format ("{0}{1}", indent_str, msg));
265 WriteLine (String.Empty);
270 if (showSummary == true){
271 TimeSpan timeElapsed = args.Timestamp - buildStart;
272 WriteLine (String.Format ("\t {0} Warning(s)", warningCount));
273 WriteLine (String.Format ("\t {0} Error(s)", errorCount));
274 WriteLine (String.Empty);
275 WriteLine (String.Format ("Time Elapsed {0}", timeElapsed));
280 public void ProjectStartedHandler (object sender, ProjectStartedEventArgs args)
282 SetColor (eventColor);
283 WriteLine (String.Format ("Project \"{0}\" ({1} target(s)):", args.ProjectFile,
284 String.IsNullOrEmpty (args.TargetNames) ? "default" : args.TargetNames));
286 WriteLine (String.Empty);
287 DumpProperties (args.Properties);
288 DumpItems (args.Items);
292 public void ProjectFinishedHandler (object sender, ProjectFinishedEventArgs args)
294 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
297 SetColor (eventColor);
298 WriteLine (String.Format ("Done building project \"{0}\".{1}", args.ProjectFile,
299 args.Succeeded ? String.Empty : "-- FAILED"));
301 WriteLine (String.Empty);
304 // no project has failed yet, so update the flag
305 projectFailed = !args.Succeeded;
310 public void TargetStartedHandler (object sender, TargetStartedEventArgs args)
313 SetColor (eventColor);
314 WriteLine (String.Format ("Target {0}:",args.TargetName));
319 public void TargetFinishedHandler (object sender, TargetFinishedEventArgs args)
321 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed) || !args.Succeeded) {
322 SetColor (eventColor);
323 WriteLine (String.Format ("Done building target \"{0}\" in project \"{1}\".{2}",
324 args.TargetName, args.ProjectFile,
325 args.Succeeded ? String.Empty : "-- FAILED"));
330 WriteLine (String.Empty);
334 public void TaskStartedHandler (object sender, TaskStartedEventArgs args)
336 if (this.verbosity == LoggerVerbosity.Detailed) {
337 SetColor (eventColor);
338 WriteLine (String.Format ("Task \"{0}\"",args.TaskName));
345 public void TaskFinishedHandler (object sender, TaskFinishedEventArgs args)
348 if (this.verbosity == LoggerVerbosity.Detailed || !args.Succeeded) {
349 SetColor (eventColor);
351 WriteLine (String.Format ("Done executing task \"{0}\"", args.TaskName));
353 WriteLine (String.Format ("Task \"{0}\" execution -- FAILED", args.TaskName));
359 public void MessageHandler (object sender, BuildMessageEventArgs args)
361 if (IsMessageOk (args)) {
362 if (no_message_color) {
363 WriteLine (args.Message);
365 SetColor (args.Importance == MessageImportance.High ? highMessageColor : messageColor);
366 WriteLine (args.Message);
372 public void WarningHandler (object sender, BuildWarningEventArgs args)
374 string msg = FormatWarningEvent (args);
375 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
376 SetColor (warningColor);
377 WriteLineWithoutIndent (msg);
382 List<string> list = null;
383 if (!warningsTable.TryGetValue (EventsAsString, out list))
384 warningsTable [EventsAsString] = list = new List<string> ();
390 public void ErrorHandler (object sender, BuildErrorEventArgs args)
392 string msg = FormatErrorEvent (args);
393 if (IsVerbosityGreaterOrEqual (LoggerVerbosity.Minimal)) {
394 SetColor (errorColor);
395 WriteLineWithoutIndent (msg);
400 List<string> list = null;
401 if (!errorsTable.TryGetValue (EventsAsString, out list))
402 errorsTable [EventsAsString] = list = new List<string> ();
408 public void CustomEventHandler (object sender, CustomBuildEventArgs args)
412 private void WriteLine (string message)
415 StringBuilder sb = new StringBuilder ();
416 for (int i = 0; i < indent; i++)
420 writeHandler (sb.ToString ());
422 writeHandler (message);
426 void PushEvent (BuildStatusEventArgs args)
429 current_events_string = null;
434 events.RemoveAt (events.Count - 1);
435 current_events_string = null;
438 string EventsToString ()
440 StringBuilder sb = new StringBuilder ();
442 string last_imported_target_file = String.Empty;
443 for (int i = 0; i < events.Count; i ++) {
444 BuildStatusEventArgs args = events [i];
445 ProjectStartedEventArgs pargs = args as ProjectStartedEventArgs;
447 sb.AppendFormat ("{0} ({1}) ->\n", pargs.ProjectFile,
448 String.IsNullOrEmpty (pargs.TargetNames) ?
451 last_imported_target_file = String.Empty;
455 TargetStartedEventArgs targs = args as TargetStartedEventArgs;
457 if (targs.TargetFile != targs.ProjectFile && targs.TargetFile != last_imported_target_file)
458 // target from an imported file,
459 // and it hasn't been mentioned as yet
460 sb.AppendFormat ("{0} ", targs.TargetFile);
462 last_imported_target_file = targs.TargetFile;
463 sb.AppendFormat ("({0} target) ->\n", targs.TargetName);
467 return sb.ToString ();
470 private void WriteLineWithoutIndent (string message)
472 writeHandler (message);
475 private void WriteHandlerFunction (string message)
477 Console.WriteLine (message);
480 void SetColor (ConsoleColor color)
492 private void ParseParameters ()
494 string[] splittedParameters = parameters.Split (';');
495 foreach (string s in splittedParameters ) {
497 case "PerformanceSummary":
498 this.performanceSummary = true;
501 this.showSummary = false;
504 throw new ArgumentException ("Invalid parameter.");
509 public virtual void Shutdown ()
513 static bool InEmacs = Environment.GetEnvironmentVariable ("EMACS") == "t";
515 private string FormatErrorEvent (BuildErrorEventArgs args)
517 // For some reason we get an 1-char empty string as Subcategory somtimes.
518 string subprefix = args.Subcategory == null || args.Subcategory == "" || args.Subcategory == " " ? "" : " ";
519 string subcat = subprefix == "" ? "" : args.Subcategory;
521 if (args.LineNumber != 0){
522 if (args.ColumnNumber != 0 && !InEmacs)
523 return String.Format ("{0}({1},{2}): {3}{4}error {5}: {6}",
524 args.File, args.LineNumber, args.ColumnNumber,
525 subprefix, subcat, args.Code, args.Message);
527 return String.Format ("{0}({1}): {2}{3}error {4}: {5}",
528 args.File, args.LineNumber,
529 subprefix, subcat, args.Code, args.Message);
531 return String.Format ("{0}: {1}{2}error {3}: {4}", args.File, subprefix, subcat, args.Code,
536 private string FormatWarningEvent (BuildWarningEventArgs args)
538 // For some reason we get an 1-char empty string as Subcategory somtimes.
539 string subprefix = args.Subcategory == null || args.Subcategory == "" || args.Subcategory == " " ? "" : " ";
540 string subcat = subprefix == "" ? "" : args.Subcategory;
542 // FIXME: show more complicated args
543 if (args.LineNumber != 0){
545 if (args.ColumnNumber != 0 && !InEmacs) {
546 return String.Format ("{0}({1},{2}): {3}{4}warning {5}: {6}",
547 args.File, args.LineNumber, args.ColumnNumber,
548 subprefix, subcat, args.Code, args.Message);
550 return String.Format ("{0}({1}): {2}{3}warning {4}: {5}",
551 args.File, args.LineNumber,
552 subprefix, subcat, args.Code, args.Message);
554 return String.Format ("{0}: {1} warning {2}: {3}", args.File, args.Subcategory, args.Code,
559 private bool IsMessageOk (BuildMessageEventArgs bsea)
561 if (bsea.Importance == MessageImportance.High && IsVerbosityGreaterOrEqual (LoggerVerbosity.Minimal)) {
563 } else if (bsea.Importance == MessageImportance.Normal && IsVerbosityGreaterOrEqual (LoggerVerbosity.Normal)) {
565 } else if (bsea.Importance == MessageImportance.Low && IsVerbosityGreaterOrEqual (LoggerVerbosity.Detailed)) {
571 private bool IsVerbosityGreaterOrEqual (LoggerVerbosity v)
573 if (v == LoggerVerbosity.Diagnostic) {
574 return LoggerVerbosity.Diagnostic <= verbosity;
575 } else if (v == LoggerVerbosity.Detailed) {
576 return LoggerVerbosity.Detailed <= verbosity;
577 } else if (v == LoggerVerbosity.Normal) {
578 return LoggerVerbosity.Normal <= verbosity;
579 } else if (v == LoggerVerbosity.Minimal) {
580 return LoggerVerbosity.Minimal <= verbosity;
581 } else if (v == LoggerVerbosity.Quiet) {
587 void DumpProperties (IEnumerable properties)
589 if (!IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic))
592 SetColor (eventColor);
594 WriteLine ("Initial Properties:");
597 if (properties == null)
600 var dict = new SortedDictionary<string, string> ();
601 foreach (DictionaryEntry de in properties)
602 dict [(string)de.Key] = (string)de.Value;
604 foreach (KeyValuePair<string, string> pair in dict)
605 WriteLine (String.Format ("{0} = {1}", pair.Key, pair.Value));
609 void DumpItems (IEnumerable items)
611 if (!IsVerbosityGreaterOrEqual (LoggerVerbosity.Diagnostic) || items == null)
614 SetColor (eventColor);
616 WriteLine ("Initial Items:");
621 var items_table = new SortedDictionary<string, List<ITaskItem>> ();
622 foreach (DictionaryEntry de in items) {
623 string key = (string)de.Key;
624 if (!items_table.ContainsKey (key))
625 items_table [key] = new List<ITaskItem> ();
627 items_table [key].Add ((ITaskItem) de.Value);
630 foreach (string name in items_table.Keys) {
633 foreach (ITaskItem item in items_table [name])
634 WriteLine (item.ItemSpec);
640 public string Parameters {
646 throw new ArgumentNullException ();
648 if (parameters != String.Empty)
653 string EventsAsString {
655 if (current_events_string == null)
656 current_events_string = EventsToString ();
657 return current_events_string;
661 public bool ShowSummary {
662 get { return showSummary; }
663 set { showSummary = value; }
666 public bool SkipProjectStartedText {
667 get { return skipProjectStartedText; }
668 set { skipProjectStartedText = value; }
671 public LoggerVerbosity Verbosity {
672 get { return verbosity; }
673 set { verbosity = value; }
676 protected WriteHandler WriteHandler {
677 get { return writeHandler; }
678 set { writeHandler = value; }