diff --git a/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs b/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs index 845b0c556c8..b941649ad74 100644 --- a/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs +++ b/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs @@ -27,7 +27,7 @@ namespace Microsoft.Build.Graph.UnitTests /// /// Performs SetPlatform negotiation for all project references when opted /// in via the EnableDynamicPlatformResolution property. - /// + /// /// The static graph mirrors the negotiation during build to determine plartform for each node. /// These tests mirror GetCompatiblePlatform_Tests.cs in order to make sure they both are in sync. /// @@ -351,5 +351,102 @@ public void PlatformIsChosenAsDefault() GetFirstNodeWithProjectNumber(graph, 2).ProjectInstance.GetPropertyValue("Platform").ShouldBe(GetFirstNodeWithProjectNumber(graph, 1).ProjectInstance.GetPropertyValue("Platform")); } } + + // Validate configurations are defined in project reference protocol + [Fact] + public void SolutionWithoutAllConfigurations() + { + using (TestEnvironment testEnvironment = TestEnvironment.Create()) + { + var firstProjectName = "1"; + var secondProjectName = "2"; + var thirdProjectName = "3"; + TransientTestFolder folder = testEnvironment.CreateFolder(createFolder: true); + TransientTestFolder project1Folder = testEnvironment.CreateFolder(Path.Combine(folder.Path, firstProjectName), createFolder: true); + TransientTestFolder project1SubFolder = testEnvironment.CreateFolder(Path.Combine(project1Folder.Path, firstProjectName), createFolder: true); + TransientTestFile project1 = testEnvironment.CreateFile(project1SubFolder, $"{firstProjectName}.csproj", + @" + + true + x64 + + + + + + + "); + + TransientTestFolder project2Folder = testEnvironment.CreateFolder(Path.Combine(folder.Path, secondProjectName), createFolder: true); + TransientTestFolder project2SubFolder = testEnvironment.CreateFolder(Path.Combine(project2Folder.Path, secondProjectName), createFolder: true); + TransientTestFile project2 = testEnvironment.CreateFile(project2SubFolder, $"{secondProjectName}.proj", + @" + + true + AnyCPU;x64 + + + "); + + TransientTestFolder project3Folder = testEnvironment.CreateFolder(Path.Combine(folder.Path, thirdProjectName), createFolder: true); + TransientTestFolder project3SubFolder = testEnvironment.CreateFolder(Path.Combine(project3Folder.Path, thirdProjectName), createFolder: true); + TransientTestFile project3 = testEnvironment.CreateFile(project3SubFolder, $"{thirdProjectName}.proj", + @" + + true + AnyCPU;x64 + + + "); + + + // Slashes here (and in the .slnf) are hardcoded as backslashes intentionally to support the common case. + TransientTestFile solutionFile = testEnvironment.CreateFile(folder, "SimpleProject.sln", + @" + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio Version 16 + VisualStudioVersion = 16.0.29326.124 + MinimumVisualStudioVersion = 10.0.40219.1 + Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""Project1"", ""1\1\1.csproj"", ""{79B5EBA6-5D27-4976-BC31-14422245A59A}"" + EndProject + Project(""{9A19103F-16F7-4668-BE54-9A1E7A4F7556}"") = ""2"", ""2\2\2.proj"", ""{8EFCCA22-9D51-4268-90F7-A595E11FCB2D}"" + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {79B5EBA6-5D27-4976-BC31-14422245A59A}.Debug|x64.ActiveCfg = Debug|x64 + {79B5EBA6-5D27-4976-BC31-14422245A59A}.Debug|x64.Build.0 = Debug|x64 + {79B5EBA6-5D27-4976-BC31-14422245A59A}.Release|x64.ActiveCfg = Release|x64 + {79B5EBA6-5D27-4976-BC31-14422245A59A}.Release|x64.Build.0 = Release|x64 + + {8EFCCA22-9D51-4268-90F7-A595E11FCB2D}.Debug|x64.ActiveCfg = Debug|Any CPU + {8EFCCA22-9D51-4268-90F7-A595E11FCB2D}.Debug|x64.Build.0 = Debug|Any CPU + {8EFCCA22-9D51-4268-90F7-A595E11FCB2D}.Release|x64.ActiveCfg = Release|Any CPU + {8EFCCA22-9D51-4268-90F7-A595E11FCB2D}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DE7234EC-0C4D-4070-B66A-DCF1B4F0CFEF} + EndGlobalSection + EndGlobal + "); + + ProjectCollection projectCollection = testEnvironment.CreateProjectCollection().Collection; + MockLogger logger = new(); + projectCollection.RegisterLogger(logger); + ProjectGraphEntryPoint entryPoint = new(solutionFile.Path, new Dictionary()); + + // We want to make sure negotiation respects configuration if defined but negotiates if not. + ProjectGraph graphFromSolution = new(entryPoint, projectCollection); + logger.AssertNoErrors(); + GetFirstNodeWithProjectNumber(graphFromSolution, 2).ProjectInstance.GetPropertyValue("Platform").ShouldBe("AnyCPU", "Project2 should have followed the sln config to AnyCPU"); + GetFirstNodeWithProjectNumber(graphFromSolution, 3).ProjectInstance.GetPropertyValue("Platform").ShouldBe("x64", "Project3 isn't in the solution so it should have negotiated to x64 to match Project1"); + } + } } } diff --git a/src/Build/Graph/ProjectInterpretation.cs b/src/Build/Graph/ProjectInterpretation.cs index dd47dbadc85..d617f78e35a 100644 --- a/src/Build/Graph/ProjectInterpretation.cs +++ b/src/Build/Graph/ProjectInterpretation.cs @@ -63,7 +63,7 @@ private readonly struct TargetSpecification { public TargetSpecification(string target, bool skipIfNonexistent) { - // Verify that if this target is skippable then it equals neither + // Verify that if this target is skippable then it equals neither // ".default" nor ".projectReferenceTargetsOrDefaultTargets". ErrorUtilities.VerifyThrow( !skipIfNonexistent || (!target.Equals(MSBuildConstants.DefaultTargetsMarker) @@ -131,6 +131,8 @@ public IEnumerable GetReferences(ProjectInstance requesterInstanc allowCollectionReuse: solutionConfiguration == null && !enableDynamicPlatformResolution, globalPropertiesModifiers); + bool configurationDefined = false; + // Match what AssignProjectConfiguration does to resolve project references. if (solutionConfiguration != null) { @@ -151,6 +153,8 @@ public IEnumerable GetReferences(ProjectInstance requesterInstanc { referenceGlobalProperties.Remove(PlatformMetadataName); } + + configurationDefined = true; } else { @@ -161,11 +165,16 @@ public IEnumerable GetReferences(ProjectInstance requesterInstanc referenceGlobalProperties.Remove(ConfigurationMetadataName); referenceGlobalProperties.Remove(PlatformMetadataName); } + else + { + configurationDefined = true; + } } } - // Note: Dynamic platform resolution is not enabled for sln-based builds. - else if (!projectReferenceItem.HasMetadata(SetPlatformMetadataName) && enableDynamicPlatformResolution) + // Note: Dynamic platform resolution is not enabled for sln-based builds, + // unless the project isn't known to the solution. + if (enableDynamicPlatformResolution && !configurationDefined && !projectReferenceItem.HasMetadata(SetPlatformMetadataName)) { string requesterPlatform = requesterInstance.GetPropertyValue("Platform"); string requesterPlatformLookupTable = requesterInstance.GetPropertyValue("PlatformLookupTable"); diff --git a/src/Tasks/Microsoft.Common.CurrentVersion.targets b/src/Tasks/Microsoft.Common.CurrentVersion.targets index 18ac9baa9a4..3289d86b32c 100644 --- a/src/Tasks/Microsoft.Common.CurrentVersion.targets +++ b/src/Tasks/Microsoft.Common.CurrentVersion.targets @@ -1648,7 +1648,6 @@ Copyright (C) Microsoft Corporation. All rights reserved. Configuration information. See AssignProjectConfiguration -->