Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8696c89
Enable Resizetizer external backends
Copilot Jul 18, 2026
f94cfe0
Track external backend images in FileWrites
Copilot Jul 18, 2026
b55f592
Strengthen Resizetizer external backend tests
Copilot Jul 18, 2026
ec48b80
Add late-import regression coverage for external Resizetizer backends
Copilot Jul 18, 2026
de0c762
Keep late Resizetizer preparation targets independent
Copilot Jul 18, 2026
326f64a
Remove duplicate platform FileWrites entries
Copilot Jul 18, 2026
61c131a
Gate Resizetizer hooks and overwrite test output
Copilot Jul 18, 2026
45c7e9c
Address review: incremental fallback from outputs file; ProcessMauiAs…
Copilot Jul 18, 2026
546a585
Fix zero-image Resizetizer incrementality
Copilot Jul 19, 2026
9019808
Deduplicate processed font outputs
Copilot Jul 19, 2026
7eefa8f
Exclude stale processed font outputs
Copilot Jul 19, 2026
da316d8
Preserve processed fonts on no-op builds
Copilot Jul 19, 2026
c7d518f
Fix Tizen Resizetizer output tracking
Copilot Jul 19, 2026
dfb0539
Recover missing Resizetizer output lists
Copilot Jul 19, 2026
fa4720c
Merge net11.0 into Resizetizer extensibility PR
Copilot Jul 25, 2026
7fef2c1
Format merged Resizetizer integration tests
Copilot Jul 25, 2026
5ee75fb
Make Resizetizer no-op test deterministic
Copilot Jul 25, 2026
8d63ac1
Fix Resizetizer incremental-build regression: advance outputs-file mt…
kubaflo Jul 27, 2026
2eefa6d
Heal missing processed font outputs
Copilot Jul 28, 2026
4f7ec2f
Merge branch 'net11.0' into kubaflo/35022-resizetizer-extensibility
kubaflo Jul 28, 2026
881f5ca
Fix font plist cache recovery
Copilot Jul 28, 2026
500cc32
Merge net11.0 into PR #36653
Copilot Jul 29, 2026
26f818e
Harden Resizetizer timestamp sentinels
Copilot Jul 29, 2026
8145eba
Harden Resizetizer timestamp comparisons
Copilot Jul 29, 2026
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
44 changes: 36 additions & 8 deletions src/SingleProject/Resizetizer/src/DpiPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,34 @@ public static DpiPath[] SplashScreen

}

public static class Generic
{
public static DpiPath Original =>
new DpiPath("", 1.0m);

public static DpiPath[] Image
=> new[]
{
new DpiPath("", 1.0m),
new DpiPath("", 2.0m, null, "@2x"),
};

public static DpiPath[] AppIcon
=> new[]
{
new DpiPath("", 1.0m, null, "-16x16", new SKSize(16, 16)),
new DpiPath("", 1.0m, null, "-32x32", new SKSize(32, 32)),
new DpiPath("", 1.0m, null, "-48x48", new SKSize(48, 48)),
new DpiPath("", 1.0m, null, "-128x128", new SKSize(128, 128)),
new DpiPath("", 1.0m, null, "-256x256", new SKSize(256, 256)),
new DpiPath("", 1.0m, null, "-512x512", new SKSize(512, 512)),
new DpiPath("", 1.0m, null, "-1024x1024", new SKSize(1024, 1024)),
};
}

public static DpiPath GetOriginal(string platform)
{
switch (platform.ToLowerInvariant())
switch ((platform ?? string.Empty).ToLowerInvariant())
Comment thread
kubaflo marked this conversation as resolved.
{
case "ios":
return DpiPath.Ios.Original;
Expand All @@ -326,14 +351,14 @@ public static DpiPath GetOriginal(string platform)
return DpiPath.Wpf.Original;
case "tizen":
return DpiPath.Tizen.Original;
default:
return DpiPath.Generic.Original;
}

return null;
}

