Skip to content

Commit

Permalink
Emulate AssignProjectConfiguration behavior in graph construction (#8625
Browse files Browse the repository at this point in the history
)

Summary
The for sln-based builds, the AssignProjectConfiguration task ends up using the Configuration and Platform defined in the sln rather than passing through the global properties from the referencing project or attempting to do dynamic platform negotiation. This change adds equivalent functionality to graph construction.

A concrete scenario this fixes for graph-based builds using an sln file is that most csproj define the "x86" platform while most vcxproj define "Win32". Previously for a graph build, if the csproj referenced the vcxproj, the platform passed to vcxproj would be x86, not Win32. Even worse, the vcxproj would be an entry point anyway, so it would double-build with both x86 AND Win32, which leads to race conditions.

Customer Impact
Microsoft-internal customer using sln-based builds will be able to opt-into graph builds

Regression?
No

Testing
Manual validation in the customer repo, as well as added unit tests

Risk
Low. Graph builds are a less-used feature, and this adds parity to what non-graph builds and VS-based builds do. It's unlikely that any behavioral change would be impactful due to those other scenarios presumably working for customers who may be using graph builds.
  • Loading branch information
dfederm committed Apr 25, 2023
1 parent f6feb85 commit 07e2947
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 64 deletions.
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the MIT license. See License.txt in the project root for full license information. -->
<Project>
<PropertyGroup>
<VersionPrefix>17.6.2</VersionPrefix><DotNetFinalVersionKind>release</DotNetFinalVersionKind>
<VersionPrefix>17.6.3</VersionPrefix><DotNetFinalVersionKind>release</DotNetFinalVersionKind>
<PackageValidationBaselineVersion>17.5.0</PackageValidationBaselineVersion>
<AssemblyVersion>15.1.0.0</AssemblyVersion>
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>
Expand Down
15 changes: 15 additions & 0 deletions src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Collections;
using Microsoft.Build.Construction;
using Microsoft.Build.Engine.UnitTests;
using Microsoft.Build.Exceptions;
Expand Down Expand Up @@ -523,6 +524,20 @@ public void SolutionsCanInjectEdgesIntoTheProjectGraph(Dictionary<int, int[]> ed

var graphFromSolutionEdges = graphFromSolution.TestOnly_Edges.TestOnly_AsConfigurationMetadata();

// Solutions add the CurrentSolutionConfigurationContents global property for platform resolution
foreach ((ConfigurationMetadata, ConfigurationMetadata) graphFromSolutionEdge in graphFromSolutionEdges.Keys)
{
graphFromSolutionEdge.Item1.GlobalProperties.ShouldContainKey("CurrentSolutionConfigurationContents");
graphFromSolutionEdge.Item2.GlobalProperties.ShouldContainKey("CurrentSolutionConfigurationContents");
}

// Remove CurrentSolutionConfigurationContents for comparison purposes. This is done as a separate pass since some edges may be sharing an instance.
foreach ((ConfigurationMetadata, ConfigurationMetadata) graphFromSolutionEdge in graphFromSolutionEdges.Keys)
{
graphFromSolutionEdge.Item1.GlobalProperties.Remove("CurrentSolutionConfigurationContents");
graphFromSolutionEdge.Item2.GlobalProperties.Remove("CurrentSolutionConfigurationContents");
}

// Original edges get preserved.
foreach (var graphEdge in graphEdges)
{
Expand Down
125 changes: 125 additions & 0 deletions src/Build.UnitTests/Graph/ProjectGraph_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
Expand Down Expand Up @@ -674,6 +675,130 @@ public void ConstructGraphWithDifferentEntryPointsAndGraphRoots()
}
}

