diff --git a/Xamarin.MacDev/RuntimeService.cs b/Xamarin.MacDev/RuntimeService.cs new file mode 100644 index 0000000..cf436d6 --- /dev/null +++ b/Xamarin.MacDev/RuntimeService.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Linq; +using Xamarin.MacDev.Models; + +#nullable enable + +namespace Xamarin.MacDev; + +/// +/// Manages simulator runtimes via xcrun simctl and xcrun xcodebuild. +/// +public class RuntimeService { + + static readonly string XcrunPath = "/usr/bin/xcrun"; + + readonly ICustomLogger log; + readonly SimCtl simctl; + + public RuntimeService (ICustomLogger log) + { + this.log = log ?? throw new ArgumentNullException (nameof (log)); + simctl = new SimCtl (log); + } + + /// + /// Lists installed simulator runtimes. Optionally filters by availability. + /// + public List List (bool availableOnly = false) + { + var json = simctl.Run ("list", "runtimes", "--json"); + if (json is null) + return new List (); + + var runtimes = SimctlOutputParser.ParseRuntimes (json, log); + + if (availableOnly) + runtimes.RemoveAll (r => !r.IsAvailable); + + log.LogInfo ("Found {0} simulator runtime(s).", runtimes.Count); + return runtimes; + } + + /// + /// Lists runtimes for a specific platform (e.g. "iOS", "tvOS", "watchOS", "visionOS"). + /// + public List ListByPlatform (string platform, bool availableOnly = false) + { + if (string.IsNullOrEmpty (platform)) + throw new ArgumentException ("Platform must not be null or empty.", nameof (platform)); + + var all = List (availableOnly); + return all.Where (r => string.Equals (r.Platform, platform, StringComparison.OrdinalIgnoreCase)).ToList (); + } + + /// + /// Downloads a platform runtime using xcrun xcodebuild -downloadPlatform. + /// + /// The platform to download (e.g. "iOS", "tvOS", "watchOS", "visionOS"). + /// Optional specific version to download (e.g. "17.5"). + /// True if the download command succeeded. + public bool DownloadPlatform (string platform, string? version = null) + { + if (string.IsNullOrEmpty (platform)) + throw new ArgumentException ("Platform must not be null or empty.", nameof (platform)); + + log.LogInfo ("Downloading {0} platform runtime via xcodebuild...", platform); + + try { + var args = string.IsNullOrEmpty (version) + ? new [] { "xcodebuild", "-downloadPlatform", platform } + : new [] { "xcodebuild", "-downloadPlatform", platform, "-buildVersion", version! }; + + log.LogInfo ("Executing: {0} {1}", XcrunPath, string.Join (" ", args)); + var (exitCode, _, stderr) = ProcessUtils.Exec (XcrunPath, args); + if (exitCode != 0) { + log.LogInfo ("xcodebuild -downloadPlatform {0} failed (exit {1}): {2}", platform, exitCode, stderr.Trim ()); + return false; + } + + log.LogInfo ("Successfully downloaded {0} platform runtime.", platform); + return true; + } catch (System.ComponentModel.Win32Exception ex) { + log.LogInfo ("Could not run xcodebuild: {0}", ex.Message); + return false; + } catch (InvalidOperationException ex) { + log.LogInfo ("Could not run xcodebuild: {0}", ex.Message); + return false; + } + } +} diff --git a/tests/RuntimeServiceTests.cs b/tests/RuntimeServiceTests.cs new file mode 100644 index 0000000..1e26988 --- /dev/null +++ b/tests/RuntimeServiceTests.cs @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using NUnit.Framework; +using Xamarin.MacDev; + +#nullable enable + +namespace tests; + +[TestFixture] +public class RuntimeServiceTests { + + [Test] + public void Constructor_ThrowsOnNullLogger () + { + Assert.Throws (() => new RuntimeService (null!)); + } + + [Test] + public void ListByPlatform_ThrowsOnNullPlatform () + { + var svc = new RuntimeService (ConsoleLogger.Instance); + Assert.Throws (() => svc.ListByPlatform (null!)); + Assert.Throws (() => svc.ListByPlatform ("")); + } + + [Test] + public void DownloadPlatform_ThrowsOnNullPlatform () + { + var svc = new RuntimeService (ConsoleLogger.Instance); + Assert.Throws (() => svc.DownloadPlatform (null!)); + Assert.Throws (() => svc.DownloadPlatform ("")); + } + + [Test] + [Platform ("MacOsX")] + public void List_DoesNotThrow () + { + var svc = new RuntimeService (ConsoleLogger.Instance); + Assert.DoesNotThrow (() => svc.List ()); + } + + [Test] + [Platform ("MacOsX")] + public void ListByPlatform_DoesNotThrow () + { + var svc = new RuntimeService (ConsoleLogger.Instance); + Assert.DoesNotThrow (() => svc.ListByPlatform ("iOS")); + } +}