xamarin-macios/tests/xharness/Simulators.cs

1073 строки
37 KiB
C#
Исходник Обычный вид История

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Xamarin;
using Xamarin.Utils;
namespace xharness
{
interface ILoadAsync {
Task LoadAsync (Log log, bool include_locked, bool force);
Harness Harness { get; set; }
}
public class Simulators : ILoadAsync
{
public Harness Harness { get; set; }
bool loaded;
SemaphoreSlim semaphore = new SemaphoreSlim (1);
BlockingEnumerableCollection<SimRuntime> supported_runtimes = new BlockingEnumerableCollection<SimRuntime> ();
BlockingEnumerableCollection<SimDeviceType> supported_device_types = new BlockingEnumerableCollection<SimDeviceType> ();
BlockingEnumerableCollection<SimDevice> available_devices = new BlockingEnumerableCollection<SimDevice> ();
BlockingEnumerableCollection<SimDevicePair> available_device_pairs = new BlockingEnumerableCollection<SimDevicePair> ();
public IEnumerable<SimRuntime> SupportedRuntimes => supported_runtimes;
public IEnumerable<SimDeviceType> SupportedDeviceTypes => supported_device_types;
public IEnumerable<SimDevice> AvailableDevices => available_devices;
public IEnumerable<SimDevicePair> AvailableDevicePairs => available_device_pairs;
public async Task LoadAsync (Log log, bool include_locked = false, bool force = false)
{
await semaphore.WaitAsync ();
if (loaded) {
if (!force) {
semaphore.Release ();
return;
}
supported_runtimes.Reset ();
supported_device_types.Reset ();
available_devices.Reset ();
available_device_pairs.Reset ();
}
loaded = true;
await Task.Run (async () =>
{
var tmpfile = Path.GetTempFileName ();
try {
using (var process = new Process ()) {
process.StartInfo.FileName = Harness.MlaunchPath;
process.StartInfo.Arguments = string.Format ("--sdkroot {0} --listsim {1}", Harness.XcodeRoot, tmpfile);
log.WriteLine ("Launching {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
var rv = await process.RunAsync (log, false, timeout: TimeSpan.FromSeconds (30));
if (!rv.Succeeded)
throw new Exception ("Failed to list simulators.");
log.WriteLine ("Result:");
log.WriteLine (File.ReadAllText (tmpfile));
var simulator_data = new XmlDocument ();
simulator_data.LoadWithoutNetworkAccess (tmpfile);
foreach (XmlNode sim in simulator_data.SelectNodes ("/MTouch/Simulator/SupportedRuntimes/SimRuntime")) {
supported_runtimes.Add (new SimRuntime ()
{
Name = sim.SelectSingleNode ("Name").InnerText,
Identifier = sim.SelectSingleNode ("Identifier").InnerText,
Version = long.Parse (sim.SelectSingleNode ("Version").InnerText),
});
}
foreach (XmlNode sim in simulator_data.SelectNodes ("/MTouch/Simulator/SupportedDeviceTypes/SimDeviceType")) {
supported_device_types.Add (new SimDeviceType ()
{
Name = sim.SelectSingleNode ("Name").InnerText,
Identifier = sim.SelectSingleNode ("Identifier").InnerText,
ProductFamilyId = sim.SelectSingleNode ("ProductFamilyId").InnerText,
MinRuntimeVersion = long.Parse (sim.SelectSingleNode ("MinRuntimeVersion").InnerText),
MaxRuntimeVersion = long.Parse (sim.SelectSingleNode ("MaxRuntimeVersion").InnerText),
Supports64Bits = bool.Parse (sim.SelectSingleNode ("Supports64Bits").InnerText),
});
}
foreach (XmlNode sim in simulator_data.SelectNodes ("/MTouch/Simulator/AvailableDevices/SimDevice")) {
available_devices.Add (new SimDevice ()
{
Harness = Harness,
Name = sim.Attributes ["Name"].Value,
UDID = sim.Attributes ["UDID"].Value,
SimRuntime = sim.SelectSingleNode ("SimRuntime").InnerText,
SimDeviceType = sim.SelectSingleNode ("SimDeviceType").InnerText,
DataPath = sim.SelectSingleNode ("DataPath").InnerText,
LogPath = sim.SelectSingleNode ("LogPath").InnerText,
});
}
var sim_device_pairs = simulator_data.
SelectNodes ("/MTouch/Simulator/AvailableDevicePairs/SimDevicePair").
Cast<XmlNode> ().
// There can be duplicates, so remove those.
Distinct (new SimulatorXmlNodeComparer ());
foreach (XmlNode sim in sim_device_pairs) {
available_device_pairs.Add (new SimDevicePair ()
{
UDID = sim.Attributes ["UDID"].Value,
Companion = sim.SelectSingleNode ("Companion").InnerText,
Gizmo = sim.SelectSingleNode ("Gizmo").InnerText,
});
}
}
} finally {
supported_runtimes.SetCompleted ();
supported_device_types.SetCompleted ();
available_devices.SetCompleted ();
available_device_pairs.SetCompleted ();
File.Delete (tmpfile);
semaphore.Release ();
}
});
}
string CreateName (string devicetype, string runtime)
{
var runtime_name = supported_runtimes?.Where ((v) => v.Identifier == runtime).FirstOrDefault ()?.Name ?? Path.GetExtension (runtime).Substring (1);
var device_name = supported_device_types?.Where ((v) => v.Identifier == devicetype).FirstOrDefault ()?.Name ?? Path.GetExtension (devicetype).Substring (1);
return $"{device_name} ({runtime_name}) - created by xharness";
}
// Will return all devices that match the runtime + devicetype (even if a new device was created, any other devices will also be returned)
async Task<IEnumerable<SimDevice>> FindOrCreateDevicesAsync (Log log, string runtime, string devicetype, bool force = false)
{
if (runtime == null || devicetype == null)
return null;
IEnumerable<SimDevice> devices = null;
if (!force) {
devices = AvailableDevices.Where ((SimDevice v) => v.SimRuntime == runtime && v.SimDeviceType == devicetype);
if (devices.Any ())
return devices;
}
var rv = await Harness.ExecuteXcodeCommandAsync ("simctl", $"create {StringUtils.Quote (CreateName (devicetype, runtime))} {devicetype} {runtime}", log, TimeSpan.FromMinutes (1));
if (!rv.Succeeded) {
log.WriteLine ($"Could not create device for runtime={runtime} and device type={devicetype}.");
return null;
}
await LoadAsync (log, force: true);
devices = AvailableDevices.Where ((SimDevice v) => v.SimRuntime == runtime && v.SimDeviceType == devicetype);
if (!devices.Any ()) {
log.WriteLine ($"No devices loaded after creating it? runtime={runtime} device type={devicetype}.");
return null;
}
return devices;
}
async Task<bool> CreateDevicePair (Log log, SimDevice device, SimDevice companion_device, string runtime, string devicetype, bool create_device)
{
if (create_device) {
// watch device is already paired to some other phone. Create a new watch device
var matchingDevices = await FindOrCreateDevicesAsync (log, runtime, devicetype, force: true);
var unPairedDevices = matchingDevices.Where ((v) => !AvailableDevicePairs.Any ((SimDevicePair p) => { return p.Gizmo == v.UDID; }));
if (device != null) {
// If we're creating a new watch device, assume that the one we were given is not usable.
unPairedDevices = unPairedDevices.Where ((v) => v.UDID != device.UDID);
}
if (unPairedDevices?.Any () != true)
return false;
device = unPairedDevices.First ();
}
log.WriteLine ($"Creating device pair for '{device.Name}' and '{companion_device.Name}'");
var capturedLog = new StringBuilder ();
var pairLog = new CallbackLog ((string value) => {
log.WriteImpl (value);
capturedLog.Append (value);
});
var rv = await Harness.ExecuteXcodeCommandAsync ("simctl", $"pair {device.UDID} {companion_device.UDID}", pairLog, TimeSpan.FromMinutes (1));
if (!rv.Succeeded) {
if (!create_device) {
var try_creating_device = false;
var captured_log = capturedLog.ToString ();
try_creating_device |= captured_log.Contains ("At least one of the requested devices is already paired with the maximum number of supported devices and cannot accept another pairing.");
try_creating_device |= captured_log.Contains ("The selected devices are already paired with each other.");
if (try_creating_device) {
log.WriteLine ($"Could not create device pair for '{device.Name}' ({device.UDID}) and '{companion_device.Name}' ({companion_device.UDID}), but will create a new watch device and try again.");
return await CreateDevicePair (log, device, companion_device, runtime, devicetype, true);
}
}
log.WriteLine ($"Could not create device pair for '{device.Name}' ({device.UDID}) and '{companion_device.Name}' ({companion_device.UDID})");
return false;
}
return true;
}
async Task<SimDevicePair> FindOrCreateDevicePairAsync (Log log, IEnumerable<SimDevice> devices, IEnumerable<SimDevice> companion_devices)
{
// Check if we already have a device pair with the specified devices
var pairs = AvailableDevicePairs.Where ((SimDevicePair pair) => {
if (!devices.Any ((v) => v.UDID == pair.Gizmo))
return false;
if (!companion_devices.Any ((v) => v.UDID == pair.Companion))
return false;
return true;
});
if (!pairs.Any ()) {
// No device pair. Create one.
// First check if the watch is already paired
var unPairedDevices = devices.Where ((v) => !AvailableDevicePairs.Any ((SimDevicePair p) => { return p.Gizmo == v.UDID; }));
var unpairedDevice = unPairedDevices.FirstOrDefault ();
var companion_device = companion_devices.First ();
var device = devices.First ();
[xharness] Check the right (unpaired) device when determining whether a new watch device must be created or not. (#4222) (#4239) This fixes a NullReferenceException when there are already paired watch devices: [0x700009ad7000:] EXCEPTION handling: System.NullReferenceException: Object reference not set to an instance of an object "<unnamed thread>" tid=0x0x700009ad7000 this=0x0x10b903ad8 , thread handle : 0x7f9ad49109a0, state : not waiting at xharness.Simulators/<CreateDevicePair>d__18.MoveNext () [0x001ae] in /Users/builder/jenkins/workspace/xamarin-macios/xamarin-macios/tests/xharness/Simulators.cs:169 at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<bool>.Start<xharness.Simulators/<CreateDevicePair>d__18> (xharness.Simulators/<CreateDevicePair>d__18&) [0x0002c] in /Users/builder/jenkins/workspace/build-package-osx-mono/2018-02/external/bockbuild/builds/mono-x64/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:471 at xharness.Simulators.CreateDevicePair (xharness.Log,xharness.SimDevice,xharness.SimDevice,string,string,bool) [0x00057] in <7c5e77efeb3146c095a26043fb517189>:0 at xharness.Simulators/<FindOrCreateDevicePairAsync>d__19.MoveNext () [0x000fc] in /Users/builder/jenkins/workspace/xamarin-macios/xamarin-macios/tests/xharness/Simulators.cs:206 at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<TResult_REF>.Start<TStateMachine_REF> (TStateMachine_REF&) [0x0002c] in <0f9df4881040473f9da7cf6c2e2cb8c3>:0 at xharness.Simulators.FindOrCreateDevicePairAsync (xharness.Log,System.Collections.Generic.IEnumerable`1<xharness.SimDevice>,System.Collections.Generic.IEnumerable`1<xharness.SimDevice>) [0x0003f] in <7c5e77efeb3146c095a26043fb517189>:0 at xharness.Simulators/<FindAsync>d__20.MoveNext () [0x00364] in /Users/builder/jenkins/workspace/xamarin-macios/xamarin-macios/tests/xharness/Simulators.cs:275
2018-06-13 11:04:26 +03:00
if (!await CreateDevicePair (log, unpairedDevice, companion_device, device.SimRuntime, device.SimDeviceType, unpairedDevice == null))
return null;
await LoadAsync (log, force: true);
pairs = AvailableDevicePairs.Where ((SimDevicePair pair) => {
if (!devices.Any ((v) => v.UDID == pair.Gizmo))
return false;
if (!companion_devices.Any ((v) => v.UDID == pair.Companion))
return false;
return true;
});
}
return pairs.FirstOrDefault ();
}
public async Task<SimDevice []> FindAsync (AppRunnerTarget target, Log log, bool create_if_needed = true, bool min_version = false)
{
SimDevice [] simulators = null;
string simulator_devicetype;
string simulator_runtime;
string companion_devicetype = null;
string companion_runtime = null;
switch (target) {
case AppRunnerTarget.Simulator_iOS32:
simulator_devicetype = "com.apple.CoreSimulator.SimDeviceType.iPhone-5";
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + (min_version ? Xamarin.SdkVersions.MiniOSSimulator : "10-3").Replace ('.', '-');
break;
case AppRunnerTarget.Simulator_iOS64:
simulator_devicetype = "com.apple.CoreSimulator.SimDeviceType." + (min_version ? "iPhone-6" : "iPhone-X");
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + (min_version ? Xamarin.SdkVersions.MiniOSSimulator : Xamarin.SdkVersions.iOS).Replace ('.', '-');
break;
case AppRunnerTarget.Simulator_iOS:
simulator_devicetype = "com.apple.CoreSimulator.SimDeviceType.iPhone-5";
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + (min_version ? Xamarin.SdkVersions.MiniOSSimulator : Xamarin.SdkVersions.iOS).Replace ('.', '-');
break;
case AppRunnerTarget.Simulator_tvOS:
simulator_devicetype = "com.apple.CoreSimulator.SimDeviceType.Apple-TV-1080p";
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.tvOS-" + (min_version ? Xamarin.SdkVersions.MinTVOSSimulator : Xamarin.SdkVersions.TVOS).Replace ('.', '-');
break;
case AppRunnerTarget.Simulator_watchOS:
simulator_devicetype = "com.apple.CoreSimulator.SimDeviceType." + (min_version ? "Apple-Watch-38mm" : "Apple-Watch-Series-3-38mm");
simulator_runtime = "com.apple.CoreSimulator.SimRuntime.watchOS-" + (min_version ? Xamarin.SdkVersions.MinWatchOSSimulator : Xamarin.SdkVersions.WatchOS).Replace ('.', '-');
companion_devicetype = "com.apple.CoreSimulator.SimDeviceType." + (min_version ? "iPhone-6" : "iPhone-X");
companion_runtime = "com.apple.CoreSimulator.SimRuntime.iOS-" + (min_version ? Xamarin.SdkVersions.MinWatchOSCompanionSimulator : Xamarin.SdkVersions.iOS).Replace ('.', '-');
break;
default:
throw new Exception (string.Format ("Unknown simulator target: {0}", target));
}
var devices = await FindOrCreateDevicesAsync (log, simulator_runtime, simulator_devicetype);
var companion_devices = await FindOrCreateDevicesAsync (log, companion_runtime, companion_devicetype);
if (devices?.Any () != true) {
log.WriteLine ($"Could not find or create devices runtime={simulator_runtime} and device type={simulator_devicetype}.");
return null;
}
if (companion_runtime == null) {
simulators = new SimDevice [] { devices.First () };
} else {
if (companion_devices?.Any () != true) {
log.WriteLine ($"Could not find or create companion devices runtime={companion_runtime} and device type={companion_devicetype}.");
return null;
}
var pair = await FindOrCreateDevicePairAsync (log, devices, companion_devices);
if (pair == null) {
log.WriteLine ($"Could not find or create device pair runtime={companion_runtime} and device type={companion_devicetype}.");
return null;
}
simulators = new SimDevice [] {
devices.First ((v) => v.UDID == pair.Gizmo),
companion_devices.First ((v) => v.UDID == pair.Companion),
};
}
if (simulators == null) {
log.WriteLine ($"Could not find simulator for runtime={simulator_runtime} and device type={simulator_devicetype}.");
return null;
}
log.WriteLine ("Found simulator: {0} {1}", simulators [0].Name, simulators [0].UDID);
if (simulators.Length > 1)
log.WriteLine ("Found companion simulator: {0} {1}", simulators [1].Name, simulators [1].UDID);
return simulators;
}
public SimDevice FindCompanionDevice (Log log, SimDevice device)
{
var pair = available_device_pairs.Where ((v) => v.Gizmo == device.UDID).Single ();
return available_devices.Single ((v) => v.UDID == pair.Companion);
}
public IEnumerable<SimDevice> SelectDevices (AppRunnerTarget target, Log log, bool min_version)
{
return new SimulatorEnumerable
{
Simulators = this,
Target = target,
MinVersion = min_version,
Log = log,
};
}
class SimulatorXmlNodeComparer : IEqualityComparer<XmlNode>
{
public bool Equals (XmlNode a, XmlNode b)
{
return a["Gizmo"].InnerText == b["Gizmo"].InnerText && a["Companion"].InnerText == b["Companion"].InnerText;
}
public int GetHashCode (XmlNode node)
{
return node["Gizmo"].InnerText.GetHashCode () ^ node["Companion"].InnerText.GetHashCode ();
}
}
class SimulatorEnumerable : IEnumerable<SimDevice>, IAsyncEnumerable
{
public Simulators Simulators;
public AppRunnerTarget Target;
public bool MinVersion;
public Log Log;
object lock_obj = new object ();
Task<SimDevice []> findTask;
public override string ToString ()
{
return $"Simulators for {Target} (MinVersion: {MinVersion})";
}
public IEnumerator<SimDevice> GetEnumerator ()
{
return new Enumerator ()
{
Enumerable = this,
};
}
IEnumerator IEnumerable.GetEnumerator ()
{
return GetEnumerator ();
}
Task<SimDevice []> Find ()
{
lock (lock_obj) {
if (findTask == null)
findTask = Simulators.FindAsync (Target, Log, min_version: MinVersion);
return findTask;
}
}
public Task ReadyTask {
get { return Find (); }
}
class Enumerator : IEnumerator<SimDevice>
{
internal SimulatorEnumerable Enumerable;
SimDevice [] devices;
bool moved;
public SimDevice Current {
get {
return devices [0];
}
}
object IEnumerator.Current {
get {
return Current;
}
}
public void Dispose ()
{
}
public bool MoveNext ()
{
if (moved)
return false;
if (devices == null)
devices = Enumerable.Find ()?.Result?.ToArray (); // Create a copy of the list of devices, so we can have enumerator-specific current index.
moved = true;
return devices?.Length > 0;
}
public void Reset ()
{
moved = false;
}
}
}
}
public interface IAsyncEnumerable
{
Task ReadyTask { get; }
}
public class SimRuntime
{
public string Name;
public string Identifier;
public long Version;
}
public class SimDeviceType
{
public string Name;
public string Identifier;
public string ProductFamilyId;
public long MinRuntimeVersion;
public long MaxRuntimeVersion;
public bool Supports64Bits;
}
public class SimDevice : IDevice
{
public string UDID { get; set; }
public string Name { get; set; }
public string SimRuntime;
public string SimDeviceType;
public string DataPath;
public string LogPath;
public string SystemLog { get { return Path.Combine (LogPath, "system.log"); } }
public Harness Harness;
public bool IsWatchSimulator { get { return SimRuntime.StartsWith ("com.apple.CoreSimulator.SimRuntime.watchOS", StringComparison.Ordinal); } }
public string OSVersion {
get {
var v = SimRuntime.Substring ("com.apple.CoreSimulator.SimRuntime.".Length);
var dash = v.IndexOf ('-');
return v.Substring (0, dash) + " " + v.Substring (dash + 1).Replace ('-', '.');
}
}
public async Task EraseAsync (Log log)
{
// here we don't care if execution fails.
// erase the simulator (make sure the device isn't running first)
await Harness.ExecuteXcodeCommandAsync ("simctl", "shutdown " + UDID, log, TimeSpan.FromMinutes (1));
await Harness.ExecuteXcodeCommandAsync ("simctl", "erase " + UDID, log, TimeSpan.FromMinutes (1));
// boot & shutdown to make sure it actually works
await Harness.ExecuteXcodeCommandAsync ("simctl", "boot " + UDID, log, TimeSpan.FromMinutes (1));
await Harness.ExecuteXcodeCommandAsync ("simctl", "shutdown " + UDID, log, TimeSpan.FromMinutes (1));
}
public async Task ShutdownAsync (Log log)
{
await Harness.ExecuteXcodeCommandAsync ("simctl", "shutdown " + UDID, log, TimeSpan.FromMinutes (1));
}
public static async Task KillEverythingAsync (Log log)
{
await ProcessHelper.ExecuteCommandAsync ("launchctl", "remove com.apple.CoreSimulator.CoreSimulatorService", log, TimeSpan.FromSeconds (10));
var to_kill = new string [] { "iPhone Simulator", "iOS Simulator", "Simulator", "Simulator (Watch)", "com.apple.CoreSimulator.CoreSimulatorService", "ibtoold" };
await ProcessHelper.ExecuteCommandAsync ("killall", "-9 " + string.Join (" ", to_kill.Select ((v) => StringUtils.Quote (v)).ToArray ()), log, TimeSpan.FromSeconds (10));
foreach (var dir in new string [] {
Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", "com.apple.watchsimulator.savedState"),
Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Saved Application State", "com.apple.iphonesimulator.savedState"),
}) {
try {
if (Directory.Exists (dir))
Directory.Delete (dir, true);
} catch (Exception e) {
log.WriteLine ("Could not delete the directory '{0}': {1}", dir, e.Message);
}
}
}
Bump to use Xcode 10 beta 1 (#4179) * Bump to use Xcode 10 beta 1 * Update Versions.plist * Add a dependency on Xcode 9.4. * [msbuild] Fix build with Xcode 10 beta 1. (#4182) Many years ago (in Xcode 7 according to code comment) Developer/Platforms/iPhoneOS.platform/Developer/usr disappeared, and we coped by looking at Developer/usr instead (and also the subsequent code to locate the bin directory was based on the location of the usr directory). Developer/Platforms/iPhoneOS.platform/Developer/usr reappeared in Xcode 10 beta 1, but it seems useless (for one it doesn't contain a bin directory), so in order to try to keep things sane don't look for this directory in Xcode 10 and instead go directly for Developer/usr (which is what we've been using as the usr directory for years anyway). Fixes this problem when building apps with Xcode 10 beta 1: /Library/Frameworks/Mono.framework/External/xbuild/Xamarin/iOS/Xamarin.iOS.Common.targets(626,3): error : Could not locate SDK bin directory [/Users/rolf/Projects/TestApp/test-app.csproj] * [runtime] Build 32-bit mac executables using Xcode 9.4. * [mtouch] Work around broken tvOS headers in Xcode 10 beta 1. * [mtouch] Work around build problem with Apple's simd headers in Objective-C++ mode. * Use version-agnostic paths to sdk directories. * [tests][xtro] Add todo files (from unclassified) and adjust ignore files to avoid errors * [macos][security] Re-enable SSL[Get|Set]AlpnProtocols. Fixes #4001 (#4022) * [macos][security] Re-enable SSL[Get}Set]AlpnProtocols. Fixes #4001 This was fixed in macOS 10.13.4 https://github.com/xamarin/xamarin-macios/issues/4001 * [tests][monotouch-tests] Disable a few test cases (one crasher, other failures). Causes to be verified later * [xharness] Fix permission dialog suppression in Xcode 10. * [xharness] Ignore 32-bit macOS tests by default. * [tests] Execute mmp regression tests with Xcode 9.4 since many of them are 32-bit and needs porting to 64-bit. * [mmptest] Ignore 32-bit XM tests if we don't have a 32-bit-capable Xcode. * [registrar] Add workaround for broken headers in Xcode 10 beta 1 (radar 40824697). * [mtouch] Restrict another workaround for an Xcode 10 beta 1 bug to a specific Xcode version to remove it asap. * [tests] Fix some protocol changes (public or not) find by introspection tests * [tests][intro] Fix DefaultCtorAllowed failures * [Intents] Obsolete several Intents classes in watchOS. Several existing Intents classes have been marked as unavailable in watchOS in the headers in Xcode 10 beta 1, and corresponding tests are now failing. So obsolete the managed wrapper types, and fix tests accordingly. * Fix xtro wrt previous Ietents/intro changes * [tests] Minor adjustments to mtouch tests to work with Xcode 10. * [msbuild] Update tests to cope with additional files produced by the Core ML compiler. * [msbuild] Xcode 10 doesn't support building watchOS 1 apps, so show a clear error message explaining it. Also update tests accordingly. * [coreimage] Stub new filters and exclude ?removed? ones from tests * Update GameplayKit and SpriteKit NSSecureCoding _upgrade_ and fix other non-public cases (in tests) * [tests] Ignore some GameKit selectors that don't respond anymore (but seems to be available, at least in header files) * [tests] Fix intro 32bits testing for filters resutls * [msbuild] Slightly change error message to be better English.
2018-06-09 04:45:24 +03:00
int TCCFormat {
get {
// v1: < iOS 9
// v2: >= iOS 9 && < iOS 12
// v3: >= iOS 12
if (SimRuntime.StartsWith ("com.apple.CoreSimulator.SimRuntime.iOS-", StringComparison.Ordinal)) {
var v = Version.Parse (SimRuntime.Substring ("com.apple.CoreSimulator.SimRuntime.iOS-".Length).Replace ('-', '.'));
if (v.Major >= 12) {
return 3;
} else if (v.Major >= 9) {
return 2;
} else {
return 1;
}
} else if (SimRuntime.StartsWith ("com.apple.CoreSimulator.SimRuntime.tvOS-", StringComparison.Ordinal)) {
var v = Version.Parse (SimRuntime.Substring ("com.apple.CoreSimulator.SimRuntime.tvOS-".Length).Replace ('-', '.'));
if (v.Major >= 12) {
return 3;
} else {
return 2;
}
} else if (SimRuntime.StartsWith ("com.apple.CoreSimulator.SimRuntime.watchOS-", StringComparison.Ordinal)) {
var v = Version.Parse (SimRuntime.Substring ("com.apple.CoreSimulator.SimRuntime.watchOS-".Length).Replace ('-', '.'));
if (v.Major >= 5) {
return 3;
} else {
return 2;
}
} else {
throw new NotImplementedException ();
}
}
}
public async Task AgreeToPromptsAsync (Log log, params string[] bundle_identifiers)
{
if (bundle_identifiers == null || bundle_identifiers.Length == 0) {
log.WriteLine ("No bundle identifiers given when requested permission editing.");
return;
}
var TCC_db = Path.Combine (DataPath, "data", "Library", "TCC", "TCC.db");
var sim_services = new string [] {
"kTCCServiceAddressBook",
"kTCCServiceCalendar",
"kTCCServicePhotos",
"kTCCServiceMediaLibrary",
"kTCCServiceUbiquity",
"kTCCServiceWillow"
};
var failure = false;
var tcc_edit_timeout = 30;
var watch = new Stopwatch ();
watch.Start ();
do {
if (failure) {
log.WriteLine ("Failed to edit TCC.db, trying again in 1 second... ", (int) (tcc_edit_timeout - watch.Elapsed.TotalSeconds));
await Task.Delay (TimeSpan.FromSeconds (1));
}
failure = false;
foreach (var bundle_identifier in bundle_identifiers) {
var sql = new System.Text.StringBuilder ();
sql.Append (StringUtils.Quote (TCC_db));
sql.Append (" \"");
foreach (var service in sim_services) {
Bump to use Xcode 10 beta 1 (#4179) * Bump to use Xcode 10 beta 1 * Update Versions.plist * Add a dependency on Xcode 9.4. * [msbuild] Fix build with Xcode 10 beta 1. (#4182) Many years ago (in Xcode 7 according to code comment) Developer/Platforms/iPhoneOS.platform/Developer/usr disappeared, and we coped by looking at Developer/usr instead (and also the subsequent code to locate the bin directory was based on the location of the usr directory). Developer/Platforms/iPhoneOS.platform/Developer/usr reappeared in Xcode 10 beta 1, but it seems useless (for one it doesn't contain a bin directory), so in order to try to keep things sane don't look for this directory in Xcode 10 and instead go directly for Developer/usr (which is what we've been using as the usr directory for years anyway). Fixes this problem when building apps with Xcode 10 beta 1: /Library/Frameworks/Mono.framework/External/xbuild/Xamarin/iOS/Xamarin.iOS.Common.targets(626,3): error : Could not locate SDK bin directory [/Users/rolf/Projects/TestApp/test-app.csproj] * [runtime] Build 32-bit mac executables using Xcode 9.4. * [mtouch] Work around broken tvOS headers in Xcode 10 beta 1. * [mtouch] Work around build problem with Apple's simd headers in Objective-C++ mode. * Use version-agnostic paths to sdk directories. * [tests][xtro] Add todo files (from unclassified) and adjust ignore files to avoid errors * [macos][security] Re-enable SSL[Get|Set]AlpnProtocols. Fixes #4001 (#4022) * [macos][security] Re-enable SSL[Get}Set]AlpnProtocols. Fixes #4001 This was fixed in macOS 10.13.4 https://github.com/xamarin/xamarin-macios/issues/4001 * [tests][monotouch-tests] Disable a few test cases (one crasher, other failures). Causes to be verified later * [xharness] Fix permission dialog suppression in Xcode 10. * [xharness] Ignore 32-bit macOS tests by default. * [tests] Execute mmp regression tests with Xcode 9.4 since many of them are 32-bit and needs porting to 64-bit. * [mmptest] Ignore 32-bit XM tests if we don't have a 32-bit-capable Xcode. * [registrar] Add workaround for broken headers in Xcode 10 beta 1 (radar 40824697). * [mtouch] Restrict another workaround for an Xcode 10 beta 1 bug to a specific Xcode version to remove it asap. * [tests] Fix some protocol changes (public or not) find by introspection tests * [tests][intro] Fix DefaultCtorAllowed failures * [Intents] Obsolete several Intents classes in watchOS. Several existing Intents classes have been marked as unavailable in watchOS in the headers in Xcode 10 beta 1, and corresponding tests are now failing. So obsolete the managed wrapper types, and fix tests accordingly. * Fix xtro wrt previous Ietents/intro changes * [tests] Minor adjustments to mtouch tests to work with Xcode 10. * [msbuild] Update tests to cope with additional files produced by the Core ML compiler. * [msbuild] Xcode 10 doesn't support building watchOS 1 apps, so show a clear error message explaining it. Also update tests accordingly. * [coreimage] Stub new filters and exclude ?removed? ones from tests * Update GameplayKit and SpriteKit NSSecureCoding _upgrade_ and fix other non-public cases (in tests) * [tests] Ignore some GameKit selectors that don't respond anymore (but seems to be available, at least in header files) * [tests] Fix intro 32bits testing for filters resutls * [msbuild] Slightly change error message to be better English.
2018-06-09 04:45:24 +03:00
switch (TCCFormat) {
case 1:
// CREATE TABLE access (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, CONSTRAINT key PRIMARY KEY (service, client, client_type));
sql.AppendFormat ("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL);", service, bundle_identifier);
sql.AppendFormat ("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL);", service, bundle_identifier + ".watchkitapp");
break;
case 2:
// CREATE TABLE access (service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, policy_id INTEGER, PRIMARY KEY (service, client, client_type), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE);
sql.AppendFormat ("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL);", service, bundle_identifier);
sql.AppendFormat ("INSERT INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL);", service, bundle_identifier + ".watchkitapp");
break;
case 3: // Xcode 10+
2018-08-15 20:23:31 +03:00
// CREATE TABLE access ( service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, policy_id INTEGER, indirect_object_identifier_type INTEGER, indirect_object_identifier TEXT, indirect_object_code_identity BLOB, flags INTEGER, last_modified INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)), PRIMARY KEY (service, client, client_type, indirect_object_identifier), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE)
sql.AppendFormat ("INSERT OR REPLACE INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL,NULL,'UNUSED',NULL,NULL,1);", service, bundle_identifier);
sql.AppendFormat ("INSERT OR REPLACE INTO access VALUES('{0}','{1}',0,1,0,NULL,NULL,NULL,'UNUSED',NULL,NULL,1);", service, bundle_identifier + ".watchkitapp");
Bump to use Xcode 10 beta 1 (#4179) * Bump to use Xcode 10 beta 1 * Update Versions.plist * Add a dependency on Xcode 9.4. * [msbuild] Fix build with Xcode 10 beta 1. (#4182) Many years ago (in Xcode 7 according to code comment) Developer/Platforms/iPhoneOS.platform/Developer/usr disappeared, and we coped by looking at Developer/usr instead (and also the subsequent code to locate the bin directory was based on the location of the usr directory). Developer/Platforms/iPhoneOS.platform/Developer/usr reappeared in Xcode 10 beta 1, but it seems useless (for one it doesn't contain a bin directory), so in order to try to keep things sane don't look for this directory in Xcode 10 and instead go directly for Developer/usr (which is what we've been using as the usr directory for years anyway). Fixes this problem when building apps with Xcode 10 beta 1: /Library/Frameworks/Mono.framework/External/xbuild/Xamarin/iOS/Xamarin.iOS.Common.targets(626,3): error : Could not locate SDK bin directory [/Users/rolf/Projects/TestApp/test-app.csproj] * [runtime] Build 32-bit mac executables using Xcode 9.4. * [mtouch] Work around broken tvOS headers in Xcode 10 beta 1. * [mtouch] Work around build problem with Apple's simd headers in Objective-C++ mode. * Use version-agnostic paths to sdk directories. * [tests][xtro] Add todo files (from unclassified) and adjust ignore files to avoid errors * [macos][security] Re-enable SSL[Get|Set]AlpnProtocols. Fixes #4001 (#4022) * [macos][security] Re-enable SSL[Get}Set]AlpnProtocols. Fixes #4001 This was fixed in macOS 10.13.4 https://github.com/xamarin/xamarin-macios/issues/4001 * [tests][monotouch-tests] Disable a few test cases (one crasher, other failures). Causes to be verified later * [xharness] Fix permission dialog suppression in Xcode 10. * [xharness] Ignore 32-bit macOS tests by default. * [tests] Execute mmp regression tests with Xcode 9.4 since many of them are 32-bit and needs porting to 64-bit. * [mmptest] Ignore 32-bit XM tests if we don't have a 32-bit-capable Xcode. * [registrar] Add workaround for broken headers in Xcode 10 beta 1 (radar 40824697). * [mtouch] Restrict another workaround for an Xcode 10 beta 1 bug to a specific Xcode version to remove it asap. * [tests] Fix some protocol changes (public or not) find by introspection tests * [tests][intro] Fix DefaultCtorAllowed failures * [Intents] Obsolete several Intents classes in watchOS. Several existing Intents classes have been marked as unavailable in watchOS in the headers in Xcode 10 beta 1, and corresponding tests are now failing. So obsolete the managed wrapper types, and fix tests accordingly. * Fix xtro wrt previous Ietents/intro changes * [tests] Minor adjustments to mtouch tests to work with Xcode 10. * [msbuild] Update tests to cope with additional files produced by the Core ML compiler. * [msbuild] Xcode 10 doesn't support building watchOS 1 apps, so show a clear error message explaining it. Also update tests accordingly. * [coreimage] Stub new filters and exclude ?removed? ones from tests * Update GameplayKit and SpriteKit NSSecureCoding _upgrade_ and fix other non-public cases (in tests) * [tests] Ignore some GameKit selectors that don't respond anymore (but seems to be available, at least in header files) * [tests] Fix intro 32bits testing for filters resutls * [msbuild] Slightly change error message to be better English.
2018-06-09 04:45:24 +03:00
break;
default:
throw new NotImplementedException ();
}
}
sql.Append ("\"");
var rv = await ProcessHelper.ExecuteCommandAsync ("sqlite3", sql.ToString (), log, TimeSpan.FromSeconds (5));
if (!rv.Succeeded) {
failure = true;
break;
}
}
} while (failure && watch.Elapsed.TotalSeconds <= tcc_edit_timeout);
if (failure) {
log.WriteLine ("Failed to edit TCC.db, the test run might hang due to permission request dialogs");
} else {
log.WriteLine ("Successfully edited TCC.db");
}
log.WriteLine ("Current TCC database contents:");
await ProcessHelper.ExecuteCommandAsync ("sqlite3", $"{StringUtils.Quote (TCC_db)} .dump", log, TimeSpan.FromSeconds (5));
}
async Task OpenSimulator (Log log)
{
string simulator_app;
if (IsWatchSimulator && Harness.XcodeVersion.Major < 9) {
simulator_app = Path.Combine (Harness.XcodeRoot, "Contents", "Developer", "Applications", "Simulator (Watch).app");
} else {
simulator_app = Path.Combine (Harness.XcodeRoot, "Contents", "Developer", "Applications", "Simulator.app");
if (!Directory.Exists (simulator_app))
simulator_app = Path.Combine (Harness.XcodeRoot, "Contents", "Developer", "Applications", "iOS Simulator.app");
}
await ProcessHelper.ExecuteCommandAsync ("open", "-a " + StringUtils.Quote (simulator_app) + " --args -CurrentDeviceUDID " + UDID, log, TimeSpan.FromSeconds (15));
}
public async Task PrepareSimulatorAsync (Log log, params string[] bundle_identifiers)
{
// Kill all existing processes
await KillEverythingAsync (log);
// We shutdown and erase all simulators.
await EraseAsync (log);
// Edit the permissions to prevent dialog boxes in the test app
var TCC_db = Path.Combine (DataPath, "data", "Library", "TCC", "TCC.db");
if (!File.Exists (TCC_db)) {
log.WriteLine ("Opening simulator to create TCC.db");
await OpenSimulator (log);
var tcc_creation_timeout = 60;
var watch = new Stopwatch ();
watch.Start ();
while (!File.Exists (TCC_db) && watch.Elapsed.TotalSeconds < tcc_creation_timeout) {
log.WriteLine ("Waiting for simulator to create TCC.db... {0}", (int)(tcc_creation_timeout - watch.Elapsed.TotalSeconds));
await Task.Delay (TimeSpan.FromSeconds (0.250));
}
}
if (File.Exists (TCC_db)) {
await AgreeToPromptsAsync (log, bundle_identifiers);
} else {
log.WriteLine ("No TCC.db found for the simulator {0} (SimRuntime={1} and SimDeviceType={1})", UDID, SimRuntime, SimDeviceType);
}
// Make sure we're in a clean state
await KillEverythingAsync (log);
// Make 100% sure we're shutdown
await ShutdownAsync (log);
}
}
public class SimDevicePair
{
public string UDID;
public string Companion;
public string Gizmo;
}
public class SimDeviceSpecification
{
public SimDevice Main;
public SimDevice Companion; // the phone for watch devices
}
public class Devices : ILoadAsync
{
public Harness Harness { get; set; }
bool loaded;
BlockingEnumerableCollection<Device> connected_devices = new BlockingEnumerableCollection<Device> ();
public IEnumerable<Device> ConnectedDevices => connected_devices;
public IEnumerable<Device> Connected64BitIOS => connected_devices.Where (x => x.DevicePlatform == DevicePlatform.iOS && x.Supports64Bit);
public IEnumerable<Device> Connected32BitIOS => connected_devices.Where (x => x.DevicePlatform == DevicePlatform.iOS && x.Supports32Bit);
public IEnumerable<Device> ConnectedTV => connected_devices.Where (x => x.DevicePlatform == DevicePlatform.tvOS);
2019-01-29 10:16:42 +03:00
public IEnumerable<Device> ConnectedWatch => connected_devices.Where (x => x.DevicePlatform == DevicePlatform.watchOS && x.Architecture == Architecture.ARMv7k);
public IEnumerable<Device> ConnectedWatch32_64 {
get {
return connected_devices.Where ((x) => {
return x.DevicePlatform == DevicePlatform.watchOS && x.Architecture == Architecture.ARM64_32;
});
}
}
Task ILoadAsync.LoadAsync (Log log, bool include_locked, bool force)
{
return LoadAsync (log, extra_data: false, removed_locked: !include_locked, force: force);
}
public async Task LoadAsync (Log log, bool extra_data = false, bool removed_locked = false, bool force = false)
{
if (loaded) {
if (!force)
return;
connected_devices.Reset ();
}
loaded = true;
await Task.Run (async () =>
{
var tmpfile = Path.GetTempFileName ();
try {
using (var process = new Process ()) {
process.StartInfo.FileName = Harness.MlaunchPath;
process.StartInfo.Arguments = string.Format ("--sdkroot {0} --listdev={1} {2} --output-format=xml", Harness.XcodeRoot, tmpfile, extra_data ? "--list-extra-data" : string.Empty);
log.WriteLine ("Launching {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
var rv = await process.RunAsync (log, false, timeout: TimeSpan.FromSeconds (120));
if (!rv.Succeeded)
throw new Exception ("Failed to list devices.");
log.WriteLine ("Result:");
log.WriteLine (File.ReadAllText (tmpfile));
log.Flush ();
var doc = new XmlDocument ();
doc.LoadWithoutNetworkAccess (tmpfile);
foreach (XmlNode dev in doc.SelectNodes ("/MTouch/Device")) {
var usable = dev.SelectSingleNode ("IsUsableForDebugging")?.InnerText;
Device d = new Device
{
DeviceIdentifier = dev.SelectSingleNode ("DeviceIdentifier")?.InnerText,
DeviceClass = dev.SelectSingleNode ("DeviceClass")?.InnerText,
CompanionIdentifier = dev.SelectSingleNode ("CompanionIdentifier")?.InnerText,
Name = dev.SelectSingleNode ("Name")?.InnerText,
BuildVersion = dev.SelectSingleNode ("BuildVersion")?.InnerText,
ProductVersion = dev.SelectSingleNode ("ProductVersion")?.InnerText,
ProductType = dev.SelectSingleNode ("ProductType")?.InnerText,
InterfaceType = dev.SelectSingleNode ("InterfaceType")?.InnerText,
IsUsableForDebugging = usable == null ? (bool?) null : ((bool?) (usable == "True")),
};
bool.TryParse (dev.SelectSingleNode ("IsLocked")?.InnerText, out d.IsLocked);
if (removed_locked && d.IsLocked) {
log.WriteLine ($"Skipping device {d.Name} ({d.DeviceIdentifier}) because it's locked.");
continue;
}
if (d.IsUsableForDebugging.HasValue && !d.IsUsableForDebugging.Value) {
log.WriteLine ($"Skipping device {d.Name} ({d.DeviceIdentifier}) because it's not usable for debugging.");
continue;
}
connected_devices.Add (d);
}
}
} finally {
connected_devices.SetCompleted ();
File.Delete (tmpfile);
log.Flush ();
}
});
}
public Device FindCompanionDevice (Log log, Device device)
{
var companion = ConnectedDevices.Where ((v) => v.DeviceIdentifier == device.CompanionIdentifier);
if (companion.Count () == 0)
throw new Exception ($"Could not find the companion device for '{device.Name}'");
if (companion.Count () > 1)
log.WriteLine ("Found {0} companion devices for {1}?!?", companion.Count (), device.Name);
return companion.First ();
}
}
public enum Architecture
{
ARMv6,
ARMv7,
ARMv7k,
ARMv7s,
ARM64,
ARM64_32,
i386,
x86_64,
}
public enum DevicePlatform
{
Unknown,
iOS,
tvOS,
watchOS,
}
public class Device : IDevice
{
public string DeviceIdentifier;
public string DeviceClass;
public string CompanionIdentifier;
public string Name { get; set; }
public string BuildVersion;
public string ProductVersion;
public string ProductType;
public string InterfaceType;
public bool? IsUsableForDebugging;
public bool IsLocked;
public string UDID { get { return DeviceIdentifier; } set { DeviceIdentifier = value; } }
public string OSVersion {
get {
return ProductVersion;
}
}
// Add a speed property that can be used to sort a list of devices according to speed.
public int DebugSpeed {
get {
var itype = InterfaceType?.ToLowerInvariant ();
if (itype == "usb")
return 0; // fastest
if (itype == null)
return 1; // mlaunch doesn't know - not sure when this can happen, but wifi is quite slow, so maybe this faster
if (itype == "wifi")
return 2; // wifi is quite slow
return 3; // Anything else is probably slower than wifi (e.g. watch).
}
}
public DevicePlatform DevicePlatform {
get {
switch (DeviceClass) {
case "iPhone":
case "iPod":
case "iPad":
return DevicePlatform.iOS;
case "AppleTV":
return DevicePlatform.tvOS;
case "Watch":
return DevicePlatform.watchOS;
default:
return DevicePlatform.Unknown;
}
}
}
public bool Supports64Bit {
get { return Architecture == Architecture.ARM64; }
}
public bool Supports32Bit {
get {
switch (DevicePlatform) {
case DevicePlatform.iOS:
return Version.Parse (ProductVersion).Major < 11;
case DevicePlatform.tvOS:
return false;
case DevicePlatform.watchOS:
return true;
default:
throw new NotImplementedException ();
}
}
}
public bool IsSupported (iOSTestProject project)
{
if (project.MonoNativeInfo == null)
return true;
var min_version = MonoNativeHelper.GetMinimumOSVersion (DevicePlatform, project.MonoNativeInfo.Flavor);
return Version.Parse (ProductVersion) >= Version.Parse (min_version);
}
public Architecture Architecture {
get {
var model = ProductType;
// https://www.theiphonewiki.com/wiki/Models
if (model.StartsWith ("iPhone", StringComparison.Ordinal)) {
var identifier = model.Substring ("iPhone".Length);
var values = identifier.Split (',');
switch (values [0]) {
case "1": // iPhone (1) and iPhone 3G (2)
return Architecture.ARMv6;
case "2": // iPhone 3GS (1)
case "3": // iPhone 4 (1-3)
case "4": // iPhone 4S (1)
return Architecture.ARMv7;
case "5": // iPhone 5 (1-2) and iPhone 5c (3-4)
return Architecture.ARMv7s;
case "6": // iPhone 5s (1-2)
case "7": // iPhone 6+ (1) and iPhone 6 (2)
case "8": // iPhone 6s (1), iPhone 6s+ (2), iPhoneSE (4)
case "9": // iPhone 7 (1,3) and iPhone 7+ (2,4)
default:
return Architecture.ARM64;
}
}
// https://www.theiphonewiki.com/wiki/List_of_iPads
if (model.StartsWith ("iPad", StringComparison.Ordinal)) {
var identifier = model.Substring ("iPad".Length);
var values = identifier.Split (',');
switch (values [0]) {
case "1": // iPad (1)
case "2": // iPad 2 (1-4) and iPad Mini (5-7)
case "3": // iPad 3 (1-3) and iPad 4 (4-6)
return Architecture.ARMv7;
case "4": // iPad Air (1-3), iPad Mini 2 (4-6) and iPad Mini 3 (7-9)
case "5": // iPad Air 2 (3-4)
case "6": // iPad Pro 9.7-inch (3-4), iPad Pro 12.9-inch (7-8)
default:
return Architecture.ARM64;
}
}
// https://www.theiphonewiki.com/wiki/List_of_iPod_touches
if (model.StartsWith ("iPod", StringComparison.Ordinal)) {
var identifier = model.Substring ("iPod".Length);
var values = identifier.Split (',');
switch (values [0]) {
case "1": // iPod touch (1)
case "2": // iPod touch 2G (1)
return Architecture.ARMv6;
case "3": // iPod touch 3G (1)
case "4": // iPod touch 4G (1)
case "5": // iPod touch 5G (1)
return Architecture.ARMv7;
case "7": // iPod touch 6G (1)
default:
return Architecture.ARM64;
}
}
// https://www.theiphonewiki.com/wiki/List_of_Apple_Watches
if (model.StartsWith ("Watch", StringComparison.Ordinal)) {
var identifier = model.Substring ("Watch".Length);
var values = identifier.Split (',');
switch (values [0]) {
case "1": // Apple Watch (1st gen)
case "2": // Apple Watch Series 1 and Series 2
case "3": // Apple Watch Series 3
return Architecture.ARMv7k;
case "4": // Apple Watch Series 4
default:
return Architecture.ARM64_32;
}
}
// https://www.theiphonewiki.com/wiki/List_of_Apple_TVs
if (model.StartsWith ("AppleTV", StringComparison.Ordinal))
return Architecture.ARM64;
throw new NotImplementedException ();
}
}
}
interface IDevice
{
string Name { get; set; }
string UDID { get; set; }
string OSVersion { get; }
}
// This is a collection whose enumerator will wait enumerating until
// the collection has been marked as completed (but the enumerator can still
// be created; this allows the creation of linq queries whose execution is
// delayed until later).
internal class BlockingEnumerableCollection<T> : IEnumerable<T> where T : class
{
List<T> list = new List<T> ();
TaskCompletionSource<bool> completed = new TaskCompletionSource<bool> ();
public int Count {
get {
WaitForCompletion ();
return list.Count;
}
}
public void Add (T device)
{
if (completed.Task.IsCompleted)
Console.WriteLine ("Adding to completed collection!");
list.Add (device);
}
public void SetCompleted ()
{
completed.TrySetResult (true);
}
void WaitForCompletion ()
{
completed.Task.Wait ();
}
public void Reset ()
{
completed = new TaskCompletionSource<bool> ();
list.Clear ();
}
public IEnumerator<T> GetEnumerator ()
{
return new Enumerator (this);
}
IEnumerator IEnumerable.GetEnumerator ()
{
return GetEnumerator ();
}
class Enumerator : IEnumerator<T>
{
BlockingEnumerableCollection<T> collection;
IEnumerator<T> enumerator;
public Enumerator (BlockingEnumerableCollection<T> collection)
{
this.collection = collection;
}
public T Current {
get {
return enumerator.Current;
}
}
object IEnumerator.Current {
get {
return enumerator.Current;
}
}
public void Dispose ()
{
enumerator.Dispose ();
}
public bool MoveNext ()
{
collection.WaitForCompletion ();
if (enumerator == null)
enumerator = collection.list.GetEnumerator ();
return enumerator.MoveNext ();
}
public void Reset ()
{
enumerator.Reset ();
}
}
}
}