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