diff --git a/src/MSBuild/TerminalLogger/EvaluationData.cs b/src/MSBuild/TerminalLogger/EvaluationData.cs
new file mode 100644
index 00000000000..228268a1c82
--- /dev/null
+++ b/src/MSBuild/TerminalLogger/EvaluationData.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+internal sealed class EvaluationData
+{
+ ///
+ /// Captures data that comes from project evaluation, is assumed to not change through execution,
+ /// and is referenced for rendering purposes throughout the execution of the build.
+ ///
+ ///
+ public EvaluationData(string? targetFramework)
+ {
+ TargetFramework = targetFramework;
+ }
+
+ ///
+ /// The target framework of the project or null if not multi-targeting.
+ ///
+ public string? TargetFramework { get; }
+
+ ///
+ /// This property is true when the project would prefer to have full paths in the logs and/or for processing tasks.
+ ///
+ ///
+ /// There's an MSBuild property called GenerateFullPaths that would be a great knob to use for this, but the Common
+ /// Targets set it to true if not set, and setting it to false completely destroys the terminal logger output.
+ /// That's why this value is hardcoded to false for now, until we define a better mechanism.
+ ///
+ public bool GenerateFullPaths { get; } = false;
+}
diff --git a/src/MSBuild/TerminalLogger/Project.cs b/src/MSBuild/TerminalLogger/Project.cs
index 27da2e6b7c2..6bcc23fb69a 100644
--- a/src/MSBuild/TerminalLogger/Project.cs
+++ b/src/MSBuild/TerminalLogger/Project.cs
@@ -22,10 +22,8 @@ internal sealed class Project
/// Initialized a new with the given .
///
/// The target framework of the project or null if not multi-targeting.
- public Project(string? targetFramework, StopwatchAbstraction? stopwatch)
+ public Project(StopwatchAbstraction? stopwatch)
{
- TargetFramework = targetFramework;
-
if (stopwatch is not null)
{
stopwatch.Start();
@@ -52,11 +50,6 @@ public Project(string? targetFramework, StopwatchAbstraction? stopwatch)
///
public DirectoryInfo? SourceRoot { get; set; }
- ///
- /// The target framework of the project or null if not multi-targeting.
- ///
- public string? TargetFramework { get; }
-
///
/// True when the project has run target with name "_TestRunStart" defined in .
///
diff --git a/src/MSBuild/TerminalLogger/TerminalLogger.cs b/src/MSBuild/TerminalLogger/TerminalLogger.cs
index 86e65f1e3e5..6c36c106f07 100644
--- a/src/MSBuild/TerminalLogger/TerminalLogger.cs
+++ b/src/MSBuild/TerminalLogger/TerminalLogger.cs
@@ -61,6 +61,16 @@ public ProjectContext(BuildEventContext context)
{ }
}
+ ///
+ /// A wrapper over the eval context ID passed to us in logger events.
+ ///
+ internal record struct EvalContext(int Id)
+ {
+ public EvalContext(BuildEventContext context)
+ : this(context.EvaluationId)
+ { }
+ }
+
///
/// The indentation to use for all build output.
///
@@ -97,6 +107,14 @@ public ProjectContext(BuildEventContext context)
///
private readonly Dictionary _projects = 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 _evaluations = new();
+
///
/// Tracks the work currently being done by build nodes. Null means the node is not doing any work worth reporting.
///
@@ -402,15 +420,30 @@ private bool TryApplyShowCommandLineParameter(string? parameterValue)
return null;
}
- private Project? CreateProject(BuildEventContext? context, System.Collections.IEnumerable? globalProperties, System.Collections.IEnumerable? properties)
+ private EvaluationData? CreateEvaluationData(BuildEventContext? context, System.Collections.IEnumerable? globalProperties, System.Collections.IEnumerable? properties)
+ {
+ if (context is not null)
+ {
+ var evalContext = new EvalContext(context);
+ if (!_evaluations.TryGetValue(evalContext, out EvaluationData? evalData))
+ {
+ string? tfm = DetectTFM(globalProperties, properties);
+ evalData = new(tfm);
+ _evaluations.Add(evalContext, evalData);
+ }
+ return evalData;
+ }
+ return null;
+ }
+
+ private Project? CreateProject(BuildEventContext? context)
{
if (context is not null)
{
var projectContext = new ProjectContext(context);
if (!_projects.TryGetValue(projectContext, out Project? project))
{
- string? tfm = DetectTFM(globalProperties, properties);
- project = new(tfm, CreateStopwatch?.Invoke());
+ project = new(CreateStopwatch?.Invoke());
_projects.Add(projectContext, project);
}
return project;
@@ -459,6 +492,7 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e)
_refresher?.Join();
_projects.Clear();
+ _evaluations.Clear();
Terminal.BeginUpdate();
try
@@ -535,10 +569,7 @@ private void StatusEventRaised(object sender, BuildStatusEventArgs e)
else if (e is ProjectEvaluationFinishedEventArgs evalFinished
&& evalFinished.BuildEventContext is not null)
{
- if (CreateProject(evalFinished.BuildEventContext, evalFinished.GlobalProperties, evalFinished.Properties) is Project project)
- {
- TryDetectGenerateFullPaths(evalFinished, project);
- }
+ CreateEvaluationData(evalFinished.BuildEventContext, evalFinished.GlobalProperties, evalFinished.Properties);
}
}
@@ -553,34 +584,20 @@ private void ProjectStarted(object sender, ProjectStartedEventArgs e)
return;
}
- CreateProject(e.BuildEventContext, e.Properties, e.GlobalProperties);
+ CreateProject(e.BuildEventContext);
ProjectContext c = new ProjectContext(buildEventContext);
- if (_restoreContext is null)
+ if (_restoreContext is null && _projects.TryGetValue(c, out var project))
{
// First ever restore in the build is starting.
if (e.TargetNames == "Restore" && !_restoreFinished)
{
_restoreContext = c;
int nodeIndex = NodeIndexForContext(buildEventContext);
- _nodes[nodeIndex] = new NodeStatus(e.ProjectFile!, null, "Restore", _projects[c].Stopwatch);
+ _nodes[nodeIndex] = new NodeStatus(e.ProjectFile!, null, "Restore", project.Stopwatch);
}
}
}
- private void TryDetectGenerateFullPaths(ProjectEvaluationFinishedEventArgs e, Project project)
- {
- if (TryGetValue(e.GlobalProperties, "GenerateFullPaths") is string generateFullPathsGPString
- && bool.TryParse(generateFullPathsGPString, out bool generateFullPathsValue))
- {
- project.GenerateFullPaths = generateFullPathsValue;
- }
- else if (TryGetValue(e.Properties, "GenerateFullPaths") is string generateFullPathsPString
- && bool.TryParse(generateFullPathsPString, out bool generateFullPathsPropertyValue))
- {
- project.GenerateFullPaths = generateFullPathsPropertyValue;
- }
- }
-
///
/// The callback.
///
@@ -605,8 +622,9 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
}
ProjectContext c = new(buildEventContext);
+ EvalContext evalContext = new(buildEventContext);
- if (_projects.TryGetValue(c, out Project? project))
+ if (_projects.TryGetValue(c, out Project? project) && _evaluations.TryGetValue(evalContext, out EvaluationData? evaluation))
{
lock (_lock)
{
@@ -665,7 +683,7 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
// Show project build complete and its output
if (project.IsTestProject)
{
- if (string.IsNullOrEmpty(project.TargetFramework))
+ if (string.IsNullOrEmpty(evaluation.TargetFramework))
{
Terminal.Write(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("TestProjectFinished_NoTF",
Indentation,
@@ -678,14 +696,14 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
Terminal.Write(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("TestProjectFinished_WithTF",
Indentation,
projectFile,
- AnsiCodes.Colorize(project.TargetFramework, TargetFrameworkColor),
+ AnsiCodes.Colorize(evaluation.TargetFramework, TargetFrameworkColor),
buildResult,
duration));
}
}
else
{
- if (string.IsNullOrEmpty(project.TargetFramework))
+ if (string.IsNullOrEmpty(evaluation.TargetFramework))
{
Terminal.Write(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectFinished_NoTF",
Indentation,
@@ -698,7 +716,7 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
Terminal.Write(ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword("ProjectFinished_WithTF",
Indentation,
projectFile,
- AnsiCodes.Colorize(project.TargetFramework, TargetFrameworkColor),
+ AnsiCodes.Colorize(evaluation.TargetFramework, TargetFrameworkColor),
buildResult,
duration));
}
@@ -729,7 +747,7 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
}
string? resolvedPathToOutput = null;
- if (project.GenerateFullPaths)
+ if (evaluation.GenerateFullPaths)
{
resolvedPathToOutput = outputPathSpan.ToString();
}
@@ -826,7 +844,9 @@ private static bool IsChildOf(FileInfo file, DirectoryInfo parent)
private void TargetStarted(object sender, TargetStartedEventArgs e)
{
var buildEventContext = e.BuildEventContext;
- if (_restoreContext is null && buildEventContext is not null && _projects.TryGetValue(new ProjectContext(buildEventContext), out Project? project))
+ if (_restoreContext is null && buildEventContext is not null
+ && _projects.TryGetValue(new(buildEventContext), out Project? project)
+ && _evaluations.TryGetValue(new(buildEventContext), out EvaluationData? evaluation))
{
project.Stopwatch.Start();
@@ -852,7 +872,7 @@ private void TargetStarted(object sender, TargetStartedEventArgs e)
project.IsTestProject = true;
}
- NodeStatus nodeStatus = new(projectFile, project.TargetFramework, targetName, project.Stopwatch);
+ NodeStatus nodeStatus = new(projectFile, evaluation.TargetFramework, targetName, project.Stopwatch);
UpdateNodeStatus(buildEventContext, nodeStatus);
}
}