gplugin/gplugin

Parents 7371e0ba5b32
Children 0bc3ce384653
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.h>
+#include <glib/gi18n-lib.h>
+
+#include <gio/gio.h>
+
+#include <gplugin.h>
+#include <gplugin-native.h>
+
+#include <girepository.h>
+
+#include "gplugin-introspection.h"
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static char *
+gplugin_introspection_get_ir_dump_directory(
+ int *argc,
+ char ***argv,
+ GError **error)
+{
+ GOptionContext *ctx = NULL;
+ char *irdump = NULL;
+ GOptionEntry entries[] = {
+ {"introspect-dump", 'i', 0, G_OPTION_ARG_STRING, &irdump, NULL, NULL},
+ {NULL}};
+
+ ctx = g_option_context_new("");
+ g_option_context_set_summary(
+ ctx,
+ _("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);
+
+ return irdump;
+}
+
+static GPluginPlugin *
+gplugin_introspection_query_and_load_plugin(
+ GPluginLoader *loader,
+ const char *filename,
+ GError **error)
+{
+ 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);
+
+ return NULL;
+ }
+
+ if(!GPLUGIN_IS_PLUGIN(plugin)) {
+ g_set_error_literal(
+ error,
+ GPLUGIN_DOMAIN,
+ 0,
+ "plugin unexpectedly is invalid");
+
+ return NULL;
+ }
+
+ loaded = gplugin_loader_load_plugin(loader, plugin, &local_error);
+ if(local_error != NULL) {
+ g_clear_object(&plugin);
+
+ g_propagate_error(error, local_error);
+
+ return NULL;
+ }
+
+ if(!loaded) {
+ g_set_error(
+ error,
+ GPLUGIN_DOMAIN,
+ 0,
+ _("Failed to load plugin %s: unknown error"),
+ filename);
+
+ g_clear_object(&plugin);
+
+ return NULL;
+ }
+
+ return plugin;
+}
+
+static void
+gplugin_introspection_introspect_unload_plugins(
+ GPluginLoader *loader,
+ GListModel *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);
+ }
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+int
+gplugin_introspection_introspect_plugin(
+ int *argc,
+ char ***argv,
+ const char *filename)
+{
+ return gplugin_introspection_introspect_plugins(argc, argv, filename, NULL);
+}
+
+int
+gplugin_introspection_introspect_plugins(int *argc, char ***argv, ...)
+{
+ GPluginLoader *loader = NULL;
+ GError *error = NULL;
+ GListStore *plugins = NULL;
+ char *irdump = NULL;
+ const char *filename = NULL;
+ va_list args;
+
+ irdump = gplugin_introspection_get_ir_dump_directory(argc, argv, &error);
+ if(error != NULL) {
+ g_print(
+ _("Failed to parse command line arguments: %s\n"),
+ error->message);
+ g_free(irdump);
+
+ return 1;
+ }
+
+ 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."));
+
+ return 1;
+ }
+
+ /* 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);
+
+ va_start(args, argv);
+ while((filename = va_arg(args, const char *)) != NULL) {
+ GPluginPlugin *plugin = NULL;
+
+ plugin = gplugin_introspection_query_and_load_plugin(
+ loader,
+ filename,
+ &error);
+ if(error != NULL) {
+ g_print(
+ _("Failed to query or load plugin %s: %s\n"),
+ filename,
+ error->message);
+
+ gplugin_introspection_introspect_unload_plugins(
+ loader,
+ G_LIST_MODEL(plugins));
+ g_clear_object(&plugins);
+
+ g_free(irdump);
+ g_clear_object(&plugin);
+ g_clear_object(&loader);
+
+ return 1;
+ }
+
+ g_list_store_append(plugins, plugin);
+
+ /* Remove our reference to the plugin as we don't need it any more. */
+ g_clear_object(&plugin);
+ }
+ va_end(args);
+
+ /* 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);
+
+ g_free(irdump);
+ gplugin_introspection_introspect_unload_plugins(
+ loader,
+ G_LIST_MODEL(plugins));
+ g_clear_object(&plugins);
+
+ g_clear_object(&loader);
+
+ return 1;
+ }
+
+ gplugin_introspection_introspect_unload_plugins(
+ loader,
+ G_LIST_MODEL(plugins));
+ g_clear_object(&plugins);
+ g_clear_object(&loader);
+ g_free(irdump);
+
+ return 0;
+}
--- /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
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+int gplugin_introspection_introspect_plugin(
+ int *argc,
+ char ***argv,
+ const char *filename);
+int gplugin_introspection_introspect_plugins(int *argc, char ***argv, ...)
+ G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#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')
+ subdir_done()
+endif
+
+SOURCES = [
+ 'gplugin-introspection.c',
+]
+
+HEADERS = [
+ '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],
+ install : true,
+)
+
+gplugin_introspection_dep = declare_dependency(
+ include_directories: [toplevel_inc, gplugin_introspection_inc],
+ link_with : [gplugin, gplugin_introspection],
+ sources : SOURCES,
+ dependencies : [GLIB, GOBJECT, GOBJECT_INTROSPECTION],
+)
+
+meson.override_dependency('gplugin-introspection', gplugin_introspection_dep)
+
+pkgconfig.generate(
+ gplugin_introspection,
+ name : 'gplugin-introspection',
+ description : 'A helper library for glugin native plugins and gobject introspection',
+ filebase : 'gplugin-introspection',
+ libraries : [gplugin],
+ requires : [GLIB, GOBJECT, GMODULE],
+)
+
+install_headers(HEADERS, subdir : 'gplugin-1.0')
+
+subdir('tests')
--- /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 <glib.h>
+
+#include <gplugin-introspection.h>
+
+int
+main(int argc, char *argv[])
+{
+ return gplugin_introspection_introspect_plugin(
+ &argc,
+ &argv,
+ PLUGIN_FILENAME);
+}
--- /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 <glib.h>
+
+#include <gplugin.h>
+#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,
+ "bind-global",
+ TRUE,
+ NULL);
+}
+
+static gboolean
+introspectable_load(GPluginPlugin *plugin, G_GNUC_UNUSED GError **error)
+{
+ introspectable_type_register(G_TYPE_MODULE(plugin));
+
+ return TRUE;
+}
+
+static gboolean
+introspectable_unload(
+ G_GNUC_UNUSED GPluginPlugin *plugin,
+ G_GNUC_UNUSED gboolean shutdown,
+ G_GNUC_UNUSED GError **error)
+{
+ return TRUE;
+}
+
+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.h>
+#include <gplugin-native.h>
+
+#include "introspectabletype.h"
+
+enum {
+ PROP_0,
+ PROP_DUMMY,
+ N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {
+ NULL,
+};
+
+struct _IntrospectableType {
+ GObject gparent;
+
+ gboolean dummy;
+};
+
+G_DEFINE_DYNAMIC_TYPE(IntrospectableType, introspectable_type, G_TYPE_OBJECT)
+
+static void
+introspectable_type_get_property(
+ GObject *obj,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IntrospectableType *type = INTROSPECTABLE_TYPE(obj);
+
+ switch(param_id) {
+ case PROP_DUMMY:
+ g_value_set_boolean(value, type->dummy);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ }
+}
+
+static void
+introspectable_type_set_property(
+ GObject *obj,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IntrospectableType *type = INTROSPECTABLE_TYPE(obj);
+
+ switch(param_id) {
+ case PROP_DUMMY:
+ type->dummy = g_value_get_boolean(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ }
+}
+
+static void
+introspectable_type_init(G_GNUC_UNUSED IntrospectableType *inst)
+{
+}
+
+static void
+introspectable_type_class_finalize(G_GNUC_UNUSED IntrospectableTypeClass *klass)
+{
+}
+
+static void
+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(
+ "dummy",
+ "dummy",
+ "Whether or not this object is a dummy",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+void
+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.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define INTROSPECTABLE_TYPE_TYPE (introspectable_type_get_type())
+G_DECLARE_FINAL_TYPE(
+ IntrospectableType,
+ introspectable_type,
+ INTROSPECTABLE,
+ TYPE,
+ GObject)
+
+G_GNUC_INTERNAL void introspectable_type_register(GTypeModule *module);
+
+G_END_DECLS
+
+#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 @@
+SOURCES = [
+ 'introspectablecore.c',
+ 'introspectabletype.c',
+ 'introspectabletype.h',
+]
+
+introspectable = shared_module(
+ 'introspectable',
+ SOURCES,
+ name_prefix : '',
+ 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@"'],
+ install : false)
+
+ introspectable_gir = gnome.generate_gir(
+ exe_introspectable,
+ sources : SOURCES,
+ includes : ['GObject-2.0', gplugin_gir[0]],
+ namespace : 'Introspectable',
+ symbol_prefix : 'introspectable',
+ nsversion : '1.0',
+ install : false,
+ export_packages : ['introspectable'],
+ extra_args : ['--quiet'])
+endif
--- 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
+Slug: extendability
+
+## 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
+> document.
+
+### Plugin Adjustments
+
+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)
+function.
+
+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
+use for this as well.
+
+> **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
+> them.
+
+### 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
+for.
+
+```c
+#include <glib.h>
+
+#include <gplugin-introspection.h>
+
+int
+main(int argc, char *argv[]) {
+ return gplugin_introspection_introspect_plugin(&argc, &argv,
+ PLUGIN_FILENAME);
+}
+```
+
+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.
+
+```c
+#include <glib.h>
+
+#include <gplugin-introspection.h>
+
+int
+main(int argc, char *argv[]) {
+ return gplugin_introspection_introspect_plugins(&argc, &argv,
+ PLUGIN1_FILENAME,
+ PLUGIN2_FILENAME,
+ NULL);
+}
+```
+
+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
+`--library` option.
\ 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
content_files = [
"embedding.md",
+ "extendable-plugins.md",
"genie.md",
"lua.md",
"native-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 = [
'embedding.md',
+ 'extendable-plugins.md',
'genie.md',
'lua.md',
'native-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 @@
endif
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 @@
# Subdirectories
###############################################################################
subdir('gplugin')
+subdir('gplugin-introspection')
subdir('gplugin-gtk4')
subdir('gplugin-gtk4-viewer', if_found: GTK4)
subdir('gplugin-query')