From fc8fb4818c2fc3156ab117e279f43582cbea482b Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Wed, 14 Jul 2021 17:42:49 +0200 Subject: [PATCH] [runtime] Set the current directory to the root directory of the app bundle for all platforms in .NET. (#12104) To have consistent behavior in .NET, set the current directory to the root of the app bundle for all platforms. This is a breaking change for legacy Xamarin.Mac, which used to set the current directory to the Contents/Resources subdirectory, but there's a simple workaround for customers that depend on the old behavior (change it in Main themselves), and I believe the consistent experience across platforms warrants this change. Note that we already had a breaking change here for macOS/.NET: we were (unintentionally) setting the current directory to the Contents/MonoBundle directory, which neither matched mobile platforms, nor the legacy Xamarin.Mac behavior. This solves the problem of what to do for Mac Catalyst apps, because there's no need to choose between the macOS or the mobile behavior, since they're the same. This required changing the launch of macOS apps using CoreCLR to pass the full path to the entry assembly, since the entry assembly isn't in the current directory anymore. --- runtime/coreclr-bridge.m | 10 +++++----- runtime/delegates.t4 | 7 +++++++ runtime/monotouch-main.m | 5 ++++- runtime/runtime.m | 17 +++++++++++++++++ runtime/xamarin/runtime.h | 3 +++ src/ObjCRuntime/Runtime.CoreCLR.cs | 6 ++++++ tests/monotouch-test/ObjCRuntime/RuntimeTest.cs | 10 ++++++++++ 7 files changed, 52 insertions(+), 6 deletions(-) diff --git a/runtime/coreclr-bridge.m b/runtime/coreclr-bridge.m index 96c174bc95..05a933b373 100644 --- a/runtime/coreclr-bridge.m +++ b/runtime/coreclr-bridge.m @@ -602,18 +602,18 @@ mono_jit_exec (MonoDomain * domain, MonoAssembly * assembly, int argc, const cha { unsigned int exitCode = 0; - char *assemblyName = xamarin_bridge_get_assembly_name (assembly->gchandle); + char *assemblyPath = xamarin_bridge_get_assembly_location (assembly->gchandle); - LOG_CORECLR (stderr, "mono_jit_exec (%p, %p, %i, %p) => EXECUTING %s\n", domain, assembly, argc, argv, assemblyName); + LOG_CORECLR (stderr, "mono_jit_exec (%p, %p, %i, %p) => EXECUTING %s\n", domain, assembly, argc, argv, assemblyPath); for (int i = 0; i < argc; i++) { LOG_CORECLR (stderr, " Argument #%i: %s\n", i + 1, argv [i]); } - int rv = coreclr_execute_assembly (coreclr_handle, coreclr_domainId, argc, argv, assemblyName, &exitCode); + int rv = coreclr_execute_assembly (coreclr_handle, coreclr_domainId, argc, argv, assemblyPath, &exitCode); - LOG_CORECLR (stderr, "mono_jit_exec (%p, %p, %i, %p) => EXECUTING %s rv: %i exitCode: %i\n", domain, assembly, argc, argv, assemblyName, rv, exitCode); + LOG_CORECLR (stderr, "mono_jit_exec (%p, %p, %i, %p) => EXECUTING %s rv: %i exitCode: %i\n", domain, assembly, argc, argv, assemblyPath, rv, exitCode); - xamarin_free (assemblyName); + xamarin_free (assemblyPath); if (rv != 0) xamarin_assertion_message ("mono_jit_exec failed: %i\n", rv); diff --git a/runtime/delegates.t4 b/runtime/delegates.t4 index ecd81292d9..4d83c22c53 100644 --- a/runtime/delegates.t4 +++ b/runtime/delegates.t4 @@ -383,6 +383,13 @@ OnlyCoreCLR = true, }, + new XDelegate ("char *", "IntPtr", "xamarin_bridge_get_assembly_location", + "GCHandle", "IntPtr", "gchandle" + ) { + WrappedManagedFunction = "GetAssemblyLocation", + OnlyCoreCLR = true, + }, + new XDelegate ("MonoObject *", "MonoObject *", "xamarin_bridge_create_exception", "enum XamarinExceptionTypes", "Runtime.ExceptionType", "type", "const char *", "IntPtr", "arg0" diff --git a/runtime/monotouch-main.m b/runtime/monotouch-main.m index 0a9a0c2cc4..e10bb7d0f8 100644 --- a/runtime/monotouch-main.m +++ b/runtime/monotouch-main.m @@ -270,8 +270,11 @@ xamarin_main (int argc, char *argv[], enum XamarinLaunchMode launch_mode) MonoAssembly *assembly; GCHandle exception_gchandle = NULL; - const char *c_bundle_path = xamarin_get_bundle_path (); + // For legacy Xamarin.Mac, we used to chdir to $appdir/Contents/Resources (I'm not sure where this comes from, earliest commit I could find was this: https://github.com/xamarin/maccore/commit/20045dd7f85cb038cea673a9281bb6131711069c) + // For mobile platforms, we chdir to $appdir + // In .NET, we always chdir to $appdir, so that we're consistent + const char *c_bundle_path = xamarin_get_app_bundle_path (); chdir (c_bundle_path); setenv ("DYLD_BIND_AT_LAUNCH", "1", 1); diff --git a/runtime/runtime.m b/runtime/runtime.m index 1c5a0f1cc0..79f6d494ae 100644 --- a/runtime/runtime.m +++ b/runtime/runtime.m @@ -1318,6 +1318,23 @@ xamarin_initialize () MONO_EXIT_GC_UNSAFE; } +static char *x_app_bundle_path = NULL; +const char * +xamarin_get_app_bundle_path () +{ + if (x_app_bundle_path != NULL) + return x_app_bundle_path; + + NSBundle *main_bundle = [NSBundle mainBundle]; + + if (main_bundle == NULL) + xamarin_assertion_message ("Could not find the main bundle in the app ([NSBundle mainBundle] returned nil)"); + + x_app_bundle_path = strdup ([[[main_bundle bundlePath] stringByStandardizingPath] UTF8String]); + + return x_app_bundle_path; +} + static char *x_bundle_path = NULL; const char * xamarin_get_bundle_path () diff --git a/runtime/xamarin/runtime.h b/runtime/xamarin/runtime.h index bb85b9a29d..86c702f5bc 100644 --- a/runtime/xamarin/runtime.h +++ b/runtime/xamarin/runtime.h @@ -154,9 +154,12 @@ void xamarin_initialize (); void xamarin_initialize_embedded (); /* Public API, must not change - this is used by the embeddinator */ void xamarin_assertion_message (const char *msg, ...) __attribute__((__noreturn__)); +// Gets the bundle path (where the managed executable is). This is *not* the path of the app bundle (.app/.appex). const char * xamarin_get_bundle_path (); /* Public API */ // Sets the bundle path (where the managed executable is). By default APP/Contents/MonoBundle. void xamarin_set_bundle_path (const char *path); /* Public API */ +// Gets the app bundle path (.app/.appex). +const char * xamarin_get_app_bundle_path (); MonoObject * xamarin_get_managed_object_for_ptr_fast (id self, GCHandle *exception_gchandle); void xamarin_check_for_gced_object (MonoObject *obj, SEL sel, id self, MonoMethod *method, GCHandle *exception_gchandle); unsigned long xamarin_objc_type_size (const char *type); diff --git a/src/ObjCRuntime/Runtime.CoreCLR.cs b/src/ObjCRuntime/Runtime.CoreCLR.cs index 01494030ce..d960337301 100644 --- a/src/ObjCRuntime/Runtime.CoreCLR.cs +++ b/src/ObjCRuntime/Runtime.CoreCLR.cs @@ -407,6 +407,12 @@ namespace ObjCRuntime { return Marshal.StringToHGlobalAuto (Path.GetFileName (asm.Location)); } + static IntPtr GetAssemblyLocation (IntPtr gchandle) + { + var asm = (Assembly) GetGCHandleTarget (gchandle); + return Marshal.StringToHGlobalAuto (asm.Location); + } + static void SetFlagsForNSObject (IntPtr gchandle, byte flags) { var obj = (NSObject) GetGCHandleTarget (gchandle); diff --git a/tests/monotouch-test/ObjCRuntime/RuntimeTest.cs b/tests/monotouch-test/ObjCRuntime/RuntimeTest.cs index e47e8c8e49..903691a1ce 100644 --- a/tests/monotouch-test/ObjCRuntime/RuntimeTest.cs +++ b/tests/monotouch-test/ObjCRuntime/RuntimeTest.cs @@ -847,5 +847,15 @@ Additional information: handles [i].Free (); } } + + [Test] + public void CurrentDirectory () + { +#if NET || !MONOMAC + Assert.AreEqual (Environment.CurrentDirectory, NSBundle.MainBundle.BundlePath, "Current directory at launch"); +#else + Assert.AreEqual (Environment.CurrentDirectory, NSBundle.MainBundle.ResourcePath, "Current directory at launch"); +#endif + } } }