Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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. A registration can therefore
activate through TargetPlatformIdentifier(s) on a recognized platform TFM or through
MauiActiveBackend on a neutral TFM without repeating its backend identity.
-->
<ItemGroup>
<MauiPlatformSpecificFolder Include="$(AndroidProjectFolder)" TargetPlatformIdentifier="android" BackendIdentity="android" />
Expand All @@ -59,10 +58,9 @@
- 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 <MauiActiveBackend>gtk</MauiActiveBackend>
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.
without having to repeat the value. This also applies when the same
registration declares TargetPlatformIdentifier(s), giving it both recognized
TPI and neutral-TFM activation paths.
- 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
Expand Down Expand Up @@ -91,17 +89,10 @@
<TargetPlatformIdentifiers>%(MauiPlatformSpecificFolder.TargetPlatformIdentifier)</TargetPlatformIdentifiers>
</MauiPlatformSpecificFolder>

<!--
Back-fill ActivationValue from BackendIdentity ONLY for folders that are not
already TPI-recognized. Built-in platforms carry both a TargetPlatformIdentifier
and a BackendIdentity; deriving an ActivationValue for them would let a stray
MauiActiveBackend value pull an unrelated built-in folder into a neutral build.
Gating on empty TargetPlatformIdentifier(s) keeps the neutral-activation branch
exclusive to backends that actually rely on it.
-->
<!-- Preserve an explicitly authored ActivationValue; otherwise use the stable backend key. -->
<MauiPlatformSpecificFolder
Update="@(MauiPlatformSpecificFolder)"
Condition=" '%(MauiPlatformSpecificFolder.ActivationValue)' == '' and '%(MauiPlatformSpecificFolder.BackendIdentity)' != '' and '%(MauiPlatformSpecificFolder.TargetPlatformIdentifier)' == '' and '%(MauiPlatformSpecificFolder.TargetPlatformIdentifiers)' == '' ">
Condition=" '%(MauiPlatformSpecificFolder.ActivationValue)' == '' and '%(MauiPlatformSpecificFolder.BackendIdentity)' != '' ">
<ActivationValue>%(MauiPlatformSpecificFolder.BackendIdentity)</ActivationValue>
</MauiPlatformSpecificFolder>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,23 +150,32 @@
<!-- Removals -->
<ItemGroup>
<!--
Remove every Compile item under $(PlatformsProjectFolder) that is not
part of the current build configuration AND has not been explicitly
Remove every Compile item under $(PlatformsProjectFolder) that is marked
as excluded from the current 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
<Compile Update> 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.
from inside an Include of a *different* item type, preserving the
cross-item-type batching that isolates the true Compile metadata bucket.
Root the removal candidates in that batched @(Compile) list rather than
a fresh filesystem glob, which would also capture physical files from
false metadata buckets. The outside-platform helper then narrows the
candidates without mutating or reordering @(Compile).
-->
<_MauiPlatformCompileToRemove
Condition=" '%(Compile.ExcludeFromCurrentConfiguration)' == 'true' "
Include="$(PlatformsProjectFolder)**/*$(DefaultLanguageSourceExtension)"
Exclude="@(_MauiPlatformSpecificCompileItems)" />
Include="@(Compile)" />
<_MauiCompileItemsOutsidePlatformFolder Include="@(_MauiPlatformCompileToRemove)" />
<_MauiCompileItemsOutsidePlatformFolder
Remove="$(PlatformsProjectFolder)**/*$(DefaultLanguageSourceExtension)" />
<_MauiPlatformCompileToRemove
Remove="@(_MauiCompileItemsOutsidePlatformFolder)"
MatchOnMetadata="FullPath"
MatchOnMetadataOptions="PathLike" />
<_MauiPlatformCompileToRemove
Remove="@(_MauiPlatformSpecificCompileItems)"
MatchOnMetadata="FullPath"
MatchOnMetadataOptions="PathLike" />
<Compile Remove="@(_MauiPlatformCompileToRemove)" />

<!-- Remove all Windows (WinUI) XAML Files from the Windows folder -->
Expand Down
174 changes: 174 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/MSBuild/MSBuildTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,112 @@ public static class After
StringComparison.OrdinalIgnoreCase);
}

