From 97dc443facc64e6c5c3056f0f1b7713b45e56a11 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Tue, 3 Mar 2026 17:36:29 +0000 Subject: [PATCH 1/6] Use SimctlOutputParser from Xamarin.MacDev for simctl JSON parsing Refactor GetAvailableDevices.RunSimCtlAsync to use the shared SimctlOutputParser from the Xamarin.MacDev submodule instead of inline JSON parsing. This: - Reuses tested parsing logic (65+ unit tests in macios-devtools) - Shares the parser across both MSBuild tasks and the MAUI CLI tool - Keeps devicetypes parsing inline (not yet in shared parser) - Preserves all MSBuild metadata, filtering, and discarding logic Depends on dotnet/macios-devtools PR #158 being merged to main. The submodule currently points to feature/simulator-management. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/Xamarin.MacDev | 2 +- .../Tasks/GetAvailableDevices.cs | 241 +++++++++--------- 2 files changed, 120 insertions(+), 123 deletions(-) diff --git a/external/Xamarin.MacDev b/external/Xamarin.MacDev index f1300986199f..5de50e5468b8 160000 --- a/external/Xamarin.MacDev +++ b/external/Xamarin.MacDev @@ -1 +1 @@ -Subproject commit f1300986199f5489191d2c9712e57bf8a0a3d84a +Subproject commit 5de50e5468b8a79bb431c34234770623b39a66f4 diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs index eda5380ab2d9..f300c4ab4f43 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs @@ -9,6 +9,7 @@ using Microsoft.Build.Utilities; using Xamarin.Localization.MSBuild; +using Xamarin.MacDev.Models; using Xamarin.Messaging.Build.Client; using Xamarin.Utils; @@ -281,145 +282,141 @@ async System.Threading.Tasks.Task> RunDeviceCtlAsync () async System.Threading.Tasks.Task> RunSimCtlAsync () { - var doc = await ExecuteCtlToJsonAsync ("simctl", "list", "--json"); + var json = await ExecuteCtlAsync ("simctl", "list", "--json"); var rv = new List (); - var runtimes = new Dictionary (); - if (doc.TryGetProperty ("runtimes", out var runtimesElement)) { - foreach (var runtime in runtimesElement.EnumerateIfArray ()) { - var name = runtime.GetStringProperty ("identifier") ?? string.Empty; - runtimes [name] = runtime; - } - } + // Use shared parser from Xamarin.MacDev for device and runtime extraction + var parsedDevices = SimctlOutputParser.ParseDevices (json); + var parsedRuntimes = SimctlOutputParser.ParseRuntimes (json); + + // Index runtimes by identifier for SupportedArchitectures lookup + var runtimesByIdentifier = new Dictionary (); + foreach (var rt in parsedRuntimes) + runtimesByIdentifier [rt.Identifier] = rt; + // devicetypes still needs raw JSON parsing (productFamily, min/maxRuntime not yet in shared parser) var deviceTypes = new Dictionary (); - if (doc.TryGetProperty ("devicetypes", out var deviceTypesElement)) { - foreach (var deviceType in deviceTypesElement.EnumerateIfArray ()) { - var name = deviceType.GetStringProperty ("identifier") ?? string.Empty; - deviceTypes [name] = deviceType; + var options = new JsonDocumentOptions { + AllowTrailingCommas = true, + CommentHandling = JsonCommentHandling.Skip, + }; + using (var doc = JsonDocument.Parse (string.IsNullOrEmpty (json) ? "{}" : json, options)) { + if (doc.RootElement.TryGetProperty ("devicetypes", out var deviceTypesElement)) { + foreach (var deviceType in deviceTypesElement.EnumerateIfArray ()) { + var dtName = deviceType.GetStringProperty ("identifier") ?? string.Empty; + deviceTypes [dtName] = deviceType.Clone (); + } } } - if (doc.TryGetProperty ("devices", out var devicesElement)) { - foreach (var runtime in devicesElement.EnumerateObject ()) { - var runtimeName = runtime.Name; - var hasRuntime = runtimes.TryGetValue (runtimeName, out var runtimeElement); - var runtimePlatform = hasRuntime ? runtimeElement.GetStringProperty ("platform") ?? string.Empty : string.Empty; - var runtimeVersion = hasRuntime ? runtimeElement.GetStringProperty ("version") ?? string.Empty : string.Empty; - var supportedArchitectures = hasRuntime ? runtimeElement.GetProperty ("supportedArchitectures").EnumerateIfArray ().Select (v => v.GetString () ?? "") : Enumerable.Empty (); - foreach (var element in runtime.Value.EnumerateIfArray ()) { - var udid = element.GetStringProperty ("udid") ?? string.Empty; - var isAvailable = element.GetBooleanProperty ("isAvailable") ?? false; - var availabilityError = element.GetStringProperty ("availabilityError") ?? string.Empty; - var deviceTypeIdentifier = element.GetStringProperty ("deviceTypeIdentifier") ?? string.Empty; - var state = element.GetStringProperty ("state") ?? string.Empty; - var name = element.GetStringProperty ("name") ?? string.Empty; - - var item = new TaskItem (udid); - item.SetMetadata ("Runtime", runtimeName); - item.SetMetadata ("IsAvailable", isAvailable.ToString ()); - item.SetMetadata ("AvailabilityError", availabilityError); - item.SetMetadata ("DeviceTypeIdentifier", deviceTypeIdentifier); - item.SetMetadata ("State", state); - item.SetMetadata ("Name", name); - item.SetMetadata ("SupportedArchitectures", string.Join (",", supportedArchitectures)); - - // we provide the following metadata for both simulator and device - item.SetMetadata ("Description", name); - item.SetMetadata ("Type", "Simulator"); - item.SetMetadata ("OSVersion", runtimeVersion); - item.SetMetadata ("UDID", udid); - - var discardedReason = ""; - var runtimeIdentifier = ""; - var runtimeIdentifiers = new List (); - if (isAvailable) { - switch (runtimePlatform.ToLowerInvariant ()) { - case "ios": - runtimeIdentifier += "iossimulator-"; - break; - case "tvos": - runtimeIdentifier += "tvossimulator-"; - break; - default: - discardedReason = $"'{runtimePlatform}' simulators are not supported"; - break; - } - - // pick the first architecture as the simulator architecture - if (string.IsNullOrEmpty (discardedReason)) { - foreach (var arch in supportedArchitectures) { - switch (arch.ToLowerInvariant ()) { - case "x64": - case "x86_64": - runtimeIdentifiers.Add (runtimeIdentifier + "x64"); - break; - case "arm64": - runtimeIdentifiers.Add (runtimeIdentifier + "arm64"); - if (!CanRunArm64) - discardedReason = $"Can't run an arm64 simulator on an x86_64 macOS desktop."; - break; - default: - discardedReason = $"Unknown CPU architecture '{arch}'"; - break; - } - } - } - } else { - discardedReason = $"Device is not available: {availabilityError}"; - } + foreach (var device in parsedDevices) { + var hasRuntime = runtimesByIdentifier.TryGetValue (device.RuntimeIdentifier, out var runtimeInfo); + var runtimePlatform = hasRuntime ? runtimeInfo!.Platform : string.Empty; + var runtimeVersion = hasRuntime ? runtimeInfo!.Version : device.OSVersion; + var supportedArchitectures = hasRuntime ? runtimeInfo!.SupportedArchitectures : new List (); + + var item = new TaskItem (device.Udid); + item.SetMetadata ("Runtime", device.RuntimeIdentifier); + item.SetMetadata ("IsAvailable", device.IsAvailable.ToString ()); + item.SetMetadata ("AvailabilityError", device.AvailabilityError); + item.SetMetadata ("DeviceTypeIdentifier", device.DeviceTypeIdentifier); + item.SetMetadata ("State", device.State); + item.SetMetadata ("Name", device.Name); + item.SetMetadata ("SupportedArchitectures", string.Join (",", supportedArchitectures)); + + // we provide the following metadata for both simulator and device + item.SetMetadata ("Description", device.Name); + item.SetMetadata ("Type", "Simulator"); + item.SetMetadata ("OSVersion", runtimeVersion); + item.SetMetadata ("UDID", device.Udid); + + var discardedReason = ""; + var runtimeIdentifier = ""; + var runtimeIdentifiers = new List (); + if (device.IsAvailable) { + switch (runtimePlatform.ToLowerInvariant ()) { + case "ios": + runtimeIdentifier += "iossimulator-"; + break; + case "tvos": + runtimeIdentifier += "tvossimulator-"; + break; + default: + discardedReason = $"'{runtimePlatform}' simulators are not supported"; + break; + } - var platformName = runtimeName.Replace ("com.apple.CoreSimulator.SimRuntime.", "").Split ('-') [0]; - var platform = ApplePlatform.None; - if (string.IsNullOrEmpty (discardedReason)) { - switch (platformName.ToLowerInvariant ()) { - case "ios": - platform = ApplePlatform.iOS; + // pick the first architecture as the simulator architecture + if (string.IsNullOrEmpty (discardedReason)) { + foreach (var arch in supportedArchitectures) { + switch (arch.ToLowerInvariant ()) { + case "x64": + case "x86_64": + runtimeIdentifiers.Add (runtimeIdentifier + "x64"); break; - case "tvos": - platform = ApplePlatform.TVOS; + case "arm64": + runtimeIdentifiers.Add (runtimeIdentifier + "arm64"); + if (!CanRunArm64) + discardedReason = $"Can't run an arm64 simulator on an x86_64 macOS desktop."; break; - case "watchos": - case "visionos": default: - discardedReason = $"'{platformName}' simulators are not supported"; + discardedReason = $"Unknown CPU architecture '{arch}'"; break; } } - var deviceType = IPhoneDeviceType.NotSet; - var minimumOSVersion = new Version (0, 0); - var maximumOSVersion = new Version (65535, 255, 255); - if (string.IsNullOrEmpty (discardedReason)) { - if (deviceTypes.TryGetValue (deviceTypeIdentifier, out var deviceTypeElement)) { - var productFamily = deviceTypeElement.GetStringProperty ("productFamily") ?? string.Empty; - switch (productFamily.ToLowerInvariant ()) { - case "iphone": - case "ipod": - deviceType = IPhoneDeviceType.IPhone; - break; - case "ipad": - deviceType = IPhoneDeviceType.IPad; - break; - case "appletv": - case "apple tv": - deviceType = IPhoneDeviceType.TV; - break; - default: - discardedReason = $"Unknown product family '{productFamily}'"; - break; - } - if (Version.TryParse (deviceTypeElement.GetStringProperty ("minRuntimeVersionString"), out var parsedMinimumOSVersion)) - minimumOSVersion = parsedMinimumOSVersion; - if (Version.TryParse (deviceTypeElement.GetStringProperty ("maxRuntimeVersionString"), out var parsedMaximumOSVersion)) - maximumOSVersion = parsedMaximumOSVersion; - } else { - discardedReason = $"Unknown device type identifier '{deviceTypeIdentifier}'"; - } - } + } + } else { + discardedReason = $"Device is not available: {device.AvailabilityError}"; + } - rv.Add (new DeviceInfo (item, runtimeIdentifiers, platform, deviceType, minimumOSVersion, maximumOSVersion, discardedReason)); + var platform = ApplePlatform.None; + if (string.IsNullOrEmpty (discardedReason)) { + switch (device.Platform.ToLowerInvariant ()) { + case "ios": + platform = ApplePlatform.iOS; + break; + case "tvos": + platform = ApplePlatform.TVOS; + break; + case "watchos": + case "visionos": + default: + discardedReason = $"'{device.Platform}' simulators are not supported"; + break; } } + var deviceType = IPhoneDeviceType.NotSet; + var minimumOSVersion = new Version (0, 0); + var maximumOSVersion = new Version (65535, 255, 255); + if (string.IsNullOrEmpty (discardedReason)) { + if (deviceTypes.TryGetValue (device.DeviceTypeIdentifier, out var deviceTypeElement)) { + var productFamily = deviceTypeElement.GetStringProperty ("productFamily") ?? string.Empty; + switch (productFamily.ToLowerInvariant ()) { + case "iphone": + case "ipod": + deviceType = IPhoneDeviceType.IPhone; + break; + case "ipad": + deviceType = IPhoneDeviceType.IPad; + break; + case "appletv": + case "apple tv": + deviceType = IPhoneDeviceType.TV; + break; + default: + discardedReason = $"Unknown product family '{productFamily}'"; + break; + } + if (Version.TryParse (deviceTypeElement.GetStringProperty ("minRuntimeVersionString"), out var parsedMinimumOSVersion)) + minimumOSVersion = parsedMinimumOSVersion; + if (Version.TryParse (deviceTypeElement.GetStringProperty ("maxRuntimeVersionString"), out var parsedMaximumOSVersion)) + maximumOSVersion = parsedMaximumOSVersion; + } else { + discardedReason = $"Unknown device type identifier '{device.DeviceTypeIdentifier}'"; + } + } + + rv.Add (new DeviceInfo (item, runtimeIdentifiers, platform, deviceType, minimumOSVersion, maximumOSVersion, discardedReason)); } return rv; } From 2dab83bc112740e4de6370f600bcdb8ab6d4ee51 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Tue, 3 Mar 2026 17:45:26 +0000 Subject: [PATCH 2/6] Use all shared parsers from Xamarin.MacDev for device JSON parsing Refactor GetAvailableDevices to use all three shared parsers: - SimctlOutputParser.ParseDevices: simulator device extraction - SimctlOutputParser.ParseRuntimes: runtime extraction - SimctlOutputParser.ParseDeviceTypes: devicetypes extraction - DeviceCtlOutputParser.ParseDevices: physical device extraction This eliminates all inline JSON parsing (System.Text.Json and Xamarin.Utils JsonExtensions no longer needed in this file). The only remaining code is MSBuild-specific TaskItem mapping, platform filtering, RuntimeIdentifier computation, and CanRunArm64. Net result: -45 lines, zero inline JSON parsing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/Xamarin.MacDev | 2 +- .../Tasks/GetAvailableDevices.cs | 209 +++++++----------- 2 files changed, 83 insertions(+), 128 deletions(-) diff --git a/external/Xamarin.MacDev b/external/Xamarin.MacDev index 5de50e5468b8..fe9f8003a480 160000 --- a/external/Xamarin.MacDev +++ b/external/Xamarin.MacDev @@ -1 +1 @@ -Subproject commit 5de50e5468b8a79bb431c34234770623b39a66f4 +Subproject commit fe9f8003a480a5c8e5ba093b608ca33ab56bb024 diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs index f300c4ab4f43..829c6fcff879 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs @@ -2,7 +2,6 @@ using System.IO; using System.Collections.Generic; using System.Linq; -using System.Text.Json; using System.Threading; using Microsoft.Build.Framework; @@ -11,7 +10,6 @@ using Xamarin.Localization.MSBuild; using Xamarin.MacDev.Models; using Xamarin.Messaging.Build.Client; -using Xamarin.Utils; namespace Xamarin.MacDev.Tasks; @@ -162,120 +160,87 @@ protected virtual async System.Threading.Tasks.Task ExecuteCtlAsync (par } } - async System.Threading.Tasks.Task ExecuteCtlToJsonAsync (params string [] args) - { - var json = await ExecuteCtlAsync (args); - var options = new JsonDocumentOptions { - AllowTrailingCommas = true, - CommentHandling = JsonCommentHandling.Skip, - }; - return JsonDocument.Parse (string.IsNullOrEmpty (json) ? "{}" : json, options); - } - async System.Threading.Tasks.Task> RunDeviceCtlAsync () { - var doc = await ExecuteCtlToJsonAsync ("devicectl", "list", "devices"); - var array = doc.FindProperty ("result", "devices")?.EnumerateIfArray (); + var json = await ExecuteCtlAsync ("devicectl", "list", "devices"); var rv = new List (); - if (array is not null) { - foreach (var device in array) { - var name = device.GetStringPropertyOrEmpty ("deviceProperties", "name"); - var udid = device.GetStringPropertyOrEmpty ("hardwareProperties", "udid"); - var identifier = device.GetStringPropertyOrEmpty ("identifier"); - - var deviceProperties = device.GetNullableProperty ("deviceProperties"); - var buildVersion = deviceProperties.GetStringPropertyOrEmpty ("osBuildUpdate"); - var productVersion = deviceProperties.GetStringPropertyOrEmpty ("osVersionNumber"); - - var hardwareProperties = device.GetNullableProperty ("hardwareProperties"); - var deviceClass = hardwareProperties.GetStringPropertyOrEmpty ("deviceType"); - var hardwareModel = hardwareProperties.GetStringPropertyOrEmpty ("hardwareModel"); - var hardwarePlatform = hardwareProperties.GetStringPropertyOrEmpty ("platform"); - var productType = hardwareProperties.GetStringPropertyOrEmpty ("productType"); - var serialNumber = hardwareProperties.GetStringPropertyOrEmpty ("serialNumber"); - var uniqueChipID = hardwareProperties.GetUInt64Property ("ecid"); - - var cpuType = hardwareProperties.GetNullableProperty ("cpuType"); - var cpuArchitecture = cpuType.GetStringPropertyOrEmpty ("name"); - - var connectionProperties = device.GetNullableProperty ("connectionProperties"); - var transportType = connectionProperties.GetStringPropertyOrEmpty ("transportType"); - var pairingState = connectionProperties.GetStringPropertyOrEmpty ("pairingState"); - - if (string.IsNullOrEmpty (udid)) - udid = identifier; - - if (string.IsNullOrEmpty (udid)) - udid = $""; - - var item = new TaskItem (udid); - item.SetMetadata ("Name", name); - item.SetMetadata ("BuildVersion", buildVersion); - item.SetMetadata ("DeviceClass", deviceClass); - item.SetMetadata ("HardwareModel", hardwareModel); - item.SetMetadata ("Platform", hardwarePlatform); - item.SetMetadata ("ProductType", productType); - item.SetMetadata ("SerialNumber", serialNumber); - item.SetMetadata ("UniqueChipID", uniqueChipID?.ToString () ?? string.Empty); - item.SetMetadata ("CPUArchitecture", cpuArchitecture); - item.SetMetadata ("TransportType", transportType); - item.SetMetadata ("PairingState", pairingState); - - // we provide the following metadata for both simulator and device - item.SetMetadata ("Description", name); - item.SetMetadata ("Type", "Device"); - item.SetMetadata ("OSVersion", productVersion); - item.SetMetadata ("UDID", udid); - - // compute the platform and runtime identifier - var runtimeIdentifier = ""; - ApplePlatform platform; - IPhoneDeviceType deviceType; - var discardedReason = ""; - switch (deviceClass.ToLowerInvariant ()) { - case "iphone": - case "ipod": - runtimeIdentifier += "ios-"; - platform = ApplePlatform.iOS; - deviceType = IPhoneDeviceType.IPhone; - break; - case "ipad": - runtimeIdentifier += "ios-"; - platform = ApplePlatform.iOS; - deviceType = IPhoneDeviceType.IPad; - break; - case "appletv": - runtimeIdentifier += "tvos-"; - platform = ApplePlatform.TVOS; - deviceType = IPhoneDeviceType.TV; + + // Use shared parser from Xamarin.MacDev for devicectl JSON extraction + var parsedDevices = DeviceCtlOutputParser.ParseDevices (json); + + foreach (var device in parsedDevices) { + var udid = device.Udid; + + if (string.IsNullOrEmpty (udid)) + udid = $""; + + var item = new TaskItem (udid); + item.SetMetadata ("Name", device.Name); + item.SetMetadata ("BuildVersion", device.BuildVersion); + item.SetMetadata ("DeviceClass", device.DeviceClass); + item.SetMetadata ("HardwareModel", device.HardwareModel); + item.SetMetadata ("Platform", device.Platform); + item.SetMetadata ("ProductType", device.ProductType); + item.SetMetadata ("SerialNumber", device.SerialNumber); + item.SetMetadata ("UniqueChipID", device.UniqueChipID?.ToString () ?? string.Empty); + item.SetMetadata ("CPUArchitecture", device.CpuArchitecture); + item.SetMetadata ("TransportType", device.TransportType); + item.SetMetadata ("PairingState", device.PairingState); + + // we provide the following metadata for both simulator and device + item.SetMetadata ("Description", device.Name); + item.SetMetadata ("Type", "Device"); + item.SetMetadata ("OSVersion", device.OSVersion); + item.SetMetadata ("UDID", udid); + + // compute the platform and runtime identifier + var runtimeIdentifier = ""; + ApplePlatform platform; + IPhoneDeviceType deviceType; + var discardedReason = ""; + switch (device.DeviceClass.ToLowerInvariant ()) { + case "iphone": + case "ipod": + runtimeIdentifier += "ios-"; + platform = ApplePlatform.iOS; + deviceType = IPhoneDeviceType.IPhone; + break; + case "ipad": + runtimeIdentifier += "ios-"; + platform = ApplePlatform.iOS; + deviceType = IPhoneDeviceType.IPad; + break; + case "appletv": + runtimeIdentifier += "tvos-"; + platform = ApplePlatform.TVOS; + deviceType = IPhoneDeviceType.TV; + break; + case "applewatch": + case "visionos": + default: + platform = ApplePlatform.None; + deviceType = IPhoneDeviceType.NotSet; + discardedReason = $"'{device.DeviceClass}' devices are not supported"; + break; + } + + if (string.IsNullOrEmpty (discardedReason)) { + switch (device.CpuArchitecture.ToLowerInvariant ()) { + case "arm64": + case "arm64e": + // arm64 and arm64e are both arm64 for our purposes + runtimeIdentifier += "arm64"; break; - case "applewatch": - case "visionos": default: - platform = ApplePlatform.None; - deviceType = IPhoneDeviceType.NotSet; - discardedReason = $"'{deviceClass}' devices are not supported"; + discardedReason = $"Unknown CPU architecture '{device.CpuArchitecture}'"; break; } + } - if (string.IsNullOrEmpty (discardedReason)) { - switch (cpuArchitecture.ToLowerInvariant ()) { - case "arm64": - case "arm64e": - // arm64 and arm64e are both arm64 for our purposes - runtimeIdentifier += "arm64"; - break; - default: - discardedReason = $"Unknown CPU architecture '{cpuArchitecture}'"; - break; - } - } - - Version.TryParse (productVersion, out var minimumOSVersion); - var maximumOSVersion = new Version (65535, 255, 255); + Version.TryParse (device.OSVersion, out var minimumOSVersion); + var maximumOSVersion = new Version (65535, 255, 255); - rv.Add (new DeviceInfo (item, [runtimeIdentifier], platform, deviceType, minimumOSVersion ?? new Version (0, 0), maximumOSVersion, discardedReason)); - } + rv.Add (new DeviceInfo (item, [runtimeIdentifier], platform, deviceType, minimumOSVersion ?? new Version (0, 0), maximumOSVersion, discardedReason)); } return rv; } @@ -294,20 +259,11 @@ async System.Threading.Tasks.Task> RunSimCtlAsync () foreach (var rt in parsedRuntimes) runtimesByIdentifier [rt.Identifier] = rt; - // devicetypes still needs raw JSON parsing (productFamily, min/maxRuntime not yet in shared parser) - var deviceTypes = new Dictionary (); - var options = new JsonDocumentOptions { - AllowTrailingCommas = true, - CommentHandling = JsonCommentHandling.Skip, - }; - using (var doc = JsonDocument.Parse (string.IsNullOrEmpty (json) ? "{}" : json, options)) { - if (doc.RootElement.TryGetProperty ("devicetypes", out var deviceTypesElement)) { - foreach (var deviceType in deviceTypesElement.EnumerateIfArray ()) { - var dtName = deviceType.GetStringProperty ("identifier") ?? string.Empty; - deviceTypes [dtName] = deviceType.Clone (); - } - } - } + // Use shared parser for devicetypes (productFamily, min/maxRuntime) + var parsedDeviceTypes = SimctlOutputParser.ParseDeviceTypes (json); + var deviceTypes = new Dictionary (); + foreach (var dt in parsedDeviceTypes) + deviceTypes [dt.Identifier] = dt; foreach (var device in parsedDevices) { var hasRuntime = runtimesByIdentifier.TryGetValue (device.RuntimeIdentifier, out var runtimeInfo); @@ -389,9 +345,8 @@ async System.Threading.Tasks.Task> RunSimCtlAsync () var minimumOSVersion = new Version (0, 0); var maximumOSVersion = new Version (65535, 255, 255); if (string.IsNullOrEmpty (discardedReason)) { - if (deviceTypes.TryGetValue (device.DeviceTypeIdentifier, out var deviceTypeElement)) { - var productFamily = deviceTypeElement.GetStringProperty ("productFamily") ?? string.Empty; - switch (productFamily.ToLowerInvariant ()) { + if (deviceTypes.TryGetValue (device.DeviceTypeIdentifier, out var deviceTypeInfo)) { + switch (deviceTypeInfo.ProductFamily.ToLowerInvariant ()) { case "iphone": case "ipod": deviceType = IPhoneDeviceType.IPhone; @@ -404,12 +359,12 @@ async System.Threading.Tasks.Task> RunSimCtlAsync () deviceType = IPhoneDeviceType.TV; break; default: - discardedReason = $"Unknown product family '{productFamily}'"; + discardedReason = $"Unknown product family '{deviceTypeInfo.ProductFamily}'"; break; } - if (Version.TryParse (deviceTypeElement.GetStringProperty ("minRuntimeVersionString"), out var parsedMinimumOSVersion)) + if (Version.TryParse (deviceTypeInfo.MinRuntimeVersionString, out var parsedMinimumOSVersion)) minimumOSVersion = parsedMinimumOSVersion; - if (Version.TryParse (deviceTypeElement.GetStringProperty ("maxRuntimeVersionString"), out var parsedMaximumOSVersion)) + if (Version.TryParse (deviceTypeInfo.MaxRuntimeVersionString, out var parsedMaximumOSVersion)) maximumOSVersion = parsedMaximumOSVersion; } else { discardedReason = $"Unknown device type identifier '{device.DeviceTypeIdentifier}'"; From 19339b10c47ba1ff068aec8d8a82a62a98f025df Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 4 Mar 2026 11:29:43 +0000 Subject: [PATCH 3/6] Bump System.Text.Json to 9.0.4 to match Xamarin.MacDev submodule Fix NU1605 package downgrade error: Xamarin.MacDev references System.Text.Json 9.0.4, while Xamarin.MacDev.Tasks pinned 8.0.5. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj index 212e46bab7a8..b8a8171a6251 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj +++ b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj @@ -21,7 +21,7 @@ - + From dcad5788e365924b95d0d978e0fa9ce5f68e13c6 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 4 Mar 2026 12:12:36 +0000 Subject: [PATCH 4/6] Fix build errors: nullability mismatch and missing using - XamarinTask: match ICustomLogger nullable signatures (Exception?, object?[]) - GetAvailableDevices: add using Xamarin.Utils for ApplePlatform - Update submodule to latest (6a2d99b) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/Xamarin.MacDev | 2 +- msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs | 1 + msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/external/Xamarin.MacDev b/external/Xamarin.MacDev index fe9f8003a480..6a2d99bfeaaf 160000 --- a/external/Xamarin.MacDev +++ b/external/Xamarin.MacDev @@ -1 +1 @@ -Subproject commit fe9f8003a480a5c8e5ba093b608ca33ab56bb024 +Subproject commit 6a2d99bfeaaff159f5a3d6e47a8d77485c173419 diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs index 829c6fcff879..57ecb577164d 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetAvailableDevices.cs @@ -10,6 +10,7 @@ using Xamarin.Localization.MSBuild; using Xamarin.MacDev.Models; using Xamarin.Messaging.Build.Client; +using Xamarin.Utils; namespace Xamarin.MacDev.Tasks; diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs index f25ef2fd388c..f5470c4cc104 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/XamarinTask.cs @@ -347,24 +347,24 @@ protected static string GetExecutable (List arguments, string toolName, } #region Xamarin.MacDev.ICustomLogger - void ICustomLogger.LogError (string message, Exception ex) + void ICustomLogger.LogError (string message, Exception? ex) { Log.LogError (message); if (ex is not null) Log.LogErrorFromException (ex); } - void ICustomLogger.LogWarning (string messageFormat, params object [] args) + void ICustomLogger.LogWarning (string messageFormat, params object? [] args) { Log.LogWarning (messageFormat, args); } - void ICustomLogger.LogInfo (string messageFormat, object [] args) + void ICustomLogger.LogInfo (string messageFormat, params object? [] args) { Log.LogMessage (MessageImportance.Normal, messageFormat, args); } - void ICustomLogger.LogDebug (string messageFormat, params object [] args) + void ICustomLogger.LogDebug (string messageFormat, params object? [] args) { Log.LogMessage (MessageImportance.Low, messageFormat, args); } From 0e139bb72a6829f98b2ed3b0fb0b47826bee3736 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 4 Mar 2026 12:51:41 +0000 Subject: [PATCH 5/6] Suppress CS0618 for AppleSdkSettings obsolete warnings AppleSdkSettings was marked [Obsolete] in macios-devtools PR #140 (XcodeLocator). Existing usages in Sdks.cs, CompileAppManifest.cs, and DetectSdkLocation.cs are intentional pending full migration. TreatWarningsAsErrors=true promotes the warning to an error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj index b8a8171a6251..ec5d6af8d5e3 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj +++ b/msbuild/Xamarin.MacDev.Tasks/Xamarin.MacDev.Tasks.csproj @@ -14,6 +14,7 @@ $(NoWarn);NU1701 $(NoWarn);MSB3277 $(NoWarn);8002 + $(NoWarn);CS0618 enable From 492716eb105b61ff9a03ff26a5fafecef1dd87e0 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 4 Mar 2026 14:44:26 +0000 Subject: [PATCH 6/6] Update macios-devtools submodule to main (PR #158 merged) Point submodule from feature/simulator-management branch to main, which now includes the merged simulator management APIs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/Xamarin.MacDev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/Xamarin.MacDev b/external/Xamarin.MacDev index 6a2d99bfeaaf..10a0c3c8ecc5 160000 --- a/external/Xamarin.MacDev +++ b/external/Xamarin.MacDev @@ -1 +1 @@ -Subproject commit 6a2d99bfeaaff159f5a3d6e47a8d77485c173419 +Subproject commit 10a0c3c8ecc5d0f509881c49cb0b4078b0a3a116