Skip to content

Commit

Permalink
Introduce a cache of Evaluation data that is looked up
Browse files Browse the repository at this point in the history
  • Loading branch information
baronfel committed Mar 20, 2024
1 parent 7069775 commit 518e84a
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 45 deletions.
30 changes: 30 additions & 0 deletions src/MSBuild/TerminalLogger/EvaluationData.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
/// <param name="targetFramework"></param>
public EvaluationData(string? targetFramework)
{
TargetFramework = targetFramework;
}

/// <summary>
/// The target framework of the project or null if not multi-targeting.
/// </summary>
public string? TargetFramework { get; }

/// <summary>
/// This property is true when the project would prefer to have full paths in the logs and/or for processing tasks.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public bool GenerateFullPaths { get; } = false;
}
14 changes: 1 addition & 13 deletions src/MSBuild/TerminalLogger/Project.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ internal sealed class Project
/// Initialized a new <see cref="Project"/> with the given <paramref name="targetFramework"/>.
/// </summary>
/// <param name="targetFramework">The target framework of the project or null if not multi-targeting.</param>
public Project(string? targetFramework, StopwatchAbstraction? stopwatch)
public Project(StopwatchAbstraction? stopwatch)
{
TargetFramework = targetFramework;

if (stopwatch is not null)
{
stopwatch.Start();
Expand All @@ -52,21 +50,11 @@ public Project(string? targetFramework, StopwatchAbstraction? stopwatch)
/// </summary>
public DirectoryInfo? SourceRoot { get; set; }

/// <summary>
/// The target framework of the project or null if not multi-targeting.
/// </summary>
public string? TargetFramework { get; }

/// <summary>
/// True when the project has run target with name "_TestRunStart" defined in <see cref="TerminalLogger._testStartTarget"/>.
/// </summary>
public bool IsTestProject { get; set; }

/// <summary>
/// This property is true when the project would prefer to have full paths in the logs and/or for processing tasks.
/// </summary>
public bool GenerateFullPaths { get; set; }

/// <summary>
/// A lazily initialized list of build messages/warnings/errors raised during the build.
/// </summary>
Expand Down
84 changes: 52 additions & 32 deletions src/MSBuild/TerminalLogger/TerminalLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ public ProjectContext(BuildEventContext context)
{ }
}

/// <summary>
/// A wrapper over the eval context ID passed to us in <see cref="IEventSource"/> logger events.
/// </summary>
internal record struct EvalContext(int Id)
{
public EvalContext(BuildEventContext context)
: this(context.EvaluationId)
{ }
}

/// <summary>
/// The indentation to use for all build output.
/// </summary>
Expand Down Expand Up @@ -83,6 +93,14 @@ public ProjectContext(BuildEventContext context)
/// </remarks>
private readonly Dictionary<ProjectContext, Project> _projects = new();

/// <summary>
/// Tracks the status of all relevant projects seen so far.
/// </summary>
/// <remarks>
/// Keyed by an ID that gets passed to logger callbacks, this allows us to quickly look up the corresponding project.
/// </remarks>
private readonly Dictionary<EvalContext, EvaluationData> _evaluations = new();

/// <summary>
/// Tracks the work currently being done by build nodes. Null means the node is not doing any work worth reporting.
/// </summary>
Expand Down Expand Up @@ -268,10 +286,7 @@ private void StatusEventRaised(object sender, BuildStatusEventArgs e)
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);
}
}

Expand Down Expand Up @@ -313,15 +328,30 @@ private void StatusEventRaised(object sender, BuildStatusEventArgs e)
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;
Expand Down Expand Up @@ -370,6 +400,7 @@ private void BuildFinished(object sender, BuildFinishedEventArgs e)
_refresher?.Join();

_projects.Clear();
_evaluations.Clear();

Terminal.BeginUpdate();
try
Expand Down Expand Up @@ -441,34 +472,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;
}
}

/// <summary>
/// The <see cref="IEventSource.ProjectFinished"/> callback.
/// </summary>
Expand All @@ -487,8 +504,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)
{
Expand Down Expand Up @@ -544,7 +562,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,
Expand All @@ -557,14 +575,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,
Expand All @@ -577,7 +595,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));
}
Expand Down Expand Up @@ -606,7 +624,7 @@ private void ProjectFinished(object sender, ProjectFinishedEventArgs e)
}

string? resolvedPathToOutput = null;
if (project.GenerateFullPaths)
if (evaluation.GenerateFullPaths)
{
resolvedPathToOutput = outputPathSpan.ToString();
}
Expand Down Expand Up @@ -703,7 +721,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();

Expand All @@ -723,7 +743,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);
}
}
Expand Down

0 comments on commit 518e84a

Please sign in to comment.