Conversation
Signed-off-by: paulober <44974737+paulober@users.noreply.github.com>
- Add .icon glob patterns to ImageAsset auto-include and BundleResource exclusion in Microsoft.Sdk.DefaultItems.template.props - Fix tvOS support: register .icon assets in both brandAssetsInAssets and imageStacksInAssets for primary/alternate icon validation - Add tvOS test case to IconFileSupport - Add tests: IconFileSupportWithIncludeAllAppIcons, IconFileSupportAsAlternateIcon, InexistentIconFile, MixedXCAssetsAndIconFile - Update DefaultCompilationIncludes.md and build-items.md documentation Fixes #24132
Add a test project that uses .icon (Xcode Icon Composer) directories instead of .xcassets for app icons. The test builds the project for all platforms and verifies that raw .icon files don't end up in the app bundle as BundleResources (they should be processed by actool).
Add a basic .xcassets directory alongside .icon in the test project so the build can succeed even when actool's icon export subprocess fails (status 255 on some systems). The test gracefully skips with Assert.Ignore when icon export is not supported.
The icon.json format used 'version'+'layers'+'filename' but the correct Icon Composer format uses 'groups' containing 'layers' with 'image-name'. The old format caused actool's icon export subprocess to fail with status 255. With the correct format, actool successfully compiles .icon files into app icons on all platforms. Also removed the Images.xcassets fallback from the test project (no longer needed) and updated the integration test to assert success instead of skipping.
There was a problem hiding this comment.
Pull request overview
Adds build- and test-level support for Xcode Icon Composer (.icon folder) app icons so they’re treated like asset catalogs and can be validated/passed through to actool as the selected AppIcon (including alternate icons).
Changes:
- Update
ACToolto treat.iconfolders as asset catalogs and recognizeicon.jsonalongsideContents.json. - Extend default MSBuild item globs/docs to include
.icondirectories asImageAsset(and exclude them fromBundleResource). - Add MSBuild task tests and a new dotnet test app/project to exercise
.iconhandling.
Reviewed changes
Copilot reviewed 21 out of 29 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs |
Recognizes .icon directories and icon.json so .icon-based AppIcon/alternate icons can pass validation and be compiled via actool. |
dotnet/targets/Microsoft.Sdk.DefaultItems.template.props |
Includes .icon/** in default ImageAsset globs and excludes .icon content from default BundleResource inclusion. |
dotnet/DefaultCompilationIncludes.md |
Documents default inclusion behavior for .icon (Icon Composer) directories. |
docs/building-apps/build-items.md |
Documents that ImageAsset includes both .xcassets and .icon inputs. |
tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ACToolTaskTest.cs |
Adds task-level tests covering .icon validation scenarios (main icon, include-all, alternate icon, missing icon, mixed catalogs). |
tests/dotnet/UnitTests/AppIconTest.cs |
Adds a unit test project build validating .icon inputs are not copied as raw bundle resources. |
tests/dotnet/AppWithComposerIcon/shared.mk |
Shared make configuration for the new composer-icon test app. |
tests/dotnet/AppWithComposerIcon/shared.csproj |
Shared project settings enabling AppIcon=AppIcon for the new composer-icon test app. |
tests/dotnet/AppWithComposerIcon/AppDelegate.cs |
Minimal app entry point for the composer-icon test app. |
tests/dotnet/AppWithComposerIcon/iOS/Makefile |
Platform makefile for iOS composer-icon test app. |
tests/dotnet/AppWithComposerIcon/iOS/AppWithComposerIcon.csproj |
iOS target project for composer-icon test app. |
tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/icon.json |
iOS Icon Composer metadata fixture. |
tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/Assets/front.png |
iOS Icon Composer asset fixture. |
tests/dotnet/AppWithComposerIcon/iOS/Resources/AppIcon.icon/Assets/back.png |
iOS Icon Composer asset fixture. |
tests/dotnet/AppWithComposerIcon/tvOS/Makefile |
Platform makefile for tvOS composer-icon test app. |
tests/dotnet/AppWithComposerIcon/tvOS/AppWithComposerIcon.csproj |
tvOS target project for composer-icon test app. |
tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/icon.json |
tvOS Icon Composer metadata fixture. |
tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/Assets/front.png |
tvOS Icon Composer asset fixture. |
tests/dotnet/AppWithComposerIcon/tvOS/Resources/AppIcon.icon/Assets/back.png |
tvOS Icon Composer asset fixture. |
tests/dotnet/AppWithComposerIcon/MacCatalyst/Makefile |
Platform makefile for Mac Catalyst composer-icon test app. |
tests/dotnet/AppWithComposerIcon/MacCatalyst/AppWithComposerIcon.csproj |
Mac Catalyst target project for composer-icon test app. |
tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/icon.json |
Mac Catalyst Icon Composer metadata fixture. |
tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/Assets/front.png |
Mac Catalyst Icon Composer asset fixture. |
tests/dotnet/AppWithComposerIcon/MacCatalyst/Resources/AppIcon.icon/Assets/back.png |
Mac Catalyst Icon Composer asset fixture. |
tests/dotnet/AppWithComposerIcon/macOS/Makefile |
Platform makefile for macOS composer-icon test app. |
tests/dotnet/AppWithComposerIcon/macOS/AppWithComposerIcon.csproj |
macOS target project for composer-icon test app. |
tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/icon.json |
macOS Icon Composer metadata fixture. |
tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/Assets/front.png |
macOS Icon Composer asset fixture. |
tests/dotnet/AppWithComposerIcon/macOS/Resources/AppIcon.icon/Assets/back.png |
macOS Icon Composer asset fixture. |
| // actool may fail on the placeholder .icon content, but the validation phase should pass | ||
| actool.Execute (); | ||
|
|
||
| // Verify that no error was logged about the icon not being found | ||
| var errorMessages = Engine.Logger.ErrorEvents.Select (e => e.Message).ToList (); | ||
| Assert.IsFalse (errorMessages.Any (m => m.Contains ("Can't find the AppIcon")), | ||
| "Should not report that AppIcon is not found among image resources"); |
There was a problem hiding this comment.
These new tests call actool.Execute () directly and then only assert that a specific validation error message wasn't logged. This can allow unrelated task failures (including unexpected logged errors) to slip through without failing the test, and it also bypasses the ExecuteTask helper that surfaces logged events on failure. Consider using ExecuteTask with an assertion strategy that still tolerates actool compilation failing, or at least assert on the task return value and/or that any errors are only the expected actool/tooling ones (while still ensuring the AppIcon/AlternateAppIcon validation passes).
| Assert.That (frontPngInBundle, Does.Not.Exist, "front.png should not be in the app bundle as a raw BundleResource"); | ||
|
|
||
| var backPngInBundle = Path.Combine (resourcesDirectory, "AppIcon.icon", "Assets", "back.png"); | ||
| Assert.That (backPngInBundle, Does.Not.Exist, "back.png should not be in the app bundle as a raw BundleResource"); |
There was a problem hiding this comment.
This new test verifies that the raw .icon inputs aren't copied as BundleResources, but it doesn't assert that asset compilation actually produced an Assets.car in the app bundle. Without that assertion, the test could pass even if the icon catalog was ignored (resulting in missing app icons). Consider asserting Assets.car exists (as TestXCAssetsImpl does) and/or validating that the compiled assets contain at least the expected icon entry.
| Assert.That (backPngInBundle, Does.Not.Exist, "back.png should not be in the app bundle as a raw BundleResource"); | |
| Assert.That (backPngInBundle, Does.Not.Exist, "back.png should not be in the app bundle as a raw BundleResource"); | |
| // Verify that the compiled asset catalog exists in the app bundle | |
| string? assetsCarPath = null; | |
| foreach (var file in Directory.GetFiles (resourcesDirectory, "Assets.car", SearchOption.AllDirectories)) { | |
| assetsCarPath = file; | |
| break; | |
| } | |
| Assert.That (assetsCarPath, Is.Not.Null, "Compiled asset catalog (Assets.car) should exist in the app bundle"); |
| public class AppDelegate : UIApplicationDelegate { | ||
| public override bool FinishedLaunching (UIApplication app, NSDictionary options) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
| #endif | ||
|
|
||
| public class Program { | ||
| static int Main (string [] args) | ||
| { | ||
| #if __MACCATALYST__ || __MACOS__ | ||
| GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly | ||
|
|
||
| Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); | ||
|
|
||
| return args.Length; | ||
| #else | ||
| UIApplication.Main (args, null, typeof (AppDelegate)); | ||
| return 0; | ||
| #endif | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
AppDelegate.cs is not formatted consistently with the rest of the test apps in this repo (missing tab indentation and standard brace layout). Please reformat to match the existing style (see tests/dotnet/AppWithXCAssets/AppDelegate.cs for an in-repo example) so that future diffs remain readable and editors can apply the repo's .editorconfig settings cleanly.
| public class AppDelegate : UIApplicationDelegate { | |
| public override bool FinishedLaunching (UIApplication app, NSDictionary options) | |
| { | |
| return true; | |
| } | |
| } | |
| #endif | |
| public class Program { | |
| static int Main (string [] args) | |
| { | |
| #if __MACCATALYST__ || __MACOS__ | |
| GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly | |
| Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); | |
| return args.Length; | |
| #else | |
| UIApplication.Main (args, null, typeof (AppDelegate)); | |
| return 0; | |
| #endif | |
| } | |
| } | |
| } | |
| public class AppDelegate : UIApplicationDelegate { | |
| public override bool FinishedLaunching (UIApplication app, NSDictionary options) | |
| { | |
| return true; | |
| } | |
| } | |
| #endif | |
| public class Program { | |
| static int Main (string [] args) | |
| { | |
| #if __MACCATALYST__ || __MACOS__ | |
| GC.KeepAlive (typeof (NSObject)); // prevent linking away the platform assembly | |
| Console.WriteLine (Environment.GetEnvironmentVariable ("MAGIC_WORD")); | |
| return args.Length; | |
| #else | |
| UIApplication.Main (args, null, typeof (AppDelegate)); | |
| return 0; | |
| #endif | |
| } | |
| } | |
| } |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ [PR Build #40f00db] Build passed (Detect API changes) ✅Pipeline on Agent |
✅ [CI Build #40f00db] Build passed (Build packages) ✅Pipeline on Agent |
✅ API diff for current PR / commitNET (empty diffs)✅ API diff vs stableNET (empty diffs)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
✅ [CI Build #40f00db] Build passed (Build macOS tests) ✅Pipeline on Agent |
💻 [CI Build #40f00db] Tests on macOS X64 - Mac Sonoma (14) passed 💻✅ All tests on macOS X64 - Mac Sonoma (14) passed. Pipeline on Agent |
💻 [CI Build #40f00db] Tests on macOS M1 - Mac Monterey (12) passed 💻✅ All tests on macOS M1 - Mac Monterey (12) passed. Pipeline on Agent |
💻 [CI Build #40f00db] Tests on macOS arm64 - Mac Sequoia (15) passed 💻✅ All tests on macOS arm64 - Mac Sequoia (15) passed. Pipeline on Agent |
💻 [CI Build #40f00db] Tests on macOS M1 - Mac Ventura (13) passed 💻✅ All tests on macOS M1 - Mac Ventura (13) passed. Pipeline on Agent |
💻 [CI Build #40f00db] Tests on macOS arm64 - Mac Tahoe (26) passed 💻✅ All tests on macOS arm64 - Mac Tahoe (26) passed. Pipeline on Agent |
🔥 [CI Build #40f00db] Test results 🔥Test results❌ Tests failed on VSTS: test results 0 tests crashed, 1 tests failed, 130 tests passed. Failures❌ linker tests1 tests failed, 43 tests passed.Failed tests
Html Report (VSDrops) Download Successes✅ cecil: All 1 tests passed. Html Report (VSDrops) Download Pipeline on Agent |
Add support for Icon Composer (.icon) app icons
This adds support for Xcode Icon Composer based
.iconfolders introduced in macOS 26 Tahoe's Liquid Glass design system (see #24132).Changes
.iconfolders: Treats.icondirectories as asset catalogs alongside.xcassetsicon.jsonfiles: Handlesicon.jsonmetadata files in addition toContents.json--app-iconflag:.icon-based icons now pass validation and get the--app-iconflag passed toactoolIncludeAllAppIconsfor runtime icon switchingBackground
The new
.iconformat is a folder structure containing:icon.json- Icon metadata (layers, materials, effects)Assets/subfolder - Vector graphics and image assetsThis replaces static
.icnsfiles to support Liquid Glass features like translucency, specular lighting, and cross-platform rendering.Usage
Add
.iconfolders to your project:The build system will:
.iconfolder as a valid app iconactoolwith the--app-icon AppIconflagAssets.carTesting
I could not figure out based on the docs how to get it installed locally to test it with my MAUI app. Advices would be appreciated.
This is a recreation of #24476 (from @paulober), because our CI can't work with pull requests from forks.