Create gobject-introspection to make native plugins introspectable
There's a lot to this, but the documentation covers the consumer side. From
the GPlugin developer side, we basically created static library that manually
loads plugins and is setup in a way to be called via g-ir-scanner's --program
argument.
Testing Done:
Ran the new unit tests that test gir generation. Also installed to a local prefix and used that to compile a binary against `gplugin-introspection` and verified it displayed the proper output for `--help`.
Reviewed at https://reviews.imfreedom.org/r/2458/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin-introspection/gplugin-introspection.c Tue Jun 13 21:23:49 2023 -0500
@@ -0,0 +1,232 @@
+ * Copyright (C) 2011-2023 Gary Kramlich <grim@reaperworld.com> + * 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, see <https://www.gnu.org/licenses/>. +#include <glib/gi18n-lib.h> +#include <gplugin-native.h> +#include <girepository.h> +#include "gplugin-introspection.h" +/****************************************************************************** + *****************************************************************************/ +gplugin_introspection_get_ir_dump_directory( + GOptionContext *ctx = NULL; + GOptionEntry entries[] = { + {"introspect-dump", 'i', 0, G_OPTION_ARG_STRING, &irdump, NULL, NULL}, + ctx = g_option_context_new(""); + g_option_context_set_summary( + _("Loads plugins so they can be scanned by g-ir-scanner")); + g_option_context_set_translation_domain(ctx, GETTEXT_PACKAGE); + g_option_context_add_main_entries(ctx, entries, GETTEXT_PACKAGE); + g_option_context_parse(ctx, argc, argv, error); + g_clear_pointer(&ctx, g_option_context_free); +gplugin_introspection_query_and_load_plugin( + GPluginPlugin *plugin = NULL; + GError *local_error = NULL; + gboolean loaded = FALSE; + plugin = gplugin_loader_query_plugin(loader, filename, &local_error); + if(local_error != NULL) { + g_clear_object(&plugin); + g_propagate_error(error, local_error); + if(!GPLUGIN_IS_PLUGIN(plugin)) { + "plugin unexpectedly is invalid"); + loaded = gplugin_loader_load_plugin(loader, plugin, &local_error); + if(local_error != NULL) { + g_clear_object(&plugin); + g_propagate_error(error, local_error); + _("Failed to load plugin %s: unknown error"), + g_clear_object(&plugin); +gplugin_introspection_introspect_unload_plugins( + for(guint i = 0; i < g_list_model_get_n_items(plugins); i++) { + GPluginPlugin *plugin = NULL; + plugin = g_list_model_get_item(plugins, i); + gplugin_loader_unload_plugin(loader, plugin, TRUE, NULL); + g_clear_object(&plugin); +/****************************************************************************** + *****************************************************************************/ +gplugin_introspection_introspect_plugin( + return gplugin_introspection_introspect_plugins(argc, argv, filename, NULL); +gplugin_introspection_introspect_plugins(int *argc, char ***argv, ...) + GPluginLoader *loader = NULL; + GListStore *plugins = NULL; + const char *filename = NULL; + irdump = gplugin_introspection_get_ir_dump_directory(argc, argv, &error); + _("Failed to parse command line arguments: %s\n"), + if(irdump == NULL || irdump[0] == '\0') { + g_print(_("The introspect-dump option was not provided. This tool is" + "only meant to be called by g-ir-scanner directly.")); + /* Create an instance of the native loader to load the plugins. */ + loader = g_object_new(GPLUGIN_TYPE_NATIVE_LOADER, NULL); + /* Load our plugins. We store them in a GListStore so we can easily unload + * and unref them later. We can't use GPLUGIN_TYPE_PLUGIN as it's an + * interface, so just force it down to GObject to make it work. + plugins = g_list_store_new(G_TYPE_OBJECT); + while((filename = va_arg(args, const char *)) != NULL) { + GPluginPlugin *plugin = NULL; + plugin = gplugin_introspection_query_and_load_plugin( + _("Failed to query or load plugin %s: %s\n"), + gplugin_introspection_introspect_unload_plugins( + G_LIST_MODEL(plugins)); + g_clear_object(&plugins); + g_clear_object(&plugin); + g_clear_object(&loader); + g_list_store_append(plugins, plugin); + /* Remove our reference to the plugin as we don't need it any more. */ + g_clear_object(&plugin); + /* Now that all of the plugins have been loaded, run the scanner. */ + if(!g_irepository_dump(irdump, &error)) { + g_print("g_irepository_dump() failed: %s\n", error->message); + gplugin_introspection_introspect_unload_plugins( + G_LIST_MODEL(plugins)); + g_clear_object(&plugins); + g_clear_object(&loader); + gplugin_introspection_introspect_unload_plugins( + G_LIST_MODEL(plugins)); + g_clear_object(&plugins); + g_clear_object(&loader); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin-introspection/gplugin-introspection.h Tue Jun 13 21:23:49 2023 -0500
@@ -0,0 +1,34 @@
+ * Copyright (C) 2011-2023 Gary Kramlich <grim@reaperworld.com> + * 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, see <https://www.gnu.org/licenses/>. +#ifndef GPLUGIN_INTROSPECTION_H +#define GPLUGIN_INTROSPECTION_H +int gplugin_introspection_introspect_plugin( +int gplugin_introspection_introspect_plugins(int *argc, char ***argv, ...) + G_GNUC_NULL_TERMINATED; +#endif /* GPLUGIN_INTROSPECTION_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin-introspection/meson.build Tue Jun 13 21:23:49 2023 -0500
@@ -0,0 +1,44 @@
+if not get_option('introspection') + 'gplugin-introspection.c', + 'gplugin-introspection.h', +GOBJECT_INTROSPECTION = dependency('gobject-introspection-1.0', version : '>=1.0.0') +gplugin_introspection_inc = include_directories('.') +gplugin_introspection = static_library( + 'gplugin-introspection', + sources : SOURCES + HEADERS, + dependencies : [gplugin_dep, GIO, GLIB, GOBJECT, GOBJECT_INTROSPECTION], +gplugin_introspection_dep = declare_dependency( + include_directories: [toplevel_inc, gplugin_introspection_inc], + link_with : [gplugin, gplugin_introspection], + dependencies : [GLIB, GOBJECT, GOBJECT_INTROSPECTION], +meson.override_dependency('gplugin-introspection', gplugin_introspection_dep) + name : 'gplugin-introspection', + description : 'A helper library for glugin native plugins and gobject introspection', + filebase : 'gplugin-introspection', + requires : [GLIB, GOBJECT, GMODULE], +install_headers(HEADERS, subdir : 'gplugin-1.0') --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin-introspection/tests/generate.c Tue Jun 13 21:23:49 2023 -0500
@@ -0,0 +1,29 @@
+ * Copyright (C) 2011-2023 Gary Kramlich <grim@reaperworld.com> + * 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, see <https://www.gnu.org/licenses/>. +#include <gplugin-introspection.h> +main(int argc, char *argv[]) + return gplugin_introspection_introspect_plugin( --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin-introspection/tests/introspectablecore.c Tue Jun 13 21:23:49 2023 -0500
@@ -0,0 +1,53 @@
+ * Copyright (C) 2011-2023 Gary Kramlich <grim@reaperworld.com> + * 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, see <https://www.gnu.org/licenses/>. +#include <gplugin-native.h> +#include "introspectabletype.h" +static GPluginPluginInfo * +introspectable_query(G_GNUC_UNUSED GError **error) + return gplugin_plugin_info_new( + "test-gplugin/introspection", + GPLUGIN_NATIVE_PLUGIN_ABI_VERSION, +introspectable_load(GPluginPlugin *plugin, G_GNUC_UNUSED GError **error) + introspectable_type_register(G_TYPE_MODULE(plugin)); + G_GNUC_UNUSED GPluginPlugin *plugin, + G_GNUC_UNUSED gboolean shutdown, + G_GNUC_UNUSED GError **error) +GPLUGIN_NATIVE_PLUGIN_DECLARE(introspectable) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin-introspection/tests/introspectabletype.c Tue Jun 13 21:23:49 2023 -0500
@@ -0,0 +1,113 @@
+ * Copyright (C) 2011-2023 Gary Kramlich <grim@reaperworld.com> + * 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, see <https://www.gnu.org/licenses/>. +#include <gplugin-native.h> +#include "introspectabletype.h" +static GParamSpec *properties[N_PROPERTIES] = { +struct _IntrospectableType { +G_DEFINE_DYNAMIC_TYPE(IntrospectableType, introspectable_type, G_TYPE_OBJECT) +introspectable_type_get_property( + IntrospectableType *type = INTROSPECTABLE_TYPE(obj); + g_value_set_boolean(value, type->dummy); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +introspectable_type_set_property( + IntrospectableType *type = INTROSPECTABLE_TYPE(obj); + type->dummy = g_value_get_boolean(value); + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec); +introspectable_type_init(G_GNUC_UNUSED IntrospectableType *inst) +introspectable_type_class_finalize(G_GNUC_UNUSED IntrospectableTypeClass *klass) +introspectable_type_class_init(IntrospectableTypeClass *klass) + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->get_property = introspectable_type_get_property; + obj_class->set_property = introspectable_type_set_property; + * IntrospectableType:dummy: + * If it's a dummy or not. + properties[PROP_DUMMY] = g_param_spec_boolean( + "Whether or not this object is a dummy", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties(obj_class, N_PROPERTIES, properties); +introspectable_type_register(GTypeModule *module) + introspectable_type_register_type(module); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin-introspection/tests/introspectabletype.h Tue Jun 13 21:23:49 2023 -0500
@@ -0,0 +1,38 @@
+ * Copyright (C) 2011-2023 Gary Kramlich <grim@reaperworld.com> + * 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, see <https://www.gnu.org/licenses/>. +#ifndef INTROSPECTABLE_TYPE_H +#define INTROSPECTABLE_TYPE_H +#include <glib-object.h> +#define INTROSPECTABLE_TYPE_TYPE (introspectable_type_get_type()) +G_GNUC_INTERNAL void introspectable_type_register(GTypeModule *module); +#endif /* INTROSPECTABLE_TYPE_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin-introspection/tests/meson.build Tue Jun 13 21:23:49 2023 -0500
@@ -0,0 +1,34 @@
+ 'introspectablecore.c', + 'introspectabletype.c', + 'introspectabletype.h', +introspectable = shared_module( + dependencies : [gplugin_dep, GLIB, GOBJECT]) +if get_option('introspection') + GIR = dependency('gobject-introspection-1.0', version : '>=1.0.0') + plugin_path = introspectable.full_path() + exe_introspectable = executable('introspectable', + sources : 'generate.c', + dependencies : [gplugin_dep, gplugin_introspection_dep, GLIB, GOBJECT, GIR], + c_args : [f'-DPLUGIN_FILENAME="@plugin_path@"'], + introspectable_gir = gnome.generate_gir( + includes : ['GObject-2.0', gplugin_gir[0]], + namespace : 'Introspectable', + symbol_prefix : 'introspectable', + export_packages : ['introspectable'], + extra_args : ['--quiet']) --- a/gplugin/gplugin-native-plugin.h Thu Mar 23 22:50:50 2023 -0500
+++ b/gplugin/gplugin-native-plugin.h Tue Jun 13 21:23:49 2023 -0500
@@ -1,5 +1,5 @@
- * Copyright (C) 2011-2021 Gary Kramlich <grim@reaperworld.com>
+ * Copyright (C) 2011-2023 Gary Kramlich <grim@reaperworld.com> * This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/gplugin/reference/extendable-plugins.md Tue Jun 13 21:23:49 2023 -0500
@@ -0,0 +1,113 @@
+Title: Extendable Plugins +There are times when you will want to create a plugin that acts as a base for +another plugin. GPlugin accommodates this, but there are a number of things +that you as the author of an extendable plugin will have to do. +> **Note**: This tutorial is meant for authors of native plugins. While it is +> possible to extend non-native plugins, it is outside the scope of this +The first and most important thing that needs to be done for this is to set the +[property@PluginInfo:bind-global] property to %TRUE for the [class@PluginInfo] +that you return from the `query` method of your plugin. +This allows other plugins to be able to see the symbols in your plugin. By +default, the symbols defined in plugins are not bound globally so other plugins +will not see them. This explicitly changes that behavior so that other plugins +can interact with your plugin. +> **Note**: This means all symbols from your plugin that are not static, +> including ones pulled in from libraries, will be visible to all other plugins +> and the application that GPlugin is being used in. +> If you have some symbols you would like to not expose you can prefix them +> with the `G_GNUC_INTERNAL` macro. +Once all of this is done there's nothing else that should need to happen to +your plugin code itself. +### Build System Adjustments +Just like when you build a shared library you will need to install your header +files in a location that dependent plugins are able to find them. This will of +course depend on your build system, but if you're using +[meson](https://mesonbuild.com/) you will want to look at the +[install_headers](https://mesonbuild.com/Reference-manual_functions.html#install_headers) +Likewise you will also want to create a +[pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) `pc` file +so that other plugins can find your header files easily. There's a +[module](https://mesonbuild.com/Pkgconfig-module.html) in meson that you can +> **Note**: You do _NOT_ want to enter anything for the `libraries` field in +> the `pc` file. Doing so will link to the library at linking time instead of +> during runtime which is explicitly what you need. GPlugin will resolve this +> by loading all dependent plugins before loading the plugin that depends on +### GObject Introspection +You may also want to generate +[GObject Introspection](https://gi.readthedocs.io/) repository and type lib +files to allow plugins written in other languages to extend your plugin or to +create documentation with +[gi-docgen](https://gnome.pages.gitlab.gnome.org/gi-docgen/). +The native plugins have to register dynamic types which is incompatible with +how `g-ir-scanner` works when it is creating your gir and type lib files. To +make this work, there is a static library provided with GPlugin named +`gplugin-introspection`. You can use this static library to build an executable +that will be called by `g-ir-scanner` with everything setup for you. You should +be able to find it using pkg-config in your build system. +In most cases you'll just need to load a single plugin, and you can do that via +`gplugin_introspection_introspect_plugin()`. You'll need to pass in your +`argc` and `argv` for argument handling as well as the filename of the plugin +you want to load. In this example we pass `PLUGIN_FILENAME` in from the build +system as that knows the correct filename for the platform that we are building +#include <gplugin-introspection.h> +main(int argc, char *argv[]) { + return gplugin_introspection_introspect_plugin(&argc, &argv, +If for some reason you need to load multiple plugins you can use +`gplugin_introspection_introspect_plugins()` instead. This works the same as +`gplugin_introspection_introspect_plugin()` except it takes multiple plugin +filenames that must be terminated by a %NULL. +#include <gplugin-introspection.h> +main(int argc, char *argv[]) { + return gplugin_introspection_introspect_plugins(&argc, &argv, +Once this is all setup, you'll need to tell `g-ir-scanner` to use this +executable. In meson, this is done by passing the executable target as the +first argument to `gnome.generate_gir`. If you are calling `g-ir-scanner` +directly, you will need to use the `--program` option instead of the \ No newline at end of file
--- a/gplugin/reference/gplugin.toml.in Thu Mar 23 22:50:50 2023 -0500
+++ b/gplugin/reference/gplugin.toml.in Tue Jun 13 21:23:49 2023 -0500
@@ -38,6 +38,7 @@
# The same order will be used when generating the index
+ "extendable-plugins.md", --- a/gplugin/reference/meson.build Thu Mar 23 22:50:50 2023 -0500
+++ b/gplugin/reference/meson.build Tue Jun 13 21:23:49 2023 -0500
@@ -4,6 +4,7 @@
gplugin_doc_content_files = [
+ 'extendable-plugins.md', --- a/meson.build Thu Mar 23 22:50:50 2023 -0500
+++ b/meson.build Tue Jun 13 21:23:49 2023 -0500
@@ -35,6 +35,7 @@
GLIB = dependency('glib-2.0', version : '>=2.70.0')
+GIO = dependency('gio-2.0') GOBJECT = dependency('gobject-2.0')
# we separate gmodule out so our test aren't linked to it
@@ -119,6 +120,7 @@
###############################################################################
+subdir('gplugin-introspection') subdir('gplugin-gtk4-viewer', if_found: GTK4)