From 1592fc18e918f76c2d8af8ae08963a2727dd1c2e Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 9 Apr 2026 08:32:45 +0200 Subject: [PATCH 01/11] Default to latest available simulator runtime Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/GetMlaunchArguments.cs | 47 ++++++- tests/dotnet/UnitTests/MlaunchTest.cs | 4 +- .../TaskTests/GetMlaunchArgumentsTaskTests.cs | 119 ++++++++++++++++++ 3 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs index 459b84eff347..8a70a9cb161a 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs @@ -106,6 +106,51 @@ public IPhoneDeviceType DeviceType { return deviceTypes.Select (v => v.Identifier).ToList (); } + List<(long Version, string Identifier)>? GetSimulatorRuntimes () + { + var output = GetSimulatorList (); + if (string.IsNullOrEmpty (output)) + return null; + + var xml = new XmlDocument (); + xml.LoadXml (output); + + var runtimePrefix = $"com.apple.CoreSimulator.SimRuntime.{PlatformName}-"; + var nodes = xml.SelectNodes ("/MTouch/Simulator/SupportedRuntimes/SimRuntime")?.Cast () ?? Array.Empty (); + var runtimes = new List<(long Version, string Identifier)> (); + foreach (var node in nodes) { + var identifier = node.SelectSingleNode ("Identifier")?.InnerText ?? string.Empty; + if (!identifier.StartsWith (runtimePrefix, StringComparison.Ordinal)) + continue; + + var versionValue = node.SelectSingleNode ("Version")?.InnerText ?? string.Empty; + if (!long.TryParse (versionValue, out var version)) + version = 0; + + runtimes.Add ((version, identifier)); + } + + runtimes.Sort ((a, b) => { + var rv = a.Version.CompareTo (b.Version); + if (rv != 0) + return rv; + return StringComparer.Ordinal.Compare (a.Identifier, b.Identifier); + }); + return runtimes; + } + + string GetDefaultSimulatorRuntime () + { + var requestedRuntime = $"com.apple.CoreSimulator.SimRuntime.{PlatformName}-{SdkVersion.Replace ('.', '-')}"; + var simulatorRuntimes = GetSimulatorRuntimes (); + if (simulatorRuntimes?.Count > 0) { + if (simulatorRuntimes.Any (v => v.Identifier == requestedRuntime)) + return requestedRuntime; + return simulatorRuntimes [simulatorRuntimes.Count - 1].Identifier; + } + return requestedRuntime; + } + string? simulator_list; string? GetSimulatorList () { @@ -241,7 +286,7 @@ protected string GenerateCommandLineCommands () } if (SdkIsSimulator && string.IsNullOrEmpty (DeviceName)) { - var simruntime = $"com.apple.CoreSimulator.SimRuntime.{PlatformName}-{SdkVersion.Replace ('.', '-')}"; + var simruntime = GetDefaultSimulatorRuntime (); var simdevicetypes = GetDeviceTypes (true); string simdevicetype; diff --git a/tests/dotnet/UnitTests/MlaunchTest.cs b/tests/dotnet/UnitTests/MlaunchTest.cs index 012cf76ba6a7..0df71d16c2c8 100644 --- a/tests/dotnet/UnitTests/MlaunchTest.cs +++ b/tests/dotnet/UnitTests/MlaunchTest.cs @@ -52,9 +52,9 @@ public void GetMlaunchInstallArguments (ApplePlatform platform, string runtimeId public static object [] GetMlaunchRunArgumentsTestCases () { return new object [] { - new object [] {ApplePlatform.iOS, "iossimulator-x64;iossimulator-arm64", $":v2:runtime=com.apple.CoreSimulator.SimRuntime.iOS-{SdkVersions.iOS.Replace('.', '-')},devicetype=com.apple.CoreSimulator.SimDeviceType.iPhone-.*" }, + new object [] {ApplePlatform.iOS, "iossimulator-x64;iossimulator-arm64", @":v2:runtime=com.apple.CoreSimulator.SimRuntime.iOS-[^,]+,devicetype=com.apple.CoreSimulator.SimDeviceType.iPhone-.*" }, new object [] {ApplePlatform.iOS, "ios-arm64", "" }, - new object [] {ApplePlatform.TVOS, "tvossimulator-arm64", $":v2:runtime=com.apple.CoreSimulator.SimRuntime.tvOS-{SdkVersions.TVOS.Replace('.', '-')},devicetype=com.apple.CoreSimulator.SimDeviceType.Apple-TV-.*" }, + new object [] {ApplePlatform.TVOS, "tvossimulator-arm64", @":v2:runtime=com.apple.CoreSimulator.SimRuntime.tvOS-[^,]+,devicetype=com.apple.CoreSimulator.SimDeviceType.Apple-TV-.*" }, }; } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs new file mode 100644 index 000000000000..478a7e815b83 --- /dev/null +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.IO; +using System.Text; + +using NUnit.Framework; + +using Xamarin.Tests; +using Xamarin.Utils; + +#nullable enable + +namespace Xamarin.MacDev.Tasks { + [TestFixture] + public class GetMlaunchArgumentsTaskTests : TestBase { + + [Test] + public void SimulatorRuntimeSelection () + { + AssertSimulatorRuntime ( + CreateSimulatorXml ( + ("com.apple.CoreSimulator.SimRuntime.iOS-26-2", 1704448), + ("com.apple.CoreSimulator.SimRuntime.iOS-26-3", 1704705) + ), + "com.apple.CoreSimulator.SimRuntime.iOS-26-2"); + + AssertSimulatorRuntime ( + CreateSimulatorXml ( + ("com.apple.CoreSimulator.SimRuntime.iOS-26-1", 1704192), + ("com.apple.CoreSimulator.SimRuntime.iOS-26-3", 1704705) + ), + "com.apple.CoreSimulator.SimRuntime.iOS-26-3"); + } + + void AssertSimulatorRuntime (string simulatorXml, string expectedRuntime) + { + var task = CreateTask (); + task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (ApplePlatform.iOS).ToString (); + task.AppManifestPath = CreateAppManifest (); + task.LaunchApp = "MySimpleApp.app"; + task.MlaunchPath = CreateMlaunch (simulatorXml); + task.SdkIsSimulator = true; + task.SdkVersion = "26.2"; + task.WaitForExit = true; + + ExecuteTask (task); + + Assert.That (task.MlaunchArguments, Does.Contain ($"--device :v2:runtime={expectedRuntime},devicetype=com.apple.CoreSimulator.SimDeviceType.iPhone-17")); + } + + string CreateAppManifest () + { + var appManifestPath = Path.Combine (Cache.CreateTemporaryDirectory ("msbuild-tests"), "Info.plist"); + File.WriteAllText (appManifestPath, @" + + + + UIDeviceFamily + + 1 + + + +"); + return appManifestPath; + } + + string CreateMlaunch (string simulatorXml) + { + var mlaunchPath = Path.Combine (Cache.CreateTemporaryDirectory ("msbuild-tests"), "mlaunch"); + var script = new StringBuilder (); + script.AppendLine ("#!/bin/sh"); + script.AppendLine ("if [ \"$1\" = \"--listsim\" ]; then"); + script.AppendLine ("cat <<'EOF' > \"$2\""); + script.AppendLine (simulatorXml); + script.AppendLine ("EOF"); + script.AppendLine ("exit 0"); + script.AppendLine ("fi"); + script.AppendLine ("exit 1"); + File.WriteAllText (mlaunchPath, script.ToString ()); + if (OperatingSystem.IsMacOS ()) + File.SetUnixFileMode (mlaunchPath, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherExecute); + return mlaunchPath; + } + + string CreateSimulatorXml (params (string Identifier, long Version) [] runtimes) + { + var xml = new StringBuilder (); + xml.AppendLine (""); + xml.AppendLine (" "); + xml.AppendLine (" "); + foreach (var runtime in runtimes) { + xml.AppendLine (" "); + xml.AppendLine ($" {runtime.Identifier}"); + xml.AppendLine ($" {runtime.Version}"); + xml.AppendLine (" "); + } + xml.AppendLine (" "); + xml.AppendLine (" "); + xml.AppendLine (" "); + xml.AppendLine (" com.apple.CoreSimulator.SimDeviceType.iPhone-16"); + xml.AppendLine (" iPhone"); + xml.AppendLine (" 1704448"); + xml.AppendLine (" 4294967295"); + xml.AppendLine (" "); + xml.AppendLine (" "); + xml.AppendLine (" com.apple.CoreSimulator.SimDeviceType.iPhone-17"); + xml.AppendLine (" iPhone"); + xml.AppendLine (" 1704705"); + xml.AppendLine (" 4294967295"); + xml.AppendLine (" "); + xml.AppendLine (" "); + xml.AppendLine (" "); + xml.AppendLine (""); + return xml.ToString (); + } + } +} From 4cf27a40efe3559cacdad5a9a6741667c5a01a5a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 9 Apr 2026 09:05:23 +0200 Subject: [PATCH 02/11] Refactor simulator device selection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/GetMlaunchArguments.cs | 222 +++++++++--------- tests/dotnet/UnitTests/MlaunchTest.cs | 4 +- .../TaskTests/GetMlaunchArgumentsTaskTests.cs | 115 ++++++--- 3 files changed, 196 insertions(+), 145 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs index 8a70a9cb161a..a0745964fc45 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs @@ -58,10 +58,19 @@ public IPhoneDeviceType DeviceType { } } - List? GetDeviceTypes (bool onlyExact) + sealed class SimulatorDeviceInfo { + public string Identifier { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public long RuntimeVersion { get; set; } + public int DeviceTypeOrder { get; set; } = int.MinValue; + public bool IsCompatible { get; set; } + public string? NotApplicableBecause { get; set; } + } + + List<(long Min, long Max, string Identifier)>? GetDeviceTypes () { var output = GetSimulatorList (); - if (output is null) + if (string.IsNullOrEmpty (output)) return null; // Which product family are we looking for? @@ -100,55 +109,31 @@ public IPhoneDeviceType DeviceType { continue; deviceTypes.Add ((minRuntimeVersion, maxRuntimeVersion, identifier)); } - // Sort by minRuntimeVersion, this is a rudimentary way of sorting so that the last device is at the end. - deviceTypes.Sort ((a, b) => a.Min.CompareTo (b.Min)); - // Return the sorted list - return deviceTypes.Select (v => v.Identifier).ToList (); - } - - List<(long Version, string Identifier)>? GetSimulatorRuntimes () - { - var output = GetSimulatorList (); - if (string.IsNullOrEmpty (output)) - return null; - - var xml = new XmlDocument (); - xml.LoadXml (output); - - var runtimePrefix = $"com.apple.CoreSimulator.SimRuntime.{PlatformName}-"; - var nodes = xml.SelectNodes ("/MTouch/Simulator/SupportedRuntimes/SimRuntime")?.Cast () ?? Array.Empty (); - var runtimes = new List<(long Version, string Identifier)> (); - foreach (var node in nodes) { - var identifier = node.SelectSingleNode ("Identifier")?.InnerText ?? string.Empty; - if (!identifier.StartsWith (runtimePrefix, StringComparison.Ordinal)) - continue; - - var versionValue = node.SelectSingleNode ("Version")?.InnerText ?? string.Empty; - if (!long.TryParse (versionValue, out var version)) - version = 0; - - runtimes.Add ((version, identifier)); - } - runtimes.Sort ((a, b) => { - var rv = a.Version.CompareTo (b.Version); + deviceTypes.Sort ((a, b) => { + var rv = a.Min.CompareTo (b.Min); + if (rv != 0) + return rv; + rv = a.Max.CompareTo (b.Max); if (rv != 0) return rv; return StringComparer.Ordinal.Compare (a.Identifier, b.Identifier); }); - return runtimes; + + return deviceTypes; } - string GetDefaultSimulatorRuntime () + static bool TryGetSimulatorVersion (long versionValue, out Version? version) { - var requestedRuntime = $"com.apple.CoreSimulator.SimRuntime.{PlatformName}-{SdkVersion.Replace ('.', '-')}"; - var simulatorRuntimes = GetSimulatorRuntimes (); - if (simulatorRuntimes?.Count > 0) { - if (simulatorRuntimes.Any (v => v.Identifier == requestedRuntime)) - return requestedRuntime; - return simulatorRuntimes [simulatorRuntimes.Count - 1].Identifier; + if (versionValue <= 0) { + version = null; + return false; } - return requestedRuntime; + + var major = (int) ((versionValue >> 16) & 0xFF); + var minor = (int) ((versionValue >> 8) & 0xFF); + version = new Version (major, minor); + return true; } string? simulator_list; @@ -187,47 +172,90 @@ string GetDefaultSimulatorRuntime () return device_list; } - List<(string Identifier, string Name, string? NotApplicableBecause)> GetDeviceListForSimulator () + List GetSimulatorDevices () { - var rv = new List<(string Identifier, string Name, string? NotApplicableBecause)> (); + var rv = new List (); var output = GetSimulatorList (); if (string.IsNullOrEmpty (output)) return rv; - var deviceTypes = GetDeviceTypes (false); + var deviceTypes = GetDeviceTypes (); if (deviceTypes is null) return rv; - // Load mlaunch's output + var deviceTypeOrders = deviceTypes + .Select ((v, index) => (v.Identifier, Index: index)) + .ToDictionary (v => v.Identifier, v => v.Index, StringComparer.Ordinal); + var xml = new XmlDocument (); xml.LoadXml (output); - // Get the device types for the product family we're looking for + + var runtimePrefix = $"com.apple.CoreSimulator.SimRuntime.{PlatformName}-"; + var runtimeVersions = new Dictionary (StringComparer.Ordinal); + var runtimeNodes = xml.SelectNodes ("/MTouch/Simulator/SupportedRuntimes/SimRuntime")?.Cast () ?? Array.Empty (); + foreach (var node in runtimeNodes) { + var identifier = node.SelectSingleNode ("Identifier")?.InnerText ?? string.Empty; + var versionValue = node.SelectSingleNode ("Version")?.InnerText ?? string.Empty; + if (long.TryParse (versionValue, out var version)) + runtimeVersions [identifier] = version; + } + var nodes = xml.SelectNodes ($"/MTouch/Simulator/AvailableDevices/SimDevice")?.Cast () ?? Array.Empty (); foreach (var node in nodes) { + var device = new SimulatorDeviceInfo { + Identifier = node.Attributes? ["UDID"]?.Value ?? string.Empty, + Name = node.Attributes? ["Name"]?.Value ?? string.Empty, + }; + var simDeviceType = node.SelectSingleNode ("SimDeviceType")?.InnerText ?? string.Empty; - if (!deviceTypes.Contains (simDeviceType)) - continue; - var udid = node.Attributes? ["UDID"]?.Value ?? string.Empty; - var name = node.Attributes? ["Name"]?.Value ?? string.Empty; - string? notApplicableBecause = null; + var simRuntime = node.SelectSingleNode ("SimRuntime")?.InnerText ?? string.Empty; + runtimeVersions.TryGetValue (simRuntime, out var simRuntimeVersion); + device.RuntimeVersion = simRuntimeVersion; - var simRuntime = node.SelectSingleNode ("SimRuntime")?.InnerText; - if (!string.IsNullOrEmpty (simRuntime)) { - var simRuntimeVersionString = xml.SelectSingleNode ($"/MTouch/Simulator/SupportedRuntimes/SimRuntime[Identifier='{simRuntime}']/Version")?.InnerText; - if (int.TryParse (simRuntimeVersionString, out var simRuntimeVersionNumber)) { - var simRuntimeVersionMajor = (simRuntimeVersionNumber >> 16) & 0xFF; - var simRuntimeVersionMinor = (simRuntimeVersionNumber >> 8) & 0xFF; - var simRuntimeVersion = new Version (simRuntimeVersionMajor, simRuntimeVersionMinor); - if (Version.TryParse (SupportedOSPlatformVersion, out var supportedOSPlatformVersion) && simRuntimeVersion < supportedOSPlatformVersion) - notApplicableBecause = $" [OS version ({simRuntimeVersion}) lower than minimum supported platform version ({SupportedOSPlatformVersion}) for this app]"; - } + string? notApplicableBecause = null; + if (!simRuntime.StartsWith (runtimePrefix, StringComparison.Ordinal)) { + notApplicableBecause = $" [Simulator runtime ({simRuntime}) does not match the requested platform ({PlatformName}) for this app]"; + } else if (!deviceTypeOrders.TryGetValue (simDeviceType, out var deviceTypeOrder)) { + notApplicableBecause = $" [Simulator device type ({simDeviceType}) is not applicable for this app]"; + } else { + device.IsCompatible = true; + device.DeviceTypeOrder = deviceTypeOrder; + if (Version.TryParse (SupportedOSPlatformVersion, out var supportedOSPlatformVersion) && TryGetSimulatorVersion (simRuntimeVersion, out var simRuntimeVersionValue) && simRuntimeVersionValue is not null && simRuntimeVersionValue < supportedOSPlatformVersion) + notApplicableBecause = $" [OS version ({simRuntimeVersionValue}) lower than minimum supported platform version ({SupportedOSPlatformVersion}) for this app]"; } - rv.Add ((udid, name, notApplicableBecause)); + + device.NotApplicableBecause = notApplicableBecause; + rv.Add (device); } + return rv; } + string SelectSimulatorDevice () + { + return GetSimulatorDevices () + .Where (v => v.IsCompatible && string.IsNullOrEmpty (v.NotApplicableBecause)) + .OrderByDescending (v => v.RuntimeVersion) + .ThenByDescending (v => v.DeviceTypeOrder) + .ThenBy (v => v.Name, StringComparer.Ordinal) + .ThenBy (v => v.Identifier, StringComparer.Ordinal) + .Select (v => v.Identifier) + .FirstOrDefault () ?? string.Empty; + } + + List<(string Identifier, string Name, string? NotApplicableBecause)> GetDeviceListForSimulator () + { + return GetSimulatorDevices () + .Where (v => v.IsCompatible) + .OrderByDescending (v => v.RuntimeVersion) + .ThenByDescending (v => v.DeviceTypeOrder) + .ThenBy (v => v.Name, StringComparer.Ordinal) + .ThenBy (v => v.Identifier, StringComparer.Ordinal) + .Select (v => (v.Identifier, v.Name, v.NotApplicableBecause)) + .ToList (); + } + List<(string Identifier, string Name, string? NotApplicableBecause)> GetDeviceListForDevice () { var rv = new List<(string Identifier, string Name, string? NotApplicableBecause)> (); @@ -274,6 +302,8 @@ string GetDefaultSimulatorRuntime () protected string GenerateCommandLineCommands () { var sb = new List (); + string? selectedSimulator = null; + var deviceName = DeviceName; if (!string.IsNullOrEmpty (LaunchApp)) { sb.Add (SdkIsSimulator ? "--launchsim" : "--launchdev"); @@ -285,58 +315,38 @@ protected string GenerateCommandLineCommands () sb.Add (InstallApp); } - if (SdkIsSimulator && string.IsNullOrEmpty (DeviceName)) { - var simruntime = GetDefaultSimulatorRuntime (); - var simdevicetypes = GetDeviceTypes (true); - string simdevicetype; - - if (simdevicetypes?.Count > 0) { - // Use the latest device type we can find. This seems to be what Xcode does by default. - simdevicetype = simdevicetypes.Last (); - } else { - // We couldn't find any device types, so pick one. - switch (Platform) { - case ApplePlatform.iOS: - // Don't try to launch an iPad-only app on an iPhone - if (DeviceType == IPhoneDeviceType.IPad) { - simdevicetype = "com.apple.CoreSimulator.SimDeviceType.iPad--7th-generation-"; - } else { - simdevicetype = "com.apple.CoreSimulator.SimDeviceType.iPhone-11"; - } - break; - case ApplePlatform.TVOS: - simdevicetype = "com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K-1080p"; - break; - default: - throw new InvalidOperationException (string.Format (MSBStrings.InvalidPlatform, Platform)); - } - } - DeviceName = $":v2:runtime={simruntime},devicetype={simdevicetype}"; + if (SdkIsSimulator && string.IsNullOrEmpty (deviceName)) { + selectedSimulator = SelectSimulatorDevice (); + deviceName = selectedSimulator; } - if (!string.IsNullOrEmpty (DeviceName)) { + if (!string.IsNullOrEmpty (deviceName)) { if (SdkIsSimulator) { sb.Add ("--device"); - // Figure out whether we got the exact name of a simulator, in which case construct the corresponding argument. - string? simulator = null; - var deviceList = GetDeviceListForSimulator (); - var simulatorsByIdentifier = deviceList.Where (v => v.Identifier == DeviceName).ToArray (); - if (simulatorsByIdentifier.Length == 1) { - simulator = simulatorsByIdentifier [0].Identifier; - } else { - var simulatorsByName = deviceList.Where (v => v.Name == DeviceName).ToArray (); - if (simulatorsByName.Length == 1) - simulator = simulatorsByName [0].Identifier; - } - if (!string.IsNullOrEmpty (simulator)) { - sb.Add ($":v2:udid={simulator}"); + if (!string.IsNullOrEmpty (selectedSimulator)) { + sb.Add ($":v2:udid={selectedSimulator}"); } else { - sb.Add (DeviceName); + // Figure out whether we got the exact name of a simulator, in which case construct the corresponding argument. + string? simulator = null; + var deviceList = GetDeviceListForSimulator (); + var simulatorsByIdentifier = deviceList.Where (v => v.Identifier == deviceName).ToArray (); + if (simulatorsByIdentifier.Length == 1) { + simulator = simulatorsByIdentifier [0].Identifier; + } else { + var simulatorsByName = deviceList.Where (v => v.Name == deviceName).ToArray (); + if (simulatorsByName.Length == 1) + simulator = simulatorsByName [0].Identifier; + } + if (!string.IsNullOrEmpty (simulator)) { + sb.Add ($":v2:udid={simulator}"); + } else { + sb.Add (deviceName); + } } } else { sb.Add ("--devname"); - sb.Add (DeviceName); + sb.Add (deviceName); } } diff --git a/tests/dotnet/UnitTests/MlaunchTest.cs b/tests/dotnet/UnitTests/MlaunchTest.cs index 0df71d16c2c8..d9be16ed88ba 100644 --- a/tests/dotnet/UnitTests/MlaunchTest.cs +++ b/tests/dotnet/UnitTests/MlaunchTest.cs @@ -52,9 +52,9 @@ public void GetMlaunchInstallArguments (ApplePlatform platform, string runtimeId public static object [] GetMlaunchRunArgumentsTestCases () { return new object [] { - new object [] {ApplePlatform.iOS, "iossimulator-x64;iossimulator-arm64", @":v2:runtime=com.apple.CoreSimulator.SimRuntime.iOS-[^,]+,devicetype=com.apple.CoreSimulator.SimDeviceType.iPhone-.*" }, + new object [] {ApplePlatform.iOS, "iossimulator-x64;iossimulator-arm64", @":v2:udid=[A-F0-9-]+" }, new object [] {ApplePlatform.iOS, "ios-arm64", "" }, - new object [] {ApplePlatform.TVOS, "tvossimulator-arm64", @":v2:runtime=com.apple.CoreSimulator.SimRuntime.tvOS-[^,]+,devicetype=com.apple.CoreSimulator.SimDeviceType.Apple-TV-.*" }, + new object [] {ApplePlatform.TVOS, "tvossimulator-arm64", @":v2:udid=[A-F0-9-]+" }, }; } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs index 478a7e815b83..5b8b620186fe 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs @@ -16,28 +16,60 @@ namespace Xamarin.MacDev.Tasks { public class GetMlaunchArgumentsTaskTests : TestBase { [Test] - public void SimulatorRuntimeSelection () + public void SelectSimulatorDeviceSortsByRuntimeDeviceTypeNameAndUdid () { - AssertSimulatorRuntime ( + AssertSelectedSimulator ( CreateSimulatorXml ( - ("com.apple.CoreSimulator.SimRuntime.iOS-26-2", 1704448), - ("com.apple.CoreSimulator.SimRuntime.iOS-26-3", 1704705) + runtimes: [ + ("com.apple.CoreSimulator.SimRuntime.iOS-26-2", 1704448), + ("com.apple.CoreSimulator.SimRuntime.iOS-26-3", 1704705), + ], + deviceTypes: [ + ("com.apple.CoreSimulator.SimDeviceType.iPhone-16", "iPhone", 1704448, 4294967295), + ("com.apple.CoreSimulator.SimDeviceType.iPhone-17", "iPhone", 1704705, 4294967295), + ], + availableDevices: [ + ("UDID-5", "A Older Runtime", "com.apple.CoreSimulator.SimRuntime.iOS-26-2", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), + ("UDID-4", "A Older Device Type", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-16"), + ("UDID-3", "B Name", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), + ("UDID-2", "A Name", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), + ("UDID-1", "A Name", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), + ] ), - "com.apple.CoreSimulator.SimRuntime.iOS-26-2"); + "UDID-1", + [1, 2]); + } - AssertSimulatorRuntime ( + [Test] + public void SelectSimulatorDeviceFiltersNonApplicableDevices () + { + AssertSelectedSimulator ( CreateSimulatorXml ( - ("com.apple.CoreSimulator.SimRuntime.iOS-26-1", 1704192), - ("com.apple.CoreSimulator.SimRuntime.iOS-26-3", 1704705) + runtimes: [ + ("com.apple.CoreSimulator.SimRuntime.iOS-26-2", 1704448), + ("com.apple.CoreSimulator.SimRuntime.iOS-26-3", 1704705), + ("com.apple.CoreSimulator.SimRuntime.tvOS-26-2", 1704448), + ], + deviceTypes: [ + ("com.apple.CoreSimulator.SimDeviceType.iPhone-17", "iPhone", 1704705, 4294967295), + ("com.apple.CoreSimulator.SimDeviceType.iPad-Pro", "iPad", 1704448, 4294967295), + ("com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K", "Apple TV", 1704448, 4294967295), + ], + availableDevices: [ + ("UDID-IPHONE", "Newest iPhone", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), + ("UDID-TV", "Apple TV", "com.apple.CoreSimulator.SimRuntime.tvOS-26-2", "com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K"), + ("UDID-IPAD", "Newest iPad", "com.apple.CoreSimulator.SimRuntime.iOS-26-2", "com.apple.CoreSimulator.SimDeviceType.iPad-Pro"), + ] ), - "com.apple.CoreSimulator.SimRuntime.iOS-26-3"); + "UDID-IPAD", + [2]); } - void AssertSimulatorRuntime (string simulatorXml, string expectedRuntime) + void AssertSelectedSimulator (string simulatorXml, string expectedUdid, int [] deviceFamilies) { var task = CreateTask (); task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (ApplePlatform.iOS).ToString (); - task.AppManifestPath = CreateAppManifest (); + task.AppManifestPath = CreateAppManifest (deviceFamilies); task.LaunchApp = "MySimpleApp.app"; task.MlaunchPath = CreateMlaunch (simulatorXml); task.SdkIsSimulator = true; @@ -46,23 +78,25 @@ void AssertSimulatorRuntime (string simulatorXml, string expectedRuntime) ExecuteTask (task); - Assert.That (task.MlaunchArguments, Does.Contain ($"--device :v2:runtime={expectedRuntime},devicetype=com.apple.CoreSimulator.SimDeviceType.iPhone-17")); + Assert.That (task.MlaunchArguments, Does.Contain ($"--device :v2:udid={expectedUdid}")); } - string CreateAppManifest () + string CreateAppManifest (int [] deviceFamilies) { var appManifestPath = Path.Combine (Cache.CreateTemporaryDirectory ("msbuild-tests"), "Info.plist"); - File.WriteAllText (appManifestPath, @" - - - - UIDeviceFamily - - 1 - - - -"); + var plist = new StringBuilder (); + plist.AppendLine (@""); + plist.AppendLine (@""); + plist.AppendLine (@""); + plist.AppendLine (""); + plist.AppendLine ("\tUIDeviceFamily"); + plist.AppendLine ("\t"); + foreach (var family in deviceFamilies) + plist.AppendLine ($"\t\t{family}"); + plist.AppendLine ("\t"); + plist.AppendLine (""); + plist.AppendLine (""); + File.WriteAllText (appManifestPath, plist.ToString ()); return appManifestPath; } @@ -84,7 +118,10 @@ string CreateMlaunch (string simulatorXml) return mlaunchPath; } - string CreateSimulatorXml (params (string Identifier, long Version) [] runtimes) + string CreateSimulatorXml ( + (string Identifier, long Version) [] runtimes, + (string Identifier, string ProductFamilyId, long MinRuntimeVersion, long MaxRuntimeVersion) [] deviceTypes, + (string Udid, string Name, string Runtime, string DeviceType) [] availableDevices) { var xml = new StringBuilder (); xml.AppendLine (""); @@ -98,19 +135,23 @@ string CreateSimulatorXml (params (string Identifier, long Version) [] runtimes) } xml.AppendLine (" "); xml.AppendLine (" "); - xml.AppendLine (" "); - xml.AppendLine (" com.apple.CoreSimulator.SimDeviceType.iPhone-16"); - xml.AppendLine (" iPhone"); - xml.AppendLine (" 1704448"); - xml.AppendLine (" 4294967295"); - xml.AppendLine (" "); - xml.AppendLine (" "); - xml.AppendLine (" com.apple.CoreSimulator.SimDeviceType.iPhone-17"); - xml.AppendLine (" iPhone"); - xml.AppendLine (" 1704705"); - xml.AppendLine (" 4294967295"); - xml.AppendLine (" "); + foreach (var deviceType in deviceTypes) { + xml.AppendLine (" "); + xml.AppendLine ($" {deviceType.Identifier}"); + xml.AppendLine ($" {deviceType.ProductFamilyId}"); + xml.AppendLine ($" {deviceType.MinRuntimeVersion}"); + xml.AppendLine ($" {deviceType.MaxRuntimeVersion}"); + xml.AppendLine (" "); + } xml.AppendLine (" "); + xml.AppendLine (" "); + foreach (var device in availableDevices) { + xml.AppendLine ($" "); + xml.AppendLine ($" {device.Runtime}"); + xml.AppendLine ($" {device.DeviceType}"); + xml.AppendLine (" "); + } + xml.AppendLine (" "); xml.AppendLine (" "); xml.AppendLine (""); return xml.ToString (); From 103252477edacbfa7277493e60e7b8e99d4e07da Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 9 Apr 2026 10:43:51 +0200 Subject: [PATCH 03/11] Use GetAvailableDevices for simulator choice Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/targets/Microsoft.Sdk.Mobile.targets | 9 +- .../Tasks/GetMlaunchArguments.cs | 8 ++ .../TaskTests/GetMlaunchArgumentsTaskTests.cs | 136 ++++-------------- 3 files changed, 39 insertions(+), 114 deletions(-) diff --git a/dotnet/targets/Microsoft.Sdk.Mobile.targets b/dotnet/targets/Microsoft.Sdk.Mobile.targets index db28965e02bd..fb6b4da20e7e 100644 --- a/dotnet/targets/Microsoft.Sdk.Mobile.targets +++ b/dotnet/targets/Microsoft.Sdk.Mobile.targets @@ -20,12 +20,13 @@ + DependsOnTargets="_DetectSdkLocations;_GenerateBundleName;_DetectAppManifest;ComputeAvailableDevices"> - + @@ -53,6 +54,7 @@ - + @@ -105,6 +107,7 @@ AdditionalArguments="@(MlaunchAdditionalArguments)" AppManifestPath="$(_AppBundleManifestPath)" CaptureOutput="$(_MlaunchCaptureOutput)" + Devices="@(Devices)" DeviceName="$(Device)" EnvironmentVariables="@(MlaunchEnvironmentVariables)" LaunchApp="$(_AppBundlePath)" diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs index a0745964fc45..349379cbcfb8 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs @@ -42,6 +42,8 @@ public class GetMlaunchArguments : XamarinTask, ICancelableTask { [Required] public string MlaunchPath { get; set; } = string.Empty; + public ITaskItem [] Devices { get; set; } = Array.Empty (); + [Output] public string MlaunchArguments { get; set; } = string.Empty; @@ -234,6 +236,12 @@ List GetSimulatorDevices () string SelectSimulatorDevice () { + if (Devices.Length > 0) { + var simulator = Devices.FirstOrDefault (v => string.Equals (v.GetMetadata ("Type"), "Simulator", StringComparison.OrdinalIgnoreCase)); + if (simulator is not null) + return string.IsNullOrEmpty (simulator.GetMetadata ("UDID")) ? simulator.ItemSpec : simulator.GetMetadata ("UDID"); + } + return GetSimulatorDevices () .Where (v => v.IsCompatible && string.IsNullOrEmpty (v.NotApplicableBecause)) .OrderByDescending (v => v.RuntimeVersion) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs index 5b8b620186fe..28d44fd11d77 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs @@ -2,8 +2,11 @@ // Licensed under the MIT License. using System.IO; +using System.Linq; using System.Text; +using Microsoft.Build.Utilities; + using NUnit.Framework; using Xamarin.Tests; @@ -16,72 +19,40 @@ namespace Xamarin.MacDev.Tasks { public class GetMlaunchArgumentsTaskTests : TestBase { [Test] - public void SelectSimulatorDeviceSortsByRuntimeDeviceTypeNameAndUdid () - { - AssertSelectedSimulator ( - CreateSimulatorXml ( - runtimes: [ - ("com.apple.CoreSimulator.SimRuntime.iOS-26-2", 1704448), - ("com.apple.CoreSimulator.SimRuntime.iOS-26-3", 1704705), - ], - deviceTypes: [ - ("com.apple.CoreSimulator.SimDeviceType.iPhone-16", "iPhone", 1704448, 4294967295), - ("com.apple.CoreSimulator.SimDeviceType.iPhone-17", "iPhone", 1704705, 4294967295), - ], - availableDevices: [ - ("UDID-5", "A Older Runtime", "com.apple.CoreSimulator.SimRuntime.iOS-26-2", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), - ("UDID-4", "A Older Device Type", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-16"), - ("UDID-3", "B Name", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), - ("UDID-2", "A Name", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), - ("UDID-1", "A Name", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), - ] - ), - "UDID-1", - [1, 2]); - } - - [Test] - public void SelectSimulatorDeviceFiltersNonApplicableDevices () - { - AssertSelectedSimulator ( - CreateSimulatorXml ( - runtimes: [ - ("com.apple.CoreSimulator.SimRuntime.iOS-26-2", 1704448), - ("com.apple.CoreSimulator.SimRuntime.iOS-26-3", 1704705), - ("com.apple.CoreSimulator.SimRuntime.tvOS-26-2", 1704448), - ], - deviceTypes: [ - ("com.apple.CoreSimulator.SimDeviceType.iPhone-17", "iPhone", 1704705, 4294967295), - ("com.apple.CoreSimulator.SimDeviceType.iPad-Pro", "iPad", 1704448, 4294967295), - ("com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K", "Apple TV", 1704448, 4294967295), - ], - availableDevices: [ - ("UDID-IPHONE", "Newest iPhone", "com.apple.CoreSimulator.SimRuntime.iOS-26-3", "com.apple.CoreSimulator.SimDeviceType.iPhone-17"), - ("UDID-TV", "Apple TV", "com.apple.CoreSimulator.SimRuntime.tvOS-26-2", "com.apple.CoreSimulator.SimDeviceType.Apple-TV-4K"), - ("UDID-IPAD", "Newest iPad", "com.apple.CoreSimulator.SimRuntime.iOS-26-2", "com.apple.CoreSimulator.SimDeviceType.iPad-Pro"), - ] - ), - "UDID-IPAD", - [2]); - } - - void AssertSelectedSimulator (string simulatorXml, string expectedUdid, int [] deviceFamilies) + public void SelectSimulatorDeviceUsesFirstAvailableSimulator () { var task = CreateTask (); task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (ApplePlatform.iOS).ToString (); - task.AppManifestPath = CreateAppManifest (deviceFamilies); + task.AppManifestPath = CreateAppManifest (1, 2); + task.Devices = CreateDevices ( + ("DEVICE-1", "Connected iPhone", "Device"), + ("SIM-2", "Preferred Simulator", "Simulator"), + ("SIM-1", "Another Simulator", "Simulator") + ); task.LaunchApp = "MySimpleApp.app"; - task.MlaunchPath = CreateMlaunch (simulatorXml); + task.MlaunchPath = "/usr/bin/false"; task.SdkIsSimulator = true; task.SdkVersion = "26.2"; task.WaitForExit = true; ExecuteTask (task); - Assert.That (task.MlaunchArguments, Does.Contain ($"--device :v2:udid={expectedUdid}")); + Assert.That (task.MlaunchArguments, Does.Contain ("--device :v2:udid=SIM-2")); } - string CreateAppManifest (int [] deviceFamilies) + static TaskItem [] CreateDevices (params (string Udid, string Name, string Type) [] devices) + { + return devices.Select (v => { + var item = new TaskItem (v.Udid); + item.SetMetadata ("Description", v.Name); + item.SetMetadata ("Name", v.Name); + item.SetMetadata ("Type", v.Type); + item.SetMetadata ("UDID", v.Udid); + return item; + }).ToArray (); + } + + static string CreateAppManifest (params int [] deviceFamilies) { var appManifestPath = Path.Combine (Cache.CreateTemporaryDirectory ("msbuild-tests"), "Info.plist"); var plist = new StringBuilder (); @@ -99,62 +70,5 @@ string CreateAppManifest (int [] deviceFamilies) File.WriteAllText (appManifestPath, plist.ToString ()); return appManifestPath; } - - string CreateMlaunch (string simulatorXml) - { - var mlaunchPath = Path.Combine (Cache.CreateTemporaryDirectory ("msbuild-tests"), "mlaunch"); - var script = new StringBuilder (); - script.AppendLine ("#!/bin/sh"); - script.AppendLine ("if [ \"$1\" = \"--listsim\" ]; then"); - script.AppendLine ("cat <<'EOF' > \"$2\""); - script.AppendLine (simulatorXml); - script.AppendLine ("EOF"); - script.AppendLine ("exit 0"); - script.AppendLine ("fi"); - script.AppendLine ("exit 1"); - File.WriteAllText (mlaunchPath, script.ToString ()); - if (OperatingSystem.IsMacOS ()) - File.SetUnixFileMode (mlaunchPath, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherExecute); - return mlaunchPath; - } - - string CreateSimulatorXml ( - (string Identifier, long Version) [] runtimes, - (string Identifier, string ProductFamilyId, long MinRuntimeVersion, long MaxRuntimeVersion) [] deviceTypes, - (string Udid, string Name, string Runtime, string DeviceType) [] availableDevices) - { - var xml = new StringBuilder (); - xml.AppendLine (""); - xml.AppendLine (" "); - xml.AppendLine (" "); - foreach (var runtime in runtimes) { - xml.AppendLine (" "); - xml.AppendLine ($" {runtime.Identifier}"); - xml.AppendLine ($" {runtime.Version}"); - xml.AppendLine (" "); - } - xml.AppendLine (" "); - xml.AppendLine (" "); - foreach (var deviceType in deviceTypes) { - xml.AppendLine (" "); - xml.AppendLine ($" {deviceType.Identifier}"); - xml.AppendLine ($" {deviceType.ProductFamilyId}"); - xml.AppendLine ($" {deviceType.MinRuntimeVersion}"); - xml.AppendLine ($" {deviceType.MaxRuntimeVersion}"); - xml.AppendLine (" "); - } - xml.AppendLine (" "); - xml.AppendLine (" "); - foreach (var device in availableDevices) { - xml.AppendLine ($" "); - xml.AppendLine ($" {device.Runtime}"); - xml.AppendLine ($" {device.DeviceType}"); - xml.AppendLine (" "); - } - xml.AppendLine (" "); - xml.AppendLine (" "); - xml.AppendLine (""); - return xml.ToString (); - } } } From 37430a94552159b7ae4d23c57fa49cd7f39fb397 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 9 Apr 2026 12:16:46 +0200 Subject: [PATCH 04/11] Error when no launch devices exist Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/GetMlaunchArguments.cs | 22 +++++++++---------- .../TaskTests/GetMlaunchArgumentsTaskTests.cs | 17 ++++++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs index 349379cbcfb8..268be679dfbe 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs @@ -236,20 +236,13 @@ List GetSimulatorDevices () string SelectSimulatorDevice () { - if (Devices.Length > 0) { - var simulator = Devices.FirstOrDefault (v => string.Equals (v.GetMetadata ("Type"), "Simulator", StringComparison.OrdinalIgnoreCase)); - if (simulator is not null) - return string.IsNullOrEmpty (simulator.GetMetadata ("UDID")) ? simulator.ItemSpec : simulator.GetMetadata ("UDID"); + var simulator = Devices.FirstOrDefault (v => string.Equals (v.GetMetadata ("Type"), "Simulator", StringComparison.OrdinalIgnoreCase)); + if (simulator is null) { + Log.LogError ("The 'Devices' item group does not contain any simulators."); + return string.Empty; } - return GetSimulatorDevices () - .Where (v => v.IsCompatible && string.IsNullOrEmpty (v.NotApplicableBecause)) - .OrderByDescending (v => v.RuntimeVersion) - .ThenByDescending (v => v.DeviceTypeOrder) - .ThenBy (v => v.Name, StringComparer.Ordinal) - .ThenBy (v => v.Identifier, StringComparer.Ordinal) - .Select (v => v.Identifier) - .FirstOrDefault () ?? string.Empty; + return string.IsNullOrEmpty (simulator.GetMetadata ("UDID")) ? simulator.ItemSpec : simulator.GetMetadata ("UDID"); } List<(string Identifier, string Name, string? NotApplicableBecause)> GetDeviceListForSimulator () @@ -461,6 +454,11 @@ public override bool Execute () return !Log.HasLoggedErrors; } + if (Devices.Length == 0) { + Log.LogError ("The 'Devices' item group is empty."); + return false; + } + MlaunchArguments = GenerateCommandLineCommands (); return !Log.HasLoggedErrors; } diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs index 28d44fd11d77..06276132d3cd 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs @@ -40,6 +40,23 @@ public void SelectSimulatorDeviceUsesFirstAvailableSimulator () Assert.That (task.MlaunchArguments, Does.Contain ("--device :v2:udid=SIM-2")); } + [Test] + public void ErrorsIfDevicesItemGroupIsEmpty () + { + var task = CreateTask (); + task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (ApplePlatform.iOS).ToString (); + task.AppManifestPath = CreateAppManifest (1, 2); + task.LaunchApp = "MySimpleApp.app"; + task.MlaunchPath = "/usr/bin/false"; + task.SdkIsSimulator = true; + task.SdkVersion = "26.2"; + task.WaitForExit = true; + + ExecuteTask (task, expectedErrorCount: 1); + + Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("The 'Devices' item group is empty.")); + } + static TaskItem [] CreateDevices (params (string Udid, string Name, string Type) [] devices) { return devices.Select (v => { From c47f2f75abc6cb1e733759ba55abf9dbde151ef0 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 9 Apr 2026 12:57:30 +0200 Subject: [PATCH 05/11] wip --- msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs index 268be679dfbe..b6c930dbcad5 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs @@ -455,7 +455,7 @@ public override bool Execute () } if (Devices.Length == 0) { - Log.LogError ("The 'Devices' item group is empty."); + Log.LogError ("No applicable and available devices found."); return false; } From 2b54ec400a866b36fa8eeaa911bc56902383970f Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 9 Apr 2026 13:18:11 +0200 Subject: [PATCH 06/11] Report discarded launch devices Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/targets/Microsoft.Sdk.Mobile.targets | 3 + .../Tasks/GetMlaunchArguments.cs | 80 ++++++++++++++++++- tests/dotnet/UnitTests/MlaunchTest.cs | 16 +++- .../TaskTests/GetMlaunchArgumentsTaskTests.cs | 54 +++++++++++-- 4 files changed, 140 insertions(+), 13 deletions(-) diff --git a/dotnet/targets/Microsoft.Sdk.Mobile.targets b/dotnet/targets/Microsoft.Sdk.Mobile.targets index fb6b4da20e7e..b852c5aa027a 100644 --- a/dotnet/targets/Microsoft.Sdk.Mobile.targets +++ b/dotnet/targets/Microsoft.Sdk.Mobile.targets @@ -26,6 +26,7 @@ (); + public ITaskItem [] DiscardedDevices { get; set; } = Array.Empty (); [Output] public string MlaunchArguments { get; set; } = string.Empty; @@ -238,15 +239,21 @@ string SelectSimulatorDevice () { var simulator = Devices.FirstOrDefault (v => string.Equals (v.GetMetadata ("Type"), "Simulator", StringComparison.OrdinalIgnoreCase)); if (simulator is null) { - Log.LogError ("The 'Devices' item group does not contain any simulators."); - return string.Empty; + var sb = new StringBuilder (); + sb.AppendLine ("The 'Devices' item group does not contain any simulators."); + AppendDiscardedDevices (sb, "", "Simulator"); + Log.LogError (sb.ToString ().TrimEnd ()); + return ""; } - return string.IsNullOrEmpty (simulator.GetMetadata ("UDID")) ? simulator.ItemSpec : simulator.GetMetadata ("UDID"); + return GetDeviceIdentifier (simulator); } List<(string Identifier, string Name, string? NotApplicableBecause)> GetDeviceListForSimulator () { + if (Devices.Length > 0 || DiscardedDevices.Length > 0) + return GetDevicesFromTaskItems ("Simulator", Devices); + return GetSimulatorDevices () .Where (v => v.IsCompatible) .OrderByDescending (v => v.RuntimeVersion) @@ -259,6 +266,9 @@ string SelectSimulatorDevice () List<(string Identifier, string Name, string? NotApplicableBecause)> GetDeviceListForDevice () { + if (Devices.Length > 0 || DiscardedDevices.Length > 0) + return GetDevicesFromTaskItems ("Device", Devices); + var rv = new List<(string Identifier, string Name, string? NotApplicableBecause)> (); var output = GetDeviceList (); @@ -300,6 +310,66 @@ string SelectSimulatorDevice () return rv; } + static string GetDeviceIdentifier (ITaskItem device) + { + var udid = device.GetMetadata ("UDID"); + return string.IsNullOrEmpty (udid) ? device.ItemSpec : udid; + } + + static string GetDeviceName (ITaskItem device) + { + var name = device.GetMetadata ("Name"); + if (string.IsNullOrEmpty (name)) + name = device.GetMetadata ("Description"); + return string.IsNullOrEmpty (name) ? GetDeviceIdentifier (device) : name; + } + + static string FormatDevice (ITaskItem device) + { + var identifier = GetDeviceIdentifier (device); + var name = GetDeviceName (device); + return name == identifier ? identifier : $"{name} ({identifier})"; + } + + static List<(string Identifier, string Name, string? NotApplicableBecause)> GetDevicesFromTaskItems (string type, ITaskItem [] items) + { + return items + .Where (v => string.Equals (v.GetMetadata ("Type"), type, StringComparison.OrdinalIgnoreCase)) + .Select (v => { + var reason = v.GetMetadata ("DiscardedReason"); + return (GetDeviceIdentifier (v), GetDeviceName (v), string.IsNullOrEmpty (reason) ? null : reason); + }) + .ToList (); + } + + void AppendDiscardedDevices (StringBuilder sb, string indent, string? type = null) + { + var discardedDevices = DiscardedDevices + .Where (v => type is null || string.Equals (v.GetMetadata ("Type"), type, StringComparison.OrdinalIgnoreCase)) + .ToArray (); + + if (discardedDevices.Length == 0) + return; + + sb.AppendLine ($"{indent}The following devices were discarded:"); + foreach (var device in discardedDevices) { + var reason = device.GetMetadata ("DiscardedReason"); + if (string.IsNullOrEmpty (reason)) { + sb.AppendLine ($"{indent} {FormatDevice (device)}"); + } else { + sb.AppendLine ($"{indent} {FormatDevice (device)}: {reason}"); + } + } + } + + void LogNoAvailableDevicesError () + { + var sb = new StringBuilder (); + sb.AppendLine ("No applicable and available devices found."); + AppendDiscardedDevices (sb, ""); + Log.LogError (sb.ToString ().TrimEnd ()); + } + protected string GenerateCommandLineCommands () { var sb = new List (); @@ -410,6 +480,7 @@ void ShowHelp () var devices = GetDeviceListForDevice (); if (devices.Count == 0) { sb.AppendLine ($" There are no devices connected to this Mac that can be used to run this app."); + AppendDiscardedDevices (sb, " ", "Device"); } else { sb.AppendLine ($" There are {devices.Count} device(s) connected to this Mac that can be used to run this app:"); foreach (var d in devices) @@ -428,6 +499,7 @@ void ShowHelp () var simulators = GetDeviceListForSimulator (); if (simulators.Count == 0) { sb.AppendLine ($" There are no simulators available that can be used to run this app. Please open Xcode, then the menu Window -> Devices and Simulators, select Simulators on the top left, and create a new simulator clicking on the plus sign on the bottom left."); + AppendDiscardedDevices (sb, " ", "Simulator"); } else { sb.AppendLine ($" There are {simulators.Count} simulators(s) on this Mac that can be used to run this app:"); foreach (var s in simulators) @@ -455,7 +527,7 @@ public override bool Execute () } if (Devices.Length == 0) { - Log.LogError ("No applicable and available devices found."); + LogNoAvailableDevicesError (); return false; } diff --git a/tests/dotnet/UnitTests/MlaunchTest.cs b/tests/dotnet/UnitTests/MlaunchTest.cs index d9be16ed88ba..fb7c3b9ba8b5 100644 --- a/tests/dotnet/UnitTests/MlaunchTest.cs +++ b/tests/dotnet/UnitTests/MlaunchTest.cs @@ -29,7 +29,13 @@ public void GetMlaunchInstallArguments (ApplePlatform platform, string runtimeId DotNet.Execute ("build", project_path, properties, target: "_DetectSdkLocations;_DetectAppManifest;_CompileAppManifest;_WriteAppManifest"); properties ["MlaunchInstallScript"] = outputPath; - var rv = DotNet.Execute ("build", project_path, properties, target: "ComputeMlaunchInstallArguments"); + var rv = DotNet.Execute ("build", project_path, properties, assert_success: false, target: "ComputeMlaunchInstallArguments"); + + if (rv.ExitCode != 0) { + var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).Select (v => v.Message).OfType ().ToArray (); + Assert.That (string.Join ("\n", errors), Does.Contain ("No applicable and available devices found.")); + return; + } if (!BinLog.TryFindPropertyValue (rv.BinLogPath, "MlaunchInstallArguments", out var mlaunchInstallArguments)) Assert.Fail ("Could not find the property 'MlaunchInstallArguments' in the binlog."); @@ -75,7 +81,13 @@ public void GetMlaunchRunArguments (ApplePlatform platform, string runtimeIdenti DotNet.Execute ("build", project_path, properties, target: "_DetectSdkLocations;_DetectAppManifest;_CompileAppManifest;_WriteAppManifest"); properties ["MlaunchRunScript"] = outputPath; - var rv = DotNet.Execute ("build", project_path, properties, target: "ComputeMlaunchRunArguments"); + var rv = DotNet.Execute ("build", project_path, properties, assert_success: false, target: "ComputeMlaunchRunArguments"); + + if (rv.ExitCode != 0) { + var errors = BinLog.GetBuildLogErrors (rv.BinLogPath).Select (v => v.Message).OfType ().ToArray (); + Assert.That (string.Join ("\n", errors), Does.Contain ("No applicable and available devices found.")); + return; + } if (!BinLog.TryFindPropertyValue (rv.BinLogPath, "MlaunchRunArguments", out var mlaunchRunArguments)) Assert.Fail ("Could not find the property 'MlaunchRunArguments' in the binlog."); diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs index 06276132d3cd..6ee6f4d50e18 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs @@ -46,6 +46,10 @@ public void ErrorsIfDevicesItemGroupIsEmpty () var task = CreateTask (); task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (ApplePlatform.iOS).ToString (); task.AppManifestPath = CreateAppManifest (1, 2); + task.DiscardedDevices = CreateDiscardedDevices ( + ("SIM-1", "Unsupported Simulator", "Simulator", "Device is not an iPad, but the app only supports iPads"), + ("DEVICE-1", "Old Phone", "Device", "Device OS version '17.0' is lower than the app's minimum OS version '18.0'") + ); task.LaunchApp = "MySimpleApp.app"; task.MlaunchPath = "/usr/bin/false"; task.SdkIsSimulator = true; @@ -54,21 +58,57 @@ public void ErrorsIfDevicesItemGroupIsEmpty () ExecuteTask (task, expectedErrorCount: 1); - Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("The 'Devices' item group is empty.")); + Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("No applicable and available devices found.")); + Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("Unsupported Simulator (SIM-1): Device is not an iPad, but the app only supports iPads")); + Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("Old Phone (DEVICE-1): Device OS version '17.0' is lower than the app's minimum OS version '18.0'")); + } + + [Test] + public void HelpListsDiscardedDevicesWhenNoDevicesAreAvailable () + { + var task = CreateTask (); + task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (ApplePlatform.iOS).ToString (); + task.AppManifestPath = CreateAppManifest (1, 2); + task.DiscardedDevices = CreateDiscardedDevices ( + ("SIM-1", "Unsupported Simulator", "Simulator", "Device is not an iPad, but the app only supports iPads"), + ("DEVICE-1", "Old Phone", "Device", "Device OS version '17.0' is lower than the app's minimum OS version '18.0'") + ); + task.Help = "true"; + task.MlaunchPath = "/usr/bin/false"; + task.SdkIsSimulator = true; + task.SdkVersion = "26.2"; + + ExecuteTask (task); + + Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("The following devices were discarded:")); + Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("Unsupported Simulator (SIM-1): Device is not an iPad, but the app only supports iPads")); + Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("Old Phone (DEVICE-1): Device OS version '17.0' is lower than the app's minimum OS version '18.0'")); } static TaskItem [] CreateDevices (params (string Udid, string Name, string Type) [] devices) { return devices.Select (v => { - var item = new TaskItem (v.Udid); - item.SetMetadata ("Description", v.Name); - item.SetMetadata ("Name", v.Name); - item.SetMetadata ("Type", v.Type); - item.SetMetadata ("UDID", v.Udid); - return item; + return CreateDevice (v.Udid, v.Name, v.Type); }).ToArray (); } + static TaskItem [] CreateDiscardedDevices (params (string Udid, string Name, string Type, string DiscardedReason) [] devices) + { + return devices.Select (v => CreateDevice (v.Udid, v.Name, v.Type, v.DiscardedReason)).ToArray (); + } + + static TaskItem CreateDevice (string udid, string name, string type, string? discardedReason = null) + { + var item = new TaskItem (udid); + item.SetMetadata ("Description", name); + item.SetMetadata ("Name", name); + item.SetMetadata ("Type", type); + item.SetMetadata ("UDID", udid); + if (!string.IsNullOrEmpty (discardedReason)) + item.SetMetadata ("DiscardedReason", discardedReason); + return item; + } + static string CreateAppManifest (params int [] deviceFamilies) { var appManifestPath = Path.Combine (Cache.CreateTemporaryDirectory ("msbuild-tests"), "Info.plist"); From 66c78ae86437f0420feafa5df2e8e5617188dc33 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Thu, 9 Apr 2026 15:58:01 +0200 Subject: [PATCH 07/11] Filter launch device item groups Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Tasks/GetMlaunchArguments.cs | 25 +++++++++++++++++-- .../TaskTests/GetMlaunchArgumentsTaskTests.cs | 12 +++++++-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs index 631ec1f0207a..53301046e193 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs @@ -237,7 +237,7 @@ List GetSimulatorDevices () string SelectSimulatorDevice () { - var simulator = Devices.FirstOrDefault (v => string.Equals (v.GetMetadata ("Type"), "Simulator", StringComparison.OrdinalIgnoreCase)); + var simulator = GetTaskItemsOfType (Devices, "Simulator").FirstOrDefault (); if (simulator is null) { var sb = new StringBuilder (); sb.AppendLine ("The 'Devices' item group does not contain any simulators."); @@ -331,6 +331,25 @@ static string FormatDevice (ITaskItem device) return name == identifier ? identifier : $"{name} ({identifier})"; } + static ITaskItem [] GetTaskItemsOfType (ITaskItem [] items, string type) + { + return items + .Where (v => string.Equals (v.GetMetadata ("Type"), type, StringComparison.OrdinalIgnoreCase)) + .ToArray (); + } + + string GetApplicableDeviceType () + { + return SdkIsSimulator ? "Simulator" : "Device"; + } + + void FilterTaskItemInputs () + { + var type = GetApplicableDeviceType (); + Devices = GetTaskItemsOfType (Devices, type); + DiscardedDevices = GetTaskItemsOfType (DiscardedDevices, type); + } + static List<(string Identifier, string Name, string? NotApplicableBecause)> GetDevicesFromTaskItems (string type, ITaskItem [] items) { return items @@ -366,7 +385,7 @@ void LogNoAvailableDevicesError () { var sb = new StringBuilder (); sb.AppendLine ("No applicable and available devices found."); - AppendDiscardedDevices (sb, ""); + AppendDiscardedDevices (sb, "", GetApplicableDeviceType ()); Log.LogError (sb.ToString ().TrimEnd ()); } @@ -521,6 +540,8 @@ public override bool Execute () if (ShouldExecuteRemotely ()) return ExecuteRemotely (); + FilterTaskItemInputs (); + if (!string.IsNullOrEmpty (Help)) { ShowHelp (); return !Log.HasLoggedErrors; diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs index 6ee6f4d50e18..1c7ad7b07033 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs @@ -46,6 +46,9 @@ public void ErrorsIfDevicesItemGroupIsEmpty () var task = CreateTask (); task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (ApplePlatform.iOS).ToString (); task.AppManifestPath = CreateAppManifest (1, 2); + task.Devices = CreateDevices ( + ("DEVICE-2", "Connected iPhone", "Device") + ); task.DiscardedDevices = CreateDiscardedDevices ( ("SIM-1", "Unsupported Simulator", "Simulator", "Device is not an iPad, but the app only supports iPads"), ("DEVICE-1", "Old Phone", "Device", "Device OS version '17.0' is lower than the app's minimum OS version '18.0'") @@ -60,7 +63,8 @@ public void ErrorsIfDevicesItemGroupIsEmpty () Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("No applicable and available devices found.")); Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("Unsupported Simulator (SIM-1): Device is not an iPad, but the app only supports iPads")); - Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Contain ("Old Phone (DEVICE-1): Device OS version '17.0' is lower than the app's minimum OS version '18.0'")); + Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Not.Contain ("Connected iPhone")); + Assert.That (Engine.Logger.ErrorEvents [0].Message, Does.Not.Contain ("Old Phone")); } [Test] @@ -69,6 +73,9 @@ public void HelpListsDiscardedDevicesWhenNoDevicesAreAvailable () var task = CreateTask (); task.TargetFrameworkMoniker = TargetFramework.GetTargetFramework (ApplePlatform.iOS).ToString (); task.AppManifestPath = CreateAppManifest (1, 2); + task.Devices = CreateDevices ( + ("DEVICE-2", "Connected iPhone", "Device") + ); task.DiscardedDevices = CreateDiscardedDevices ( ("SIM-1", "Unsupported Simulator", "Simulator", "Device is not an iPad, but the app only supports iPads"), ("DEVICE-1", "Old Phone", "Device", "Device OS version '17.0' is lower than the app's minimum OS version '18.0'") @@ -82,7 +89,8 @@ public void HelpListsDiscardedDevicesWhenNoDevicesAreAvailable () Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("The following devices were discarded:")); Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("Unsupported Simulator (SIM-1): Device is not an iPad, but the app only supports iPads")); - Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("Old Phone (DEVICE-1): Device OS version '17.0' is lower than the app's minimum OS version '18.0'")); + Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Not.Contain ("Connected iPhone")); + Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Not.Contain ("Old Phone")); } static TaskItem [] CreateDevices (params (string Udid, string Name, string Type) [] devices) From eca00c3f1436b1717ae027fd3d46b4e01b925762 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 13 Apr 2026 10:02:07 +0200 Subject: [PATCH 08/11] Update msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs index 53301046e193..2df8821edc96 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs @@ -540,13 +540,12 @@ public override bool Execute () if (ShouldExecuteRemotely ()) return ExecuteRemotely (); - FilterTaskItemInputs (); - if (!string.IsNullOrEmpty (Help)) { ShowHelp (); return !Log.HasLoggedErrors; } + FilterTaskItemInputs (); if (Devices.Length == 0) { LogNoAvailableDevicesError (); return false; From 4bc688c4a5632ccf32fc10d06d8d59ea9adfee80 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 13 Apr 2026 10:03:06 +0200 Subject: [PATCH 09/11] Update tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../TaskTests/GetMlaunchArgumentsTaskTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs index 1c7ad7b07033..9042df3d7196 100644 --- a/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs +++ b/tests/msbuild/Xamarin.MacDev.Tasks.Tests/TaskTests/GetMlaunchArgumentsTaskTests.cs @@ -89,8 +89,8 @@ public void HelpListsDiscardedDevicesWhenNoDevicesAreAvailable () Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("The following devices were discarded:")); Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("Unsupported Simulator (SIM-1): Device is not an iPad, but the app only supports iPads")); - Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Not.Contain ("Connected iPhone")); - Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Not.Contain ("Old Phone")); + Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("Connected iPhone")); + Assert.That (Engine.Logger.WarningsEvents [0].Message, Does.Contain ("Old Phone (DEVICE-1): Device OS version '17.0' is lower than the app's minimum OS version '18.0'")); } static TaskItem [] CreateDevices (params (string Udid, string Name, string Type) [] devices) From 14521eb9a70765eec4ef4c08f8e7440cf47c763a Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 13 Apr 2026 10:31:58 +0200 Subject: [PATCH 10/11] Fix build error. --- msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs index 2df8821edc96..74e39a0600ab 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs @@ -80,7 +80,7 @@ sealed class SimulatorDeviceInfo { string [] productFamilies; switch (DeviceType) { case IPhoneDeviceType.IPhone: // if we're looking for an iPhone, an iPad also works - productFamilies = onlyExact ? ["iPhone"] : ["iPhone", "iPad"]; + productFamilies = ["iPhone", "iPad"]; break; case IPhoneDeviceType.IPad: productFamilies = ["iPad"]; From 81eebbf2c4cc3fc05ac39419be17a4d90d87b9e8 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 13 Apr 2026 11:12:10 +0200 Subject: [PATCH 11/11] Fix discarded device help output Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs index 74e39a0600ab..3b0f1b6115b6 100644 --- a/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs +++ b/msbuild/Xamarin.MacDev.Tasks/Tasks/GetMlaunchArguments.cs @@ -499,7 +499,6 @@ void ShowHelp () var devices = GetDeviceListForDevice (); if (devices.Count == 0) { sb.AppendLine ($" There are no devices connected to this Mac that can be used to run this app."); - AppendDiscardedDevices (sb, " ", "Device"); } else { sb.AppendLine ($" There are {devices.Count} device(s) connected to this Mac that can be used to run this app:"); foreach (var d in devices) @@ -510,6 +509,7 @@ void ShowHelp () var sampleDevice = firstDevice.Name == StringUtils.Quote (firstDevice.Name) ? firstDevice.Name : firstDevice.Identifier; sb.AppendLine ($" dotnet run -f {f} -r {rid} -p:DeviceName={sampleDevice}"); } + AppendDiscardedDevices (sb, " ", "Device"); sb.AppendLine ($""); sb.AppendLine ($"To run in a simulator:"); @@ -518,7 +518,6 @@ void ShowHelp () var simulators = GetDeviceListForSimulator (); if (simulators.Count == 0) { sb.AppendLine ($" There are no simulators available that can be used to run this app. Please open Xcode, then the menu Window -> Devices and Simulators, select Simulators on the top left, and create a new simulator clicking on the plus sign on the bottom left."); - AppendDiscardedDevices (sb, " ", "Simulator"); } else { sb.AppendLine ($" There are {simulators.Count} simulators(s) on this Mac that can be used to run this app:"); foreach (var s in simulators) @@ -529,6 +528,7 @@ void ShowHelp () var sampleDevice = firstSim.Name == StringUtils.Quote (firstSim.Name) ? firstSim.Name : firstSim.Identifier; sb.AppendLine ($" dotnet run -f {f} -p:DeviceName={sampleDevice}"); } + AppendDiscardedDevices (sb, " ", "Simulator"); sb.AppendLine (); // Sadly the only way to have the help show up in the terminal reliably is to make it a warning