Skip to content

Commit 944145c

Browse files
committed
Only record extra Evaluator data when needed (#1060)
This saves memory
1 parent e701988 commit 944145c

File tree

3 files changed

+110
-25
lines changed

3 files changed

+110
-25
lines changed

src/XMakeBuildEngine/Definition/Project.cs

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,12 @@ public enum ProjectLoadSettings
6161
/// <summary>
6262
/// Throw an exception and stop the evaluation of a project if any circular imports are detected
6363
/// </summary>
64-
RejectCircularImports = 4
64+
RejectCircularImports = 4,
65+
66+
/// <summary>
67+
/// Record the item elements that got evaluated
68+
/// </summary>
69+
RecordEvaluatedItemElements = 8
6570
}
6671

6772
/// <summary>
@@ -1069,6 +1074,7 @@ public static string GetEvaluatedItemIncludeEscaped(ProjectItemDefinition item)
10691074
/// </returns>
10701075
public List<GlobResult> GetAllGlobs()
10711076
{
1077+
ReevaluateForPostMortemAnalysisIfNecessary();
10721078
return GetAllGlobs(_data.EvaluatedItemElements);
10731079
}
10741080

@@ -1082,6 +1088,8 @@ public List<GlobResult> GetAllGlobs(string itemType)
10821088
{
10831089
return new List<GlobResult>();
10841090
}
1091+
1092+
ReevaluateForPostMortemAnalysisIfNecessary();
10851093
return GetAllGlobs(GetItemElementsByType(_data.EvaluatedItemElements, itemType));
10861094
}
10871095

@@ -1156,6 +1164,7 @@ private IEnumerable<GlobResult> GetAllGlobs(ProjectItemElement itemElement)
11561164
/// </returns>
11571165
public List<ProvenanceResult> GetItemProvenance(string itemToMatch)
11581166
{
1167+
ReevaluateForPostMortemAnalysisIfNecessary();
11591168
return GetItemProvenance(itemToMatch, _data.EvaluatedItemElements);
11601169
}
11611170

@@ -1166,6 +1175,7 @@ public List<ProvenanceResult> GetItemProvenance(string itemToMatch)
11661175
/// <param name="itemType">The item type to constrain the search in</param>
11671176
public List<ProvenanceResult> GetItemProvenance(string itemToMatch, string itemType)
11681177
{
1178+
ReevaluateForPostMortemAnalysisIfNecessary();
11691179
return GetItemProvenance(itemToMatch, GetItemElementsByType(_data.EvaluatedItemElements, itemType));
11701180
}
11711181

@@ -1184,11 +1194,30 @@ public List<ProvenanceResult> GetItemProvenance(ProjectItem item)
11841194
return new List<ProvenanceResult>();
11851195
}
11861196

1197+
ReevaluateForPostMortemAnalysisIfNecessary();
11871198
var itemElementsAbove = GetItemElementsThatMightAffectItem(_data.EvaluatedItemElements, item);
11881199

11891200
return GetItemProvenance(item.EvaluatedInclude, itemElementsAbove);
11901201
}
11911202

