1 /*
2  * peas-plugin-loader-python.c
3  * This file is part of libpeas
4  *
5  * Copyright (C) 2008 - Jesse van den Kieboom
6  * Copyright (C) 2009 - Steve Frécinaux
7  *
8  * libpeas is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * libpeas is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include "peas-plugin-loader-python.h"
28 #include "peas-python-internal.h"
29 #include "libpeas/peas-plugin-info-priv.h"
30 
31 /* _POSIX_C_SOURCE is defined in Python.h and in limits.h included by
32  * glib-object.h, so we unset it here to avoid a warning. Yep, that's bad.
33  */
34 #undef _POSIX_C_SOURCE
35 #include <pygobject.h>
36 
37 typedef struct {
38   PyThreadState *py_thread_state;
39 
40   guint n_loaded_plugins;
41 
42   guint init_failed : 1;
43   guint must_finalize_python : 1;
44 } PeasPluginLoaderPythonPrivate;
45 
46 G_DEFINE_TYPE_WITH_PRIVATE (PeasPluginLoaderPython,
47                             peas_plugin_loader_python,
48                             PEAS_TYPE_PLUGIN_LOADER)
49 
50 #define GET_PRIV(o) \
51   (peas_plugin_loader_python_get_instance_private (o))
52 
53 static GQuark quark_extension_type = 0;
54 
55 G_MODULE_EXPORT void
peas_register_types(PeasObjectModule * module)56 peas_register_types (PeasObjectModule *module)
57 {
58   peas_object_module_register_extension_type (module,
59                                               PEAS_TYPE_PLUGIN_LOADER,
60                                               PEAS_TYPE_PLUGIN_LOADER_PYTHON);
61 }
62 
63 static GType
find_python_extension_type(GType exten_type,PyObject * pymodule)64 find_python_extension_type (GType     exten_type,
65                             PyObject *pymodule)
66 {
67   PyObject *pyexten_type, *pytype;
68   GType the_type = G_TYPE_INVALID;
69 
70   pyexten_type = pyg_type_wrapper_new (exten_type);
71 
72   pytype = peas_python_internal_call ("find_extension_type",
73                                       &PyType_Type, "(OO)",
74                                       pyexten_type, pymodule);
75   Py_DECREF (pyexten_type);
76 
77   if (pytype != NULL)
78     {
79       the_type = pyg_type_from_object (pytype);
80       Py_DECREF (pytype);
81 
82       g_return_val_if_fail (g_type_is_a (the_type, exten_type),
83                             G_TYPE_INVALID);
84     }
85 
86   return the_type;
87 }
88 
89 static gboolean
peas_plugin_loader_python_provides_extension(PeasPluginLoader * loader,PeasPluginInfo * info,GType exten_type)90 peas_plugin_loader_python_provides_extension (PeasPluginLoader *loader,
91                                               PeasPluginInfo   *info,
92                                               GType             exten_type)
93 {
94   PyObject *pymodule = info->loader_data;
95   GType the_type;
96   PyGILState_STATE state = PyGILState_Ensure ();
97 
98   the_type = find_python_extension_type (exten_type, pymodule);
99 
100   PyGILState_Release (state);
101   return the_type != G_TYPE_INVALID;
102 }
103 
104 static PeasExtension *
peas_plugin_loader_python_create_extension(PeasPluginLoader * loader,PeasPluginInfo * info,GType exten_type,guint n_parameters,GParameter * parameters)105 peas_plugin_loader_python_create_extension (PeasPluginLoader *loader,
106                                             PeasPluginInfo   *info,
107                                             GType             exten_type,
108                                             guint             n_parameters,
109                                             GParameter       *parameters)
110 {
111   PyObject *pymodule = info->loader_data;
112   GType the_type;
113   GObject *object = NULL;
114   PyObject *pyobject;
115   PyObject *pyplinfo;
116   PyGILState_STATE state = PyGILState_Ensure ();
117 
118   the_type = find_python_extension_type (exten_type, pymodule);
119   if (the_type == G_TYPE_INVALID)
120     goto out;
121 
122   object = g_object_newv (the_type, n_parameters, parameters);
123   if (object == NULL)
124     goto out;
125 
126   /* Sink floating references if necessary */
127   if (g_object_is_floating (object))
128     g_object_ref_sink (object);
129 
130   /* We have to remember which interface we are instantiating
131    * for the deprecated peas_extension_get_extension_type().
132    */
133   g_object_set_qdata (object, quark_extension_type,
134                       GSIZE_TO_POINTER (exten_type));
135 
136   pyobject = pygobject_new (object);
137   pyplinfo = pyg_boxed_new (PEAS_TYPE_PLUGIN_INFO, info, TRUE, TRUE);
138 
139   /* Set the plugin info as an attribute of the instance */
140   if (PyObject_SetAttrString (pyobject, "plugin_info", pyplinfo) != 0)
141     {
142       g_warning ("Failed to set 'plugin_info' for '%s'",
143                  g_type_name (the_type));
144 
145       if (PyErr_Occurred ())
146         PyErr_Print ();
147 
148       g_clear_object (&object);
149     }
150 
151   Py_DECREF (pyplinfo);
152   Py_DECREF (pyobject);
153 
154 out:
155 
156   PyGILState_Release (state);
157   return object;
158 }
159 
160 static gboolean
peas_plugin_loader_python_load(PeasPluginLoader * loader,PeasPluginInfo * info)161 peas_plugin_loader_python_load (PeasPluginLoader *loader,
162                                 PeasPluginInfo   *info)
163 {
164   PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (loader);
165   PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader);
166   const gchar *module_dir, *module_name;
167   PyObject *pymodule;
168   PyGILState_STATE state = PyGILState_Ensure ();
169 
170   module_dir = peas_plugin_info_get_module_dir (info);
171   module_name = peas_plugin_info_get_module_name (info);
172 
173   pymodule = peas_python_internal_call ("load", &PyModule_Type, "(sss)",
174                                         info->filename,
175                                         module_dir, module_name);
176 
177   if (pymodule != NULL)
178     {
179       info->loader_data = pymodule;
180       priv->n_loaded_plugins += 1;
181     }
182 
183   PyGILState_Release (state);
184   return pymodule != NULL;
185 }
186 
187 static void
peas_plugin_loader_python_unload(PeasPluginLoader * loader,PeasPluginInfo * info)188 peas_plugin_loader_python_unload (PeasPluginLoader *loader,
189                                   PeasPluginInfo   *info)
190 {
191   PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (loader);
192   PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader);
193   PyGILState_STATE state = PyGILState_Ensure ();
194 
195   /* We have to use this as a hook as the
196    * loader will not be finalized by applications
197    */
198   if (--priv->n_loaded_plugins == 0)
199     peas_python_internal_call ("all_plugins_unloaded", NULL, NULL);
200 
201   Py_CLEAR (info->loader_data);
202   PyGILState_Release (state);
203 }
204 
205 static void
peas_plugin_loader_python_garbage_collect(PeasPluginLoader * loader)206 peas_plugin_loader_python_garbage_collect (PeasPluginLoader *loader)
207 {
208   PyGILState_STATE state = PyGILState_Ensure ();
209 
210   peas_python_internal_call ("garbage_collect", NULL, NULL);
211 
212   PyGILState_Release (state);
213 }
214 
215 static gboolean
peas_plugin_loader_python_initialize(PeasPluginLoader * loader)216 peas_plugin_loader_python_initialize (PeasPluginLoader *loader)
217 {
218   PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (loader);
219   PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader);
220   PyGILState_STATE state = 0;
221   long hexversion;
222 
223   /* We can't support multiple Python interpreter states:
224    * https://bugzilla.gnome.org/show_bug.cgi?id=677091
225    */
226 
227   /* Python initialization */
228   if (Py_IsInitialized ())
229     {
230       state = PyGILState_Ensure ();
231     }
232   else
233     {
234       Py_InitializeEx (FALSE);
235       priv->must_finalize_python = TRUE;
236     }
237 
238   hexversion = PyLong_AsLong (PySys_GetObject ((char *) "hexversion"));
239 
240 #if PY_VERSION_HEX < 0x03000000
241   if (hexversion >= 0x03000000)
242 #else
243   if (hexversion < 0x03000000)
244 #endif
245     {
246       g_critical ("Attempting to mix incompatible Python versions");
247 
248       goto python_init_error;
249     }
250 
251   /* Initialize PyGObject */
252   pygobject_init (PYGOBJECT_MAJOR_VERSION,
253                   PYGOBJECT_MINOR_VERSION,
254                   PYGOBJECT_MICRO_VERSION);
255 
256   if (PyErr_Occurred ())
257     {
258       g_warning ("Error initializing Python Plugin Loader: "
259                  "PyGObject initialization failed");
260 
261       goto python_init_error;
262     }
263 
264   /* Initialize support for threads */
265   pyg_enable_threads ();
266   PyEval_InitThreads ();
267 
268   /* Only redirect warnings when python was not already initialized */
269   if (!priv->must_finalize_python)
270     pyg_disable_warning_redirections ();
271 
272   /* Must be done last, finalize() depends on init_failed */
273   if (!peas_python_internal_setup (!priv->must_finalize_python))
274     {
275       /* Already warned */
276       goto python_init_error;
277     }
278 
279   if (!priv->must_finalize_python)
280     PyGILState_Release (state);
281   else
282     priv->py_thread_state = PyEval_SaveThread ();
283 
284   return TRUE;
285 
286 python_init_error:
287 
288   if (PyErr_Occurred ())
289     PyErr_Print ();
290 
291   g_warning ("Please check the installation of all the Python "
292              "related packages required by libpeas and try again");
293 
294   if (!priv->must_finalize_python)
295     PyGILState_Release (state);
296 
297   priv->init_failed = TRUE;
298   return FALSE;
299 }
300 
301 static void
peas_plugin_loader_python_init(PeasPluginLoaderPython * pyloader)302 peas_plugin_loader_python_init (PeasPluginLoaderPython *pyloader)
303 {
304 }
305 
306 static void
peas_plugin_loader_python_finalize(GObject * object)307 peas_plugin_loader_python_finalize (GObject *object)
308 {
309   PeasPluginLoaderPython *pyloader = PEAS_PLUGIN_LOADER_PYTHON (object);
310   PeasPluginLoaderPythonPrivate *priv = GET_PRIV (pyloader);
311   PyGILState_STATE state;
312 
313   if (!Py_IsInitialized ())
314     goto out;
315 
316   g_warn_if_fail (priv->n_loaded_plugins == 0);
317 
318   if (!priv->init_failed)
319     {
320       state = PyGILState_Ensure ();
321       peas_python_internal_shutdown ();
322       PyGILState_Release (state);
323     }
324 
325   if (priv->py_thread_state)
326     PyEval_RestoreThread (priv->py_thread_state);
327 
328   if (priv->must_finalize_python)
329     {
330       if (!priv->init_failed)
331         PyGILState_Ensure ();
332 
333       Py_Finalize ();
334     }
335 
336 out:
337 
338   G_OBJECT_CLASS (peas_plugin_loader_python_parent_class)->finalize (object);
339 }
340 
341 static void
peas_plugin_loader_python_class_init(PeasPluginLoaderPythonClass * klass)342 peas_plugin_loader_python_class_init (PeasPluginLoaderPythonClass *klass)
343 {
344   GObjectClass *object_class = G_OBJECT_CLASS (klass);
345   PeasPluginLoaderClass *loader_class = PEAS_PLUGIN_LOADER_CLASS (klass);
346 
347   quark_extension_type = g_quark_from_static_string ("peas-extension-type");
348 
349   object_class->finalize = peas_plugin_loader_python_finalize;
350 
351   loader_class->initialize = peas_plugin_loader_python_initialize;
352   loader_class->load = peas_plugin_loader_python_load;
353   loader_class->unload = peas_plugin_loader_python_unload;
354   loader_class->create_extension = peas_plugin_loader_python_create_extension;
355   loader_class->provides_extension = peas_plugin_loader_python_provides_extension;
356   loader_class->garbage_collect = peas_plugin_loader_python_garbage_collect;
357 }
358