From 47ba51c0d0b100c690c91ebdb1ccca18831e4ea6 Mon Sep 17 00:00:00 2001 From: Rainer Sigwald Date: Wed, 24 Apr 2024 13:46:57 -0500 Subject: [PATCH] Shared EvaluationContext for graph construction (#9680) Fix #9678 by creating a single shared `EvaluationContext` in the `Graph` object and using it when creating `ProjectInstance`s using the default factory. This is similar to how NuGet static-graph restore already works: https://github.com/NuGet/NuGet.Client/blob/b83566ec2369c4e9fd07e6f95d734dfe370a1e66/src/NuGet.Core/NuGet.Build.Tasks.Console/MSBuildStaticGraphRestore.cs#L885 Since we're evaluating the projects in the graph in parallel and "all at once", the shared caches in the `EvaluationContext` should be a solid improvement. --- .../Graph/ProjectGraph_Tests.cs | 6 ++-- src/Build/Graph/ProjectGraph.cs | 30 +++++++++++++++++-- src/Build/Instance/ProjectInstance.cs | 17 +++++++++++ 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs index 351bef34086..5cc0de97d2c 100644 --- a/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs +++ b/src/Build.UnitTests/Graph/ProjectGraph_Tests.cs @@ -9,6 +9,7 @@ using System.Text.RegularExpressions; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; using Microsoft.Build.Framework; @@ -107,10 +108,11 @@ public void ConstructWithSingleNodeWithProjectInstanceFactory() (projectPath, globalProperties, projectCollection) => { factoryCalled = true; - return ProjectGraph.DefaultProjectInstanceFactory( + return ProjectGraph.StaticProjectInstanceFactory( projectPath, globalProperties, - projectCollection); + projectCollection, + EvaluationContext.Create(EvaluationContext.SharingPolicy.Isolated)); }); projectGraph.ProjectNodes.Count.ShouldBe(1); projectGraph.ProjectNodes.First().ProjectInstance.FullPath.ShouldBe(entryProject.Path); diff --git a/src/Build/Graph/ProjectGraph.cs b/src/Build/Graph/ProjectGraph.cs index 8bbf4e453c4..4df1c7e3ea7 100644 --- a/src/Build/Graph/ProjectGraph.cs +++ b/src/Build/Graph/ProjectGraph.cs @@ -13,6 +13,7 @@ using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; +using Microsoft.Build.Evaluation.Context; using Microsoft.Build.Eventing; using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; @@ -58,6 +59,8 @@ public delegate ProjectInstance ProjectInstanceFactoryFunc( private readonly Lazy> _projectNodesTopologicallySorted; + private readonly EvaluationContext _evaluationContext = null; + private GraphBuilder.GraphEdges Edges { get; } internal GraphBuilder.GraphEdges TestOnly_Edges => Edges; @@ -422,7 +425,11 @@ public ProjectGraph( var measurementInfo = BeginMeasurement(); - projectInstanceFactory ??= DefaultProjectInstanceFactory; + if (projectInstanceFactory is null) + { + _evaluationContext = EvaluationContext.Create(EvaluationContext.SharingPolicy.Shared); + projectInstanceFactory = DefaultProjectInstanceFactory; + } var graphBuilder = new GraphBuilder( entryPoints, @@ -825,16 +832,33 @@ private static ImmutableList ExpandDefaultTargets(ImmutableList return targets; } - internal static ProjectInstance DefaultProjectInstanceFactory( + internal ProjectInstance DefaultProjectInstanceFactory( string projectPath, Dictionary globalProperties, ProjectCollection projectCollection) + { + Debug.Assert(_evaluationContext is not null); + + return StaticProjectInstanceFactory( + projectPath, + globalProperties, + projectCollection, + _evaluationContext); + } + + internal static ProjectInstance StaticProjectInstanceFactory( + string projectPath, + Dictionary globalProperties, + ProjectCollection projectCollection, + EvaluationContext evaluationContext) { return new ProjectInstance( projectPath, globalProperties, MSBuildConstants.CurrentToolsVersion, - projectCollection); + subToolsetVersion: null, + projectCollection, + evaluationContext); } private struct ProjectGraphBuildRequest : IEquatable diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index 572e73b2c86..32a408a5ca4 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -254,6 +254,23 @@ public ProjectInstance(string projectFile, IDictionary globalPro { } + /// + /// Creates a ProjectInstance directly. + /// No intermediate Project object is created. + /// This is ideal if the project is simply going to be built, and not displayed or edited. + /// + /// The path to the project file. + /// The global properties to use. + /// The tools version. May be . + /// The sub-toolset version, used in tandem with to determine the set of toolset properties. May be . + /// Project collection + /// Context to evaluate inside, potentially sharing caches with other evaluations. + /// Indicates if loading the project is allowed to interact with the user. + internal ProjectInstance(string projectFile, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, EvaluationContext context, bool interactive = false) + : this(projectFile, globalProperties, toolsVersion, subToolsetVersion, projectCollection, projectLoadSettings: null, evaluationContext: context, directoryCacheFactory: null, interactive: interactive) + { + } + /// /// Creates a ProjectInstance directly. /// No intermediate Project object is created.