public static DpiPath[] GetDpis(string platform)
{
switch (platform.ToLowerInvariant())
switch ((platform ?? string.Empty).ToLowerInvariant())
{
case "ios":
return DpiPath.Ios.Image;
Expand All @@ -345,16 +370,16 @@ public static DpiPath[] GetDpis(string platform)
return DpiPath.Wpf.Image;
case "tizen":
return DpiPath.Tizen.Image;
default:
return DpiPath.Generic.Image;
}

return null;
}

public static DpiPath[] GetAppIconDpis(string platform, string appIconName)
{
DpiPath[] result = null;
DpiPath[] result;

switch (platform.ToLowerInvariant())
switch ((platform ?? string.Empty).ToLowerInvariant())
{
case "ios":
result = DpiPath.Ios.AppIcon;
Expand All @@ -371,6 +396,9 @@ public static DpiPath[] GetAppIconDpis(string platform, string appIconName)
case "tizen":
result = DpiPath.Tizen.AppIcon;
break;
default:
result = DpiPath.Generic.AppIcon;
Comment thread
kubaflo marked this conversation as resolved.
break;
}

foreach (var r in result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,28 @@

<PropertyGroup Condition="'$(_ResizetizerIsAndroidApp)' == 'True' Or '$(_ResizetizerIsiOSApp)' == 'True' Or '$(_ResizetizerIsWPFApp)' == 'True' Or '$(_ResizetizerIsWindowsAppSdk)' == 'True' Or '$(_ResizetizerIsTizenApp)' == 'True'">
<_ResizetizerIsCompatibleApp>True</_ResizetizerIsCompatibleApp>
</PropertyGroup>

<!-- External backends opt in by setting ResizetizerPlatformType in their targets. -->
<PropertyGroup Condition="'$(_ResizetizerIsCompatibleApp)' != 'True' And '$(ResizetizerPlatformType)' != ''">
Comment thread
kubaflo marked this conversation as resolved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic — External opt-in schedules splash processing but silently produces no output

Flagged by: 2/3 reviewers after dispute

This new compatibility branch also activates the existing ResizetizeDependsOnTargets, which includes ProcessMauiSplashScreens. For an external ResizetizerPlatformType, however, every splash generator in that target is conditioned on a built-in platform flag; none runs, yet the target still touches _MauiSplashStampFile. There is also no MauiProcessedSplashScreen or post-processing hook. The result is a successful-looking build that drops the configured splash output.

Issue #35022 explicitly lists "MauiSplashScreen processed by the same pipeline" as a success criterion. Add a generic splash-processing/output contract and backend hook with regression coverage, or avoid marking external projects compatible with this stage and narrow the linked issue/PR contract accordingly.

<_ResizetizerIsCompatibleApp>True</_ResizetizerIsCompatibleApp>
</PropertyGroup>

<PropertyGroup Condition="'$(_ResizetizerIsCompatibleApp)' == 'True'">
<ResizetizeDependsOnTargets>
$(ResizetizeDependsOnTargets);
ResizetizeCollectItems;
ProcessMauiSplashScreens;
_ReadResizetizeImagesOutputs;
</ResizetizeDependsOnTargets>
<!-- Ensures project-reference MauiAsset items are collected before ProcessMauiAssets
for backends that opt in at evaluation time (ResizetizerPlatformType set in props
or the project body). The execution-time _PrepareExternalMauiAssets fallback only
handles the late-import case where _ResizetizerIsCompatibleApp was not yet True. -->
<ProcessMauiAssetsDependsOnTargets>
$(ProcessMauiAssetsDependsOnTargets);
ResizetizeCollectItems;
</ProcessMauiAssetsDependsOnTargets>
<ProcessMauiFontsDependsOnTargets>
$(ProcessMauiFontsDependsOnTargets);
ResizetizeCollectItems;
Expand Down Expand Up @@ -252,9 +267,9 @@
</Target>


<!-- Collect images from referenced projects -->
<!-- Collect MAUI resources from referenced projects -->
<Target Name="ResizetizeCollectItems"
Condition="'$(_ResizetizerIsCompatibleApp)' == 'True' And '$(DisableResizetizer)' != 'true'"
Condition="'$(DisableResizetizer)' != 'true'"
BeforeTargets="$(ResizetizeCollectItemsBeforeTargets)"
AfterTargets="$(ResizetizeCollectItemsAfterTargets)">

Comment thread
kubaflo marked this conversation as resolved.
Expand All @@ -264,7 +279,7 @@
ItemName="_ImportedMauiItem" />
</CallTarget>

<!-- Invoke the GetMauiItems target on all project references. This will accumulate images into our MauiImage group -->
<!-- Invoke GetMauiItems on all project references to accumulate supported MAUI resource items. -->
<ItemGroup>
<!-- Filter out the items missing the OriginalProjectReferenceItemSpec because this is essential -->
<_ResizetizeCollectItemsProjectWithOIS Include="@(_ResolvedProjectReferencePaths->HasMetadata('OriginalProjectReferenceItemSpec'))" />
Expand Down Expand Up @@ -359,8 +374,29 @@
</ItemGroup>
</Target>

<!--
A backend may set ResizetizerPlatformType in a .targets file imported after
this file. In that case the evaluation-time dependency properties above were
not populated, so wire the required prerequisites at execution time.
-->
<Target Name="_PrepareExternalMauiAssets"
BeforeTargets="ProcessMauiAssets"
DependsOnTargets="ResizetizeCollectItems"
Condition="'$(_ResizetizerIsCompatibleApp)' != 'True' And '$(ResizetizerPlatformType)' != ''" />
Comment thread
kubaflo marked this conversation as resolved.

<Target Name="_PrepareExternalMauiFonts"
BeforeTargets="ProcessMauiFonts"
DependsOnTargets="ResizetizeCollectItems;ProcessMauiAssets;ProcessMauiSplashScreens"
Condition="'$(_ResizetizerIsCompatibleApp)' != 'True' And '$(ResizetizerPlatformType)' != ''" />

<Target Name="_PrepareExternalMauiImages"
BeforeTargets="ResizetizeImages"
DependsOnTargets="ResizetizeCollectItems;ProcessMauiSplashScreens;_ReadResizetizeImagesOutputs"
Condition="'$(_ResizetizerIsCompatibleApp)' != 'True' And '$(ResizetizerPlatformType)' != ''" />

<Target Name="ProcessMauiAssets"
Condition="'$(EnableMauiAssetProcessing)' == 'true'">
Condition="'$(EnableMauiAssetProcessing)' == 'true'"
DependsOnTargets="$(ProcessMauiAssetsDependsOnTargets)">
<ItemGroup>
<!--
The GetMauiAssetPath task uses the Link metadata or the ItemSpec, so make
Expand All @@ -375,14 +411,15 @@
<_MauiAssetItemMetadata Condition="'$(_ResizetizerIsiOSApp)' == 'True'">Link</_MauiAssetItemMetadata>
<_MauiAssetItemMetadata Condition="'$(_ResizetizerIsWindowsAppSdk)' == 'True'">TargetPath</_MauiAssetItemMetadata>
<_MauiAssetItemMetadata Condition="'$(_ResizetizerIsTizenApp)' == 'True'">TizenTpkFileName</_MauiAssetItemMetadata>
<_MauiAssetItemMetadata Condition="'$(_MauiAssetItemMetadata)' == ''">Link</_MauiAssetItemMetadata>
Comment thread
kubaflo marked this conversation as resolved.
</PropertyGroup>
<GetMauiAssetPath
ProjectDirectory="$(MSBuildProjectDirectory)"
ItemMetadata="$(_MauiAssetItemMetadata)"
Input="@(_MauiAssetWithLinkMetadata)">
<Output ItemName="_MauiAssetItemWithMetadata" TaskParameter="Output" />
</GetMauiAssetPath>
<!-- add the processed assets to the correct itme groups and add any additional properties -->
<!-- add the processed assets to the correct item groups and add any additional properties -->
<ItemGroup Condition="'$(_ResizetizerIsAndroidApp)' == 'True'">
<AndroidAsset Include="@(_MauiAssetItemWithMetadata)" />
</ItemGroup>
Expand All @@ -395,8 +432,17 @@
<ItemGroup Condition="'$(_ResizetizerIsTizenApp)' == 'True'">
<TizenResource Include="@(_MauiAssetItemWithMetadata)" />
</ItemGroup>
<ItemGroup>
<MauiProcessedAsset Include="@(_MauiAssetItemWithMetadata)" />
</ItemGroup>
</Target>

<!-- External backends add packaging targets to ResizetizerAfterAssetProcessingTargets. -->
<Target Name="ResizetizerAfterAssetProcessing"
AfterTargets="ProcessMauiAssets"
DependsOnTargets="$(ResizetizerAfterAssetProcessingTargets)"
Condition="'$(EnableMauiAssetProcessing)' == 'true' And '$(ResizetizerAfterAssetProcessingTargets)' != ''" />

<Target Name="ProcessMauiSplashScreens"
Condition="'$(EnableMauiSplashScreenProcessing)' == 'true'"
Inputs="$(MSBuildThisFileFullPath);$(_ResizetizerTaskAssemblyName);$(_MauiSplashInputsFile);@(MauiSplashScreen)"
Expand Down Expand Up @@ -533,6 +579,7 @@

<ItemGroup>
<_MauiFontCopied Include="$(_MauiIntermediateFonts)*" />
<MauiProcessedFont Include="@(MauiFont->'$(_MauiIntermediateFonts)%(Filename)%(Extension)'->Distinct())" />
Comment thread
kubaflo marked this conversation as resolved.
Comment thread
kubaflo marked this conversation as resolved.
</ItemGroup>

<!-- iOS -->
Expand Down Expand Up @@ -606,6 +653,12 @@
</ItemGroup>
</Target>

<!-- External backends add packaging targets to ResizetizerAfterFontProcessingTargets. -->
<Target Name="ResizetizerAfterFontProcessing"
AfterTargets="ProcessMauiFonts"
DependsOnTargets="$(ResizetizerAfterFontProcessingTargets)"
Condition="'$(EnableMauiFontProcessing)' == 'true' And '$(ResizetizerAfterFontProcessingTargets)' != ''" />

<Target Name="_ReadResizetizeImagesOutputs">
<ReadLinesFromFile File="$(_ResizetizerOutputsFile)" Condition="Exists ('$(_ResizetizerOutputsFile)')">
<Output TaskParameter="Lines" ItemName="_ResizetizerOutputs" />
Expand All @@ -615,7 +668,7 @@
<Target Name="ResizetizeImages"
Condition="'$(EnableMauiImageProcessing)' == 'true'"
Inputs="$(MSBuildThisFileFullPath);$(_ResizetizerTaskAssemblyName);$(_ResizetizerInputsFile);@(MauiImage)"
Outputs="$(_ResizetizerStampFile);@(_ResizetizerOutputs)"
Outputs="$(_ResizetizerStampFile);$(_ResizetizerOutputsFile);@(_ResizetizerOutputs)"
AfterTargets="$(ResizetizeAfterTargets)"
BeforeTargets="$(ResizetizeBeforeTargets)"
DependsOnTargets="$(ResizetizeDependsOnTargets)">
Expand All @@ -642,12 +695,15 @@
</ResizetizeImages>

<ItemGroup>
<!-- Get Images that were generated -->
<!-- Either from the task, or if the task was skipped (up to date), use the wildcard lookup -->
<!-- Fresh build: use the task output directly -->
<_ResizetizerCollectedImages Condition="'@(_CopiedResources->Count())' != '0'" Include="@(_CopiedResources)" />
<!-- Up-to-date path: restore from the persisted output list so that non-image artifacts
written into the intermediate folder by external backends (e.g. *.items files) are
never surfaced through MauiProcessedImage / _ResizetizerCollectedImages. -->
<_ResizetizerCollectedImages Condition="'@(_CopiedResources)' == '' And '@(_MauiImageToProcess)' != ''" Include="@(_ResizetizerOutputs)" />
Comment thread
kubaflo marked this conversation as resolved.
<!-- Wildcard is kept solely for stale-file detection and cleanup. -->
<_ResizetizerExistingImages Include="$(_MauiIntermediateImages)\**\*" />
<_ResizetizerImagesToDelete Include="@(_ResizetizerExistingImages->'%(FullPath)')" />
<_ResizetizerCollectedImages Condition="'@(_CopiedResources)' == ''" Include="@(_ResizetizerExistingImages->'%(FullPath)')" />
<_ResizetizerImagesToDelete Remove="@(_ResizetizerCollectedImages)" />
</ItemGroup>

Expand All @@ -657,9 +713,41 @@
Files="@(_ResizetizerImagesToDelete)"
/>

<ItemGroup>
<MauiProcessedImage Include="@(_ResizetizerCollectedImages)" />
Comment thread
kubaflo marked this conversation as resolved.
Comment thread
kubaflo marked this conversation as resolved.
</ItemGroup>
Comment thread
kubaflo marked this conversation as resolved.

<!-- Touch/create our stamp file for outputs -->
<Touch Files="$(_ResizetizerStampFile)" AlwaysCreate="True" />
<WriteLinesToFile
File="$(_ResizetizerOutputsFile)"
Lines="@(_ResizetizerCollectedImages->'%(Identity)')"
Overwrite="true"
WriteOnlyWhenDifferent="true"
Comment thread
kubaflo marked this conversation as resolved.
Outdated
/>

<!-- Include our images and stamp file as filewrites so they don't get rm'd -->
<ItemGroup>
<FileWrites Include="@(_ResizetizerCollectedImages)" />
<FileWrites Include="$(_ResizetizerStampFile)" />
<FileWrites Include="$(_ResizetizerOutputsFile)" />
</ItemGroup>
Comment thread
kubaflo marked this conversation as resolved.
</Target>

<!-- External backends add packaging targets to ResizetizerAfterImageProcessingTargets. -->
<Target Name="ResizetizerAfterImageProcessing"
AfterTargets="ResizetizeImages"
BeforeTargets="_ResizetizeInjectPlatformItems"
DependsOnTargets="$(ResizetizerAfterImageProcessingTargets)"
Condition="'$(EnableMauiImageProcessing)' == 'true' And '$(ResizetizerAfterImageProcessingTargets)' != ''" />

<Target Name="_ResizetizeInjectPlatformItems"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regression — Preserve the existing AfterTargets="ResizetizeImages" completion contract

Flagged by: 2/3 reviewers after dispute

This extraction makes _ResizetizeInjectPlatformItems a sibling AfterTargets="ResizetizeImages" target. Real MSBuild probes reproduced that sibling AfterTargets targets run in declaration/import order, so an existing consumer target declared in the project before the package import now executes first and observes empty BundleResource, ContentWithTargetPath, LibraryResourceDirectories, etc. Before this PR, injection happened inside ResizetizeImages, so every AfterTargets="ResizetizeImages" consumer necessarily ran after those items existed.

Keep ResizetizeImages as the compatibility completion target: move processing to a private inner target, run the new backend hook and built-in injection from that inner target, and let the public ResizetizeImages target complete only after injection. That preserves the new seam without changing legacy observer ordering.

AfterTargets="ResizetizeImages"
Condition="'$(EnableMauiImageProcessing)' == 'true' And ('$(_ResizetizerIsAndroidApp)' == 'True' Or '$(_ResizetizerIsiOSApp)' == 'True' Or '$(_ResizetizerIsWindowsAppSdk)' == 'True' Or '$(_ResizetizerIsWPFApp)' == 'True' Or '$(_ResizetizerIsTizenApp)' == 'True')">

<!-- iOS -->
<ItemGroup Condition="'$(_ResizetizerIsiOSApp)' == 'True'">
<!-- Batch the collectd items into BundleResource which iOS expects -->
<!-- Batch the collected items into BundleResource which iOS expects -->
<_ResizetizerCollectedBundleResourceImages Include="@(_ResizetizerCollectedImages->'%(FullPath)')">
<LogicalName>%(_ResizetizerCollectedImages.Filename)%(_ResizetizerCollectedImages.Extension)</LogicalName>
<TargetPath>%(_ResizetizerCollectedImages.Filename)%(_ResizetizerCollectedImages.Extension)</TargetPath>
Expand All @@ -675,8 +763,6 @@
<TargetPath>Assets.xcassets\$([System.IO.Path]::GetFileName($([System.IO.Path]::GetDirectoryName(%(_ResizetizerCollectedBundleResourceImages.Identity)))))\%(_ResizetizerCollectedBundleResourceImages.Filename)%(_ResizetizerCollectedBundleResourceImages.Extension)</TargetPath>
<Link>Assets.xcassets\$([System.IO.Path]::GetFileName($([System.IO.Path]::GetDirectoryName(%(_ResizetizerCollectedBundleResourceImages.Identity)))))\%(_ResizetizerCollectedBundleResourceImages.Filename)%(_ResizetizerCollectedBundleResourceImages.Extension)</Link>
</ImageAsset>

<FileWrites Include="@(_ResizetizerCollectedBundleResourceImages)" />
</ItemGroup>

<!-- Android -->
Expand All @@ -685,8 +771,6 @@
<LibraryResourceDirectories Condition="Exists ('$(_MauiIntermediateImages)')" Include="$(_MauiIntermediateImages)">
<StampFile>$(_ResizetizerStampFile)</StampFile>
</LibraryResourceDirectories>

<FileWrites Include="@(_ResizetizerCollectedImages)" />
</ItemGroup>

<!-- Windows App SDK -->
Expand All @@ -695,9 +779,6 @@
<TargetPath>%(_ResizetizerCollectedImages.Filename)%(_ResizetizerCollectedImages.Extension)</TargetPath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ContentWithTargetPath>

<FileWrites Include="@(_ResizetizerCollectedImages)" />

<_MauiAppIconFile Include="@(_ResizetizerCollectedImages)"
Condition="'%(Extension)' == '.ico'" />
</ItemGroup>
Expand All @@ -712,7 +793,6 @@
<LogicalName>%(_ResizetizerCollectedImages.Filename)%(_ResizetizerCollectedImages.Extension)</LogicalName>
<Link>%(_ResizetizerCollectedImages.Filename)%(_ResizetizerCollectedImages.Extension)</Link>
</Resource>
<FileWrites Include="@(_ResizetizerCollectedImages)" />
</ItemGroup>

<!-- Tizen -->
Expand All @@ -721,28 +801,13 @@
</PropertyGroup>
<ItemGroup Condition="'$(_ResizetizerIsTizenApp)' == 'True' And '@(_ResizetizerCollectedImages)' != ''">
<TizenTpkUserIncludeFiles Include="$(ResizetizerIntermediateOutputAbsolutePath)res\res.xml" TizenTpkSubDir="res\" />
<FileWrites Include="$(ResizetizerIntermediateOutputAbsolutePath)res\res.xml)" />
<FileWrites Include="$(ResizetizerIntermediateOutputAbsolutePath)res\res.xml" />

<TizenTpkUserIncludeFiles Include="@(_ResizetizerCollectedImages)">
Comment thread
kubaflo marked this conversation as resolved.
<TizenTpkSubDir>$([MSBuild]::MakeRelative($(ResizetizerIntermediateOutputAbsolutePath), $([System.IO.Path]::GetFullPath('%(_ResizetizerCollectedImages.RelativeDir)'))))</TizenTpkSubDir>
</TizenTpkUserIncludeFiles>
<FileWrites Include="@(_ResizetizerCollectedImages)" />
</ItemGroup>

<!-- Touch/create our stamp file for outputs -->
<Touch Files="$(_ResizetizerStampFile)" AlwaysCreate="True" />
<WriteLinesToFile
File="$(_ResizetizerOutputsFile)"
Lines="@(_ResizetizerCollectedImages->'%(Identity)')"
Overwrite="true"
WriteOnlyWhenDifferent="true"
/>

<!-- Include our images and stamp file as filewrites so they don't get rm'd -->
<ItemGroup>
<FileWrites Include="$(_ResizetizerStampFile)" />
<FileWrites Include="$(_ResizetizerOutputsFile)" />
</ItemGroup>
</Target>

<!--
Expand Down
Loading
Loading