diff --git a/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets b/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets
index feb26d3d5946..8084ff847526 100644
--- a/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets
+++ b/src/SingleProject/Resizetizer/src/nuget/buildTransitive/Microsoft.Maui.Resizetizer.After.targets
@@ -75,9 +75,7 @@
<_ResizetizerOutputsFile>$(_ResizetizerIntermediateOutputPath)mauiimage.outputs
<_ResizetizerStampFile>$(_ResizetizerIntermediateOutputPath)mauiimage.stamp
<_MauiFontInputsFile>$(_ResizetizerIntermediateOutputPath)mauifont.inputs
- <_MauiFontStampFile>$(_ResizetizerIntermediateOutputPath)mauifont.stamp
<_MauiSplashInputsFile>$(_ResizetizerIntermediateOutputPath)mauisplash.inputs
- <_MauiSplashStampFile>$(_ResizetizerIntermediateOutputPath)mauisplash.stamp
<_MauiManifestStampFile>$(_ResizetizerIntermediateOutputPath)mauimanifest.stamp
<_ResizetizerIntermediateOutputRoot>$(_ResizetizerIntermediateOutputPath)resizetizer\
@@ -175,6 +173,11 @@
$(ProcessMauiFontsAfterTargets);
ResizetizeCollectItems;
+
+
+ $(ProcessMauiFontsBeforeTargets);
+ _ComputeAndroidAssetsPaths;
+
@@ -397,9 +400,7 @@
+ Condition="'$(EnableMauiSplashScreenProcessing)' == 'true'">
<_MauiHasSplashScreens>false
@@ -505,21 +506,15 @@
-
-
-
-
+
-
@@ -595,12 +590,8 @@
-
-
-
-
+
-
diff --git a/src/SingleProject/Resizetizer/test/UnitTests/ResizetizeTargetStructureTests.cs b/src/SingleProject/Resizetizer/test/UnitTests/ResizetizeTargetStructureTests.cs
new file mode 100644
index 000000000000..fd4caa41d73a
--- /dev/null
+++ b/src/SingleProject/Resizetizer/test/UnitTests/ResizetizeTargetStructureTests.cs
@@ -0,0 +1,161 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Xml.Linq;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.Maui.Resizetizer.Tests
+{
+ ///
+ /// Verifies the MSBuild target structure in Microsoft.Maui.Resizetizer.After.targets
+ /// to prevent regression of the "fonts missing on first build" bug (#23268) and
+ /// the "splash screens randomly missing" bug (#33092).
+ ///
+ /// Root cause: ProcessMauiFonts and ProcessMauiSplashScreens used Inputs/Outputs
+ /// for incremental builds. When the target was skipped (stamp file up-to-date),
+ /// platform item groups (AndroidAsset, BundleResource, etc.) were never populated
+ /// — causing fonts/splash screens to silently disappear from build output.
+ ///
+ public class ResizetizeTargetStructureTests
+ {
+ static readonly XNamespace MSBuildNs = "http://schemas.microsoft.com/developer/msbuild/2003";
+
+ readonly ITestOutputHelper _output;
+ readonly XDocument _targetsDoc;
+
+ public ResizetizeTargetStructureTests(ITestOutputHelper output)
+ {
+ _output = output;
+
+ // Navigate from test output dir (artifacts/bin/.../net10.0/) to repo root
+ var repoRoot = Path.GetFullPath(Path.Combine(
+ Directory.GetCurrentDirectory(), "..", "..", "..", "..", ".."));
+ var targetsFilePath = Path.Combine(repoRoot,
+ "src", "SingleProject", "Resizetizer", "src", "nuget",
+ "buildTransitive", "Microsoft.Maui.Resizetizer.After.targets");
+
+ Assert.True(File.Exists(targetsFilePath),
+ $"Targets file not found at: {targetsFilePath}");
+ _output.WriteLine($"Loading targets from: {targetsFilePath}");
+
+ _targetsDoc = XDocument.Load(targetsFilePath);
+ }
+
+ XElement FindTarget(string name) =>
+ _targetsDoc.Root!
+ .Elements(MSBuildNs + "Target")
+ .FirstOrDefault(t => t.Attribute("Name")?.Value == name);
+
+ XElement FindAndroidPropertyGroup() =>
+ _targetsDoc.Root!
+ .Elements(MSBuildNs + "PropertyGroup")
+ .FirstOrDefault(pg =>
+ {
+ var cond = pg.Attribute("Condition")?.Value;
+ return cond != null
+ && cond.Contains("_ResizetizerIsAndroidApp", StringComparison.Ordinal)
+ && !cond.Contains("_ResizetizerIsiOSApp", StringComparison.Ordinal);
+ });
+
+ // ──────────────────────────────────────────────────────────
+ // ProcessMauiFonts — #23268
+ // ──────────────────────────────────────────────────────────
+
+ [Fact]
+ public void ProcessMauiFonts_ShouldNotHaveInputsAttribute()
+ {
+ var target = FindTarget("ProcessMauiFonts");
+ Assert.NotNull(target);
+ Assert.True(target.Attribute("Inputs") is null,
+ "ProcessMauiFonts must not use Inputs for incremental builds. " +
+ "See https://github.com/dotnet/maui/issues/23268");
+ }
+
+ [Fact]
+ public void ProcessMauiFonts_ShouldNotHaveOutputsAttribute()
+ {
+ var target = FindTarget("ProcessMauiFonts");
+ Assert.NotNull(target);
+ Assert.True(target.Attribute("Outputs") is null,
+ "ProcessMauiFonts must not use Outputs for incremental builds. " +
+ "See https://github.com/dotnet/maui/issues/23268");
+ }
+
+ [Fact]
+ public void Android_ProcessMauiFontsBeforeTargets_ShouldIncludeComputeAndroidAssetsPaths()
+ {
+ var androidPG = FindAndroidPropertyGroup();
+ Assert.NotNull(androidPG);
+
+ var beforeTargets = androidPG.Element(MSBuildNs + "ProcessMauiFontsBeforeTargets");
+ Assert.NotNull(beforeTargets);
+ Assert.Contains("_ComputeAndroidAssetsPaths", beforeTargets.Value, StringComparison.Ordinal);
+ }
+
+ [Fact]
+ public void ProcessMauiFonts_MatchesWorkingProcessMauiAssetsPattern()
+ {
+ var assetsTarget = FindTarget("ProcessMauiAssets");
+ Assert.NotNull(assetsTarget);
+ Assert.Null(assetsTarget.Attribute("Inputs"));
+ Assert.Null(assetsTarget.Attribute("Outputs"));
+
+ var fontsTarget = FindTarget("ProcessMauiFonts");
+ Assert.NotNull(fontsTarget);
+ Assert.Null(fontsTarget.Attribute("Inputs"));
+ Assert.Null(fontsTarget.Attribute("Outputs"));
+ }
+
+ // ──────────────────────────────────────────────────────────
+ // ProcessMauiSplashScreens — #33092
+ // ──────────────────────────────────────────────────────────
+
+ ///
+ /// ProcessMauiSplashScreens must NOT have Inputs. Same root cause as fonts:
+ /// when skipped, platform item groups (LibraryResourceDirectories,
+ /// BundleResource, ContentWithTargetPath) may not be populated, causing
+ /// splash screens to randomly disappear from builds.
+ /// The custom tasks (GenerateSplashAndroidResources, etc.) have built-in
+ /// file-level incrementality via Resizer.IsUpToDate(), so removing
+ /// Inputs/Outputs does not cause expensive re-rendering.
+ ///
+ [Fact]
+ public void ProcessMauiSplashScreens_ShouldNotHaveInputsAttribute()
+ {
+ var target = FindTarget("ProcessMauiSplashScreens");
+ Assert.NotNull(target);
+ Assert.True(target.Attribute("Inputs") is null,
+ "ProcessMauiSplashScreens must not use Inputs for incremental builds. " +
+ "See https://github.com/dotnet/maui/issues/33092");
+ }
+
+ [Fact]
+ public void ProcessMauiSplashScreens_ShouldNotHaveOutputsAttribute()
+ {
+ var target = FindTarget("ProcessMauiSplashScreens");
+ Assert.NotNull(target);
+ Assert.True(target.Attribute("Outputs") is null,
+ "ProcessMauiSplashScreens must not use Outputs for incremental builds. " +
+ "See https://github.com/dotnet/maui/issues/33092");
+ }
+
+ ///
+ /// All three content-producing targets should follow the same pattern
+ /// as ProcessMauiAssets (no Inputs/Outputs).
+ ///
+ [Fact]
+ public void AllContentTargets_FollowProcessMauiAssetsPattern()
+ {
+ foreach (var targetName in new[] { "ProcessMauiAssets", "ProcessMauiFonts", "ProcessMauiSplashScreens" })
+ {
+ var target = FindTarget(targetName);
+ Assert.NotNull(target);
+ Assert.True(target.Attribute("Inputs") is null,
+ $"{targetName} must not use Inputs. See #23268 / #33092");
+ Assert.True(target.Attribute("Outputs") is null,
+ $"{targetName} must not use Outputs. See #23268 / #33092");
+ }
+ }
+ }
+}