[gnome-builder] libide: add IdeRunManager and IdeRunner interface



commit 7b60eb6b16d135eb43eae7dee4f1741dbd6d4e3e
Author: Christian Hergert <chergert redhat com>
Date:   Sun Jul 17 03:12:59 2016 -0700

    libide: add IdeRunManager and IdeRunner interface
    
    IdeRunner allows us to spawn a program within the runtime. It needs to
    allow some mutating of path, so building the argv is delayed until as
    late as possible. (We may need to alter argv0 to gdb and similar in the
    future).

 libide/ide-context.c             |   24 ++
 libide/ide-context.h             |    1 +
 libide/ide-types.h               |    4 +-
 libide/ide.h                     |    3 +
 libide/runner/OVERVIEW.md        |   89 +++++
 libide/runner/ide-run-manager.c  |  351 ++++++++++++++++++++
 libide/runner/ide-run-manager.h  |   44 +++
 libide/runner/ide-runner-addin.c |  141 ++++++++
 libide/runner/ide-runner-addin.h |   78 +++++
 libide/runner/ide-runner.c       |  653 ++++++++++++++++++++++++++++++++++++++
 libide/runner/ide-runner.h       |   50 ++-
 libide/runtimes/ide-runtime.c    |   61 ++++
 libide/runtimes/ide-runtime.h    |   24 ++
 13 files changed, 1505 insertions(+), 18 deletions(-)
---
diff --git a/libide/ide-context.c b/libide/ide-context.c
index 1d375f1..633f75e 100644
--- a/libide/ide-context.c
+++ b/libide/ide-context.c
@@ -42,6 +42,7 @@
 #include "projects/ide-project-item.h"
 #include "projects/ide-project.h"
 #include "projects/ide-recent-projects.h"
+#include "runner/ide-run-manager.h"
 #include "runtimes/ide-runtime-manager.h"
 #include "scripting/ide-script-manager.h"
 #include "search/ide-search-engine.h"
@@ -66,6 +67,7 @@ struct _IdeContext
   IdeDeviceManager         *device_manager;
   IdeDoap                  *doap;
   GtkRecentManager         *recent_manager;
+  IdeRunManager            *run_manager;
   IdeRuntimeManager        *runtime_manager;
   IdeScriptManager         *script_manager;
   IdeSearchEngine          *search_engine;
@@ -842,6 +844,10 @@ ide_context_init (IdeContext *self)
                                 "context", self,
                                 NULL);
 
+  self->run_manager = g_object_new (IDE_TYPE_RUN_MANAGER,
+                                    "context", self,
+                                    NULL);
+
   self->runtime_manager = g_object_new (IDE_TYPE_RUNTIME_MANAGER,
                                         "context", self,
                                         NULL);
@@ -2171,3 +2177,21 @@ ide_context_warning (IdeContext  *self,
   g_logv ("Ide", G_LOG_LEVEL_WARNING, format, args);
   va_end (args);
 }