1203+
/// <summary>
1204+
/// Some project APIs need to do analysis that requires the Evaluator to record more data than usual as it evaluates.
1205+
/// This method checks if the Evaluator was run with the extra required settings and if not, does a re-evaluation.
1206+
/// If a re-evaluation was necessary, it saves this information so a next call does not re-evaluate.
1207+
///
1208+
/// Using this method avoids storing extra data in memory when its not needed.
1209+
/// </summary>
1210+
private void ReevaluateForPostMortemAnalysisIfNecessary()
1211+
{
1212+
if (_loadSettings.HasFlag(ProjectLoadSettings.RecordEvaluatedItemElements))
1213+
{
1214+
return;
1215+
}
1216+
1217+
_loadSettings = _loadSettings | ProjectLoadSettings.RecordEvaluatedItemElements;
1218+
Reevaluate(LoggingService, _loadSettings);
1219+
}
1220+
11921221
private static IEnumerable<ProjectItemElement> GetItemElementsThatMightAffectItem(List<ProjectItemElement> evaluatedItemElements, ProjectItem item)
11931222
{
11941223
return evaluatedItemElements
@@ -2392,34 +2421,22 @@ private ProjectInstance CreateProjectInstance(ILoggingService loggingServiceForE
23922421
/// Re-evaluates the project using the specified logging service.
23932422
/// </summary>
23942423
private void ReevaluateIfNecessary(ILoggingService loggingServiceForEvaluation)
2424+
{
2425+
ReevaluateIfNecessary(loggingServiceForEvaluation, _loadSettings);
2426+
}
2427+
2428+
/// <summary>
2429+
/// Re-evaluates the project using the specified logging service and load settings.
2430+
/// </summary>
2431+
private void ReevaluateIfNecessary(ILoggingService loggingServiceForEvaluation, ProjectLoadSettings loadSettings)
23952432
{
23962433
// We will skip the evaluation if the flag is set. This will give us better performance on scenarios
23972434
// that we know we don't have to reevaluate. One example is project conversion bulk addfiles and set attributes.
23982435
if (!SkipEvaluation && !_projectCollection.SkipEvaluation && IsDirty)
23992436
{
24002437
try
24012438
{
2402-
Evaluator<ProjectProperty, ProjectItem, ProjectMetadata, ProjectItemDefinition>.Evaluate(_data, _xml, _loadSettings, ProjectCollection.MaxNodeCount, ProjectCollection.EnvironmentProperties, loggingServiceForEvaluation, new ProjectItemFactory(this), _projectCollection as IToolsetProvider, _projectCollection.ProjectRootElementCache, s_buildEventContext, null /* no project instance for debugging */);
2403-
2404-
// We have to do this after evaluation, because evaluation might have changed
2405-
// the imports being pulled in.
2406-
int highestXmlVersion = Xml.Version;
2407-
2408-
if (_data.ImportClosure != null)
2409-
{
2410-
foreach (Triple<ProjectImportElement, ProjectRootElement, int> triple in _data.ImportClosure)
2411-
{
2412-
highestXmlVersion = (highestXmlVersion < triple.Third) ? triple.Third : highestXmlVersion;
2413-
}
2414-
}
2415-
2416-
_explicitlyMarkedDirty = false;
2417-
_evaluatedVersion = highestXmlVersion;
2418-
_evaluatedToolsetCollectionVersion = ProjectCollection.ToolsetsVersion;
2419-
_evaluationCounter = GetNextEvaluationCounter();
2420-
_data.HasUnsavedChanges = false;
2421-
2422-
ErrorUtilities.VerifyThrow(!IsDirty, "Should not be dirty now");
2439+
Reevaluate(loggingServiceForEvaluation, loadSettings);
24232440
}
24242441
catch (InvalidProjectFileException ex)
24252442
{
@@ -2429,6 +2446,31 @@ private void ReevaluateIfNecessary(ILoggingService loggingServiceForEvaluation)
24292446
}
24302447
}
24312448

2449+
private void Reevaluate(ILoggingService loggingServiceForEvaluation, ProjectLoadSettings loadSettings)
2450+
{
2451+
Evaluator<ProjectProperty, ProjectItem, ProjectMetadata, ProjectItemDefinition>.Evaluate(_data, _xml, loadSettings, ProjectCollection.MaxNodeCount, ProjectCollection.EnvironmentProperties, loggingServiceForEvaluation, new ProjectItemFactory(this), _projectCollection as IToolsetProvider, _projectCollection.ProjectRootElementCache, s_buildEventContext, null /* no project instance for debugging */);
2452+
2453+
// We have to do this after evaluation, because evaluation might have changed
2454+
// the imports being pulled in.
2455+
int highestXmlVersion = Xml.Version;
2456+
2457+
if (_data.ImportClosure != null)
2458+
{
2459+
foreach (Triple<ProjectImportElement, ProjectRootElement, int> triple in _data.ImportClosure)
2460+
{
2461+
highestXmlVersion = (highestXmlVersion < triple.Third) ? triple.Third : highestXmlVersion;
2462+
}
2463+
}
2464+
2465+
_explicitlyMarkedDirty = false;
2466+
_evaluatedVersion = highestXmlVersion;
2467+
_evaluatedToolsetCollectionVersion = ProjectCollection.ToolsetsVersion;
2468+
_evaluationCounter = GetNextEvaluationCounter();
2469+
_data.HasUnsavedChanges = false;
2470+
2471+
ErrorUtilities.VerifyThrow(!IsDirty, "Should not be dirty now");
2472+
}
2473+
24322474
/// <summary>
24332475
/// Common code for the constructors.
24342476
/// Applies global properties that are on the collection.

src/XMakeBuildEngine/Evaluation/Evaluator.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1642,7 +1642,7 @@ private void EvaluateItemElement(bool itemGroupConditionResult, ProjectItemEleme
16421642

16431643
if (conditionResult)
16441644
{
1645-
_data.EvaluatedItemElements.Add(itemElement);
1645+
RecordEvaluatedItemElement(itemElement);
16461646
}
16471647

16481648
return;
@@ -1660,7 +1660,7 @@ private void EvaluateItemElement(bool itemGroupConditionResult, ProjectItemEleme
16601660

16611661
private void EvaluateItemElementUpdate(ProjectItemElement itemElement)
16621662
{
1663-
_data.EvaluatedItemElements.Add(itemElement);
1663+
RecordEvaluatedItemElement(itemElement);
16641664

16651665
var expandedItemSet =
16661666
new HashSet<string>(
@@ -1734,7 +1734,7 @@ private void EvaluateItemElementInclude(bool itemGroupConditionResult, bool item
17341734
// FINALLY: Add the items to the project
17351735
if (itemConditionResult && itemGroupConditionResult)
17361736
{
1737-
_data.EvaluatedItemElements.Add(itemElement);
1737+
RecordEvaluatedItemElement(itemElement);
17381738

17391739
foreach (I item in items)
17401740
{
@@ -2624,6 +2624,14 @@ private string GetCurrentDirectoryForConditionEvaluation(ProjectElement element)
26242624
}
26252625
}
26262626

2627+
private void RecordEvaluatedItemElement(ProjectItemElement itemElement)
2628+
{
2629+
if (_loadSettings.HasFlag(ProjectLoadSettings.RecordEvaluatedItemElements))
2630+
{
2631+
_data.EvaluatedItemElements.Add(itemElement);
2632+
}
2633+
}
2634+
26272635
/// <summary>
26282636
/// Throws InvalidProjectException because we failed to import a project which contained a ProjectImportSearchPath fall-back.
26292637
/// <param name="searchPathMatch">MSBuildExtensionsPath reference kind found in the Project attribute of the Import element</param>

src/XMakeBuildEngine/UnitTestsPublicOM/Definition/Project_Tests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2535,6 +2535,41 @@ public void GetItemProvenanceOnlyGlob()
25352535
AssertProvenanceResult(expected, project, "2.foo");
25362536
}
25372537

2538+
[Fact]
2539+
public void GetItemProvenanceShouldReturnTheSameResultsIfProjectIsReevaluated()
2540+
{
2541+
var projectContents =
2542+
@"<Project ToolsVersion='msbuilddefaulttoolsversion' DefaultTargets='Build' xmlns='msbuildnamespace'>
2543+
<ItemGroup>
2544+
<A Include=`*.foo`/>
2545+
<B Include=`1.foo;2.foo` Exclude=`*.foo`/>
2546+
<C Include=`2` Exclude=`*.bar`/>
2547+
</ItemGroup>
2548+
</Project>
2549+
";
2550+
2551+
var expected = new ProvenanceResultTupleList
2552+
{
2553+
Tuple.Create("A", Operation.Include, Provenance.Glob, 1),
2554+
Tuple.Create("B", Operation.Exclude, Provenance.Glob, 1)
2555+
};
2556+
2557+
// Create a project. The initial evaluation does not record the information needed for GetItemProvenance
2558+
var project = ObjectModelHelpers.CreateInMemoryProject(projectContents);
2559+
2560+
// Since GetItemProvenance does not have the required evaluator data (evaluated item elements), it internally reevaluates the project to collect it
2561+
var provenanceResult = project.GetItemProvenance("2.foo");
2562+
AssertProvenanceResult(expected, provenanceResult);
2563+
2564+
// Dirty the xml to force another reevaluation.
2565+
project.AddItem("new", "new value");
2566+
project.ReevaluateIfNecessary();
2567+
2568+
// Assert that provenance returns the same result and that no data duplication happened
2569+
provenanceResult = project.GetItemProvenance("2.foo");
2570+
AssertProvenanceResult(expected, provenanceResult);
2571+
}
2572+
25382573
[Fact]
25392574
public void GetItemProvenanceShouldHandleComplexGlobExclusion()
25402575
{

0 commit comments

Comments
 (0)