|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using ColorResetter = Microsoft.Build.Logging.ColorResetter;
using ColorSetter = Microsoft.Build.Logging.ColorSetter;
using WriteHandler = Microsoft.Build.Logging.WriteHandler;
#nullable disable
namespace Microsoft.Build.BackEnd.Logging
{
internal class SerialConsoleLogger : BaseConsoleLogger
{
#region Constructors
/// <summary>
/// Default constructor.
/// </summary>
public SerialConsoleLogger()
: this(LoggerVerbosity.Normal)
{
// do nothing
}
/// <summary>
/// Create a logger instance with a specific verbosity. This logs to
/// the default console.
/// </summary>
/// <param name="verbosity">Verbosity level.</param>
public SerialConsoleLogger(LoggerVerbosity verbosity)
:
this
(
verbosity,
new WriteHandler(Console.Out.Write),
new ColorSetter(SetColor),
new ColorResetter(ResetColor))
{
// do nothing
}
/// <summary>
/// Initializes the logger, with alternate output handlers.
/// </summary>
/// <param name="verbosity"></param>
/// <param name="write"></param>
/// <param name="colorSet"></param>
/// <param name="colorReset"></param>
public SerialConsoleLogger(
LoggerVerbosity verbosity,
WriteHandler write,
ColorSetter colorSet,
ColorResetter colorReset)
{
InitializeConsoleMethods(verbosity, write, colorSet, colorReset);
}
#endregion
#region Methods
/// <summary>
/// Reset the states of per-build member variables
/// VSW#516376
/// </summary>
internal override void ResetConsoleLoggerState()
{
if (ShowSummary == true)
{
errorList = new List<BuildErrorEventArgs>();
warningList = new List<BuildWarningEventArgs>();
}
else
{
errorList = null;
warningList = null;
}
errorCount = 0;
warningCount = 0;
projectPerformanceCounters = null;
targetPerformanceCounters = null;
taskPerformanceCounters = null;
}
/// <summary>
/// Handler for build started events
/// </summary>
/// <param name="sender">sender (should be null)</param>
/// <param name="e">event arguments</param>
public override void BuildStartedHandler(object sender, BuildStartedEventArgs e)
{
buildStarted = e.Timestamp;
if (ShowSummary == true)
{
WriteLinePrettyFromResource("BuildStartedWithTime", e.Timestamp);
}
if (Traits.LogAllEnvironmentVariables)
{
WriteEnvironment(e.BuildEnvironment);
}
else
{
WriteEnvironment(e.BuildEnvironment?.Where(kvp => EnvironmentUtilities.IsWellKnownEnvironmentDerivedProperty(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
}
}
/// <summary>
/// Handler for build finished events
/// </summary>
/// <param name="sender">sender (should be null)</param>
/// <param name="e">event arguments</param>
public override void BuildFinishedHandler(object sender, BuildFinishedEventArgs e)
{
// Show the performance summary if the verbosity is diagnostic or the user specifically asked for it
// with a logger parameter.
if (this.showPerfSummary)
{
ShowPerfSummary();
}
if (ShowSummary == true)
{
if (e.Succeeded)
{
setColor(ConsoleColor.Green);
}
// Write the "Build Finished" event.
WriteNewLine();
WriteLinePretty(e.Message);
resetColor();
}
// The decision whether or not to show a summary at this verbosity
// was made during initialization. We just do what we're told.
if (ShowSummary == true)
{
ShowErrorWarningSummary();
if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
{
// Emit text like:
// 1 Warning(s)
// 0 Error(s)
// Don't color the line if it's zero. (Per Whidbey behavior.)
if (warningCount > 0)
{
setColor(ConsoleColor.Yellow);
}
WriteLinePrettyFromResource(2, "WarningCount", warningCount);
resetColor();
if (errorCount > 0)
{
setColor(ConsoleColor.Red);
}
WriteLinePrettyFromResource(2, "ErrorCount", errorCount);
resetColor();
}
}
if (ShowSummary == true)
{
string timeElapsed = LogFormatter.FormatTimeSpan(e.Timestamp - buildStarted);
WriteNewLine();
WriteLinePrettyFromResource("TimeElapsed", timeElapsed);
}
ResetConsoleLoggerState();
}
/// <summary>
/// At the end of the build, repeats the errors and warnings that occurred
/// during the build, and displays the error count and warning count.
/// </summary>
private void ShowErrorWarningSummary()
{
if (warningCount == 0 && errorCount == 0)
{
return;
}
// Make some effort to distinguish the summary from the previous output
WriteNewLine();
if (warningCount > 0)
{
setColor(ConsoleColor.Yellow);
foreach (BuildWarningEventArgs warningEventArgs in warningList)
{
WriteLinePretty(EventArgsFormatting.FormatEventMessage(warningEventArgs, showProjectFile));
}
}
if (errorCount > 0)
{
setColor(ConsoleColor.Red);
foreach (BuildErrorEventArgs errorEventArgs in errorList)
{
WriteLinePretty(EventArgsFormatting.FormatEventMessage(errorEventArgs, showProjectFile));
}
}
resetColor();
}
/// <summary>
/// Handler for project started events
/// </summary>
/// <param name="sender">sender (should be null)</param>
/// <param name="e">event arguments</param>
public override void ProjectStartedHandler(object sender, ProjectStartedEventArgs e)
{
if (!contextStack.IsEmpty())
{
this.VerifyStack(contextStack.Peek().type == FrameType.Target, "Bad stack -- Top is project {0}", contextStack.Peek().ID);
}
// if verbosity is normal, detailed or diagnostic
if (IsVerbosityAtLeast(LoggerVerbosity.Normal) && ShowSummary != false)
{
ShowDeferredMessages();
// check for stack corruption
if (!contextStack.IsEmpty())
{
this.VerifyStack(contextStack.Peek().type == FrameType.Target, "Bad stack -- Top is target {0}", contextStack.Peek().ID);
}
contextStack.Push(new Frame(FrameType.Project,
false, // message not yet displayed
this.currentIndentLevel,
e.ProjectFile,
e.TargetNames,
null,
GetCurrentlyBuildingProjectFile()));
WriteProjectStarted();
}
else
{
contextStack.Push(new Frame(FrameType.Project,
false, // message not yet displayed
this.currentIndentLevel,
e.ProjectFile,
e.TargetNames,
null,
GetCurrentlyBuildingProjectFile()));
}
if (this.showPerfSummary)
{
PerformanceCounter counter = GetPerformanceCounter(e.ProjectFile, ref projectPerformanceCounters);
// Place the counter "in scope" meaning the project is executing right now.
counter.InScope = true;
}
if (Verbosity == LoggerVerbosity.Diagnostic && showItemAndPropertyList)
{
if (e.Properties != null)
{
var propertyList = ExtractPropertyList(e.Properties);
WriteProperties(propertyList);
}
if (e.Items != null)
{
SortedList itemList = ExtractItemList(e.Items);
WriteItems(itemList);
}
}
}
/// <summary>
/// Handler for project finished events
/// </summary>
/// <param name="sender">sender (should be null)</param>
/// <param name="e">event arguments</param>
public override void ProjectFinishedHandler(object sender, ProjectFinishedEventArgs e)
{
if (this.showPerfSummary)
{
PerformanceCounter counter = GetPerformanceCounter(e.ProjectFile, ref projectPerformanceCounters);
// Place the counter "in scope" meaning the project is done executing right now.
counter.InScope = false;
}
// if verbosity is detailed or diagnostic,
// or there was an error or warning
if (contextStack.Peek().hasErrorsOrWarnings
|| (IsVerbosityAtLeast(LoggerVerbosity.Detailed)))
{
setColor(ConsoleColor.Cyan);
if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
{
WriteNewLine();
}
WriteLinePretty(e.Message);
resetColor();
}
Frame top = contextStack.Pop();
this.VerifyStack(top.type == FrameType.Project, "Unexpected project frame {0}", top.ID);
this.VerifyStack(top.ID == e.ProjectFile, "Project frame {0} expected, but was {1}.", e.ProjectFile, top.ID);
}
/// <summary>
/// Handler for target started events
/// </summary>
/// <param name="sender">sender (should be null)</param>
/// <param name="e">event arguments</param>
public override void TargetStartedHandler(object sender, TargetStartedEventArgs e)
{
contextStack.Push(new Frame(FrameType.Target,
false,
this.currentIndentLevel,
e.TargetName,
null,
e.TargetFile,
GetCurrentlyBuildingProjectFile()));
// if verbosity is detailed or diagnostic
if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
{
WriteTargetStarted();
}
if (this.showPerfSummary)
{
PerformanceCounter counter = GetPerformanceCounter(e.TargetName, ref targetPerformanceCounters);
// Place the counter "in scope" meaning the target is executing right now.
counter.InScope = true;
}
// Bump up the overall number of indents, so that anything within this target will show up
// indented.
this.currentIndentLevel++;
}
/// <summary>
/// Handler for target finished events
/// </summary>
/// <param name="sender">sender (should be null)</param>
/// <param name="e">event arguments</param>
public override void TargetFinishedHandler(object sender, TargetFinishedEventArgs e)
{
// Done with the target, so shift everything left again.
this.currentIndentLevel--;
if (this.showPerfSummary)
{
PerformanceCounter counter = GetPerformanceCounter(e.TargetName, ref targetPerformanceCounters);
// Place the counter "in scope" meaning the target is done executing right now.
counter.InScope = false;
}
bool targetHasErrorsOrWarnings = contextStack.Peek().hasErrorsOrWarnings;
// if verbosity is diagnostic,
// or there was an error or warning and verbosity is normal or detailed
if ((targetHasErrorsOrWarnings && (IsVerbosityAtLeast(LoggerVerbosity.Normal)))
|| Verbosity == LoggerVerbosity.Diagnostic)
{
setColor(ConsoleColor.Cyan);
if (showTargetOutputs)
{
IEnumerable targetOutputs = e.TargetOutputs;
if (targetOutputs != null)
{
WriteLinePretty(ResourceUtilities.GetResourceString("TargetOutputItemsHeader"));
foreach (ITaskItem item in targetOutputs)
{
WriteLinePretty(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("TargetOutputItem", item.ItemSpec));
}
}
}
WriteLinePretty(e.Message);
resetColor();
}
Frame top = contextStack.Pop();
this.VerifyStack(top.type == FrameType.Target, "bad stack frame type");
this.VerifyStack(top.ID == e.TargetName, "bad stack frame id");
// set the value on the Project frame, for the ProjectFinished handler
if (targetHasErrorsOrWarnings)
{
SetErrorsOrWarningsOnCurrentFrame();
}
}
/// <summary>
/// Handler for task started events
/// </summary>
/// <param name="sender">sender (should be null)</param>
/// <param name="e">event arguments</param>
public override void TaskStartedHandler(object sender, TaskStartedEventArgs e)
{
// if verbosity is detailed or diagnostic
if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
{
setColor(ConsoleColor.Cyan);
WriteLinePretty(e.Message);
resetColor();
}
if (this.showPerfSummary)
{
PerformanceCounter counter = GetPerformanceCounter(e.TaskName, ref taskPerformanceCounters);
// Place the counter "in scope" meaning the task is executing right now.
counter.InScope = true;
}
// Bump up the overall number of indents, so that anything within this task will show up
// indented.
this.currentIndentLevel++;
}
/// <summary>
/// Handler for task finished events
/// </summary>
/// <param name="sender">sender (should be null)</param>
/// <param name="e">event arguments</param>
public override void TaskFinishedHandler(object sender, TaskFinishedEventArgs e)
{
// Done with the task, so shift everything left again.
this.currentIndentLevel--;
if (this.showPerfSummary)
{
PerformanceCounter counter = GetPerformanceCounter(e.TaskName, ref taskPerformanceCounters);
// Place the counter "in scope" meaning the task is done executing.
counter.InScope = false;
}
// if verbosity is detailed or diagnostic
if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
{
setColor(ConsoleColor.Cyan);
WriteLinePretty(e.Message);
resetColor();
}
}
/// <summary>
/// Prints an error event
/// </summary>
public override void ErrorHandler(object sender, BuildErrorEventArgs e)
{
errorCount++;
SetErrorsOrWarningsOnCurrentFrame();
ShowDeferredMessages();
setColor(ConsoleColor.Red);
WriteLinePretty(EventArgsFormatting.FormatEventMessage(e, showProjectFile));
if (ShowSummary == true)
{
errorList.Add(e);
}
resetColor();
}
/// <summary>
/// Prints a warning event
/// </summary>
public override void WarningHandler(object sender, BuildWarningEventArgs e)
{
warningCount++;
SetErrorsOrWarningsOnCurrentFrame();
ShowDeferredMessages();
setColor(ConsoleColor.Yellow);
WriteLinePretty(EventArgsFormatting.FormatEventMessage(e, showProjectFile));
if (ShowSummary == true)
{
warningList.Add(e);
}
resetColor();
}
/// <summary>
/// Prints a message event
/// </summary>
public override void MessageHandler(object sender, BuildMessageEventArgs e)
{
LoggerVerbosity minimumVerbosity = ImportanceToMinimumVerbosity(e.Importance, out bool lightenText);
bool print = IsVerbosityAtLeast(minimumVerbosity);
if (print)
{
ShowDeferredMessages();
if (lightenText)
{
setColor(ConsoleColor.DarkGray);
}
string nonNullMessage = e is EnvironmentVariableReadEventArgs environmentDerivedProperty
? ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("EnvironmentDerivedPropertyRead", environmentDerivedProperty.EnvironmentVariableName, e.Message)
: e.Message ?? String.Empty;
// Include file information if present.
if (e.File != null)
{
nonNullMessage = EventArgsFormatting.FormatEventMessage(e, showProjectFile);
}
WriteLinePretty(nonNullMessage);
if (lightenText)
{
resetColor();
}
}
}
/// <summary>
/// Prints a custom event
/// </summary>
public override void CustomEventHandler(object sender, CustomBuildEventArgs e)
{
// if verbosity is detailed or diagnostic
if (IsVerbosityAtLeast(LoggerVerbosity.Detailed))
{
// ignore custom events with null messages -- some other
// logger will handle them appropriately
if (e.Message != null)
{
ShowDeferredMessages();
WriteLinePretty(e.Message);
}
}
}
public override void StatusEventHandler(object sender, BuildStatusEventArgs e)
{
if (e is ProjectEvaluationStartedEventArgs projectEvaluationStarted)
{
if (showPerfSummary)
{
PerformanceCounter counter = GetPerformanceCounter(projectEvaluationStarted.ProjectFile, ref projectEvaluationPerformanceCounters);
counter.InScope = true;
}
}
else if (e is ProjectEvaluationFinishedEventArgs projectEvaluationFinished)
{
if (showPerfSummary)
{
PerformanceCounter counter = GetPerformanceCounter(projectEvaluationFinished.ProjectFile, ref projectEvaluationPerformanceCounters);
counter.InScope = false;
}
if (Verbosity == LoggerVerbosity.Diagnostic && showItemAndPropertyList)
{
if (projectEvaluationFinished.Properties != null)
{
var propertyList = ExtractPropertyList(projectEvaluationFinished.Properties);
WriteProperties(propertyList);
}
if (projectEvaluationFinished.Items != null)
{
SortedList itemList = ExtractItemList(projectEvaluationFinished.Items);
WriteItems(itemList);
}
}
}
else if (e is BuildCanceledEventArgs buildCanceled)
{
Console.WriteLine(e.Message);
}
}
/// <summary>
/// Writes project started messages.
/// </summary>
internal void WriteProjectStarted()
{
this.VerifyStack(!contextStack.IsEmpty(), "Bad project stack");
// Pop the current project
Frame outerMost = contextStack.Pop();
this.VerifyStack(!outerMost.displayed, "Bad project stack on {0}", outerMost.ID);
this.VerifyStack(outerMost.type == FrameType.Project, "Bad project stack");
outerMost.displayed = true;
contextStack.Push(outerMost);
WriteProjectStartedText(outerMost.ID, outerMost.targetNames, outerMost.parentProjectFile,
this.IsVerbosityAtLeast(LoggerVerbosity.Normal) ? outerMost.indentLevel : 0);
}
/// <summary>
/// Displays the text for a project started message.
/// </summary>
/// <param name ="current">current project file</param>
/// <param name ="previous">previous project file</param>
/// <param name="targetNames">targets that are being invoked</param>
/// <param name="indentLevel">indentation level</param>
private void WriteProjectStartedText(string current, string targetNames, string previous, int indentLevel)
{
if (!SkipProjectStartedText)
{
setColor(ConsoleColor.Cyan);
this.VerifyStack(current != null, "Unexpected null project stack");
WriteLinePretty(projectSeparatorLine);
if (previous == null)
{
if (string.IsNullOrEmpty(targetNames))
{
WriteLinePrettyFromResource(indentLevel, "ProjectStartedPrefixForTopLevelProjectWithDefaultTargets", current);
}
else
{
WriteLinePrettyFromResource(indentLevel, "ProjectStartedPrefixForTopLevelProjectWithTargetNames", current, targetNames);
}
}
else
{
if (string.IsNullOrEmpty(targetNames))
{
WriteLinePrettyFromResource(indentLevel, "ProjectStartedPrefixForNestedProjectWithDefaultTargets", previous, current);
}
else
{
WriteLinePrettyFromResource(indentLevel, "ProjectStartedPrefixForNestedProjectWithTargetNames", previous, current, targetNames);
}
}
// add a little bit of extra space
WriteNewLine();
resetColor();
}
}
/// <summary>
/// Writes target started messages.
/// </summary>
private void WriteTargetStarted()
{
Frame f = contextStack.Pop();
f.displayed = true;
contextStack.Push(f);
setColor(ConsoleColor.Cyan);
if (this.Verbosity == LoggerVerbosity.Diagnostic)
{
WriteLinePrettyFromResource(f.indentLevel, "TargetStartedFromFile", f.ID, f.file);
}
else
{
WriteLinePrettyFromResource(this.IsVerbosityAtLeast(LoggerVerbosity.Normal) ? f.indentLevel : 0,
"TargetStartedPrefix", f.ID);
}
resetColor();
}
/// <summary>
/// Determines the currently building project file.
/// </summary>
/// <returns>name of project file currently being built</returns>
private string GetCurrentlyBuildingProjectFile()
{
if (contextStack.IsEmpty())
{
return null;
}
Frame topOfStack = contextStack.Peek();
// If the top of the stack is a TargetStarted event, then its parent project
// file is the one we want.
if (topOfStack.type == FrameType.Target)
{
return topOfStack.parentProjectFile;
}
// If the top of the stack is a ProjectStarted event, then its ID is the project
// file we want.
else if (topOfStack.type == FrameType.Project)
{
return topOfStack.ID;
}
else
{
ErrorUtilities.ThrowInternalError("Unexpected frame type.");
return null;
}
}
/// <summary>
/// Displays project started and target started messages that
/// are shown only when the associated project or target produces
/// output.
/// </summary>
private void ShowDeferredMessages()
{
if (contextStack.IsEmpty())
{
return;
}
if (!contextStack.Peek().displayed)
{
Frame f = contextStack.Pop();
ShowDeferredMessages();
// push now, so that the stack is in a good state
// for WriteProjectStarted() and WriteLinePretty()
// because we use the stack to control indenting
contextStack.Push(f);
switch (f.type)
{
case FrameType.Project:
WriteProjectStarted();
break;
case FrameType.Target:
// Only do things if we're at normal verbosity. If
// we're at a higher verbosity, we can assume that all
// targets have already be printed. If we're at lower
// verbosity we don't need to print at all.
ErrorUtilities.VerifyThrow(this.Verbosity < LoggerVerbosity.Detailed,
"This target should have already been printed at a higher verbosity.");
if (IsVerbosityAtLeast(LoggerVerbosity.Normal))
{
WriteTargetStarted();
}
break;
default:
ErrorUtilities.ThrowInternalError("Unexpected frame type.");
break;
}
}
}
/// <summary>
/// Marks the current frame to indicate that an error or warning
/// occurred during it.
/// </summary>
private void SetErrorsOrWarningsOnCurrentFrame()
{
// under unit test, there may not be frames on the stack
if (contextStack.Count == 0)
{
return;
}
Frame frame = contextStack.Pop();
frame.hasErrorsOrWarnings = true;
contextStack.Push(frame);
}
/// <summary>
/// Checks the condition passed in. If it's false, it emits an error message to the console
/// indicating that there's a problem with the console logger. These "problems" should
/// never occur in the real world after we ship, unless there's a bug in the MSBuild
/// engine such that events aren't getting paired up properly. So the messages don't
/// really need to be localized here, since they're only for our own benefit, and have
/// zero value to a customer.
/// </summary>
/// <param name="condition"></param>
/// <param name="unformattedMessage"></param>
/// <param name="args"></param>
private void VerifyStack(
bool condition,
string unformattedMessage,
params object[] args)
{
if (!condition && !ignoreLoggerErrors)
{
string errorMessage = "INTERNAL CONSOLE LOGGER ERROR. " + ResourceUtilities.FormatString(unformattedMessage, args);
ErrorUtilities.ThrowInternalError(errorMessage);
}
}
#endregion
#region Supporting classes
/// <summary>
/// This enumeration represents the kinds of context that can be
/// stored in the context stack.
/// </summary>
internal enum FrameType
{
Project,
Target
}
/// <summary>
/// This struct represents context information about a single
/// target or project.
/// </summary>
internal struct Frame
{
/// <summary>
/// Initializes a new instance of the <see cref="Frame"/> struct with all fields specified.
/// </summary>
/// <param name="t">the type of the this frame</param>
/// <param name="d">display state. true indicates this frame has been displayed to the user</param>
/// <param name="indent">indentation level for this frame</param>
/// <param name="s">frame id</param>
/// <param name="targets">targets to execute, in the case of a project frame</param>
/// <param name="fileOfTarget">the file name where the target is defined</param>
/// <param name="parent">parent project file</param>
internal Frame(
FrameType t,
bool d,
int indent,
string s,
string targets,
string fileOfTarget,
string parent)
{
type = t;
displayed = d;
indentLevel = indent;
ID = s;
targetNames = targets;
file = fileOfTarget;
hasErrorsOrWarnings = false;
parentProjectFile = parent;
}
/// <summary>
/// Indicates if project or target frame.
/// </summary>
internal FrameType type;
/// <summary>
/// Set to true to indicate the user has seen a message about this frame.
/// </summary>
internal bool displayed;
/// <summary>
/// The number of tabstops to indent this event when it is eventually displayed.
/// </summary>
internal int indentLevel;
/// <summary>
/// A string associated with this frame -- should be a target name
/// or a project file.
/// </summary>
internal string ID;
/// <summary>
/// For a TargetStarted or a ProjectStarted event, this field tells us
/// the name of the *parent* project file that was responsible.
/// </summary>
internal string parentProjectFile;
/// <summary>
/// Stores the TargetNames from the ProjectStarted event. Null for Target frames.
/// </summary>
internal string targetNames;
/// <summary>
/// For TargetStarted events, this stores the filename where the Target is defined
/// (e.g., Microsoft.Common.targets). This is different than the project that is
/// being built.
/// For ProjectStarted events, this is null.
/// </summary>
internal string file;
/// <summary>
/// True if there were errors/warnings during the project or target frame.
/// </summary>
internal bool hasErrorsOrWarnings;
}
/// <summary>
/// The FrameStack class represents a (lifo) stack of Frames.
/// </summary>
internal class FrameStack
{
/// <summary>
/// The frames member is contained by FrameStack and does
/// all the heavy lifting for FrameStack.
/// </summary>
private readonly Stack<Frame> _frames;
/// <summary>
/// Initializes a new instance of the <see cref="FrameStack"/> class.
/// </summary>
internal FrameStack()
{
_frames = new Stack<Frame>();
}
/// <summary>
/// Remove and return the top element in the stack.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when stack is empty.</exception>
internal Frame Pop()
{
return _frames.Pop();
}
/// <summary>
/// Returns, but does not remove, the top of the stack.
/// </summary>
internal Frame Peek()
{
return _frames.Peek();
}
/// <summary>
/// Push(f) adds f to the top of the stack.
/// </summary>
/// <param name="f">a frame to push</param>
internal void Push(Frame f)
{
_frames.Push(f);
}
/// <summary>
/// Constant property that indicates the number of elements
/// in the stack.
/// </summary>
internal int Count
{
get
{
return _frames.Count;
}
}
/// <summary>
/// s.IsEmpty() is true iff s.Count == 0
/// </summary>
internal bool IsEmpty()
{
return _frames.Count == 0;
}
}
#endregion
#region Private member data
/// <summary>
/// contextStack is the only interesting state in the console
/// logger. The context stack contains a sequence of frames
/// denoting current and previous containing projects and targets
/// </summary>
internal FrameStack contextStack = new FrameStack();
#endregion
}
}
|