// Regression for cross-item-type batching in _MauiRemovePlatformCompileItems.
// The true metadata batch must contain only the matching Compile identities;
// a fresh filesystem glob would also capture KeepMarker.cs from the false
// bucket and remove it because this test intentionally has no folder allow-list.
[Fact]
public void SingleProject_RemovePlatformCompileItems_RemovesOnlyCompileItemsMarkedTrue()
{
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\\Mixed\\KeepMarker.cs"));
compileItems.Add(NewElement("Compile").WithAttribute("Include", "Platforms\\Mixed\\RemoveMarker.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\\Mixed\\KeepMarker.cs", @"
namespace Microsoft.Maui.Controls.Xaml.UnitTests;

public static class KeepMarker
{
public static string Value => ""Keep"";
}");

WriteFile("Platforms\\Mixed\\RemoveMarker.cs", @"
namespace Microsoft.Maui.Controls.Xaml.UnitTests;

public static class RemoveMarker
{
public static string Value => ""Remove"";
}");

WriteFile("After.cs", @"
namespace Microsoft.Maui.Controls.Xaml.UnitTests;

public static class After
{
public static string Value => ""After"";
}");

AddSingleProjectTargetsImport(project);

// Apply the explicit metadata after the shipping targets import so the
// blanket Platforms/** update has already run.
var compileMetadata = NewElement("ItemGroup");
var keptCompile = NewElement("Compile").WithAttribute("Update", "Platforms\\Mixed\\KeepMarker.cs");
keptCompile.Add(NewElement("ExcludeFromCurrentConfiguration").WithValue("false"));
keptCompile.Add(NewElement("TestMetadata").WithValue("preserved"));
compileMetadata.Add(keptCompile);
var removedCompile = NewElement("Compile").WithAttribute("Update", "Platforms\\Mixed\\RemoveMarker.cs");
removedCompile.Add(NewElement("ExcludeFromCurrentConfiguration").WithValue("true"));
compileMetadata.Add(removedCompile);
project.Add(compileMetadata);

var dumpTarget = NewElement("Target")
.WithAttribute("Name", "_TestDumpCompileItemsAfterRemoval")
.WithAttribute("AfterTargets", "_MauiRemovePlatformCompileItems");
var dumpItems = NewElement("ItemGroup");
dumpItems.Add(NewElement("_TestKeptCompile")
.WithAttribute("Include", "@(Compile)")
.WithAttribute("Condition", " '%(Compile.Filename)' == 'KeepMarker' "));
dumpTarget.Add(dumpItems);
dumpTarget.Add(NewElement("Message")
.WithAttribute("Importance", "high")
.WithAttribute("Text", "COMPILE_ORDER: @(Compile->'%(Filename)', '|')"));
dumpTarget.Add(NewElement("Message")
.WithAttribute("Importance", "high")
.WithAttribute("Condition", " '%(Compile.Filename)' == 'KeepMarker' ")
.WithAttribute("Text", "KEEP_META: %(Compile.ExcludeFromCurrentConfiguration)|%(Compile.TestMetadata)"));
dumpTarget.Add(NewElement("Message")
.WithAttribute("Importance", "high")
.WithAttribute("Text", "KEEP_COUNT: @(_TestKeptCompile->Count())"));
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");
AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.KeepMarker");
AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.RemoveMarker");
AssertTypeExists(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.After");
Assert.Contains("COMPILE_ORDER: Before|KeepMarker|After", log, StringComparison.OrdinalIgnoreCase);
Assert.Contains("KEEP_META: false|preserved", log, StringComparison.OrdinalIgnoreCase);
Assert.Contains("KEEP_COUNT: 1", log, StringComparison.OrdinalIgnoreCase);
}

// Backward compatibility: a folder that declares only the legacy singular
// TargetPlatformIdentifier metadata must continue to match exactly that TPI.
[Theory]
Expand Down Expand Up @@ -1465,6 +1571,73 @@ public static class LegacyIosMarker
AssertTypeDoesNotExist(testDll, "Microsoft.Maui.Controls.Xaml.UnitTests.LegacyIosMarker");
}

// One backend registration may serve both a recognized TPI and a neutral TFM.
// BackendIdentity defaults the neutral ActivationValue even when TPI metadata
// is present, while the neutral selector remains ignored on another recognized TPI.
[Theory]
[InlineData("macos", "", true)]
[InlineData("", "macos", true)]
[InlineData("android", "macos", false)]
[InlineData("android", "", false)]
[InlineData("", "gtk", false)]
public void SingleProject_BackendRegistrationSupportsRecognizedAndNeutralActivation(
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
Expand Down Expand Up @@ -1659,6 +1832,7 @@ public void SingleProject_CustomActivationPropertyAndValueActivateBackend(string
var customMappings = NewElement("ItemGroup");
customMappings.Add(NewElement("MauiPlatformSpecificFolder")
.WithAttribute("Include", "Platforms\\Foo\\")
.WithAttribute("TargetPlatformIdentifiers", "foo")
.WithAttribute("BackendIdentity", "foo")
.WithAttribute("ActivationProperty", "MyBackendSwitch")
.WithAttribute("ActivationValue", "on"));
Expand Down
Loading