1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System;
5 using System.Text;
6 using System.Collections;
7 using System.Diagnostics;
8 using System.Globalization;
9 using System.IO;
10 using System.Collections.Generic;
11 
12 using Microsoft.Build.Framework;
13 using Microsoft.Build.BuildEngine.Shared;
14 
15 namespace Microsoft.Build.BuildEngine
16 {
17     /// <summary>
18     /// This class implements the default logger that outputs event data
19     /// to the console (stdout).
20     /// </summary>
21     /// <remarks>This class is not thread safe.</remarks>
22     internal class ParallelConsoleLogger : BaseConsoleLogger
23     {
24         #region Constructors
25 
26         /// <summary>
27         /// Default constructor.
28         /// </summary>
ParallelConsoleLogger()29         public ParallelConsoleLogger()
30             : this(LoggerVerbosity.Normal)
31         {
32             // do nothing
33         }
34 
35         /// <summary>
36         /// Create a logger instance with a specific verbosity.  This logs to
37         /// the default console.
38         /// </summary>
ParallelConsoleLogger(LoggerVerbosity verbosity)39         public ParallelConsoleLogger(LoggerVerbosity verbosity)
40             :
41             this
42             (
43                 verbosity,
44                 new WriteHandler(Console.Out.Write),
45                 new ColorSetter(SetColor),
46                 new ColorResetter(Console.ResetColor)
47             )
48         {
49             // do nothing
50         }
51 
52         /// <summary>
53         /// Initializes the logger, with alternate output handlers.
54         /// </summary>
ParallelConsoleLogger( LoggerVerbosity verbosity, WriteHandler write, ColorSetter colorSet, ColorResetter colorReset )55         public ParallelConsoleLogger
56         (
57             LoggerVerbosity verbosity,
58             WriteHandler write,
59             ColorSetter colorSet,
60             ColorResetter colorReset
61         )
62         {
63             InitializeConsoleMethods(verbosity, write, colorSet, colorReset);
64             deferredMessages = new Dictionary<BuildEventContext, List<BuildMessageEventArgs>>(compareContextNodeId);
65             buildEventManager = new BuildEventManager();
66         }
67 
68         /// <summary>
69         /// Check to see if the console is going to a char output such as a console,printer or com port, or if it going to a file
70         /// </summary>
CheckIfOutputSupportsAlignment()71         private void CheckIfOutputSupportsAlignment()
72         {
73             alignMessages = false;
74             bufferWidth = -1;
75 
76             // If forceNoAlign is set there is no point getting the console width as there will be no aligning of the text
77             if (!forceNoAlign)
78             {
79                 if (runningWithCharacterFileType)
80                 {
81                     // Get the size of the console buffer so messages can be formatted to the console width
82                     bufferWidth = Console.BufferWidth;
83                     alignMessages = true;
84                 }
85                 else
86                 {
87                     alignMessages = false;
88                 }
89             }
90         }
91 
92         #endregion
93 
94         #region Methods
95 
96         /// <summary>
97         /// Allows the logger to take action based on a parameter passed on when initializing the logger
98         /// </summary>
ApplyParameter(string parameterName, string parameterValue)99         internal override bool ApplyParameter(string parameterName, string parameterValue)
100         {
101             if (base.ApplyParameter(parameterName, parameterValue))
102             {
103                 return true;
104             }
105             if (0 == String.Compare(parameterName, "SHOWCOMMANDLINE", StringComparison.OrdinalIgnoreCase))
106             {
107                 showCommandline = true;
108                 return true;
109             }
110             else if (0 == String.Compare(parameterName, "SHOWTIMESTAMP", StringComparison.OrdinalIgnoreCase))
111             {
112                 showTimeStamp = true;
113                 return true;
114             }
115             else if (0 == String.Compare(parameterName, "SHOWEVENTID", StringComparison.OrdinalIgnoreCase))
116             {
117                 showEventId = true;
118                 return true;
119             }
120             else if (0 == String.Compare(parameterName, "FORCENOALIGN", StringComparison.OrdinalIgnoreCase))
121             {
122                 forceNoAlign = true;
123                 alignMessages = false;
124                 return true;
125             }
126             return false;
127         }
128 
Initialize(IEventSource eventSource)129         public override void Initialize(IEventSource eventSource)
130         {
131             // If the logger is being used in singleproc do not show EventId after each message unless it is set as part of a console parameter
132             if (numberOfProcessors == 1)
133             {
134                 showEventId = false;
135             }
136 
137             // Parameters are parsed in Initialize
138             base.Initialize(eventSource);
139             CheckIfOutputSupportsAlignment();
140         }
141 
142         /// <summary>
143         /// Keep track of the last event displayed so target names can be displayed at the correct time
144         /// </summary>
ShownBuildEventContext(BuildEventContext e)145         private void ShownBuildEventContext(BuildEventContext e)
146         {
147             lastDisplayedBuildEventContext = e;
148         }
149 
150         /// <summary>
151         /// Reset the states of per-build member variables
152         /// VSW#516376
153         /// </summary>
ResetConsoleLoggerState()154         internal override void ResetConsoleLoggerState()
155         {
156             if (ShowSummary)
157             {
158                 errorList = new ArrayList();
159                 warningList = new ArrayList();
160             }
161             else
162             {
163                 errorList = null;
164                 warningList = null;
165             }
166 
167             errorCount = 0;
168             warningCount = 0;
169             projectPerformanceCounters = null;
170             targetPerformanceCounters = null;
171             taskPerformanceCounters = null;
172             hasBuildStarted = false;
173 
174             // Reset the two data structures created when the logger was created
175             buildEventManager = new BuildEventManager();
176             deferredMessages = new Dictionary<BuildEventContext, List<BuildMessageEventArgs>>(compareContextNodeId);
177             prefixWidth = 0;
178             lastDisplayedBuildEventContext = null;
179         }
180 
181         /// <summary>
182         /// Handler for build started events
183         /// </summary>
184         /// <param name="sender">sender (should be null)</param>
185         /// <param name="e">event arguments</param>
BuildStartedHandler(object sender, BuildStartedEventArgs e)186         public override void BuildStartedHandler(object sender, BuildStartedEventArgs e)
187         {
188             buildStarted = e.Timestamp;
189             hasBuildStarted = true;
190 
191             if (showOnlyErrors || showOnlyWarnings) return;
192 
193             if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
194             {
195                 WriteLinePrettyFromResource("BuildStartedWithTime", e.Timestamp);
196             }
197         }
198 
199         /// <summary>
200         /// Handler for build finished events
201         /// </summary>
202         /// <param name="sender">sender (should be null)</param>
203         /// <param name="e">event arguments</param>
BuildFinishedHandler(object sender, BuildFinishedEventArgs e)204         public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs e)
205         {
206             if (!showOnlyErrors && !showOnlyWarnings)
207             {
208                 // If for some reason we have deferred messages at the end of the build they should be displayed
209                 // so that the reason why they are still buffered can be determined
210                 if (deferredMessages.Count > 0)
211                 {
212                     if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
213                     {
214                         // Print out all of the deferred messages
215                         WriteLinePrettyFromResource("DeferredMessages");
216                         foreach (List<BuildMessageEventArgs> messageList in deferredMessages.Values)
217                         {
218                             foreach (BuildMessageEventArgs message in messageList)
219                             {
220                                 PrintMessage(message, false);
221                             }
222                         }
223                     }
224                     else if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
225                     {
226                         // In normal vervosity we do not want to print out the deferred messages but we do want
227                         // to let the users know that there were deferred messages to be seen
228                         WriteLinePrettyFromResource("DeferredMessagesAvailiable");
229                     }
230                 }
231 
232                 // Show the performance summary iff the verbosity is diagnostic or the user specifically asked for it
233                 // with a logger parameter.
234                 if (this.showPerfSummary)
235                 {
236                     ShowPerfSummary();
237                 }
238 
239                 // if verbosity is normal, detailed or diagnostic
240                 if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
241                 {
242                     if (e.Succeeded)
243                     {
244                         setColor(ConsoleColor.Green);
245                     }
246 
247                     // Write the "Build Finished" event.
248                     WriteNewLine();
249                     WriteLinePretty(e.Message);
250                     resetColor();
251                 }
252 
253                 // The decision whether or not to show a summary at this verbosity
254                 // was made during initalization. We just do what we're told.
255                 if (ShowSummary)
256                 {
257                     // We can't display a nice nested summary unless we're at Normal or above,
258                     // since we need to have gotten TargetStarted events, which aren't forwarded otherwise.
259                     if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
260                     {
261                         ShowNestedErrorWarningSummary();
262 
263                         // Emit text like:
264                         //     1 Warning(s)
265                         //     0 Error(s)
266                         // Don't color the line if it's zero. (Per Whidbey behavior.)
267                         if (warningCount > 0)
268                         {
269                             setColor(ConsoleColor.Yellow);
270                         }
271                         WriteLinePrettyFromResource(2, "WarningCount", warningCount);
272                         resetColor();
273 
274                         if (errorCount > 0)
275                         {
276                             setColor(ConsoleColor.Red);
277                         }
278                         WriteLinePrettyFromResource(2, "ErrorCount", errorCount);
279                         resetColor();
280                     }
281                     else
282                     {
283                         ShowFlatErrorWarningSummary();
284                     }
285                 }
286 
287                 // if verbosity is normal, detailed or diagnostic
288                 if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
289                 {
290                     // The time elapsed is the difference between when the BuildStartedEventArg
291                     // was created and when the BuildFinishedEventArg was created
292                     string timeElapsed = LogFormatter.FormatTimeSpan(e.Timestamp - buildStarted);
293 
294                     WriteNewLine();
295                     WriteLinePrettyFromResource("TimeElapsed", timeElapsed);
296                 }
297             }
298 
299            ResetConsoleLoggerState();
300            CheckIfOutputSupportsAlignment();
301         }
302 
303         /// <summary>
304         /// At the end of the build, repeats the errors and warnings that occurred
305         /// during the build, and displays the error count and warning count.
306         /// Does this in a "flat" style, without context.
307         /// </summary>
ShowFlatErrorWarningSummary()308         private void ShowFlatErrorWarningSummary()
309         {
310             if (warningList.Count == 0 && errorList.Count == 0) return;
311 
312             // If we're showing only warnings and/or errors, don't summarize.
313             // This is the buildc.err case. There's no point summarizing since we'd just
314             // repeat the entire log content again.
315             if (showOnlyErrors || showOnlyWarnings) return;
316 
317             // Make some effort to distinguish this summary from the output above, since otherwise
318             // it's not clear in lower verbosities
319             WriteNewLine();
320 
321             if (warningList.Count > 0)
322             {
323                 setColor(ConsoleColor.Yellow);
324                 foreach (BuildWarningEventArgs warning in warningList)
325                 {
326                     WriteMessageAligned(EventArgsFormatting.FormatEventMessage(warning, runningWithCharacterFileType), true);
327                 }
328             }
329 
330             if (errorList.Count > 0)
331             {
332                 setColor(ConsoleColor.Red);
333                 foreach (BuildErrorEventArgs error in errorList)
334                 {
335                     WriteMessageAligned(EventArgsFormatting.FormatEventMessage(error, runningWithCharacterFileType), true);
336                 }
337             }
338 
339             resetColor();
340         }
341 
342         /// <summary>
343         /// At the end of the build, repeats the errors and warnings that occurred
344         /// during the build, and displays the error count and warning count.
345         /// Does this in a "nested" style.
346         /// </summary>
ShowNestedErrorWarningSummary()347         private void ShowNestedErrorWarningSummary()
348         {
349             if (warningList.Count == 0 && errorList.Count == 0) return;
350 
351             // If we're showing only warnings and/or errors, don't summarize.
352             // This is the buildc.err case. There's no point summarizing since we'd just
353             // repeat the entire log content again.
354             if (showOnlyErrors || showOnlyWarnings) return;
355 
356             if (warningCount > 0)
357             {
358                 setColor(ConsoleColor.Yellow);
359                 ShowErrorWarningSummary<BuildWarningEventArgs>(warningList);
360             }
361 
362             if (errorCount > 0)
363             {
364                 setColor(ConsoleColor.Red);
365                 ShowErrorWarningSummary<BuildErrorEventArgs>(errorList);
366             }
367 
368             resetColor();
369         }
370 
371         private void ShowErrorWarningSummary<T>(ArrayList listToProcess) where T : BuildEventArgs
372         {
373             // Group the build warning event args based on the entry point and the target in which the warning occurred
374             Dictionary<ErrorWarningSummaryDictionaryKey, List<T>> groupByProjectEntryPoint = new Dictionary<ErrorWarningSummaryDictionaryKey, List<T>>();
375 
376             // Loop through each of the warnings and put them into the correct buckets
377             for (int listCount = 0; listCount < listToProcess.Count; listCount++)
378             {
379 
380                 T errorWarningEventArgs = (T)listToProcess[listCount];
381 
382                 // Target event may be null for a couple of reasons:
383                 // 1) If the event was from a project load, or engine
384                 // 2) If the flushing of the event queue for each request and result is turned off
385                 // as this could cause errors and warnings to be seen by the logger after the target finished event
386                 // which would cause the error or warning to have no matching target started event as they are removed
387                 // when a target finished event is logged.
388                 // 3) On NORMAL verbosity if the error or warning occurres in a project load then the error or warning and the target started event will be forwarded to
389                 // different forwarding loggers which cannot communicate to each other, meaning there will be no matching target started event logged
390                 // as the forwarding logger did not know to forward the target started event
391                 string targetName = null;
392                 TargetStartedEventMinimumFields targetEvent = buildEventManager.GetTargetStartedEvent(errorWarningEventArgs.BuildEventContext);
393 
394                 if (targetEvent != null)
395                 {
396                     targetName = targetEvent.TargetName;
397                 }
398 
399                 // Create a new key from the error event context and the target where the error happened
400                 ErrorWarningSummaryDictionaryKey key = new ErrorWarningSummaryDictionaryKey(errorWarningEventArgs.BuildEventContext, targetName);
401 
402                 // Check to see if there is a bucket for the warning
403                 if (!groupByProjectEntryPoint.ContainsKey(key))
404                 {
405                     // If there is no bucket create a new one which contains a list of all the errors which
406                     // happened for a given buildEventContext / target
407                     List<T> errorWarningEventListByTarget = new List<T>();
408                     groupByProjectEntryPoint.Add(key, errorWarningEventListByTarget);
409                 }
410 
411                 // Add the error event to the correct bucket
412                 groupByProjectEntryPoint[key].Add(errorWarningEventArgs);
413             }
414 
415             BuildEventContext previousEntryPoint = null;
416             string previousTarget = null;
417             // Loop through each of the bucket and print out the stack trace information for the errors
418             foreach (KeyValuePair<ErrorWarningSummaryDictionaryKey, List<T>> valuePair in groupByProjectEntryPoint)
419             {
420                 //If the project entrypoint where the error occurred is the same as the previous message do not print the
421                 // stack trace again
422                 if (previousEntryPoint != valuePair.Key.EntryPointContext)
423                 {
424                     WriteNewLine();
425                     foreach (string s in buildEventManager.ProjectCallStackFromProject(valuePair.Key.EntryPointContext))
426                     {
427                         WriteMessageAligned(s, false);
428                     }
429                     previousEntryPoint = valuePair.Key.EntryPointContext;
430                 }
431 
432                 //If the target where the error occurred is the same as the previous message do not print the location
433                 // where the error occurred again
434                 if (String.Compare(previousTarget, valuePair.Key.TargetName, StringComparison.OrdinalIgnoreCase) != 0)
435                 {
436                     //If no targetName was specified then do not show the target where the error occurred
437                     if (! string.IsNullOrEmpty(valuePair.Key.TargetName))
438                     {
439                         WriteMessageAligned(ResourceUtilities.FormatResourceString("ErrorWarningInTarget", valuePair.Key.TargetName), false);
440                     }
441                     previousTarget = valuePair.Key.TargetName;
442                 }
443 
444                 // Print out all of the errors under the ProjectEntryPoint / target
445                 foreach (T errorWarningEvent in valuePair.Value)
446                 {
447                     if (errorWarningEvent is BuildErrorEventArgs)
448                     {
449                         WriteMessageAligned("  " + EventArgsFormatting.FormatEventMessage(errorWarningEvent as BuildErrorEventArgs, runningWithCharacterFileType), false);
450                     }
451                     else if (errorWarningEvent is BuildWarningEventArgs)
452                     {
453                         WriteMessageAligned("  " + EventArgsFormatting.FormatEventMessage(errorWarningEvent as BuildWarningEventArgs, runningWithCharacterFileType), false);
454                     }
455                 }
456                 WriteNewLine();
457             }
458         }
459 
460         /// <summary>
461         /// Handler for project started events
462         /// </summary>
463         /// <param name="sender">sender (should be null)</param>
464         /// <param name="e">event arguments</param>
ProjectStartedHandler(object sender, ProjectStartedEventArgs e)465         public override void ProjectStartedHandler(object sender, ProjectStartedEventArgs e)
466         {
467             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
468             ErrorUtilities.VerifyThrowArgumentNull(e.ParentProjectBuildEventContext, "ParentProjectBuildEventContext");
469 
470             // Add the project to the BuildManager so we can use the start information later in the build process
471             buildEventManager.AddProjectStartedEvent(e);
472 
473             if (this.showPerfSummary)
474             {
475                 // Create a new project performance counter for this project
476                 MPPerformanceCounter counter = GetPerformanceCounter(e.ProjectFile, ref projectPerformanceCounters);
477                 counter.AddEventStarted(e.TargetNames, e.BuildEventContext, e.Timestamp, compareContextNodeId);
478             }
479 
480             // If there were deferred messages then we should show them now, this will cause the project started event to be shown properly
481             if (deferredMessages.ContainsKey(e.BuildEventContext))
482             {
483                 if (!showOnlyErrors && !showOnlyWarnings)
484                 {
485                     foreach (BuildMessageEventArgs message in deferredMessages[e.BuildEventContext])
486                     {
487                         // This will display the project started event before the messages is shown
488                         this.MessageHandler(sender, message);
489                     }
490                 }
491                 deferredMessages.Remove(e.BuildEventContext);
492             }
493 
494             //If we are in diagnostic and are going to show items, show the project started event
495             // along with the items. The project started event will only be shown if it has not been shown before
496             if (Verbosity == LoggerVerbosity.Diagnostic && showItemAndPropertyList)
497             {
498                 //Show the deferredProjectStartedEvent
499                 if (!showOnlyErrors && !showOnlyWarnings)
500                 {
501                     DisplayDeferredProjectStartedEvent(e.BuildEventContext);
502                 }
503                 if (null != e.Properties)
504                 {
505                     WriteProperties(e, e.Properties);
506                 }
507 
508                 if (null != e.Items)
509                 {
510                     WriteItems(e, e.Items);
511                 }
512             }
513         }
514 
515         /// <summary>
516         /// Handler for project finished events
517         /// </summary>
518         /// <param name="sender">sender (should be null)</param>
519         /// <param name="e">event arguments</param>
ProjectFinishedHandler(object sender, ProjectFinishedEventArgs e)520         public override void ProjectFinishedHandler(object sender, ProjectFinishedEventArgs e)
521         {
522             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
523 
524 
525             //Get the project started event so we can use its information to properly display a project finished event
526             ProjectStartedEventMinimumFields startedEvent = buildEventManager.GetProjectStartedEvent(e.BuildEventContext);
527             ErrorUtilities.VerifyThrow(startedEvent != null, "Started event should not be null in the finished event handler");
528 
529             if (this.showPerfSummary)
530             {
531                 // Stop the performance counter which was created in the project started event handler
532                 MPPerformanceCounter counter = GetPerformanceCounter(e.ProjectFile, ref projectPerformanceCounters);
533                 counter.AddEventFinished(startedEvent.TargetNames, e.BuildEventContext, e.Timestamp);
534             }
535 
536             if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
537             {
538                 // Only want to show the project finished event if a project started event has been shown
539                 if (startedEvent.ShowProjectFinishedEvent)
540                 {
541                     lastProjectFullKey = GetFullProjectKey(e.BuildEventContext);
542 
543                     if (!showOnlyErrors && !showOnlyWarnings)
544                     {
545                         WriteLinePrefix(e.BuildEventContext, e.Timestamp, false);
546                         setColor(ConsoleColor.Cyan);
547 
548                         // In the project finished message the targets which were built and the project which was built
549                         // should be shown
550                         string targets = startedEvent.TargetNames;
551 
552                         string projectName = string.Empty;
553 
554                         projectName = startedEvent.ProjectFile == null ? string.Empty : startedEvent.ProjectFile;
555                         // Show which targets were built as part of this project
556                         if (string.IsNullOrEmpty(targets))
557                         {
558                             if (e.Succeeded)
559                             {
560                                 WriteMessageAligned(ResourceUtilities.FormatResourceString("ProjectFinishedPrefixWithDefaultTargetsMultiProc", projectName), true);
561                             }
562                             else
563                             {
564                                 WriteMessageAligned(ResourceUtilities.FormatResourceString("ProjectFinishedPrefixWithDefaultTargetsMultiProcFailed", projectName), true);
565                             }
566                         }
567                         else
568                         {
569                             if (e.Succeeded)
570                             {
571                                 WriteMessageAligned(ResourceUtilities.FormatResourceString("ProjectFinishedPrefixWithTargetNamesMultiProc", projectName, targets), true);
572                             }
573                             else
574                             {
575                                 WriteMessageAligned(ResourceUtilities.FormatResourceString("ProjectFinishedPrefixWithTargetNamesMultiProcFailed", projectName, targets), true);
576                             }
577                         }
578 
579                         // In single proc only make a space between the project done event and the next line, this
580                         // is to increase the readability on the single proc log when there are a number of done events
581                         // or a mix of done events and project started events. Also only do this on the console and not any log file.
582                         if (numberOfProcessors == 1 && runningWithCharacterFileType)
583                         {
584                             WriteNewLine();
585                         }
586                     }
587 
588                     ShownBuildEventContext(e.BuildEventContext);
589                     resetColor();
590                 }
591             }
592             // We are done with the project started event if the project has finished building, remove its reference
593             buildEventManager.RemoveProjectStartedEvent(e.BuildEventContext);
594         }
595 
596         /// <summary>
597         /// Writes out the list of property names and their values.
598         /// This could be done at any time during the build to show the latest
599         /// property values, using the cached reference to the list from the
600         /// appropriate ProjectStarted event.
601         /// </summary>
602         /// <param name="properties">List of properties</param>
WriteProperties(BuildEventArgs e, IEnumerable properties)603         internal void WriteProperties(BuildEventArgs e, IEnumerable properties)
604         {
605             if (showOnlyErrors || showOnlyWarnings) return;
606             ArrayList propertyList = ExtractPropertyList(properties);
607 
608             // if there are no properties to display return out of the method and dont print out anything related to displaying
609             // the properties, this includes the multiproc prefix information or the Initial properties header
610             if (propertyList.Count == 0)
611             {
612                 return;
613             }
614 
615             WriteLinePrefix(e.BuildEventContext, e.Timestamp, false);
616             WriteProperties(propertyList);
617             ShownBuildEventContext(e.BuildEventContext);
618         }
619 
OutputProperties(ArrayList list)620         internal override void OutputProperties(ArrayList list)
621         {
622             // Write the banner
623             setColor(ConsoleColor.Green);
624             WriteMessageAligned(ResourceUtilities.FormatResourceString("PropertyListHeader"), true);
625             // Write each property name and its value, one per line
626             foreach (DictionaryEntry prop in list)
627             {
628                 setColor(ConsoleColor.Gray);
629                 string propertyString = String.Format(CultureInfo.CurrentCulture, "{0} = {1}", prop.Key, (string)(prop.Value));
630                 WriteMessageAligned(propertyString, false);
631             }
632             resetColor();
633         }
634         /// <summary>
635         /// Writes out the list of item specs and their metadata.
636         /// This could be done at any time during the build to show the latest
637         /// items, using the cached reference to the list from the
638         /// appropriate ProjectStarted event.
639         /// </summary>
640         /// <param name="items">List of items</param>
WriteItems(BuildEventArgs e, IEnumerable items)641         internal void WriteItems(BuildEventArgs e, IEnumerable items)
642         {
643             if (showOnlyErrors || showOnlyWarnings) return;
644             SortedList itemList = ExtractItemList(items);
645 
646             // if there are no Items to display return out of the method and dont print out anything related to displaying
647             // the items, this includes the multiproc prefix information or the Initial items header
648             if (itemList.Count == 0)
649             {
650                 return;
651             }
652             WriteLinePrefix(e.BuildEventContext, e.Timestamp, false);
653             WriteItems(itemList);
654             ShownBuildEventContext(e.BuildEventContext);
655         }
656 
OutputItems(string itemType, ArrayList itemTypeList)657         internal override void OutputItems(string itemType, ArrayList itemTypeList)
658         {
659             // Write each item, one per line
660             bool haveWrittenItemType = false;
661             foreach (ITaskItem item in itemTypeList)
662             {
663                 string itemString = null;
664                 if (!haveWrittenItemType)
665                 {
666                     itemString=itemType;
667                     setColor(ConsoleColor.DarkGray);
668                     WriteMessageAligned(itemType, false);
669                     haveWrittenItemType = true;
670                 }
671                 setColor(ConsoleColor.Gray);
672 
673                 // Indent the text by two tab lengths
674                 StringBuilder result = new StringBuilder();
675                 result.Append(' ', 2 * tabWidth).Append(item.ItemSpec);
676                 WriteMessageAligned(result.ToString() , false);
677             }
678             resetColor();
679         }
680         /// <summary>
681         /// Handler for target started events
682         /// </summary>
683         /// <param name="sender">sender (should be null)</param>
684         /// <param name="e">event arguments</param>
TargetStartedHandler(object sender, TargetStartedEventArgs e)685         public override void TargetStartedHandler(object sender, TargetStartedEventArgs e)
686         {
687             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
688 
689             // Add the target started information to the buildEventManager so its information can be used
690             // later in the build
691             buildEventManager.AddTargetStartedEvent(e);
692 
693             if (this.showPerfSummary)
694             {
695                 // Create a new performance counter for this target
696                 MPPerformanceCounter counter = GetPerformanceCounter(e.TargetName, ref targetPerformanceCounters);
697                 counter.AddEventStarted(null, e.BuildEventContext, e.Timestamp, compareContextNodeIdTargetId);
698             }
699         }
700 
701         /// <summary>
702         /// Handler for target finished events
703         /// </summary>
704         /// <param name="sender">sender (should be null)</param>
705         /// <param name="e">event arguments</param>
TargetFinishedHandler(object sender, TargetFinishedEventArgs e)706         public override void TargetFinishedHandler(object sender, TargetFinishedEventArgs e)
707         {
708             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
709 
710             if (this.showPerfSummary)
711             {
712                 // Stop the performance counter started in the targetStartedEventHandler
713                 MPPerformanceCounter counter = GetPerformanceCounter(e.TargetName, ref targetPerformanceCounters);
714                 counter.AddEventFinished(null, e.BuildEventContext, e.Timestamp);
715             }
716 
717             if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
718             {
719                 // Get the target started event so we can determine whether or not to show the targetFinishedEvent
720                 // as we only want to show target finished events if a target started event has been shown
721                 TargetStartedEventMinimumFields startedEvent = buildEventManager.GetTargetStartedEvent(e.BuildEventContext);
722                 ErrorUtilities.VerifyThrow(startedEvent != null, "Started event should not be null in the finished event handler");
723                 if (startedEvent.ShowTargetFinishedEvent)
724                 {
725                     if (!showOnlyErrors && !showOnlyWarnings)
726                     {
727                         lastProjectFullKey = GetFullProjectKey(e.BuildEventContext);
728                         WriteLinePrefix(e.BuildEventContext, e.Timestamp, false);
729                         setColor(ConsoleColor.Cyan);
730                         if (IsVerbosityAtLeast(LoggerVerbosity.Diagnostic) || showEventId)
731                         {
732                             WriteMessageAligned(ResourceUtilities.FormatResourceString("TargetMessageWithId", e.Message, e.BuildEventContext.TargetId), true);
733                         }
734                         else
735                         {
736                             WriteMessageAligned(e.Message, true);
737                         }
738                         resetColor();
739                     }
740                     ShownBuildEventContext(e.BuildEventContext);
741                 }
742             }
743 
744             //We no longer need this target started event, it can be removed
745             buildEventManager.RemoveTargetStartedEvent(e.BuildEventContext);
746         }
747 
748         /// <summary>
749         /// Handler for task started events
750         /// </summary>
751         /// <param name="sender">sender (should be null)</param>
752         /// <param name="e">event arguments</param>
TaskStartedHandler(object sender, TaskStartedEventArgs e)753         public override void TaskStartedHandler(object sender, TaskStartedEventArgs e)
754         {
755             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
756 
757             // if verbosity is detailed or diagnostic
758 
759             if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
760             {
761                 DisplayDeferredStartedEvents(e.BuildEventContext);
762 
763                 if (!showOnlyErrors && !showOnlyWarnings)
764                 {
765                     bool prefixAlreadyWritten = WriteTargetMessagePrefix(e, e.BuildEventContext, e.Timestamp);
766                     setColor(ConsoleColor.DarkCyan);
767                     if (IsVerbosityAtLeast(LoggerVerbosity.Diagnostic) || showEventId)
768                     {
769                         WriteMessageAligned(ResourceUtilities.FormatResourceString("TaskMessageWithId", e.Message, e.BuildEventContext.TaskId), prefixAlreadyWritten);
770                     }
771                     else
772                     {
773                         WriteMessageAligned(e.Message, prefixAlreadyWritten);
774                     }
775                     resetColor();
776                 }
777 
778                 ShownBuildEventContext(e.BuildEventContext);
779             }
780 
781             if (this.showPerfSummary)
782             {
783                 // Create a new performance counter for this task
784                 MPPerformanceCounter counter = GetPerformanceCounter(e.TaskName, ref taskPerformanceCounters);
785                 counter.AddEventStarted(null, e.BuildEventContext, e.Timestamp, null);
786             }
787         }
788 
789         /// <summary>
790         /// Handler for task finished events
791         /// </summary>
792         /// <param name="sender">sender (should be null)</param>
793         /// <param name="e">event arguments</param>
TaskFinishedHandler(object sender, TaskFinishedEventArgs e)794         public override void TaskFinishedHandler(object sender, TaskFinishedEventArgs e)
795         {
796             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
797             if (this.showPerfSummary)
798             {
799                 // Stop the task performance counter which was started in the task started event
800                 MPPerformanceCounter counter = GetPerformanceCounter(e.TaskName, ref taskPerformanceCounters);
801                 counter.AddEventFinished(null, e.BuildEventContext, e.Timestamp);
802             }
803 
804             // if verbosity is detailed or diagnostic
805             if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
806             {
807                 if (!showOnlyErrors && !showOnlyWarnings)
808                 {
809                     bool prefixAlreadyWritten = WriteTargetMessagePrefix(e, e.BuildEventContext, e.Timestamp);
810                     setColor(ConsoleColor.DarkCyan);
811                     if (IsVerbosityAtLeast(LoggerVerbosity.Diagnostic) || showEventId)
812                     {
813                         WriteMessageAligned(ResourceUtilities.FormatResourceString("TaskMessageWithId", e.Message, e.BuildEventContext.TaskId), prefixAlreadyWritten);
814                     }
815                     else
816                     {
817                         WriteMessageAligned(e.Message, prefixAlreadyWritten);
818                     }
819                     resetColor();
820                 }
821                 ShownBuildEventContext(e.BuildEventContext);
822             }
823         }
824 
825         /// <summary>
826         /// Prints an error event
827         /// </summary>
ErrorHandler(object sender, BuildErrorEventArgs e)828         public override void ErrorHandler(object sender, BuildErrorEventArgs e)
829         {
830             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
831             // Keep track of the number of error events raisd
832             errorCount++;
833 
834             // If there is an error we need to walk up the call stack and make sure that
835             // the project started events back to the root project know an error has occurred
836             // and are not removed when they finish
837             buildEventManager.SetErrorWarningFlagOnCallStack(e.BuildEventContext);
838 
839             TargetStartedEventMinimumFields targetStartedEvent = buildEventManager.GetTargetStartedEvent(e.BuildEventContext);
840             // Can be null if the error occurred outside of a target, or the error occurres before the targetStartedEvent
841             if (targetStartedEvent != null)
842             {
843                 targetStartedEvent.ErrorInTarget = true;
844             }
845 
846             DisplayDeferredStartedEvents(e.BuildEventContext);
847 
848             // Display only if showOnlyWarnings is false;
849             // unless showOnlyErrors is true, which trumps it.
850             if (!showOnlyWarnings || showOnlyErrors)
851             {
852                 if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
853                 {
854                     WriteLinePrefix(e.BuildEventContext, e.Timestamp, false);
855                 }
856 
857                 setColor(ConsoleColor.Red);
858                 WriteMessageAligned(EventArgsFormatting.FormatEventMessage(e, runningWithCharacterFileType), true);
859                 ShownBuildEventContext(e.BuildEventContext);
860                 if (ShowSummary)
861                 {
862                     if (!errorList.Contains(e))
863                     {
864                         errorList.Add(e);
865                     }
866                 }
867                 resetColor();
868             }
869         }
870 
871         /// <summary>
872         /// Prints a warning event
873         /// </summary>
WarningHandler(object sender, BuildWarningEventArgs e)874         public override void WarningHandler(object sender, BuildWarningEventArgs e)
875         {
876             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
877             // Keep track of the number of warning events raised during the build
878             warningCount++;
879 
880             // If there is a warning we need to walk up the call stack and make sure that
881             // the project started events back to the root project know a warning has ocured
882             // and are not removed when they finish
883             buildEventManager.SetErrorWarningFlagOnCallStack(e.BuildEventContext);
884             TargetStartedEventMinimumFields targetStartedEvent = buildEventManager.GetTargetStartedEvent(e.BuildEventContext);
885 
886             // Can be null if the error occurred outside of a target, or the error occurres before the targetStartedEvent
887             if (targetStartedEvent != null)
888             {
889                 targetStartedEvent.ErrorInTarget = true;
890             }
891 
892             DisplayDeferredStartedEvents(e.BuildEventContext);
893 
894             // Display only if showOnlyErrors is false;
895             // unless showOnlyWarnings is true, which trumps it.
896             if (!showOnlyErrors || showOnlyWarnings)
897             {
898                 if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
899                 {
900                     WriteLinePrefix(e.BuildEventContext, e.Timestamp, false);
901                 }
902 
903                 setColor(ConsoleColor.Yellow);
904                 WriteMessageAligned(EventArgsFormatting.FormatEventMessage(e, runningWithCharacterFileType), true);
905             }
906 
907             ShownBuildEventContext(e.BuildEventContext);
908 
909             if (ShowSummary)
910             {
911                 if (!warningList.Contains(e))
912                 {
913                     warningList.Add(e);
914                 }
915             }
916             resetColor();
917         }
918 
919         /// <summary>
920         /// Prints a message event
921         /// </summary>
MessageHandler(object sender, BuildMessageEventArgs e)922         public override void MessageHandler(object sender, BuildMessageEventArgs e)
923         {
924             if (showOnlyErrors || showOnlyWarnings) return;
925 
926             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
927             bool print = false;
928             bool lightenText = false;
929 
930             if (e is TaskCommandLineEventArgs)
931             {
932                 if (!showCommandline && verbosity < LoggerVerbosity.Detailed)
933                 {
934                     return;
935                 }
936                 print = true;
937             }
938             else
939             {
940                 switch (e.Importance)
941                 {
942                     case MessageImportance.High:
943                         print = IsVerbosityAtLeast(LoggerVerbosity.Minimal);
944                         break;
945                     case MessageImportance.Normal:
946                         print = IsVerbosityAtLeast(LoggerVerbosity.Normal);
947                         lightenText = true;
948                         break;
949                     case MessageImportance.Low:
950                         print = IsVerbosityAtLeast(LoggerVerbosity.Detailed);
951                         lightenText = true;
952                         break;
953                     default:
954                         ErrorUtilities.VerifyThrow(false, "Impossible");
955                         break;
956                 }
957             }
958 
959             if (print)
960             {
961                 // If the event has a valid Project contextId but the project started event has not been fired, the message needs to be
962                 // buffered until the project started event is fired
963                 if (
964                        hasBuildStarted
965                        && e.BuildEventContext.ProjectContextId != BuildEventContext.InvalidProjectContextId
966                        && buildEventManager.GetProjectStartedEvent(e.BuildEventContext) == null
967                        && IsVerbosityAtLeast(LoggerVerbosity.Normal)
968                     )
969                 {
970                     List<BuildMessageEventArgs> messageList = null;
971                     if (deferredMessages.ContainsKey(e.BuildEventContext))
972                     {
973                         messageList = deferredMessages[e.BuildEventContext];
974                     }
975                     else
976                     {
977                         messageList = new List<BuildMessageEventArgs>();
978                         deferredMessages.Add(e.BuildEventContext, messageList);
979                     }
980                     messageList.Add(e);
981                     return;
982                 }
983 
984                 DisplayDeferredStartedEvents(e.BuildEventContext);
985 
986                 // Print the message event out to the console
987                 PrintMessage(e, lightenText);
988                 ShownBuildEventContext(e.BuildEventContext);
989             }
990         }
991 
DisplayDeferredStartedEvents(BuildEventContext e)992         private void DisplayDeferredStartedEvents(BuildEventContext e)
993         {
994             if (showOnlyErrors || showOnlyWarnings) return;
995 
996             // Display any project started events which were deferred until a visible
997             // message from their project is displayed
998             if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
999             {
1000                 DisplayDeferredProjectStartedEvent(e);
1001             }
1002 
1003             // Display any target started events which were deferred until a visible
1004             // message from their target is displayed
1005             if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
1006             {
1007                 DisplayDeferredTargetStartedEvent(e);
1008             }
1009         }
1010 
1011         /// <summary>
1012         /// Prints out a message event to the console
1013         /// </summary>
PrintMessage(BuildMessageEventArgs e, bool lightenText)1014         private void PrintMessage(BuildMessageEventArgs e, bool lightenText)
1015         {
1016             string nonNullMessage = (e.Message == null) ? String.Empty : e.Message;
1017             int prefixAdjustment = 0;
1018 
1019             if (e.BuildEventContext.TaskId != BuildEventContext.InvalidTaskId)
1020             {
1021                 prefixAdjustment = 2;
1022             }
1023 
1024             if (lightenText)
1025             {
1026                 setColor(ConsoleColor.DarkGray);
1027             }
1028 
1029             PrintTargetNamePerMessage(e, lightenText);
1030 
1031             // On diagnostic or if showEventId is set the task message should also display the taskId to assist debugging
1032             if ((IsVerbosityAtLeast(LoggerVerbosity.Diagnostic) || showEventId) && e.BuildEventContext.TaskId != BuildEventContext.InvalidTaskId)
1033             {
1034                 bool prefixAlreadyWritten = WriteTargetMessagePrefix(e, e.BuildEventContext, e.Timestamp);
1035                 WriteMessageAligned(ResourceUtilities.FormatResourceString("TaskMessageWithId", nonNullMessage, e.BuildEventContext.TaskId), prefixAlreadyWritten, prefixAdjustment);
1036             }
1037             else
1038             {
1039                 //A time stamp may be shown on verbosities lower than diagnostic
1040                 if (showTimeStamp || IsVerbosityAtLeast(LoggerVerbosity.Detailed))
1041                 {
1042                     bool prefixAlreadyWritten = WriteTargetMessagePrefix(e, e.BuildEventContext, e.Timestamp);
1043                     WriteMessageAligned(nonNullMessage, prefixAlreadyWritten, prefixAdjustment);
1044                 }
1045                 else
1046                 {
1047                     WriteMessageAligned(nonNullMessage, false, prefixAdjustment);
1048                 }
1049             }
1050 
1051             if (lightenText)
1052             {
1053                 resetColor();
1054             }
1055         }
1056 
PrintTargetNamePerMessage(BuildMessageEventArgs e, bool lightenText)1057         private void PrintTargetNamePerMessage(BuildMessageEventArgs e, bool lightenText)
1058         {
1059                 // Event Context of the current message
1060                 BuildEventContext currentBuildEventContext = e.BuildEventContext;
1061 
1062                 // Should the target name be written before the message
1063                 bool writeTargetName = false;
1064                 string targetName = string.Empty;
1065 
1066                 // Does the context (Project, Node, Context, Target, NOT task) of the previous event match the current message
1067                 bool contextAreEqual = compareContextNodeIdTargetId.Equals(currentBuildEventContext, lastDisplayedBuildEventContext == null ? null : lastDisplayedBuildEventContext);
1068 
1069                 TargetStartedEventMinimumFields targetStartedEvent = null;
1070                 // If the previous event does not have the same target context information, the target name needs to be printed to the console
1071                 // to give the message some more contextual information
1072                 if (!contextAreEqual)
1073                 {
1074                     targetStartedEvent = buildEventManager.GetTargetStartedEvent(currentBuildEventContext);
1075                     // Some messages such as engine messages will not have a target started event, in their case, dont print the targetName
1076                     if (targetStartedEvent != null)
1077                     {
1078                         targetName = targetStartedEvent.TargetName;
1079                         writeTargetName = true;
1080                     }
1081                 }
1082                 else
1083                 {
1084                     writeTargetName = false;
1085                 }
1086 
1087                 if (writeTargetName)
1088                 {
1089                     bool prefixAlreadyWritten = WriteTargetMessagePrefix(e, targetStartedEvent.ProjectBuildEventContext, targetStartedEvent.TimeStamp);
1090 
1091                     setColor(ConsoleColor.Cyan);
1092                     if (IsVerbosityAtLeast(LoggerVerbosity.Diagnostic) || showEventId)
1093                     {
1094                         WriteMessageAligned(ResourceUtilities.FormatResourceString("TargetMessageWithId", targetName, e.BuildEventContext.TargetId), prefixAlreadyWritten);
1095                     }
1096                     else
1097                     {
1098                         WriteMessageAligned(targetName + ":", prefixAlreadyWritten);
1099                     }
1100 
1101                     if (lightenText)
1102                     {
1103                         setColor(ConsoleColor.DarkGray);
1104                     }
1105                     else
1106                     {
1107                         resetColor();
1108                     }
1109                 }
1110         }
1111 
WriteTargetMessagePrefix(BuildEventArgs e, BuildEventContext context, DateTime timeStamp)1112         private bool WriteTargetMessagePrefix(BuildEventArgs e, BuildEventContext context, DateTime timeStamp)
1113         {
1114             bool prefixAlreadyWritten = true;
1115             ProjectFullKey currentProjectFullKey = GetFullProjectKey(e.BuildEventContext);
1116             if (!(lastProjectFullKey.Equals(currentProjectFullKey)))
1117             {
1118                 // Write the prefix information about the target for the message
1119                 WriteLinePrefix(context, timeStamp, false);
1120                 lastProjectFullKey = currentProjectFullKey;
1121             }
1122             else
1123             {
1124                 prefixAlreadyWritten = false;
1125             }
1126             return prefixAlreadyWritten;
1127         }
1128 
1129         /// <summary>
1130         /// Writes a message to the console, aligned and formatted to fit within the console width
1131         /// </summary>
1132         /// <param name="message">Message to be formatted to fit on the console</param>
1133         /// <param name="prefixAlreadyWritten">Has the prefix(timestamp or key been written)</param>
WriteMessageAligned(string message, bool prefixAlreadyWritten)1134         private void WriteMessageAligned(string message, bool prefixAlreadyWritten)
1135         {
1136             WriteMessageAligned(message, prefixAlreadyWritten, 0);
1137         }
1138 
1139         /// <summary>
1140         /// Writes a message to the console, aligned and formatted to fit within the console width
1141         /// </summary>
1142         /// <param name="message">Message to be formatted to fit on the console</param>
1143         /// <param name="prefixAlreadyWritten">Has the prefix(timestamp or key been written)</param>
WriteMessageAligned(string message, bool prefixAlreadyWritten, int prefixAdjustment)1144         private void WriteMessageAligned(string message, bool prefixAlreadyWritten, int prefixAdjustment)
1145         {
1146             // This method may require the splitting of lines inorder to format them to the console, this must be an atomic operation
1147             lock (lockObject)
1148             {
1149                 int adjustedPrefixWidth = prefixWidth + prefixAdjustment;
1150 
1151                 // The string may contain new lines, treat each new line as a different string to format and send to the console
1152                 string[] nonNullMessages = SplitStringOnNewLines(message);
1153                 for (int i = 0; i < nonNullMessages.Length; i++)
1154                 {
1155                     string nonNullMessage = nonNullMessages[i];
1156                     // Take into account the new line char which will be added to the end or each reformatted string
1157                     int bufferWidthMinusNewLine = bufferWidth - 1;
1158 
1159                     // If the buffer is larger then the prefix information (timestamp and key) then reformat the messages.
1160                     // If there is not enough room just print the message out and let the console do the formatting
1161                     bool bufferIsLargerThanPrefix = bufferWidthMinusNewLine > adjustedPrefixWidth;
1162                     bool messageAndPrefixTooLargeForBuffer = (nonNullMessage.Length + adjustedPrefixWidth) > bufferWidthMinusNewLine;
1163                     if (bufferIsLargerThanPrefix && messageAndPrefixTooLargeForBuffer && alignMessages)
1164                     {
1165                         // Our message may have embedded tab characters, so expand those to their space
1166                         // equivalent so that wrapping works as expected.
1167                         nonNullMessage = nonNullMessage.Replace("\t", consoleTab);
1168 
1169                         // If the message and the prefix are too large for one line in the console, split the string to fit
1170                         int index = 0;
1171                         int messageLength = nonNullMessage.Length;
1172                         int amountToCopy = 0;
1173                         // Loop until all the string has been sent to the console
1174                         while (index < messageLength)
1175                         {
1176                             // Calculate how many chars will fit on the console buffer
1177                             amountToCopy = (messageLength - index) < (bufferWidthMinusNewLine - adjustedPrefixWidth) ? (messageLength - index) : (bufferWidthMinusNewLine - adjustedPrefixWidth);
1178                             WriteBasedOnPrefix(nonNullMessage.Substring(index, amountToCopy), (prefixAlreadyWritten && index == 0 && i == 0), adjustedPrefixWidth);
1179                             index = index + amountToCopy;
1180                         }
1181                     }
1182                     else
1183                     {
1184                         //there is not enough room just print the message out and let the console do the formatting
1185                         WriteBasedOnPrefix(nonNullMessage, prefixAlreadyWritten, adjustedPrefixWidth);
1186                     }
1187                 }
1188             }
1189         }
1190 
1191         /// <summary>
1192         /// Write message takinginto account whether or not the prefix (timestamp and key) have already been written on the line
1193         /// </summary>
1194         /// <param name="nonNullMessage"></param>
1195         /// <param name="prefixAlreadyWritten"></param>
WriteBasedOnPrefix(string nonNullMessage, bool prefixAlreadyWritten, int adjustedPrefixWidth)1196         private void WriteBasedOnPrefix(string nonNullMessage, bool prefixAlreadyWritten, int adjustedPrefixWidth)
1197         {
1198             if (prefixAlreadyWritten)
1199             {
1200                 write(nonNullMessage);
1201                 WriteNewLine();
1202             }
1203             else
1204             {
1205                 // No prefix info has been written, indent the line to the proper location
1206                 write(IndentString(nonNullMessage, adjustedPrefixWidth));
1207             }
1208         }
1209 
1210         /// <summary>
1211         /// Will display the target started event which was deferred until the first visible message for the target is ready to be displayed
1212         /// </summary>
DisplayDeferredTargetStartedEvent(BuildEventContext e)1213         private void DisplayDeferredTargetStartedEvent(BuildEventContext e)
1214         {
1215             if (showOnlyErrors || showOnlyWarnings) return;
1216 
1217             // Get the deferred target started event
1218             TargetStartedEventMinimumFields targetStartedEvent = buildEventManager.GetTargetStartedEvent(e);
1219 
1220             //Make sure we have not shown the event before
1221             if (targetStartedEvent != null && !targetStartedEvent.ShowTargetFinishedEvent)
1222             {
1223                 //Since the target started event has been shows, the target finished event should also be shown
1224                 targetStartedEvent.ShowTargetFinishedEvent = true;
1225 
1226                 // If there are any other started events waiting and we are the first message, show them
1227                 DisplayDeferredStartedEvents(targetStartedEvent.ProjectBuildEventContext);
1228 
1229                 WriteLinePrefix(targetStartedEvent.ProjectBuildEventContext, targetStartedEvent.TimeStamp, false);
1230 
1231                 setColor(ConsoleColor.Cyan);
1232 
1233                 ProjectStartedEventMinimumFields startedEvent = buildEventManager.GetProjectStartedEvent(e);
1234                 ErrorUtilities.VerifyThrow(startedEvent != null, "Project Started should not be null in deferred target started");
1235                 string currentProjectFile = startedEvent.ProjectFile == null ? string.Empty : startedEvent.ProjectFile;
1236 
1237                 string targetName = null;
1238                 if (IsVerbosityAtLeast(LoggerVerbosity.Diagnostic) || showEventId)
1239                 {
1240                    targetName = ResourceUtilities.FormatResourceString("TargetMessageWithId", targetStartedEvent.TargetName, targetStartedEvent.ProjectBuildEventContext.TargetId);
1241                 }
1242                 else
1243                 {
1244                     targetName = targetStartedEvent.TargetName;
1245                 }
1246 
1247                 if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
1248                 {
1249                     WriteMessageAligned(ResourceUtilities.FormatResourceString("TargetStartedFromFileInProject", targetName, targetStartedEvent.TargetFile, currentProjectFile), true);
1250                 }
1251                 else
1252                 {
1253                     WriteMessageAligned(ResourceUtilities.FormatResourceString("TargetStartedPrefixInProject", targetName, currentProjectFile), true);
1254                 }
1255 
1256                 resetColor();
1257                 ShownBuildEventContext(e);
1258             }
1259         }
1260 
1261         /// <summary>
1262         /// Will display the project started event which was deferred until the first visible message for the project is ready to be displayed
1263         /// </summary>
DisplayDeferredProjectStartedEvent(BuildEventContext e)1264         private void DisplayDeferredProjectStartedEvent(BuildEventContext e)
1265         {
1266             if (showOnlyErrors || showOnlyWarnings) return;
1267 
1268             if (!SkipProjectStartedText)
1269             {
1270                 // Get the project started event which matched the passed in event context
1271                 ProjectStartedEventMinimumFields projectStartedEvent = buildEventManager.GetProjectStartedEvent(e);
1272 
1273                 // Make sure the project started event has not been show yet
1274                 if (projectStartedEvent != null && !projectStartedEvent.ShowProjectFinishedEvent)
1275                 {
1276                     projectStartedEvent.ShowProjectFinishedEvent = true;
1277 
1278                     ProjectStartedEventMinimumFields parentStartedEvent = projectStartedEvent.ParentProjectStartedEvent;
1279                     if (parentStartedEvent != null)
1280                     {
1281                         //Make sure that if there are any events deferred on this event to show them first
1282                         DisplayDeferredStartedEvents(parentStartedEvent.ProjectBuildEventContext);
1283                     }
1284 
1285                     string current = projectStartedEvent.ProjectFile == null ? string.Empty : projectStartedEvent.ProjectFile;
1286                     string previous = parentStartedEvent == null ? null : parentStartedEvent.ProjectFile;
1287                     string targetNames = projectStartedEvent.TargetNames;
1288 
1289                     // Log 0-based node id's, where 0 is the parent. This is a little unnatural for the reader,
1290                     // but avoids confusion by being consistent with the Engine and any error messages it may produce.
1291                     int currentProjectNodeId = (projectStartedEvent.ProjectBuildEventContext.NodeId);
1292                     if (previous == null)
1293                     {
1294                         WriteLinePrefix(projectStartedEvent.FullProjectKey, projectStartedEvent.TimeStamp, false);
1295                         setColor(ConsoleColor.Cyan);
1296                         string message = string.Empty;
1297                         if ((targetNames == null) || (targetNames.Length == 0))
1298                         {
1299                             message = ResourceUtilities.FormatResourceString("ProjectStartedTopLevelProjectWithDefaultTargets", current, currentProjectNodeId);
1300                         }
1301                         else
1302                         {
1303                             message = ResourceUtilities.FormatResourceString("ProjectStartedTopLevelProjectWithTargetNames", current, currentProjectNodeId, targetNames);
1304                         }
1305 
1306                         WriteMessageAligned(message, true);
1307                         resetColor();
1308                     }
1309                     else
1310                     {
1311                         WriteLinePrefix(parentStartedEvent.FullProjectKey, parentStartedEvent.TimeStamp, false);
1312                         setColor(ConsoleColor.Cyan);
1313                         if ((targetNames == null) || (targetNames.Length == 0))
1314                         {
1315                             WriteMessageAligned(ResourceUtilities.FormatResourceString("ProjectStartedWithDefaultTargetsMultiProc", previous, parentStartedEvent.FullProjectKey, current, projectStartedEvent.FullProjectKey, currentProjectNodeId), true);
1316                         }
1317                         else
1318                         {
1319                             WriteMessageAligned(ResourceUtilities.FormatResourceString("ProjectStartedWithTargetsMultiProc", previous, parentStartedEvent.FullProjectKey, current, projectStartedEvent.FullProjectKey, currentProjectNodeId, targetNames), true);
1320                         }
1321                         resetColor();
1322                     }
1323 
1324                     ShownBuildEventContext(e);
1325                 }
1326             }
1327         }
1328 
1329         /// <summary>
1330         /// Prints a custom event
1331         /// </summary>
CustomEventHandler(object sender, CustomBuildEventArgs e)1332         public override void CustomEventHandler(object sender, CustomBuildEventArgs e)
1333         {
1334             if (showOnlyErrors || showOnlyWarnings) return;
1335 
1336             ErrorUtilities.VerifyThrowArgumentNull(e.BuildEventContext, "BuildEventContext");
1337             if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
1338             {
1339                 // ignore custom events with null messages -- some other
1340                 // logger will handle them appropriately
1341                 if (e.Message != null)
1342                 {
1343                     DisplayDeferredStartedEvents(e.BuildEventContext);
1344                     WriteLinePrefix(e.BuildEventContext, e.Timestamp, false);
1345                     WriteMessageAligned(e.Message, true);
1346                     ShownBuildEventContext(e.BuildEventContext);
1347                 }
1348             }
1349         }
1350 
1351         /// <summary>
1352         /// Writes message contextual information for each message displayed on the console
1353         /// </summary>
WriteLinePrefix(BuildEventContext e, DateTime eventTimeStamp, bool isMessagePrefix)1354         private void WriteLinePrefix(BuildEventContext e, DateTime eventTimeStamp, bool isMessagePrefix)
1355         {
1356             WriteLinePrefix(GetFullProjectKey(e).ToString(verbosity), eventTimeStamp, isMessagePrefix);
1357         }
1358 
WriteLinePrefix(string key, DateTime eventTimeStamp, bool isMessagePrefix)1359         private void WriteLinePrefix(string key, DateTime eventTimeStamp, bool isMessagePrefix)
1360         {
1361             // Dont want any prefix for single proc
1362             if (numberOfProcessors == 1)
1363             {
1364                 return;
1365             }
1366 
1367             setColor(ConsoleColor.Cyan);
1368 
1369             string context = string.Empty;
1370             if (showTimeStamp || IsVerbosityAtLeast(LoggerVerbosity.Diagnostic))
1371             {
1372                 context = LogFormatter.FormatLogTimeStamp(eventTimeStamp);
1373             }
1374 
1375             string prefixString = string.Empty;
1376 
1377             if (!isMessagePrefix || IsVerbosityAtLeast(LoggerVerbosity.Detailed))
1378             {
1379                 prefixString = ResourceUtilities.FormatResourceString("BuildEventContext", context, key) + ">";
1380             }
1381             else
1382             {
1383                 prefixString = ResourceUtilities.FormatResourceString("BuildEventContext", context, string.Empty) + " ";
1384             }
1385 
1386             WritePretty(prefixString);
1387             resetColor();
1388 
1389             if (prefixWidth == 0)
1390             {
1391                 prefixWidth = prefixString.Length;
1392             }
1393         }
1394 
1395         /// <summary>
1396         /// Extract the full project key from the BuildEventContext
1397         /// </summary>
GetFullProjectKey(BuildEventContext e)1398         private ProjectFullKey GetFullProjectKey(BuildEventContext e)
1399         {
1400             ProjectStartedEventMinimumFields startedEvent = null;
1401 
1402             if (e != null)
1403             {
1404                 startedEvent = buildEventManager.GetProjectStartedEvent(e);
1405             }
1406 
1407             //Project started event can be null, if the message has come before the project started event
1408             // or the message is not part of a project such as if the message came from the engine
1409             if (startedEvent == null)
1410             {
1411                 return new ProjectFullKey(0, 0);
1412             }
1413             else
1414             {
1415                 return new ProjectFullKey(startedEvent.ProjectKey, startedEvent.EntryPointKey);
1416             }
1417         }
1418 
1419         /// <summary>
1420         /// Returns a performance counter for a given scope (either task name or target name)
1421         /// from the given table.
1422         /// </summary>
1423         /// <param name="scopeName">Task name or target name.</param>
1424         /// <param name="table">Table that has tasks or targets.</param>
GetPerformanceCounter(string scopeName, ref Hashtable table)1425         internal new static MPPerformanceCounter GetPerformanceCounter(string scopeName, ref Hashtable table)
1426         {
1427             // Lazily construct the performance counter table.
1428             if (table == null)
1429             {
1430                 table = new Hashtable(StringComparer.OrdinalIgnoreCase);
1431             }
1432 
1433             MPPerformanceCounter counter = (MPPerformanceCounter)table[scopeName];
1434 
1435             // And lazily construct the performance counter itself.
1436             if (counter == null)
1437             {
1438                 counter = new MPPerformanceCounter(scopeName);
1439                 table[scopeName] = counter;
1440             }
1441 
1442             return counter;
1443         }
1444         #endregion
1445 
1446         #region InternalClass
1447         /// <summary>
1448         /// Stores and calculates the performance numbers for the different events
1449         /// </summary>
1450         internal class MPPerformanceCounter : PerformanceCounter
1451         {
1452             // Set of performance counters for a project
1453             private Hashtable internalPerformanceCounters;
1454             // Dictionary mapping event context to the start number of ticks, this will be used to calculate the amount
1455             // of time between the start of the performance counter and the end
1456             // An object is being used to box the start time long value to prevent jitting when this code path is executed.
1457             private Dictionary<BuildEventContext, object> startedEvent;
1458             private int messageIdentLevel = 2;
1459 
1460             internal int MessageIdentLevel
1461             {
1462                 get { return messageIdentLevel; }
1463                 set { messageIdentLevel = value; }
1464             }
1465 
MPPerformanceCounter(string scopeName)1466             internal MPPerformanceCounter(string scopeName)
1467                 : base(scopeName)
1468             {
1469                 // Do Nothing
1470             }
1471 
1472             /// <summary>
1473             /// Add a started event to the performance counter, by adding the event this sets the start time of the performance counter
1474             /// </summary>
AddEventStarted(string projectTargetNames, BuildEventContext buildEventContext, DateTime eventTimeStamp, IEqualityComparer<BuildEventContext> comparer)1475             internal void AddEventStarted(string projectTargetNames, BuildEventContext buildEventContext, DateTime eventTimeStamp, IEqualityComparer<BuildEventContext> comparer)
1476             {
1477                 //If the projectTargetNames are set then we should be a projectstarted event
1478                 if (!string.IsNullOrEmpty(projectTargetNames))
1479                 {
1480                     // Create a new performance counter for the project entry point to calculate how much time and how many calls
1481                     // were made to the entry point
1482                     MPPerformanceCounter entryPoint = GetPerformanceCounter(projectTargetNames, ref internalPerformanceCounters);
1483                     entryPoint.AddEventStarted(null, buildEventContext, eventTimeStamp, compareContextNodeIdTargetId);
1484                     // Indent the output so it is intented with respect to its parent project
1485                     entryPoint.messageIdentLevel = 7;
1486                 }
1487 
1488                 if (startedEvent == null)
1489                 {
1490                     if (comparer == null)
1491                     {
1492                         startedEvent = new Dictionary<BuildEventContext, object>();
1493                     }
1494                     else
1495                     {
1496                         startedEvent = new Dictionary<BuildEventContext, object>(comparer);
1497                     }
1498                 }
1499 
1500                 if (!startedEvent.ContainsKey(buildEventContext))
1501                 {
1502                     startedEvent.Add(buildEventContext, (object)eventTimeStamp.Ticks);
1503                     ++calls;
1504                 }
1505             }
1506 
1507             /// <summary>
1508             ///  Add a finished event to the performance counter, so perf numbers can be calculated
1509             /// </summary>
AddEventFinished(string projectTargetNames, BuildEventContext buildEventContext, DateTime eventTimeStamp)1510             internal void AddEventFinished(string projectTargetNames, BuildEventContext buildEventContext, DateTime eventTimeStamp)
1511             {
1512 
1513                 if (!string.IsNullOrEmpty(projectTargetNames))
1514                 {
1515                     MPPerformanceCounter entryPoint = GetPerformanceCounter(projectTargetNames, ref internalPerformanceCounters);
1516                     entryPoint.AddEventFinished(null, buildEventContext, eventTimeStamp);
1517                 }
1518 
1519                 if (startedEvent == null)
1520                 {
1521                     Debug.Assert(startedEvent != null, "Cannot have finished counter without started counter. ");
1522                 }
1523 
1524                 if (startedEvent.ContainsKey(buildEventContext))
1525                 {
1526                     // Calculate the amount of time spent in the event based on the time stamp of when
1527                     // the started event was created and when the finished event was created
1528                     elapsedTime += (TimeSpan.FromTicks(eventTimeStamp.Ticks - (long)startedEvent[buildEventContext]));
1529                     startedEvent.Remove(buildEventContext);
1530                 }
1531             }
1532 
1533             /// <summary>
1534             /// Print out the performance counter message
1535             /// </summary>
PrintCounterMessage(WriteLinePrettyFromResourceDelegate WriteLinePrettyFromResource, ColorSetter setColor, ColorResetter resetColor)1536             internal override void PrintCounterMessage(WriteLinePrettyFromResourceDelegate WriteLinePrettyFromResource, ColorSetter setColor, ColorResetter resetColor)
1537             {
1538                 // round: submillisecond values are not meaningful
1539                 string time = String.Format(CultureInfo.CurrentCulture,
1540                        "{0,5}", Math.Round(elapsedTime.TotalMilliseconds, 0));
1541 
1542                 WriteLinePrettyFromResource
1543                    (
1544                        messageIdentLevel,
1545                        "PerformanceLine",
1546                        time,
1547                        String.Format(CultureInfo.CurrentCulture,
1548                                "{0,-40}" /* pad to 40 align left */, scopeName),
1549                        String.Format(CultureInfo.CurrentCulture,
1550                                "{0,3}", calls)
1551                    );
1552 
1553                 if (internalPerformanceCounters != null && internalPerformanceCounters.Count > 0)
1554                 {
1555                     // For each of the entry points in the project print out the performance numbers for them
1556                     foreach (MPPerformanceCounter counter in internalPerformanceCounters.Values)
1557                     {
1558                         setColor(ConsoleColor.White);
1559                         counter.PrintCounterMessage(WriteLinePrettyFromResource, setColor, resetColor);
1560                         resetColor();
1561                     }
1562                 }
1563             }
1564         }
1565         #endregion
1566 
1567         #region internal MemberData
1568         private static ComparerContextNodeId<BuildEventContext> compareContextNodeId = new ComparerContextNodeId<BuildEventContext>();
1569         private static ComparerContextNodeIdTargetId<BuildEventContext> compareContextNodeIdTargetId = new ComparerContextNodeIdTargetId<BuildEventContext>();
1570         private BuildEventContext lastDisplayedBuildEventContext;
1571         private int bufferWidth = -1;
1572         private object lockObject = new Object();
1573         private int prefixWidth = 0;
1574         private ProjectFullKey lastProjectFullKey = new ProjectFullKey(-1, -1);
1575         private bool alignMessages;
1576         private bool forceNoAlign;
1577         private bool showEventId;
1578         // According to the documentaion for ENABLE_PROCESSED_OUTPUT tab width for the console is 8 characters
1579         private const string consoleTab = "        ";
1580         #endregion
1581 
1582         #region Per-build Members
1583         //Holds messages that were going to be shown before the project started event, buffer them until the project started event is shown
1584         private Dictionary<BuildEventContext, List<BuildMessageEventArgs>> deferredMessages;
1585         private BuildEventManager buildEventManager;
1586         //  Has the build started
1587         private bool hasBuildStarted;
1588         private bool showCommandline;
1589         private bool showTimeStamp;
1590         #endregion
1591     }
1592 }
1593