[Fact]
public void ConstructGraphWithSolution()
{
// This test exercises two key features of solution-based builds from AssignProjectConfiguration:
// 1. Adding synthetic project references
// 2. Resolving project configuration based on the sln
using (var env = TestEnvironment.Create())
{
const string SolutionFileContents = """
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 17.0.31903.59
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Project1", "Project1.csproj", "{8761499A-7280-43C4-A32F-7F41C47CA6DF}"
ProjectSection(ProjectDependencies) = postProject
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98} = {52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Project2", "Project2.vcxproj", "{D638A8EF-3A48-45F2-913C-88B29FED03CB}"
EndProject
Project("{13B669BE-BB05-4DDF-9536-439F39A36129}") = "Project3", "Project3.vcxproj", "{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Win32 = Release|Win32
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Debug|Win32.ActiveCfg = Debug|x86
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Debug|Win32.Build.0 = Debug|x86
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Debug|x64.ActiveCfg = Debug|x64
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Debug|x64.Build.0 = Debug|x64
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Debug|x86.ActiveCfg = Debug|x86
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Debug|x86.Build.0 = Debug|x86
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Release|Win32.ActiveCfg = Release|x86
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Release|Win32.Build.0 = Release|x86
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Release|x64.ActiveCfg = Release|x64
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Release|x64.Build.0 = Release|x64
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Release|x86.ActiveCfg = Release|x86
{8761499A-7280-43C4-A32F-7F41C47CA6DF}.Release|x86.Build.0 = Release|x86
{D638A8EF-3A48-45F2-913C-88B29FED03CB}.Debug|Win32.ActiveCfg = Debug|Win32
{D638A8EF-3A48-45F2-913C-88B29FED03CB}.Debug|Win32.Build.0 = Debug|Win32
{D638A8EF-3A48-45F2-913C-88B29FED03CB}.Debug|x64.ActiveCfg = Debug|x64
{D638A8EF-3A48-45F2-913C-88B29FED03CB}.Debug|x64.Build.0 = Debug|x64
{D638A8EF-3A48-45F2-913C-88B29FED03CB}.Release|Win32.ActiveCfg = Release|Win32
{D638A8EF-3A48-45F2-913C-88B29FED03CB}.Release|Win32.Build.0 = Release|Win32
{D638A8EF-3A48-45F2-913C-88B29FED03CB}.Release|x64.ActiveCfg = Release|x64
{D638A8EF-3A48-45F2-913C-88B29FED03CB}.Release|x64.Build.0 = Release|x64
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|Win32.ActiveCfg = Debug|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|Win32.Build.0 = Debug|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|x64.ActiveCfg = Debug|x64
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|x64.Build.0 = Debug|x64
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|x86.ActiveCfg = Debug|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|x86.Build.0 = Debug|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|Win32.ActiveCfg = Release|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|Win32.Build.0 = Release|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|x64.ActiveCfg = Release|x64
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|x64.Build.0 = Release|x64
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|x86.ActiveCfg = Release|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|x86.Build.0 = Release|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|Win32.ActiveCfg = Debug|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|Win32.Build.0 = Debug|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|x64.ActiveCfg = Debug|x64
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|x64.Build.0 = Debug|x64
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|x86.ActiveCfg = Debug|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Debug|x86.Build.0 = Debug|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|Win32.ActiveCfg = Release|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|Win32.Build.0 = Release|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|x64.ActiveCfg = Release|x64
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|x64.Build.0 = Release|x64
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|x86.ActiveCfg = Release|Win32
{52B2ED64-1CFC-401B-8C5B-6D1E1DEADF98}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
""";
TransientTestFile slnFile = env.CreateFile(@"Solution.sln", SolutionFileContents);
SolutionFile solutionFile = SolutionFile.Parse(slnFile.Path);

ProjectRootElement project1Xml = ProjectRootElement.Create();

// Project 1 depends on Project 2 using ProjectReference but there is a sln-based dependency defined on Project 3 as well.
project1Xml.AddItem("ProjectReference", "Project2.vcxproj");

ProjectRootElement project2Xml = ProjectRootElement.Create();
ProjectRootElement project3Xml = ProjectRootElement.Create();

string project1Path = Path.Combine(env.DefaultTestDirectory.Path, "Project1.csproj");
string project2Path = Path.Combine(env.DefaultTestDirectory.Path, "Project2.vcxproj");
string project3Path = Path.Combine(env.DefaultTestDirectory.Path, "Project3.vcxproj");

project1Xml.Save(project1Path);
project2Xml.Save(project2Path);
project3Xml.Save(project3Path);

var projectGraph = new ProjectGraph(slnFile.Path);
projectGraph.EntryPointNodes.Count.ShouldBe(3);
projectGraph.GraphRoots.Count.ShouldBe(1);
projectGraph.GraphRoots.First().ProjectInstance.FullPath.ShouldBe(project1Path);
projectGraph.ProjectNodes.Count.ShouldBe(3);

ProjectGraphNode project1Node = projectGraph.ProjectNodes.Single(node => node.ProjectInstance.FullPath == project1Path);
project1Node.ProjectInstance.GlobalProperties["Configuration"].ShouldBe("Debug");
project1Node.ProjectInstance.GlobalProperties["Platform"].ShouldBe("x86");
project1Node.ProjectReferences.Count.ShouldBe(2);

ProjectGraphNode project2Node = projectGraph.ProjectNodes.Single(node => node.ProjectInstance.FullPath == project2Path);
project2Node.ProjectInstance.GlobalProperties["Configuration"].ShouldBe("Debug");
project2Node.ProjectInstance.GlobalProperties["Platform"].ShouldBe("Win32");
project2Node.ProjectReferences.Count.ShouldBe(0);

ProjectGraphNode project3Node = projectGraph.ProjectNodes.Single(node => node.ProjectInstance.FullPath == project3Path);
project3Node.ProjectInstance.GlobalProperties["Configuration"].ShouldBe("Debug");
project3Node.ProjectInstance.GlobalProperties["Platform"].ShouldBe("Win32");
project3Node.ProjectReferences.Count.ShouldBe(0);
}
}

