diff --git a/src/Resizetizer/src/AndroidAdaptiveIconGenerator.cs b/src/Resizetizer/src/AndroidAdaptiveIconGenerator.cs index 8fc47fb..1f6cf46 100644 --- a/src/Resizetizer/src/AndroidAdaptiveIconGenerator.cs +++ b/src/Resizetizer/src/AndroidAdaptiveIconGenerator.cs @@ -51,7 +51,7 @@ public IEnumerable Generate() void ProcessBackground(List results, DirectoryInfo fullIntermediateOutputPath) { var backgroundFile = Info.Filename; - var backgroundExists = File.Exists(backgroundFile); + var (backgroundExists, backgroundModified) = Utils.FileExists(backgroundFile); var backgroundDestFilename = AppIconName + "_background.png"; if (backgroundExists) @@ -67,8 +67,16 @@ void ProcessBackground(List results, DirectoryInfo fullInterme { var dir = Path.Combine(fullIntermediateOutputPath.FullName, dpi.Path); var destination = Path.Combine(dir, backgroundDestFilename); + var (destinationExists, destinationModified) = Utils.FileExists(destination); Directory.CreateDirectory(dir); + if (destinationModified > backgroundModified) + { + Logger.Log($"Skipping `{backgroundFile}` => `{destination}` file is up to date."); + results.Add(new ResizedImageInfo { Dpi = dpi, Filename = destination }); + continue; + } + Logger.Log($"App Icon Background Part: " + destination); if (backgroundExists) @@ -91,7 +99,7 @@ void ProcessBackground(List results, DirectoryInfo fullInterme void ProcessForeground(List results, DirectoryInfo fullIntermediateOutputPath) { var foregroundFile = Info.ForegroundFilename; - var foregroundExists = File.Exists(foregroundFile); + var (foregroundExists, foregroundModified) = Utils.FileExists(foregroundFile); var foregroundDestFilename = AppIconName + "_foreground.png"; if (foregroundExists) @@ -107,8 +115,16 @@ void ProcessForeground(List results, DirectoryInfo fullInterme { var dir = Path.Combine(fullIntermediateOutputPath.FullName, dpi.Path); var destination = Path.Combine(dir, foregroundDestFilename); + var (destinationExists, destinationModified) = Utils.FileExists(destination); Directory.CreateDirectory(dir); + if (destinationModified > foregroundModified) + { + Logger.Log($"Skipping `{foregroundFile}` => `{destination}` file is up to date."); + results.Add(new ResizedImageInfo { Dpi = dpi, Filename = destination }); + continue; + } + Logger.Log($"App Icon Foreground Part: " + destination); if (foregroundExists) @@ -130,14 +146,21 @@ void ProcessForeground(List results, DirectoryInfo fullInterme void ProcessAdaptiveIcon(List results, DirectoryInfo fullIntermediateOutputPath) { - var adaptiveIconXmlStr = AdaptiveIconDrawableXml - .Replace("{name}", AppIconName); - var dir = Path.Combine(fullIntermediateOutputPath.FullName, "mipmap-anydpi-v26"); var adaptiveIconDestination = Path.Combine(dir, AppIconName + ".xml"); var adaptiveIconRoundDestination = Path.Combine(dir, AppIconName + "_round.xml"); Directory.CreateDirectory(dir); + if (File.Exists(adaptiveIconDestination) && File.Exists(adaptiveIconRoundDestination)) + { + results.Add(new ResizedImageInfo { Dpi = new DpiPath("mipmap-anydpi-v26", 1), Filename = adaptiveIconDestination }); + results.Add(new ResizedImageInfo { Dpi = new DpiPath("mipmap-anydpi-v26", 1, "_round"), Filename = adaptiveIconRoundDestination }); + return; + } + + var adaptiveIconXmlStr = AdaptiveIconDrawableXml + .Replace("{name}", AppIconName); + // Write out the adaptive icon xml drawables File.WriteAllText(adaptiveIconDestination, adaptiveIconXmlStr); File.WriteAllText(adaptiveIconRoundDestination, adaptiveIconXmlStr); diff --git a/src/Resizetizer/src/AppleIconAssetsGenerator.cs b/src/Resizetizer/src/AppleIconAssetsGenerator.cs index 160c21d..da38e0a 100644 --- a/src/Resizetizer/src/AppleIconAssetsGenerator.cs +++ b/src/Resizetizer/src/AppleIconAssetsGenerator.cs @@ -43,6 +43,17 @@ public IEnumerable Generate() var assetContentsFile = Path.Combine(outputAssetsDir, "Contents.json"); var appIconSetContentsFile = Path.Combine(outputAppIconSetDir, "Contents.json"); + var (sourceExists, sourceModified) = Utils.FileExists(Info.Filename); + var (destinationExists, destinationModified) = Utils.FileExists(appIconSetContentsFile); + + if (destinationModified > sourceModified) + { + Logger.Log($"Skipping `{Info.Filename}` => `{appIconSetContentsFile}` file is up to date."); + return new List { + new ResizedImageInfo { Dpi = new DpiPath("", 1), Filename = appIconSetContentsFile } + }; + } + var infoJsonProp = new JsonObject { ["info"] = new JsonObject diff --git a/src/Resizetizer/src/ResizetizeImages.cs b/src/Resizetizer/src/ResizetizeImages.cs index 15ed2dc..db1e379 100644 --- a/src/Resizetizer/src/ResizetizeImages.cs +++ b/src/Resizetizer/src/ResizetizeImages.cs @@ -233,8 +233,17 @@ void ProcessAppIcon(ResizeImageInfo img, ConcurrentBag resized var destination = Resizer.GetFileDestination(img, dpi, iconPath) .Replace("{name}", appIconName); + var (sourceExists, sourceModified) = Utils.FileExists(img.Filename); + var (destinationExists, destinationModified) = Utils.FileExists(destination); + LogDebugMessage($"App Icon Destination: " + destination); + if (destinationModified > sourceModified) + { + Logger.Log($"Skipping `{img.Filename}` => `{destination}` file is up to date."); + continue; + } + appTool.Resize(dpi, Path.ChangeExtension(destination, ".png")); } } diff --git a/src/Resizetizer/src/Utils.cs b/src/Resizetizer/src/Utils.cs index a6b7497..af5af76 100644 --- a/src/Resizetizer/src/Utils.cs +++ b/src/Resizetizer/src/Utils.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Text.RegularExpressions; using SkiaSharp; @@ -66,11 +67,20 @@ public static ResizedImageInfo GenerateIcoFile(string intermediateOutputPath, IL string destination = Path.Combine(destinationFolder, $"{fileName}.ico"); Directory.CreateDirectory(destinationFolder); + var (sourceExists, sourceModified) = FileExists(info.Filename); + var (destinationExists, destinationModified) = FileExists(destination); + logger.Log($"Generating ICO: {destination}"); var tools = new SkiaSharpAppIconTools(info, logger); var dpi = new DpiPath(fileName, 1.0m, size: new SKSize(64, 64)); + if (destinationModified > sourceModified) + { + logger.Log($"Skipping `{info.Filename}` => `{destination}` file is up to date."); + return new ResizedImageInfo { Dpi = dpi, Filename = destination }; + } + MemoryStream memoryStream = new MemoryStream(); tools.Resize(dpi, destination, () => memoryStream); memoryStream.Position = 0; @@ -111,5 +121,12 @@ public static string SkiaColorWithoutAlpha(SKColor? skColor) result = result.Substring(3); return "#" + result; } + + public static (bool Exists, DateTime Modified) FileExists(string path) + { + var exists = File.Exists(path); + var modified = exists ? File.GetLastWriteTimeUtc(path) : DateTime.MinValue; + return (exists, modified); + } } } diff --git a/src/Resizetizer/test/UnitTests/ResizetizeImagesTests.cs b/src/Resizetizer/test/UnitTests/ResizetizeImagesTests.cs index 1b7c80b..d42d909 100644 --- a/src/Resizetizer/test/UnitTests/ResizetizeImagesTests.cs +++ b/src/Resizetizer/test/UnitTests/ResizetizeImagesTests.cs @@ -1055,6 +1055,38 @@ public void ColorsInCssCanBeUsed() AssertFileContains("not_working.scale-100.png", 0xFF71559B, 2, 6); } + + [Theory] + [InlineData("android")] + [InlineData("uwp")] + [InlineData("ios")] + [InlineData("wasm")] + [InlineData("wpf")] + public void GenerationSkippedOnIncrementalBuild(string platform) + { + var items = new[] + { + new TaskItem("images/dotnet_logo.svg", new Dictionary + { + ["IsAppIcon"] = bool.TrueString, + ["ForegroundFile"] = $"images/dotnet_foreground.svg", + ["Link"] = "appicon", + ["BackgroundFile"] = $"images/dotnet_background.svg", + }), + }; + + var task = GetNewTask(platform, items); + var success = task.Execute(); + Assert.True(success, LogErrorEvents.FirstOrDefault()?.Message); + + LogErrorEvents.Clear(); + LogMessageEvents.Clear(); + task = GetNewTask(platform, items); + success = task.Execute(); + Assert.True(success, LogErrorEvents.FirstOrDefault()?.Message); + + Assert.True(LogMessageEvents.Any(x => x.Message.Contains("Skipping ", StringComparison.OrdinalIgnoreCase)), $"Image generation should have been skipped."); + } } public class ExecuteForAny : ExecuteForApp