+
+/**
+ * ide_context_get_run_manager:
+ *
+ * Gets the #IdeRunManager for the context. This manager object simplifies
+ * the process of running an #IdeBuildTarget from the build system. Primarily,
+ * it enforces that only a single target may be run at a time, since that is
+ * what the UI will expect.
+ *
+ * Returns: (transfer none): An #IdeRunManager.
+ */
+IdeRunManager *
+ide_context_get_run_manager (IdeContext *self)
+{
+  g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
+
+  return self->run_manager;
+}
diff --git a/libide/ide-context.h b/libide/ide-context.h
index af24378..96ea370 100644
--- a/libide/ide-context.h
+++ b/libide/ide-context.h
@@ -39,6 +39,7 @@ IdeConfigurationManager  *ide_context_get_configuration_manager (IdeContext
 IdeDeviceManager         *ide_context_get_device_manager        (IdeContext           *self);
 IdeProject               *ide_context_get_project               (IdeContext           *self);
 GtkRecentManager         *ide_context_get_recent_manager        (IdeContext           *self);
+IdeRunManager            *ide_context_get_run_manager           (IdeContext           *self);
 IdeRuntimeManager        *ide_context_get_runtime_manager       (IdeContext           *self);
 IdeScriptManager         *ide_context_get_script_manager        (IdeContext           *self);
 IdeSearchEngine          *ide_context_get_search_engine         (IdeContext           *self);
diff --git a/libide/ide-types.h b/libide/ide-types.h
index 92c1d73..89dedea 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -36,10 +36,10 @@ typedef struct _IdeBufferChangeMonitor         IdeBufferChangeMonitor;
 typedef struct _IdeBufferManager               IdeBufferManager;
 
 typedef struct _IdeBuilder                     IdeBuilder;
-
+typedef struct _IdeBuildManager                IdeBuildManager;
 typedef struct _IdeBuildResult                 IdeBuildResult;
-
 typedef struct _IdeBuildSystem                 IdeBuildSystem;
+typedef struct _IdeBuildTarget                 IdeBuildTarget;
 
 typedef struct _IdeConfiguration               IdeConfiguration;
 typedef struct _IdeConfigurationManager        IdeConfigurationManager;
diff --git a/libide/ide.h b/libide/ide.h
index 0dc0765..eaad665 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -85,6 +85,9 @@ G_BEGIN_DECLS
 #include "projects/ide-project-miner.h"
 #include "projects/ide-project.h"
 #include "projects/ide-recent-projects.h"
+#include "runner/ide-run-manager.h"
+#include "runner/ide-runner.h"
+#include "runner/ide-runner-addin.h"
 #include "runtimes/ide-runtime-manager.h"
 #include "runtimes/ide-runtime-provider.h"
 #include "runtimes/ide-runtime.h"
diff --git a/libide/runner/OVERVIEW.md b/libide/runner/OVERVIEW.md
new file mode 100644
index 0000000..cf5801a
--- /dev/null
+++ b/libide/runner/OVERVIEW.md
@@ -0,0 +1,89 @@
+# Running Projects
+
+Running a project target in Builder needs to accomidate a few difficult
+to plumb components. For example, Builder supports runtimes which might
+be different than the current host (such as org.gnome.Platform 3.22).
+Additionally, we might need to attach a debugger. The build system might
+also need to perform an installation of the application bits into a
+runtime so that the project can run as it's "installed state".
+
+All of these complexities results in project execution being abstracted
+into an “IdeRunner” object and series of “IdeRunnerAddin” extensions.
+This allows the various plugins involved (build system, runtime, possible
+debugger or profilers) to hook in to various stages and modify things as
+necessary.
+
+# IdeRunner
+
+This is our core runner structure. It manages some basic process execution
+stuff, but most things get tweaked and configured by the addins.
+
+The process is launched by the IdeSubprocessLauncher created by the configured
+runtime. However, addins can tweak things as necessary.
+
+## IdeRunnerAddin
+
+Plugins should implement this interface so that they can modify the runner
+as necessary. This should be used for custom extensoin points that are always
+needed, not to add integration points like debugger, profiler, etc.
+
+## Debugger Integration
+
+Debuggers may need to hook into the runtime pid namespace (and possibly mount
+namespace to bring along tooling). This could also mean starting the process as
+an inferior of something like `gdb`. In the flatpak scenario, this could be
+
+  flatpak reaper → gdb → inferior
+
+Additionally, the debugger will need to be able to IPC with the gdb instance
+inside the runtime.
+
+While we don't have support for this yet, I think the design we can use is that
+the `IdeRunner` can have API to bring in "sidecars" to the runtime which
+contain the debugging tooling. For the local system, this would be a
+passthrough. For something like flatpak, it would need to enter the namespace
+and add a mountpoint for the tooling. (This is probably something that needs to
+be implemented in bubblewrap to some degree).
+
+When the debugger sets up the runner, it will need to be able to modify argv
+similar to our IdeSubprocessLauncher. This will allow it to add gdb to the
+prefix of the command.
+
+Getting a runner will be somewhat consistent with:
+
+  BuildSystem = context.get_build_system()
+  ConfigManager = context.get_configuration_manager()
+
+  config = ConfigManager.get_current()
+  runtime = config.get_runtime()
+  target = BuildSystem.get_default_run_command(config)
+  runner = runtime.create_runner(config, target)
+
+    -> the build system, at this point, may have added a prehook via
+       the runner addin that will install the project before it can
+       be run.
+
+And the debugger might do something like:
+
+  runner.prepend_argv('gdbserver')
+  runner.prepend_argv('--once')
+  runner.prepend_argv('--args')
+
+When runner.run() is called, the implementation might choose to prepend
+additional argv parameters. This allows gdb to simply prepend the command
+line, but still have flatpak spawn the container process (bubblewrap).
+
+## Profiling Integration
+
+A profiler (such as sysprof) will need to know the process identifier of the
+spawned process (and possibly children processes). This is somewhat similar
+to debugging except that it isn't strictly necessary that the process stops
+before reaching `main()`.
+
+To synchronize on startup, sysprof might spawn a helper process that
+communicates with the listener process, and then execs the child command.
+
+So it might need to do something like:
+
+  runner.prepend_argv('sysprof-spawner')
+
diff --git a/libide/runner/ide-run-manager.c b/libide/runner/ide-run-manager.c
new file mode 100644
index 0000000..b86d93a
--- /dev/null
+++ b/libide/runner/ide-run-manager.c
@@ -0,0 +1,351 @@
+/* ide-run-manager.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-run-manager"
+
+#include <glib/gi18n.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+
+#include "buildsystem/ide-build-manager.h"
+#include "buildsystem/ide-build-target.h"
+#include "buildsystem/ide-configuration.h"
+#include "buildsystem/ide-configuration-manager.h"
+#include "runner/ide-run-manager.h"
+#include "runner/ide-runner.h"
+#include "runtimes/ide-runtime.h"
+
+struct _IdeRunManager
+{
+  IdeObject           parent_instance;
+
+  GCancellable       *cancellable;
+  GSimpleActionGroup *actions;
+
+  guint               busy : 1;
+};
+
+G_DEFINE_TYPE (IdeRunManager, ide_run_manager, IDE_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_BUSY,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_run_manager_finalize (GObject *object)
+{
+  IdeRunManager *self = (IdeRunManager *)object;
+
+  g_clear_object (&self->cancellable);
+  g_clear_object (&self->actions);
+
+  G_OBJECT_CLASS (ide_run_manager_parent_class)->finalize (object);
+}
+
+static void
+ide_run_manager_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  IdeRunManager *self = IDE_RUN_MANAGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUSY:
+      g_value_set_boolean (value, ide_run_manager_get_busy (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_run_manager_class_init (IdeRunManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_run_manager_finalize;
+  object_class->get_property = ide_run_manager_get_property;
+
+  properties [PROP_BUSY] =
+    g_param_spec_boolean ("busy",
+                          "Busy",
+                          "Busy",
+                          FALSE,
+                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_run_manager_init (IdeRunManager *self)
+{
+}
+
+gboolean
+ide_run_manager_get_busy (IdeRunManager *self)
+{
+  g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), FALSE);
+
+  return self->busy;
+}
+
+static gboolean
+ide_run_manager_check_busy (IdeRunManager  *self,
+                            GError        **error)
+{
+  g_assert (IDE_IS_RUN_MANAGER (self));
+  g_assert (error != NULL);
+
+  if (ide_run_manager_get_busy (self))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_BUSY,
+                   "%s",
+                   _("Cannot run target, another target is running"));
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+ide_run_manager_run_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  IdeRunner *runner = (IdeRunner *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_RUNNER (runner));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_runner_run_finish (runner, result, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  g_task_return_boolean (task, TRUE);
+
+failure:
+  IDE_EXIT;
+}
+
+static void
+ide_run_manager_install_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  IdeBuildManager *build_manager = (IdeBuildManager *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(IdeRunner) runner = NULL;
+  IdeConfigurationManager *config_manager;
+  IdeConfiguration *config;
+  IdeBuildTarget *build_target;
+  IdeRunManager *self;
+  GCancellable *cancellable;
+  IdeContext *context;
+  IdeRuntime *runtime;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUILD_MANAGER (build_manager));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_build_manager_build_finish (build_manager, result, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  build_target = g_task_get_task_data (task);
+  self = g_task_get_source_object (task);
+
+  g_assert (IDE_IS_BUILD_TARGET (build_target));
+  g_assert (IDE_IS_RUN_MANAGER (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  g_assert (IDE_IS_CONTEXT (context));
+
+  config_manager = ide_context_get_configuration_manager (context);
+  config = ide_configuration_manager_get_current (config_manager);
+  runtime = ide_configuration_get_runtime (config);
+
+  if (runtime == NULL)
+    {
+      g_task_return_new_error (task,
+                               IDE_RUNTIME_ERROR,
+                               IDE_RUNTIME_ERROR_NO_SUCH_RUNTIME,
+                               "%s “%s”",
+                               _("Failed to locate runtime"),
+                               ide_configuration_get_runtime_id (config));
+      IDE_GOTO (failure);
+    }
+
+  runner = ide_runtime_create_runner (runtime, build_target);
+  cancellable = g_task_get_cancellable (task);
+
+  g_assert (IDE_IS_RUNNER (runner));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  ide_runner_run_async (runner,
+                        cancellable,
+                        ide_run_manager_run_cb,
+                        g_steal_pointer (&task));
+
+failure:
+  IDE_EXIT;
+}
+
+static void
+ide_run_manager_task_completed (IdeRunManager *self,
+                                GParamSpec    *pspec,
+                                GTask         *task)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_RUN_MANAGER (self));
+  g_assert (pspec != NULL);
+  g_assert (G_IS_TASK (task));
+
+  self->busy = FALSE;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+
+  IDE_EXIT;
+}
+
+void
+ide_run_manager_run_async (IdeRunManager       *self,
+                           IdeBuildTarget      *build_target,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GCancellable) local_cancellable = NULL;
+  IdeBuildManager *build_manager;
+  IdeContext *context;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_RUN_MANAGER (self));
+  g_return_if_fail (IDE_IS_BUILD_TARGET (build_target));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (cancellable == NULL)
+    cancellable = local_cancellable = g_cancellable_new ();
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_run_manager_run_async);
+  g_task_set_task_data (task, g_object_ref (build_target), g_object_unref);
+
+  if (ide_run_manager_check_busy (self, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  /*
+   * First we need to make sure the target is up to date and installed
+   * so that all the dependent resources are available.
+   */
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  build_manager = ide_context_get_build_manager (context);
+
+  self->busy = TRUE;
+
+  g_set_object (&self->cancellable, cancellable);
+
+  g_signal_connect_object (task,
+                           "notify::completed",
+                           G_CALLBACK (ide_run_manager_task_completed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_build_manager_install_async (build_manager,
+                                   cancellable,
+                                   ide_run_manager_install_cb,
+                                   g_steal_pointer (&task));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+
+failure:
+  IDE_EXIT;
+}
+
+gboolean
+ide_run_manager_run_finish (IdeRunManager  *self,
+                            GAsyncResult   *result,
+                            GError        **error)
+{
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  ret = g_task_propagate_boolean (G_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static gboolean
+do_cancel_in_timeout (gpointer user_data)
+{
+  GCancellable *cancellable = user_data;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_CANCELLABLE (cancellable));
+
+  if (!g_cancellable_is_cancelled (cancellable))
+    g_cancellable_cancel (cancellable);
+
+  IDE_RETURN (G_SOURCE_REMOVE);
+}
+
+void
+ide_run_manager_cancel (IdeRunManager *self)
+{
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_RUN_MANAGER (self));
+
+  if (self->cancellable != NULL)
+    g_timeout_add (0, do_cancel_in_timeout, g_object_ref (self->cancellable));
+
+  IDE_EXIT;
+}
diff --git a/libide/runner/ide-run-manager.h b/libide/runner/ide-run-manager.h
new file mode 100644
index 0000000..49a3e3f
--- /dev/null
+++ b/libide/runner/ide-run-manager.h
@@ -0,0 +1,44 @@
+/* ide-run-manager.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_RUN_MANAGER_H
+#define IDE_RUN_MANAGER_H
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUN_MANAGER (ide_run_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeRunManager, ide_run_manager, IDE, RUN_MANAGER, IdeObject)
+
+void     ide_run_manager_cancel     (IdeRunManager        *self);
+gboolean ide_run_manager_get_busy   (IdeRunManager        *self);
+void     ide_run_manager_run_async  (IdeRunManager        *self,
+                                     IdeBuildTarget       *build_target,
+                                     GCancellable         *cancellable,
+                                     GAsyncReadyCallback   callback,
+                                     gpointer              user_data);
+gboolean ide_run_manager_run_finish (IdeRunManager        *self,
+                                     GAsyncResult         *result,
+                                     GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_RUN_MANAGER_H */
+
diff --git a/libide/runner/ide-runner-addin.c b/libide/runner/ide-runner-addin.c
new file mode 100644
index 0000000..d2a455b
--- /dev/null
+++ b/libide/runner/ide-runner-addin.c
@@ -0,0 +1,141 @@
+/* ide-runner-addin.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-runner-addin"
+
+#include "ide-runner-addin.h"
+
+G_DEFINE_INTERFACE (IdeRunnerAddin, ide_runner_addin, G_TYPE_OBJECT)
+
+static void
+ide_runner_addin_real_load (IdeRunnerAddin *self,
+                            IdeRunner      *runner)
+{
+}
+
+static void
+ide_runner_addin_real_unload (IdeRunnerAddin *self,
+                              IdeRunner      *runner)
+{
+}
+
+static void
+dummy_async (IdeRunnerAddin      *self,
+             GCancellable        *cancellable,
+             GAsyncReadyCallback  callback,
+             gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (callback == NULL)
+    return;
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+dummy_finish (IdeRunnerAddin  *self,
+              GAsyncResult    *result,
+              GError         **error)
+{
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_runner_addin_default_init (IdeRunnerAddinInterface *iface)
+{
+  iface->load = ide_runner_addin_real_load;
+  iface->unload = ide_runner_addin_real_unload;
+  iface->prehook_async = dummy_async;
+  iface->prehook_finish = dummy_finish;
+  iface->posthook_async = dummy_async;
+  iface->posthook_finish = dummy_finish;
+}
+
+void
+ide_runner_addin_load (IdeRunnerAddin *self,
+                       IdeRunner      *runner)
+{
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (IDE_IS_RUNNER (runner));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->load (self, runner);
+}
+
+void
+ide_runner_addin_unload (IdeRunnerAddin *self,
+                         IdeRunner      *runner)
+{
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (IDE_IS_RUNNER (runner));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->unload (self, runner);
+}
+
+void
+ide_runner_addin_prehook_async (IdeRunnerAddin      *self,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_RUNNER_ADDIN (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->prehook_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runner_addin_prehook_finish (IdeRunnerAddin  *self,
+                                 GAsyncResult    *result,
+                                 GError         **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER_ADDIN (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_RUNNER_ADDIN_GET_IFACE (self)->prehook_finish (self, result, error);
+}
+
+void
+ide_runner_addin_posthook_async (IdeRunnerAddin      *self,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_RUNNER_ADDIN (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->posthook_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runner_addin_posthook_finish (IdeRunnerAddin  *self,
+                                  GAsyncResult    *result,
+                                  GError         **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER_ADDIN (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_RUNNER_ADDIN_GET_IFACE (self)->posthook_finish (self, result, error);
+}
diff --git a/libide/runner/ide-runner-addin.h b/libide/runner/ide-runner-addin.h
new file mode 100644
index 0000000..60e6a17
--- /dev/null
+++ b/libide/runner/ide-runner-addin.h
@@ -0,0 +1,78 @@
+/* ide-runner-addin.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_RUNNER_ADDIN_H
+#define IDE_RUNNER_ADDIN_H
+
+#include <gio/gio.h>
+
+#include "ide-types.h"
+#include "ide-runner.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUNNER_ADDIN (ide_runner_addin_get_type())
+
+G_DECLARE_INTERFACE (IdeRunnerAddin, ide_runner_addin, IDE, RUNNER_ADDIN, GObject)
+
+struct _IdeRunnerAddinInterface
+{
+  GTypeInterface parent_interface;
+
+  void     (*load)            (IdeRunnerAddin       *self,
+                               IdeRunner            *runner);
+  void     (*unload)          (IdeRunnerAddin       *self,
+                               IdeRunner            *runner);
+  void     (*prehook_async)   (IdeRunnerAddin       *self,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data);
+  gboolean (*prehook_finish)  (IdeRunnerAddin       *self,
+                               GAsyncResult         *result,
+                               GError              **error);
+  void     (*posthook_async)  (IdeRunnerAddin       *self,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data);
+  gboolean (*posthook_finish) (IdeRunnerAddin       *self,
+                               GAsyncResult         *result,
+                               GError              **error);
+};
+
+void     ide_runner_addin_load            (IdeRunnerAddin       *self,
+                                           IdeRunner            *runner);
+void     ide_runner_addin_unload          (IdeRunnerAddin       *self,
+                                           IdeRunner            *runner);
+void     ide_runner_addin_prehook_async   (IdeRunnerAddin       *self,
+                                           GCancellable         *cancellable,
+                                           GAsyncReadyCallback   callback,
+                                           gpointer              user_data);
+gboolean ide_runner_addin_prehook_finish  (IdeRunnerAddin       *self,
+                                           GAsyncResult         *result,
+                                           GError              **error);
+void     ide_runner_addin_posthook_async  (IdeRunnerAddin       *self,
+                                           GCancellable         *cancellable,
+                                           GAsyncReadyCallback   callback,
+                                           gpointer              user_data);
+gboolean ide_runner_addin_posthook_finish (IdeRunnerAddin       *self,
+                                           GAsyncResult         *result,
+                                           GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_RUNNER_ADDIN_H */
diff --git a/libide/runner/ide-runner.c b/libide/runner/ide-runner.c
new file mode 100644
index 0000000..8b4f1e0
--- /dev/null
+++ b/libide/runner/ide-runner.c
@@ -0,0 +1,653 @@
+/* ide-runner.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-runner"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+#include <stdlib.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+
+#include "runner/ide-runner.h"
+#include "runner/ide-runner-addin.h"
+#include "workers/ide-subprocess-launcher.h"
+
+typedef struct
+{
+  PeasExtensionSet *addins;
+  GQueue argv;
+} IdeRunnerPrivate;
+
+typedef struct
+{
+  GSList *prehook_queue;
+  GSList *posthook_queue;
+} IdeRunnerRunState;
+
+enum {
+  PROP_0,
+  PROP_ARGV,
+  N_PROPS
+};
+
+static void ide_runner_tick_posthook (GTask *task);
+static void ide_runner_tick_prehook  (GTask *task);
+static void ide_runner_tick_run      (GTask *task);
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeRunner, ide_runner, IDE_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static IdeRunnerAddin *
+pop_runner_addin (GSList **list)
+{
+  IdeRunnerAddin *ret;
+
+  g_assert (list != NULL);
+  g_assert (*list != NULL);
+
+  ret = (*list)->data;
+
+  *list = g_slist_delete_link (*list, *list);
+
+  return ret;
+}
+
+static void
+ide_runner_run_state_free (gpointer data)
+{
+  IdeRunnerRunState *state = data;
+
+  g_slist_foreach (state->prehook_queue, (GFunc)g_object_unref, NULL);
+  g_slist_free (state->prehook_queue);
+
+  g_slist_foreach (state->posthook_queue, (GFunc)g_object_unref, NULL);
+  g_slist_free (state->posthook_queue);
+
+  g_slice_free (IdeRunnerRunState, state);
+}
+
+static void
+ide_runner_run_wait_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  GSubprocess *subprocess = (GSubprocess *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SUBPROCESS (subprocess));
+
+  if (!g_subprocess_wait_finish (subprocess, result, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_EXIT;
+    }
+
+  if (g_subprocess_get_if_exited (subprocess))
+    {
+      gint exit_code;
+
+      exit_code = g_subprocess_get_exit_status (subprocess);
+
+      if (exit_code == EXIT_SUCCESS)
+        {
+          g_task_return_boolean (task, TRUE);
+          IDE_EXIT;
+        }
+    }
+
+  g_task_return_new_error (task,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           "%s",
+                           _("Process quit unexpectedly"));
+
+  IDE_EXIT;
+}
+
+static void
+ide_runner_real_run_async (IdeRunner           *self,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autoptr(GSubprocess) subprocess = NULL;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_runner_real_run_async);
+
+  launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+
+  for (GList *iter = priv->argv.head; iter != NULL; iter = iter->next)
+    ide_subprocess_launcher_push_argv (launcher, iter->data);
+
+  ide_subprocess_launcher_set_cwd (launcher, g_get_home_dir ());
+
+  subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, &error);
+
+  g_assert (subprocess == NULL || G_IS_SUBPROCESS (subprocess));
+
+  if (subprocess == NULL)
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  g_subprocess_wait_async (subprocess,
+                           cancellable,
+                           ide_runner_run_wait_cb,
+                           g_steal_pointer (&task));
+
+failure:
+  IDE_EXIT;
+}
+
+static gboolean
+ide_runner_real_run_finish (IdeRunner     *self,
+                            GAsyncResult  *result,
+                            GError       **error)
+{
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (G_IS_TASK (result));
+  g_assert (g_task_is_valid (G_TASK (result), self));
+  g_assert (g_task_get_source_tag (G_TASK (result)) == ide_runner_real_run_async);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_runner_extension_added (PeasExtensionSet *set,
+                            PeasPluginInfo   *plugin_info,
+                            PeasExtension    *exten,
+                            gpointer          user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
+  IdeRunner *self = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (IDE_IS_RUNNER (self));
+
+  ide_runner_addin_load (addin, self);
+}
+
+static void
+ide_runner_extension_removed (PeasExtensionSet *set,
+                              PeasPluginInfo   *plugin_info,
+                              PeasExtension    *exten,
+                              gpointer          user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
+  IdeRunner *self = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (IDE_IS_RUNNER (self));
+
+  ide_runner_addin_unload (addin, self);
+}
+
+static void
+ide_runner_constructed (GObject *object)
+{
+  IdeRunner *self = (IdeRunner *)object;
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  G_OBJECT_CLASS (ide_runner_parent_class)->constructed (object);
+
+  priv->addins = peas_extension_set_new (peas_engine_get_default (),
+                                         IDE_TYPE_RUNNER_ADDIN,
+                                         NULL);
+
+  g_signal_connect (priv->addins,
+                    "extension-added",
+                    G_CALLBACK (ide_runner_extension_added),
+                    self);
+
+  g_signal_connect (priv->addins,
+                    "extension-removed",
+                    G_CALLBACK (ide_runner_extension_removed),
+                    self);
+
+  peas_extension_set_foreach (priv->addins,
+                              ide_runner_extension_added,
+                              self);
+}
+
+static void
+ide_runner_finalize (GObject *object)
+{
+  IdeRunner *self = (IdeRunner *)object;
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
+  g_queue_clear (&priv->argv);
+
+  G_OBJECT_CLASS (ide_runner_parent_class)->finalize (object);
+}
+
+static void
+ide_runner_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  IdeRunner *self = IDE_RUNNER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ARGV:
+      g_value_take_boxed (value, ide_runner_get_argv (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_runner_set_property (GObject      *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  IdeRunner *self = IDE_RUNNER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ARGV:
+      ide_runner_set_argv (self, g_value_get_boxed (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_runner_class_init (IdeRunnerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_runner_constructed;
+  object_class->finalize = ide_runner_finalize;
+  object_class->get_property = ide_runner_get_property;
+  object_class->set_property = ide_runner_set_property;
+
+  klass->run_async = ide_runner_real_run_async;
+  klass->run_finish = ide_runner_real_run_finish;
+
+  properties [PROP_ARGV] =
+    g_param_spec_boxed ("argv",
+                        "Argv",
+                        "The argument list for the command",
+                        G_TYPE_STRV,
+                        (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_runner_init (IdeRunner *self)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_queue_init (&priv->argv);
+}
+
+/**
+ * ide_runner_get_stdin:
+ *
+ * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
+ */
+GInputStream *
+ide_runner_get_stdin (IdeRunner *self)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  return IDE_RUNNER_GET_CLASS (self)->get_stdin (self);
+}
+
+/**
+ * ide_runner_get_stdout:
+ *
+ * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
+ */
+GOutputStream *
+ide_runner_get_stdout (IdeRunner *self)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  return IDE_RUNNER_GET_CLASS (self)->get_stdout (self);
+}
+
+/**
+ * ide_runner_get_stderr:
+ *
+ * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
+ */
+GOutputStream *
+ide_runner_get_stderr (IdeRunner *self)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  return IDE_RUNNER_GET_CLASS (self)->get_stderr (self);
+}
+
+void
+ide_runner_force_quit (IdeRunner *self)
+{
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  IDE_RUNNER_GET_CLASS (self)->force_quit (self);
+}
+
+void
+ide_runner_set_argv (IdeRunner           *self,
+                     const gchar * const *argv)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+  guint i;
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
+  g_queue_clear (&priv->argv);
+
+  if (argv != NULL)
+    {
+      for (i = 0; argv [i]; i++)
+        g_queue_push_tail (&priv->argv, g_strdup (argv [i]));
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
+}
+
+/**
+ * ide_runner_get_argv:
+ *
+ * Gets the argument list as a newly allocated string array.
+ *
+ * Returns: (transfer full): A newly allocated string array that should
+ *   be freed with g_strfreev().
+ */
+gchar **
+ide_runner_get_argv (IdeRunner *self)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+  GPtrArray *ar;
+  GList *iter;
+
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  ar = g_ptr_array_new ();
+
+  for (iter = priv->argv.head; iter != NULL; iter = iter->next)
+    {
+      const gchar *param = iter->data;
+
+      g_ptr_array_add (ar, g_strdup (param));
+    }
+
+  g_ptr_array_add (ar, NULL);
+
+  return (gchar **)g_ptr_array_free (ar, FALSE);
+}
+
+static void
+ide_runner_collect_addins_cb (PeasExtensionSet *set,
+                              PeasPluginInfo   *plugin_info,
+                              PeasExtension    *exten,
+                              gpointer          user_data)
+{
+  GSList **list = user_data;
+
+  *list = g_slist_prepend (*list, exten);
+}
+
+static void
+ide_runner_collect_addins (IdeRunner  *self,
+                           GSList    **list)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (list != NULL);
+
+  peas_extension_set_foreach (priv->addins,
+                              ide_runner_collect_addins_cb,
+                              list);
+}
+
+static void
+ide_runner_posthook_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!ide_runner_addin_posthook_finish (addin, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_runner_tick_posthook (task);
+}
+
+static void
+ide_runner_tick_posthook (GTask *task)
+{
+  IdeRunnerRunState *state;
+
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+
+  if (state->posthook_queue != NULL)
+    {
+      g_autoptr(IdeRunnerAddin) addin = NULL;
+
+      addin = pop_runner_addin (&state->posthook_queue);
+      ide_runner_addin_posthook_async (addin,
+                                       g_task_get_cancellable (task),
+                                       ide_runner_posthook_cb,
+                                       g_object_ref (task));
+      return;
+    }
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_runner_run_cb (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  IdeRunner *self = (IdeRunner *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!IDE_RUNNER_GET_CLASS (self)->run_finish (self, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_runner_tick_posthook (task);
+}
+
+static void
+ide_runner_tick_run (GTask *task)
+{
+  IdeRunner *self;
+
+  g_assert (G_IS_TASK (task));
+
+  self = g_task_get_source_object (task);
+
+  IDE_RUNNER_GET_CLASS (self)->run_async (self,
+                                          g_task_get_cancellable (task),
+                                          ide_runner_run_cb,
+                                          g_object_ref (task));
+}
+
+static void
+ide_runner_prehook_cb (GObject      *object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!ide_runner_addin_prehook_finish (addin, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_runner_tick_prehook (task);
+}
+
+static void
+ide_runner_tick_prehook (GTask *task)
+{
+  IdeRunnerRunState *state;
+
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+
+  if (state->prehook_queue != NULL)
+    {
+      g_autoptr(IdeRunnerAddin) addin = NULL;
+
+      addin = pop_runner_addin (&state->prehook_queue);
+      ide_runner_addin_prehook_async (addin,
+                                      g_task_get_cancellable (task),
+                                      ide_runner_prehook_cb,
+                                      g_object_ref (task));
+      return;
+    }
+
+  ide_runner_tick_run (task);
+}
+
+void
+ide_runner_run_async (IdeRunner           *self,
+                      GCancellable        *cancellable,
+                      GAsyncReadyCallback  callback,
+                      gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  IdeRunnerRunState *state;
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_runner_run_async);
+  g_task_set_check_cancellable (task, FALSE);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+
+  /*
+   * We need to run the prehook functions for each addin first before we
+   * can call our IdeRunnerClass.run vfunc.  Since these are async, we
+   * have to bring some state along with us.
+   */
+  state = g_slice_new0 (IdeRunnerRunState);
+  ide_runner_collect_addins (self, &state->prehook_queue);
+  ide_runner_collect_addins (self, &state->posthook_queue);
+  g_task_set_task_data (task, state, ide_runner_run_state_free);
+
+  ide_runner_tick_prehook (task);
+}
+
+gboolean
+ide_runner_run_finish (IdeRunner     *self,
+                       GAsyncResult  *result,
+                       GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+ide_runner_append_argv (IdeRunner   *self,
+                        const gchar *param)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+  g_return_if_fail (param != NULL);
+
+  g_queue_push_tail (&priv->argv, g_strdup (param));
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
+}
+
+void
+ide_runner_prepend_argv (IdeRunner   *self,
+                         const gchar *param)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+  g_return_if_fail (param != NULL);
+
+  g_queue_push_head (&priv->argv, g_strdup (param));
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
+}
+
+IdeRunner *
+ide_runner_new (IdeContext *context)
+{
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+  return g_object_new (IDE_TYPE_RUNNER,
+                       "context", context,
+                       NULL);
+}
diff --git a/libide/runner/ide-runner.h b/libide/runner/ide-runner.h
index 94f2c12..39ac41f 100644
--- a/libide/runner/ide-runner.h
+++ b/libide/runner/ide-runner.h
@@ -19,18 +19,11 @@
 #ifndef IDE_RUNNER_H
 #define IDE_RUNNER_H
 
-#include "ide-types.h"
+#include <gio/gio.h>
 
-G_BEGIN_DECLS
+#include "ide-object.h"
 
-typedef enum
-{
-  IDE_RUNNER_INVALID,
-  IDE_RUNNER_READY,
-  IDE_RUNNER_RUNNING,
-  IDE_RUNNER_EXITED,
-  IDE_RUNNER_FAILED,
-} IdeRunnerState;
+G_BEGIN_DECLS
 
 #define IDE_TYPE_RUNNER (ide_runner_get_type())
 
@@ -38,15 +31,40 @@ G_DECLARE_DERIVABLE_TYPE (IdeRunner, ide_runner, IDE, RUNNER, IdeObject)
 
 struct _IdeRunnerClass
 {
-  IdeObject parent_instance;
+  IdeObjectClass parent;
 
-  void (*force_quit) (IdeRunner *self);
-  void (*run)        (IdeRunner *self);
+  void           (*force_quit) (IdeRunner            *self);
+  GInputStream  *(*get_stdin)  (IdeRunner            *self);
+  GOutputStream *(*get_stdout) (IdeRunner            *self);
+  GOutputStream *(*get_stderr) (IdeRunner            *self);
+  void           (*run_async)  (IdeRunner            *self,
+                                GCancellable         *cancellable,
+                                GAsyncReadyCallback   callback,
+                                gpointer              user_data);
+  gboolean       (*run_finish) (IdeRunner            *self,
+                                GAsyncResult         *result,
+                                GError              **error);
 };
 
-IdeRunner *ide_runner_new        (IdeContext *context);
-void       ide_runner_force_quit (IdeRunner  *self);
-void       ide_runner_run        (IdeRunner  *self);
+IdeRunner      *ide_runner_new          (IdeContext           *context);
+void            ide_runner_force_quit   (IdeRunner            *self);
+void            ide_runner_run_async    (IdeRunner            *self,
+                                         GCancellable         *cancellable,
+                                         GAsyncReadyCallback   callback,
+                                         gpointer              user_data);
+gboolean        ide_runner_run_finish   (IdeRunner            *self,
+                                         GAsyncResult         *result,
+                                         GError              **error);
+void            ide_runner_prepend_argv (IdeRunner            *self,
+                                         const gchar          *param);
+void            ide_runner_append_argv  (IdeRunner            *self,
+                                         const gchar          *param);
+gchar         **ide_runner_get_argv     (IdeRunner            *self);
+void            ide_runner_set_argv     (IdeRunner            *self,
+                                         const gchar * const  *argv);
+GInputStream   *ide_runner_get_stdin    (IdeRunner            *self);
+GOutputStream  *ide_runner_get_stdout   (IdeRunner            *self);
+GOutputStream  *ide_runner_get_stderr   (IdeRunner            *self);
 
 G_END_DECLS
 
diff --git a/libide/runtimes/ide-runtime.c b/libide/runtimes/ide-runtime.c
index e0fc95e..b9fff8e 100644
--- a/libide/runtimes/ide-runtime.c
+++ b/libide/runtimes/ide-runtime.c
@@ -169,12 +169,48 @@ ide_runtime_real_prepare_configuration (IdeRuntime       *self,
   ide_configuration_set_prefix (configuration, install_path);
 }
 
+static IdeRunner *
+ide_runtime_real_create_runner (IdeRuntime     *self,
+                                IdeBuildTarget *build_target)
+{
+  g_autofree gchar *name = NULL;
+  g_autofree gchar *binpath = NULL;
+  g_autoptr(GFile) installdir = NULL;
+  g_autoptr(GFile) bin = NULL;
+  IdeContext *context;
+  IdeRunner *runner;
+
+  g_assert (IDE_IS_RUNTIME (self));
+  g_assert (IDE_IS_BUILD_TARGET (build_target));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  g_assert (IDE_IS_CONTEXT (context));
+
+  runner = ide_runner_new (context);
+
+  g_assert (IDE_IS_RUNNER (runner));
+
+  g_object_get (build_target,
+                "install-directory", &installdir,
+                "name", &name,
+                NULL);
+
+  bin = g_file_get_child (installdir, name);
+  binpath = g_file_get_path (bin);
+
+  ide_runner_append_argv (runner, binpath);
+
+  return runner;
+}
+
 static void
 ide_runtime_finalize (GObject *object)
 {
   IdeRuntime *self = (IdeRuntime *)object;
   IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
 
+  g_clear_pointer (&priv->id, g_free);
   g_clear_pointer (&priv->display_name, g_free);
 
   G_OBJECT_CLASS (ide_runtime_parent_class)->finalize (object);
@@ -240,6 +276,7 @@ ide_runtime_class_init (IdeRuntimeClass *klass)
   klass->postbuild_async = ide_runtime_real_postbuild_async;
   klass->postbuild_finish = ide_runtime_real_postbuild_finish;
   klass->create_launcher = ide_runtime_real_create_launcher;
+  klass->create_runner = ide_runtime_real_create_runner;
   klass->contains_program_in_path = ide_runtime_real_contains_program_in_path;
   klass->prepare_configuration = ide_runtime_real_prepare_configuration;
 
@@ -324,6 +361,10 @@ ide_runtime_new (IdeContext  *context,
                  const gchar *id,
                  const gchar *display_name)
 {
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+  g_return_val_if_fail (id != NULL, NULL);
+  g_return_val_if_fail (display_name != NULL, NULL);
+
   return g_object_new (IDE_TYPE_RUNTIME,
                        "context", context,
                        "id", id,
@@ -408,6 +449,26 @@ ide_runtime_prepare_configuration (IdeRuntime       *self,
   IDE_RUNTIME_GET_CLASS (self)->prepare_configuration (self, configuration);
 }
 
+/**
+ * ide_runtime_create_runner:
+ *
+ * Creates a new runner that can be used to execute the build target within
+ * the runtime. This should be used to implement such features as "run target"
+ * or "run unit test" inside the target runtime.
+ *
+ * Returns: (transfer full) (nullable): An #IdeRunner if successful, otherwise
+ *   %NULL and @error is set.
+ */
+IdeRunner *
+ide_runtime_create_runner (IdeRuntime     *self,
+                           IdeBuildTarget *build_target)
+{
+  g_return_val_if_fail (IDE_IS_RUNTIME (self), NULL);
+  g_return_val_if_fail (IDE_IS_BUILD_TARGET (build_target), NULL);
+
+  return IDE_RUNTIME_GET_CLASS (self)->create_runner (self, build_target);
+}
+
 GQuark
 ide_runtime_error_quark (void)
 {
diff --git a/libide/runtimes/ide-runtime.h b/libide/runtimes/ide-runtime.h
index 09196c9..cccdc3a 100644
--- a/libide/runtimes/ide-runtime.h
+++ b/libide/runtimes/ide-runtime.h
@@ -23,6 +23,8 @@
 
 #include "ide-object.h"
 
+#include "buildsystem/ide-build-target.h"
+#include "runner/ide-runner.h"
 #include "workers/ide-subprocess-launcher.h"
 
 G_BEGIN_DECLS
@@ -62,8 +64,28 @@ struct _IdeRuntimeClass
                                                       GError              **error);
   void                   (*prepare_configuration)    (IdeRuntime           *self,
                                                       IdeConfiguration     *configuration);
+  IdeRunner             *(*create_runner)            (IdeRuntime           *self,
+                                                      IdeBuildTarget       *build_target);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+  gpointer _reserved9;
+  gpointer _reserved10;
+  gpointer _reserved11;
+  gpointer _reserved12;
+  gpointer _reserved13;
+  gpointer _reserved14;
+  gpointer _reserved15;
+  gpointer _reserved16;
 };
 
+GQuark                 ide_runtime_error_quark              (void) G_GNUC_CONST;
 void                   ide_runtime_prebuild_async           (IdeRuntime           *self,
                                                              GCancellable         *cancellable,
                                                              GAsyncReadyCallback   callback,
@@ -83,6 +105,8 @@ gboolean               ide_runtime_contains_program_in_path (IdeRuntime
                                                              GCancellable         *cancellable);
 IdeSubprocessLauncher *ide_runtime_create_launcher          (IdeRuntime           *self,
                                                              GError              **error);
+IdeRunner             *ide_runtime_create_runner            (IdeRuntime           *self,
+                                                             IdeBuildTarget       *build_target);
 void                   ide_runtime_prepare_configuration    (IdeRuntime           *self,
                                                              IdeConfiguration     *configuration);
 IdeRuntime            *ide_runtime_new                      (IdeContext           *context,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]