From 62fb5eb6d41e12a3dca52b34cf4b34133b1df0d0 Mon Sep 17 00:00:00 2001 From: Andoni Morales Alastruey Date: Tue, 21 May 2024 16:34:37 +0200 Subject: [PATCH 1/4] girepository: add support for relocations Relocation is a common scenario in macOS applications and frameworks. This commit provides a mechanism to support relocations by inferring the typelib dir from the path of the loaded image libgirepository-1.0.dylib. --- girepository/girepository.c | 126 ++++++++++++++++++++++++++++++++---- girepository/meson.build | 1 + 2 files changed, 113 insertions(+), 14 deletions(-) diff --git a/girepository/girepository.c b/girepository/girepository.c index 0cf6da148..c768af18f 100644 --- a/girepository/girepository.c +++ b/girepository/girepository.c @@ -30,6 +30,7 @@ #include #include +#include #include #include #include "gibaseinfo-private.h" @@ -135,7 +136,6 @@ struct _GIRepository G_DEFINE_TYPE (GIRepository, gi_repository, G_TYPE_OBJECT); #ifdef G_PLATFORM_WIN32 - #include static HMODULE girepository_dll = NULL; @@ -155,19 +155,117 @@ DllMain (HINSTANCE hinstDLL, return TRUE; } +#endif /* DLL_EXPORT */ +#endif /* G_PLATFORM_WIN32 */ + +#ifdef __APPLE__ +#include + +/* This function returns the file path of the loaded libgirepository1.0.dylib. + * It iterates over all the loaded images to find the one with the + * gi_repository_init symbol and returns its file path. + */ +static const char * +gi_repository_get_library_path_macos (void) +{ + /* + * Relevant documentation: + * https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachOTopics/0-Introduction/introduction.html + * https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dyld.3.html + * https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h + */ + const void *ptr = gi_repository_init; + const struct mach_header *header; + intptr_t offset; + uint32_t i, count; + + /* Iterate over all the loaded images */ + count = _dyld_image_count (); + for (i = 0; i < count; i++) + { + header = _dyld_get_image_header (i); + offset = _dyld_get_image_vmaddr_slide (i); + + /* Locate the first `load` command */ + struct load_command *cmd = (struct load_command *) ((char *) header + sizeof (struct mach_header)); + if (header->magic == MH_MAGIC_64) + cmd = (struct load_command *) ((char *) header + sizeof (struct mach_header_64)); + + /* Find the first `segment` command iterating over all the `load` commands. + * Then, check if the gi_repository_init symbol is in this image by checking + * if the pointer is in the segment's memory address range. + */ + uint32_t j = 0; + while (j < header->ncmds) + { + if (cmd->cmd == LC_SEGMENT) + { + struct segment_command *seg = (struct segment_command *) cmd; + if (((intptr_t) ptr >= (seg->vmaddr + offset)) && ((intptr_t) ptr < (seg->vmaddr + offset + seg->vmsize))) + return _dyld_get_image_name (i); + } + if (cmd->cmd == LC_SEGMENT_64) + { + struct segment_command_64 *seg = (struct segment_command_64 *) cmd; + if (((uintptr_t ) ptr >= (seg->vmaddr + offset)) && ((uintptr_t ) ptr < (seg->vmaddr + offset + seg->vmsize))) + return _dyld_get_image_name (i); + } + /* Jump to the next command */ + j++; + cmd = (struct load_command *) ((char *) cmd + cmd->cmdsize); + } + } + return NULL; +} +#endif /* __APPLE__ */ + +/* + * gi_repository_get_libdir: + * + * Returns the directory where the typelib files are installed. + * + * In platforms without relocation support, this functions returns the + * `GOBJECT_INTROSPECTION_LIBDIR` directory defined at build time . + * + * On Windows and macOS this function returns the directory + * relative to the installation directory detected at runtime. + * + * On macOS, if the library is installed in + * `/Applications/MyApp.app/Contents/Home/lib/libgirepository-1.0.dylib`, it returns + * `/Applications/MyApp.app/Contents/Home/lib/girepository-1.0` + * + * On Windows, if the application is installed in + * `C:/Program Files/MyApp/bin/MyApp.exe`, it returns + * `C:/Program Files/MyApp/lib/girepository-1.0` +*/ +static const gchar * +gi_repository_get_libdir (void) +{ + static gchar *static_libdir; + + if (g_once_init_enter_pointer (&static_libdir)) + { + gchar *libdir; +#if defined(G_PLATFORM_WIN32) + const char *toplevel = g_win32_get_package_installation_directory_of_module (girepository_dll); + libdir = g_build_filename (toplevel, GOBJECT_INTROSPECTION_RELATIVE_LIBDIR, NULL); + g_ignore_leak (libdir); +#elif defined(__APPLE__) + const char *libpath = gi_repository_get_library_path_macos (); + if (libpath != NULL) + { + libdir = g_path_get_dirname (libpath); + g_ignore_leak (libdir); + } else { + libdir = GOBJECT_INTROSPECTION_LIBDIR; + } +#else /* !G_PLATFORM_WIN32 && !__APPLE__ */ + libdir = GOBJECT_INTROSPECTION_LIBDIR; #endif - -#undef GOBJECT_INTROSPECTION_LIBDIR - -/* GOBJECT_INTROSPECTION_LIBDIR is used only in code called just once, - * so no problem leaking this - */ -#define GOBJECT_INTROSPECTION_LIBDIR \ - g_build_filename (g_win32_get_package_installation_directory_of_module (girepository_dll), \ - "lib", \ - NULL) - -#endif + g_once_init_leave_pointer (&static_libdir, libdir); + } + return static_libdir; +} static void gi_repository_init (GIRepository *repository) @@ -197,7 +295,7 @@ gi_repository_init (GIRepository *repository) repository->typelib_search_path = g_ptr_array_new_null_terminated (1, g_free, TRUE); } - libdir = GOBJECT_INTROSPECTION_LIBDIR; + libdir = gi_repository_get_libdir (); typelib_dir = g_build_filename (libdir, "girepository-1.0", NULL); diff --git a/girepository/meson.build b/girepository/meson.build index fa0adc631..dc02cf15e 100644 --- a/girepository/meson.build +++ b/girepository/meson.build @@ -85,6 +85,7 @@ gir_c_args = [ '-DGIR_DIR="@0@"'.format(glib_girdir), '-DGOBJECT_INTROSPECTION_LIBDIR="@0@"'.format(glib_libdir), '-DGOBJECT_INTROSPECTION_DATADIR="@0@"'.format(glib_datadir), + '-DGOBJECT_INTROSPECTION_RELATIVE_LIBDIR="@0@"'.format(get_option('libdir')), ] custom_c_args = [] From 99382cfb4b5edf56f7c62eccc155f361a640298a Mon Sep 17 00:00:00 2001 From: Andoni Morales Alastruey Date: Tue, 28 May 2024 17:26:38 +0200 Subject: [PATCH 2/4] girepository: fix autoptr tests build In macOS compilation fails with the following error: ``` In file included from ../girepository/tests/autoptr.c:23: ../girepository/girffi.h:30:10: fatal error: 'ffi.h' file not found ``` --- girepository/tests/meson.build | 1 + 1 file changed, 1 insertion(+) diff --git a/girepository/tests/meson.build b/girepository/tests/meson.build index 6fa947cfd..f9928267d 100644 --- a/girepository/tests/meson.build +++ b/girepository/tests/meson.build @@ -72,6 +72,7 @@ if enable_gir if cc.get_id() != 'msvc' girepository_tests += { 'autoptr-girepository' : { + 'dependencies': [libffi_dep], 'source' : 'autoptr.c', 'depends': gio_gir_testing_dep, }, From 9d0988ca62ee96e09aa76abbd65ff192cfce6858 Mon Sep 17 00:00:00 2001 From: Andoni Morales Alastruey Date: Tue, 28 May 2024 17:30:17 +0200 Subject: [PATCH 3/4] girepository: fix find_by_type tests in macOS --- girepository/tests/repository.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/girepository/tests/repository.c b/girepository/tests/repository.c index c79b0df9a..312bd615c 100644 --- a/girepository/tests/repository.c +++ b/girepository/tests/repository.c @@ -35,7 +35,7 @@ #include "glib.h" #include "test-common.h" -#if defined(G_OS_UNIX) +#if defined(G_OS_UNIX) && !defined(__APPLE__) #include "gio/gdesktopappinfo.h" #elif defined(G_OS_WIN32) #include "gio/gwin32inputstream.h" @@ -837,7 +837,7 @@ test_repository_find_by_gtype (RepositoryFixture *fx, GType platform_specific_type; const char *expected_name, *expected_namespace; -#if defined(G_OS_UNIX) +#if defined(G_OS_UNIX) && !(__APPLE__) platform_specific_type = G_TYPE_DESKTOP_APP_INFO; expected_name = "DesktopAppInfo"; expected_namespace = "GioUnix"; From c93c4d41e8625e9176763ba8b7258aac42c4f313 Mon Sep 17 00:00:00 2001 From: Andoni Morales Alastruey Date: Tue, 28 May 2024 17:33:17 +0200 Subject: [PATCH 4/4] girepository: add default search paths tests for relocations --- girepository/tests/repository-search-paths.c | 29 ++++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/girepository/tests/repository-search-paths.c b/girepository/tests/repository-search-paths.c index 7bfe14dbd..cef32368c 100644 --- a/girepository/tests/repository-search-paths.c +++ b/girepository/tests/repository-search-paths.c @@ -22,6 +22,23 @@ #include "glib.h" #include "girepository.h" +static char * +test_repository_search_paths_get_expected_libdir_path (void) +{ +#if defined(G_PLATFORM_WIN32) + const char *tests_build_dir = g_getenv ("G_TEST_BUILDDIR"); + char *expected_rel_path = g_build_filename (tests_build_dir, "lib", "girepository-1.0", NULL); +#elif defined(__APPLE__) + const char *tests_build_dir = g_getenv ("G_TEST_BUILDDIR"); + char *expected_rel_path = g_build_filename (tests_build_dir, "..", "girepository-1.0", NULL); +#else /* !G_PLATFORM_WIN32 && !__APPLE__ */ + char *expected_rel_path = g_build_filename (GOBJECT_INTROSPECTION_LIBDIR, "girepository-1.0", NULL); +#endif + char *expected_path = g_canonicalize_filename (expected_rel_path, NULL); + g_clear_pointer (&expected_rel_path, g_free); + return expected_path; +} + static void test_repository_search_paths_default (void) { @@ -37,11 +54,9 @@ test_repository_search_paths_default (void) g_assert_cmpstr (search_paths[0], ==, g_get_tmp_dir ()); -#ifndef G_PLATFORM_WIN32 - char *expected_path = g_build_filename (GOBJECT_INTROSPECTION_LIBDIR, "girepository-1.0", NULL); + char *expected_path = test_repository_search_paths_get_expected_libdir_path (); g_assert_cmpstr (search_paths[1], ==, expected_path); g_clear_pointer (&expected_path, g_free); -#endif g_clear_object (&repository); } @@ -63,11 +78,9 @@ test_repository_search_paths_prepend (void) g_assert_cmpstr (search_paths[0], ==, g_test_get_dir (G_TEST_BUILT)); g_assert_cmpstr (search_paths[1], ==, g_get_tmp_dir ()); -#ifndef G_PLATFORM_WIN32 - char *expected_path = g_build_filename (GOBJECT_INTROSPECTION_LIBDIR, "girepository-1.0", NULL); + char *expected_path = test_repository_search_paths_get_expected_libdir_path (); g_assert_cmpstr (search_paths[2], ==, expected_path); g_clear_pointer (&expected_path, g_free); -#endif gi_repository_prepend_search_path (repository, g_test_get_dir (G_TEST_DIST)); search_paths = gi_repository_get_search_path (repository, &n_search_paths); @@ -78,11 +91,9 @@ test_repository_search_paths_prepend (void) g_assert_cmpstr (search_paths[1], ==, g_test_get_dir (G_TEST_BUILT)); g_assert_cmpstr (search_paths[2], ==, g_get_tmp_dir ()); -#ifndef G_PLATFORM_WIN32 - expected_path = g_build_filename (GOBJECT_INTROSPECTION_LIBDIR, "girepository-1.0", NULL); + expected_path = test_repository_search_paths_get_expected_libdir_path (); g_assert_cmpstr (search_paths[3], ==, expected_path); g_clear_pointer (&expected_path, g_free); -#endif g_clear_object (&repository); }