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 17b91db2f42b..56dee6a5b1e0 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
@@ -28,14 +28,124 @@
$([MSBuild]::EnsureTrailingSlash('$(TizenProjectFolder)'))
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ %(MauiPlatformSpecificFolder.TargetPlatformIdentifier)
+
+
+
+
+ %(MauiPlatformSpecificFolder.BackendIdentity)
+
+
+
+
+ MauiActiveBackend
+
+
+
+
+ <_MauiResolvedActivationValue>$(%(MauiPlatformSpecificFolder.ActivationProperty))
+
+
+
+
+ <_MauiActivationValueNormalized>$([System.Text.RegularExpressions.Regex]::Replace('%(MauiPlatformSpecificFolder.ActivationValue)', '\s+', '').ToLowerInvariant())
+ <_MauiResolvedActivationValueNormalized>$([System.Text.RegularExpressions.Regex]::Replace('%(MauiPlatformSpecificFolder._MauiResolvedActivationValue)', '\s+', '').ToLowerInvariant())
+
+
+
+
$(AndroidProjectFolder)AndroidManifest.xml
$(AndroidProjectFolder)Resources
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 6f4069e6d36d..4cb5e4d95105 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
@@ -35,6 +35,109 @@
+
+
+
+ <_MauiPlatformSpecificCompileItems
+ Include="$([MSBuild]::EnsureTrailingSlash('%(MauiPlatformSpecificFolder.Identity)'))**/*$(DefaultLanguageSourceExtension)"
+ Condition=" ('%(MauiPlatformSpecificFolder.TargetPlatformIdentifiers)' == '' and '%(MauiPlatformSpecificFolder.ActivationValue)' == '' and '%(MauiPlatformSpecificFolder.BackendIdentity)' == '' and '%(MauiPlatformSpecificFolder.ActivationProperty)' == '') or ('$(TargetPlatformIdentifier)' != '' and $([System.Text.RegularExpressions.Regex]::Replace(';%(MauiPlatformSpecificFolder.TargetPlatformIdentifiers);', '\s+', '').ToLowerInvariant().Contains(';$(TargetPlatformIdentifier.ToLowerInvariant());'))) or ('$(TargetPlatformIdentifier)' == '' and '%(MauiPlatformSpecificFolder._MauiActivationValueNormalized)' != '' and '%(MauiPlatformSpecificFolder._MauiResolvedActivationValueNormalized)' == '%(MauiPlatformSpecificFolder._MauiActivationValueNormalized)') " />
+
+
+
+
+
+
+
+ <_MauiCompileItemsNotToUnflip Include="@(Compile)" />
+ <_MauiCompileItemsNotToUnflip
+ Remove="@(_MauiPlatformSpecificCompileItems)"
+ MatchOnMetadata="FullPath"
+ MatchOnMetadataOptions="PathLike" />
+
+ <_MauiCompileItemsToUnflip
+ Include="@(Compile)"
+ Condition=" '%(Compile.ExcludeFromCurrentConfiguration)' == 'true' " />
+ <_MauiCompileItemsToUnflip
+ Remove="@(_MauiCompileItemsNotToUnflip)"
+ MatchOnMetadata="FullPath"
+ MatchOnMetadataOptions="PathLike" />
+
+
+ false
+
+
+
+
-
- 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.
+ -->
+ <_MauiPlatformCompileToRemove
Condition=" '%(Compile.ExcludeFromCurrentConfiguration)' == 'true' "
- Remove="$(PlatformsProjectFolder)**/*$(DefaultLanguageSourceExtension)" />
+ Include="$(PlatformsProjectFolder)**/*$(DefaultLanguageSourceExtension)"
+ Exclude="@(_MauiPlatformSpecificCompileItems)" />
+
<_MauiXamlToRemove
diff --git a/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs b/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs
index fa82c0d92690..2a6551e1203e 100644
--- a/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs
+++ b/src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs
@@ -283,6 +283,71 @@ void AssertDoesNotExist(string path)
Assert.False(File.Exists(path), $"{path} should *not* exist!");
}
+ void WriteFile(string name, string contents)
+ {
+ var filePath = IOPath.Combine(tempDirectory, name.Replace('\\', IOPath.DirectorySeparatorChar).Replace('/', IOPath.DirectorySeparatorChar));
+ Directory.CreateDirectory(IOPath.GetDirectoryName(filePath));
+ File.WriteAllText(filePath, contents);
+ }
+
+ void AssertTypeExists(string assemblyPath, string fullTypeName)
+ {
+ using var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
+ Assert.Contains(assembly.MainModule.Types.Select(t => t.FullName), t => t == fullTypeName);
+ }
+
+ void AssertTypeDoesNotExist(string assemblyPath, string fullTypeName)
+ {
+ using var assembly = AssemblyDefinition.ReadAssembly(assemblyPath);
+ Assert.DoesNotContain(assembly.MainModule.Types.Select(t => t.FullName), t => t == fullTypeName);
+ }
+
+ void AddSingleProjectBeforeTargetsImport(XElement project)
+ {
+ var beforeTargetsPath = AssemblyInfoTests.GetFilePathFromRoot(IOPath.Combine("src", "Controls", "src", "Build.Tasks", "nuget", "buildTransitive", "netstandard2.0", "Microsoft.Maui.Controls.SingleProject.Before.targets"));
+ project.Add(NewElement("Import").WithAttribute("Project", beforeTargetsPath));
+ }
+
+ void AddSingleProjectTargetsImport(XElement project)
+ {
+ var targetsPath = AssemblyInfoTests.GetFilePathFromRoot(IOPath.Combine("src", "Controls", "src", "Build.Tasks", "nuget", "buildTransitive", "netstandard2.0", "Microsoft.Maui.Controls.SingleProject.targets"));
+ project.Add(NewElement("Import").WithAttribute("Project", targetsPath));
+
+ // Assign the synthetic TargetPlatformIdentifier from a private test-only property
+ // inside a target rather than as a global 'dotnet build' property. Passing a platform
+ // TPI globally makes the SDK attempt workload resolution during evaluation for what is a
+ // plain net11.0 (non-platform) project, which fails on CI agents without that workload
+ // (NETSDK1208 / NETSDK1178) before the SingleProject targets under test ever run. Setting
+ // it here — after SDK evaluation but before the SingleProject compile-filtering targets —
+ // keeps the tests workload-neutral while still exercising the TPI-dependent logic (the
+ // allow-list built by _MauiCollectPlatformSpecificCompileItems determines which files are
+ // compiled, so it does not rely on the evaluation-time per-TPI Compile metadata flip).
+ var applyTpiTarget = NewElement("Target")
+ .WithAttribute("Name", "_ApplyTestTargetPlatformIdentifier")
+ .WithAttribute("BeforeTargets", "_MauiNormalizePlatformSpecificFolders;_MauiCollectPlatformSpecificCompileItems;_MauiRemovePlatformCompileItems")
+ .WithAttribute("Condition", " '$(_SingleProjectTestTargetPlatformIdentifier)' != '' ");
+ var tpiPropertyGroup = NewElement("PropertyGroup");
+ tpiPropertyGroup.Add(NewElement("TargetPlatformIdentifier").WithValue("$(_SingleProjectTestTargetPlatformIdentifier)"));
+ applyTpiTarget.Add(tpiPropertyGroup);
+ project.Add(applyTpiTarget);
+ }
+
+ void AddMauiReferences(XElement project)
+ {
+ var itemGroup = NewElement("ItemGroup");
+ foreach (var assembly in references)
+ {
+ var reference = NewElement("Reference").WithAttribute("Include", assembly);
+ if (assembly.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
+ {
+ reference.Add(NewElement("HintPath").WithValue(IOPath.Combine("..", "..", assembly)));
+ }
+ itemGroup.Add(reference);
+ }
+
+ project.Add(itemGroup);
+ }
+
[Fact]
public void BuildAProject()
{
@@ -316,7 +381,7 @@ public void HotReloadSupportForXSG(string configuration)
var generatorDirectory = IOPath.Combine(tempDirectory, "Generated", "Microsoft.Maui.Controls.SourceGen", "Microsoft.Maui.Controls.SourceGen.XamlGenerator");
AssertExists(IOPath.Combine(generatorDirectory, "MainPage.xaml.sg.cs"), nonEmpty: true);
AssertExists(IOPath.Combine(generatorDirectory, "MainPage.xaml.xsg.cs"), nonEmpty: true);
-
+
var sg = File.ReadAllText(IOPath.Combine(generatorDirectory, "MainPage.xaml.sg.cs"));
var xsg = File.ReadAllText(IOPath.Combine(generatorDirectory, "MainPage.xaml.xsg.cs"));
if (configuration == "Debug")
@@ -325,10 +390,10 @@ public void HotReloadSupportForXSG(string configuration)
Assert.Contains("InitializeComponentRuntime", xsg, StringComparison.Ordinal);
}
else
- {
+ {
Assert.DoesNotContain("InitializeComponentRuntime", sg, StringComparison.Ordinal);
Assert.DoesNotContain("InitializeComponentRuntime", xsg, StringComparison.Ordinal);
- }
+ }
}
@@ -713,5 +778,923 @@ public void SingleProject_DefaultEntitlementsUsedWhenNoCustomSet()
log.Contains("CodesignEntitlements = Platforms\\iOS\\Entitlements.plist", StringComparison.Ordinal),
"Default Entitlements.plist should be used when no custom CodesignEntitlements is set");
}
+
+ // These tests exercise the data-driven MauiPlatformSpecificFolder compile
+ // selection contract: recognized TargetPlatformIdentifier(s) matching, shared
+ // folders, backward-compatible singular metadata, condition-gated folders, and
+ // the neutral-TFM backend activation added for external backends (Part of
+ // #35021 / Part of #36650). They build a minimal SingleProject csproj that
+ // imports the real SingleProject Before/After targets so the actual shipping
+ // logic is under test rather than a copy.
+
+ [Theory]
+ [InlineData("ios", "ios;maccatalyst", true)]
+ [InlineData("maccatalyst", "ios;maccatalyst", true)]
+ [InlineData("android", "ios;maccatalyst", false)]
+ [InlineData("ios", "ios; maccatalyst", true)]
+ [InlineData("maccatalyst", "ios; maccatalyst", true)]
+ [InlineData("android", "ios; maccatalyst", false)]
+ // Tab and mixed whitespace in the list — see Regex.Replace(\s+, '') in
+ // _MauiCollectPlatformSpecificCompileItems. ASCII-space-only stripping
+ // (.Replace(' ', '')) would silently miss these and break shared folders.
+ [InlineData("ios", "ios;\tmaccatalyst", true)]
+ [InlineData("maccatalyst", "ios;\tmaccatalyst", true)]
+ [InlineData("ios", "ios; \t maccatalyst", true)]
+ [InlineData("maccatalyst", "ios; \t maccatalyst", true)]
+ public void SingleProject_SharedPlatformFolderMappingsAreRespected(string targetPlatformIdentifier, string targetPlatformIdentifiers, bool shouldIncludeAppleSharedFile)
+ {
+ 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\\Apple\\")
+ .WithAttribute("TargetPlatformIdentifiers", targetPlatformIdentifiers));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Apple\\AppleSharedMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class AppleSharedMarker
+{
+ public static string Value => ""Apple"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ Build(projectFile, additionalArgs: $"-p:_SingleProjectTestTargetPlatformIdentifier={targetPlatformIdentifier}");
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+
+ if (shouldIncludeAppleSharedFile)
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.AppleSharedMarker");
+ else
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.AppleSharedMarker");
+ }
+
+ // Regression test: a user-supplied folder declared without a trailing slash
+ // must NOT match sibling folders sharing a common prefix. Without
+ // EnsureTrailingSlash() in _MauiCollectPlatformSpecificCompileItems, the
+ // glob "Platforms\Apple**/*.cs" would silently include AppleX/AppleLegacy.
+ [Theory]
+ [InlineData("Platforms\\Apple", "ios")]
+ [InlineData("Platforms\\Apple\\", "ios")]
+ [InlineData("Platforms\\Apple", "maccatalyst")]
+ [InlineData("Platforms\\Apple\\", "maccatalyst")]
+ public void SingleProject_PlatformFolderWithoutTrailingSlashDoesNotMatchSiblingFolders(string includePath, string targetPlatformIdentifier)
+ {
+ 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", includePath)
+ .WithAttribute("TargetPlatformIdentifiers", "ios;maccatalyst"));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Apple\\AppleSharedMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class AppleSharedMarker
+{
+ public static string Value => ""Apple"";
+}");
+
+ // Sibling folder with a common prefix — must NOT be picked up by the
+ // "Apple" mapping regardless of trailing-slash authoring.
+ WriteFile("Platforms\\AppleX\\AppleXMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class AppleXMarker
+{
+ public static string Value => ""AppleX"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ Build(projectFile, additionalArgs: $"-p:_SingleProjectTestTargetPlatformIdentifier={targetPlatformIdentifier}");
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.AppleSharedMarker");
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.AppleXMarker");
+ }
+
+ // Non-platform builds (TargetPlatformIdentifier empty, e.g. design-time
+ // or netstandard TFM) must keep removing platform folders that declare a
+ // non-empty TargetPlatformIdentifiers. An unconditioned shared folder with
+ // empty TargetPlatformIdentifiers and no authored ActivationValue must still
+ // participate — locks in the legacy "empty TPI + empty ActivationValue =
+ // always include" branch of _MauiCollectPlatformSpecificCompileItems.
+ // (Genuine item-Condition gating — where the mapping carries its own Condition —
+ // is covered separately by
+ // SingleProject_ConditionGatedFolderParticipatesOnlyWhenConditionIsTrue.)
+ [Fact]
+ public void SingleProject_NonPlatformBuildExcludesPlatformSpecificFoldersButKeepsSharedFolder()
+ {
+ 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\\Apple\\")
+ .WithAttribute("TargetPlatformIdentifiers", "ios;maccatalyst"));
+ customMappings.Add(NewElement("MauiPlatformSpecificFolder")
+ .WithAttribute("Include", "Platforms\\Shared\\"));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Apple\\AppleSharedMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class AppleSharedMarker
+{
+ public static string Value => ""Apple"";
+}");
+
+ WriteFile("Platforms\\Shared\\SharedMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class SharedMarker
+{
+ public static string Value => ""Shared"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ // No -p:_SingleProjectTestTargetPlatformIdentifier — simulates the
+ // non-platform TFM / design-time evaluation scenario.
+ Build(projectFile);
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.AppleSharedMarker");
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.SharedMarker");
+ }
+
+ // A bare ActivationValue that expands to empty during project evaluation is
+ // indistinguishable from a legacy shared-folder mapping by the time the
+ // normalization target runs. Preserve that compatibility behavior on neutral
+ // and recognized TFMs; conditional backends must also declare an
+ // ActivationProperty or BackendIdentity so malformed metadata fails closed.
+ [Theory]
+ [InlineData("")]
+ [InlineData("ios")]
+ public void SingleProject_BareUnresolvedActivationValueUsesLegacySharedFolderBehavior(string targetPlatformIdentifier)
+ {
+ 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\\BareUnresolved\\")
+ .WithAttribute("ActivationValue", "$(SomeUnsetProperty)"));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\BareUnresolved\\BareUnresolvedMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class BareUnresolvedMarker
+{
+ public static string Value => ""BareUnresolved"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ var args = string.IsNullOrEmpty(targetPlatformIdentifier)
+ ? ""
+ : $"-p:_SingleProjectTestTargetPlatformIdentifier={targetPlatformIdentifier}";
+ Build(projectFile, additionalArgs: args);
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.BareUnresolvedMarker");
+ }
+
+ // Authored activation metadata that normalizes to empty is invalid backend
+ // configuration, not a legacy shared folder. Fail closed on neutral and
+ // recognized TFMs so backend-only sources cannot leak into every inner build.
+ [Theory]
+ [InlineData("")]
+ [InlineData("ios")]
+ public void SingleProject_WhitespaceActivationValueIsExcluded(string targetPlatformIdentifier)
+ {
+ 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\\Whitespace\\")
+ .WithAttribute("ActivationValue", " "));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Whitespace\\WhitespaceMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class WhitespaceMarker
+{
+ public static string Value => ""Whitespace"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ var args = string.IsNullOrEmpty(targetPlatformIdentifier)
+ ? ""
+ : $"-p:_SingleProjectTestTargetPlatformIdentifier={targetPlatformIdentifier}";
+ Build(projectFile, additionalArgs: args);
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.WhitespaceMarker");
+ }
+
+ // An authored activation property whose value expands to empty is malformed
+ // backend metadata, not a legacy shared-folder mapping. Fail closed on both
+ // neutral and recognized TFMs instead of leaking its sources into every build.
+ [Theory]
+ [InlineData("")]
+ [InlineData("ios")]
+ public void SingleProject_UnresolvedActivationValueWithBackendMetadataIsExcluded(string targetPlatformIdentifier)
+ {
+ 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\\Unresolved\\")
+ .WithAttribute("ActivationProperty", "MyBackendSwitch")
+ .WithAttribute("ActivationValue", "$(SomeUnsetProperty)"));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Unresolved\\UnresolvedMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class UnresolvedMarker
+{
+ public static string Value => ""Unresolved"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ var args = string.IsNullOrEmpty(targetPlatformIdentifier)
+ ? ""
+ : $"-p:_SingleProjectTestTargetPlatformIdentifier={targetPlatformIdentifier}";
+ Build(projectFile, additionalArgs: args);
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.UnresolvedMarker");
+ }
+
+ // Genuine item-Condition gating: a MauiPlatformSpecificFolder mapping may
+ // carry its own MSBuild item Condition that decides participation at
+ // evaluation time. When the Condition is true the item materializes and —
+ // declaring neither TargetPlatformIdentifiers nor an ActivationValue — is
+ // kept via the "always include" branch of
+ // _MauiCollectPlatformSpecificCompileItems, so its Platforms//**/*.cs
+ // compile even on a non-platform build. When the Condition is false the item
+ // never exists and _MauiRemovePlatformCompileItems strips the folder like any
+ // other Platforms/** content. Exercises both branches through the actual
+ // shipping SingleProject targets.
+ [Theory]
+ [InlineData("true", "", true)]
+ [InlineData("false", "", false)]
+ [InlineData("true", "android", true)]
+ [InlineData("false", "android", false)]
+ public void SingleProject_ConditionGatedFolderParticipatesOnlyWhenConditionIsTrue(
+ string conditionValue,
+ string targetPlatformIdentifier,
+ bool shouldIncludeConditionalFile)
+ {
+ 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");
+ // An unconditioned shared folder guarantees @(MauiPlatformSpecificFolder)
+ // is non-empty in both branches, so the false case still flows through the
+ // collect/remove pipeline rather than short-circuiting on an empty list.
+ customMappings.Add(NewElement("MauiPlatformSpecificFolder")
+ .WithAttribute("Include", "Platforms\\Shared\\"));
+ customMappings.Add(NewElement("MauiPlatformSpecificFolder")
+ .WithAttribute("Include", "Platforms\\Conditional\\")
+ .WithAttribute("Condition", " '$(IncludeConditionalBackend)' == 'true' "));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Shared\\SharedMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class SharedMarker
+{
+ public static string Value => ""Shared"";
+}");
+
+ WriteFile("Platforms\\Conditional\\ConditionalMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class ConditionalMarker
+{
+ public static string Value => ""Conditional"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ // Exercise both neutral and recognized-platform builds. The property toggles
+ // the mapping's item Condition to true/false, while the unconditioned shared
+ // folder must remain included in every case.
+ var additionalArgs = $"-p:IncludeConditionalBackend={conditionValue}";
+ if (!string.IsNullOrEmpty(targetPlatformIdentifier))
+ additionalArgs += $" -p:_SingleProjectTestTargetPlatformIdentifier={targetPlatformIdentifier}";
+ Build(projectFile, additionalArgs: additionalArgs);
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ // The unconditioned shared folder is always kept regardless of the gate.
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.SharedMarker");
+
+ if (shouldIncludeConditionalFile)
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.ConditionalMarker");
+ else
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.ConditionalMarker");
+ }
+
+ // Design-time metadata contract for _MauiUnflipKeptCompileItemMetadata.
+ // The blanket marks every Platforms/** file
+ // ExcludeFromCurrentConfiguration=true, and the per-TPI flips only cover the
+ // built-in $(ProjectFolder) paths. A kept shared folder (empty TPI +
+ // empty ActivationValue, so it survives the blanket removal) therefore relies
+ // on _MauiUnflipKeptCompileItemMetadata to flip its Compile item back to
+ // ExcludeFromCurrentConfiguration=false so Visual Studio does not grey it out.
+ // This asserts the actual metadata value on the kept item via a diagnostic
+ // Message target that runs after the shipping unflip target on a non-platform
+ // (design-time-style) build.
+ [Fact]
+ public void SingleProject_UnflipKeptCompileItemMetadata_SetsExcludeFromCurrentConfigurationFalseForKeptSharedFolder()
+ {
+ 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\\Shared\\"));
+ project.Add(customMappings);
+
+ var unrelatedCompileMetadata = NewElement("ItemGroup");
+ var unrelatedCompile = NewElement("Compile").WithAttribute("Update", "Entry.cs");
+ unrelatedCompile.Add(NewElement("ExcludeFromCurrentConfiguration").WithValue("true"));
+ unrelatedCompileMetadata.Add(unrelatedCompile);
+ project.Add(unrelatedCompileMetadata);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Shared\\SharedMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class SharedMarker
+{
+ public static string Value => ""Shared"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ // Diagnostic target: after the shipping unflip target runs, emit the
+ // ExcludeFromCurrentConfiguration metadata for each surviving Compile item
+ // (one Message per item via cross-item batching) so the test can assert the
+ // kept shared file was flipped back to false.
+ var dumpTarget = NewElement("Target")
+ .WithAttribute("Name", "_TestDumpExcludeFromCurrentConfiguration")
+ .WithAttribute("AfterTargets", "_MauiUnflipKeptCompileItemMetadata");
+ dumpTarget.Add(NewElement("Message")
+ .WithAttribute("Importance", "high")
+ .WithAttribute("Condition", " '%(Compile.Identity)' != '' ")
+ .WithAttribute("Text", "COMPILE_META: %(Compile.Identity)|ExcludeFromCurrentConfiguration=%(Compile.ExcludeFromCurrentConfiguration)"));
+ project.Add(dumpTarget);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ // Non-platform build (no _SingleProjectTestTargetPlatformIdentifier) mirrors
+ // the design-time evaluation where the IDE would otherwise grey out the file.
+ var log = Build(projectFile);
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.SharedMarker");
+
+ // The kept shared file must carry ExcludeFromCurrentConfiguration=false; the
+ // pipe format keeps the identity (which may use either path separator) and
+ // the metadata on the same emitted line.
+ Assert.True(
+ log.Contains("SharedMarker.cs|ExcludeFromCurrentConfiguration=false", StringComparison.OrdinalIgnoreCase),
+ "_MauiUnflipKeptCompileItemMetadata should flip ExcludeFromCurrentConfiguration back to false " +
+ "on the kept shared folder's Compile item. Build log:\n" + log);
+ Assert.True(
+ log.Contains("Entry.cs|ExcludeFromCurrentConfiguration=true", StringComparison.OrdinalIgnoreCase),
+ "_MauiUnflipKeptCompileItemMetadata must preserve unrelated Compile metadata. Build log:\n" + log);
+ }
+
+ [Fact]
+ public void SingleProject_UnflipKeptCompileItemMetadata_PreservesAlreadyActiveCompileItemOrder()
+ {
+ 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"));
+ var activePlatformCompile = NewElement("Compile").WithAttribute("Include", "Platforms\\Windows\\WindowsMarker.cs");
+ activePlatformCompile.Add(NewElement("ExcludeFromCurrentConfiguration").WithValue("false"));
+ compileItems.Add(activePlatformCompile);
+ 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\\Windows\\WindowsMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class WindowsMarker
+{
+ public static string Value => ""Windows"";
+}");
+
+ WriteFile("After.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class After
+{
+ public static string Value => ""After"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ // The synthetic TPI is assigned during target execution, after the
+ // evaluation-time built-in folder metadata flips have already run.
+ // Model the resulting active-platform metadata before the shipping
+ // unflip target so this test covers its ordering behavior.
+ var markActivePlatformItemTarget = NewElement("Target")
+ .WithAttribute("Name", "_TestMarkActivePlatformCompile")
+ .WithAttribute("AfterTargets", "_MauiRemovePlatformCompileItems")
+ .WithAttribute("BeforeTargets", "_MauiUnflipKeptCompileItemMetadata");
+ var markActivePlatformItemGroup = NewElement("ItemGroup");
+ var markActivePlatformCompile = NewElement("Compile")
+ .WithAttribute("Update", "Platforms\\Windows\\WindowsMarker.cs");
+ markActivePlatformCompile.Add(NewElement("ExcludeFromCurrentConfiguration").WithValue("false"));
+ markActivePlatformItemGroup.Add(markActivePlatformCompile);
+ markActivePlatformItemTarget.Add(markActivePlatformItemGroup);
+ project.Add(markActivePlatformItemTarget);
+
+ var dumpTarget = NewElement("Target")
+ .WithAttribute("Name", "_TestDumpCompileOrder")
+ .WithAttribute("AfterTargets", "_MauiUnflipKeptCompileItemMetadata");
+ dumpTarget.Add(NewElement("Message")
+ .WithAttribute("Importance", "high")
+ .WithAttribute("Text", "COMPILE_ORDER: @(Compile->'%(Filename)', '|')"));
+ project.Add(dumpTarget);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ var log = Build(projectFile, additionalArgs: "-p:_SingleProjectTestTargetPlatformIdentifier=windows");
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.Before");
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.WindowsMarker");
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.After");
+ Assert.Contains(
+ "COMPILE_ORDER: Before|WindowsMarker|After",
+ log,
+ StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Backward compatibility: a folder that declares only the legacy singular
+ // TargetPlatformIdentifier metadata must continue to match exactly that TPI.
+ [Theory]
+ [InlineData("ios", true)]
+ [InlineData("maccatalyst", false)]
+ [InlineData("android", false)]
+ public void SingleProject_SingularPlatformFolderMetadataRemainsBackwardCompatible(string targetPlatformIdentifier, bool shouldIncludeLegacyFile)
+ {
+ 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\\LegacyiOS\\")
+ .WithAttribute("TargetPlatformIdentifier", "ios"));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\LegacyiOS\\LegacyIosMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class LegacyIosMarker
+{
+ public static string Value => ""LegacyiOS"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ Build(projectFile, additionalArgs: $"-p:_SingleProjectTestTargetPlatformIdentifier={targetPlatformIdentifier}");
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+
+ if (shouldIncludeLegacyFile)
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.LegacyIosMarker");
+ else
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.LegacyIosMarker");
+ }
+
+ // 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
+ // MauiActiveBackend selector. Only the activated backend's Platforms/
+ // files compile; a recognized built-in folder (iOS) stays excluded because
+ // its TPI does not match the (empty) neutral TPI.
+ [Fact]
+ public void SingleProject_NeutralTfmActivatesBackendByIdentity()
+ {
+ 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\\Gtk\\")
+ .WithAttribute("BackendIdentity", "gtk"));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Gtk\\GtkMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class GtkMarker
+{
+ public static string Value => ""Gtk"";
+}");
+
+ // A built-in recognized folder (iOS) that must NOT come in on a neutral
+ // build — proves activation is exclusive to the selected backend.
+ WriteFile("Platforms\\iOS\\IosMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class IosMarker
+{
+ public static string Value => ""iOS"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ // Neutral TFM (no _SingleProjectTestTargetPlatformIdentifier) + backend selector.
+ Build(projectFile, additionalArgs: "-p:MauiActiveBackend=gtk");
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.GtkMarker");
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.IosMarker");
+ }
+
+ // The neutral backend selector must not add backend files to a recognized
+ // platform inner build. Android remains the only active platform here even
+ // when MauiActiveBackend names GTK.
+ [Fact]
+ public void SingleProject_RecognizedTfmIgnoresNeutralBackendSelector()
+ {
+ 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\\Gtk\\")
+ .WithAttribute("BackendIdentity", "gtk"));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Android\\AndroidMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class AndroidMarker
+{
+ public static string Value => ""Android"";
+}");
+
+ WriteFile("Platforms\\Gtk\\GtkMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class GtkMarker
+{
+ public static string Value => ""Gtk"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ Build(projectFile, additionalArgs: "-p:_SingleProjectTestTargetPlatformIdentifier=android -p:MauiActiveBackend=gtk");
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.AndroidMarker");
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.GtkMarker");
+ }
+
+ // A backend that is registered but not selected must be excluded: either
+ // MauiActiveBackend names a different backend, or it is unset entirely.
+ [Theory]
+ [InlineData("cocoa")]
+ [InlineData("")]
+ public void SingleProject_NonMatchingBackendIsExcludedOnNeutralTfm(string activeBackend)
+ {
+ 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\\Gtk\\")
+ .WithAttribute("BackendIdentity", "gtk"));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Gtk\\GtkMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class GtkMarker
+{
+ public static string Value => ""Gtk"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ var args = string.IsNullOrEmpty(activeBackend) ? "" : $"-p:MauiActiveBackend={activeBackend}";
+ Build(projectFile, additionalArgs: args);
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.GtkMarker");
+ }
+
+ // A backend may name a custom activation property instead of the default
+ // MauiActiveBackend selector. The folder compiles only when the named
+ // property equals the declared ActivationValue.
+ [Theory]
+ [InlineData("on", true)]
+ [InlineData("off", false)]
+ [InlineData("", false)]
+ public void SingleProject_CustomActivationPropertyAndValueActivateBackend(string switchValue, bool shouldIncludeFooFile)
+ {
+ 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\\Foo\\")
+ .WithAttribute("BackendIdentity", "foo")
+ .WithAttribute("ActivationProperty", "MyBackendSwitch")
+ .WithAttribute("ActivationValue", "on"));
+ project.Add(customMappings);
+
+ WriteFile("Entry.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class Entry
+{
+ public static string Value => ""ok"";
+}");
+
+ WriteFile("Platforms\\Foo\\FooMarker.cs", @"
+namespace Microsoft.Maui.Controls.Xaml.UnitTests;
+
+public static class FooMarker
+{
+ public static string Value => ""Foo"";
+}");
+
+ AddSingleProjectTargetsImport(project);
+
+ var projectFile = IOPath.Combine(tempDirectory, "test.csproj");
+ project.Save(projectFile);
+
+ var args = string.IsNullOrEmpty(switchValue) ? "" : $"-p:MyBackendSwitch={switchValue}";
+ Build(projectFile, additionalArgs: args);
+
+ var testDll = IOPath.Combine(intermediateDirectory, "test.dll");
+ AssertExists(testDll, nonEmpty: true);
+
+ if (shouldIncludeFooFile)
+ AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.FooMarker");
+ else
+ AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.FooMarker");
+ }
}
}