From 8ead272365bc521ec488d7055fcbcd6faddea186 Mon Sep 17 00:00:00 2001 From: Michael Shea <78166458+MIchaelRShea@users.noreply.github.com> Date: Tue, 28 Mar 2023 21:12:15 -0400 Subject: [PATCH] Adding ability to override setplatform negotiation (#8594) With setplatform negotiation there is no way to force a project to as a certain platform. there is but that will override the feature all together and there is no way to set the platform to be blank. Because of this there is effectively no way to force a dependent project to build as its default value since platform=default will cause overbuilding and does nothing. here is an example A( building as x64) references project B (Available platforms x86;x64 with default platform x86) Platform negotiation will negotate to x64 since its available but if we actually wanted to reference b x86 its not possible because we should leave platform blank if we want to build b as x86 but and platform= are not valid ways to do this therefore the only way to build b as x86 is platform=86 but this will lead to overbuilding if b is built once with global props {platform = x86} and once with global props {} --- .../Graph/GetCompatiblePlatformGraph_Tests.cs | 42 +++++++++++++++++++ src/Build/Graph/ProjectInterpretation.cs | 5 ++- src/Shared/PlatformNegotiation.cs | 9 +++- .../GetCompatiblePlatform_Tests.cs | 23 ++++++++++ src/Tasks/GetCompatiblePlatform.cs | 3 +- 5 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs b/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs index 679e8a48178..845b0c556c8 100644 --- a/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs +++ b/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs @@ -102,6 +102,48 @@ public void ValidateSetPlatformOverride() } } + [Fact] + public void ValidateNegotiationOverride() + { + using (var env = TestEnvironment.Create()) + { + + TransientTestFile entryProject = CreateProjectFile(env, 1, extraContent: @" + true + x64 + win32=x64 + + + + x86 + + "); + var proj2 = env.CreateFile("2.proj", @" + + + true + x64;AnyCPU + x86 + + + + + + "); + var proj3 = env.CreateFile("3.proj", @" + + + AnyCPU;x86 + + "); + + + ProjectGraph graph = new ProjectGraph(entryProject.Path); + GetFirstNodeWithProjectNumber(graph, 2).ProjectInstance.GlobalProperties.ContainsKey("Platform").ShouldBeFalse(); + GetFirstNodeWithProjectNumber(graph, 3).ProjectInstance.GlobalProperties["Platform"].ShouldBe("x86"); + } + } + [Fact] public void ResolvesMultipleReferencesToSameProject() { diff --git a/src/Build/Graph/ProjectInterpretation.cs b/src/Build/Graph/ProjectInterpretation.cs index d4c48ac0ac7..23b0fc15ad9 100644 --- a/src/Build/Graph/ProjectInterpretation.cs +++ b/src/Build/Graph/ProjectInterpretation.cs @@ -33,6 +33,7 @@ internal sealed class ProjectInterpretation private const string PlatformMetadataName = "Platform"; private const string PlatformsMetadataName = "Platforms"; private const string EnableDynamicPlatformResolutionMetadataName = "EnableDynamicPlatformResolution"; + private const string OverridePlatformNegotiationValue = "OverridePlatformNegotiationValue"; private static readonly char[] PropertySeparator = MSBuildConstants.SemicolonChar; @@ -134,7 +135,9 @@ public IEnumerable GetReferences(ProjectInstance requesterInstanc null, // Platform negotiation requires an evaluation with no global properties first _projectCollection); - var selectedPlatform = PlatformNegotiation.GetNearestPlatform(projectInstance.GetPropertyValue(PlatformMetadataName), projectInstance.GetPropertyValue(PlatformsMetadataName), projectInstance.GetPropertyValue(PlatformLookupTableMetadataName), requesterInstance.GetPropertyValue(PlatformLookupTableMetadataName), projectInstance.FullPath, requesterInstance.GetPropertyValue(PlatformMetadataName)); + string overridePlatformNegotiationMetadataValue = projectReferenceItem.GetMetadataValue(OverridePlatformNegotiationValue); + + var selectedPlatform = PlatformNegotiation.GetNearestPlatform(overridePlatformNegotiationMetadataValue, projectInstance.GetPropertyValue(PlatformMetadataName), projectInstance.GetPropertyValue(PlatformsMetadataName), projectInstance.GetPropertyValue(PlatformLookupTableMetadataName), requesterInstance.GetPropertyValue(PlatformLookupTableMetadataName), projectInstance.FullPath, requesterInstance.GetPropertyValue(PlatformMetadataName)); if (selectedPlatform.Equals(String.Empty)) { diff --git a/src/Shared/PlatformNegotiation.cs b/src/Shared/PlatformNegotiation.cs index 4a3faba5c34..226a7780017 100644 --- a/src/Shared/PlatformNegotiation.cs +++ b/src/Shared/PlatformNegotiation.cs @@ -19,7 +19,7 @@ namespace Microsoft.Build.Shared /// internal static class PlatformNegotiation { - internal static string GetNearestPlatform(string referencedProjectPlatform, string projectReferencePlatformsMetadata, string projectReferenceLookupTableMetadata, string platformLookupTable, string projectPath, string currentProjectPlatform, TaskLoggingHelper? log = null) + internal static string GetNearestPlatform(string overridePlatformValue, string referencedProjectPlatform, string projectReferencePlatformsMetadata, string projectReferenceLookupTableMetadata, string platformLookupTable, string projectPath, string currentProjectPlatform, TaskLoggingHelper? log = null) { Dictionary? currentProjectLookupTable = ExtractLookupTable(platformLookupTable, log); @@ -41,9 +41,14 @@ internal static string GetNearestPlatform(string referencedProjectPlatform, stri string buildProjectReferenceAs = string.Empty; + // If an override value is set define that as the platform value as the top priority + if (!string.IsNullOrEmpty(overridePlatformValue)) + { + buildProjectReferenceAs = overridePlatformValue; + } // If the referenced project has a defined `Platform` that's compatible, it will build that way by default. // Don't set `buildProjectReferenceAs` and the `_GetProjectReferencePlatformProperties` target will handle the rest. - if (!string.IsNullOrEmpty(referencedProjectPlatform) && referencedProjectPlatform.Equals(currentProjectPlatform, StringComparison.OrdinalIgnoreCase)) + else if (!string.IsNullOrEmpty(referencedProjectPlatform) && referencedProjectPlatform.Equals(currentProjectPlatform, StringComparison.OrdinalIgnoreCase)) { log?.LogMessageFromResources(MessageImportance.Low, "GetCompatiblePlatform.ReferencedProjectHasDefinitivePlatform", projectPath, referencedProjectPlatform); } diff --git a/src/Tasks.UnitTests/GetCompatiblePlatform_Tests.cs b/src/Tasks.UnitTests/GetCompatiblePlatform_Tests.cs index 6c3861314bd..d9359ef1a6c 100644 --- a/src/Tasks.UnitTests/GetCompatiblePlatform_Tests.cs +++ b/src/Tasks.UnitTests/GetCompatiblePlatform_Tests.cs @@ -40,6 +40,29 @@ public void ResolvesViaPlatformLookupTable() task.AssignedProjectsWithPlatform[0].GetMetadata("NearestPlatform").ShouldBe("x64"); } + + [Fact] + public void ResolvesViaOverride() + { + // OverridePlatformNegotiationValue always takes priority over everything. It is typically user-defined. + TaskItem projectReference = new TaskItem("foo.bar"); + projectReference.SetMetadata("Platforms", "x64;x86;AnyCPU"); + projectReference.SetMetadata("platform", "x86"); + projectReference.SetMetadata("OverridePlatformNegotiationValue", "x86"); + + GetCompatiblePlatform task = new GetCompatiblePlatform() + { + BuildEngine = new MockEngine(_output), + CurrentProjectPlatform = "x64", + PlatformLookupTable = "win32=x64", + AnnotatedProjects = new TaskItem[] { projectReference } + }; + + task.Execute().ShouldBeTrue(); + + task.AssignedProjectsWithPlatform[0].GetMetadata("NearestPlatform").ShouldBe(""); + } + [Fact] public void ResolvesViaProjectReferencesPlatformLookupTable() { diff --git a/src/Tasks/GetCompatiblePlatform.cs b/src/Tasks/GetCompatiblePlatform.cs index 2b81a9dca99..daec24f865d 100644 --- a/src/Tasks/GetCompatiblePlatform.cs +++ b/src/Tasks/GetCompatiblePlatform.cs @@ -59,8 +59,9 @@ public override bool Execute() string referencedProjectPlatform = AssignedProjectsWithPlatform[i].GetMetadata("Platform"); string projectReferencePlatformsMetadata = AssignedProjectsWithPlatform[i].GetMetadata("Platforms"); string projectReferenceLookupTableMetadata = AssignedProjectsWithPlatform[i].GetMetadata("PlatformLookupTable"); + string projectReferenceOverridePlatformNegotiationMetadata = AssignedProjectsWithPlatform[i].GetMetadata("OverridePlatformNegotiationValue"); - string? buildProjectReferenceAs = PlatformNegotiation.GetNearestPlatform(referencedProjectPlatform, projectReferencePlatformsMetadata, projectReferenceLookupTableMetadata, PlatformLookupTable, AssignedProjectsWithPlatform[i].ItemSpec, CurrentProjectPlatform, Log); + string? buildProjectReferenceAs = PlatformNegotiation.GetNearestPlatform(projectReferenceOverridePlatformNegotiationMetadata, referencedProjectPlatform, projectReferencePlatformsMetadata, projectReferenceLookupTableMetadata, PlatformLookupTable, AssignedProjectsWithPlatform[i].ItemSpec, CurrentProjectPlatform, Log); AssignedProjectsWithPlatform[i].SetMetadata("NearestPlatform", buildProjectReferenceAs); Log.LogMessageFromResources(MessageImportance.Low, "GetCompatiblePlatform.DisplayChosenPlatform", AssignedProjectsWithPlatform[i].ItemSpec, buildProjectReferenceAs);