| /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- |
| * GObject introspection: Repository implementation |
| * |
| * Copyright (C) 2005 Matthias Clasen |
| * Copyright (C) 2008 Colin Walters <walters@verbum.org> |
| * Copyright (C) 2008 Red Hat, Inc. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| #include "config.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| #include <glib.h> |
| #include <glib/gprintf.h> |
| #include <gmodule.h> |
| #include "gibaseinfo-private.h" |
| #include "girepository.h" |
| #include "gitypelib-internal.h" |
| #include "girepository-private.h" |
| |
| /** |
| * GIRepository: |
| * |
| * `GIRepository` is used to manage repositories of namespaces. Namespaces |
| * are represented on disk by type libraries (`.typelib` files). |
| * |
| * The individual pieces of API within a type library are represented by |
| * subclasses of [class@GIRepository.BaseInfo]. These can be found using |
| * methods like [method@GIRepository.Repository.find_by_name] or |
| * [method@GIRepository.Repository.get_info]. |
| * |
| * You are responsible for ensuring that the lifetime of the |
| * [class@GIRepository.Repository] exceeds that of the lifetime of any of its |
| * [class@GIRepository.BaseInfo]s. This cannot be guaranteed by using internal |
| * references within libgirepository as that would affect performance. |
| * |
| * ### Discovery of type libraries |
| * |
| * `GIRepository` will typically look for a `girepository-1.0` directory |
| * under the library directory used when compiling gobject-introspection. On a |
| * standard Linux system this will end up being `/usr/lib/girepository-1.0`. |
| * |
| * It is possible to control the search paths programmatically, using |
| * [method@GIRepository.Repository.prepend_search_path]. It is also possible to |
| * modify the search paths by using the `GI_TYPELIB_PATH` environment variable. |
| * The environment variable takes precedence over the default search path |
| * and the [method@GIRepository.Repository.prepend_search_path] calls. |
| * |
| * Since: 2.80 |
| */ |
| |
| /* The namespace and version corresponding to libgirepository itself, so |
| * that we can refuse to load typelibs corresponding to the older, |
| * incompatible version of this same library in gobject-introspection. */ |
| #define GIREPOSITORY_TYPELIB_NAME "GIRepository" |
| #define GIREPOSITORY_TYPELIB_VERSION "3.0" |
| #define GIREPOSITORY_TYPELIB_FILENAME \ |
| GIREPOSITORY_TYPELIB_NAME "-" GIREPOSITORY_TYPELIB_VERSION ".typelib" |
| |
| typedef struct { |
| size_t n_interfaces; |
| GIBaseInfo *interfaces[]; |
| } GTypeInterfaceCache; |
| |
| static void |
| gtype_interface_cache_free (gpointer data) |
| { |
| GTypeInterfaceCache *cache = data; |
| |
| for (size_t i = 0; i < cache->n_interfaces; i++) |
| gi_base_info_unref ((GIBaseInfo*) cache->interfaces[i]); |
| g_free (cache); |
| } |
| |
| struct _GIRepository |
| { |
| GObject parent; |
| |
| GPtrArray *typelib_search_path; /* (element-type filename) (owned) */ |
| GPtrArray *library_paths; /* (element-type filename) (owned) */ |
| |
| GHashTable *typelibs; /* (string) namespace -> GITypelib */ |
| GHashTable *lazy_typelibs; /* (string) namespace-version -> GITypelib */ |
| GHashTable *info_by_gtype; /* GType -> GIBaseInfo */ |
| GHashTable *info_by_error_domain; /* GQuark -> GIBaseInfo */ |
| GHashTable *interfaces_for_gtype; /* GType -> GTypeInterfaceCache */ |
| GHashTable *unknown_gtypes; /* hashset of GType */ |
| |
| char **cached_shared_libraries; /* (owned) (nullable) (array zero-terminated=1) */ |
| size_t cached_n_shared_libraries; /* length of @cached_shared_libraries, not including NULL terminator */ |
| }; |
| |
| G_DEFINE_TYPE (GIRepository, gi_repository, G_TYPE_OBJECT); |
| |
| #ifdef G_PLATFORM_WIN32 |
| |
| #include <windows.h> |
| |
| static HMODULE girepository_dll = NULL; |
| |
| #ifdef DLL_EXPORT |
| |
| BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); |
| |
| BOOL WINAPI |
| DllMain (HINSTANCE hinstDLL, |
| DWORD fdwReason, |
| LPVOID lpvReserved) |
| { |
| if (fdwReason == DLL_PROCESS_ATTACH) |
| girepository_dll = hinstDLL; |
| |
| return TRUE; |
| } |
| |
| #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 |
| |
| static void |
| gi_repository_init (GIRepository *repository) |
| { |
| /* typelib search path */ |
| { |
| const char *libdir; |
| char *typelib_dir; |
| const char *type_lib_path_env; |
| |
| /* This variable is intended to take precedence over both: |
| * - the default search path; |
| * - all gi_repository_prepend_search_path() calls. |
| */ |
| type_lib_path_env = g_getenv ("GI_TYPELIB_PATH"); |
| |
| if (type_lib_path_env) |
| { |
| char **custom_dirs; |
| |
| custom_dirs = g_strsplit (type_lib_path_env, G_SEARCHPATH_SEPARATOR_S, 0); |
| repository->typelib_search_path = |
| g_ptr_array_new_take_null_terminated ((gpointer) g_steal_pointer (&custom_dirs), g_free); |
| } |
| else |
| { |
| repository->typelib_search_path = g_ptr_array_new_null_terminated (1, g_free, TRUE); |
| } |
| |
| libdir = GOBJECT_INTROSPECTION_LIBDIR; |
| |
| typelib_dir = g_build_filename (libdir, "girepository-1.0", NULL); |
| |
| g_ptr_array_add (repository->typelib_search_path, g_steal_pointer (&typelib_dir)); |
| } |
| |
| repository->library_paths = g_ptr_array_new_null_terminated (1, g_free, TRUE); |
| |
| repository->typelibs |
| = g_hash_table_new_full (g_str_hash, g_str_equal, |
| (GDestroyNotify) g_free, |
| (GDestroyNotify) gi_typelib_unref); |
| repository->lazy_typelibs |
| = g_hash_table_new_full (g_str_hash, g_str_equal, |
| (GDestroyNotify) g_free, |
| (GDestroyNotify) gi_typelib_unref); |
| repository->info_by_gtype |
| = g_hash_table_new_full (g_direct_hash, g_direct_equal, |
| (GDestroyNotify) NULL, |
| (GDestroyNotify) gi_base_info_unref); |
| repository->info_by_error_domain |
| = g_hash_table_new_full (g_direct_hash, g_direct_equal, |
| (GDestroyNotify) NULL, |
| (GDestroyNotify) gi_base_info_unref); |
| repository->interfaces_for_gtype |
| = g_hash_table_new_full (g_direct_hash, g_direct_equal, |
| (GDestroyNotify) NULL, |
| (GDestroyNotify) gtype_interface_cache_free); |
| repository->unknown_gtypes = g_hash_table_new (NULL, NULL); |
| } |
| |
| static void |
| gi_repository_finalize (GObject *object) |
| { |
| GIRepository *repository = GI_REPOSITORY (object); |
| |
| g_hash_table_destroy (repository->typelibs); |
| g_hash_table_destroy (repository->lazy_typelibs); |
| g_hash_table_destroy (repository->info_by_gtype); |
| g_hash_table_destroy (repository->info_by_error_domain); |
| g_hash_table_destroy (repository->interfaces_for_gtype); |
| g_hash_table_destroy (repository->unknown_gtypes); |
| |
| g_clear_pointer (&repository->cached_shared_libraries, g_strfreev); |
| |
| g_clear_pointer (&repository->library_paths, g_ptr_array_unref); |
| g_clear_pointer (&repository->typelib_search_path, g_ptr_array_unref); |
| |
| (* G_OBJECT_CLASS (gi_repository_parent_class)->finalize) (G_OBJECT (repository)); |
| } |
| |
| static void |
| gi_repository_class_init (GIRepositoryClass *class) |
| { |
| GObjectClass *gobject_class; |
| |
| gobject_class = G_OBJECT_CLASS (class); |
| |
| gobject_class->finalize = gi_repository_finalize; |
| } |
| |
| /** |
| * gi_repository_prepend_search_path: |
| * @repository: A #GIRepository |
| * @directory: (type filename): directory name to prepend to the typelib |
| * search path |
| * |
| * Prepends @directory to the typelib search path. |
| * |
| * See also: gi_repository_get_search_path(). |
| * |
| * Since: 2.80 |
| */ |
| void |
| gi_repository_prepend_search_path (GIRepository *repository, |
| const char *directory) |
| { |
| g_return_if_fail (GI_IS_REPOSITORY (repository)); |
| |
| g_ptr_array_insert (repository->typelib_search_path, 0, g_strdup (directory)); |
| } |
| |
| /** |
| * gi_repository_get_search_path: |
| * @repository: A #GIRepository |
| * @n_paths_out: (optional) (out): The number of search paths returned. |
| * |
| * Returns the current search path [class@GIRepository.Repository] will use when |
| * loading typelib files. |
| * |
| * The list is internal to [class@GIRepository.Repository] and should not be |
| * freed, nor should its string elements. |
| * |
| * The list is guaranteed to be `NULL` terminated. The `NULL` terminator is not |
| * counted in @n_paths_out. |
| * |
| * Returns: (element-type filename) (transfer none) (array length=n_paths_out): list of search paths, most |
| * important first |
| * Since: 2.80 |
| */ |
| const char * const * |
| gi_repository_get_search_path (GIRepository *repository, |
| size_t *n_paths_out) |
| { |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| |
| if G_UNLIKELY (!repository->typelib_search_path || |
| !repository->typelib_search_path->pdata) |
| { |
| static const char * const empty_search_path[] = {NULL}; |
| |
| if (n_paths_out) |
| *n_paths_out = 0; |
| |
| return empty_search_path; |
| } |
| |
| if (n_paths_out) |
| *n_paths_out = repository->typelib_search_path->len; |
| |
| return (const char * const *) repository->typelib_search_path->pdata; |
| } |
| |
| /** |
| * gi_repository_prepend_library_path: |
| * @repository: A #GIRepository |
| * @directory: (type filename): a single directory to scan for shared libraries |
| * |
| * Prepends @directory to the search path that is used to |
| * search shared libraries referenced by imported namespaces. |
| * |
| * Multiple calls to this function all contribute to the final |
| * list of paths. |
| * |
| * The list of paths is unique to @repository. When a typelib is loaded by the |
| * repository, the list of paths from the @repository at that instant is used |
| * by the typelib for loading its modules. |
| * |
| * If the library is not found in the directories configured |
| * in this way, loading will fall back to the system library |
| * path (i.e. `LD_LIBRARY_PATH` and `DT_RPATH` in ELF systems). |
| * See the documentation of your dynamic linker for full details. |
| * |
| * Since: 2.80 |
| */ |
| void |
| gi_repository_prepend_library_path (GIRepository *repository, |
| const char *directory) |
| { |
| g_return_if_fail (GI_IS_REPOSITORY (repository)); |
| |
| g_ptr_array_insert (repository->library_paths, 0, g_strdup (directory)); |
| } |
| |
| /** |
| * gi_repository_get_library_path: |
| * @repository: A #GIRepository |
| * @n_paths_out: (optional) (out): The number of library paths returned. |
| * |
| * Returns the current search path [class@GIRepository.Repository] will use when |
| * loading shared libraries referenced by imported namespaces. |
| * |
| * The list is internal to [class@GIRepository.Repository] and should not be |
| * freed, nor should its string elements. |
| * |
| * The list is guaranteed to be `NULL` terminated. The `NULL` terminator is not |
| * counted in @n_paths_out. |
| * |
| * Returns: (element-type filename) (transfer none) (array length=n_paths_out): list of search paths, most |
| * important first |
| * Since: 2.80 |
| */ |
| const char * const * |
| gi_repository_get_library_path (GIRepository *repository, |
| size_t *n_paths_out) |
| { |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| |
| if G_UNLIKELY (!repository->library_paths || !repository->library_paths->pdata) |
| { |
| static const char * const empty_search_path[] = {NULL}; |
| |
| if (n_paths_out) |
| *n_paths_out = 0; |
| |
| return empty_search_path; |
| } |
| |
| if (n_paths_out) |
| *n_paths_out = repository->library_paths->len; |
| |
| return (const char * const *) repository->library_paths->pdata; |
| } |
| |
| static char * |
| build_typelib_key (const char *name, const char *source) |
| { |
| GString *str = g_string_new (name); |
| g_string_append_c (str, '\0'); |
| g_string_append (str, source); |
| return g_string_free (str, FALSE); |
| } |
| |
| /* Note: Returns %NULL (not an empty %NULL-terminated array) if there are no |
| * dependencies. */ |
| static char ** |
| get_typelib_dependencies (GITypelib *typelib) |
| { |
| Header *header; |
| const char *dependencies_glob; |
| |
| header = (Header *)typelib->data; |
| |
| if (header->dependencies == 0) |
| return NULL; |
| |
| dependencies_glob = gi_typelib_get_string (typelib, header->dependencies); |
| return g_strsplit (dependencies_glob, "|", 0); |
| } |
| |
| static GITypelib * |
| check_version_conflict (GITypelib *typelib, |
| const char *namespace, |
| const char *expected_version, |
| char **version_conflict) |
| { |
| Header *header; |
| const char *loaded_version; |
| |
| if (expected_version == NULL) |
| { |
| if (version_conflict) |
| *version_conflict = NULL; |
| return typelib; |
| } |
| |
| header = (Header*)typelib->data; |
| loaded_version = gi_typelib_get_string (typelib, header->nsversion); |
| g_assert (loaded_version != NULL); |
| |
| if (strcmp (expected_version, loaded_version) != 0) |
| { |
| if (version_conflict) |
| *version_conflict = (char*)loaded_version; |
| return NULL; |
| } |
| if (version_conflict) |
| *version_conflict = NULL; |
| return typelib; |
| } |
| |
| static GITypelib * |
| get_registered_status (GIRepository *repository, |
| const char *namespace, |
| const char *version, |
| gboolean allow_lazy, |
| gboolean *lazy_status, |
| char **version_conflict) |
| { |
| GITypelib *typelib; |
| |
| if (lazy_status) |
| *lazy_status = FALSE; |
| typelib = g_hash_table_lookup (repository->typelibs, namespace); |
| if (typelib) |
| return check_version_conflict (typelib, namespace, version, version_conflict); |
| typelib = g_hash_table_lookup (repository->lazy_typelibs, namespace); |
| if (!typelib) |
| return NULL; |
| if (lazy_status) |
| *lazy_status = TRUE; |
| if (!allow_lazy) |
| return NULL; |
| return check_version_conflict (typelib, namespace, version, version_conflict); |
| } |
| |
| static GITypelib * |
| get_registered (GIRepository *repository, |
| const char *namespace, |
| const char *version) |
| { |
| return get_registered_status (repository, namespace, version, TRUE, NULL, NULL); |
| } |
| |
| static gboolean |
| load_dependencies_recurse (GIRepository *repository, |
| GITypelib *typelib, |
| GError **error) |
| { |
| char **dependencies; |
| |
| dependencies = get_typelib_dependencies (typelib); |
| |
| if (dependencies != NULL) |
| { |
| int i; |
| |
| for (i = 0; dependencies[i]; i++) |
| { |
| char *dependency = dependencies[i]; |
| const char *last_dash; |
| char *dependency_namespace; |
| const char *dependency_version; |
| |
| last_dash = strrchr (dependency, '-'); |
| dependency_namespace = g_strndup (dependency, last_dash - dependency); |
| dependency_version = last_dash+1; |
| |
| if (!gi_repository_require (repository, dependency_namespace, dependency_version, |
| 0, error)) |
| { |
| g_free (dependency_namespace); |
| g_strfreev (dependencies); |
| return FALSE; |
| } |
| g_free (dependency_namespace); |
| } |
| g_strfreev (dependencies); |
| } |
| return TRUE; |
| } |
| |
| static const char * |
| register_internal (GIRepository *repository, |
| const char *source, |
| gboolean lazy, |
| GITypelib *typelib, |
| GError **error) |
| { |
| Header *header; |
| const char *namespace; |
| |
| g_return_val_if_fail (typelib != NULL, FALSE); |
| |
| header = (Header *)typelib->data; |
| |
| g_return_val_if_fail (header != NULL, FALSE); |
| |
| namespace = gi_typelib_get_string (typelib, header->namespace); |
| |
| if (lazy) |
| { |
| g_assert (!g_hash_table_lookup (repository->lazy_typelibs, |
| namespace)); |
| g_hash_table_insert (repository->lazy_typelibs, |
| build_typelib_key (namespace, source), gi_typelib_ref (typelib)); |
| } |
| else |
| { |
| gpointer value; |
| char *key; |
| |
| /* First, try loading all the dependencies */ |
| if (!load_dependencies_recurse (repository, typelib, error)) |
| return NULL; |
| |
| /* Check if we are transitioning from lazily loaded state */ |
| if (g_hash_table_lookup_extended (repository->lazy_typelibs, |
| namespace, |
| (gpointer)&key, &value)) |
| g_hash_table_remove (repository->lazy_typelibs, key); |
| else |
| key = build_typelib_key (namespace, source); |
| |
| g_hash_table_insert (repository->typelibs, |
| g_steal_pointer (&key), |
| gi_typelib_ref (typelib)); |
| } |
| |
| /* These types might be resolved now, clear the cache */ |
| g_hash_table_remove_all (repository->unknown_gtypes); |
| |
| return namespace; |
| } |
| |
| /** |
| * gi_repository_get_immediate_dependencies: |
| * @repository: A #GIRepository |
| * @namespace_: Namespace of interest |
| * @n_dependencies_out: (optional) (out): Return location for the number of |
| * dependencies |
| * |
| * Return an array of the immediate versioned dependencies for @namespace_. |
| * Returned strings are of the form `namespace-version`. |
| * |
| * Note: @namespace_ must have already been loaded using a function |
| * such as [method@GIRepository.Repository.require] before calling this |
| * function. |
| * |
| * To get the transitive closure of dependencies for @namespace_, use |
| * [method@GIRepository.Repository.get_dependencies]. |
| * |
| * The list is guaranteed to be `NULL` terminated. The `NULL` terminator is not |
| * counted in @n_dependencies_out. |
| * |
| * Returns: (transfer full) (array length=n_dependencies_out): String array of |
| * immediate versioned dependencies |
| * Since: 2.80 |
| */ |
| char ** |
| gi_repository_get_immediate_dependencies (GIRepository *repository, |
| const char *namespace, |
| size_t *n_dependencies_out) |
| { |
| GITypelib *typelib; |
| char **deps; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| g_return_val_if_fail (namespace != NULL, NULL); |
| |
| typelib = get_registered (repository, namespace, NULL); |
| g_return_val_if_fail (typelib != NULL, NULL); |
| |
| /* Ensure we always return a non-%NULL vector. */ |
| deps = get_typelib_dependencies (typelib); |
| if (deps == NULL) |
| deps = g_strsplit ("", "|", 0); |
| |
| if (n_dependencies_out != NULL) |
| *n_dependencies_out = g_strv_length (deps); |
| |
| return deps; |
| } |
| |
| /* Load the transitive closure of dependency namespace-version strings for the |
| * given @typelib. @repository must be non-%NULL. @transitive_dependencies must |
| * be a pre-existing GHashTable<owned utf8, owned utf8> set for storing the |
| * dependencies. */ |
| static void |
| get_typelib_dependencies_transitive (GIRepository *repository, |
| GITypelib *typelib, |
| GHashTable *transitive_dependencies) |
| { |
| char **immediate_dependencies; |
| |
| immediate_dependencies = get_typelib_dependencies (typelib); |
| |
| for (size_t i = 0; immediate_dependencies != NULL && immediate_dependencies[i]; i++) |
| { |
| char *dependency; |
| const char *last_dash; |
| char *dependency_namespace; |
| |
| dependency = immediate_dependencies[i]; |
| |
| /* Steal from the strv. */ |
| g_hash_table_add (transitive_dependencies, dependency); |
| immediate_dependencies[i] = NULL; |
| |
| /* Recurse for this namespace. */ |
| last_dash = strrchr (dependency, '-'); |
| dependency_namespace = g_strndup (dependency, last_dash - dependency); |
| |
| typelib = get_registered (repository, dependency_namespace, NULL); |
| g_return_if_fail (typelib != NULL); |
| get_typelib_dependencies_transitive (repository, typelib, |
| transitive_dependencies); |
| |
| g_free (dependency_namespace); |
| } |
| |
| g_free (immediate_dependencies); |
| } |
| |
| /** |
| * gi_repository_get_dependencies: |
| * @repository: A #GIRepository |
| * @namespace_: Namespace of interest |
| * @n_dependencies_out: (optional) (out): Return location for the number of |
| * dependencies |
| * |
| * Retrieves all (transitive) versioned dependencies for |
| * @namespace_. |
| * |
| * The returned strings are of the form `namespace-version`. |
| * |
| * Note: @namespace_ must have already been loaded using a function |
| * such as [method@GIRepository.Repository.require] before calling this |
| * function. |
| * |
| * To get only the immediate dependencies for @namespace_, use |
| * [method@GIRepository.Repository.get_immediate_dependencies]. |
| * |
| * The list is guaranteed to be `NULL` terminated. The `NULL` terminator is not |
| * counted in @n_dependencies_out. |
| * |
| * Returns: (transfer full) (array length=n_dependencies_out): String array of |
| * all versioned dependencies |
| * Since: 2.80 |
| */ |
| char ** |
| gi_repository_get_dependencies (GIRepository *repository, |
| const char *namespace, |
| size_t *n_dependencies_out) |
| { |
| GITypelib *typelib; |
| GHashTable *transitive_dependencies; /* set of owned utf8 */ |
| GHashTableIter iter; |
| char *dependency; |
| GPtrArray *out; /* owned utf8 elements */ |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| g_return_val_if_fail (namespace != NULL, NULL); |
| |
| typelib = get_registered (repository, namespace, NULL); |
| g_return_val_if_fail (typelib != NULL, NULL); |
| |
| /* Load the dependencies. */ |
| transitive_dependencies = g_hash_table_new_full (g_str_hash, g_str_equal, |
| g_free, NULL); |
| get_typelib_dependencies_transitive (repository, typelib, |
| transitive_dependencies); |
| |
| /* Convert to a string array. */ |
| out = g_ptr_array_new_null_terminated (g_hash_table_size (transitive_dependencies), |
| g_free, TRUE); |
| g_hash_table_iter_init (&iter, transitive_dependencies); |
| |
| while (g_hash_table_iter_next (&iter, (gpointer) &dependency, NULL)) |
| { |
| g_ptr_array_add (out, dependency); |
| g_hash_table_iter_steal (&iter); |
| } |
| |
| g_hash_table_unref (transitive_dependencies); |
| |
| if (n_dependencies_out != NULL) |
| *n_dependencies_out = out->len; |
| |
| return (char **) g_ptr_array_free (out, FALSE); |
| } |
| |
| /** |
| * gi_repository_load_typelib: |
| * @repository: A #GIRepository |
| * @typelib: (transfer none): the typelib to load |
| * @flags: flags affecting the loading operation |
| * @error: return location for a [type@GLib.Error], or `NULL` |
| * |
| * Load the given @typelib into the repository. |
| * |
| * Returns: namespace of the loaded typelib |
| * Since: 2.80 |
| */ |
| const char * |
| gi_repository_load_typelib (GIRepository *repository, |
| GITypelib *typelib, |
| GIRepositoryLoadFlags flags, |
| GError **error) |
| { |
| Header *header; |
| const char *namespace; |
| const char *nsversion; |
| gboolean allow_lazy = flags & GI_REPOSITORY_LOAD_FLAG_LAZY; |
| gboolean is_lazy; |
| char *version_conflict; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| |
| header = (Header *) typelib->data; |
| namespace = gi_typelib_get_string (typelib, header->namespace); |
| nsversion = gi_typelib_get_string (typelib, header->nsversion); |
| |
| if (get_registered_status (repository, namespace, nsversion, allow_lazy, |
| &is_lazy, &version_conflict)) |
| { |
| if (version_conflict != NULL) |
| { |
| g_set_error (error, GI_REPOSITORY_ERROR, |
| GI_REPOSITORY_ERROR_NAMESPACE_VERSION_CONFLICT, |
| "Attempting to load namespace '%s', version '%s', but '%s' is already loaded", |
| namespace, nsversion, version_conflict); |
| return NULL; |
| } |
| return namespace; |
| } |
| return register_internal (repository, "<builtin>", |
| allow_lazy, typelib, error); |
| } |
| |
| /** |
| * gi_repository_is_registered: |
| * @repository: A #GIRepository |
| * @namespace_: Namespace of interest |
| * @version: (nullable): Required version, may be `NULL` for latest |
| * |
| * Check whether a particular namespace (and optionally, a specific |
| * version thereof) is currently loaded. |
| * |
| * This function is likely to only be useful in unusual circumstances; in order |
| * to act upon metadata in the namespace, you should call |
| * [method@GIRepository.Repository.require] instead which will ensure the |
| * namespace is loaded, and return as quickly as this function will if it has |
| * already been loaded. |
| * |
| * Returns: `TRUE` if namespace-version is loaded, `FALSE` otherwise |
| * Since: 2.80 |
| */ |
| gboolean |
| gi_repository_is_registered (GIRepository *repository, |
| const char *namespace, |
| const char *version) |
| { |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), FALSE); |
| |
| return get_registered (repository, namespace, version) != NULL; |
| } |
| |
| /** |
| * gi_repository_new: |
| * |
| * Create a new [class@GIRepository.Repository]. |
| * |
| * Returns: (transfer full): a new [class@GIRepository.Repository] |
| * Since: 2.80 |
| */ |
| GIRepository * |
| gi_repository_new (void) |
| { |
| return g_object_new (GI_TYPE_REPOSITORY, NULL); |
| } |
| |
| /** |
| * gi_repository_get_n_infos: |
| * @repository: A #GIRepository |
| * @namespace_: Namespace to inspect |
| * |
| * This function returns the number of metadata entries in |
| * given namespace @namespace_. |
| * |
| * The namespace must have already been loaded before calling this function. |
| * |
| * Returns: number of metadata entries |
| * Since: 2.80 |
| */ |
| unsigned int |
| gi_repository_get_n_infos (GIRepository *repository, |
| const char *namespace) |
| { |
| GITypelib *typelib; |
| unsigned int n_interfaces = 0; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), -1); |
| g_return_val_if_fail (namespace != NULL, -1); |
| |
| typelib = get_registered (repository, namespace, NULL); |
| |
| g_return_val_if_fail (typelib != NULL, -1); |
| |
| n_interfaces = ((Header *)typelib->data)->n_local_entries; |
| |
| return n_interfaces; |
| } |
| |
| /** |
| * gi_repository_get_info: |
| * @repository: A #GIRepository |
| * @namespace_: Namespace to inspect |
| * @idx: 0-based offset into namespace metadata for entry |
| * |
| * This function returns a particular metadata entry in the |
| * given namespace @namespace_. |
| * |
| * The namespace must have already been loaded before calling this function. |
| * See [method@GIRepository.Repository.get_n_infos] to find the maximum number |
| * of entries. It is an error to pass an invalid @idx to this function. |
| * |
| * Returns: (transfer full) (not nullable): [class@GIRepository.BaseInfo] |
| * containing metadata |
| * Since: 2.80 |
| */ |
| GIBaseInfo * |
| gi_repository_get_info (GIRepository *repository, |
| const char *namespace, |
| unsigned int idx) |
| { |
| GITypelib *typelib; |
| DirEntry *entry; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| g_return_val_if_fail (namespace != NULL, NULL); |
| g_return_val_if_fail (idx < G_MAXUINT16, NULL); |
| |
| typelib = get_registered (repository, namespace, NULL); |
| |
| g_return_val_if_fail (typelib != NULL, NULL); |
| |
| entry = gi_typelib_get_dir_entry (typelib, idx + 1); |
| g_return_val_if_fail (entry != NULL, NULL); |
| |
| return gi_info_new_full (gi_typelib_blob_type_to_info_type (entry->blob_type), |
| repository, |
| NULL, typelib, entry->offset); |
| } |
| |
| typedef struct { |
| const char *gtype_name; |
| GITypelib *result_typelib; |
| } FindByGTypeData; |
| |
| static DirEntry * |
| find_by_gtype (GHashTable *table, FindByGTypeData *data, gboolean check_prefix) |
| { |
| GHashTableIter iter; |
| gpointer key, value; |
| DirEntry *ret; |
| |
| g_hash_table_iter_init (&iter, table); |
| while (g_hash_table_iter_next (&iter, &key, &value)) |
| { |
| GITypelib *typelib = (GITypelib*)value; |
| if (check_prefix) |
| { |
| if (!gi_typelib_matches_gtype_name_prefix (typelib, data->gtype_name)) |
| continue; |
| } |
| |
| ret = gi_typelib_get_dir_entry_by_gtype_name (typelib, data->gtype_name); |
| if (ret) |
| { |
| data->result_typelib = typelib; |
| return ret; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * gi_repository_find_by_gtype: |
| * @repository: A #GIRepository |
| * @gtype: [type@GObject.Type] to search for |
| * |
| * Searches all loaded namespaces for a particular [type@GObject.Type]. |
| * |
| * Note that in order to locate the metadata, the namespace corresponding to |
| * the type must first have been loaded. There is currently no |
| * mechanism for determining the namespace which corresponds to an |
| * arbitrary [type@GObject.Type] — thus, this function will operate most |
| * reliably when you know the [type@GObject.Type] is from a loaded namespace. |
| * |
| * Returns: (transfer full) (nullable): [class@GIRepository.BaseInfo] |
| * representing metadata about @type, or `NULL` if none found |
| * Since: 2.80 |
| */ |
| GIBaseInfo * |
| gi_repository_find_by_gtype (GIRepository *repository, |
| GType gtype) |
| { |
| FindByGTypeData data; |
| GIBaseInfo *cached; |
| DirEntry *entry; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| g_return_val_if_fail (gtype != G_TYPE_INVALID, NULL); |
| |
| cached = g_hash_table_lookup (repository->info_by_gtype, |
| (gpointer)gtype); |
| |
| if (cached != NULL) |
| return gi_base_info_ref (cached); |
| |
| if (g_hash_table_contains (repository->unknown_gtypes, (gpointer)gtype)) |
| return NULL; |
| |
| data.gtype_name = g_type_name (gtype); |
| data.result_typelib = NULL; |
| |
| /* Inside each typelib, we include the "C prefix" which acts as |
| * a namespace mechanism. For GtkTreeView, the C prefix is Gtk. |
| * Given the assumption that GTypes for a library also use the |
| * C prefix, we know we can skip examining a typelib if our |
| * target type does not have this typelib's C prefix. Use this |
| * assumption as our first attempt at locating the DirEntry. |
| */ |
| entry = find_by_gtype (repository->typelibs, &data, TRUE); |
| if (entry == NULL) |
| entry = find_by_gtype (repository->lazy_typelibs, &data, TRUE); |
| |
| /* Not ever class library necessarily specifies a correct c_prefix, |
| * so take a second pass. This time we will try a global lookup, |
| * ignoring prefixes. |
| * See http://bugzilla.gnome.org/show_bug.cgi?id=564016 |
| */ |
| if (entry == NULL) |
| entry = find_by_gtype (repository->typelibs, &data, FALSE); |
| if (entry == NULL) |
| entry = find_by_gtype (repository->lazy_typelibs, &data, FALSE); |
| |
| if (entry != NULL) |
| { |
| cached = gi_info_new_full (gi_typelib_blob_type_to_info_type (entry->blob_type), |
| repository, |
| NULL, data.result_typelib, entry->offset); |
| |
| g_hash_table_insert (repository->info_by_gtype, |
| (gpointer) gtype, |
| gi_base_info_ref (cached)); |
| return cached; |
| } |
| else |
| { |
| g_hash_table_add (repository->unknown_gtypes, (gpointer) gtype); |
| return NULL; |
| } |
| } |
| |
| /** |
| * gi_repository_find_by_name: |
| * @repository: A #GIRepository |
| * @namespace_: Namespace which will be searched |
| * @name: Entry name to find |
| * |
| * Searches for a particular entry in a namespace. |
| * |
| * Before calling this function for a particular namespace, you must call |
| * [method@GIRepository.Repository.require] to load the namespace, or otherwise |
| * ensure the namespace has already been loaded. |
| * |
| * Returns: (transfer full) (nullable): [class@GIRepository.BaseInfo] |
| * representing metadata about @name, or `NULL` if none found |
| * Since: 2.80 |
| */ |
| GIBaseInfo * |
| gi_repository_find_by_name (GIRepository *repository, |
| const char *namespace, |
| const char *name) |
| { |
| GITypelib *typelib; |
| DirEntry *entry; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| g_return_val_if_fail (namespace != NULL, NULL); |
| |
| typelib = get_registered (repository, namespace, NULL); |
| g_return_val_if_fail (typelib != NULL, NULL); |
| |
| entry = gi_typelib_get_dir_entry_by_name (typelib, name); |
| if (entry == NULL) |
| return NULL; |
| return gi_info_new_full (gi_typelib_blob_type_to_info_type (entry->blob_type), |
| repository, |
| NULL, typelib, entry->offset); |
| } |
| |
| typedef struct { |
| GIRepository *repository; |
| GQuark domain; |
| |
| GITypelib *result_typelib; |
| DirEntry *result; |
| } FindByErrorDomainData; |
| |
| static void |
| find_by_error_domain_foreach (gpointer key, |
| gpointer value, |
| gpointer datap) |
| { |
| GITypelib *typelib = (GITypelib*)value; |
| FindByErrorDomainData *data = datap; |
| |
| if (data->result != NULL) |
| return; |
| |
| data->result = gi_typelib_get_dir_entry_by_error_domain (typelib, data->domain); |
| if (data->result) |
| data->result_typelib = typelib; |
| } |
| |
| /** |
| * gi_repository_find_by_error_domain: |
| * @repository: A #GIRepository |
| * @domain: a [type@GLib.Error] domain |
| * |
| * Searches for the enum type corresponding to the given [type@GLib.Error] |
| * domain. |
| * |
| * Before calling this function for a particular namespace, you must call |
| * [method@GIRepository.Repository.require] to load the namespace, or otherwise |
| * ensure the namespace has already been loaded. |
| * |
| * Returns: (transfer full) (nullable): [class@GIRepository.EnumInfo] |
| * representing metadata about @domain’s enum type, or `NULL` if none found |
| * Since: 2.80 |
| */ |
| GIEnumInfo * |
| gi_repository_find_by_error_domain (GIRepository *repository, |
| GQuark domain) |
| { |
| FindByErrorDomainData data; |
| GIEnumInfo *cached; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| |
| cached = g_hash_table_lookup (repository->info_by_error_domain, |
| GUINT_TO_POINTER (domain)); |
| |
| if (cached != NULL) |
| return (GIEnumInfo *) gi_base_info_ref ((GIBaseInfo *)cached); |
| |
| data.repository = repository; |
| data.domain = domain; |
| data.result_typelib = NULL; |
| data.result = NULL; |
| |
| g_hash_table_foreach (repository->typelibs, find_by_error_domain_foreach, &data); |
| if (data.result == NULL) |
| g_hash_table_foreach (repository->lazy_typelibs, find_by_error_domain_foreach, &data); |
| |
| if (data.result != NULL) |
| { |
| cached = (GIEnumInfo *) gi_info_new_full (gi_typelib_blob_type_to_info_type (data.result->blob_type), |
| repository, |
| NULL, data.result_typelib, data.result->offset); |
| |
| g_hash_table_insert (repository->info_by_error_domain, |
| GUINT_TO_POINTER (domain), |
| gi_base_info_ref ((GIBaseInfo *) cached)); |
| return cached; |
| } |
| return NULL; |
| } |
| |
| /** |
| * gi_repository_get_object_gtype_interfaces: |
| * @repository: a #GIRepository |
| * @gtype: a [type@GObject.Type] whose fundamental type is `G_TYPE_OBJECT` |
| * @n_interfaces_out: (out): Number of interfaces |
| * @interfaces_out: (out) (transfer none) (array length=n_interfaces_out): Interfaces for @gtype |
| * |
| * Look up the implemented interfaces for @gtype. |
| * |
| * This function cannot fail per se; but for a totally ‘unknown’ |
| * [type@GObject.Type], it may return 0 implemented interfaces. |
| * |
| * The semantics of this function are designed for a dynamic binding, |
| * where in certain cases (such as a function which returns an |
| * interface which may have ‘hidden’ implementation classes), not all |
| * data may be statically known, and will have to be determined from |
| * the [type@GObject.Type] of the object. An example is |
| * [func@Gio.File.new_for_path] returning a concrete class of |
| * `GLocalFile`, which is a [type@GObject.Type] we see at runtime, but |
| * not statically. |
| * |
| * Since: 2.80 |
| */ |
| void |
| gi_repository_get_object_gtype_interfaces (GIRepository *repository, |
| GType gtype, |
| size_t *n_interfaces_out, |
| GIInterfaceInfo ***interfaces_out) |
| { |
| GTypeInterfaceCache *cache; |
| |
| g_return_if_fail (GI_IS_REPOSITORY (repository)); |
| g_return_if_fail (g_type_fundamental (gtype) == G_TYPE_OBJECT); |
| |
| cache = g_hash_table_lookup (repository->interfaces_for_gtype, |
| (void *) gtype); |
| if (cache == NULL) |
| { |
| GType *interfaces; |
| unsigned int i; |
| unsigned int n_interfaces; |
| GList *interface_infos = NULL, *iter; |
| |
| interfaces = g_type_interfaces (gtype, &n_interfaces); |
| for (i = 0; i < n_interfaces; i++) |
| { |
| GIBaseInfo *base_info; |
| |
| base_info = gi_repository_find_by_gtype (repository, interfaces[i]); |
| if (base_info == NULL) |
| continue; |
| |
| if (gi_base_info_get_info_type (base_info) != GI_INFO_TYPE_INTERFACE) |
| { |
| /* FIXME - could this really happen? */ |
| gi_base_info_unref (base_info); |
| continue; |
| } |
| |
| if (!g_list_find (interface_infos, base_info)) |
| interface_infos = g_list_prepend (interface_infos, base_info); |
| } |
| |
| cache = g_malloc (sizeof (GTypeInterfaceCache) |
| + sizeof (GIBaseInfo*) * g_list_length (interface_infos)); |
| cache->n_interfaces = g_list_length (interface_infos); |
| for (iter = interface_infos, i = 0; iter; iter = iter->next, i++) |
| cache->interfaces[i] = iter->data; |
| g_list_free (interface_infos); |
| |
| g_hash_table_insert (repository->interfaces_for_gtype, (gpointer) gtype, |
| cache); |
| |
| g_free (interfaces); |
| } |
| |
| *n_interfaces_out = cache->n_interfaces; |
| *interfaces_out = (GIInterfaceInfo**)&cache->interfaces[0]; |
| } |
| |
| static void |
| collect_namespaces (gpointer key, |
| gpointer value, |
| gpointer data) |
| { |
| GList **list = data; |
| |
| *list = g_list_append (*list, key); |
| } |
| |
| /** |
| * gi_repository_get_loaded_namespaces: |
| * @repository: A #GIRepository |
| * @n_namespaces_out: (optional) (out): Return location for the number of |
| * namespaces |
| * |
| * Return the list of currently loaded namespaces. |
| * |
| * The list is guaranteed to be `NULL` terminated. The `NULL` terminator is not |
| * counted in @n_namespaces_out. |
| * |
| * Returns: (element-type utf8) (transfer full) (array length=n_namespaces_out): |
| * list of namespaces |
| * Since: 2.80 |
| */ |
| char ** |
| gi_repository_get_loaded_namespaces (GIRepository *repository, |
| size_t *n_namespaces_out) |
| { |
| GList *l, *list = NULL; |
| char **names; |
| size_t i; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| |
| g_hash_table_foreach (repository->typelibs, collect_namespaces, &list); |
| g_hash_table_foreach (repository->lazy_typelibs, collect_namespaces, &list); |
| |
| names = g_malloc0 (sizeof (char *) * (g_list_length (list) + 1)); |
| i = 0; |
| for (l = list; l; l = l->next) |
| names[i++] = g_strdup (l->data); |
| g_list_free (list); |
| |
| if (n_namespaces_out != NULL) |
| *n_namespaces_out = i; |
| |
| return names; |
| } |
| |
| /** |
| * gi_repository_get_version: |
| * @repository: A #GIRepository |
| * @namespace_: Namespace to inspect |
| * |
| * This function returns the loaded version associated with the given |
| * namespace @namespace_. |
| * |
| * Note: The namespace must have already been loaded using a function |
| * such as [method@GIRepository.Repository.require] before calling this |
| * function. |
| * |
| * Returns: Loaded version |
| * Since: 2.80 |
| */ |
| const char * |
| gi_repository_get_version (GIRepository *repository, |
| const char *namespace) |
| { |
| GITypelib *typelib; |
| Header *header; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| g_return_val_if_fail (namespace != NULL, NULL); |
| |
| typelib = get_registered (repository, namespace, NULL); |
| |
| g_return_val_if_fail (typelib != NULL, NULL); |
| |
| header = (Header *) typelib->data; |
| return gi_typelib_get_string (typelib, header->nsversion); |
| } |
| |
| /** |
| * gi_repository_get_shared_libraries: |
| * @repository: A #GIRepository |
| * @namespace_: Namespace to inspect |
| * @out_n_elements: (out) (optional): Return location for the number of elements |
| * in the returned array |
| * |
| * This function returns an array of paths to the |
| * shared C libraries associated with the given namespace @namespace_. |
| * |
| * There may be no shared library path associated, in which case this |
| * function will return `NULL`. |
| * |
| * Note: The namespace must have already been loaded using a function |
| * such as [method@GIRepository.Repository.require] before calling this |
| * function. |
| * |
| * The list is internal to [class@GIRepository.Repository] and should not be |
| * freed, nor should its string elements. |
| * |
| * The list is guaranteed to be `NULL` terminated. The `NULL` terminator is not |
| * counted in @out_n_elements. |
| * |
| * Returns: (nullable) (array length=out_n_elements) (transfer none): Array of |
| * paths to shared libraries, or `NULL` if none are associated |
| * Since: 2.80 |
| */ |
| const char * const * |
| gi_repository_get_shared_libraries (GIRepository *repository, |
| const char *namespace, |
| size_t *out_n_elements) |
| { |
| GITypelib *typelib; |
| Header *header; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| g_return_val_if_fail (namespace != NULL, NULL); |
| |
| typelib = get_registered (repository, namespace, NULL); |
| |
| g_return_val_if_fail (typelib != NULL, NULL); |
| |
| header = (Header *) typelib->data; |
| if (!header->shared_library) |
| { |
| if (out_n_elements != NULL) |
| *out_n_elements = 0; |
| return NULL; |
| } |
| |
| /* Populate the cache. */ |
| if (repository->cached_shared_libraries == NULL) |
| { |
| const char *comma_separated = gi_typelib_get_string (typelib, header->shared_library); |
| |
| if (comma_separated != NULL && *comma_separated != '\0') |
| { |
| repository->cached_shared_libraries = g_strsplit (comma_separated, ",", -1); |
| repository->cached_n_shared_libraries = g_strv_length (repository->cached_shared_libraries); |
| } |
| } |
| |
| if (out_n_elements != NULL) |
| *out_n_elements = repository->cached_n_shared_libraries; |
| |
| return (const char * const *) repository->cached_shared_libraries; |
| } |
| |
| /** |
| * gi_repository_get_c_prefix: |
| * @repository: A #GIRepository |
| * @namespace_: Namespace to inspect |
| * |
| * This function returns the ‘C prefix’, or the C level namespace |
| * associated with the given introspection namespace. |
| * |
| * Each C symbol starts with this prefix, as well each [type@GObject.Type] in |
| * the library. |
| * |
| * Note: The namespace must have already been loaded using a function |
| * such as [method@GIRepository.Repository.require] before calling this |
| * function. |
| * |
| * Returns: (nullable): C namespace prefix, or `NULL` if none associated |
| * Since: 2.80 |
| */ |
| const char * |
| gi_repository_get_c_prefix (GIRepository *repository, |
| const char *namespace_) |
| { |
| GITypelib *typelib; |
| Header *header; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| g_return_val_if_fail (namespace_ != NULL, NULL); |
| |
| typelib = get_registered (repository, namespace_, NULL); |
| |
| g_return_val_if_fail (typelib != NULL, NULL); |
| |
| header = (Header *) typelib->data; |
| if (header->c_prefix) |
| return gi_typelib_get_string (typelib, header->c_prefix); |
| else |
| return NULL; |
| } |
| |
| /** |
| * gi_repository_get_typelib_path: |
| * @repository: A #GIRepository |
| * @namespace_: GI namespace to use, e.g. `Gtk` |
| * |
| * If namespace @namespace_ is loaded, return the full path to the |
| * .typelib file it was loaded from. |
| * |
| * If the typelib for namespace @namespace_ was included in a shared library, |
| * return the special string `<builtin>`. |
| * |
| * Returns: (type filename) (nullable): Filesystem path (or `<builtin>`) if |
| * successful, `NULL` if namespace is not loaded |
| * Since: 2.80 |
| */ |
| const char * |
| gi_repository_get_typelib_path (GIRepository *repository, |
| const char *namespace) |
| { |
| gpointer orig_key, value; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| |
| if (!g_hash_table_lookup_extended (repository->typelibs, namespace, |
| &orig_key, &value)) |
| { |
| if (!g_hash_table_lookup_extended (repository->lazy_typelibs, namespace, |
| &orig_key, &value)) |
| |
| return NULL; |
| } |
| return ((char*)orig_key) + strlen ((char *) orig_key) + 1; |
| } |
| |
| /* This simple search function looks for a specified namespace-version; |
| it's faster than the full directory listing required for latest version. */ |
| static GMappedFile * |
| find_namespace_version (const char *namespace, |
| const char *version, |
| const char * const *search_paths, |
| size_t n_search_paths, |
| char **path_ret) |
| { |
| GError *error = NULL; |
| GMappedFile *mfile = NULL; |
| char *fname; |
| |
| if (g_str_equal (namespace, GIREPOSITORY_TYPELIB_NAME) && |
| !g_str_equal (version, GIREPOSITORY_TYPELIB_VERSION)) |
| { |
| g_debug ("Ignoring %s-%s.typelib because this libgirepository " |
| "corresponds to %s-%s", |
| namespace, version, |
| namespace, GIREPOSITORY_TYPELIB_VERSION); |
| return NULL; |
| } |
| |
| fname = g_strdup_printf ("%s-%s.typelib", namespace, version); |
| |
| for (size_t i = 0; i < n_search_paths; ++i) |
| { |
| char *path = g_build_filename (search_paths[i], fname, NULL); |
| |
| mfile = g_mapped_file_new (path, FALSE, &error); |
| if (error) |
| { |
| g_free (path); |
| g_clear_error (&error); |
| continue; |
| } |
| *path_ret = path; |
| break; |
| } |
| g_free (fname); |
| return mfile; |
| } |
| |
| static gboolean |
| parse_version (const char *version, |
| int *major, |
| int *minor) |
| { |
| const char *dot; |
| char *end; |
| |
| *major = strtol (version, &end, 10); |
| dot = strchr (version, '.'); |
| if (dot == NULL) |
| { |
| *minor = 0; |
| return TRUE; |
| } |
| if (dot != end) |
| return FALSE; |
| *minor = strtol (dot+1, &end, 10); |
| if (end != (version + strlen (version))) |
| return FALSE; |
| return TRUE; |
| } |
| |
| static int |
| compare_version (const char *v1, |
| const char *v2) |
| { |
| gboolean success; |
| int v1_major, v1_minor; |
| int v2_major, v2_minor; |
| |
| success = parse_version (v1, &v1_major, &v1_minor); |
| g_assert (success); |
| |
| success = parse_version (v2, &v2_major, &v2_minor); |
| g_assert (success); |
| |
| /* Avoid a compiler warning about `success` being unused with G_DISABLE_ASSERT */ |
| (void) success; |
| |
| if (v1_major > v2_major) |
| return 1; |
| else if (v2_major > v1_major) |
| return -1; |
| else if (v1_minor > v2_minor) |
| return 1; |
| else if (v2_minor > v1_minor) |
| return -1; |
| return 0; |
| } |
| |
| struct NamespaceVersionCandidadate |
| { |
| GMappedFile *mfile; |
| int path_index; |
| char *path; |
| char *version; |
| }; |
| |
| static int |
| compare_candidate_reverse (struct NamespaceVersionCandidadate *c1, |
| struct NamespaceVersionCandidadate *c2) |
| { |
| int result = compare_version (c1->version, c2->version); |
| /* First, check the version */ |
| if (result > 0) |
| return -1; |
| else if (result < 0) |
| return 1; |
| else |
| { |
| /* Now check the path index, which says how early in the search path |
| * we found it. This ensures that of equal version targets, we |
| * pick the earlier one. |
| */ |
| if (c1->path_index == c2->path_index) |
| return 0; |
| else if (c1->path_index > c2->path_index) |
| return 1; |
| else |
| return -1; |
| } |
| } |
| |
| static void |
| free_candidate (struct NamespaceVersionCandidadate *candidate) |
| { |
| g_mapped_file_unref (candidate->mfile); |
| g_free (candidate->path); |
| g_free (candidate->version); |
| g_slice_free (struct NamespaceVersionCandidadate, candidate); |
| } |
| |
| static GSList * |
| enumerate_namespace_versions (const char *namespace, |
| const char * const *search_paths, |
| size_t n_search_paths) |
| { |
| GSList *candidates = NULL; |
| GHashTable *found_versions = g_hash_table_new (g_str_hash, g_str_equal); |
| char *namespace_dash; |
| char *namespace_typelib; |
| GError *error = NULL; |
| int index; |
| |
| namespace_dash = g_strdup_printf ("%s-", namespace); |
| namespace_typelib = g_strdup_printf ("%s.typelib", namespace); |
| |
| index = 0; |
| for (size_t i = 0; i < n_search_paths; ++i) |
| { |
| GDir *dir; |
| const char *dirname; |
| const char *entry; |
| |
| dirname = search_paths[i]; |
| dir = g_dir_open (dirname, 0, NULL); |
| if (dir == NULL) |
| continue; |
| while ((entry = g_dir_read_name (dir)) != NULL) |
| { |
| GMappedFile *mfile; |
| char *path, *version; |
| struct NamespaceVersionCandidadate *candidate; |
| |
| if (!g_str_has_suffix (entry, ".typelib")) |
| continue; |
| |
| if (g_str_has_prefix (entry, namespace_dash)) |
| { |
| const char *last_dash; |
| const char *name_end; |
| int major, minor; |
| |
| if (g_str_equal (namespace, GIREPOSITORY_TYPELIB_NAME) && |
| !g_str_equal (entry, GIREPOSITORY_TYPELIB_FILENAME)) |
| { |
| g_debug ("Ignoring %s because this libgirepository " |
| "corresponds to %s", |
| entry, GIREPOSITORY_TYPELIB_FILENAME); |
| continue; |
| } |
| |
| name_end = strrchr (entry, '.'); |
| last_dash = strrchr (entry, '-'); |
| version = g_strndup (last_dash+1, name_end-(last_dash+1)); |
| if (!parse_version (version, &major, &minor)) |
| { |
| g_free (version); |
| continue; |
| } |
| } |
| else |
| continue; |
| |
| if (g_hash_table_lookup (found_versions, version) != NULL) |
| { |
| g_free (version); |
| continue; |
| } |
| |
| path = g_build_filename (dirname, entry, NULL); |
| mfile = g_mapped_file_new (path, FALSE, &error); |
| if (mfile == NULL) |
| { |
| g_free (path); |
| g_free (version); |
| g_clear_error (&error); |
| continue; |
| } |
| candidate = g_slice_new0 (struct NamespaceVersionCandidadate); |
| candidate->mfile = mfile; |
| candidate->path_index = index; |
| candidate->path = path; |
| candidate->version = version; |
| candidates = g_slist_prepend (candidates, candidate); |
| g_hash_table_add (found_versions, version); |
| } |
| g_dir_close (dir); |
| index++; |
| } |
| |
| g_free (namespace_dash); |
| g_free (namespace_typelib); |
| g_hash_table_destroy (found_versions); |
| |
| return candidates; |
| } |
| |
| static GMappedFile * |
| find_namespace_latest (const char *namespace, |
| const char * const *search_paths, |
| size_t n_search_paths, |
| char **version_ret, |
| char **path_ret) |
| { |
| GSList *candidates; |
| GMappedFile *result = NULL; |
| |
| *version_ret = NULL; |
| *path_ret = NULL; |
| |
| candidates = enumerate_namespace_versions (namespace, search_paths, n_search_paths); |
| |
| if (candidates != NULL) |
| { |
| struct NamespaceVersionCandidadate *elected; |
| candidates = g_slist_sort (candidates, (GCompareFunc) compare_candidate_reverse); |
| |
| elected = (struct NamespaceVersionCandidadate *) candidates->data; |
| /* Remove the elected one so we don't try to free its contents */ |
| candidates = g_slist_delete_link (candidates, candidates); |
| |
| result = elected->mfile; |
| *path_ret = elected->path; |
| *version_ret = elected->version; |
| g_slice_free (struct NamespaceVersionCandidadate, elected); /* just free the container */ |
| g_slist_foreach (candidates, (GFunc) (void *) free_candidate, NULL); |
| g_slist_free (candidates); |
| } |
| return result; |
| } |
| |
| /** |
| * gi_repository_enumerate_versions: |
| * @repository: A #GIRepository |
| * @namespace_: GI namespace, e.g. `Gtk` |
| * @n_versions_out: (optional) (out): The number of versions returned. |
| * |
| * Obtain an unordered list of versions (either currently loaded or |
| * available) for @namespace_ in this @repository. |
| * |
| * The list is guaranteed to be `NULL` terminated. The `NULL` terminator is not |
| * counted in @n_versions_out. |
| * |
| * Returns: (element-type utf8) (transfer full) (array length=n_versions_out): the array of versions. |
| * Since: 2.80 |
| */ |
| char ** |
| gi_repository_enumerate_versions (GIRepository *repository, |
| const char *namespace_, |
| size_t *n_versions_out) |
| { |
| GPtrArray *versions; |
| GSList *candidates, *link; |
| const char *loaded_version; |
| char **ret; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| |
| candidates = enumerate_namespace_versions (namespace_, |
| (const char * const *) repository->typelib_search_path->pdata, |
| repository->typelib_search_path->len); |
| |
| if (!candidates) |
| { |
| if (n_versions_out) |
| *n_versions_out = 0; |
| return g_strdupv ((char *[]) {NULL}); |
| } |
| |
| versions = g_ptr_array_new_null_terminated (1, g_free, TRUE); |
| for (link = candidates; link; link = link->next) |
| { |
| struct NamespaceVersionCandidadate *candidate = link->data; |
| g_ptr_array_add (versions, g_steal_pointer (&candidate->version)); |
| free_candidate (candidate); |
| } |
| g_slist_free (candidates); |
| |
| /* The currently loaded version of a namespace is also part of the |
| * available versions, as it could have been loaded using |
| * require_private(). |
| */ |
| if (gi_repository_is_registered (repository, namespace_, NULL)) |
| { |
| loaded_version = gi_repository_get_version (repository, namespace_); |
| if (loaded_version && |
| !g_ptr_array_find_with_equal_func (versions, loaded_version, g_str_equal, NULL)) |
| g_ptr_array_add (versions, g_strdup (loaded_version)); |
| } |
| |
| ret = (char **) g_ptr_array_steal (versions, n_versions_out); |
| g_ptr_array_unref (g_steal_pointer (&versions)); |
| |
| return ret; |
| } |
| |
| static GITypelib * |
| require_internal (GIRepository *repository, |
| const char *namespace, |
| const char *version, |
| GIRepositoryLoadFlags flags, |
| const char * const *search_paths, |
| size_t n_search_paths, |
| GError **error) |
| { |
| GMappedFile *mfile; |
| GITypelib *ret = NULL; |
| Header *header; |
| GITypelib *typelib = NULL; |
| GITypelib *typelib_owned = NULL; |
| const char *typelib_namespace, *typelib_version; |
| gboolean allow_lazy = (flags & GI_REPOSITORY_LOAD_FLAG_LAZY) > 0; |
| gboolean is_lazy; |
| char *version_conflict = NULL; |
| char *path = NULL; |
| char *tmp_version = NULL; |
| |
| g_return_val_if_fail (GI_IS_REPOSITORY (repository), NULL); |
| g_return_val_if_fail (namespace != NULL, FALSE); |
| |
| typelib = get_registered_status (repository, namespace, version, allow_lazy, |
| &is_lazy, &version_conflict); |
| if (typelib) |
| return typelib; |
| |
| if (version_conflict != NULL) |
| { |
| g_set_error (error, GI_REPOSITORY_ERROR, |
| GI_REPOSITORY_ERROR_NAMESPACE_VERSION_CONFLICT, |
| "Requiring namespace '%s' version '%s', but '%s' is already loaded", |
| namespace, version, version_conflict); |
| return NULL; |
| } |
| |
| if (version != NULL) |
| { |
| mfile = find_namespace_version (namespace, version, search_paths, |
| n_search_paths, &path); |
| tmp_version = g_strdup (version); |
| } |
| else |
| { |
| mfile = find_namespace_latest (namespace, search_paths, n_search_paths, |
| &tmp_version, &path); |
| } |
| |
| if (mfile == NULL) |
| { |
| if (version != NULL) |
| g_set_error (error, GI_REPOSITORY_ERROR, |
| GI_REPOSITORY_ERROR_TYPELIB_NOT_FOUND, |
| "Typelib file for namespace '%s', version '%s' not found", |
| namespace, version); |
| else |
| g_set_error (error, GI_REPOSITORY_ERROR, |
| GI_REPOSITORY_ERROR_TYPELIB_NOT_FOUND, |
| "Typelib file for namespace '%s' (any version) not found", |
| namespace); |
| goto out; |
| } |
| |
| { |
| GError *temp_error = NULL; |
| GBytes *bytes = NULL; |
| |
| bytes = g_mapped_file_get_bytes (mfile); |
| typelib_owned = typelib = gi_typelib_new_from_bytes (bytes, &temp_error); |
| g_bytes_unref (bytes); |
| g_clear_pointer (&mfile, g_mapped_file_unref); |
| |
| if (!typelib) |
| { |
| g_set_error (error, GI_REPOSITORY_ERROR, |
| GI_REPOSITORY_ERROR_TYPELIB_NOT_FOUND, |
| "Failed to load typelib file '%s' for namespace '%s': %s", |
| path, namespace, temp_error->message); |
| g_clear_error (&temp_error); |
| goto out; |
| } |
| |
| typelib->library_paths = (repository->library_paths != NULL) ? g_ptr_array_ref (repository->library_paths) : NULL; |
| } |
| header = (Header *) typelib->data; |
| typelib_namespace = gi_typelib_get_string (typelib, header->namespace); |
| typelib_version = gi_typelib_get_string (typelib, header->nsversion); |
| |
| if (strcmp (typelib_namespace, namespace) != 0) |
| { |
| g_set_error (error, GI_REPOSITORY_ERROR, |
| GI_REPOSITORY_ERROR_NAMESPACE_MISMATCH, |
| "Typelib file %s for namespace '%s' contains " |
| "namespace '%s' which doesn't match the file name", |
| path, namespace, typelib_namespace); |
| goto out; |
| } |
| if (version != NULL && strcmp (typelib_version, version) != 0) |
| { |
| g_set_error (error, GI_REPOSITORY_ERROR, |
| GI_REPOSITORY_ERROR_NAMESPACE_MISMATCH, |
| "Typelib file %s for namespace '%s' contains " |
| "version '%s' which doesn't match the expected version '%s'", |
| path, namespace, typelib_version, version); |
| goto out; |
| } |
| |
| if (!register_internal (repository, path, allow_lazy, |
| typelib, error)) |
| goto out; |
| ret = typelib; |
| out: |
| g_clear_pointer (&typelib_owned, gi_typelib_unref); |
| g_free (tmp_version); |
| g_free (path); |
| return ret; |
| } |
| |
| /** |
| * gi_repository_require: |
| * @repository: A #GIRepository |
| * @namespace_: GI namespace to use, e.g. `Gtk` |
| * @version: (nullable): Version of namespace, may be `NULL` for latest |
| * @flags: Set of [flags@GIRepository.RepositoryLoadFlags], may be 0 |
| * @error: a [type@GLib.Error]. |
| * |
| * Force the namespace @namespace_ to be loaded if it isn’t already. |
| * |
| * If @namespace_ is not loaded, this function will search for a |
| * `.typelib` file using the repository search path. In addition, a |
| * version @version of namespace may be specified. If @version is |
| * not specified, the latest will be used. |
| * |
| * Returns: (transfer none): a pointer to the [type@GIRepository.Typelib] if |
| * successful, `NULL` otherwise |
| * Since: 2.80 |
| */ |
| GITypelib * |
| gi_repository_require (GIRepository *repository, |
| const char *namespace, |
| const char *version, |
| GIRepositoryLoadFlags flags, |
| GError **error) |
| { |
| GITypelib *typelib; |
| |
| typelib = require_internal (repository, namespace, version, flags, |
| (const char * const *) repository->typelib_search_path->pdata, |
| repository->typelib_search_path->len, error); |
| |
| return typelib; |
| } |
| |
| /** |
| * gi_repository_require_private: |
| * @repository: A #GIRepository |
| * @typelib_dir: (type filename): Private directory where to find the requested |
| * typelib |
| * @namespace_: GI namespace to use, e.g. `Gtk` |
| * @version: (nullable): Version of namespace, may be `NULL` for latest |
| * @flags: Set of [flags@GIRepository.RepositoryLoadFlags], may be 0 |
| * @error: a [type@GLib.Error]. |
| * |
| * Force the namespace @namespace_ to be loaded if it isn’t already. |
| * |
| * If @namespace_ is not loaded, this function will search for a |
| * `.typelib` file within the private directory only. In addition, a |
| * version @version of namespace should be specified. If @version is |
| * not specified, the latest will be used. |
| * |
| * Returns: (transfer none): a pointer to the [type@GIRepository.Typelib] if |
| * successful, `NULL` otherwise |
| * Since: 2.80 |
| */ |
| GITypelib * |
| gi_repository_require_private (GIRepository *repository, |
| const char *typelib_dir, |
| const char *namespace, |
| const char *version, |
| GIRepositoryLoadFlags flags, |
| GError **error) |
| { |
| const char * const search_path[] = { typelib_dir, NULL }; |
| |
| return require_internal (repository, namespace, version, flags, |
| search_path, 1, error); |
| } |
| |
| static gboolean |
| gi_repository_introspect_cb (const char *option_name, |
| const char *value, |
| gpointer data, |
| GError **error) |
| { |
| GError *tmp_error = NULL; |
| char **args; |
| |
| args = g_strsplit (value, ",", 2); |
| |
| if (!gi_repository_dump (args[0], args[1], &tmp_error)) |
| { |
| g_error ("Failed to extract GType data: %s", |
| tmp_error->message); |
| exit (1); |
| } |
| exit (0); |
| } |
| |
| static const GOptionEntry introspection_args[] = { |
| { "introspect-dump", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, |
| gi_repository_introspect_cb, "Dump introspection information", |
| "infile.txt,outfile.xml" }, |
| G_OPTION_ENTRY_NULL |
| }; |
| |
| /** |
| * gi_repository_get_option_group: |
| * |
| * Obtain the option group for girepository. |
| * |
| * It’s used by the dumper and for programs that want to provide introspection |
| * information |
| * |
| * Returns: (transfer full): the option group |
| * Since: 2.80 |
| */ |
| GOptionGroup * |
| gi_repository_get_option_group (void) |
| { |
| GOptionGroup *group; |
| group = g_option_group_new ("girepository", "Introspection Options", "Show Introspection Options", NULL, NULL); |
| |
| g_option_group_add_entries (group, introspection_args); |
| return group; |
| } |
| |
| GQuark |
| gi_repository_error_quark (void) |
| { |
| static GQuark quark = 0; |
| if (quark == 0) |
| quark = g_quark_from_static_string ("g-irepository-error-quark"); |
| return quark; |
| } |
| |
| /** |
| * gi_type_tag_to_string: |
| * @type: the type_tag |
| * |
| * Obtain a string representation of @type |
| * |
| * Returns: the string |
| * Since: 2.80 |
| */ |
| const char * |
| gi_type_tag_to_string (GITypeTag type) |
| { |
| switch (type) |
| { |
| case GI_TYPE_TAG_VOID: |
| return "void"; |
| case GI_TYPE_TAG_BOOLEAN: |
| return "gboolean"; |
| case GI_TYPE_TAG_INT8: |
| return "gint8"; |
| case GI_TYPE_TAG_UINT8: |
| return "guint8"; |
| case GI_TYPE_TAG_INT16: |
| return "gint16"; |
| case GI_TYPE_TAG_UINT16: |
| return "guint16"; |
| case GI_TYPE_TAG_INT32: |
| return "gint32"; |
| case GI_TYPE_TAG_UINT32: |
| return "guint32"; |
| case GI_TYPE_TAG_INT64: |
| return "gint64"; |
| case GI_TYPE_TAG_UINT64: |
| return "guint64"; |
| case GI_TYPE_TAG_FLOAT: |
| return "gfloat"; |
| case GI_TYPE_TAG_DOUBLE: |
| return "gdouble"; |
| case GI_TYPE_TAG_UNICHAR: |
| return "gunichar"; |
| case GI_TYPE_TAG_GTYPE: |
| return "GType"; |
| case GI_TYPE_TAG_UTF8: |
| return "utf8"; |
| case GI_TYPE_TAG_FILENAME: |
| return "filename"; |
| case GI_TYPE_TAG_ARRAY: |
| return "array"; |
| case GI_TYPE_TAG_INTERFACE: |
| return "interface"; |
| case GI_TYPE_TAG_GLIST: |
| return "glist"; |
| case GI_TYPE_TAG_GSLIST: |
| return "gslist"; |
| case GI_TYPE_TAG_GHASH: |
| return "ghash"; |
| case GI_TYPE_TAG_ERROR: |
| return "error"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| /** |
| * gi_info_type_to_string: |
| * @type: the info type |
| * |
| * Obtain a string representation of @type |
| * |
| * Returns: the string |
| * Since: 2.80 |
| */ |
| const char * |
| gi_info_type_to_string (GIInfoType type) |
| { |
| switch (type) |
| { |
| case GI_INFO_TYPE_INVALID: |
| return "invalid"; |
| case GI_INFO_TYPE_FUNCTION: |
| return "function"; |
| case GI_INFO_TYPE_CALLBACK: |
| return "callback"; |
| case GI_INFO_TYPE_STRUCT: |
| return "struct"; |
| case GI_INFO_TYPE_ENUM: |
| return "enum"; |
| case GI_INFO_TYPE_FLAGS: |
| return "flags"; |
| case GI_INFO_TYPE_OBJECT: |
| return "object"; |
| case GI_INFO_TYPE_INTERFACE: |
| return "interface"; |
| case GI_INFO_TYPE_CONSTANT: |
| return "constant"; |
| case GI_INFO_TYPE_UNION: |
| return "union"; |
| case GI_INFO_TYPE_VALUE: |
| return "value"; |
| case GI_INFO_TYPE_SIGNAL: |
| return "signal"; |
| case GI_INFO_TYPE_VFUNC: |
| return "vfunc"; |
| case GI_INFO_TYPE_PROPERTY: |
| return "property"; |
| case GI_INFO_TYPE_FIELD: |
| return "field"; |
| case GI_INFO_TYPE_ARG: |
| return "arg"; |
| case GI_INFO_TYPE_TYPE: |
| return "type"; |
| case GI_INFO_TYPE_UNRESOLVED: |
| return "unresolved"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| GIInfoType |
| gi_typelib_blob_type_to_info_type (GITypelibBlobType blob_type) |
| { |
| switch (blob_type) |
| { |
| case BLOB_TYPE_BOXED: |
| /* `BLOB_TYPE_BOXED` now always refers to a `StructBlob`, and |
| * `GIRegisteredTypeInfo` (the parent type of `GIStructInfo`) has a method |
| * for distinguishing whether the struct is a boxed type. So presenting |
| * `BLOB_TYPE_BOXED` as its own `GIBaseInfo` subclass is not helpful. |
| * See commit e28078c70cbf4a57c7dbd39626f43f9bd2674145 and |
| * https://gitlab.gnome.org/GNOME/glib/-/issues/3245. */ |
| return GI_INFO_TYPE_STRUCT; |
| default: |
| return (GIInfoType) blob_type; |
| } |
| } |