xed/plugin-loaders/python/xedit-plugin-loader-python.c

720 lines
18 KiB
C

/*
* xedit-plugin-loader-python.c
* This file is part of xedit
*
* Copyright (C) 2008 - Jesse van den Kieboom
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "xedit-plugin-loader-python.h"
#include "xedit-plugin-python.h"
#include <xedit/xedit-object-module.h>
#define NO_IMPORT_PYGOBJECT
#define NO_IMPORT_PYGTK
#include <Python.h>
#include <pygobject.h>
#include <pygtk/pygtk.h>
#include <signal.h>
#include "config.h"
#if PY_VERSION_HEX < 0x02050000
typedef int Py_ssize_t;
#define PY_SSIZE_T_MAX INT_MAX
#define PY_SSIZE_T_MIN INT_MIN
#endif
#define XEDIT_PLUGIN_LOADER_PYTHON_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), XEDIT_TYPE_PLUGIN_LOADER_PYTHON, XeditPluginLoaderPythonPrivate))
struct _XeditPluginLoaderPythonPrivate
{
GHashTable *loaded_plugins;
guint idle_gc;
gboolean init_failed;
};
typedef struct
{
PyObject *type;
PyObject *instance;
gchar *path;
} PythonInfo;
static void xedit_plugin_loader_iface_init (gpointer g_iface, gpointer iface_data);
/* Exported by pyxedit module */
void pyxedit_register_classes (PyObject *d);
void pyxedit_add_constants (PyObject *module, const gchar *strip_prefix);
extern PyMethodDef pyxedit_functions[];
/* Exported by pyxeditutils module */
void pyxeditutils_register_classes (PyObject *d);
extern PyMethodDef pyxeditutils_functions[];
/* Exported by pyxeditcommands module */
void pyxeditcommands_register_classes (PyObject *d);
extern PyMethodDef pyxeditcommands_functions[];
/* We retreive this to check for correct class hierarchy */
static PyTypeObject *PyXeditPlugin_Type;
XEDIT_PLUGIN_LOADER_REGISTER_TYPE (XeditPluginLoaderPython, xedit_plugin_loader_python, G_TYPE_OBJECT, xedit_plugin_loader_iface_init);
static PyObject *
find_python_plugin_type (XeditPluginInfo *info,
PyObject *pymodule)
{
PyObject *locals, *key, *value;
Py_ssize_t pos = 0;
locals = PyModule_GetDict (pymodule);
while (PyDict_Next (locals, &pos, &key, &value))
{
if (!PyType_Check(value))
continue;
if (PyObject_IsSubclass (value, (PyObject*) PyXeditPlugin_Type))
return value;
}
g_warning ("No XeditPlugin derivative found in Python plugin '%s'",
xedit_plugin_info_get_name (info));
return NULL;
}
static XeditPlugin *
new_plugin_from_info (XeditPluginLoaderPython *loader,
XeditPluginInfo *info)
{
PythonInfo *pyinfo;
PyTypeObject *pytype;
PyObject *pyobject;
PyGObject *pygobject;
XeditPlugin *instance;
PyObject *emptyarg;
pyinfo = (PythonInfo *)g_hash_table_lookup (loader->priv->loaded_plugins, info);
if (pyinfo == NULL)
return NULL;
pytype = (PyTypeObject *)pyinfo->type;
if (pytype->tp_new == NULL)
return NULL;
emptyarg = PyTuple_New(0);
pyobject = pytype->tp_new (pytype, emptyarg, NULL);
Py_DECREF (emptyarg);
if (pyobject == NULL)
{
g_error ("Could not create instance for %s.", xedit_plugin_info_get_name (info));
return NULL;
}
pygobject = (PyGObject *)pyobject;
if (pygobject->obj != NULL)
{
Py_DECREF (pyobject);
g_error ("Could not create instance for %s (GObject already initialized).", xedit_plugin_info_get_name (info));
return NULL;
}
pygobject_construct (pygobject,
"install-dir", pyinfo->path,
"data-dir-name", xedit_plugin_info_get_module_name (info),
NULL);
if (pygobject->obj == NULL)
{
g_error ("Could not create instance for %s (GObject not constructed).", xedit_plugin_info_get_name (info));
Py_DECREF (pyobject);
return NULL;
}
/* now call tp_init manually */
if (PyType_IsSubtype (pyobject->ob_type, pytype) &&
pyobject->ob_type->tp_init != NULL)
{
emptyarg = PyTuple_New(0);
pyobject->ob_type->tp_init (pyobject, emptyarg, NULL);
Py_DECREF (emptyarg);
}
instance = XEDIT_PLUGIN (pygobject->obj);
pyinfo->instance = (PyObject *)pygobject;
/* make sure to register the python instance for the XeditPluginPython
object to it can wrap the virtual xedit plugin funcs back to python */
_xedit_plugin_python_set_instance (XEDIT_PLUGIN_PYTHON (instance), (PyObject *)pygobject);
/* we return a reference here because the other is owned by python */
return XEDIT_PLUGIN (g_object_ref (instance));
}
static XeditPlugin *
add_python_info (XeditPluginLoaderPython *loader,
XeditPluginInfo *info,
PyObject *module,
const gchar *path,
PyObject *type)
{
PythonInfo *pyinfo;
pyinfo = g_new (PythonInfo, 1);
pyinfo->path = g_strdup (path);
pyinfo->type = type;
Py_INCREF (pyinfo->type);
g_hash_table_insert (loader->priv->loaded_plugins, info, pyinfo);
return new_plugin_from_info (loader, info);
}
static const gchar *
xedit_plugin_loader_iface_get_id (void)
{
return "Python";
}
static XeditPlugin *
xedit_plugin_loader_iface_load (XeditPluginLoader *loader,
XeditPluginInfo *info,
const gchar *path)
{
XeditPluginLoaderPython *pyloader = XEDIT_PLUGIN_LOADER_PYTHON (loader);
PyObject *main_module, *main_locals, *pytype;
PyObject *pymodule, *fromlist;
gchar *module_name;
XeditPlugin *result;
if (pyloader->priv->init_failed)
{
g_warning ("Cannot load python plugin Python '%s' since xedit was"
"not able to initialize the Python interpreter.",
xedit_plugin_info_get_name (info));
return NULL;
}
/* see if py definition for the plugin is already loaded */
result = new_plugin_from_info (pyloader, info);
if (result != NULL)
return result;
main_module = PyImport_AddModule ("xedit.plugins");
if (main_module == NULL)
{
g_warning ("Could not get xedit.plugins.");
return NULL;
}
/* If we have a special path, we register it */
if (path != NULL)
{
PyObject *sys_path = PySys_GetObject ("path");
PyObject *pypath = PyString_FromString (path);
if (PySequence_Contains (sys_path, pypath) == 0)
PyList_Insert (sys_path, 0, pypath);
Py_DECREF (pypath);
}
main_locals = PyModule_GetDict (main_module);
/* we need a fromlist to be able to import modules with a '.' in the
name. */
fromlist = PyTuple_New(0);
module_name = g_strdup (xedit_plugin_info_get_module_name (info));
pymodule = PyImport_ImportModuleEx (module_name,
main_locals,
main_locals,
fromlist);
Py_DECREF(fromlist);
if (!pymodule)
{
g_free (module_name);
PyErr_Print ();
return NULL;
}
PyDict_SetItemString (main_locals, module_name, pymodule);
g_free (module_name);
pytype = find_python_plugin_type (info, pymodule);
if (pytype)
return add_python_info (pyloader, info, pymodule, path, pytype);
return NULL;
}
static void
xedit_plugin_loader_iface_unload (XeditPluginLoader *loader,
XeditPluginInfo *info)
{
XeditPluginLoaderPython *pyloader = XEDIT_PLUGIN_LOADER_PYTHON (loader);
PythonInfo *pyinfo;
PyGILState_STATE state;
pyinfo = (PythonInfo *)g_hash_table_lookup (pyloader->priv->loaded_plugins, info);
if (!pyinfo)
return;
state = pyg_gil_state_ensure ();
Py_XDECREF (pyinfo->instance);
pyg_gil_state_release (state);
pyinfo->instance = NULL;
}
static gboolean
run_gc (XeditPluginLoaderPython *loader)
{
while (PyGC_Collect ())
;
loader->priv->idle_gc = 0;
return FALSE;
}
static void
xedit_plugin_loader_iface_garbage_collect (XeditPluginLoader *loader)
{
XeditPluginLoaderPython *pyloader;
if (!Py_IsInitialized())
return;
pyloader = XEDIT_PLUGIN_LOADER_PYTHON (loader);
/*
* We both run the GC right now and we schedule
* a further collection in the main loop.
*/
while (PyGC_Collect ())
;
if (pyloader->priv->idle_gc == 0)
pyloader->priv->idle_gc = g_idle_add ((GSourceFunc)run_gc, pyloader);
}
static void
xedit_plugin_loader_iface_init (gpointer g_iface,
gpointer iface_data)
{
XeditPluginLoaderInterface *iface = (XeditPluginLoaderInterface *)g_iface;
iface->get_id = xedit_plugin_loader_iface_get_id;
iface->load = xedit_plugin_loader_iface_load;
iface->unload = xedit_plugin_loader_iface_unload;
iface->garbage_collect = xedit_plugin_loader_iface_garbage_collect;
}
static void
xedit_python_shutdown (XeditPluginLoaderPython *loader)
{
if (!Py_IsInitialized ())
return;
if (loader->priv->idle_gc != 0)
{
g_source_remove (loader->priv->idle_gc);
loader->priv->idle_gc = 0;
}
while (PyGC_Collect ())
;
Py_Finalize ();
}
/* C equivalent of
* import pygtk
* pygtk.require ("2.0")
*/
static gboolean
xedit_check_pygtk2 (void)
{
PyObject *pygtk, *mdict, *require;
/* pygtk.require("2.0") */
pygtk = PyImport_ImportModule ("pygtk");
if (pygtk == NULL)
{
g_warning ("Error initializing Python interpreter: could not import pygtk.");
return FALSE;
}
mdict = PyModule_GetDict (pygtk);
require = PyDict_GetItemString (mdict, "require");
PyObject_CallObject (require,
Py_BuildValue ("(S)", PyString_FromString ("2.0")));
if (PyErr_Occurred())
{
g_warning ("Error initializing Python interpreter: pygtk 2 is required.");
return FALSE;
}
return TRUE;
}
/* Note: the following two functions are needed because
* init_pyobject and init_pygtk which are *macros* which in case
* case of error set the PyErr and then make the calling
* function return behind our back.
* It's up to the caller to check the result with PyErr_Occurred()
*/
static void
xedit_init_pygobject (void)
{
init_pygobject_check (2, 11, 5); /* FIXME: get from config */
}
static void
xedit_init_pygtk (void)
{
PyObject *gtk, *mdict, *version, *required_version;
init_pygtk ();
/* there isn't init_pygtk_check(), do the version
* check ourselves */
gtk = PyImport_ImportModule("gtk");
mdict = PyModule_GetDict(gtk);
version = PyDict_GetItemString (mdict, "pygtk_version");
if (!version)
{
PyErr_SetString (PyExc_ImportError,
"PyGObject version too old");
return;
}
required_version = Py_BuildValue ("(iii)", 2, 4, 0); /* FIXME */
if (PyObject_Compare (version, required_version) == -1)
{
PyErr_SetString (PyExc_ImportError,
"PyGObject version too old");
Py_DECREF (required_version);
return;
}
Py_DECREF (required_version);
}
static void
old_gtksourceview_init (void)
{
PyErr_SetString(PyExc_ImportError,
"gtksourceview module not allowed, use gtksourceview2");
}
static void
xedit_init_pygtksourceview (void)
{
PyObject *gtksourceview, *mdict, *version, *required_version;
gtksourceview = PyImport_ImportModule("gtksourceview2");
if (gtksourceview == NULL)
{
PyErr_SetString (PyExc_ImportError,
"could not import gtksourceview");
return;
}
mdict = PyModule_GetDict (gtksourceview);
version = PyDict_GetItemString (mdict, "pygtksourceview2_version");
if (!version)
{
PyErr_SetString (PyExc_ImportError,
"PyGtkSourceView version too old");
return;
}
required_version = Py_BuildValue ("(iii)", 0, 8, 0); /* FIXME */
if (PyObject_Compare (version, required_version) == -1)
{
PyErr_SetString (PyExc_ImportError,
"PyGtkSourceView version too old");
Py_DECREF (required_version);
return;
}
Py_DECREF (required_version);
/* Create a dummy 'gtksourceview' module to prevent
* loading of the old 'gtksourceview' modules that
* has conflicting symbols with the gtksourceview2 module.
* Raise an exception when trying to import it.
*/
PyImport_AppendInittab ("gtksourceview", old_gtksourceview_init);
}
static gboolean
xedit_python_init (XeditPluginLoaderPython *loader)
{
PyObject *mdict, *tuple;
PyObject *xedit, *xeditutils, *xeditcommands, *xeditplugins;
PyObject *gettext, *install, *gettext_args;
//char *argv[] = { "xedit", NULL };
char *argv[] = { XEDIT_PLUGINS_LIBS_DIR, NULL };
#ifdef HAVE_SIGACTION
gint res;
struct sigaction old_sigint;
#endif
if (loader->priv->init_failed)
{
/* We already failed to initialized Python, don't need to
* retry again */
return FALSE;
}
if (Py_IsInitialized ())
{
/* Python has already been successfully initialized */
return TRUE;
}
/* We are trying to initialize Python for the first time,
set init_failed to FALSE only if the entire initialization process
ends with success */
loader->priv->init_failed = TRUE;
/* Hack to make python not overwrite SIGINT: this is needed to avoid
* the crash reported on bug #326191 */
/* CHECK: can't we use Py_InitializeEx instead of Py_Initialize in order
to avoid to manage signal handlers ? - Paolo (Dec. 31, 2006) */
#ifdef HAVE_SIGACTION
/* Save old handler */
res = sigaction (SIGINT, NULL, &old_sigint);
if (res != 0)
{
g_warning ("Error initializing Python interpreter: cannot get "
"handler to SIGINT signal (%s)",
g_strerror (errno));
return FALSE;
}
#endif
/* Python initialization */
Py_Initialize ();
#ifdef HAVE_SIGACTION
/* Restore old handler */
res = sigaction (SIGINT, &old_sigint, NULL);
if (res != 0)
{
g_warning ("Error initializing Python interpreter: cannot restore "
"handler to SIGINT signal (%s).",
g_strerror (errno));
goto python_init_error;
}
#endif
PySys_SetArgv (1, argv);
if (!xedit_check_pygtk2 ())
{
/* Warning message already printed in check_pygtk2 */
goto python_init_error;
}
/* import gobject */
xedit_init_pygobject ();
if (PyErr_Occurred ())
{
g_warning ("Error initializing Python interpreter: could not import pygobject.");
goto python_init_error;
}
/* import gtk */
xedit_init_pygtk ();
if (PyErr_Occurred ())
{
g_warning ("Error initializing Python interpreter: could not import pygtk.");
goto python_init_error;
}
/* import gtksourceview */
xedit_init_pygtksourceview ();
if (PyErr_Occurred ())
{
PyErr_Print ();
g_warning ("Error initializing Python interpreter: could not import pygtksourceview.");
goto python_init_error;
}
/* import xedit */
xedit = Py_InitModule ("xedit", pyxedit_functions);
mdict = PyModule_GetDict (xedit);
pyxedit_register_classes (mdict);
pyxedit_add_constants (xedit, "XEDIT_");
/* xedit version */
tuple = Py_BuildValue("(iii)",
XEDIT_MAJOR_VERSION,
XEDIT_MINOR_VERSION,
XEDIT_MICRO_VERSION);
PyDict_SetItemString(mdict, "version", tuple);
Py_DECREF(tuple);
/* Retrieve the Python type for xedit.Plugin */
PyXeditPlugin_Type = (PyTypeObject *) PyDict_GetItemString (mdict, "Plugin");
if (PyXeditPlugin_Type == NULL)
{
PyErr_Print ();
goto python_init_error;
}
/* import xedit.utils */
xeditutils = Py_InitModule ("xedit.utils", pyxeditutils_functions);
PyDict_SetItemString (mdict, "utils", xeditutils);
/* import xedit.commands */
xeditcommands = Py_InitModule ("xedit.commands", pyxeditcommands_functions);
PyDict_SetItemString (mdict, "commands", xeditcommands);
/* initialize empty xedit.plugins module */
xeditplugins = Py_InitModule ("xedit.plugins", NULL);
PyDict_SetItemString (mdict, "plugins", xeditplugins);
mdict = PyModule_GetDict (xeditutils);
pyxeditutils_register_classes (mdict);
mdict = PyModule_GetDict (xeditcommands);
pyxeditcommands_register_classes (mdict);
/* i18n support */
gettext = PyImport_ImportModule ("gettext");
if (gettext == NULL)
{
g_warning ("Error initializing Python interpreter: could not import gettext.");
goto python_init_error;
}
mdict = PyModule_GetDict (gettext);
install = PyDict_GetItemString (mdict, "install");
gettext_args = Py_BuildValue ("ss", GETTEXT_PACKAGE, XEDIT_LOCALEDIR);
PyObject_CallObject (install, gettext_args);
Py_DECREF (gettext_args);
/* Python has been successfully initialized */
loader->priv->init_failed = FALSE;
return TRUE;
python_init_error:
g_warning ("Please check the installation of all the Python related packages required "
"by xedit and try again.");
PyErr_Clear ();
xedit_python_shutdown (loader);
return FALSE;
}
static void
xedit_plugin_loader_python_finalize (GObject *object)
{
XeditPluginLoaderPython *pyloader = XEDIT_PLUGIN_LOADER_PYTHON (object);
g_hash_table_destroy (pyloader->priv->loaded_plugins);
xedit_python_shutdown (pyloader);
G_OBJECT_CLASS (xedit_plugin_loader_python_parent_class)->finalize (object);
}
static void
xedit_plugin_loader_python_class_init (XeditPluginLoaderPythonClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = xedit_plugin_loader_python_finalize;
g_type_class_add_private (object_class, sizeof (XeditPluginLoaderPythonPrivate));
}
static void
xedit_plugin_loader_python_class_finalize (XeditPluginLoaderPythonClass *klass)
{
}
static void
destroy_python_info (PythonInfo *info)
{
PyGILState_STATE state = pyg_gil_state_ensure ();
Py_XDECREF (info->type);
pyg_gil_state_release (state);
g_free (info->path);
g_free (info);
}
static void
xedit_plugin_loader_python_init (XeditPluginLoaderPython *self)
{
self->priv = XEDIT_PLUGIN_LOADER_PYTHON_GET_PRIVATE (self);
/* initialize python interpreter */
xedit_python_init (self);
/* loaded_plugins maps XeditPluginInfo to a PythonInfo */
self->priv->loaded_plugins = g_hash_table_new_full (g_direct_hash,
g_direct_equal,
NULL,
(GDestroyNotify)destroy_python_info);
}
XeditPluginLoaderPython *
xedit_plugin_loader_python_new ()
{
GObject *loader = g_object_new (XEDIT_TYPE_PLUGIN_LOADER_PYTHON, NULL);
return XEDIT_PLUGIN_LOADER_PYTHON (loader);
}