diff --git a/docs/building-apps/build-properties.md b/docs/building-apps/build-properties.md index 094b6c8c8cc4..98623c7160e6 100644 --- a/docs/building-apps/build-properties.md +++ b/docs/building-apps/build-properties.md @@ -275,6 +275,27 @@ By default we require a provisioning profile if: Setting this property to `true` or `false` will override the default logic. +## ComputeInstructionSetForReadyToRun + +Controls whether to automatically compute and pass the instruction set to the ReadyToRun (R2R) compiler based on the deployment target. + +When `PublishReadyToRun` is `true`, the build system automatically computes the minimum CPU instruction set required based on: +* The `SupportedOSPlatformVersion` (minimum OS version the app supports) +* The `RuntimeIdentifier` (target architecture and platform) + +This computed instruction set is then passed to crossgen2 via the `--instruction-set` argument, enabling the R2R compiler to generate optimized native code using appropriate CPU instructions. + +Set this property to `false` to disable automatic instruction set computation and use crossgen2's default behavior. + +Default: `true` + +Example: +```xml + + false + +``` + ## CompressBindingResourcePackage The native references in a binding projects are copied to the output directory during the build process, next to the binding assembly (into something we call a "binding resource package"). diff --git a/dotnet/targets/Microsoft.Sdk.R2R.targets b/dotnet/targets/Microsoft.Sdk.R2R.targets index d759b7215729..e7ff16a4a2d4 100644 --- a/dotnet/targets/Microsoft.Sdk.R2R.targets +++ b/dotnet/targets/Microsoft.Sdk.R2R.targets @@ -1,5 +1,7 @@ + + @@ -14,6 +16,31 @@ + + + + + + + + + --instruction-set:$(_ComputedInstructionSet) + $(PublishReadyToRunCrossgen2ExtraArgs) --instruction-set:$(_ComputedInstructionSet) + + + diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeInstructionSet.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeInstructionSet.cs new file mode 100644 index 000000000000..7f3717a6215b --- /dev/null +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/ComputeInstructionSet.cs @@ -0,0 +1,336 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Build.Framework; +using Xamarin.Localization.MSBuild; +using Xamarin.Utils; + +#nullable enable + +namespace Xamarin.MacDev.Tasks { + /// + /// Computes the minimum instruction set required for a given OS version and platform. + /// This is used to configure the R2R/NAOT compiler (crossgen2) with the appropriate --instruction-set argument. + /// + public class ComputeInstructionSet : XamarinTask { + #region Inputs + + [Required] + public string RuntimeIdentifier { get; set; } = ""; + + [Required] + public string SupportedOSPlatformVersion { get; set; } = ""; + + #endregion + + #region Outputs + + [Output] + public string InstructionSet { get; set; } = ""; + + #endregion + + // Device information structure + class DeviceInfo { + public string Cpu { get; } + public string MaxOSVersion { get; } + + public DeviceInfo (string cpu, string maxOSVersion) + { + Cpu = cpu; + MaxOSVersion = maxOSVersion; + } + } + + // Device information mapping + // This maps each device model to its CPU model and maximum supported OS version. + // + // Sources for device information: + // - iPhone models and chips: https://en.wikipedia.org/wiki/List_of_iPhone_models + // - iPad models and chips: https://en.wikipedia.org/wiki/List_of_iPad_models + // - Apple TV models and chips: https://en.wikipedia.org/wiki/Apple_TV#Specifications + // - Apple's technical specifications pages for each device (e.g., https://support.apple.com/kb/SP714 for iPhone 6s) + // - iOS compatibility: https://en.wikipedia.org/wiki/IOS_version_history#Overview + // - iPadOS compatibility: https://en.wikipedia.org/wiki/IPadOS_version_history#Overview + // - tvOS compatibility: https://en.wikipedia.org/wiki/TvOS_version_history#Overview + // - https://support.apple.com/en-us/120256 (iOS and iPadOS compatibility) + // + // To update: Cross-reference Wikipedia articles with Apple's official tech specs when new devices are released. + // Note: For devices that support the latest OS version, use SdkVersions constants instead of hardcoding the version number. + static Dictionary GetDeviceInfo () + { + // Use SdkVersions for the latest OS versions + var latestIOSVersion = SdkVersions.iOS; + var latestTVOSVersion = SdkVersions.TVOS; + + return new Dictionary { + // iOS devices + { "iPhone6s", new DeviceInfo ("A9", "15.8") }, + { "iPhone6sPlus", new DeviceInfo ("A9", "15.8") }, + { "iPhoneSE", new DeviceInfo ("A9", "15.8") }, + { "iPhone7", new DeviceInfo ("A10", "15.8") }, + { "iPhone7Plus", new DeviceInfo ("A10", "15.8") }, + { "iPhone8", new DeviceInfo ("A11", "16.7") }, + { "iPhone8Plus", new DeviceInfo ("A11", "16.7") }, + { "iPhoneX", new DeviceInfo ("A11", "16.7") }, + { "iPhoneXR", new DeviceInfo ("A12", latestIOSVersion) }, + { "iPhoneXS", new DeviceInfo ("A12", latestIOSVersion) }, + { "iPhoneXSMax", new DeviceInfo ("A12", latestIOSVersion) }, + { "iPhone11", new DeviceInfo ("A13", latestIOSVersion) }, + { "iPhone11Pro", new DeviceInfo ("A13", latestIOSVersion) }, + { "iPhone11ProMax", new DeviceInfo ("A13", latestIOSVersion) }, + { "iPhoneSE2", new DeviceInfo ("A13", latestIOSVersion) }, + { "iPhone12mini", new DeviceInfo ("A14", latestIOSVersion) }, + { "iPhone12", new DeviceInfo ("A14", latestIOSVersion) }, + { "iPhone12Pro", new DeviceInfo ("A14", latestIOSVersion) }, + { "iPhone12ProMax", new DeviceInfo ("A14", latestIOSVersion) }, + { "iPhone13mini", new DeviceInfo ("A15", latestIOSVersion) }, + { "iPhone13", new DeviceInfo ("A15", latestIOSVersion) }, + { "iPhone13Pro", new DeviceInfo ("A15", latestIOSVersion) }, + { "iPhone13ProMax", new DeviceInfo ("A15", latestIOSVersion) }, + { "iPhoneSE3", new DeviceInfo ("A15", latestIOSVersion) }, + { "iPhone14", new DeviceInfo ("A15", latestIOSVersion) }, + { "iPhone14Plus", new DeviceInfo ("A15", latestIOSVersion) }, + { "iPhone14Pro", new DeviceInfo ("A16", latestIOSVersion) }, + { "iPhone14ProMax", new DeviceInfo ("A16", latestIOSVersion) }, + { "iPhone15", new DeviceInfo ("A16", latestIOSVersion) }, + { "iPhone15Plus", new DeviceInfo ("A16", latestIOSVersion) }, + { "iPhone15Pro", new DeviceInfo ("A17Pro", latestIOSVersion) }, + { "iPhone15ProMax", new DeviceInfo ("A17Pro", latestIOSVersion) }, + { "iPhone16", new DeviceInfo ("A18", latestIOSVersion) }, + { "iPhone16Plus", new DeviceInfo ("A18", latestIOSVersion) }, + { "iPhone16Pro", new DeviceInfo ("A18Pro", latestIOSVersion) }, + { "iPhone16ProMax", new DeviceInfo ("A18Pro", latestIOSVersion) }, + + // iPad models + { "iPadAir2", new DeviceInfo ("A8X", "15.8") }, + { "iPadMini4", new DeviceInfo ("A8", "15.8") }, + { "iPadPro9_7", new DeviceInfo ("A9X", "16.7") }, + { "iPadPro12_9", new DeviceInfo ("A9X", "16.7") }, + { "iPad5", new DeviceInfo ("A9", "16.7") }, + { "iPadPro10_5", new DeviceInfo ("A10X", "16.7") }, + { "iPadPro12_9_2", new DeviceInfo ("A10X", "16.7") }, + { "iPad6", new DeviceInfo ("A10", "16.7") }, + { "iPadAir3", new DeviceInfo ("A12", "17.7") }, + { "iPadMini5", new DeviceInfo ("A12", "17.7") }, + { "iPad7", new DeviceInfo ("A10", "17.7") }, + { "iPadPro11", new DeviceInfo ("A12X", latestIOSVersion) }, + { "iPadPro12_9_3", new DeviceInfo ("A12X", latestIOSVersion) }, + { "iPad8", new DeviceInfo ("A12", latestIOSVersion) }, + { "iPadAir4", new DeviceInfo ("A14", latestIOSVersion) }, + { "iPad9", new DeviceInfo ("A13", latestIOSVersion) }, + { "iPadMini6", new DeviceInfo ("A15", latestIOSVersion) }, + { "iPadAir5", new DeviceInfo ("M1", latestIOSVersion) }, + { "iPadPro11_3", new DeviceInfo ("M1", latestIOSVersion) }, + { "iPadPro12_9_5", new DeviceInfo ("M1", latestIOSVersion) }, + { "iPad10", new DeviceInfo ("A14", latestIOSVersion) }, + { "iPadPro11_4", new DeviceInfo ("M2", latestIOSVersion) }, + { "iPadPro12_9_6", new DeviceInfo ("M2", latestIOSVersion) }, + { "iPadAir6", new DeviceInfo ("M2", latestIOSVersion) }, + { "iPadPro11_M4", new DeviceInfo ("M4", latestIOSVersion) }, + { "iPadPro13_M4", new DeviceInfo ("M4", latestIOSVersion) }, + + // Apple TV models + { "AppleTV4", new DeviceInfo ("A8", "15.6") }, + { "AppleTV4K", new DeviceInfo ("A10X", latestTVOSVersion) }, + { "AppleTV4K2", new DeviceInfo ("A12", latestTVOSVersion) }, + { "AppleTV4K3", new DeviceInfo ("A15", latestTVOSVersion) }, + }; + } + + // CPU-to-instruction-set mapping + // This maps each CPU model to the instruction set it supports. + // + // Sources for CPU instruction set architecture information: + // - Apple A-series chips: https://en.wikipedia.org/wiki/Apple_silicon#A_series + // - Apple M-series chips: https://en.wikipedia.org/wiki/Apple_silicon#M_series + // - ARM architecture versions: https://en.wikipedia.org/wiki/ARM_architecture_family#Cores + // - Apple's LLVM source and documentation for architecture features + // - ARM Architecture Reference Manuals: https://developer.arm.com/documentation/ + // + // Crossgen2 instruction set compatibility: + // Run `crossgen2 --help` to see supported instruction sets (current list): + // x86-64-v2, x86-64-v3, x86-64-v4, armv8-a, armv8.1-a, armv8.2-a, armv8.3-a, armv8.4-a, armv8.5-a, armv8.6-a, apple-m1 + // + // To update: Verify new Apple chip architectures against ARM documentation and crossgen2 supported instruction sets. + static readonly Dictionary CpuToInstructionSet = new Dictionary { + // ARM chips + { "A8", "armv8-a" }, // ARMv8.0-A (iPhone 6, iPad Air 2, Apple TV 4) + { "A8X", "armv8-a" }, // ARMv8.0-A (iPad Air 2) + { "A9", "armv8-a" }, // ARMv8.0-A (iPhone 6s, iPad 5, iPad Pro 9.7) + { "A9X", "armv8-a" }, // ARMv8.0-A (iPad Pro 12.9 1st gen, iPad Pro 9.7) + { "A10", "armv8-a" }, // ARMv8.0-A (iPhone 7) + { "A10X", "armv8.1-a" }, // ARMv8.1-A (iPad Pro 10.5, iPad Pro 12.9 2nd gen, Apple TV 4K) + { "A11", "armv8.2-a" }, // ARMv8.2-A (iPhone 8, iPhone X) + { "A12", "armv8.3-a" }, // ARMv8.3-A (iPhone XS, iPad Air 3, Apple TV 4K 2nd gen) + { "A12X", "armv8.3-a" }, // ARMv8.3-A (iPad Pro 11, iPad Pro 12.9 3rd gen) + { "A13", "armv8.4-a" }, // ARMv8.4-A (iPhone 11, iPhone SE 2nd gen) + { "A14", "armv8.4-a" }, // ARMv8.4-A with additional features (iPhone 12, iPad Air 4) + { "A15", "armv8.5-a" }, // ARMv8.5-A (iPhone 13, Apple TV 4K 3rd gen) + { "A16", "armv8.6-a" }, // ARMv8.6-A (iPhone 14 Pro) + { "A17Pro", "armv8.6-a" }, // ARMv8.6-A+ (iPhone 15 Pro) + { "A18", "armv8.6-a" }, // ARMv8.6-A+ (iPhone 16) + { "A18Pro", "armv8.6-a" }, // ARMv8.6-A+ (iPhone 16 Pro) + + // Apple Silicon (M-series) for macOS/Mac Catalyst + { "M1", "apple-m1" }, // Apple M1 (Mac, iPad Air 5, iPad Pro) + { "M2", "apple-m1" }, // Apple M2 (similar to M1 in instruction support for crossgen2) + { "M3", "apple-m1" }, // Apple M3 (similar to M1 in instruction support for crossgen2) + { "M4", "apple-m1" }, // Apple M4 (similar to M1 in instruction support for crossgen2) + + // Intel chips for macOS + { "Intel", "x86-64-v2" }, // Default Intel instruction set + }; + + // Crossgen2 supported instruction sets (from crossgen2 --help) + static readonly HashSet SupportedInstructionSets = new HashSet { + "x86-64-v2", + "x86-64-v3", + "x86-64-v4", + "armv8-a", + "armv8.1-a", + "armv8.2-a", + "armv8.3-a", + "armv8.4-a", + "armv8.5-a", + "armv8.6-a", + "apple-m1" + }; + + public override bool Execute () + { + try { + var instructionSet = ComputeMinimumInstructionSet (Platform, SupportedOSPlatformVersion); +#if NET + if (!string.IsNullOrEmpty (instructionSet)) { +#else + if (!string.IsNullOrEmpty (instructionSet) && instructionSet is not null) { +#endif + InstructionSet = instructionSet; + Log.LogMessage (MessageImportance.Low, $"Computed instruction set '{InstructionSet}' for {PlatformName} {SupportedOSPlatformVersion}"); + } else { + Log.LogMessage (MessageImportance.Low, $"No instruction set computed for {PlatformName} {SupportedOSPlatformVersion}"); + } + + return !Log.HasLoggedErrors; + } catch (Exception ex) { + Log.LogError ($"Error computing instruction set: {ex.Message}"); + return false; + } + } + + string? ComputeMinimumInstructionSet (ApplePlatform platform, string osVersion) + { + // Parse the OS version + if (!Version.TryParse (osVersion, out var targetVersion)) { + Log.LogMessage (MessageImportance.Low, $"Could not parse OS version: {osVersion}"); + return null; + } + + // For macOS, Mac Catalyst, and simulators (which run on Mac hardware), we need different logic + if (platform == ApplePlatform.MacOSX || platform == ApplePlatform.MacCatalyst || RuntimeIdentifier.Contains ("simulator")) { + return ComputeMacInstructionSet (platform, targetVersion); + } + + // For iOS and tvOS, find the oldest device that can run this OS version + var deviceInfo = GetDeviceInfo (); + var devicesToCheck = platform == ApplePlatform.TVOS ? + deviceInfo.Where (kv => kv.Key.StartsWith ("AppleTV")) : + deviceInfo.Where (kv => !kv.Key.StartsWith ("AppleTV")); + + string? oldestCpu = null; + string? oldestInstructionSet = null; + + foreach (var device in devicesToCheck) { + var deviceName = device.Key; + var info = device.Value; + var maxOSString = info.MaxOSVersion; + + if (!Version.TryParse (maxOSString, out var maxOS)) + continue; + + // Check if this device can run the target OS version + // A device can run the target OS if its max OS >= target OS + if (maxOS >= targetVersion) { + // This device can run the target OS + var cpu = info.Cpu; + if (CpuToInstructionSet.TryGetValue (cpu, out var instructionSet)) { + // Keep track of the oldest instruction set we've seen + // We want the minimum instruction set that all compatible devices support + if (oldestCpu is null || IsOlderInstructionSet (instructionSet, oldestInstructionSet)) { + oldestCpu = cpu; + oldestInstructionSet = instructionSet; + } + } + } + } + + // Validate that the instruction set is supported by crossgen2 + if (oldestInstructionSet is not null && !SupportedInstructionSets.Contains (oldestInstructionSet)) { + Log.LogMessage (MessageImportance.Low, $"Instruction set '{oldestInstructionSet}' is not supported by crossgen2, skipping"); + return null; + } + + return oldestInstructionSet; + } + + string? ComputeMacInstructionSet (ApplePlatform platform, Version targetVersion) + { + // For macOS and Mac Catalyst, we need to determine the instruction set based on RuntimeIdentifier + // RuntimeIdentifier format: - or .- + // Examples: "osx-x64", "osx-arm64", "osx.13.0-arm64", "maccatalyst-x64", "maccatalyst-arm64" + + var parts = RuntimeIdentifier.Split ('-'); + if (parts.Length < 2) { + Log.LogMessage (MessageImportance.Low, $"RuntimeIdentifier '{RuntimeIdentifier}' has unexpected format"); + return null; + } + + // The architecture is always the last segment after the last hyphen + var arch = parts [parts.Length - 1]; + + // Determine instruction set based on architecture + if (string.Equals (arch, "x64", StringComparison.OrdinalIgnoreCase)) { + // Intel/AMD x64 architecture + return "x86-64-v2"; + } else if (string.Equals (arch, "arm64", StringComparison.OrdinalIgnoreCase)) { + // Apple Silicon + return "apple-m1"; + } else { + Log.LogMessage (MessageImportance.Low, $"Unknown architecture '{arch}' in RuntimeIdentifier '{RuntimeIdentifier}'"); + return null; + } + } + + bool IsOlderInstructionSet (string? instructionSet1, string? instructionSet2) + { + if (instructionSet2 is null) + return true; + if (instructionSet1 is null) + return false; + + // Order instruction sets from oldest to newest + var order = new [] { + "armv8-a", + "armv8.1-a", + "armv8.2-a", + "armv8.3-a", + "armv8.4-a", + "armv8.5-a", + "armv8.6-a", + "apple-m1", + "x86-64-v2", + "x86-64-v3", + "x86-64-v4" + }; + + var index1 = Array.IndexOf (order, instructionSet1); + var index2 = Array.IndexOf (order, instructionSet2); + + if (index1 == -1 || index2 == -1) + return false; + + return index1 < index2; + } + } +} diff --git a/tests/dotnet/UnitTests/expected/iOS-CoreCLR-R2R-size.txt b/tests/dotnet/UnitTests/expected/iOS-CoreCLR-R2R-size.txt index c8182551db33..b8060946b314 100644 --- a/tests/dotnet/UnitTests/expected/iOS-CoreCLR-R2R-size.txt +++ b/tests/dotnet/UnitTests/expected/iOS-CoreCLR-R2R-size.txt @@ -1,29 +1,29 @@ -AppBundleSize: 20,862,389 bytes (20,373.4 KB = 19.9 MB) +AppBundleSize: 20,862,501 bytes (20,373.5 KB = 19.9 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: _CodeSignature/CodeResources: 11,701 bytes (11.4 KB = 0.0 MB) archived-expanded-entitlements.xcent: 384 bytes (0.4 KB = 0.0 MB) Frameworks/libcoreclr.framework/_CodeSignature/CodeResources: 1,798 bytes (1.8 KB = 0.0 MB) -Frameworks/libcoreclr.framework/Info.plist: 839 bytes (0.8 KB = 0.0 MB) -Frameworks/libcoreclr.framework/libcoreclr: 6,641,648 bytes (6,486.0 KB = 6.3 MB) +Frameworks/libcoreclr.framework/Info.plist: 825 bytes (0.8 KB = 0.0 MB) +Frameworks/libcoreclr.framework/libcoreclr: 6,641,824 bytes (6,486.2 KB = 6.3 MB) Frameworks/libSystem.Globalization.Native.framework/_CodeSignature/CodeResources: 1,798 bytes (1.8 KB = 0.0 MB) -Frameworks/libSystem.Globalization.Native.framework/Info.plist: 881 bytes (0.9 KB = 0.0 MB) +Frameworks/libSystem.Globalization.Native.framework/Info.plist: 867 bytes (0.8 KB = 0.0 MB) Frameworks/libSystem.Globalization.Native.framework/libSystem.Globalization.Native: 110,608 bytes (108.0 KB = 0.1 MB) Frameworks/libSystem.IO.Compression.Native.framework/_CodeSignature/CodeResources: 1,798 bytes (1.8 KB = 0.0 MB) -Frameworks/libSystem.IO.Compression.Native.framework/Info.plist: 883 bytes (0.9 KB = 0.0 MB) +Frameworks/libSystem.IO.Compression.Native.framework/Info.plist: 869 bytes (0.8 KB = 0.0 MB) Frameworks/libSystem.IO.Compression.Native.framework/libSystem.IO.Compression.Native: 1,467,440 bytes (1,433.0 KB = 1.4 MB) Frameworks/libSystem.Native.framework/_CodeSignature/CodeResources: 1,798 bytes (1.8 KB = 0.0 MB) -Frameworks/libSystem.Native.framework/Info.plist: 853 bytes (0.8 KB = 0.0 MB) +Frameworks/libSystem.Native.framework/Info.plist: 839 bytes (0.8 KB = 0.0 MB) Frameworks/libSystem.Native.framework/libSystem.Native: 164,176 bytes (160.3 KB = 0.2 MB) Frameworks/libSystem.Net.Security.Native.framework/_CodeSignature/CodeResources: 1,798 bytes (1.8 KB = 0.0 MB) -Frameworks/libSystem.Net.Security.Native.framework/Info.plist: 879 bytes (0.9 KB = 0.0 MB) +Frameworks/libSystem.Net.Security.Native.framework/Info.plist: 865 bytes (0.8 KB = 0.0 MB) Frameworks/libSystem.Net.Security.Native.framework/libSystem.Net.Security.Native: 88,432 bytes (86.4 KB = 0.1 MB) Frameworks/libSystem.Security.Cryptography.Native.Apple.framework/_CodeSignature/CodeResources: 1,798 bytes (1.8 KB = 0.0 MB) -Frameworks/libSystem.Security.Cryptography.Native.Apple.framework/Info.plist: 909 bytes (0.9 KB = 0.0 MB) +Frameworks/libSystem.Security.Cryptography.Native.Apple.framework/Info.plist: 895 bytes (0.9 KB = 0.0 MB) Frameworks/libSystem.Security.Cryptography.Native.Apple.framework/libSystem.Security.Cryptography.Native.Apple: 224,080 bytes (218.8 KB = 0.2 MB) Frameworks/SizeTestApp.framework/_CodeSignature/CodeResources: 1,798 bytes (1.8 KB = 0.0 MB) -Frameworks/SizeTestApp.framework/Info.plist: 842 bytes (0.8 KB = 0.0 MB) -Frameworks/SizeTestApp.framework/SizeTestApp: 9,441,664 bytes (9,220.4 KB = 9.0 MB) -Info.plist: 1,167 bytes (1.1 KB = 0.0 MB) +Frameworks/SizeTestApp.framework/Info.plist: 828 bytes (0.8 KB = 0.0 MB) +Frameworks/SizeTestApp.framework/SizeTestApp: 9,441,712 bytes (9,220.4 KB = 9.0 MB) +Info.plist: 1,153 bytes (1.1 KB = 0.0 MB) Microsoft.iOS.dll: 163,840 bytes (160.0 KB = 0.2 MB) PkgInfo: 8 bytes (0.0 KB = 0.0 MB) runtimeconfig.bin: 1,481 bytes (1.4 KB = 0.0 MB) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ComputeInstructionSetTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ComputeInstructionSetTaskTests.cs new file mode 100644 index 000000000000..83afdb878553 --- /dev/null +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/ComputeInstructionSetTaskTests.cs @@ -0,0 +1,119 @@ +#nullable enable +using System; +using NUnit.Framework; +using Xamarin.MacDev.Tasks; +using Xamarin.Utils; + +namespace Xamarin.MacDev.Tasks { + [TestFixture] + public class ComputeInstructionSetTaskTests : TestBase { + ComputeInstructionSet CreateTask (ApplePlatform platform = ApplePlatform.iOS) + { + var task = CreateTask (); + task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (platform).ToString (); + return task; + } + + [Test] + [TestCase (ApplePlatform.iOS, "12.0", "armv8-a")] // iPhone 6s era (iOS 12) + [TestCase (ApplePlatform.iOS, "13.0", "armv8-a")] // iPhone 6s era (iOS 13) + [TestCase (ApplePlatform.iOS, "14.0", "armv8-a")] // iPhone 6s era (iOS 14) + [TestCase (ApplePlatform.iOS, "15.0", "armv8-a")] // iPhone 6s era (iOS 15) + [TestCase (ApplePlatform.iOS, "16.0", "armv8-a")] // iPad5 (A9) can run iOS 16 + [TestCase (ApplePlatform.iOS, "17.0", "armv8-a")] // iPad7 (A10) can run iOS 17 + [TestCase (ApplePlatform.iOS, "18.0", "armv8.3-a")] // iPhone XR and newer (iOS 18) + public void TestiOSInstructionSets (ApplePlatform platform, string osVersion, string expectedInstructionSet) + { + var task = CreateTask (platform); + task.SupportedOSPlatformVersion = osVersion; + + ExecuteTask (task); + + Assert.AreEqual (expectedInstructionSet, task.InstructionSet, $"Instruction set for {platform} {osVersion}"); + } + + [Test] + [TestCase (ApplePlatform.TVOS, "12.0", "armv8-a")] // Apple TV 4 + [TestCase (ApplePlatform.TVOS, "13.0", "armv8-a")] // Apple TV 4 + [TestCase (ApplePlatform.TVOS, "14.0", "armv8-a")] // Apple TV 4 + [TestCase (ApplePlatform.TVOS, "15.0", "armv8-a")] // Apple TV 4 + [TestCase (ApplePlatform.TVOS, "16.0", "armv8.1-a")] // Apple TV 4K and newer + [TestCase (ApplePlatform.TVOS, "17.0", "armv8.1-a")] // Apple TV 4K and newer + public void TesttvOSInstructionSets (ApplePlatform platform, string osVersion, string expectedInstructionSet) + { + var task = CreateTask (platform); + task.SupportedOSPlatformVersion = osVersion; + + ExecuteTask (task); + + Assert.AreEqual (expectedInstructionSet, task.InstructionSet, $"Instruction set for {platform} {osVersion}"); + } + + [Test] + [TestCase (ApplePlatform.MacOSX, "10.15", "osx-x64", "x86-64-v2")] + [TestCase (ApplePlatform.MacOSX, "11.0", "osx-x64", "x86-64-v2")] + [TestCase (ApplePlatform.MacOSX, "11.0", "osx-arm64", "apple-m1")] + [TestCase (ApplePlatform.MacOSX, "12.0", "osx-x64", "x86-64-v2")] + [TestCase (ApplePlatform.MacOSX, "12.0", "osx-arm64", "apple-m1")] + [TestCase (ApplePlatform.MacCatalyst, "14.0", "maccatalyst-x64", "x86-64-v2")] + [TestCase (ApplePlatform.MacCatalyst, "14.0", "maccatalyst-arm64", "apple-m1")] + [TestCase (ApplePlatform.MacCatalyst, "15.0", "maccatalyst-x64", "x86-64-v2")] + [TestCase (ApplePlatform.MacCatalyst, "15.0", "maccatalyst-arm64", "apple-m1")] + public void TestmacOSInstructionSets (ApplePlatform platform, string osVersion, string runtimeIdentifier, string expectedInstructionSet) + { + var task = CreateTask (platform); + task.SupportedOSPlatformVersion = osVersion; + task.RuntimeIdentifier = runtimeIdentifier; + + ExecuteTask (task); + + Assert.AreEqual (expectedInstructionSet, task.InstructionSet, $"Instruction set for {platform} {osVersion} with {runtimeIdentifier}"); + } + + [Test] + public void TestEmptyOSVersion () + { + var task = CreateTask (ApplePlatform.iOS); + task.SupportedOSPlatformVersion = ""; + + ExecuteTask (task); + + Assert.AreEqual ("", task.InstructionSet, "Instruction set should be empty for empty OS version"); + } + + [Test] + public void TestInvalidOSVersion () + { + var task = CreateTask (ApplePlatform.iOS); + task.SupportedOSPlatformVersion = "invalid"; + + ExecuteTask (task); + + Assert.AreEqual ("", task.InstructionSet, "Instruction set should be empty for invalid OS version"); + } + + [Test] + public void TestVeryOldOSVersion () + { + var task = CreateTask (ApplePlatform.iOS); + task.SupportedOSPlatformVersion = "9.0"; + + // iOS 9 was supported by iPhone 4s (A5), but we don't have data for that era + // The task should handle this gracefully + ExecuteTask (task); + } + + [Test] + public void TestFutureOSVersion () + { + var task = CreateTask (ApplePlatform.iOS); + task.SupportedOSPlatformVersion = "99.0"; + + ExecuteTask (task); + + // For a future OS version that no current device supports, we won't compute an instruction set + // This is expected behavior - we can't predict future instruction sets + Assert.IsEmpty (task.InstructionSet, "Should not compute instruction set for future OS version"); + } + } +}