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..e4ed9a712ceb 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,9 @@
<_ResizetizerOutputsFile>$(_ResizetizerIntermediateOutputPath)mauiimage.outputs
<_ResizetizerStampFile>$(_ResizetizerIntermediateOutputPath)mauiimage.stamp
<_MauiFontInputsFile>$(_ResizetizerIntermediateOutputPath)mauifont.inputs
- <_MauiFontStampFile>$(_ResizetizerIntermediateOutputPath)mauifont.stamp
+ <_MauiFontOutputsFile>$(_ResizetizerIntermediateOutputPath)mauifont.outputs
<_MauiSplashInputsFile>$(_ResizetizerIntermediateOutputPath)mauisplash.inputs
- <_MauiSplashStampFile>$(_ResizetizerIntermediateOutputPath)mauisplash.stamp
+ <_MauiSplashOutputsFile>$(_ResizetizerIntermediateOutputPath)mauisplash.outputs
<_MauiManifestStampFile>$(_ResizetizerIntermediateOutputPath)mauimanifest.stamp
<_ResizetizerIntermediateOutputRoot>$(_ResizetizerIntermediateOutputPath)resizetizer\
@@ -120,11 +120,16 @@
ProcessMauiSplashScreens;
_ReadResizetizeImagesOutputs;
+
+ $(ProcessMauiSplashScreensDependsOnTargets);
+ _ReadMauiSplashOutputs;
+
$(ProcessMauiFontsDependsOnTargets);
ResizetizeCollectItems;
ProcessMauiAssets;
ProcessMauiSplashScreens;
+ _ReadMauiFontOutputs;
@@ -399,7 +404,8 @@
+ Outputs="$(_MauiSplashOutputsFile);@(_MauiSplashOutputs)"
+ DependsOnTargets="$(ProcessMauiSplashScreensDependsOnTargets)">
<_MauiHasSplashScreens>false
@@ -505,21 +511,33 @@
-
-
+
+
+
-
+
+
+
+
+
+
+
@@ -531,7 +549,7 @@
SkipUnchangedFiles="true" />
- <_MauiFontCopied Include="$(_MauiIntermediateFonts)*" />
+ <_MauiFontCopied Include="$(_MauiIntermediateFonts)*" Exclude="$(_MauiIntermediateFonts)MauiInfo.plist" />
@@ -595,16 +613,30 @@
-
-
+
-
+
+
+
+
+
-
+
+
+
+
+
+
+
diff --git a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/ResizetizerTests.cs b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/ResizetizerTests.cs
index fefbe048b1ed..b8623d987f4f 100644
--- a/src/TestUtils/src/Microsoft.Maui.IntegrationTests/ResizetizerTests.cs
+++ b/src/TestUtils/src/Microsoft.Maui.IntegrationTests/ResizetizerTests.cs
@@ -1,3 +1,6 @@
+using Microsoft.Build.Framework;
+using Microsoft.Build.Logging.StructuredLogger;
+
namespace Microsoft.Maui.IntegrationTests;
[Trait("Category", "Build")]
@@ -99,4 +102,109 @@ public void CollectsAssets(string id, string libid, bool unpackaged)
Assert.True(File.Exists(Path.Combine(appDir, $"obj\\Debug\\{DotNetCurrent}-windows10.0.19041.0\\win-x64\\resizetizer\\r\\the_image.scale-100.png")),
"Windows was missing the image file.");
}
+
+ [Fact]
+ public void BuildRegeneratesFontsAndSplashWhenIntermediateOutputsAreMissing()
+ {
+ SetTestIdentifier("MissingResizetizerOutputs");
+
+ var projectDir = TestDirectory;
+ var projectFile = Path.Combine(projectDir, $"{Path.GetFileName(projectDir)}.csproj");
+ const string config = "Debug";
+
+ Assert.True(DotnetInternal.New("maui", projectDir, DotNetCurrent, output: _output),
+ $"Unable to create template maui. Check test output for errors.");
+
+ Assert.True(DotnetInternal.Build(projectFile, config, properties: BuildProps, output: _output),
+ $"Project {Path.GetFileName(projectFile)} failed to build. Check test output/attachments for errors.");
+
+ var intermediateOutputRoots = GetResizetizerOutputRoots(projectDir, config);
+ AssertBuiltTargetPlatforms(intermediateOutputRoots);
+ AssertIntermediateOutputsExist(intermediateOutputRoots);
+
+ foreach (var intermediateOutputRoot in intermediateOutputRoots)
+ {
+ DeleteDirectory(Path.Combine(intermediateOutputRoot, "resizetizer", "f"));
+ DeleteDirectory(Path.Combine(intermediateOutputRoot, "resizetizer", "sp"));
+ }
+
+ Assert.True(DotnetInternal.Build(projectFile, config, properties: BuildProps, output: _output),
+ $"Project {Path.GetFileName(projectFile)} failed to rebuild. Check test output/attachments for errors.");
+
+ AssertIntermediateOutputsExist(intermediateOutputRoots);
+
+ var noOpBinlogPath = Path.Combine(projectDir, "no-op.binlog");
+ Assert.True(DotnetInternal.Build(projectFile, config, properties: BuildProps, binlogPath: noOpBinlogPath, output: _output),
+ $"Project {Path.GetFileName(projectFile)} failed to no-op rebuild. Check test output/attachments for errors.");
+
+ AssertTargetSkipped(noOpBinlogPath, "ProcessMauiFonts", intermediateOutputRoots.Count);
+ AssertTargetSkipped(noOpBinlogPath, "ProcessMauiSplashScreens", intermediateOutputRoots.Count);
+ AssertIntermediateOutputsExist(intermediateOutputRoots);
+ }
+
+ static IReadOnlyList GetResizetizerOutputRoots(string projectDir, string config)
+ {
+ var intermediateOutputPath = Path.Combine(projectDir, "obj", config);
+ var outputRoots = Directory
+ .GetFiles(intermediateOutputPath, "mauifont.outputs", SearchOption.AllDirectories)
+ .Select(Path.GetDirectoryName)
+ .Where(root => root is not null && File.Exists(Path.Combine(root, "mauisplash.outputs")))
+ .Cast()
+ .OrderBy(root => root, StringComparer.OrdinalIgnoreCase)
+ .ToArray();
+
+ Assert.NotEmpty(outputRoots);
+ return outputRoots;
+ }
+
+ static void AssertBuiltTargetPlatforms(IReadOnlyList intermediateOutputRoots)
+ {
+ Assert.Contains(intermediateOutputRoots, root => ContainsTargetFramework(root, $"{DotNetCurrent}-android"));
+ Assert.Contains(intermediateOutputRoots, root => ContainsTargetFramework(root, $"{DotNetCurrent}-ios"));
+ Assert.Contains(intermediateOutputRoots, root => ContainsTargetFramework(root, $"{DotNetCurrent}-maccatalyst"));
+
+ if (TestEnvironment.IsWindows)
+ Assert.Contains(intermediateOutputRoots, root => ContainsTargetFramework(root, $"{DotNetCurrent}-windows"));
+ }
+
+ static bool ContainsTargetFramework(string path, string targetFramework) =>
+ path.Contains(targetFramework, StringComparison.OrdinalIgnoreCase);
+
+ static void AssertIntermediateOutputsExist(IReadOnlyList intermediateOutputRoots)
+ {
+ foreach (var intermediateOutputRoot in intermediateOutputRoots)
+ {
+ var fontsDir = Path.Combine(intermediateOutputRoot, "resizetizer", "f");
+ Assert.True(File.Exists(Path.Combine(fontsDir, "OpenSans-Regular.ttf")),
+ $"Missing OpenSans-Regular.ttf in {fontsDir}.");
+ Assert.True(File.Exists(Path.Combine(fontsDir, "OpenSans-Semibold.ttf")),
+ $"Missing OpenSans-Semibold.ttf in {fontsDir}.");
+
+ if (!ContainsTargetFramework(intermediateOutputRoot, $"{DotNetCurrent}-maccatalyst"))
+ {
+ var splashDir = Path.Combine(intermediateOutputRoot, "resizetizer", "sp");
+ Assert.True(
+ Directory.Exists(splashDir) && Directory.EnumerateFiles(splashDir, "*", SearchOption.AllDirectories).Any(),
+ $"Missing generated splash screen files in {splashDir}.");
+ }
+ }
+ }
+
+ static void DeleteDirectory(string path)
+ {
+ if (Directory.Exists(path))
+ Directory.Delete(path, recursive: true);
+ }
+
+ static void AssertTargetSkipped(string binlogPath, string targetName, int minimumSkipCount)
+ {
+ var skipCount = new BinLogReader()
+ .ReadRecords(binlogPath)
+ .Count(record => record.Args is BuildMessageEventArgs { Message: string message } &&
+ message.Contains($"Skipping target \"{targetName}\"", StringComparison.Ordinal) &&
+ message.Contains("because all output files are up-to-date", StringComparison.OrdinalIgnoreCase));
+
+ Assert.True(skipCount >= minimumSkipCount,
+ $"Expected target '{targetName}' to be skipped at least {minimumSkipCount} times, but found {skipCount}. See binlog: {binlogPath}");
+ }
}