Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0.0] iOS on Windows support + Universal profiler mode #126

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@
"dotnetMeteor.debuggerOptions.useExternalTypeResolver": {
"type": "boolean",
"default": true,
"description": "%configuration.description.debuggerOptions.useExternalTypeResolver%"
"markdownDescription": "%configuration.description.debuggerOptions.useExternalTypeResolver%"
},
"dotnetMeteor.debuggerOptions.currentExceptionTag": {
"type": "string",
Expand Down
2 changes: 1 addition & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"configuration.description.debuggerOptions.flattenHierarchy": "Controls whether the debugger should flatten the hierarchy of inherited members.",
"configuration.description.debuggerOptions.groupPrivateMembers": "Controls whether the debugger should group private members in the debugging window.",
"configuration.description.debuggerOptions.groupStaticMembers": "Controls whether the debugger should group static members in the debugging window.",
"configuration.description.debuggerOptions.useExternalTypeResolver": "Search for unknown types in the project's assemblies for the evaluation expressions.",
"configuration.description.debuggerOptions.useExternalTypeResolver": "Resolve types using the external type resolver. Requires the [DotRush](https://github.com/JaneySprings/DotRush) extension.",
"configuration.description.debuggerOptions.currentExceptionTag": "Specifies the display name of the current exception in the debugging window.",
"configuration.description.debuggerOptions.ellipsizeStrings": "Truncates strings in the debugging window.",
"configuration.description.debuggerOptions.ellipsizedLength": "The maximum length of a truncated string.",
Expand Down
45 changes: 31 additions & 14 deletions src/DotNet.Meteor.Common/Apple/AppleSdkLocator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics;
using System.Text.RegularExpressions;
using DotNet.Meteor.Common.Processes;

Expand Down Expand Up @@ -58,25 +59,41 @@ public static string DotNetRootLocation() {
return Directory.GetParent(sdkLocation)?.FullName ?? string.Empty;
}
public static string IDeviceLocation() {
string dotnetPath = AppleSdkLocator.DotNetRootLocation();
string sdkPath = Path.Combine(dotnetPath, "packs", "Microsoft.iOS.Windows.Sdk");
DirectoryInfo? newestTool = null;
var ideviceDirectory = Environment.GetEnvironmentVariable("IDEVICE_DIR");
if (Directory.Exists(ideviceDirectory))
return ideviceDirectory;

foreach (string directory in Directory.GetDirectories(sdkPath)) {
string idevicePath = Path.Combine(directory, "tools", "msbuild", "iOS", "imobiledevice-x64");
if (RuntimeSystem.IsLinux)
return Path.Combine("/usr", "bin"); // There is no 'Microsoft.iOS.Linux.Sdk' workload

if (Directory.Exists(idevicePath)) {
var tool = new DirectoryInfo(idevicePath);
var sdkPath = string.Empty;
var dotnetPacksPath = Path.Combine(AppleSdkLocator.DotNetRootLocation(), "packs");
var sdkPaths = Directory.GetDirectories(dotnetPacksPath, "Microsoft.iOS.Windows.Sdk.net*");

if (sdkPaths.Length > 0)
sdkPath = sdkPaths.OrderByDescending(x => Path.GetFileName(x)).First();
if (string.IsNullOrEmpty(sdkPath))
sdkPath = Path.Combine(dotnetPacksPath, "Microsoft.iOS.Windows.Sdk");
if (!Directory.Exists(sdkPath))
throw new DirectoryNotFoundException("Could not find idevice tool");

if (newestTool == null || tool.CreationTime > newestTool.CreationTime)
newestTool = tool;
}
}
var toolLocations = Directory.GetDirectories(sdkPath);
if (toolLocations.Length == 0)
throw new FileNotFoundException("Could not find idevice tool");

if (newestTool == null || !newestTool.Exists)
throw new DirectoryNotFoundException("imobiledevice-x64");
var latestToolDirectory = toolLocations.OrderByDescending(x => Path.GetFileName(x)).First();
return Path.Combine(latestToolDirectory, "tools", "msbuild", "iOS", "imobiledevice-x64");
}
public static bool IsAppleDriverRunning() {
if (RuntimeSystem.IsMacOS)
throw new PlatformNotSupportedException();

return newestTool.FullName;
if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("USBMUXD_CHECK_BYPASS")))
return true;

var processName = RuntimeSystem.IsWindows ? "AppleMobileDeviceProcess" : "usbmuxd";
var process = Process.GetProcessesByName(processName);
return process.Length > 0;
}

