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 
11 using Microsoft.Build.Framework;
12 using Microsoft.Build.BuildEngine.Shared;
13 
14 namespace Microsoft.Build.BuildEngine
15 {
16     #region Delegates
17 
18     /// <summary>
19     /// Delegate to use for writing a string to some location like
20     /// the console window or the IDE build window.
21     /// </summary>
22     /// <param name="message"></param>
WriteHandler(string message)23     public delegate void WriteHandler(string message);
24 
25     /// <summary>
26     /// Type of delegate used to set console color.
27     /// </summary>
28     /// <param name="color">Text color</param>
ColorSetter(ConsoleColor color)29     public delegate void ColorSetter(ConsoleColor color);
30 
31     /// <summary>
32     /// Type of delegate used to reset console color.
33     /// </summary>
ColorResetter()34     public delegate void ColorResetter();
35 
36     #endregion
37 
38     /// <summary>
39     /// This class implements the default logger that outputs event data
40     /// to the console (stdout).
41     /// It is a facade: it creates, wraps and delegates to a kind of BaseConsoleLogger,
42     /// either SerialConsoleLogger or ParallelConsoleLogger.
43     /// </summary>
44     /// <remarks>This class is not thread safe.</remarks>
45     public class ConsoleLogger : INodeLogger
46     {
47         private BaseConsoleLogger consoleLogger;
48         private int numberOfProcessors = 1;
49         private LoggerVerbosity verbosity;
50         private WriteHandler write;
51         private ColorSetter colorSet;
52         private ColorResetter colorReset;
53         private string parameters;
54         private bool skipProjectStartedText = false;
55         private bool? showSummary;
56 
57 
58         #region Constructors
59 
60         /// <summary>
61         /// Default constructor.
62         /// </summary>
ConsoleLogger()63         public ConsoleLogger()
64             : this(LoggerVerbosity.Normal)
65         {
66             // do nothing
67         }
68 
69         /// <summary>
70         /// Create a logger instance with a specific verbosity.  This logs to
71         /// the default console.
72         /// </summary>
73         /// <param name="verbosity">Verbosity level.</param>
ConsoleLogger(LoggerVerbosity verbosity)74         public ConsoleLogger(LoggerVerbosity verbosity)
75             :
76             this
77             (
78                 verbosity,
79                 new WriteHandler(Console.Out.Write),
80                 new ColorSetter(SetColor),
81                 new ColorResetter(Console.ResetColor)
82             )
83         {
84             // do nothing
85         }
86 
87         /// <summary>
88         /// Initializes the logger, with alternate output handlers.
89         /// </summary>
90         /// <param name="verbosity"></param>
91         /// <param name="write"></param>
92         /// <param name="colorSet"></param>
93         /// <param name="colorReset"></param>
ConsoleLogger( LoggerVerbosity verbosity, WriteHandler write, ColorSetter colorSet, ColorResetter colorReset )94         public ConsoleLogger
95         (
96             LoggerVerbosity verbosity,
97             WriteHandler write,
98             ColorSetter colorSet,
99             ColorResetter colorReset
100         )
101         {
102             ErrorUtilities.VerifyThrowArgumentNull(write, "write");
103             this.verbosity = verbosity;
104             this.write = write;
105             this.colorSet = colorSet;
106             this.colorReset = colorReset;
107         }
108 
109         /// <summary>
110         /// This is called by every event handler for compat reasons -- see DDB #136924
111         /// However it will skip after the first call
112         /// </summary>
InitializeBaseConsoleLogger()113         private void InitializeBaseConsoleLogger()
114         {
115             if (consoleLogger == null)
116             {
117                 bool useMPLogger = false;
118                 if (!string.IsNullOrEmpty(parameters))
119                 {
120                     string [] parameterComponents = parameters.Split(BaseConsoleLogger.parameterDelimiters);
121                     for (int param = 0; param < parameterComponents.Length; param++)
122                     {
123                         if (parameterComponents[param].Length > 0)
124                         {
125                             if (0 == String.Compare(parameterComponents[param], "ENABLEMPLOGGING", StringComparison.OrdinalIgnoreCase))
126                             {
127                                 useMPLogger = true;
128                             }
129                             if (0 == String.Compare(parameterComponents[param], "DISABLEMPLOGGING", StringComparison.OrdinalIgnoreCase))
130                             {
131                                 useMPLogger = false;
132                             }
133                         }
134                     }
135                 }
136 
137                 if (numberOfProcessors == 1 && !useMPLogger)
138                 {
139                     consoleLogger = new SerialConsoleLogger(verbosity, write, colorSet, colorReset);
140                 }
141                 else
142                 {
143                     consoleLogger = new ParallelConsoleLogger(verbosity, write, colorSet, colorReset);
144                 }
145 
146                 if(!string.IsNullOrEmpty(parameters))
147                 {
148                     consoleLogger.Parameters = parameters;
149                     parameters = null;
150                 }
151 
152                 if (showSummary != null)
153                 {
154                     consoleLogger.ShowSummary = (bool)showSummary;
155                 }
156 
157                 consoleLogger.SkipProjectStartedText = skipProjectStartedText;
158             }
159         }
160 
161         #endregion
162 
163         #region Properties
164 
165         /// <summary>
166         /// Gets or sets the level of detail to show in the event log.
167         /// </summary>
168         /// <value>Verbosity level.</value>
169         public LoggerVerbosity Verbosity
170         {
171             get
172             {
173                 return consoleLogger == null ? verbosity : consoleLogger.Verbosity;
174             }
175 
176             set
177             {
178                 if (consoleLogger == null)
179                 {
180                     verbosity = value;
181                 }
182                 else
183                 {
184                     consoleLogger.Verbosity = value;
185                 }
186             }
187         }
188 
189         /// <summary>
190         /// The console logger takes a single parameter to suppress the output of the errors
191         /// and warnings summary at the end of a build.
192         /// </summary>
193         /// <value>null</value>
194         public string Parameters
195         {
196             get
197             {
198                 return consoleLogger == null ? parameters : consoleLogger.Parameters;
199             }
200 
201             set
202             {
203                 if (consoleLogger == null)
204                 {
205                     parameters = value;
206                 }
207                 else
208                 {
209                     consoleLogger.Parameters = value;
210                 }
211             }
212         }
213 
214         /// <summary>
215         /// Suppresses the display of project headers. Project headers are
216         /// displayed by default unless this property is set.
217         /// </summary>
218         /// <remarks>This is only needed by the IDE logger.</remarks>
219         public bool SkipProjectStartedText
220         {
221             get
222             {
223                 return consoleLogger == null ? skipProjectStartedText : consoleLogger.SkipProjectStartedText;
224             }
225 
226             set
227             {
228                 if (consoleLogger == null)
229                 {
230                     skipProjectStartedText = value;
231                 }
232                 else
233                 {
234                     consoleLogger.SkipProjectStartedText = value;
235                 }
236             }
237         }
238 
239         /// <summary>
240         /// Suppresses the display of error and warnings summary.
241         /// </summary>
242         public bool ShowSummary
243         {
244             get
245             {
246                 return (consoleLogger == null ? showSummary : consoleLogger.ShowSummary) ?? false;
247             }
248 
249             set
250             {
251                 if (consoleLogger == null)
252                 {
253                     showSummary = value;
254                 }
255                 else
256                 {
257                     consoleLogger.ShowSummary = value;
258 
259                 }
260             }
261         }
262 
263         /// <summary>
264         /// Provide access to the write hander delegate so that it can be redirected
265         /// if necessary (e.g. to a file)
266         /// </summary>
267         protected WriteHandler WriteHandler
268         {
269             get
270             {
271                 return consoleLogger == null ? write : consoleLogger.write;
272             }
273 
274             set
275             {
276                 if (consoleLogger == null)
277                 {
278                     write = value;
279                 }
280                 else
281                 {
282                     consoleLogger.write = value;
283                 }
284             }
285         }
286 
287         #endregion
288 
289         #region Methods
290 
291         /// <summary>
292         /// Apply a parameter.
293         /// NOTE: This method was public by accident in Whidbey, so it cannot be made internal now. It has
294         /// no good reason for being public.
295         /// </summary>
ApplyParameter(string parameterName, string parameterValue)296         public void ApplyParameter(string parameterName, string parameterValue)
297         {
298             ErrorUtilities.VerifyThrowInvalidOperation(consoleLogger != null, "MustCallInitializeBeforeApplyParameter");
299             consoleLogger.ApplyParameter(parameterName, parameterValue);
300         }
301 
302         /// <summary>
303         /// Signs up the console logger for all build events.
304         /// </summary>
305         /// <param name="eventSource">Available events.</param>
Initialize(IEventSource eventSource)306         public virtual void Initialize(IEventSource eventSource)
307         {
308             InitializeBaseConsoleLogger();
309             consoleLogger.Initialize(eventSource);
310         }
311 
Initialize(IEventSource eventSource, int nodeCount)312         public virtual void Initialize(IEventSource eventSource, int nodeCount)
313         {
314             this.numberOfProcessors = nodeCount;
315             InitializeBaseConsoleLogger();
316             consoleLogger.Initialize(eventSource, nodeCount);
317         }
318 
319         /// <summary>
320         /// The console logger does not need to release any resources.
321         /// This method does nothing.
322         /// </summary>
Shutdown()323         public virtual void Shutdown()
324         {
325             if (consoleLogger != null)
326             {
327                 consoleLogger.Shutdown();
328             }
329         }
330 
331         /// <summary>
332         /// Handler for build started events
333         /// </summary>
334         /// <param name="sender">sender (should be null)</param>
335         /// <param name="e">event arguments</param>
BuildStartedHandler(object sender, BuildStartedEventArgs e)336         public void BuildStartedHandler(object sender, BuildStartedEventArgs e)
337         {
338             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
339 
340             consoleLogger.BuildStartedHandler(sender, e);
341         }
342 
343         /// <summary>
344         /// Handler for build finished events
345         /// </summary>
346         /// <param name="sender">sender (should be null)</param>
347         /// <param name="e">event arguments</param>
BuildFinishedHandler(object sender, BuildFinishedEventArgs e)348         public void BuildFinishedHandler(object sender, BuildFinishedEventArgs e)
349         {
350             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
351 
352             consoleLogger.BuildFinishedHandler(sender, e);
353         }
354 
355         /// <summary>
356         /// Handler for project started events
357         /// </summary>
358         /// <param name="sender">sender (should be null)</param>
359         /// <param name="e">event arguments</param>
ProjectStartedHandler(object sender, ProjectStartedEventArgs e)360         public void ProjectStartedHandler(object sender, ProjectStartedEventArgs e)
361         {
362             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
363 
364             consoleLogger.ProjectStartedHandler(sender, e);
365         }
366 
367         /// <summary>
368         /// Handler for project finished events
369         /// </summary>
370         /// <param name="sender">sender (should be null)</param>
371         /// <param name="e">event arguments</param>
ProjectFinishedHandler(object sender, ProjectFinishedEventArgs e)372         public void ProjectFinishedHandler(object sender, ProjectFinishedEventArgs e)
373         {
374             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
375 
376             consoleLogger.ProjectFinishedHandler(sender, e);
377         }
378 
379         /// <summary>
380         /// Handler for target started events
381         /// </summary>
382         /// <param name="sender">sender (should be null)</param>
383         /// <param name="e">event arguments</param>
TargetStartedHandler(object sender, TargetStartedEventArgs e)384         public void TargetStartedHandler(object sender, TargetStartedEventArgs e)
385         {
386             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
387 
388             consoleLogger.TargetStartedHandler(sender, e);
389         }
390 
391         /// <summary>
392         /// Handler for target finished events
393         /// </summary>
394         /// <param name="sender">sender (should be null)</param>
395         /// <param name="e">event arguments</param>
TargetFinishedHandler(object sender, TargetFinishedEventArgs e)396         public void TargetFinishedHandler(object sender, TargetFinishedEventArgs e)
397         {
398             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
399 
400             consoleLogger.TargetFinishedHandler(sender, e);
401         }
402 
403         /// <summary>
404         /// Handler for task started events
405         /// </summary>
406         /// <param name="sender">sender (should be null)</param>
407         /// <param name="e">event arguments</param>
TaskStartedHandler(object sender, TaskStartedEventArgs e)408         public void TaskStartedHandler(object sender, TaskStartedEventArgs e)
409         {
410             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
411 
412             consoleLogger.TaskStartedHandler(sender, e);
413         }
414 
415         /// <summary>
416         /// Handler for task finished events
417         /// </summary>
418         /// <param name="sender">sender (should be null)</param>
419         /// <param name="e">event arguments</param>
TaskFinishedHandler(object sender, TaskFinishedEventArgs e)420         public void TaskFinishedHandler(object sender, TaskFinishedEventArgs e)
421         {
422             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
423 
424             consoleLogger.TaskFinishedHandler(sender, e);
425         }
426 
427         /// <summary>
428         /// Prints an error event
429         /// </summary>
ErrorHandler(object sender, BuildErrorEventArgs e)430         public void ErrorHandler(object sender, BuildErrorEventArgs e)
431         {
432             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
433 
434             consoleLogger.ErrorHandler(sender, e);
435         }
436 
437         /// <summary>
438         /// Prints a warning event
439         /// </summary>
WarningHandler(object sender, BuildWarningEventArgs e)440         public void WarningHandler(object sender, BuildWarningEventArgs e)
441         {
442             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
443 
444             consoleLogger.WarningHandler(sender, e);
445         }
446 
447         /// <summary>
448         /// Prints a message event
449         /// </summary>
MessageHandler(object sender, BuildMessageEventArgs e)450         public void MessageHandler(object sender, BuildMessageEventArgs e)
451         {
452             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
453 
454             consoleLogger.MessageHandler(sender, e);
455         }
456 
457         /// <summary>
458         /// Prints a custom event
459         /// </summary>
CustomEventHandler(object sender, CustomBuildEventArgs e)460         public void CustomEventHandler(object sender, CustomBuildEventArgs e)
461         {
462             InitializeBaseConsoleLogger(); // for compat: see DDB#136924
463 
464             consoleLogger.CustomEventHandler(sender, e);
465         }
466 
467         /// <summary>
468         /// Sets foreground color to color specified
469         /// </summary>
470         /// <param name="c">foreground color</param>
SetColor(ConsoleColor c)471         internal static void SetColor(ConsoleColor c)
472         {
473             Console.ForegroundColor =
474                         TransformColor(c, Console.BackgroundColor);
475         }
476 
477         /// <summary>
478         /// Changes the foreground color to black if the foreground is the
479         /// same as the background. Changes the foreground to white if the
480         /// background is black.
481         /// </summary>
482         /// <param name="foreground">foreground color for black</param>
483         /// <param name="background">current background</param>
TransformColor(ConsoleColor foreground, ConsoleColor background)484         private static ConsoleColor TransformColor(ConsoleColor foreground,
485                                                    ConsoleColor background)
486         {
487             ConsoleColor result = foreground; //typically do nothing ...
488 
489             if (foreground == background)
490             {
491                 if (background != ConsoleColor.Black)
492                 {
493                     result = ConsoleColor.Black;
494                 }
495                 else
496                 {
497                     result = ConsoleColor.Gray;
498                 }
499             }
500 
501             return result;
502         }
503 
504         #endregion
505     }
506 }
507