diff --git a/src/Build/Logging/ProjectTrackingLoggerBase.cs b/src/Build/Logging/ProjectTrackingLoggerBase.cs new file mode 100644 index 00000000000..375257ba117 --- /dev/null +++ b/src/Build/Logging/ProjectTrackingLoggerBase.cs @@ -0,0 +1,430 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.Build.Framework; + +namespace Microsoft.Build.Logging; + +/// +/// A wrapper over the project context ID passed to us in logger events. +/// +internal record struct ProjectContext(int Id) +{ + public ProjectContext(BuildEventContext context) + : this(context.ProjectContextId) + { + } +} + +/// +/// A wrapper over the evaluation context ID passed to us in logger events. +/// +internal record struct EvalContext(int Id) +{ + public EvalContext(BuildEventContext context) + : this(context.EvaluationId) + { + } +} + +/// +/// Base class that tracks build state (evaluation data, node status, completed project data, and whole-build data) during builds. +/// Subclasses can specialize the type of data tracked for each area and implement rendering logic. +/// +/// Data gathered/projected for each evaluation context +/// Data stored for each live, actively running build worker node +/// Data stored for each completed project context instance +/// Data that is aggregated across the entire build session +public abstract class ProjectTrackingLoggerBase : INodeLogger +{ + + /// + /// Tracks the evaluation data for all evaluations seen so far. + /// + /// + /// Keyed by an ID that gets passed to logger callbacks, this allows us to quickly look up the corresponding evaluation. + /// + private readonly Dictionary _evaluationDataByEvalId = new(); + + /// + /// Tracks the status of all relevant projects seen so far. + /// + /// + /// Keyed by an ID that gets passed to logger callbacks, this allows us to quickly look up the corresponding project. + /// + private readonly Dictionary _projectDataByProjectContextId = new(); + + /// + /// Tracks build-level data for the entire build session. + /// + private TBuildData? _buildData; + + #region INodeLogger implementation + + /// + public abstract LoggerVerbosity Verbosity { get; set; } + + /// + public abstract string? Parameters { get; set; } + + /// + /// The number of nodes in the build. Handles the case where MSBUILDNOINPROCNODE is set by reserving an extra slot. + /// + protected int NodeCount { get; private set; } + + /// + public virtual void Initialize(IEventSource eventSource, int nodeCount) + { + // When MSBUILDNOINPROCNODE enabled, NodeId's reported by build start with 2. We need to reserve an extra spot for this case. + NodeCount = nodeCount + 1; + + Initialize(eventSource); + } + + /// + public virtual void Initialize(IEventSource eventSource) + { + eventSource.BuildStarted += BuildStartedHandler; + eventSource.BuildFinished += BuildFinishedHandler; + eventSource.ProjectStarted += ProjectStartedHandler; + eventSource.ProjectFinished += ProjectFinishedHandler; + eventSource.TargetStarted += TargetStartedHandler; + eventSource.TargetFinished += TargetFinishedHandler; + eventSource.TaskStarted += TaskStartedHandler; + eventSource.StatusEventRaised += StatusEventRaisedHandler; + eventSource.MessageRaised += MessageRaisedHandler; + eventSource.WarningRaised += WarningRaisedHandler; + eventSource.ErrorRaised += ErrorRaisedHandler; + + if (eventSource is IEventSource3 eventSource3) + { + eventSource3.IncludeTaskInputs(); + } + + if (eventSource is IEventSource4 eventSource4) + { + eventSource4.IncludeEvaluationPropertiesAndItems(); + } + } + + /// + public abstract void Shutdown(); + + #endregion + + #region Logger callbacks + + /// + /// The callback. + /// + private void BuildStartedHandler(object sender, BuildStartedEventArgs e) + { + _buildData = CreateBuildData(e); + OnBuildStarted(e, _buildData); + } + + /// + /// The callback. + /// + private void BuildFinishedHandler(object sender, BuildFinishedEventArgs e) + { + OnBuildFinished(e, _projectDataByProjectContextId.Values.ToArray(), _buildData!); + + // Clear tracking data + _projectDataByProjectContextId.Clear(); + _evaluationDataByEvalId.Clear(); + _buildData = default; + } + + /// + /// The callback. + /// + private void StatusEventRaisedHandler(object sender, BuildStatusEventArgs e) + { + switch (e) + { + case BuildCanceledEventArgs cancelEvent: + OnBuildCanceled(cancelEvent); + break; + case ProjectEvaluationStartedEventArgs: + break; + case ProjectEvaluationFinishedEventArgs evalFinish: + CaptureEvalContext(evalFinish); + break; + } + } + + /// + /// The callback. + /// + private void ProjectStartedHandler(object sender, ProjectStartedEventArgs e) + { + if (e.BuildEventContext is null) + { + return; + } + + ProjectContext projectContext = new(e.BuildEventContext); + EvalContext evalContext = new(e.BuildEventContext); + + // Get eval data for this project + if (_evaluationDataByEvalId.TryGetValue(evalContext, out TEvalData? evalData)) + { + // Create project data using the eval data + TProjectData? projectData = CreateProjectData(evalData, e); + if (projectData != null) + { + _projectDataByProjectContextId[projectContext] = projectData; + OnProjectStarted(e, evalData, projectData!, _buildData!); + } + } + } + + /// + /// The callback. + /// + private void ProjectFinishedHandler(object sender, ProjectFinishedEventArgs e) + { + var buildEventContext = e.BuildEventContext; + if (buildEventContext is null) + { + return; + } + + ProjectContext projectContext = new(buildEventContext); + + // Get project data + if (_projectDataByProjectContextId.TryGetValue(projectContext, out var projectData)) + { + OnProjectFinished(e, projectData, _buildData!); + } + } + + /// + /// The callback. + /// + private void TargetStartedHandler(object sender, TargetStartedEventArgs e) + { + var buildEventContext = e.BuildEventContext; + if (buildEventContext is not null) + { + ProjectContext projectContext = new(buildEventContext); + if (_projectDataByProjectContextId.TryGetValue(projectContext, out TProjectData? projectData)) + { + OnTargetStarted(e, projectData, _buildData!); + } + } + } + + /// + /// The callback. + /// + private void TargetFinishedHandler(object sender, TargetFinishedEventArgs e) + { + var buildEventContext = e.BuildEventContext; + if (buildEventContext is null) + { + return; + } + + ProjectContext projectContext = new(buildEventContext); + if (_projectDataByProjectContextId.TryGetValue(projectContext, out var projectData)) + { + OnTargetFinished(e, projectData, _buildData!); + } + } + + /// + /// The callback. + /// + private void TaskStartedHandler(object sender, TaskStartedEventArgs e) + { + var buildEventContext = e.BuildEventContext; + if (buildEventContext is not null) + { + ProjectContext projectContext = new(buildEventContext); + if (_projectDataByProjectContextId.TryGetValue(projectContext, out var projectData)) + { + OnTaskStarted(e, projectData, _buildData!); + } + } + } + + /// + /// The callback. + /// + private void MessageRaisedHandler(object sender, BuildMessageEventArgs e) + { + var buildEventContext = e.BuildEventContext; + if (buildEventContext is null) + { + return; + } + + ProjectContext projectContext = new(buildEventContext); + TProjectData? projectData = default; + _projectDataByProjectContextId.TryGetValue(projectContext, out projectData); + OnMessageRaised(e, projectData, _buildData!); + } + + /// + /// The callback. + /// + private void WarningRaisedHandler(object sender, BuildWarningEventArgs e) + { + BuildEventContext? buildEventContext = e.BuildEventContext; + if (buildEventContext is null) + { + OnWarningRaised(e, default, _buildData!); + return; + } + + ProjectContext projectContext = new(buildEventContext); + TProjectData? projectData = default; + _projectDataByProjectContextId.TryGetValue(projectContext, out projectData); + OnWarningRaised(e, projectData, _buildData!); + } + + /// + /// The callback. + /// + private void ErrorRaisedHandler(object sender, BuildErrorEventArgs e) + { + BuildEventContext? buildEventContext = e.BuildEventContext; + if (buildEventContext is null) + { + OnErrorRaised(e, default, _buildData!); + return; + } + + ProjectContext projectContext = new(buildEventContext); + TProjectData? projectData = default; + _projectDataByProjectContextId.TryGetValue(projectContext, out projectData); + OnErrorRaised(e, projectData, _buildData!); + } + + #endregion + + #region Protected helpers + + protected int? GetNodeIdForEvent(BuildEventArgs args) => args?.BuildEventContext is null ? null : NodeIndexForContext(args.BuildEventContext); + + #endregion + + #region Private helpers + + private int NodeIndexForContext(BuildEventContext context) + { + // Node IDs reported by the build are 1-based. + return context.NodeId - 1; + } + + /// + /// Captures evaluation context data from the evaluation finished event. + /// + private void CaptureEvalContext(ProjectEvaluationFinishedEventArgs evalFinish) + { + var buildEventContext = evalFinish.BuildEventContext; + if (buildEventContext is null) + { + return; + } + + EvalContext evalContext = new(buildEventContext); + + if (!_evaluationDataByEvalId.ContainsKey(evalContext)) + { + TEvalData evalData = CreateEvalData(evalFinish); + _evaluationDataByEvalId[evalContext] = evalData; + } + } + + #endregion + + #region Abstract methods - must be implemented by subclasses + + /// + /// Creates evaluation data from the evaluation finished event. + /// + /// The evaluation finished event args. + /// The evaluation data to store, or null to not track this evaluation. + protected abstract TEvalData CreateEvalData(ProjectEvaluationFinishedEventArgs e); + + /// + /// Creates project data from the project started event and evaluation data. + /// + /// The evaluation data for this project. + /// The project started event args. + /// The project data to store, or null to not track this project. + protected abstract TProjectData? CreateProjectData(TEvalData evalData, ProjectStartedEventArgs e); + + /// + /// Creates build data when the build starts. + /// + /// The build started event args. + /// The build data to track for this build session. + protected abstract TBuildData CreateBuildData(BuildStartedEventArgs e); + + #endregion + + #region Virtual methods - can be overridden by subclasses + + /// + /// Called when the build starts. + /// + protected virtual void OnBuildStarted(BuildStartedEventArgs e, TBuildData buildData) { } + + /// + /// Called when the build finishes. + /// + protected virtual void OnBuildFinished(BuildFinishedEventArgs e, TProjectData[] projectData, TBuildData buildData) { } + + /// + /// Called when the build is canceled. + /// + protected virtual void OnBuildCanceled(BuildCanceledEventArgs e) { } + + /// + /// Called when a project starts. + /// + protected virtual void OnProjectStarted(ProjectStartedEventArgs e, TEvalData evalData, TProjectData projectData, TBuildData buildData) { } + + /// + /// Called when a project finishes. + /// + protected virtual void OnProjectFinished(ProjectFinishedEventArgs e, TProjectData projectData, TBuildData buildData) { } + + /// + /// Called when a target starts. + /// + protected virtual void OnTargetStarted(TargetStartedEventArgs e, TProjectData projectData, TBuildData buildData) { } + + /// + /// Called when a target finishes. + /// + protected virtual void OnTargetFinished(TargetFinishedEventArgs e, TProjectData projectData, TBuildData buildData) { } + + /// + /// Called when a task starts. + /// + protected virtual void OnTaskStarted(TaskStartedEventArgs e, TProjectData projectData, TBuildData buildData) { } + + /// + /// Called when a message is raised. + /// + protected virtual void OnMessageRaised(BuildMessageEventArgs e, TProjectData? projectData, TBuildData buildData) { } + + /// + /// Called when a warning is raised. + /// + protected virtual void OnWarningRaised(BuildWarningEventArgs e, TProjectData? projectData, TBuildData buildData) { } + + /// + /// Called when an error is raised. + /// + protected virtual void OnErrorRaised(BuildErrorEventArgs e, TProjectData? projectData, TBuildData buildData) { } + + #endregion +} \ No newline at end of file diff --git a/src/Build/Logging/TerminalLogger/StopwatchAbstraction.cs b/src/Build/Logging/TerminalLogger/StopwatchAbstraction.cs index c4f72f630de..d6c3920910c 100644 --- a/src/Build/Logging/TerminalLogger/StopwatchAbstraction.cs +++ b/src/Build/Logging/TerminalLogger/StopwatchAbstraction.cs @@ -3,7 +3,7 @@ namespace Microsoft.Build.Logging; -internal abstract class StopwatchAbstraction +public abstract class StopwatchAbstraction { public abstract void Start(); public abstract void Stop(); diff --git a/src/Build/Logging/TerminalLogger/TerminalBuildData.cs b/src/Build/Logging/TerminalLogger/TerminalBuildData.cs new file mode 100644 index 00000000000..9ef619c8443 --- /dev/null +++ b/src/Build/Logging/TerminalLogger/TerminalBuildData.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Build.Logging; + +/// +/// Tracks build-level data for the TerminalLogger across an entire build session. +/// +public sealed class TerminalBuildData +{ + /// + /// The timestamp of the build start event. + /// + public DateTime BuildStartTime { get; set; } + + /// + /// Number of build errors encountered during the build. + /// + public int BuildErrorsCount { get; set; } + + /// + /// Number of build warnings encountered during the build. + /// + public int BuildWarningsCount { get; set; } + + /// + /// The project build context corresponding to the Restore initial target, or null if the build is currently not restoring. + /// + public int? RestoreContext { get; set; } + + /// + /// True if restore failed and this failure has already been reported. + /// + public bool RestoreFailed { get; set; } + + /// + /// True if restore happened and finished. + /// + public bool RestoreFinished { get; set; } + + /// + /// Initializes a new instance of TerminalBuildData. + /// + /// The timestamp when the build started. + public TerminalBuildData(DateTime buildStartTime) + { + BuildStartTime = buildStartTime; + } +} \ No newline at end of file diff --git a/src/Build/Logging/TerminalLogger/TerminalBuildMessage.cs b/src/Build/Logging/TerminalLogger/TerminalBuildMessage.cs index 8e90b6f85e2..1097ff85b61 100644 --- a/src/Build/Logging/TerminalLogger/TerminalBuildMessage.cs +++ b/src/Build/Logging/TerminalLogger/TerminalBuildMessage.cs @@ -6,5 +6,5 @@ namespace Microsoft.Build.Logging; /// /// Represents a piece of diagnostic output (message/warning/error). /// -internal record struct TerminalBuildMessage(TerminalMessageSeverity Severity, string Message) +public record struct TerminalBuildMessage(TerminalMessageSeverity Severity, string Message) { } diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index b64fefb705e..ef9a99704c8 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -30,7 +30,7 @@ namespace Microsoft.Build.Logging; /// /// Uses ANSI/VT100 control codes to erase and overwrite lines as the build is progressing. /// -public sealed partial class TerminalLogger : INodeLogger +public sealed partial class TerminalLogger : ProjectTrackingLoggerBase { private const string FilePathPattern = " -> "; @@ -42,27 +42,7 @@ public sealed partial class TerminalLogger : INodeLogger private static readonly string[] newLineStrings = { "\r\n", "\n" }; - /// - /// A wrapper over the project context ID passed to us in logger events. - /// - internal record struct ProjectContext(int Id) - { - public ProjectContext(BuildEventContext context) - : this(context.ProjectContextId) - { - } - } - - /// - /// A wrapper over the evaluation context ID passed to us in logger events. - /// - internal record struct EvalContext(int Id) - { - public EvalContext(BuildEventContext context) - : this(context.EvaluationId) - { - } - } + // ProjectContext and EvalContext are now inherited from BuildTrackerLogger private readonly record struct TestSummary(int Total, int Passed, int Skipped, int Failed); @@ -80,31 +60,6 @@ public EvalContext(BuildEventContext context) internal Func? CreateStopwatch = null; - /// - /// Name of target that identifies the project cache plugin run has just started. - /// - private const string CachePluginStartTarget = "_CachePluginRunStart"; - - /// - /// Protects access to state shared between the logger callbacks and the rendering thread. - /// - private readonly LockType _lock = new LockType(); - - /// - /// A cancellation token to signal the rendering thread that it should exit. - /// - private readonly CancellationTokenSource _cts = new(); - - /// - /// Tracks the status of all relevant projects seen so far. - /// - /// - /// Keyed by an ID that gets passed to logger callbacks, this allows us to quickly look up the corresponding project. - /// - private readonly Dictionary _projects = new(); - - private readonly Dictionary _evals = new(); - /// /// Tracks the work currently being done by build nodes. Null means the node is not doing any work worth reporting. /// @@ -112,43 +67,33 @@ public EvalContext(BuildEventContext context) /// There is no locking around access to this data structure despite it being accessed concurrently by multiple threads. /// However, reads and writes to locations in an array is atomic, so locking is not required. /// - private TerminalNodeStatus?[] _nodes = Array.Empty(); + private TerminalNodeStatus?[] _nodes = []; /// - /// The timestamp of the event. + /// Name of target that identifies the project cache plugin run has just started. /// - private DateTime _buildStartTime; + private const string CachePluginStartTarget = "_CachePluginRunStart"; /// - /// The working directory when the build starts, to trim relative output paths. + /// Protects access to state shared between the logger callbacks and the rendering thread. /// - private readonly string _initialWorkingDirectory = Environment.CurrentDirectory; + private readonly LockType _lock = new LockType(); /// - /// Number of build errors. + /// A cancellation token to signal the rendering thread that it should exit. /// - private int _buildErrorsCount; + private readonly CancellationTokenSource _cts = new(); - /// - /// Number of build warnings. - /// - private int _buildWarningsCount; + // Tracking dictionaries and node array are now inherited from BuildTrackerLogger - /// - /// True if restore failed and this failure has already been reported. - /// - private bool _restoreFailed; + // BuildStartTime is now inherited from BuildTrackerLogger /// - /// True if restore happened and finished. + /// The working directory when the build starts, to trim relative output paths. /// - private bool _restoreFinished = false; + private readonly string _initialWorkingDirectory = Environment.CurrentDirectory; - /// - /// The project build context corresponding to the Restore initial target, or null if the build is currently - /// not restoring. - /// - private ProjectContext? _restoreContext; + // Build error/warning counts and restore state are now inherited from BuildTrackerLogger /// /// The thread that performs periodic refresh of the console output. @@ -347,49 +292,19 @@ private static bool IsTerminalLoggerDisabled(string? value) => #region INodeLogger implementation /// - public LoggerVerbosity Verbosity { get; set; } = LoggerVerbosity.Minimal; - - /// - public string? Parameters { get; set; } = null; + public override LoggerVerbosity Verbosity { get; set; } = LoggerVerbosity.Minimal; /// - public void Initialize(IEventSource eventSource, int nodeCount) - { - // When MSBUILDNOINPROCNODE enabled, NodeId's reported by build start with 2. We need to reserve an extra spot for this case. - _nodes = new TerminalNodeStatus[nodeCount + 1]; - - Initialize(eventSource); - } + public override string? Parameters { get; set; } = null; /// - public void Initialize(IEventSource eventSource) + public override void Initialize(IEventSource eventSource) { ParseParameters(); - - eventSource.BuildStarted += BuildStarted; - eventSource.BuildFinished += BuildFinished; - eventSource.ProjectStarted += ProjectStarted; - eventSource.ProjectFinished += ProjectFinished; - eventSource.TargetStarted += TargetStarted; - eventSource.TargetFinished += TargetFinished; - eventSource.TaskStarted += TaskStarted; - eventSource.StatusEventRaised += StatusEventRaised; - eventSource.MessageRaised += MessageRaised; - eventSource.WarningRaised += WarningRaised; - eventSource.ErrorRaised += ErrorRaised; - - if (eventSource is IEventSource3 eventSource3) - { - eventSource3.IncludeTaskInputs(); - } - - if (eventSource is IEventSource4 eventSource4) - { - eventSource4.IncludeEvaluationPropertiesAndItems(); - } + base.Initialize(eventSource); + _nodes = new TerminalNodeStatus[NodeCount]; } - /// /// Parses out the logger parameters from the Parameters string. /// @@ -467,7 +382,7 @@ private bool TryApplyShowCommandLineParameter(string? parameterValue) } /// - public void Shutdown() + public override void Shutdown() { NativeMethodsShared.RestoreConsoleMode(_originalConsoleMode); @@ -489,12 +404,77 @@ public MessageImportance GetMinimumMessageImportance() #endregion - #region Logger callbacks + #region BuildTrackerLogger implementation - /// - /// The callback. - /// - private void BuildStarted(object sender, BuildStartedEventArgs e) + /// + protected override TerminalBuildData CreateBuildData(BuildStartedEventArgs e) + { + return new TerminalBuildData(e.Timestamp); + } + + /// + protected override EvalProjectInfo CreateEvalData(ProjectEvaluationFinishedEventArgs e) + { + string? tfm = null; + string? rid = null; + foreach (var property in e.EnumerateProperties()) + { + if (tfm is not null && rid is not null) + { + // We already have both properties, no need to continue. + break; + } + switch (property.Name) + { + case "TargetFramework": + tfm = property.Value; + break; + case "RuntimeIdentifier": + rid = property.Value; + break; + } + } + return new EvalProjectInfo(e.ProjectFile!, tfm, rid); + } + + /// + protected override TerminalProjectInfo? CreateProjectData(EvalProjectInfo evalData, ProjectStartedEventArgs e) + { + return new TerminalProjectInfo(evalData, CreateStopwatch?.Invoke()); + } + + private TerminalNodeStatus? CreateNodeData(TargetStartedEventArgs e, TerminalProjectInfo projectData) + { + projectData.Stopwatch.Start(); + string projectFile = Path.GetFileNameWithoutExtension(e.ProjectFile); + string targetName = e.TargetName; + + if (targetName == CachePluginStartTarget) + { + projectData.IsCachePluginProject = true; + _hasUsedCache = true; + } + + if (targetName == _testStartTarget) + { + targetName = "Testing"; + _testStartTime = _testStartTime == null + ? e.Timestamp + : e.Timestamp < _testStartTime + ? e.Timestamp : _testStartTime; + projectData.IsTestProject = true; + } + + return new TerminalNodeStatus(projectFile, projectData.TargetFramework, projectData.RuntimeIdentifier, targetName, projectData.Stopwatch); + } + + + #endregion + + #region Logger event overrides + + /// + protected override void OnBuildStarted(BuildStartedEventArgs e, TerminalBuildData buildData) { if (!_manualRefresh && _showNodesDisplay) { @@ -503,18 +483,14 @@ private void BuildStarted(object sender, BuildStartedEventArgs e) _refresher.Start(); } - _buildStartTime = e.Timestamp; - if (Terminal.SupportsProgressReporting && Verbosity != LoggerVerbosity.Quiet) { Terminal.Write(AnsiCodes.SetProgressIndeterminate); } } - /// - /// The callback. - /// - private void BuildFinished(object sender, BuildFinishedEventArgs e) + /// + protected override void OnBuildFinished(BuildFinishedEventArgs e, TerminalProjectInfo[] projectInfos, TerminalBuildData buildData) { _cts.Cancel(); _refresher?.Join(); @@ -524,8 +500,8 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) { if (Verbosity > LoggerVerbosity.Quiet) { - string duration = (e.Timestamp - _buildStartTime).TotalSeconds.ToString("F1"); - string buildResult = GetBuildResultString(e.Succeeded, _buildErrorsCount, _buildWarningsCount); + string duration = (e.Timestamp - buildData.BuildStartTime).TotalSeconds.ToString("F1"); + string buildResult = GetBuildResultString(e.Succeeded, buildData.BuildErrorsCount, buildData.BuildWarningsCount); Terminal.WriteLine(""); if (_testRunSummaries.Any()) @@ -537,8 +513,8 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) var testDuration = (_testStartTime != null && _testEndTime != null ? (_testEndTime - _testStartTime).Value.TotalSeconds : 0).ToString("F1"); var colorizeFailed = failed > 0; - var colorizePassed = passed > 0 && _buildErrorsCount == 0 && failed == 0; - var colorizeSkipped = skipped > 0 && skipped == total && _buildErrorsCount == 0 && failed == 0; + var colorizePassed = passed > 0 && buildData.BuildErrorsCount == 0 && failed == 0; + var colorizeSkipped = skipped > 0 && skipped == total && buildData.BuildErrorsCount == 0 && failed == 0; string summaryAndTotalText = ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("TestSummary_BannerAndTotal", total); string failedText = ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("TestSummary_Failed", failed); @@ -555,10 +531,10 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) if (_showSummary == true) { - RenderBuildSummary(); + RenderBuildSummary(buildData, projectInfos); } - if (_restoreFailed) + if (buildData?.RestoreFailed == true) { Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreCompleteWithMessage", buildResult, @@ -582,18 +558,14 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e) Terminal.EndUpdate(); } - _projects.Clear(); _testRunSummaries.Clear(); - _buildErrorsCount = 0; - _buildWarningsCount = 0; - _restoreFailed = false; _testStartTime = null; _testEndTime = null; } - private void RenderBuildSummary() + private void RenderBuildSummary(TerminalBuildData buildData, TerminalProjectInfo[] projectInfos) { - if (_buildErrorsCount == 0 && _buildWarningsCount == 0) + if (buildData.BuildErrorsCount == 0 && buildData.BuildWarningsCount == 0) { // No errors/warnings to display. return; @@ -601,7 +573,7 @@ private void RenderBuildSummary() Terminal.WriteLine(ResourceUtilities.GetResourceString("BuildSummary")); - foreach (TerminalProjectInfo project in _projects.Values.Where(p => p.HasErrorsOrWarnings)) + foreach (TerminalProjectInfo project in projectInfos.Where(p => p.HasErrorsOrWarnings)) { string duration = project.Stopwatch.ElapsedSeconds.ToString("F1"); string buildResult = GetBuildResultString(project.Succeeded, project.ErrorCount, project.WarningCount); @@ -618,62 +590,25 @@ private void RenderBuildSummary() Terminal.WriteLine(string.Empty); } - private void StatusEventRaised(object sender, BuildStatusEventArgs e) + /// + protected override void OnBuildCanceled(BuildCanceledEventArgs e) { - switch (e) - { - case BuildCanceledEventArgs cancelEvent: - RenderImmediateMessage(cancelEvent.Message!); - break; - case ProjectEvaluationStartedEventArgs _evalStart: - break; - case ProjectEvaluationFinishedEventArgs evalFinish: - CaptureEvalContext(evalFinish); - break; - } + RenderImmediateMessage(e.Message!); } - /// - /// The callback. - /// - private void ProjectStarted(object sender, ProjectStartedEventArgs e) + /// + protected override void OnProjectStarted(ProjectStartedEventArgs e, EvalProjectInfo evalData, TerminalProjectInfo projectData, TerminalBuildData buildData) { - if (e.BuildEventContext is null) + // Handle restore case + if (buildData.RestoreContext is null && e.TargetNames == "Restore" && !buildData.RestoreFinished && e.BuildEventContext is not null) { - return; - } - - ProjectContext c = new(e.BuildEventContext); - - if (_restoreContext is null) - { - EvalContext evalContext = new(e.BuildEventContext); - string? targetFramework = null; - string? runtimeIdentifier = null; - if (_evals.TryGetValue(evalContext, out EvalProjectInfo evalInfo)) - { - targetFramework = evalInfo.TargetFramework; - runtimeIdentifier = evalInfo.RuntimeIdentifier; - } - System.Diagnostics.Debug.Assert(evalInfo != default, "EvalProjectInfo should have been captured before ProjectStarted"); - - TerminalProjectInfo projectInfo = new(c, evalInfo, CreateStopwatch?.Invoke()); - _projects[c] = projectInfo; - - // First ever restore in the build is starting. - if (e.TargetNames == "Restore" && !_restoreFinished) - { - _restoreContext = c; - int nodeIndex = NodeIndexForContext(e.BuildEventContext); - _nodes[nodeIndex] = new TerminalNodeStatus(e.ProjectFile!, targetFramework, runtimeIdentifier, "Restore", _projects[c].Stopwatch); - } + buildData.RestoreContext = e.BuildEventContext.ProjectContextId; + StartNode(e, new TerminalNodeStatus(e.ProjectFile!, evalData.TargetFramework, evalData.RuntimeIdentifier, "Restore", projectData.Stopwatch)); } } - /// - /// The callback. - /// - private void ProjectFinished(object sender, ProjectFinishedEventArgs e) + /// + protected override void OnProjectFinished(ProjectFinishedEventArgs e, TerminalProjectInfo? projectData, TerminalBuildData buildData) { var buildEventContext = e.BuildEventContext; if (buildEventContext is null) @@ -682,9 +617,9 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e) } // Mark node idle until something uses it again - if (_restoreContext is null) + if (buildData.RestoreContext is null) { - UpdateNodeStatus(buildEventContext, null); + YieldNode(e); } // Continue execution and add project summary to the static part of the Console only if verbosity is higher than Quiet. @@ -693,12 +628,20 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e) return; } - ProjectContext c = new(buildEventContext); - - if (_projects.TryGetValue(c, out TerminalProjectInfo? project)) + if (projectData != null) { - project.Succeeded = e.Succeeded; - project.Stopwatch.Stop(); + projectData.Succeeded = e.Succeeded; + projectData.Stopwatch.Stop(); + + // Handle restore completion + if (buildEventContext.ProjectContextId == buildData.RestoreContext) + { + buildData.RestoreContext = null; + buildData.RestoreFinished = true; + buildData.RestoreFailed = !e.Succeeded; + OnRestoreFinished(e, projectData, buildData); + } + lock (_lock) { Terminal.BeginUpdate(); @@ -706,52 +649,26 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e) { EraseNodes(); - string duration = project.Stopwatch.ElapsedSeconds.ToString("F1"); - ReadOnlyMemory? outputPath = project.OutputPath; + string duration = projectData.Stopwatch.ElapsedSeconds.ToString("F1"); + ReadOnlyMemory? outputPath = projectData.OutputPath; // Build result. One of 'failed', 'succeeded with warnings', or 'succeeded' depending on the build result and diagnostic messages // reported during build. - string buildResult = GetBuildResultString(project.Succeeded, project.ErrorCount, project.WarningCount); - - // Check if we're done restoring. - if (c == _restoreContext) - { - if (e.Succeeded) - { - if (project.HasErrorsOrWarnings) - { - Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreCompleteWithMessage", - buildResult, - duration)); - } - else - { - Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreComplete", - duration)); - } - } - else - { - // It will be reported after build finishes. - _restoreFailed = true; - } + string buildResult = GetBuildResultString(projectData.Succeeded, projectData.ErrorCount, projectData.WarningCount); - _restoreContext = null; - _restoreFinished = true; - } // If this was a notable project build, we print it as completed only if it's produced an output or warnings/error. // If this is a test project, print it always, so user can see either a success or failure, otherwise success is hidden // and it is hard to see if project finished, or did not run at all. - else if (project.OutputPath is not null || project.BuildMessages is not null || project.IsTestProject) + if (projectData.OutputPath is not null || projectData.BuildMessages is not null || projectData.IsTestProject) { // Show project build complete and its output - string projectFinishedHeader = GetProjectFinishedHeader(project, buildResult, duration); + string projectFinishedHeader = GetProjectFinishedHeader(projectData, buildResult, duration); Terminal.Write(projectFinishedHeader); // Print the output path as a link if we have it. if (outputPath is { } outputPathSpan) { - (var projectDisplayPath, var urlLink) = DetermineOutputPathToRender(outputPathSpan, _initialWorkingDirectory.AsMemory(), project.SourceRoot); + (var projectDisplayPath, var urlLink) = DetermineOutputPathToRender(outputPathSpan, _initialWorkingDirectory.AsMemory(), projectData.SourceRoot); Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectFinished_OutputPath", CreateLink(urlLink, projectDisplayPath.ToString()))); } else @@ -761,16 +678,20 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e) } // Print diagnostic output under the Project -> Output line. - if (project.BuildMessages is not null) + if (projectData.BuildMessages is not null) { - foreach (TerminalBuildMessage buildMessage in project.BuildMessages) + foreach (TerminalBuildMessage buildMessage in projectData.BuildMessages) { Terminal.WriteLine($"{DoubleIndentation}{buildMessage.Message}"); } } - _buildErrorsCount += project.ErrorCount; - _buildWarningsCount += project.WarningCount; + // Track errors and warnings in build data + if (buildData != null) + { + buildData.BuildErrorsCount += projectData.ErrorCount; + buildData.BuildWarningsCount += projectData.WarningCount; + } if (_showNodesDisplay) { @@ -785,39 +706,27 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e) } } - private void CaptureEvalContext(ProjectEvaluationFinishedEventArgs evalFinish) + private void OnRestoreFinished(ProjectFinishedEventArgs e, TerminalProjectInfo? projectData, TerminalBuildData buildData) { - var buildEventContext = evalFinish.BuildEventContext; - if (buildEventContext is null) + if (Verbosity > LoggerVerbosity.Quiet && projectData != null) { - return; - } - - EvalContext c = new(buildEventContext); + string duration = projectData.Stopwatch.ElapsedSeconds.ToString("F1"); + string buildResult = GetBuildResultString(projectData.Succeeded, projectData.ErrorCount, projectData.WarningCount); - if (!_evals.TryGetValue(c, out EvalProjectInfo _)) - { - string? tfm = null; - string? rid = null; - foreach (var property in evalFinish.EnumerateProperties()) + if (e.Succeeded) { - if (tfm is not null && rid is not null) + if (projectData.HasErrorsOrWarnings) { - // We already have both properties, no need to continue. - break; + Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreCompleteWithMessage", + buildResult, + duration)); } - switch (property.Name) + else { - case "TargetFramework": - tfm = property.Value; - break; - case "RuntimeIdentifier": - rid = property.Value; - break; + Terminal.WriteLine(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("RestoreComplete", + duration)); } } - var evalInfo = new EvalProjectInfo(c, evalFinish.ProjectFile!, tfm, rid); - _evals[c] = evalInfo; } } @@ -941,123 +850,64 @@ private static string GetProjectFinishedHeader(TerminalProjectInfo project, stri }; } - /// - /// The callback. - /// - private void TargetStarted(object sender, TargetStartedEventArgs e) + /// + protected override void OnTargetStarted(TargetStartedEventArgs e, TerminalProjectInfo projectData, TerminalBuildData buildData) { - var buildEventContext = e.BuildEventContext; - if (_restoreContext is null && buildEventContext is not null && _projects.TryGetValue(new ProjectContext(buildEventContext), out TerminalProjectInfo? project)) + TerminalNodeStatus? nodeData = CreateNodeData(e, projectData); + if (nodeData != null) { - project.Stopwatch.Start(); - - string projectFile = Path.GetFileNameWithoutExtension(e.ProjectFile); - - string targetName = e.TargetName; - if (targetName == CachePluginStartTarget) - { - project.IsCachePluginProject = true; - _hasUsedCache = true; - } - - if (targetName == _testStartTarget) - { - targetName = "Testing"; - - // Use the minimal start time, so if we run tests in parallel, we can calculate duration - // as this start time, minus time when tests finished. - _testStartTime = _testStartTime == null - ? e.Timestamp - : e.Timestamp < _testStartTime - ? e.Timestamp : _testStartTime; - project.IsTestProject = true; - } - - TerminalNodeStatus nodeStatus = new(projectFile, project.TargetFramework, project.RuntimeIdentifier, targetName, project.Stopwatch); - UpdateNodeStatus(buildEventContext, nodeStatus); + StartNode(e, nodeData); } } - private void UpdateNodeStatus(BuildEventContext buildEventContext, TerminalNodeStatus? nodeStatus) - { - int nodeIndex = NodeIndexForContext(buildEventContext); - _nodes[nodeIndex] = nodeStatus; - } - - /// - /// The callback. Unused. - /// - private void TargetFinished(object sender, TargetFinishedEventArgs e) + /// + protected override void OnTargetFinished(TargetFinishedEventArgs e, TerminalProjectInfo? projectData, TerminalBuildData buildData) { // For cache plugin projects which result in a cache hit, ensure the output path is set // to the item spec corresponding to the GetTargetPath target upon completion. - var buildEventContext = e.BuildEventContext; var targetOutputs = e.TargetOutputs; - if (_restoreContext is not null || buildEventContext is null) + if (projectData is null || targetOutputs is null) { return; } - if (targetOutputs is not null - && _hasUsedCache - && e.TargetName == "GetTargetPath" - && _projects.TryGetValue(new ProjectContext(buildEventContext), out TerminalProjectInfo? project)) + if (_hasUsedCache && e.TargetName == "GetTargetPath" && projectData.IsCachePluginProject) { - if (project is not null && project.IsCachePluginProject) + foreach (ITaskItem output in targetOutputs) { - foreach (ITaskItem output in targetOutputs) - { - project.OutputPath = output.ItemSpec.AsMemory(); - break; - } + projectData.OutputPath = output.ItemSpec.AsMemory(); + break; } } - else if (targetOutputs is not null - && e.TargetName == "InitializeSourceRootMappedPaths" - && _projects.TryGetValue(new ProjectContext(buildEventContext), out project) - && project.SourceRoot is null) + else if (e.TargetName == "InitializeSourceRootMappedPaths" && projectData.SourceRoot is null) { - project.SourceRoot = + projectData.SourceRoot = (targetOutputs as IEnumerable)? .FirstOrDefault(root => !string.IsNullOrEmpty(root.GetMetadata("SourceControl"))) ?.ItemSpec.AsMemory(); } } - /// - /// The callback. - /// - private void TaskStarted(object sender, TaskStartedEventArgs e) + /// + protected override void OnTaskStarted(TaskStartedEventArgs e, TerminalProjectInfo projectData, TerminalBuildData buildData) { - var buildEventContext = e.BuildEventContext; - if (_restoreContext is null && buildEventContext is not null && e.TaskName == "MSBuild") + if (e.TaskName == "MSBuild") { // This will yield the node, so preemptively mark it idle - UpdateNodeStatus(buildEventContext, null); + YieldNode(e); - if (_projects.TryGetValue(new ProjectContext(buildEventContext), out TerminalProjectInfo? project)) - { - project.Stopwatch.Stop(); - } + projectData.Stopwatch.Stop(); } } - /// - /// The callback. - /// - private void MessageRaised(object sender, BuildMessageEventArgs e) + /// + protected override void OnMessageRaised(BuildMessageEventArgs e, TerminalProjectInfo? projectData, TerminalBuildData buildData) { - var buildEventContext = e.BuildEventContext; - if (buildEventContext is null) - { - return; - } - string? message = e.Message; if (message is not null && e.Importance == MessageImportance.High) { - var hasProject = _projects.TryGetValue(new ProjectContext(buildEventContext), out TerminalProjectInfo? project); + var hasProject = projectData != null; // Detect project output path by matching high-importance messages against the "$(MSBuildProjectName) -> ..." // pattern used by the CopyFilesToOutputDirectory target. @@ -1066,10 +916,10 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) { var projectFileName = Path.GetFileName(e.ProjectFile.AsSpan()); if (!projectFileName.IsEmpty && - message.AsSpan().StartsWith(Path.GetFileNameWithoutExtension(projectFileName)) && hasProject) + message.AsSpan().StartsWith(Path.GetFileNameWithoutExtension(projectFileName)) && hasProject && projectData != null) { ReadOnlyMemory outputPath = e.Message.AsMemory().Slice(index + 4); - project!.OutputPath = outputPath; + projectData.OutputPath = outputPath; return; } } @@ -1098,10 +948,9 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) } } - if (hasProject && project!.IsTestProject) + if (hasProject && projectData != null && projectData.IsTestProject) { - var node = _nodes[NodeIndexForContext(buildEventContext)]; - + var node = GetNodeForEvent(e); // Consumes test update messages produced by VSTest and MSTest runner. if (node != null && e is IExtendedBuildEventArgs extendedMessage) { @@ -1112,8 +961,11 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) var indicator = extendedMessage.ExtendedMetadata!["localizedResult"]!; var displayName = extendedMessage.ExtendedMetadata!["displayName"]!; - var status = new TerminalNodeStatus(node.Project, node.TargetFramework, node.RuntimeIdentifier, TerminalColor.Green, indicator, displayName, project.Stopwatch); - UpdateNodeStatus(buildEventContext, status); + var status = new TerminalNodeStatus(node.Project, node.TargetFramework, node.RuntimeIdentifier, TerminalColor.Green, indicator, displayName, projectData.Stopwatch); + if (e.BuildEventContext != null) + { + StartNode(e, status); + } break; } @@ -1122,8 +974,11 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) var indicator = extendedMessage.ExtendedMetadata!["localizedResult"]!; var displayName = extendedMessage.ExtendedMetadata!["displayName"]!; - var status = new TerminalNodeStatus(node.Project, node.TargetFramework, node.RuntimeIdentifier, TerminalColor.Yellow, indicator, displayName, project.Stopwatch); - UpdateNodeStatus(buildEventContext, status); + var status = new TerminalNodeStatus(node.Project, node.TargetFramework, node.RuntimeIdentifier, TerminalColor.Yellow, indicator, displayName, projectData.Stopwatch); + if (e.BuildEventContext != null) + { + StartNode(e, status); + } break; } @@ -1168,9 +1023,9 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) return; } - if (hasProject) + if (hasProject && projectData != null) { - project!.AddBuildMessage(TerminalMessageSeverity.Message, FormatInformationalMessage(e)); + projectData.AddBuildMessage(TerminalMessageSeverity.Message, FormatInformationalMessage(e)); } else { @@ -1194,13 +1049,9 @@ private void MessageRaised(object sender, BuildMessageEventArgs e) } } - /// - /// The callback. - /// - private void WarningRaised(object sender, BuildWarningEventArgs e) + /// + protected override void OnWarningRaised(BuildWarningEventArgs e, TerminalProjectInfo? projectData, TerminalBuildData buildData) { - BuildEventContext? buildEventContext = e.BuildEventContext; - // auth provider messages are 'global' in nature and should be a) immediate reported, and b) not re-reported in the summary. if (IsAuthProviderMessage(e.Message)) { @@ -1208,9 +1059,7 @@ private void WarningRaised(object sender, BuildWarningEventArgs e) return; } - if (buildEventContext is not null - && _projects.TryGetValue(new ProjectContext(buildEventContext), out TerminalProjectInfo? project) - && Verbosity > LoggerVerbosity.Quiet) + if (projectData != null && Verbosity > LoggerVerbosity.Quiet) { // If the warning is not a 'global' auth provider message, but is immediate, we render it immediately // but we don't early return so that the project also tracks it. @@ -1221,17 +1070,17 @@ private void WarningRaised(object sender, BuildWarningEventArgs e) // This is the general case - _most_ warnings are not immediate, so we add them to the project summary // and display them in the per-project and final summary. - project.AddBuildMessage(TerminalMessageSeverity.Warning, FormatWarningMessage(e, TripleIndentation)); + projectData.AddBuildMessage(TerminalMessageSeverity.Warning, FormatWarningMessage(e, TripleIndentation)); } else { // It is necessary to display warning messages reported by MSBuild, - // even if it's not tracked in _projects collection or the verbosity is Quiet. + // even if it's not tracked in projects collection or the verbosity is Quiet. // The idea here (similar to the implementation in ErrorRaised) is that // even in Quiet scenarios we need to show warnings/errors, even if not in // full project-tree view RenderImmediateMessage(FormatWarningMessage(e, Indentation)); - _buildWarningsCount++; + buildData.BuildWarningsCount++; } } @@ -1286,26 +1135,20 @@ private static bool IsAuthProviderMessage(string? message) => } } - /// - /// The callback. - /// - private void ErrorRaised(object sender, BuildErrorEventArgs e) + /// + protected override void OnErrorRaised(BuildErrorEventArgs e, TerminalProjectInfo? projectData, TerminalBuildData buildData) { - BuildEventContext? buildEventContext = e.BuildEventContext; - - if (buildEventContext is not null - && _projects.TryGetValue(new ProjectContext(buildEventContext), out TerminalProjectInfo? project) - && Verbosity > LoggerVerbosity.Quiet) + if (projectData != null && Verbosity > LoggerVerbosity.Quiet) { - project.AddBuildMessage(TerminalMessageSeverity.Error, FormatErrorMessage(e, TripleIndentation)); + projectData.AddBuildMessage(TerminalMessageSeverity.Error, FormatErrorMessage(e, TripleIndentation)); } else { - // It is necessary to display error messages reported by MSBuild, even if it's not tracked in _projects collection or the verbosity is Quiet. + // It is necessary to display error messages reported by MSBuild, even if it's not tracked in projects collection or the verbosity is Quiet. // For nicer formatting, any messages from the engine we strip the file portion from. bool hasMSBuildPlaceholderLocation = e.File.Equals("MSBUILD", StringComparison.Ordinal); RenderImmediateMessage(FormatErrorMessage(e, Indentation, requireFileAndLinePortion: !hasMSBuildPlaceholderLocation)); - _buildErrorsCount++; + buildData.BuildErrorsCount++; } } @@ -1391,6 +1234,35 @@ private void EraseNodes() #region Helpers + private TerminalNodeStatus? GetNodeForEvent(BuildEventArgs e) + { + var node = GetNodeIdForEvent(e); + if (node is int nodeId && _nodes[nodeId] is TerminalNodeStatus status) + { + return status; + } + + return null; + } + + private void StartNode(BuildEventArgs e, TerminalNodeStatus status) + { + var node = GetNodeIdForEvent(e); + if (node is int nodeId) + { + _nodes[nodeId] = status; + } + } + + public void YieldNode(BuildEventArgs e) + { + var node = GetNodeIdForEvent(e); + if (node is int nodeId) + { + _nodes[nodeId] = null; + } + } + /// /// Construct a build result summary string. /// @@ -1437,14 +1309,7 @@ private void RenderImmediateMessage(string message) } } - /// - /// Returns the index corresponding to the given . - /// - private int NodeIndexForContext(BuildEventContext context) - { - // Node IDs reported by the build are 1-based. - return context.NodeId - 1; - } + // NodeIndexForContext is now inherited from base class /// /// Colorizes the filename part of the given path. diff --git a/src/Build/Logging/TerminalLogger/TerminalMessageSeverity.cs b/src/Build/Logging/TerminalLogger/TerminalMessageSeverity.cs index 40fafcea1c6..976db15823f 100644 --- a/src/Build/Logging/TerminalLogger/TerminalMessageSeverity.cs +++ b/src/Build/Logging/TerminalLogger/TerminalMessageSeverity.cs @@ -6,4 +6,4 @@ namespace Microsoft.Build.Logging; /// /// Enumerates the supported message severities. /// -internal enum TerminalMessageSeverity { Message, Warning, Error } +public enum TerminalMessageSeverity { Message, Warning, Error } diff --git a/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs b/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs index dd14f8586ee..eb78f3719e3 100644 --- a/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs +++ b/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs @@ -11,7 +11,7 @@ namespace Microsoft.Build.Logging; /// /// Encapsulates the per-node data shown in live node output. /// -internal class TerminalNodeStatus +public class TerminalNodeStatus { public string Project { get; } public string? TargetFramework { get; } diff --git a/src/Build/Logging/TerminalLogger/TerminalProjectInfo.cs b/src/Build/Logging/TerminalLogger/TerminalProjectInfo.cs index b69c2c515e9..d1b4944e922 100644 --- a/src/Build/Logging/TerminalLogger/TerminalProjectInfo.cs +++ b/src/Build/Logging/TerminalLogger/TerminalProjectInfo.cs @@ -10,32 +10,25 @@ namespace Microsoft.Build.Logging; /// /// A struct containing relevant evaluation-time data that may not be knowable just from ProjectStart events. /// -/// -/// -/// -/// -internal record struct EvalProjectInfo(TerminalLogger.EvalContext context, string? ProjectFile, string? TargetFramework, string? RuntimeIdentifier) +public record struct EvalProjectInfo(string? ProjectFile, string? TargetFramework, string? RuntimeIdentifier) { - public readonly int Id => context.Id; } /// /// Represents a project being built. /// -internal sealed class TerminalProjectInfo +public sealed class TerminalProjectInfo { private List? _buildMessages; /// /// Initialized a new with the given . /// - /// The ProjectContext of this project execution. /// A subset of the interesting eval-time data for this running project /// A stopwatch to time the build of the project. - public TerminalProjectInfo(TerminalLogger.ProjectContext context, EvalProjectInfo evalInfo, StopwatchAbstraction? stopwatch) + public TerminalProjectInfo(EvalProjectInfo evalInfo, StopwatchAbstraction? stopwatch) { _evalInfo = evalInfo; - _context = context; if (stopwatch is not null) { @@ -48,11 +41,6 @@ public TerminalProjectInfo(TerminalLogger.ProjectContext context, EvalProjectInf } } - /// - /// The int value of the ProjectContext id of this project execution. - /// - public int Id => _context.Id; - /// /// The full path to the project file. /// @@ -82,7 +70,6 @@ public TerminalProjectInfo(TerminalLogger.ProjectContext context, EvalProjectInf /// The runtime identifier of the project or null if platform-agnostic. /// public string? RuntimeIdentifier => _evalInfo.RuntimeIdentifier; - private readonly TerminalLogger.ProjectContext _context; private readonly EvalProjectInfo _evalInfo; /// diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index d11075fd13b..45f18e67f5e 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -178,6 +178,7 @@ + diff --git a/src/Framework/Logging/TerminalColor.cs b/src/Framework/Logging/TerminalColor.cs index 10e66d8f719..102eacba511 100644 --- a/src/Framework/Logging/TerminalColor.cs +++ b/src/Framework/Logging/TerminalColor.cs @@ -6,7 +6,7 @@ namespace Microsoft.Build.Framework.Logging; /// /// Enumerates the text colors supported by VT100 terminal. /// -internal enum TerminalColor +public enum TerminalColor { Black = 30, Red = 31,