public static FileInfo SystemProfilerTool() {
Expand Down
53 changes: 28 additions & 25 deletions src/DotNet.Meteor.Common/Apple/IDeviceTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,49 @@

namespace DotNet.Meteor.Common.Apple;

// This tool requires the 'Apple Devices' app daemon (AppleMobileDevice) or (usbmuxd) to be running.
// https://www.microsoft.com/store/productId/9NP83LWLPZ9K?ocid=pdpshare
public static class IDeviceTool {
public static void Installer(string serial, string bundlePath, IProcessLogger? logger = null) {
var tool = new FileInfo(Path.Combine(AppleSdkLocator.IDeviceLocation(), "ideviceinstaller.exe"));
var tool = new FileInfo(Path.Combine(AppleSdkLocator.IDeviceLocation(), "ideviceinstaller" + RuntimeSystem.ExecExtension));
var result = new ProcessRunner(tool, new ProcessArgumentBuilder()
.Append("--udid").Append(serial)
.Append("--install").AppendQuoted(bundlePath)
.Append("--notify-wait"), logger)
.Append("--install").AppendQuoted(bundlePath), logger)
.WaitForExit();

if (!result.Success)
throw new InvalidOperationException(string.Join(Environment.NewLine, result.StandardError));
}
public static Process Debug(string serial, string bundleId, int port, IProcessLogger? logger = null) {
var tool = new FileInfo(Path.Combine(AppleSdkLocator.IDeviceLocation(), "idevicedebug.exe"));
return new ProcessRunner(tool, new ProcessArgumentBuilder()
.Append("run").Append(bundleId)
.Append("--udid").Append(serial)
.Append("--env").Append($"__XAMARIN_DEBUG_PORT__={port}")
.Append("--debug"), logger)
.Start();
}
public static DeviceData Info() {
var tool = new FileInfo(Path.Combine(AppleSdkLocator.IDeviceLocation(), "ideviceinfo.exe"));
public static IEnumerable<DeviceData> Info() {
var tool = new FileInfo(Path.Combine(AppleSdkLocator.IDeviceLocation(), "ideviceinfo" + RuntimeSystem.ExecExtension));
var result = new ProcessRunner(tool).WaitForExit();

if (!result.Success)
throw new InvalidOperationException(string.Join(Environment.NewLine, result.StandardError));
return Enumerable.Empty<DeviceData>();

return new DeviceData {
Name = FindValue(result.StandardOutput, "DeviceName"),
Serial = FindValue(result.StandardOutput, "UniqueDeviceID"),
OSVersion = "iOS " + FindValue(result.StandardOutput, "ProductVersion"),
RuntimeId = Runtimes.iOSArm64,
Detail = Details.iOSDevice,
Platform = Platforms.iOS,
IsEmulator = false,
IsRunning = true,
IsMobile = true
return new List<DeviceData> {
new DeviceData {
Name = FindValue(result.StandardOutput, "DeviceName"),
Serial = FindValue(result.StandardOutput, "UniqueDeviceID"),
OSVersion = "iOS " + FindValue(result.StandardOutput, "ProductVersion"),
RuntimeId = Runtimes.iOSArm64,
Detail = Details.iOSDevice,
Platform = Platforms.iOS,
IsEmulator = false,
IsRunning = true,
IsMobile = true
}
};
}
public static Process Proxy(string serial, int port, IProcessLogger? logger = null) {
var tool = new FileInfo(Path.Combine(AppleSdkLocator.IDeviceLocation(), "iproxy" + RuntimeSystem.ExecExtension));
var separator = RuntimeSystem.IsWindows ? ' ' : ':';
return new ProcessRunner(tool, new ProcessArgumentBuilder()
.Append($"{port}{separator}{port}")
.Append(serial), logger)
.Start();
}


private static string FindValue(List<string> records, string key) {
return records
Expand Down
44 changes: 34 additions & 10 deletions src/DotNet.Meteor.Common/Processes/ProcessExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
using System.Diagnostics;
using DotNet.Meteor.Common.Processes;

namespace DotNet.Meteor.Common {
namespace DotNet.Meteor.Common;

public static class ProcessExtensions {
// private const int ExitTimeout = 1000;

public static void Terminate(this Process process) {
if (!process.HasExited) {
process.Kill();
// process.WaitForExit(ExitTimeout);
}
process.Close();
public static class ProcessExtensions {
// private const int ExitTimeout = 1000;

public static void Terminate(this Process process) {
if (!process.HasExited) {
process.Kill();
// process.WaitForExit(ExitTimeout);
}
process.Close();
}
}

public class CatchTargetLogger : IProcessLogger {
private readonly IProcessLogger innerLogger;
private readonly Action handler;
private readonly string catchTarget;

public CatchTargetLogger(string catchTarget, IProcessLogger innerLogger, Action handler) {
this.innerLogger = innerLogger;
this.handler = handler;
this.catchTarget = catchTarget;
}

void IProcessLogger.OnErrorDataReceived(string stderr) {
innerLogger.OnErrorDataReceived(stderr);
if (stderr.Contains(catchTarget, StringComparison.OrdinalIgnoreCase))
handler.Invoke();
}

void IProcessLogger.OnOutputDataReceived(string stdout) {
innerLogger.OnOutputDataReceived(stdout);
if (stdout.Contains(catchTarget, StringComparison.OrdinalIgnoreCase))
handler.Invoke();
}
}
1 change: 1 addition & 0 deletions src/DotNet.Meteor.Common/RuntimeSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace DotNet.Meteor.Common;
public static class RuntimeSystem {
public static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
public static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
public static bool IsAarch64 => RuntimeInformation.ProcessArchitecture == Architecture.Arm64;

public static string ExecExtension => IsWindows ? ".exe" : "";
Expand Down
5 changes: 2 additions & 3 deletions src/DotNet.Meteor.Debug/Agents/BaseLaunchAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ namespace DotNet.Meteor.Debug;

public abstract class BaseLaunchAgent {
public const string CommandPrefix = "/";
public const string LanguageSeparator = "!";

protected List<Action> Disposables { get; init; }
protected LaunchConfiguration Configuration { get; init; }
Expand All @@ -20,7 +19,7 @@ protected BaseLaunchAgent(LaunchConfiguration configuration) {
}

public abstract void Connect(SoftDebuggerSession session);
public abstract void Launch(IProcessLogger logger);
public abstract void Launch(DebugSession debugSession);
public virtual void HandleCommand(string command, string args, IProcessLogger logger) { }

public void HandleCommand(string command, IProcessLogger logger) {
Expand All @@ -38,7 +37,7 @@ public List<CompletionItem> GetCompletionItems() {
new CompletionItem() { Label = ProcessedCommand, Type = CompletionItemType.Snippet }
};
}
public virtual void Dispose() {
public void Dispose() {
foreach (var disposable in Disposables) {
try {
disposable.Invoke();
Expand Down
54 changes: 31 additions & 23 deletions src/DotNet.Meteor.Debug/Agents/DebugLaunchAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace DotNet.Meteor.Debug;
public class DebugLaunchAgent : BaseLaunchAgent {
private readonly SoftDebuggerStartArgs startArguments;
private readonly SoftDebuggerStartInfo startInformation;
private readonly ExternalTypeResolver typeResolver;

public DebugLaunchAgent(LaunchConfiguration configuration) : base(configuration) {
if (configuration.Device.IsAndroid || (configuration.Device.IsIPhone && !configuration.Device.IsEmulator))
Expand All @@ -21,45 +22,52 @@ public DebugLaunchAgent(LaunchConfiguration configuration) : base(configuration)

ArgumentNullException.ThrowIfNull(startArguments, "Debugger connection arguments not implemented.");

typeResolver = new ExternalTypeResolver(configuration.TransportId);
startInformation = new SoftDebuggerStartInfo(startArguments);
startInformation.SetAssemblies(configuration.GetAssemblyPath(), configuration.DebuggerSessionOptions);
}
public override void Launch(IProcessLogger logger) {
public override void Launch(DebugSession debugSession) {
if (Configuration.Device.IsAndroid)
LaunchAndroid(logger);
LaunchAndroid(debugSession);
if (Configuration.Device.IsIPhone)
LaunchAppleMobile(logger);
LaunchAppleMobile(debugSession);
if (Configuration.Device.IsMacCatalyst)
LaunchMacCatalyst(logger);
LaunchMacCatalyst(debugSession);
if (Configuration.Device.IsWindows)
throw new NotSupportedException();
}
public override void Connect(SoftDebuggerSession session) {
session.Run(startInformation, Configuration.DebuggerSessionOptions);
}

private void LaunchAppleMobile(IProcessLogger logger) {
// TODO: Implement Apple launching for Windows
// if (RuntimeSystem.IsWindows) {
// IDeviceTool.Installer(Configuration.Device.Serial, Configuration.OutputAssembly, this);
if (typeResolver.TryConnect()) {
Disposables.Add(() => typeResolver.Dispose());
session.TypeResolverHandler = typeResolver.Resolve;
}
}

// var debugProcess = IDeviceTool.Debug(Configuration.Device.Serial, Configuration.GetApplicationId(), Configuration.DebugPort, this);
// disposables.Add(() => debugProcess.Kill());
// return;
// }
private void LaunchAppleMobile(DebugSession debugSession) {
if (RuntimeSystem.IsMacOS) {
if (Configuration.Device.IsEmulator) {
var debugProcess = MonoLauncher.DebugSim(Configuration.Device.Serial, Configuration.ProgramPath, Configuration.DebugPort, debugSession);
Disposables.Add(() => debugProcess.Terminate());
} else {
var debugPortForwarding = MonoLauncher.TcpTunnel(Configuration.Device.Serial, Configuration.DebugPort, debugSession);
var hotReloadPortForwarding = MonoLauncher.TcpTunnel(Configuration.Device.Serial, Configuration.ReloadHostPort, debugSession);
MonoLauncher.InstallDev(Configuration.Device.Serial, Configuration.ProgramPath, debugSession);

if (Configuration.Device.IsEmulator) {
var debugProcess = MonoLauncher.DebugSim(Configuration.Device.Serial, Configuration.ProgramPath, Configuration.DebugPort, logger);
Disposables.Add(() => debugProcess.Terminate());
var debugProcess = MonoLauncher.DebugDev(Configuration.Device.Serial, Configuration.ProgramPath, Configuration.DebugPort, debugSession);
Disposables.Add(() => debugProcess.Terminate());
Disposables.Add(() => debugPortForwarding.Terminate());
Disposables.Add(() => hotReloadPortForwarding.Terminate());
}
} else {
var debugPortForwarding = MonoLauncher.TcpTunnel(Configuration.Device.Serial, Configuration.DebugPort, logger);
var hotReloadPortForwarding = MonoLauncher.TcpTunnel(Configuration.Device.Serial, Configuration.ReloadHostPort, logger);
MonoLauncher.InstallDev(Configuration.Device.Serial, Configuration.ProgramPath, logger);
var debugProxyProcess = IDeviceTool.Proxy(Configuration.Device.Serial, Configuration.DebugPort, debugSession);
Disposables.Add(() => debugProxyProcess.Terminate());
var reloadProxyProcess = IDeviceTool.Proxy(Configuration.Device.Serial, Configuration.ReloadHostPort, debugSession);
Disposables.Add(() => debugProxyProcess.Terminate());

var debugProcess = MonoLauncher.DebugDev(Configuration.Device.Serial, Configuration.ProgramPath, Configuration.DebugPort, logger);
Disposables.Add(() => debugProcess.Terminate());
Disposables.Add(() => debugPortForwarding.Terminate());
Disposables.Add(() => hotReloadPortForwarding.Terminate());
IDeviceTool.Installer(Configuration.Device.Serial, Configuration.ProgramPath, debugSession);
debugSession.OnImportantDataReceived("Application installed on device. Please tap on the app icon to run it.");
}
}
private void LaunchMacCatalyst(IProcessLogger logger) {
Expand Down
10 changes: 5 additions & 5 deletions src/DotNet.Meteor.Debug/Agents/GCDumpLaunchAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ public class GCDumpLaunchAgent : BaseLaunchAgent {

public GCDumpLaunchAgent(LaunchConfiguration configuration) : base(configuration) { }
public override void Connect(SoftDebuggerSession session) { }
public override void Launch(IProcessLogger logger) {
public override void Launch(DebugSession debugSession) {
gcdumpPath = Path.Combine(Path.GetDirectoryName(Configuration.Project.Path)!, $"{Configuration.GetApplicationName()}.gcdump");
diagnosticPort = Path.Combine(RuntimeSystem.HomeDirectory, $"{Configuration.Device.Platform}-port.lock");
ServerExtensions.TryDeleteFile(diagnosticPort);

if (Configuration.Device.IsAndroid)
LaunchAndroid(logger);
LaunchAndroid(debugSession);
if (Configuration.Device.IsIPhone)
LaunchAppleMobile(logger);
LaunchAppleMobile(debugSession);
if (Configuration.Device.IsMacCatalyst)
LaunchMacCatalyst(logger);
LaunchMacCatalyst(debugSession);
if (Configuration.Device.IsWindows)
LaunchWindows(logger);
LaunchWindows(debugSession);

Disposables.Add(() => ServerExtensions.TryDeleteFile(diagnosticPort));
}
Expand Down
Loading