[Fact]
public void GetTargetListsAggregatesFromMultipleEdges()
{
Expand Down
32 changes: 19 additions & 13 deletions src/Build/Construction/Solution/SolutionProjectGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,23 @@ internal static void AddPropertyGroupForSolutionConfiguration(ProjectRootElement
msbuildProject.AppendChild(solutionConfigurationProperties);
solutionConfigurationProperties.Condition = GetConditionStringForConfiguration(solutionConfiguration);

string escapedSolutionConfigurationContents = GetSolutionConfiguration(solutionFile, solutionConfiguration);

solutionConfigurationProperties.AddProperty("CurrentSolutionConfigurationContents", escapedSolutionConfigurationContents);

msbuildProject.AddItem(
"SolutionConfiguration",
solutionConfiguration.FullName,
new Dictionary<string, string>
{
{ "Configuration", solutionConfiguration.ConfigurationName },
{ "Platform", solutionConfiguration.PlatformName },
{ "Content", escapedSolutionConfigurationContents },
});
}

internal static string GetSolutionConfiguration(SolutionFile solutionFile, SolutionConfigurationInSolution solutionConfiguration)
{
var solutionConfigurationContents = new StringBuilder(1024);
var settings = new XmlWriterSettings
{
Expand Down Expand Up @@ -292,19 +309,8 @@ internal static void AddPropertyGroupForSolutionConfiguration(ProjectRootElement
xw.WriteEndElement(); // </SolutionConfiguration>
}

var escapedSolutionConfigurationContents = EscapingUtilities.Escape(solutionConfigurationContents.ToString());

solutionConfigurationProperties.AddProperty("CurrentSolutionConfigurationContents", escapedSolutionConfigurationContents);

msbuildProject.AddItem(
"SolutionConfiguration",
solutionConfiguration.FullName,
new Dictionary<string, string>
{
{ "Configuration", solutionConfiguration.ConfigurationName },
{ "Platform", solutionConfiguration.PlatformName },
{ "Content", escapedSolutionConfigurationContents },
});
string escapedSolutionConfigurationContents = EscapingUtilities.Escape(solutionConfigurationContents.ToString());
return escapedSolutionConfigurationContents;
}

/// <summary>
Expand Down

0 comments on commit 07e2947

Please sign in to comment.