/* * peas-plugin-loader-python.c * This file is part of libpeas * * Copyright (C) 2008 - Jesse van den Kieboom * Copyright (C) 2009 - Steve Frécinaux * * libpeas 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.1 of the License, or (at your option) any later version. * * libpeas 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * SPDX-License-Identifier: LGPL-2.1-or-later */ #include "config.h" #include "peas-plugin-loader-python.h" #include "peas-python-internal.h" #include "libpeas/peas-plugin-info-priv.h" /* _POSIX_C_SOURCE is defined in Python.h and in limits.h included by * glib-object.h, so we unset it here to avoid a warning. Yep, that's bad. */ #undef _POSIX_C_SOURCE #include struct _PeasPluginLoaderPython { PeasPluginLoader parent_instance; PyThreadState *py_thread_state; guint n_loaded_plugins; guint init_failed : 1; guint must_finalize_python : 1; }; G_DEFINE_FINAL_TYPE (PeasPluginLoaderPython, peas_plugin_loader_python, PEAS_TYPE_PLUGIN_LOADER) static GQuark quark_extension_type = 0; G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module) { peas_object_module_register_extension_type (module, PEAS_TYPE_PLUGIN_LOADER, PEAS_TYPE_PLUGIN_LOADER_PYTHON); } static GType find_python_extension_type (GType exten_type, PyObject *pymodule) { PyObject *pyexten_type, *pytype; GType the_type = G_TYPE_INVALID; pyexten_type = pyg_type_wrapper_new (exten_type); pytype = peas_python_internal_call ("find_extension_type", &PyType_Type, "(OO)", pyexten_type, pymodule); Py_DECREF (pyexten_type); if (pytype != NULL) { the_type = pyg_type_from_object (pytype); Py_DECREF (pytype); g_return_val_if_fail (g_type_is_a (the_type, exten_type), G_TYPE_INVALID); } return the_type; } static gboolean peas_plugin_loader_python_provides_extension (PeasPluginLoader *loader, PeasPluginInfo *info, GType exten_type) { PyObject *pymodule = info->loader_data; GType the_type; PyGILState_STATE state = PyGILState_Ensure (); the_type = find_python_extension_type (exten_type, pymodule); PyGILState_Release (state); return the_type != G_TYPE_INVALID; } static GObject * peas_plugin_loader_python_create_extension (PeasPluginLoader *loader, PeasPluginInfo *info, GType exten_type, guint n_parameters, GParameter *parameters) { PyObject *pymodule = info->loader_data; GType the_type; GObject *object = NULL; PyObject *pyobject; PyObject *pyplinfo; PyGILState_STATE state = PyGILState_Ensure (); the_type = find_python_extension_type (exten_type, pymodule); if (the_type == G_TYPE_INVALID) goto out; object = g_object_newv (the_type, n_parameters, parameters); if (object == NULL) goto out; /* Sink floating references if necessary */ if (g_object_is_floating (object)) g_object_ref_sink (object); /* We have to remember which interface we are instantiating * for the deprecated peas_extension_get_extension_type(). */ g_object_set_qdata (object, quark_extension_type, GSIZE_TO_POINTER (exten_type)); pyobject = pygobject_new (object); pyplinfo = pyg_boxed_new (PEAS_TYPE_PLUGIN_INFO, info, TRUE, TRUE); /* Set the plugin info as an attribute of the instance */ if (PyObject_SetAttrString (pyobject, "plugin_info", pyplinfo) != 0) { g_warning ("Failed to set 'plugin_info' for '%s'", g_type_name (the_type)); if (PyErr_Occurred ()) PyErr_Print (); g_clear_object (&object); } Py_DECREF (pyplinfo); Py_DECREF (pyobject); out: PyGILState_Release (state); return object; } static gboolean peas_plugin_loader_python_load (PeasPluginLoader *loader, PeasPluginInfo *info) { PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (loader); const gchar *module_dir, *module_name; PyObject *pymodule; PyGILState_STATE state = PyGILState_Ensure (); module_dir = peas_plugin_info_get_module_dir (info); module_name = peas_plugin_info_get_module_name (info); pymodule = peas_python_internal_call ("load", &PyModule_Type, "(sss)", info->filename, module_dir, module_name); if (pymodule != NULL) { info->loader_data = pymodule; pyloader->n_loaded_plugins += 1; } PyGILState_Release (state); return pymodule != NULL; } static void peas_plugin_loader_python_unload (PeasPluginLoader *loader, PeasPluginInfo *info) { PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (loader); PyGILState_STATE state = PyGILState_Ensure (); /* We have to use this as a hook as the * loader will not be finalized by applications */ if (--pyloader->n_loaded_plugins == 0) peas_python_internal_call ("all_plugins_unloaded", NULL, NULL); Py_CLEAR (info->loader_data); PyGILState_Release (state); } static void peas_plugin_loader_python_garbage_collect (PeasPluginLoader *loader) { PyGILState_STATE state = PyGILState_Ensure (); peas_python_internal_call ("garbage_collect", NULL, NULL); PyGILState_Release (state); } static gboolean peas_plugin_loader_python_initialize (PeasPluginLoader *loader) { PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (loader); PyGILState_STATE state = 0; long hexversion; /* We can't support multiple Python interpreter states: * https://bugzilla.gnome.org/show_bug.cgi?id=677091 */ /* Python initialization */ if (Py_IsInitialized ()) { state = PyGILState_Ensure (); } else { Py_InitializeEx (FALSE); pyloader->must_finalize_python = TRUE; } hexversion = PyLong_AsLong (PySys_GetObject ((char *) "hexversion")); #if PY_VERSION_HEX < 0x03000000 if (hexversion >= 0x03000000) #else if (hexversion < 0x03000000) #endif { g_critical ("Attempting to mix incompatible Python versions"); goto python_init_error; } /* Initialize PyGObject */ pygobject_init (PYGOBJECT_MAJOR_VERSION, PYGOBJECT_MINOR_VERSION, PYGOBJECT_MICRO_VERSION); if (PyErr_Occurred ()) { g_warning ("Error initializing Python Plugin Loader: " "PyGObject initialization failed"); goto python_init_error; } /* Initialize support for threads */ pyg_enable_threads (); PyEval_InitThreads (); /* Only redirect warnings when python was not already initialized */ if (!pyloader->must_finalize_python) pyg_disable_warning_redirections (); /* Must be done last, finalize() depends on init_failed */ if (!peas_python_internal_setup (!pyloader->must_finalize_python)) { /* Already warned */ goto python_init_error; } if (!pyloader->must_finalize_python) PyGILState_Release (state); else pyloader->py_thread_state = PyEval_SaveThread (); return TRUE; python_init_error: if (PyErr_Occurred ()) PyErr_Print (); g_warning ("Please check the installation of all the Python " "related packages required by libpeas and try again"); if (!pyloader->must_finalize_python) PyGILState_Release (state); pyloader->init_failed = TRUE; return FALSE; } static void peas_plugin_loader_python_init (PeasPluginLoaderPython *pyloader) { } static void peas_plugin_loader_python_finalize (GObject *object) { PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (object); PyGILState_STATE state; if (!Py_IsInitialized ()) goto out; g_warn_if_fail (pyloader->n_loaded_plugins == 0); if (!pyloader->init_failed) { state = PyGILState_Ensure (); peas_python_internal_shutdown (); PyGILState_Release (state); } if (pyloader->py_thread_state) PyEval_RestoreThread (pyloader->py_thread_state); if (pyloader->must_finalize_python) { if (!pyloader->init_failed) PyGILState_Ensure (); Py_Finalize (); } out: G_OBJECT_CLASS (peas_plugin_loader_python_parent_class)->finalize (object); } static void peas_plugin_loader_python_class_init (PeasPluginLoaderPythonClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PeasPluginLoaderClass *loader_class = PEAS_PLUGIN_LOADER_CLASS (klass); quark_extension_type = g_quark_from_static_string ("peas-extension-type"); object_class->finalize = peas_plugin_loader_python_finalize; loader_class->initialize = peas_plugin_loader_python_initialize; loader_class->load = peas_plugin_loader_python_load; loader_class->unload = peas_plugin_loader_python_unload; loader_class->create_extension = peas_plugin_loader_python_create_extension; loader_class->provides_extension = peas_plugin_loader_python_provides_extension; loader_class->garbage_collect = peas_plugin_loader_python_garbage_collect; }