From 29211095d615f58ff4edbcfdfb0e87be75cb7bd2 Mon Sep 17 00:00:00 2001 From: PureWeen <223556219+Copilot@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:59:01 -0500 Subject: [PATCH] Fix SingleProject backend and compile selection Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: ac6265fe-09a0-473c-b3f9-9ec6d6dd97cd --- ...Maui.Controls.SingleProject.Before.targets | 29 ++-- ...rosoft.Maui.Controls.SingleProject.targets | 26 +-- .../Xaml.UnitTests/MSBuild/MSBuildTests.cs | 148 ++++++++++++++++++ 3 files changed, 175 insertions(+), 28 deletions(-) diff --git a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.Before.targets b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.Before.targets index 56dee6a5b1e0..d23cd1be64c5 100644 --- a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.Before.targets +++ b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.Before.targets @@ -38,10 +38,9 @@ BackendIdentity records the stable, MAUI-owned key for each built-in backend so the built-in platforms describe themselves through exactly the same registration - metadata that an external backend NuGet contributes. For recognized platform TFMs - activation still flows through TargetPlatformIdentifier(s); BackendIdentity here is - purely descriptive (built-ins do not declare an ActivationValue, so the neutral-TFM - activation branch never fires for them). + metadata that an external backend NuGet contributes. Recognized platform TFMs + activate through TargetPlatformIdentifier(s), while a neutral TFM can activate the + same registration through BackendIdentity and MauiActiveBackend. --> @@ -57,12 +56,11 @@ - TargetPlatformIdentifiers is back-filled from the legacy singular TargetPlatformIdentifier when only the singular is set. - ActivationValue is back-filled from BackendIdentity so a backend that - only declares a stable identity key (BackendIdentity="gtk") can be - activated for a neutral TFM via gtk - without having to repeat the value. Built-in platforms declare a - BackendIdentity but no ActivationValue is derived for them here because - they are TPI-recognized (see the guard below), so they never take the - neutral-activation branch. + declares a stable identity key (BackendIdentity="gtk") can be activated + for a neutral TFM via gtk without + having to repeat the value. This applies even when the same registration + also declares TargetPlatformIdentifier(s), allowing one item to support + both recognized-TPI and neutral-TFM activation. - ActivationProperty defaults to the well-known MauiActiveBackend selector whenever an ActivationValue is present but no explicit property was named. - _MauiResolvedActivationValue captures the *current value* of the property @@ -91,17 +89,10 @@ %(MauiPlatformSpecificFolder.TargetPlatformIdentifier) - + + Condition=" '%(MauiPlatformSpecificFolder.ActivationValue)' == '' and '%(MauiPlatformSpecificFolder.BackendIdentity)' != '' "> %(MauiPlatformSpecificFolder.BackendIdentity) diff --git a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.targets b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.targets index 4cb5e4d95105..d591971f450b 100644 --- a/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.targets +++ b/src/Controls/src/Build.Tasks/nuget/buildTransitive/netstandard2.0/Microsoft.Maui.Controls.SingleProject.targets @@ -154,19 +154,27 @@ part of the current build configuration AND has not been explicitly kept by _MauiCollectPlatformSpecificCompileItems above. - The Condition references %(Compile.ExcludeFromCurrentConfiguration) - from inside an Include of a *different* item type — this is MSBuild - cross-item-type batching: the filesystem glob is evaluated once per - unique value of Compile.ExcludeFromCurrentConfiguration. The blanket - initially marks every $(PlatformsProjectFolder)/** - file true, then active-platform updates flip matching items back to - false. Keep the Condition so only the true batch is removed; otherwise - active-platform files may be removed or inactive files may leak in. + Copy the Compile items marked true to a separate item type, then intersect + those identities with the platform filesystem candidates. This ensures the + removal set contains only Compile items explicitly excluded from the current + configuration. Using temporary item types preserves downstream false metadata + buckets without removing and re-adding active Compile items, which would + perturb ordering and other metadata. --> + <_MauiCompileItemsMarkedForRemoval + Include="@(Compile->WithMetadataValue('ExcludeFromCurrentConfiguration', 'true'))" /> <_MauiPlatformCompileToRemove - Condition=" '%(Compile.ExcludeFromCurrentConfiguration)' == 'true' " Include="$(PlatformsProjectFolder)**/*$(DefaultLanguageSourceExtension)" Exclude="@(_MauiPlatformSpecificCompileItems)" /> + <_MauiPlatformCompileNotMarkedForRemoval Include="@(_MauiPlatformCompileToRemove)" /> + <_MauiPlatformCompileNotMarkedForRemoval + Remove="@(_MauiCompileItemsMarkedForRemoval)" + MatchOnMetadata="FullPath" + MatchOnMetadataOptions="PathLike" /> + <_MauiPlatformCompileToRemove + Remove="@(_MauiPlatformCompileNotMarkedForRemoval)" + MatchOnMetadata="FullPath" + MatchOnMetadataOptions="PathLike" /> diff --git a/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs b/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs index 70a9faea78b9..e8b147b60cda 100644 --- a/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs +++ b/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs @@ -1410,6 +1410,91 @@ public static class After StringComparison.OrdinalIgnoreCase); } + [Fact] + public void SingleProject_RemovePlatformCompileItems_RemovesOnlyCompileItemsMarkedExcluded() + { + SetUp(); + var project = NewElement("Project").WithAttribute("Sdk", "Microsoft.NET.Sdk"); + var propertyGroup = NewElement("PropertyGroup"); + propertyGroup.Add(NewElement("TargetFramework").WithValue(GetTfm())); + propertyGroup.Add(NewElement("SingleProject").WithValue("true")); + propertyGroup.Add(NewElement("EnableDefaultCompileItems").WithValue("false")); + project.Add(propertyGroup); + AddMauiReferences(project); + AddSingleProjectBeforeTargetsImport(project); + + var compileItems = NewElement("ItemGroup"); + compileItems.Add(NewElement("Compile").WithAttribute("Include", "Before.cs")); + compileItems.Add(NewElement("Compile").WithAttribute("Include", "Platforms\\Removed\\RemovedMarker.cs")); + compileItems.Add(NewElement("Compile").WithAttribute("Include", "Platforms\\Preserved\\PreservedMarker.cs")); + compileItems.Add(NewElement("Compile").WithAttribute("Include", "After.cs")); + project.Add(compileItems); + + WriteFile("Before.cs", @" +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public static class Before +{ + public static string Value => ""Before""; +}"); + + WriteFile("Platforms\\Removed\\RemovedMarker.cs", @" +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public static class RemovedMarker +{ + public static string Value => ""Removed""; +}"); + + WriteFile("Platforms\\Preserved\\PreservedMarker.cs", @" +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public static class PreservedMarker +{ + public static string Value => ""Preserved""; +}"); + + WriteFile("After.cs", @" +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public static class After +{ + public static string Value => ""After""; +}"); + + AddSingleProjectTargetsImport(project); + + var downstreamCompileMetadata = NewElement("ItemGroup"); + var markActiveCompile = NewElement("Compile").WithAttribute("Update", "Platforms\\Preserved\\PreservedMarker.cs"); + markActiveCompile.Add(NewElement("ExcludeFromCurrentConfiguration").WithValue("false")); + downstreamCompileMetadata.Add(markActiveCompile); + project.Add(downstreamCompileMetadata); + + var dumpTarget = NewElement("Target") + .WithAttribute("Name", "_TestDumpCompileItems") + .WithAttribute("AfterTargets", "_MauiUnflipKeptCompileItemMetadata"); + dumpTarget.Add(NewElement("Message") + .WithAttribute("Importance", "high") + .WithAttribute("Text", "COMPILE_ITEMS: @(Compile->'%(Filename)', '|')")); + project.Add(dumpTarget); + + var projectFile = IOPath.Combine(tempDirectory, "test.csproj"); + project.Save(projectFile); + + var log = Build(projectFile); + + var testDll = IOPath.Combine(intermediateDirectory, "test.dll"); + AssertExists(testDll, nonEmpty: true); + AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.Before"); + AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.RemovedMarker"); + AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.PreservedMarker"); + AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.After"); + Assert.Contains( + "COMPILE_ITEMS: Before|PreservedMarker|After", + log, + StringComparison.OrdinalIgnoreCase); + } + // Backward compatibility: a folder that declares only the legacy singular // TargetPlatformIdentifier metadata must continue to match exactly that TPI. [Theory] @@ -1465,6 +1550,69 @@ public static class LegacyIosMarker AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.LegacyIosMarker"); } + [Theory] + [InlineData("macos", "", true)] + [InlineData("", "macos", true)] + [InlineData("ios", "macos", false)] + [InlineData("", "gtk", false)] + public void SingleProject_BackendIdentitySupportsRecognizedAndNeutralActivation( + string targetPlatformIdentifier, + string activeBackend, + bool shouldIncludeMacOsFile) + { + SetUp(); + var project = NewElement("Project").WithAttribute("Sdk", "Microsoft.NET.Sdk"); + var propertyGroup = NewElement("PropertyGroup"); + propertyGroup.Add(NewElement("TargetFramework").WithValue(GetTfm())); + propertyGroup.Add(NewElement("SingleProject").WithValue("true")); + project.Add(propertyGroup); + AddMauiReferences(project); + AddSingleProjectBeforeTargetsImport(project); + + var customMappings = NewElement("ItemGroup"); + customMappings.Add(NewElement("MauiPlatformSpecificFolder") + .WithAttribute("Include", "Platforms\\MacOS\\") + .WithAttribute("TargetPlatformIdentifiers", "macos") + .WithAttribute("BackendIdentity", "macos")); + project.Add(customMappings); + + WriteFile("Entry.cs", @" +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public static class Entry +{ + public static string Value => ""ok""; +}"); + + WriteFile("Platforms\\MacOS\\MacOsMarker.cs", @" +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public static class MacOsMarker +{ + public static string Value => ""MacOS""; +}"); + + AddSingleProjectTargetsImport(project); + + var projectFile = IOPath.Combine(tempDirectory, "test.csproj"); + project.Save(projectFile); + + var args = ""; + if (!string.IsNullOrEmpty(targetPlatformIdentifier)) + args = $"-p:_SingleProjectTestTargetPlatformIdentifier={targetPlatformIdentifier}"; + if (!string.IsNullOrEmpty(activeBackend)) + args += $" -p:MauiActiveBackend={activeBackend}"; + Build(projectFile, additionalArgs: args); + + var testDll = IOPath.Combine(intermediateDirectory, "test.dll"); + AssertExists(testDll, nonEmpty: true); + + if (shouldIncludeMacOsFile) + AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.MacOsMarker"); + else + AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.MacOsMarker"); + } + // Neutral-TFM activation (the GTK scenario from #35021/#36650). On a plain // net11.0 inner build no TargetPlatformIdentifier is recognized; a backend // declares a stable BackendIdentity and is activated via the well-known