[gnome-builder] Add Python scripting support
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] Add Python scripting support
- Date: Mon, 23 Mar 2015 23:37:40 +0000 (UTC)
commit 0d78bff1b41364eda07a5bae3a02d3bd84f9a4f6
Author: Garrett Regier <garrettregier gmail com>
Date: Wed Feb 18 17:08:54 2015 -0800
Add Python scripting support
Signed-off-by: Garrett Regier <garrettregier gmail com>
build/autotools/autoconf.d/50_dependencies.post-am | 12 +-
examples/scripts/README.md | 8 +-
examples/scripts/search-provider.py | 26 ++
libide/Makefile.am | 3 +
libide/ide-script-manager.c | 5 +-
libide/ide.c | 6 +
libide/pygobject/ide-pygobject-script.c | 299 ++++++++++++++++++++
libide/pygobject/ide-pygobject-script.h | 32 ++
8 files changed, 384 insertions(+), 7 deletions(-)
---
diff --git a/build/autotools/autoconf.d/50_dependencies.post-am
b/build/autotools/autoconf.d/50_dependencies.post-am
index ecbc31b..faefbcc 100644
--- a/build/autotools/autoconf.d/50_dependencies.post-am
+++ b/build/autotools/autoconf.d/50_dependencies.post-am
@@ -3,6 +3,7 @@ m4_define([glib_required_version], [2.43.4])
m4_define([gtksourceview_required_version], [3.15.4])
m4_define([ggit_required_version], [0.0.24])
m4_define([gjs_required_version], [1.42.0])
+m4_define([pygobject_required_version], [3.0.0])
PKG_CHECK_MODULES(BUILDER, [gtk+-3.0 >= gtk_required_version
gio-2.0 >= glib_required_version
@@ -15,7 +16,16 @@ PKG_CHECK_MODULES(LIBIDE, [gio-2.0 >= glib_required_version
gtksourceview-3.0 >= gtksourceview_required_version
libgit2-glib-1.0 >= ggit_required_version
gjs-1.0 >= gjs_required_version
- gjs-internals-1.0 >= gjs_required_version])
+ gjs-internals-1.0 >= gjs_required_version
+ pygobject-3.0 >= pygobject_required_version])
+AC_PATH_TOOL(PYTHON3_CONFIG, "python3-config")
+if test -z "${PYTHON3_CONFIG}"; then
+ AC_MSG_ERROR([Failed to locate python3-config.])
+fi
+
+LIBIDE_CFLAGS="${LIBIDE_CFLAGS} `${PYTHON3_CONFIG} --cflags`"
+LIBIDE_LIBS="${LIBIDE_LIBS} `${PYTHON3_CONFIG} --libs`"
+LIBIDE_LDFLAGS="${LIBIDE_LDFLAGS} `${PYTHON3_CONFIG} --ldflags`"
GOBJECT_INTROSPECTION_CHECK([1.30.0])
diff --git a/examples/scripts/README.md b/examples/scripts/README.md
index 54b5405..8840790 100644
--- a/examples/scripts/README.md
+++ b/examples/scripts/README.md
@@ -4,8 +4,8 @@ This directory contains various examples you can take inspiration from when
writing your own IDE extensions.
Extensions are placed in ~/.config/gnome-builder/scripts/. Currently,
-JavaScript is supported. However, more languages may be added in the future
-based on demands.
+JavaScript and Python 3 are supported. However, more languages may be
+added in the future based on demands.
-Simply place a `*.js` file in the proper directory, and it will be loaded
-when the `Ide.Context` is initialized.
+Simply place a `*.js` or `*.py` file in the proper directory, and it will
+be loaded when the `Ide.Context` is initialized.
diff --git a/examples/scripts/search-provider.py b/examples/scripts/search-provider.py
new file mode 100644
index 0000000..c20dc26
--- /dev/null
+++ b/examples/scripts/search-provider.py
@@ -0,0 +1,26 @@
+# ~/.config/gnome-builder/scripts/script1.py
+
+from gi.repository import GObject, Ide
+
+# just some examples of things you can access
+Project = Context.get_project()
+BuildSystem = Context.get_build_system()
+Vcs = Context.get_vcs()
+
+# get a handle to the search engine
+SearchEngine = Context.get_search_engine()
+
+# create a custom provider subclass
+class MySearchProvider(Ide.SearchProvider):
+ # perform the search operation. can be asynchronous
+ def do_populate(self, search_context, search_terms, max_results, cancellable):
+ result = Ide.SearchResult(Context, 'weeee', 'more weeeeeee', 0.5)
+ search_context.add_result(self, result)
+ search_context.provider_completed(self)
+
+ # this is what is shown in the search ui describing the action
+ def do_get_verb(self):
+ return "foobar"
+
+# add our custom provider to the search engine
+SearchEngine.add_provider(MySearchProvider(context=Context))
diff --git a/libide/Makefile.am b/libide/Makefile.am
index ce6acee..203b2f3 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -138,6 +138,8 @@ libide_1_0_la_public_sources = \
libide/ide.h \
libide/local/ide-local-device.c \
libide/local/ide-local-device.h \
+ libide/pygobject/ide-pygobject-script.c \
+ libide/pygobject/ide-pygobject-script.h \
libide/python/ide-python-indenter.c \
libide/python/ide-python-indenter.h \
libide/python/ide-python-language.c \
@@ -193,6 +195,7 @@ libide_1_0_la_includes = \
-I$(top_srcdir)/libide/gjs \
-I$(top_srcdir)/libide/gsettings \
-I$(top_srcdir)/libide/local \
+ -I$(top_srcdir)/libide/pygobject \
-I$(top_srcdir)/libide/python \
-I$(top_srcdir)/libide/tasks \
-I$(top_srcdir)/libide/xml \
diff --git a/libide/ide-script-manager.c b/libide/ide-script-manager.c
index a65c119..7828d8a 100644
--- a/libide/ide-script-manager.c
+++ b/libide/ide-script-manager.c
@@ -146,9 +146,10 @@ allow_file (const gchar *name)
/* NOTE:
*
* Add your allowed suffix here if you are adding a new scripting language
- * (ie: python, etc)
+ * (ie: Lua, etc)
*/
- return g_str_has_suffix (name, ".js");
+ return g_str_has_suffix (name, ".js") ||
+ g_str_has_suffix (name, ".py");
}
static void
diff --git a/libide/ide.c b/libide/ide.c
index a309b53..9fb7cf9 100644
--- a/libide/ide.c
+++ b/libide/ide.c
@@ -34,6 +34,7 @@
#include "ide-git-vcs.h"
#include "ide-gjs-script.h"
#include "ide-gsettings-file-settings.h"
+#include "ide-pygobject-script.h"
#include "ide-python-language.h"
#include "ide-search-provider.h"
#include "ide-xml-language.h"
@@ -112,6 +113,11 @@ ide_init_ctor (void)
IDE_SCRIPT_EXTENSION_POINT".gjs",
-100);
+ g_io_extension_point_implement (IDE_SCRIPT_EXTENSION_POINT,
+ IDE_TYPE_PYGOBJECT_SCRIPT,
+ IDE_SCRIPT_EXTENSION_POINT".py",
+ -100);
+
g_io_extension_point_implement (IDE_SEARCH_PROVIDER_EXTENSION_POINT,
IDE_TYPE_GIT_SEARCH_PROVIDER,
IDE_SEARCH_PROVIDER_EXTENSION_POINT".git",
diff --git a/libide/pygobject/ide-pygobject-script.c b/libide/pygobject/ide-pygobject-script.c
new file mode 100644
index 0000000..a2fa998
--- /dev/null
+++ b/libide/pygobject/ide-pygobject-script.c
@@ -0,0 +1,299 @@
+/* ide-pygobject-script.c
+ *
+ * Copyright (C) 2015 Garrett Regier <garrettregier gmail com>
+ *
+ * This file 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-context.h"
+#include "ide-pygobject-script.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 <pygobject.h>
+
+#include <glib/gi18n.h>
+
+struct _IdePyGObjectScript
+{
+ IdeScript parent_instance;
+};
+
+static PyThreadState *py_thread_state = NULL;
+
+static void async_initable_iface_init (GAsyncInitableIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdePyGObjectScript, ide_pygobject_script, IDE_TYPE_SCRIPT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+ async_initable_iface_init))
+
+static gboolean
+init_pygobject (void)
+{
+ PyGILState_STATE state = 0;
+ long hexversion;
+ gboolean must_finalize_python = FALSE;
+ static gboolean initialized = FALSE;
+ static gboolean success = FALSE;
+
+ if (initialized)
+ return success;
+
+ initialized = TRUE;
+
+ /* Python initialization */
+ if (Py_IsInitialized ())
+ {
+ state = PyGILState_Ensure ();
+ }
+ else
+ {
+ Py_InitializeEx (FALSE);
+ 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");
+ return FALSE;
+ }
+
+ /* Initialize PyGObject */
+ pygobject_init (3, 0, 0);
+
+ if (PyErr_Occurred ())
+ {
+ g_warning ("PyGObject initialization failed");
+ PyErr_Print ();
+ return FALSE;
+ }
+
+ /* Initialize support for threads */
+ pyg_enable_threads ();
+ PyEval_InitThreads ();
+
+ /* Only redirect warnings when pygobject was not already initialized */
+ if (!must_finalize_python)
+ pyg_disable_warning_redirections ();
+
+ if (!must_finalize_python)
+ PyGILState_Release (state);
+ else
+ py_thread_state = PyEval_SaveThread ();
+
+ success = TRUE;
+ return TRUE;
+}
+
+static void
+ide_pygobject_script_load (IdeScript *script)
+{
+ IdePyGObjectScript *self = (IdePyGObjectScript *)script;
+ IdeContext *context;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(gchar) contents = NULL;
+ g_autoptr(gchar) path = NULL;
+ g_autoptr(GFile) parent = NULL;
+ g_autoptr(gchar) parent_path = NULL;
+ GFile *file;
+ PyObject *globals = NULL;
+ PyObject *builtins_module;
+ PyObject *module_dir = NULL;
+ PyObject *retval;
+ PyObject *pycontext = NULL;
+ PyObject *code;
+ PyGILState_STATE state;
+
+ g_return_if_fail (IDE_IS_PYGOBJECT_SCRIPT (self));
+
+ file = ide_script_get_file (IDE_SCRIPT (script));
+
+ if (!file)
+ {
+ g_warning (_("Attempt to load a PyGObject script with no filename."));
+ return;
+ }
+
+ path = g_file_get_basename (file);
+
+ if (!g_file_load_contents (file, NULL, &contents, NULL, NULL, &error))
+ {
+ g_warning ("%s", error->message);
+ return;
+ }
+
+ if (!init_pygobject ())
+ return;
+
+ state = PyGILState_Ensure ();
+
+ globals = PyDict_New ();
+ if (globals == NULL)
+ goto out;
+
+ builtins_module = PyImport_ImportModule ("builtins");
+ if (builtins_module == NULL)
+ goto out;
+
+ if (PyDict_SetItemString (globals, "__builtins__", builtins_module) != 0)
+ goto out;
+
+ parent = g_file_get_parent (file);
+ parent_path = g_file_get_path (parent);
+ module_dir = PyUnicode_FromString (parent_path);
+
+ if (PyDict_SetItemString (globals, "module_dir", module_dir) != 0)
+ goto out;
+
+ retval = PyRun_String ("import signal\n"
+ "import sys\n"
+ "if module_dir not in sys.path:\n"
+ " sys.path.insert(0, module_dir)\n"
+ "\n"
+ "signal.signal(signal.SIGINT, signal.SIG_DFL)\n",
+ Py_file_input,
+ globals, globals);
+
+ if (PyDict_DelItemString (globals, "module_dir") != 0)
+ goto out;
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ pycontext = pygobject_new (G_OBJECT (context));
+ if (pycontext == NULL)
+ goto out;
+
+ if (PyDict_SetItemString (globals, "Context", pycontext) != 0)
+ goto out;
+
+ code = Py_CompileString (contents, path, Py_file_input);
+ if (code == NULL)
+ goto out;
+
+ retval = PyEval_EvalCode (code, globals, globals);
+ Py_XDECREF (retval);
+
+out:
+
+ Py_XDECREF (code);
+ Py_XDECREF (pycontext);
+ Py_XDECREF (module_dir);
+ Py_XDECREF (globals);
+
+ if (PyErr_Occurred ())
+ PyErr_Print ();
+
+ PyGILState_Release (state);
+}
+
+static void
+ide_pygobject_script_unload (IdeScript *self)
+{
+ g_return_if_fail (IDE_IS_PYGOBJECT_SCRIPT (self));
+}
+
+static void
+ide_pygobject_script_class_init (IdePyGObjectScriptClass *klass)
+{
+ IdeScriptClass *script_class = IDE_SCRIPT_CLASS (klass);
+
+ script_class->load = ide_pygobject_script_load;
+ script_class->unload = ide_pygobject_script_unload;
+}
+
+static void
+ide_pygobject_script_init (IdePyGObjectScript *self)
+{
+}
+
+static void
+ide_pygobject_script_init_async (GAsyncInitable *initable,
+ gint io_priority,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdePyGObjectScript *self = (IdePyGObjectScript *)initable;
+ g_autoptr(GTask) task = NULL;
+ g_autoptr(gchar) path = NULL;
+ GFile *file;
+
+ g_return_if_fail (IDE_IS_PYGOBJECT_SCRIPT (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+
+ file = ide_script_get_file (IDE_SCRIPT (self));
+
+ if (!file)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_FILENAME,
+ _("The filename for the script was not provided."));
+ return;
+ }
+
+ path = g_file_get_path (file);
+
+ if (!path)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_FILENAME,
+ _("The script must be on a local filesystem."));
+ return;
+ }
+
+ if (!g_str_has_suffix (path, ".py"))
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("The script \"%s\" is not a PyGObject file."),
+ path);
+ return;
+ }
+
+ ide_script_load (IDE_SCRIPT (self));
+
+ g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+ide_pygobject_script_init_finish (GAsyncInitable *initable,
+ GAsyncResult *result,
+ GError **error)
+{
+ GTask *task = (GTask *)result;
+
+ g_return_val_if_fail (IDE_IS_PYGOBJECT_SCRIPT (initable), FALSE);
+ g_return_val_if_fail (G_IS_TASK (task), FALSE);
+
+ return g_task_propagate_boolean (task, error);
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *iface)
+{
+ iface->init_async = ide_pygobject_script_init_async;
+ iface->init_finish = ide_pygobject_script_init_finish;
+}
diff --git a/libide/pygobject/ide-pygobject-script.h b/libide/pygobject/ide-pygobject-script.h
new file mode 100644
index 0000000..fa059f2
--- /dev/null
+++ b/libide/pygobject/ide-pygobject-script.h
@@ -0,0 +1,32 @@
+/* ide-pygobject-script.h
+ *
+ * Copyright (C) 2015 Garrett Regier <garrettregier gmail com>
+ *
+ * This file 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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 General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_PYGOBJECT_SCRIPT_H
+#define IDE_PYGOBJECT_SCRIPT_H
+
+#include "ide-script.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PYGOBJECT_SCRIPT (ide_pygobject_script_get_type())
+
+G_DECLARE_FINAL_TYPE (IdePyGObjectScript, ide_pygobject_script, IDE, PYGOBJECT_SCRIPT, IdeScript)
+
+G_END_DECLS
+
+#endif /* IDE_PYGOBJECT_SCRIPT_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]