Skip to content

Commit f7ab99b

Browse files
committed
Handle direct inner-build edges in static graph
Preserve explicit inner-build references when outer-build expansion produces the same graph edge, and merge their target requirements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bd403e09-8890-421f-ab7c-83eaac6d05b1
1 parent c003388 commit f7ab99b

2 files changed

Lines changed: 100 additions & 6 deletions

File tree

src/Build.UnitTests/Graph/ProjectGraph_Tests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,6 +1973,90 @@ public void InnerBuildsFromNonRootOuterBuildInheritEdgesToOuterBuild()
19731973
}
19741974
}
19751975

1976+
[Theory]
1977+
[InlineData(null, null)]
1978+
[InlineData("OuterTarget", null)]
1979+
[InlineData(null, "ExplicitTarget")]
1980+
[InlineData("OuterTarget", "ExplicitTarget")]
1981+
public void DirectInnerBuildReferenceIsPreservedWhenOuterBuildGeneratesTheSameEdge(
1982+
string outerTargets,
1983+
string explicitTargets)
1984+
{
1985+
string outerTargetsMetadata = outerTargets is null ? string.Empty : $" Targets=\"{outerTargets}\"";
1986+
string explicitTargetsMetadata = explicitTargets is null ? string.Empty : $" Targets=\"{explicitTargets}\"";
1987+
string expectedOuterTarget = outerTargets ?? "ChildDefault";
1988+
string expectedExplicitTarget = explicitTargets ?? "ChildDefault";
1989+
string expectedMergedTargets = outerTargets is null && explicitTargets is null
1990+
? string.Empty
1991+
: $"{expectedExplicitTarget};{expectedOuterTarget}";
1992+
1993+
TransientTestFile child = _env.CreateFile("child.proj", """
1994+
<Project DefaultTargets="ChildDefault">
1995+
<PropertyGroup>
1996+
<InnerBuildProperty>TargetFramework</InnerBuildProperty>
1997+
<InnerBuildPropertyValues>TargetFrameworks</InnerBuildPropertyValues>
1998+
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
1999+
</PropertyGroup>
2000+
<Target Name="ChildDefault" />
2001+
<Target Name="OuterTarget" />
2002+
<Target Name="ExplicitTarget" />
2003+
</Project>
2004+
""");
2005+
2006+
TransientTestFile root = _env.CreateFile("root.proj", $"""
2007+
<Project DefaultTargets="Build">
2008+
<ItemGroup>
2009+
<ProjectReference Include="{child.Path}"{outerTargetsMetadata} />
2010+
<ProjectReference Include="{child.Path}"
2011+
SetTargetFramework="TargetFramework=net8.0"{explicitTargetsMetadata} />
2012+
<ProjectReferenceTargets Include="Build"
2013+
Targets="{MSBuildConstants.ProjectReferenceTargetsOrDefaultTargetsMarker}"
2014+
OuterBuild="true" />
2015+
<ProjectReferenceTargets Include="Build"
2016+
Targets="{MSBuildConstants.ProjectReferenceTargetsOrDefaultTargetsMarker}" />
2017+
</ItemGroup>
2018+
<Target Name="Build" />
2019+
</Project>
2020+
""");
2021+
2022+
var graph = new ProjectGraph(root.Path);
2023+
2024+
ProjectGraphNode rootNode = graph.EntryPointNodes.ShouldHaveSingleItem();
2025+
ProjectGraphNode outerBuild = graph.ProjectNodes.Single(
2026+
node => node.ProjectInstance.FullPath == child.Path &&
2027+
!node.ProjectInstance.GlobalProperties.ContainsKey("TargetFramework"));
2028+
ProjectGraphNode net8Build = graph.ProjectNodes.Single(
2029+
node => node.ProjectInstance.FullPath == child.Path &&
2030+
node.ProjectInstance.GlobalProperties.TryGetValue("TargetFramework", out string targetFramework) &&
2031+
targetFramework == "net8.0");
2032+
ProjectGraphNode net9Build = graph.ProjectNodes.Single(
2033+
node => node.ProjectInstance.FullPath == child.Path &&
2034+
node.ProjectInstance.GlobalProperties.TryGetValue("TargetFramework", out string targetFramework) &&
2035+
targetFramework == "net9.0");
2036+
2037+
graph.ProjectNodes.Count.ShouldBe(4);
2038+
rootNode.ProjectReferences.Count.ShouldBe(3);
2039+
outerBuild.ProjectReferences.ShouldBe([net8Build, net9Build], ignoreOrder: true);
2040+
2041+
ProjectItemInstance edgeToOuterBuild = graph.TestOnly_Edges[(rootNode, outerBuild)];
2042+
ProjectItemInstance edgeToNet8Build = graph.TestOnly_Edges[(rootNode, net8Build)];
2043+
ProjectItemInstance edgeToNet9Build = graph.TestOnly_Edges[(rootNode, net9Build)];
2044+
2045+
edgeToNet8Build.ItemType.ShouldBe(ItemTypeNames.ProjectReference);
2046+
edgeToNet8Build.GetMetadataValue("SetTargetFramework").ShouldBe("TargetFramework=net8.0");
2047+
edgeToNet8Build.GetMetadataValue("Targets").ShouldBe(expectedMergedTargets);
2048+
edgeToNet9Build.ShouldBeSameAs(edgeToOuterBuild);
2049+
2050+
IReadOnlyDictionary<ProjectGraphNode, ImmutableList<string>> targetLists = graph.GetTargetLists(["Build"]);
2051+
targetLists[rootNode].ShouldBe(["Build"]);
2052+
targetLists[outerBuild].ShouldBe([expectedOuterTarget]);
2053+
targetLists[net8Build].ShouldBe(
2054+
outerTargets is null && explicitTargets is null
2055+
? ["ChildDefault"]
2056+
: [expectedExplicitTarget, expectedOuterTarget]);
2057+
targetLists[net9Build].ShouldBe([expectedOuterTarget]);
2058+
}
2059+
19762060
[Fact]
19772061
public void DuplicatedInnerBuildMonikersShouldGetDeduplicated()
19782062
{

src/Build/Graph/ProjectInterpretation.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -303,12 +303,22 @@ public void AddInnerBuildEdges(Dictionary<ConfigurationMetadata, ParsedProject>
303303

304304
if (outerBuildReferencingProject.ProjectReferences.Contains(innerBuild))
305305
{
306-
Assumed.True(graphBuilder.Edges[(outerBuildReferencingProject, innerBuild)]
307-
.ItemType.Equals(
308-
TransitiveReferenceItemName,
309-
StringComparison.OrdinalIgnoreCase), "Only transitive references may reference inner builds that got generated by outer builds");
310-
311-
outerBuildReferencingProject.RemoveReference(innerBuild, graphBuilder.Edges);
306+
ProjectItemInstance existingInnerBuildEdge = graphBuilder.Edges[(outerBuildReferencingProject, innerBuild)];
307+
308+
if (existingInnerBuildEdge.ItemType.Equals(TransitiveReferenceItemName, StringComparison.OrdinalIgnoreCase))
309+
{
310+
outerBuildReferencingProject.RemoveReference(innerBuild, graphBuilder.Edges);
311+
}
312+
else
313+
{
314+
// An explicit reference to an inner build takes precedence over the edge generated
315+
// from the outer build. GraphEdges.AddOrUpdateEdge will preserve it and merge target metadata.
316+
Assumed.Equal(
317+
existingInnerBuildEdge.ItemType,
318+
ItemTypeNames.ProjectReference,
319+
StringComparison.OrdinalIgnoreCase,
320+
"Only direct or transitive references may reference inner builds that got generated by outer builds");
321+
}
312322
}
313323

314324
outerBuildReferencingProject.AddProjectReference(innerBuild, newInnerBuildEdge, graphBuilder.Edges);

0 commit comments

Comments
 (0)