Skip to content
34 changes: 19 additions & 15 deletions msbuild/Xamarin.MacDev.Tasks/Tasks/ACTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ void FindXCAssetsDirectory (string main, string secondary, out string mainResult
mainResult = main;
secondaryResult = secondary;

while (!string.IsNullOrEmpty (mainResult) && !mainResult.EndsWith (".xcassets", StringComparison.OrdinalIgnoreCase)) {
while (!string.IsNullOrEmpty (mainResult) && !mainResult.EndsWith (".xcassets", StringComparison.OrdinalIgnoreCase) && !mainResult.EndsWith (".icon", StringComparison.OrdinalIgnoreCase)) {
mainResult = Path.GetDirectoryName (mainResult)!;
if (!string.IsNullOrEmpty (secondaryResult))
secondaryResult = Path.GetDirectoryName (secondaryResult)!;
Expand Down Expand Up @@ -296,13 +296,13 @@ public override bool Execute ()
var vpath = BundleResource.GetVirtualProjectPath (this, imageAsset);
var catalogFullPath = imageAsset.GetMetadata ("FullPath");

// get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, etc)
// get the parent (which will typically be .appiconset, .launchimage, .imageset, .iconset, .icon, etc)
var catalog = Path.GetDirectoryName (vpath);
catalogFullPath = Path.GetDirectoryName (catalogFullPath);

var assetType = Path.GetExtension (catalog).TrimStart ('.');

// keep walking up the directory structure until we get to the .xcassets directory
// keep walking up the directory structure until we get to the .xcassets or .icon directory
FindXCAssetsDirectory (catalog, catalogFullPath, out var catalog2, out var catalogFullPath2);
catalog = catalog2;
catalogFullPath = catalogFullPath2;
Expand All @@ -329,11 +329,11 @@ public override bool Execute ()
continue;
}

// filter out everything except paths containing a Contents.json file since our main processing loop only cares about these
if (Path.GetFileName (vpath) != "Contents.json")
continue;

items.Add (asset);
// Handle both Contents.json (for .xcassets) and icon.json (for .icon folders)
var fileName = Path.GetFileName (vpath);
if (fileName == "Contents.json" || fileName == "icon.json") {
items.Add (asset);
}
}

// clone any *.xcassets dirs that need cloning
Expand Down Expand Up @@ -374,8 +374,9 @@ public override bool Execute ()

File.Copy (src, dest, true);

// filter out everything except paths containing a Contents.json file since our main processing loop only cares about these
if (Path.GetFileName (vpath) != "Contents.json")
// Handle both Contents.json (for .xcassets) and icon.json (for .icon folders)
var fileName = Path.GetFileName (vpath);
if (fileName != "Contents.json" && fileName != "icon.json")
continue;

item = new TaskItem (dest);
Expand All @@ -384,16 +385,17 @@ public override bool Execute ()
FindXCAssetsDirectory (Path.GetFullPath (dest), "", out var catalogFullPath, out var _);
items.Add (new AssetInfo (item, vpath, asset.Catalog, catalogFullPath, asset.AssetType));
} else {
// filter out everything except paths containing a Contents.json file since our main processing loop only cares about these
if (Path.GetFileName (vpath) != "Contents.json")
// Handle both Contents.json (for .xcassets) and icon.json (for .icon folders)
var fileName = Path.GetFileName (vpath);
if (fileName != "Contents.json" && fileName != "icon.json")
continue;

items.Add (asset);
}
}
}

// Note: `items` contains only the Contents.json files at this point
// Note: `items` contains only the Contents.json and icon.json files at this point
for (int i = 0; i < items.Count; i++) {
var asset = items [i];
var assetItem = asset.Item;
Expand All @@ -409,8 +411,9 @@ public override bool Execute ()
brandAssetsInAssets.Add (Path.GetFileNameWithoutExtension (Path.GetDirectoryName (vpath)));
}
} else {
if (assetType.Equals ("appiconset", StringComparison.OrdinalIgnoreCase))
if (assetType.Equals ("appiconset", StringComparison.OrdinalIgnoreCase) || assetType.Equals ("icon", StringComparison.OrdinalIgnoreCase)) {
appIconsInAssets.Add (Path.GetFileNameWithoutExtension (Path.GetDirectoryName (vpath)));
}
}

if (unique.Add (catalog)) {
Expand All @@ -420,7 +423,8 @@ public override bool Execute ()
catalogs.Add (item);
}

if (SdkPlatform != "WatchSimulator") {
// Only process Contents.json files for on-demand resources (not icon.json files)
if (SdkPlatform != "WatchSimulator" && Path.GetFileName (vpath) == "Contents.json") {
var text = File.ReadAllText (assetItem.ItemSpec);

if (string.IsNullOrEmpty (text))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -634,5 +634,45 @@ public void XSAppIconAssetsAndAppIcon (ApplePlatform platform)
ExecuteTask (actool, 1);
Assert.AreEqual ("Can't specify both 'XSAppIconAssets' in the Info.plist and 'AppIcon' in the project file. Please select one or the other.", Engine.Logger.ErrorEvents [0].Message, "Error message");
}

[Test]
[TestCase (ApplePlatform.iOS)]
[TestCase (ApplePlatform.MacCatalyst)]
[TestCase (ApplePlatform.MacOSX)]
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.

It doesn't work on tvOS?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It should work on tvOS I just forget to add it

public void IconFileSupport (ApplePlatform platform)
{
// Test that .icon folders (Icon Composer format) are recognized as app icons
var projectDir = Cache.CreateTemporaryDirectory ();
var iconFolderPath = Path.Combine (projectDir, "Resources", "AppIcon.icon");
var assetsPath = Path.Combine (iconFolderPath, "Assets");
Directory.CreateDirectory (assetsPath);

// Create a dummy icon.json file (simplified structure for testing)
var iconJsonPath = Path.Combine (iconFolderPath, "icon.json");
File.WriteAllText (iconJsonPath, @"{""version"":1}");

// Create a dummy image file in the Assets folder
var imagePath = Path.Combine (assetsPath, "icon_512x512.png");
File.WriteAllText (imagePath, "dummy image");

var actool = CreateACToolTask (
platform,
projectDir,
out var _,
iconJsonPath + "|Resources/AppIcon.icon/icon.json",
imagePath + "|Resources/AppIcon.icon/Assets/icon_512x512.png"
);
actool.AppIcon = "AppIcon";

// The task should recognize AppIcon as a valid icon
// Note: This will fail at actool execution since we don't have a real .icon structure,
// but we're testing that the icon is recognized in the validation phase
ExecuteTask (actool, expectedErrorCount: 1); // Expected to fail at actool execution

// 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");
}
}
}