From 8d5c6bb55587302c98b26afc9d7a9749d8226bee Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Mon, 24 Jun 2024 09:47:18 -0700 Subject: [PATCH] Initial implementation for Mac --- .vscode/launch.json | 47 ++++++++-- src/Directory.Build.props | 8 ++ src/compute.geometry/Program.cs | 11 ++- src/compute.geometry/Resolver.cs | 91 +++++++++++++++++++- src/compute.geometry/compute.geometry.csproj | 39 ++++++--- src/hops/HopsAppSettingsUserControl.cs | 18 ++-- src/hops/HopsComponent.cs | 4 +- src/hops/Servers.cs | 27 +++++- src/rhino.compute/ComputeChildren.cs | 10 ++- src/rhino.compute/Program.cs | 14 ++- src/rhino.compute/rhino.compute.csproj | 10 ++- 11 files changed, 237 insertions(+), 42 deletions(-) create mode 100644 src/Directory.Build.props diff --git a/.vscode/launch.json b/.vscode/launch.json index 0544265c..aa5aa176 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,7 +6,8 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "Build Hops", - "program": "/Applications/RhinoWIP.app/Contents/MacOS/Rhinoceros", + // "program": "/Applications/Rhino 8.app/Contents/MacOS/Rhinoceros", + "program": "/Users/curtis/Library/Developer/Xcode/DerivedData/MacRhino-dalqjlsjnqqsltdayygnhqhgntxb/Build/Products/Debug/Rhinoceros.app/Contents/MacOS/Rhinoceros", "args": [], "env": { "GRASSHOPPER_PLUGINS": "${workspaceFolder}/src/hops/bin/Debug/Hops.gha" @@ -45,13 +46,49 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "Build", - "program": "${workspaceFolder}/src/bin/Debug/compute.geometry/compute.geometry.exe", - "targetArchitecture": "x86_64", + "windows": { + "program": "${workspaceFolder}/src/bin/Debug/compute.geometry/compute.geometry.exe", + "targetArchitecture": "x86_64", + }, + "osx": { + "program": "${workspaceFolder}/src/bin/Debug/compute.geometry/osx-arm64/compute.geometry", + "env": { + // "DYLD_LIBRARY_PATH": "/Users/curtis/Library/Developer/Xcode/DerivedData/MacRhino-dalqjlsjnqqsltdayygnhqhgntxb/Build/Products/Debug/Rhinoceros.app/Contents/Frameworks" + "DYLD_LIBRARY_PATH": "/Applications/Rhino 8.app/Contents/Frameworks" + } + }, "justMyCode": false, "args": [], "cwd": "${workspaceFolder}", - "console": "internalConsole" - } + "console": "integratedTerminal", + + }, + { + "name": "Launch compute.geometry C++", + "type": "cppdbg", + "request": "launch", + "preLaunchTask": "Build", + "osx": { + "program": "${workspaceFolder}/src/bin/Debug/compute.geometry/compute.geometry", + }, + "args": [ + // "-runscript=\"grasshopper\"" + ], + "stopAtEntry": false, + "targetArchitecture": "arm64", + "cwd": "${workspaceFolder}/src/bin/Debug/compute.geometry", + "externalConsole": false, + "miDebuggerArgs": "--local-lldbinit ", + "MIMode": "lldb", + "environment": [ + { "name": "DYLD_LIBRARY_PATH", "value": "/Users/curtis/Library/Developer/Xcode/DerivedData/MacRhino-dalqjlsjnqqsltdayygnhqhgntxb/Build/Products/Debug/Rhinoceros.app/Contents/Frameworks"} + { "name": "DOTNET_gcConcurrent", "value": "0" }, + { "name": "DOTNET_EnableDiagnostics", "value": "0" }, + { "name": "DOTNET_TC_QuickJit", "value": "1" }, + // { "name": "DYLD_PRINT_LIBRARIES", "value": "1" } + ] + }, + ], "compounds": [] } \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 00000000..1162eecd --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,8 @@ + + + + NU1701;NU1702 + True + + + diff --git a/src/compute.geometry/Program.cs b/src/compute.geometry/Program.cs index 85878adf..b3ee92ff 100644 --- a/src/compute.geometry/Program.cs +++ b/src/compute.geometry/Program.cs @@ -21,6 +21,9 @@ class Program static void Main(string[] args) { + if (RhinoInside.Resolver.RelaunchIfNeeded()) + return; + Config.Load(); Logging.Init(); @@ -76,9 +79,9 @@ static void Main(string[] args) Log.CloseAndFlush(); } - - static void ParseCommandLineArgs(string[] args) - { + + static void ParseCommandLineArgs(string[] args) + { for (int i = 0; i < args.Length; i++) { string[] items = args[i].Split(':'); @@ -221,4 +224,4 @@ public void AddRoutes(IEndpointRouteBuilder app) } } } -} +} diff --git a/src/compute.geometry/Resolver.cs b/src/compute.geometry/Resolver.cs index 8535c218..7bda48fd 100644 --- a/src/compute.geometry/Resolver.cs +++ b/src/compute.geometry/Resolver.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -62,6 +63,10 @@ public static string AssemblyPathFromName(string systemDirectory, string name) if (name == "Microsoft.macOS") return null; + // only use the plain name to resolve assemblies, not the full name. + var assemblyName = new AssemblyName(name); + name = assemblyName.Name; + string path = null; if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { @@ -98,6 +103,7 @@ static Assembly ResolveForRhinoAssemblies(object sender, ResolveEventArgs args) static string FindRhinoSystemDirectory() { + var major = Assembly.GetExecutingAssembly().GetName().Version.Major; if (RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows)) @@ -134,9 +140,82 @@ static string FindRhinoSystemDirectory() } } } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // TODO: detect the app location + var path = "/Applications/Rhino 8.app"; + // var path = "/Users/curtis/Library/Developer/Xcode/DerivedData/MacRhino-dalqjlsjnqqsltdayygnhqhgntxb/Build/Products/Debug/Rhinoceros.app"; + + path = Path.Combine(path, "Contents", "Frameworks"); + if (Directory.Exists(path)) + { + return path; + } + } + return null; } + public static bool RelaunchIfNeeded() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return false; + + const string RHCORE_LIB = "RhCore.framework/Versions/A/RhCore"; + bool found = false; + var libPaths = Environment.GetEnvironmentVariable("DYLD_LIBRARY_PATH")?.Split(";").ToList(); + if (libPaths != null) + { + foreach (var libPath in libPaths) + { + if (File.Exists(Path.Combine(libPath, RHCORE_LIB))) + { + // found Rhino! Let's use it. + RhinoSystemDirectory = libPath; + Console.WriteLine($"Using Rhino from {RhinoSystemDirectory}"); + found = true; + break; + } + } + } + else + { + Console.WriteLine("DYLD_LIBRARY_PATH is null"); + } + + if (!found) + { + Console.WriteLine("DYLD_LIBRARY_PATH not set, launching as child process"); + + string systemDirectory = RhinoSystemDirectory; + if (!File.Exists(Path.Combine(systemDirectory, RHCORE_LIB))) + { + Console.WriteLine("Could not find Rhino"); + return true; + } + + // executable has the same name without the .dll extension + var executable = Assembly.GetEntryAssembly().Location; + if (executable.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + executable = executable.Substring(0, executable.Length - 4); + var startInfo = new ProcessStartInfo + { + UseShellExecute = false, + FileName = executable + }; + foreach (var arg in Environment.GetCommandLineArgs()) + { + startInfo.ArgumentList.Add(arg); + } + startInfo.Environment.Add("DYLD_LIBRARY_PATH", systemDirectory); + var process = Process.Start(startInfo); + process.WaitForExit(); + return true; + } + return false; + } + public static void LoadRhino() { string systemDirectory = RhinoSystemDirectory; @@ -158,13 +237,14 @@ public static void LoadRhino() else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { rhinoLibraryHandle = NativeLibrary.Load(Path.Combine(systemDirectory, "RhinoLibrary.framework/Versions/A/RhinoLibrary")); + AssemblyLoadContext.Default.ResolvingUnmanagedDll += ResolvingUnmanagedDll; } else { throw new Exception("Unsupported platform"); } - nint handle = NativeLibrary.GetExport(rhinoLibraryHandle, "RhLibRegisterDotNetInitializer"); + IntPtr handle = NativeLibrary.GetExport(rhinoLibraryHandle, "RhLibRegisterDotNetInitializer"); var setLoaderProc = Marshal.GetDelegateForFunctionPointer(handle); //Action load = () => ExecuteLoadProc(rhinoContext); @@ -173,6 +253,15 @@ public static void LoadRhino() setLoaderProc(load); } + private static IntPtr ResolvingUnmanagedDll(Assembly assembly, string unmanagedDllName) + { + var systemDirectory = RhinoSystemDirectory; + if (unmanagedDllName == "RhinoLibrary") + return NativeLibrary.Load(Path.Combine(systemDirectory, "RhinoLibrary.framework/Versions/A/RhinoLibrary")); + + return IntPtr.Zero; + } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] delegate void SetLoaderProc(Action p); diff --git a/src/compute.geometry/compute.geometry.csproj b/src/compute.geometry/compute.geometry.csproj index 2f334152..4c905367 100644 --- a/src/compute.geometry/compute.geometry.csproj +++ b/src/compute.geometry/compute.geometry.csproj @@ -1,30 +1,32 @@  - net7.0-windows + net7.0;net7.0-windows compute.geometry Exe - win-x64 7.0.0 LatestMinor - True - True False False False - False True 8.0.0 - - ..\bin\Debug\$(AssemblyName) + + + win-x64 + True + True - - ..\bin\Release\$(AssemblyName) - ..\dist\$(AssemblyName) + + osx-x64;osx-arm64 + true - - True + + + ..\bin\$(Configuration)\$(AssemblyName) + ..\dist\$(AssemblyName) + @@ -46,6 +48,17 @@ - + + + + + + + + + + + + diff --git a/src/hops/HopsAppSettingsUserControl.cs b/src/hops/HopsAppSettingsUserControl.cs index dff55f45..825d5122 100755 --- a/src/hops/HopsAppSettingsUserControl.cs +++ b/src/hops/HopsAppSettingsUserControl.cs @@ -46,14 +46,14 @@ public HopsAppSettingsUserControl() }; _lblCacheCount.Text = $"({Hops.MemoryCache.EntryCount} items in cache)"; - if (Rhino.Runtime.HostUtils.RunningOnOSX) - { - _hideWorkerWindows.Visible = false; - _launchWorkerAtStart.Visible = false; - _childComputeCount.Visible = false; - _updateChildCountButton.Visible = false; - } - else if (Rhino.Runtime.HostUtils.RunningOnWindows) + // if (Rhino.Runtime.HostUtils.RunningOnOSX) + // { + // _hideWorkerWindows.Visible = false; + // _launchWorkerAtStart.Visible = false; + // _childComputeCount.Visible = false; + // _updateChildCountButton.Visible = false; + // } + // else if (Rhino.Runtime.HostUtils.RunningOnWindows) { _hideWorkerWindows.Checked = HopsAppSettings.HideWorkerWindows; _hideWorkerWindows.CheckedChanged += (s, e) => @@ -99,7 +99,7 @@ public HopsAppSettingsUserControl() HopsUIHelper.MinGroupBoxHeight += extraSpace; HopsUIHelper.MinControlHeight -= 32; _gpboxFunctionMgr.Height += extraSpace; - _gpboxFunctionMgr.Top -= 74; + // _gpboxFunctionMgr.Top -= 74; Size = new System.Drawing.Size(Size.Width, _gpboxFunctionMgr.Bottom + 4); } diff --git a/src/hops/HopsComponent.cs b/src/hops/HopsComponent.cs index d87649f2..f7f8c545 100644 --- a/src/hops/HopsComponent.cs +++ b/src/hops/HopsComponent.cs @@ -42,8 +42,8 @@ public class HopsComponent : GH_TaskCapableComponent, IGH_VariableParame static HopsComponent() { - if (!Rhino.Runtime.HostUtils.RunningOnWindows) - return; + // if (!Rhino.Runtime.HostUtils.RunningOnWindows) + // return; if (Rhino.RhinoApp.IsRunningHeadless) return; if (Hops.HopsAppSettings.Servers.Length > 0) diff --git a/src/hops/Servers.cs b/src/hops/Servers.cs index 2c7531c8..9c21a743 100644 --- a/src/hops/Servers.cs +++ b/src/hops/Servers.cs @@ -2,7 +2,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; +using Rhino; +using Rhino.Runtime; namespace Hops { @@ -118,7 +122,7 @@ static string GetComputeServerBaseUrl() } } - if (string.IsNullOrEmpty(url) && Rhino.Runtime.HostUtils.RunningOnWindows) + if (string.IsNullOrEmpty(url)) // && Rhino.Runtime.HostUtils.RunningOnWindows) { _computeServerQueue = new Queue(); if (_computeServerQueue.Count == 0) @@ -194,7 +198,9 @@ static void LaunchLocalRhinoCompute(Queue serverQueue, bool waitU string pathToGha = typeof(Servers).Assembly.Location; dir = System.IO.Path.GetDirectoryName(pathToGha); } - string pathToRhinoCompute = System.IO.Path.Combine(dir, "rhino.compute", "rhino.compute.exe"); + string computeExecutable = HostUtils.RunningOnWindows ? "rhino.compute.exe" : "rhino.compute"; + string runtimeIdentifier = HostUtils.RunningOnWindows ? "win-x64" : RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "osx-arm64" : "osx-x64"; + string pathToRhinoCompute = System.IO.Path.Combine(dir, "rhino.compute", runtimeIdentifier, computeExecutable); if (!System.IO.File.Exists(pathToRhinoCompute)) { // debug builds are in net5.0 directory @@ -208,6 +214,11 @@ static void LaunchLocalRhinoCompute(Queue serverQueue, bool waitU if (childCount < 1) childCount = 1; int thisProc = Process.GetCurrentProcess().Id; + + // Ensure we use this rhino when running on Mac + if (HostUtils.RunningOnOSX) + startInfo.Environment.Add("DYLD_LIBRARY_PATH", RhinoApp.GetExecutableDirectory().Parent.GetDirectories("Frameworks").FirstOrDefault()?.FullName); + startInfo.Arguments = $"--childof {thisProc} --childcount {childCount} --port {RhinoComputePort} --spawn-on-startup"; startInfo.WindowStyle = Hops.HopsAppSettings.HideWorkerWindows ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Minimized; // uncomment next line to ease debugging @@ -220,6 +231,18 @@ static void LaunchLocalRhinoCompute(Queue serverQueue, bool waitU // set to false. startInfo.UseShellExecute = true; startInfo.CreateNoWindow = Hops.HopsAppSettings.HideWorkerWindows; + + if (HostUtils.RunningOnOSX && !Hops.HopsAppSettings.HideWorkerWindows) + { + startInfo.UseShellExecute = false; + // launching Terminal.app clears out DYLD_LIBRARY_PATH and doesn't support passing arguments + startInfo.Environment["RHINO_COMPUTE_ARGUMENTS"] = startInfo.Arguments; + startInfo.Environment["RHINO_DYLD_LIBRARY_PATH"] = RhinoApp.GetExecutableDirectory().Parent.GetDirectories("Frameworks").FirstOrDefault()?.FullName; + startInfo.Arguments = $"-W -g -n -a Terminal.app \"{startInfo.FileName}\""; + startInfo.FileName = "/usr/bin/open"; + } + + string assemblyPath = Assembly.GetExecutingAssembly().Location; // 6 April 2022 - S. Baer (COMPUTE-241) // When grasshopper memory loads assemblies, the above line results in an diff --git a/src/rhino.compute/ComputeChildren.cs b/src/rhino.compute/ComputeChildren.cs index 1aab52c1..53711dbd 100644 --- a/src/rhino.compute/ComputeChildren.cs +++ b/src/rhino.compute/ComputeChildren.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.InteropServices; using Serilog; namespace rhino.compute @@ -153,12 +154,14 @@ static void LaunchCompute(Queue> processQueue, bool waitUnti // compute.geometry is allowed to be either in: // - a sibling directory named compute.geometry // - a child directory named compute.geometry - var parentDirectory = pathToThisAssembly.Directory.Parent; - string pathToCompute = System.IO.Path.Combine(parentDirectory.FullName, "compute.geometry", "compute.geometry.exe"); + var parentDirectory = pathToThisAssembly.Directory.Parent.Parent; + string geometryExecutable = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "compute.geometry.exe" : "compute.geometry"; + string runtimeIdentifier = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win-x64" : RuntimeInformation.ProcessArchitecture == Architecture.Arm64 ? "osx-arm64" : "osx-x64"; + string pathToCompute = System.IO.Path.Combine(parentDirectory.FullName, "compute.geometry", runtimeIdentifier, geometryExecutable); if (!System.IO.File.Exists(pathToCompute)) { - pathToCompute = System.IO.Path.Combine(pathToThisAssembly.Directory.FullName, "compute.geometry", "compute.geometry.exe"); + pathToCompute = System.IO.Path.Combine(pathToThisAssembly.Directory.FullName, "compute.geometry", runtimeIdentifier, geometryExecutable); if (!System.IO.File.Exists(pathToCompute)) return; } @@ -188,6 +191,7 @@ static void LaunchCompute(Queue> processQueue, bool waitUnti } var startInfo = new ProcessStartInfo(pathToCompute); + startInfo.UseShellExecute = false; var rhinoProcess = Process.GetCurrentProcess(); string commandLineArgs = $"-port:{port} -childof:{rhinoProcess.Id}"; if (!string.IsNullOrEmpty(RhinoSysDir)) diff --git a/src/rhino.compute/Program.cs b/src/rhino.compute/Program.cs index c9af9262..244690cc 100644 --- a/src/rhino.compute/Program.cs +++ b/src/rhino.compute/Program.cs @@ -56,7 +56,7 @@ compute.geometry.exe processes will shut down and stop incurring core hour billi static System.Diagnostics.Process _parentProcess; static System.Timers.Timer _selfDestructTimer; public static IConfiguration Configuration { get; } = new ConfigurationBuilder() - .SetBasePath(Directory.GetCurrentDirectory()) + .SetBasePath(AppContext.BaseDirectory) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables() .Build(); @@ -71,6 +71,18 @@ public static void Main(string[] args) .WriteTo.Console() .CreateLogger(); + var arguments = Environment.GetEnvironmentVariable("RHINO_COMPUTE_ARGUMENTS"); + if (!string.IsNullOrEmpty(arguments)) + { + args = arguments.Split(" "); + } + + var rhinoPath = Environment.GetEnvironmentVariable("RHINO_DYLD_LIBRARY_PATH"); + if (rhinoPath != null) + { + Environment.SetEnvironmentVariable("DYLD_LIBRARY_PATH", rhinoPath); + } + int port = -1; Parser.Default.ParseArguments(args).WithParsed(o => { diff --git a/src/rhino.compute/rhino.compute.csproj b/src/rhino.compute/rhino.compute.csproj index d593a377..cadb3584 100644 --- a/src/rhino.compute/rhino.compute.csproj +++ b/src/rhino.compute/rhino.compute.csproj @@ -4,6 +4,7 @@ rhino.compute Exe win-x64 + win-x64;osx-arm64;osx-x64 7.0.0 LatestMinor false @@ -11,7 +12,6 @@ true True False - false ..\bin\Debug\$(AssemblyName) @@ -20,7 +20,7 @@ ..\bin\Release\$(AssemblyName) TRACE;RHINO_COMPUTE - ..\dist\$(AssemblyName) + ..\dist\$(OutputFramework)\$(AssemblyName) @@ -35,4 +35,10 @@ + + + + + +