diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleDeviceCommandsArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleDeviceCommandsArguments.cs index bee5fb82a..eae4ce4ce 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleDeviceCommandsArguments.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleDeviceCommandsArguments.cs @@ -6,7 +6,7 @@ namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; -internal class AppleDeviceCommandsArguments : XHarnessCommandArguments, IAppleArguments +internal class AppleDeviceCommandArguments : XHarnessCommandArguments, IAppleArguments { public DeviceNameArgument DeviceName { get; } = new(); public IncludeWirelessArgument IncludeWireless { get; } = new(); @@ -15,9 +15,9 @@ internal class AppleDeviceCommandsArguments : XHarnessCommandArguments, IAppleAr protected override IEnumerable GetArguments() => new Argument[] { - DeviceName, - IncludeWireless, - XcodeRoot, - MlaunchPath, + DeviceName, + IncludeWireless, + XcodeRoot, + MlaunchPath, }; } diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleMlaunchCommandArguments.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleMlaunchCommandArguments.cs new file mode 100644 index 000000000..7c356e6af --- /dev/null +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/AppleMlaunchCommandArguments.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; + +internal class AppleMlaunchCommandArguments : XHarnessCommandArguments, IAppleArguments +{ + public MlaunchArgument MlaunchPath { get; } = new(); + public XcodeArgument XcodeRoot { get; } = new(); + public TimeoutArgument Timeout { get; set; } = new(TimeSpan.FromMinutes(2)); + public EnvironmentalVariablesArgument EnvironmentalVariables { get; } = new(); + + protected override IEnumerable GetArguments() => new Argument[] + { + MlaunchPath, + XcodeRoot, + Timeout, + EnvironmentalVariables, + }; +} diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/MlaunchArgument.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/MlaunchArgument.cs index 4422d53c3..859b1b6d7 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/MlaunchArgument.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/MlaunchArgument.cs @@ -15,7 +15,7 @@ namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// internal class MlaunchArgument : Argument { - public MlaunchArgument() : base("mlaunch=", "Path to the mlaunch binary", MacOSProcessManager.DetectMlaunchPath()) + public MlaunchArgument() : base("mlaunch=", "Path to the mlaunch binary. Defaults to mlaunch bundled with the XHarness nupkg", MacOSProcessManager.DetectMlaunchPath()) { } diff --git a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/XcodeArgument.cs b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/XcodeArgument.cs index 7d26e27bb..741a20b56 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/XcodeArgument.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/CommandArguments/Apple/Arguments/XcodeArgument.cs @@ -12,7 +12,7 @@ namespace Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; /// internal class XcodeArgument : PathArgument { - public XcodeArgument() : base("xcode=", "Path where Xcode is installed", false) + public XcodeArgument() : base("xcode=", "Path where Xcode is installed. If not provided, determined from xcode-select", false) { } diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleCommandSet.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleCommandSet.cs index 82b99e28c..b6ab1a13a 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleCommandSet.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleCommandSet.cs @@ -33,6 +33,7 @@ public AppleCommandSet() : base("apple") // Commands for getting information Add(new AppleDeviceCommand(services)); + Add(new AppleMlaunchCommand(services)); Add(new AppleStateCommand()); // Commands for simulator management diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleDeviceCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleDeviceCommand.cs index e6e9af92a..1c3e2e070 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleDeviceCommand.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleDeviceCommand.cs @@ -17,9 +17,9 @@ namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; -internal class AppleDeviceCommand : AppleCommand +internal class AppleDeviceCommand : AppleCommand { - protected override AppleDeviceCommandsArguments Arguments { get; } = new(); + protected override AppleDeviceCommandArguments Arguments { get; } = new(); protected override string CommandUsage { get; } = "apple device [OPTIONS] [TARGET]"; diff --git a/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleMlaunchCommand.cs b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleMlaunchCommand.cs new file mode 100644 index 000000000..f640ba242 --- /dev/null +++ b/src/Microsoft.DotNet.XHarness.CLI/Commands/Apple/AppleMlaunchCommand.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.XHarness.CLI.CommandArguments.Apple; +using Microsoft.DotNet.XHarness.Common.CLI; +using Microsoft.DotNet.XHarness.Common.Logging; +using Microsoft.DotNet.XHarness.iOS.Shared.Execution; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.DotNet.XHarness.CLI.Commands.Apple; + +internal class AppleMlaunchCommand : AppleCommand +{ + private const string Description = "Invoke bundled mlaunch with given arguments"; + protected override string CommandUsage { get; } = "apple mlaunch [OPTIONS] -- [MLAUNCH ARGUMENTS]"; + protected override string CommandDescription => Description; + + protected override AppleMlaunchCommandArguments Arguments { get; } = new(); + + public AppleMlaunchCommand(IServiceCollection services) : base("mlaunch", false, services, Description) + { + } + + protected override async Task Invoke(ILogger logger) + { + if (!PassThroughArguments.Any()) + { + logger.LogError("Please provide delimeter '--' followed by arguments for ADB:" + Environment.NewLine + + $" {CommandUsage}" + Environment.NewLine + + $"Example:" + Environment.NewLine + + $" apple mlaunch --timeout 00:01:30 -- devices -l"); + + return ExitCode.INVALID_ARGUMENTS; + } + + var processManager = Services.BuildServiceProvider().GetRequiredService(); + + try + { + var nullLog = new CallbackLog(s => { }); + var stdout = new CallbackLog(Console.Write); + var stderr = new CallbackLog(Console.Error.Write); + + var args = new MlaunchArguments(PassThroughArguments.Select(arg => new SimpleMlaunchArgument(arg)).ToArray()); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(Arguments.Timeout); + + var result = await processManager.ExecuteCommandAsync( + args, + Arguments.Verbosity < LogLevel.Information ? stdout : nullLog, + Arguments.Verbosity <= LogLevel.Warning ? stdout : nullLog, + stderr, + Arguments.Timeout, + Arguments.EnvironmentalVariables.Value.ToDictionary(t => t.Item1, t => t.Item2), + verbosity: 0, // -v needs to be supplied by user + cts.Token); + + if (result.TimedOut) + { + return ExitCode.TIMED_OUT; + } + + return (ExitCode)result.ExitCode; + } + catch (Exception e) + { + logger.LogError(e.ToString()); + return ExitCode.GENERAL_FAILURE; + } + } + + // This is needed because ProcessManagers accepts MlaunchArguments only which are strong-typed args supported by mlaunch + // Since in this command, these are supplied by user, we need to forward them as-is + private class SimpleMlaunchArgument : iOS.Shared.Execution.MlaunchArgument + { + private readonly string _argument; + + public SimpleMlaunchArgument(string argument) + { + _argument = argument; + } + + public override string AsCommandLineArgument() => Escape(_argument); + } +} diff --git a/src/Microsoft.DotNet.XHarness.CLI/Program.cs b/src/Microsoft.DotNet.XHarness.CLI/Program.cs index d043246db..66d12fb65 100644 --- a/src/Microsoft.DotNet.XHarness.CLI/Program.cs +++ b/src/Microsoft.DotNet.XHarness.CLI/Program.cs @@ -96,26 +96,26 @@ private static bool IsOutputSensitive(string[] args) return false; } - switch (args[0]) - { - case "apple": - return args[1] switch - { - "device" => true, - "state" => args.Contains("--json"), - _ => false, - }; - - case "android": - return args[1] switch - { - "device" => true, - "state" => args.Contains("--json"), - "adb" => true, - _ => false, - }; - } + var platform = args[0]; + var command = args[1]; - return false; + return platform switch + { + "apple" => command switch + { + "device" => true, + "state" => args.Contains("--json"), + "mlaunch" => true, + _ => false, + }, + "android" => command switch + { + "device" => true, + "state" => args.Contains("--json"), + "adb" => true, + _ => false, + }, + _ => false, + }; } }