[gnome-builder/wip/chergert/flatpak-breakout] sandboxing: add subprocess abstraction



commit da45d952e0ce9b32c76d951bfd71328284664af9
Author: Christian Hergert <chergert redhat com>
Date:   Tue Sep 13 00:46:14 2016 -0700

    sandboxing: add subprocess abstraction
    
    To support Building applications while running from inside the sandbox, we
    need to allow breaking out of the sandbox for such tooling. For example,
    we want to run the build tooling for flatpaks from the host system (since
    we wont have sufficient privileges inside the sandbox).
    
    This abstracts our use of GSubprocess so that we can have multiple
    implementations. Sadly, since GSubprocess is not an interface, we cannot
    simply create an alternate implementation.
    
    There are two implementations of the abstraction. IdeSimpleSubprocess is
    just a wrapper around GSubprocess (since our API is almost identical).
    Additionally, we have IdeHostProcess which is a process that runs within
    the host system, via the --allow=devel support in flatpak. Also required
    --talk-name=org.freedesktop.Flatpak.

 libide/Makefile.am                                 |   11 +-
 libide/buildsystem/ide-build-command.c             |   31 +-
 libide/buildsystem/ide-build-manager.c             |    1 +
 libide/buildsystem/ide-build-result.c              |    9 +-
 libide/buildsystem/ide-build-result.h              |    2 +-
 libide/buildsystem/ide-configuration-manager.c     |    4 +
 libide/ide-types.h                                 |    3 +
 libide/ide.h                                       |    3 +-
 libide/runner/ide-runner.c                         |   89 +-
 libide/runner/ide-runner.h                         |   45 +-
 libide/runtimes/ide-runtime.h                      |    2 +-
 .../subprocess/ide-breakout-subprocess-private.h   |   36 +
 libide/subprocess/ide-breakout-subprocess.c        | 1574 ++++++++++++++++++++
 libide/subprocess/ide-breakout-subprocess.h        |   29 +
 libide/subprocess/ide-simple-subprocess.c          |  310 ++++
 libide/subprocess/ide-simple-subprocess.h          |   34 +
 .../ide-subprocess-launcher.c                      |  269 +++-
 .../ide-subprocess-launcher.h                      |   39 +-
 libide/subprocess/ide-subprocess.c                 |  328 ++++
 libide/subprocess/ide-subprocess.h                 |  141 ++
 plugins/autotools/ide-autotools-build-system.c     |   19 +-
 plugins/autotools/ide-autotools-build-task.c       |   68 +-
 plugins/autotools/ide-makecache.c                  |    4 +-
 plugins/flatpak/gbp-flatpak-runtime.c              |   42 +-
 plugins/jhbuild/jhbuild_plugin.py                  |   16 +
 25 files changed, 2956 insertions(+), 153 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index dddce43..cc1ef10 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -124,6 +124,8 @@ libide_1_0_la_public_headers =                            \
        sourceview/ide-source-style-scheme.h              \
        sourceview/ide-source-view-mode.h                 \
        sourceview/ide-source-view.h                      \
+       subprocess/ide-subprocess.h                       \
+       subprocess/ide-subprocess-launcher.h              \
        symbols/ide-symbol-node.h                         \
        symbols/ide-symbol-resolver.h                     \
        symbols/ide-symbol-tree.h                         \
@@ -175,7 +177,6 @@ libide_1_0_la_public_headers =                            \
        workbench/ide-workbench-addin.h                   \
        workbench/ide-workbench-header-bar.h              \
        workbench/ide-workbench.h                         \
-       workers/ide-subprocess-launcher.h                 \
        workers/ide-worker.h                              \
        $(NULL)
 
@@ -287,6 +288,8 @@ libide_1_0_la_public_sources =                            \
        sourceview/ide-source-style-scheme.c              \
        sourceview/ide-source-view-mode.c                 \
        sourceview/ide-source-view.c                      \
+       subprocess/ide-subprocess.c                       \
+       subprocess/ide-subprocess-launcher.c              \
        symbols/ide-symbol-node.c                         \
        symbols/ide-symbol-resolver.c                     \
        symbols/ide-symbol-tree.c                         \
@@ -326,7 +329,6 @@ libide_1_0_la_public_sources =                            \
        workbench/ide-workbench-header-bar.c              \
        workbench/ide-workbench-open.c                    \
        workbench/ide-workbench.c                         \
-       workers/ide-subprocess-launcher.c                 \
        workers/ide-worker.c                              \
        $(NULL)
 
@@ -416,6 +418,11 @@ libide_1_0_la_SOURCES =                                   \
        sourceview/ide-text-iter.h                        \
        sourceview/ide-text-util.c                        \
        sourceview/ide-text-util.h                        \
+       subprocess/ide-breakout-subprocess.c              \
+       subprocess/ide-breakout-subprocess.h              \
+       subprocess/ide-breakout-subprocess-private.h      \
+       subprocess/ide-simple-subprocess.c                \
+       subprocess/ide-simple-subprocess.h                \
        theatrics/ide-box-theatric.c                      \
        theatrics/ide-box-theatric.h                      \
        theming/ide-css-provider.c                        \
diff --git a/libide/buildsystem/ide-build-command.c b/libide/buildsystem/ide-build-command.c
index 3aa69e3..300133c 100644
--- a/libide/buildsystem/ide-build-command.c
+++ b/libide/buildsystem/ide-build-command.c
@@ -24,7 +24,8 @@
 #include "buildsystem/ide-build-result.h"
 #include "buildsystem/ide-environment.h"
 #include "runtimes/ide-runtime.h"
-#include "workers/ide-subprocess-launcher.h"
+#include "subprocess/ide-subprocess.h"
+#include "subprocess/ide-subprocess-launcher.h"
 
 typedef struct
 {
@@ -46,17 +47,17 @@ ide_build_command_wait_cb (GObject      *object,
                            GAsyncResult *result,
                            gpointer      user_data)
 {
-  GSubprocess *subprocess = (GSubprocess *)object;
+  IdeSubprocess *subprocess = (IdeSubprocess *)object;
   g_autoptr(GTask) task = user_data;
   GError *error = NULL;
 
   IDE_ENTRY;
 
-  g_assert (G_IS_SUBPROCESS (subprocess));
+  g_assert (IDE_IS_SUBPROCESS (subprocess));
   g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (G_IS_TASK (task));
 
-  if (!g_subprocess_wait_finish (subprocess, result, &error))
+  if (!ide_subprocess_wait_finish (subprocess, result, &error))
     {
       g_task_return_error (task, error);
       IDE_EXIT;
@@ -121,7 +122,7 @@ ide_build_command_real_run (IdeBuildCommand  *self,
 {
   IdeBuildCommandPrivate *priv = ide_build_command_get_instance_private (self);
   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
   gboolean ret;
 
   IDE_ENTRY;
@@ -147,7 +148,7 @@ ide_build_command_real_run (IdeBuildCommand  *self,
 
   ide_build_result_log_subprocess (build_result, subprocess);
 
-  ret = g_subprocess_wait (subprocess, cancellable, error);
+  ret = ide_subprocess_wait (subprocess, cancellable, error);
 
   IDE_RETURN (ret);
 }
@@ -163,7 +164,7 @@ ide_build_command_real_run_async (IdeBuildCommand     *self,
 {
   IdeBuildCommandPrivate *priv = ide_build_command_get_instance_private (self);
   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
   g_autoptr(GTask) task = NULL;
   GError *error = NULL;
 
@@ -201,10 +202,10 @@ ide_build_command_real_run_async (IdeBuildCommand     *self,
 
   ide_build_result_log_subprocess (build_result, subprocess);
 
-  g_subprocess_wait_async (subprocess,
-                           cancellable,
-                           ide_build_command_wait_cb,
-                           g_steal_pointer (&task));
+  ide_subprocess_wait_async (subprocess,
+                             cancellable,
+                             ide_build_command_wait_cb,
+                             g_steal_pointer (&task));
 
   IDE_EXIT;
 }
@@ -214,10 +215,16 @@ ide_build_command_real_run_finish (IdeBuildCommand  *self,
                                    GAsyncResult     *result,
                                    GError          **error)
 {
+  gboolean ret;
+
+  IDE_ENTRY;
+
   g_assert (IDE_IS_BUILD_COMMAND (self));
   g_assert (G_IS_TASK (result));
 
-  return g_task_propagate_boolean (G_TASK (result), error);
+  ret = g_task_propagate_boolean (G_TASK (result), error);
+
+  IDE_RETURN (ret);
 }
 
 static IdeBuildCommand *
diff --git a/libide/buildsystem/ide-build-manager.c b/libide/buildsystem/ide-build-manager.c
index 4ce6c24..4f8e383 100644
--- a/libide/buildsystem/ide-build-manager.c
+++ b/libide/buildsystem/ide-build-manager.c
@@ -530,6 +530,7 @@ ide_build_manager_build_cb (GObject      *object,
 
   if (build_result == NULL)
     {
+      IDE_TRACE_MSG ("%s", error->message);
       g_task_return_error (task, error);
       IDE_GOTO (failure);
     }
diff --git a/libide/buildsystem/ide-build-result.c b/libide/buildsystem/ide-build-result.c
index 6e7045e..f37139e 100644
--- a/libide/buildsystem/ide-build-result.c
+++ b/libide/buildsystem/ide-build-result.c
@@ -29,6 +29,7 @@
 #include "buildsystem/ide-build-result-addin.h"
 #include "diagnostics/ide-source-location.h"
 #include "files/ide-file.h"
+#include "subprocess/ide-subprocess.h"
 
 #define POINTER_MARK(p)   GSIZE_TO_POINTER(GPOINTER_TO_SIZE(p)|1)
 #define POINTER_UNMARK(p) GSIZE_TO_POINTER(GPOINTER_TO_SIZE(p)&~(gsize)1)
@@ -351,27 +352,27 @@ ide_build_result_tail_into (IdeBuildResult    *self,
 
 void
 ide_build_result_log_subprocess (IdeBuildResult *self,
-                                 GSubprocess    *subprocess)
+                                 IdeSubprocess  *subprocess)
 {
   IdeBuildResultPrivate *priv = ide_build_result_get_instance_private (self);
   GInputStream *stdout_stream;
   GInputStream *stderr_stream;
 
   g_return_if_fail (IDE_IS_BUILD_RESULT (self));
-  g_return_if_fail (G_IS_SUBPROCESS (subprocess));
+  g_return_if_fail (IDE_IS_SUBPROCESS (subprocess));
 
   /* ensure lazily created streams are available */
   (void)ide_build_result_get_stderr_stream (self);
   (void)ide_build_result_get_stdout_stream (self);
 
-  stderr_stream = g_subprocess_get_stderr_pipe (subprocess);
+  stderr_stream = ide_subprocess_get_stderr_pipe (subprocess);
   if (stderr_stream)
     ide_build_result_tail_into (self,
                                 IDE_BUILD_RESULT_LOG_STDERR,
                                 stderr_stream,
                                 priv->stderr_writer);
 
-  stdout_stream = g_subprocess_get_stdout_pipe (subprocess);
+  stdout_stream = ide_subprocess_get_stdout_pipe (subprocess);
   if (stdout_stream)
     ide_build_result_tail_into (self,
                                 IDE_BUILD_RESULT_LOG_STDOUT,
diff --git a/libide/buildsystem/ide-build-result.h b/libide/buildsystem/ide-build-result.h
index 26b802f..6df1e7e 100644
--- a/libide/buildsystem/ide-build-result.h
+++ b/libide/buildsystem/ide-build-result.h
@@ -60,7 +60,7 @@ struct _IdeBuildResultClass
 GInputStream  *ide_build_result_get_stdout_stream (IdeBuildResult *result);
 GInputStream  *ide_build_result_get_stderr_stream (IdeBuildResult *result);
 void           ide_build_result_log_subprocess    (IdeBuildResult *result,
-                                                   GSubprocess    *subprocess);
+                                                   IdeSubprocess  *subprocess);
 GTimeSpan      ide_build_result_get_running_time  (IdeBuildResult *self);
 gboolean       ide_build_result_get_running       (IdeBuildResult *self);
 void           ide_build_result_set_running       (IdeBuildResult *self,
diff --git a/libide/buildsystem/ide-configuration-manager.c b/libide/buildsystem/ide-configuration-manager.c
index 0aaa4ab..bdcd9b4 100644
--- a/libide/buildsystem/ide-configuration-manager.c
+++ b/libide/buildsystem/ide-configuration-manager.c
@@ -457,12 +457,16 @@ ide_configuration_manager_queue_writeback (IdeConfigurationManager *self)
 {
   g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
 
+  IDE_ENTRY;
+
   if (self->writeback_handler != 0)
     g_source_remove (self->writeback_handler);
 
   self->writeback_handler = g_timeout_add_seconds (WRITEBACK_TIMEOUT_SECS,
                                                    ide_configuration_manager_do_writeback,
                                                    self);
+
+  IDE_EXIT;
 }
 
 static void
diff --git a/libide/ide-types.h b/libide/ide-types.h
index c5afe50..b4aee57 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -122,6 +122,9 @@ typedef struct _IdeSourceSnippets              IdeSourceSnippets;
 
 typedef struct _IdeSourceSnippetsManager       IdeSourceSnippetsManager;
 
+typedef struct _IdeSubprocess                  IdeSubprocess;
+typedef struct _IdeSubprocessLauncher          IdeSubprocessLauncher;
+
 typedef struct _IdeSymbol                      IdeSymbol;
 
 typedef struct _IdeSymbolResolver              IdeSymbolResolver;
diff --git a/libide/ide.h b/libide/ide.h
index 20258c0..1b93c8e 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -115,6 +115,8 @@ G_BEGIN_DECLS
 #include "sourceview/ide-source-map.h"
 #include "sourceview/ide-source-style-scheme.h"
 #include "sourceview/ide-source-view.h"
+#include "subprocess/ide-subprocess.h"
+#include "subprocess/ide-subprocess-launcher.h"
 #include "symbols/ide-symbol-resolver.h"
 #include "symbols/ide-symbol.h"
 #include "symbols/ide-tags-builder.h"
@@ -148,7 +150,6 @@ G_BEGIN_DECLS
 #include "workbench/ide-workbench-addin.h"
 #include "workbench/ide-workbench-header-bar.h"
 #include "workbench/ide-workbench.h"
-#include "workers/ide-subprocess-launcher.h"
 
 #undef IDE_INSIDE
 
diff --git a/libide/runner/ide-runner.c b/libide/runner/ide-runner.c
index d64dec5..9b9fc7c 100644
--- a/libide/runner/ide-runner.c
+++ b/libide/runner/ide-runner.c
@@ -27,13 +27,16 @@
 
 #include "runner/ide-runner.h"
 #include "runner/ide-runner-addin.h"
-#include "workers/ide-subprocess-launcher.h"
+#include "subprocess/ide-subprocess.h"
+#include "subprocess/ide-subprocess-launcher.h"
 
 typedef struct
 {
   PeasExtensionSet *addins;
   GQueue argv;
   IdeEnvironment *env;
+  GSubprocessFlags flags;
+  guint run_on_host : 1;
 } IdeRunnerPrivate;
 
 typedef struct
@@ -46,6 +49,7 @@ enum {
   PROP_0,
   PROP_ARGV,
   PROP_ENV,
+  PROP_RUN_ON_HOST,
   N_PROPS
 };
 
@@ -98,14 +102,16 @@ ide_runner_run_wait_cb (GObject      *object,
                         GAsyncResult *result,
                         gpointer      user_data)
 {
-  GSubprocess *subprocess = (GSubprocess *)object;
+  IdeSubprocess *subprocess = (IdeSubprocess *)object;
   g_autoptr(GTask) task = user_data;
   GError *error = NULL;
   IdeRunner *self;
 
   IDE_ENTRY;
 
-  g_assert (G_IS_SUBPROCESS (subprocess));
+  g_assert (IDE_IS_SUBPROCESS (subprocess));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
 
   self = g_task_get_source_object (task);
 
@@ -113,17 +119,17 @@ ide_runner_run_wait_cb (GObject      *object,
 
   g_signal_emit (self, signals [EXITED], 0);
 
-  if (!g_subprocess_wait_finish (subprocess, result, &error))
+  if (!ide_subprocess_wait_finish (subprocess, result, &error))
     {
       g_task_return_error (task, error);
       IDE_EXIT;
     }
 
-  if (g_subprocess_get_if_exited (subprocess))
+  if (ide_subprocess_get_if_exited (subprocess))
     {
       gint exit_code;
 
-      exit_code = g_subprocess_get_exit_status (subprocess);
+      exit_code = ide_subprocess_get_exit_status (subprocess);
 
       if (exit_code == EXIT_SUCCESS)
         {
@@ -150,7 +156,7 @@ ide_runner_real_run_async (IdeRunner           *self,
   IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
   g_autoptr(GTask) task = NULL;
   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
   g_auto(GStrv) environ = NULL;
   const gchar *identifier;
   GError *error = NULL;
@@ -163,7 +169,10 @@ ide_runner_real_run_async (IdeRunner           *self,
   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);
+  launcher = ide_subprocess_launcher_new (priv->flags);
+
+  ide_subprocess_launcher_set_run_on_host (launcher, priv->run_on_host);
+  ide_subprocess_launcher_set_clear_env (launcher, FALSE);
 
   environ = g_get_environ (); /* We still rely on many system vars like DISPLAY */
   ide_subprocess_launcher_set_environ (launcher, (const gchar * const *)environ);
@@ -176,7 +185,7 @@ ide_runner_real_run_async (IdeRunner           *self,
 
   subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, &error);
 
-  g_assert (subprocess == NULL || G_IS_SUBPROCESS (subprocess));
+  g_assert (subprocess == NULL || IDE_IS_SUBPROCESS (subprocess));
 
   if (subprocess == NULL)
     {
@@ -184,14 +193,14 @@ ide_runner_real_run_async (IdeRunner           *self,
       IDE_GOTO (failure);
     }
 
-  identifier = g_subprocess_get_identifier (subprocess);
+  identifier = ide_subprocess_get_identifier (subprocess);
 
   g_signal_emit (self, signals [SPAWNED], 0, identifier);
 
-  g_subprocess_wait_async (subprocess,
-                           cancellable,
-                           ide_runner_run_wait_cb,
-                           g_steal_pointer (&task));
+  ide_subprocess_wait_async (subprocess,
+                             cancellable,
+                             ide_runner_run_wait_cb,
+                             g_steal_pointer (&task));
 
 failure:
   IDE_EXIT;
@@ -302,6 +311,10 @@ ide_runner_get_property (GObject    *object,
       g_value_set_object (value, ide_runner_get_environment (self));
       break;
 
+    case PROP_RUN_ON_HOST:
+      g_value_set_boolean (value, ide_runner_get_run_on_host (self));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -321,6 +334,10 @@ ide_runner_set_property (GObject      *object,
       ide_runner_set_argv (self, g_value_get_boxed (value));
       break;
 
+    case PROP_RUN_ON_HOST:
+      ide_runner_set_run_on_host (self, g_value_get_boolean (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -353,6 +370,13 @@ ide_runner_class_init (IdeRunnerClass *klass)
                          IDE_TYPE_ENVIRONMENT,
                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
+  properties [PROP_RUN_ON_HOST] =
+    g_param_spec_boolean ("run-on-host",
+                          "Run on Host",
+                          "Run on Host",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_properties (object_class, N_PROPS, properties);
 
   signals [EXITED] =
@@ -386,6 +410,7 @@ ide_runner_init (IdeRunner *self)
 
   g_queue_init (&priv->argv);
   priv->env = ide_environment_new ();
+  priv->flags = 0;
 }
 
 /**
@@ -728,3 +753,39 @@ ide_runner_new (IdeContext *context)
                        "context", context,
                        NULL);
 }
+
+gboolean
+ide_runner_get_run_on_host (IdeRunner *self)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
+
+  return priv->run_on_host;
+}
+
+void
+ide_runner_set_run_on_host (IdeRunner *self,
+                            gboolean   run_on_host)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  run_on_host = !!run_on_host;
+
+  if (run_on_host != priv->run_on_host)
+    {
+      priv->run_on_host = run_on_host;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUN_ON_HOST]);
+    }
+}
+
+void
+ide_runner_set_flags (IdeRunner        *self,
+                      GSubprocessFlags  flags)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  priv->flags = flags;
+}
diff --git a/libide/runner/ide-runner.h b/libide/runner/ide-runner.h
index 74f4cd3..19444d6 100644
--- a/libide/runner/ide-runner.h
+++ b/libide/runner/ide-runner.h
@@ -47,26 +47,31 @@ struct _IdeRunnerClass
                                 GError              **error);
 };
 
-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);
-IdeEnvironment *ide_runner_get_environment(IdeRunner          *self);
-GInputStream   *ide_runner_get_stdin    (IdeRunner            *self);
-GOutputStream  *ide_runner_get_stdout   (IdeRunner            *self);
-GOutputStream  *ide_runner_get_stderr   (IdeRunner            *self);
+IdeRunner      *ide_runner_new             (IdeContext           *context);
+void            ide_runner_force_quit      (IdeRunner            *self);
+IdeEnvironment *ide_runner_get_environment (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_set_flags       (IdeRunner            *self,
+                                            GSubprocessFlags      flags);
+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);
+gboolean        ide_runner_get_run_on_host (IdeRunner            *self);
+void            ide_runner_set_run_on_host (IdeRunner            *self,
+                                            gboolean              run_on_host);
 
 G_END_DECLS
 
diff --git a/libide/runtimes/ide-runtime.h b/libide/runtimes/ide-runtime.h
index cccdc3a..28f3931 100644
--- a/libide/runtimes/ide-runtime.h
+++ b/libide/runtimes/ide-runtime.h
@@ -25,7 +25,7 @@
 
 #include "buildsystem/ide-build-target.h"
 #include "runner/ide-runner.h"
-#include "workers/ide-subprocess-launcher.h"
+#include "subprocess/ide-subprocess-launcher.h"
 
 G_BEGIN_DECLS
 
diff --git a/libide/subprocess/ide-breakout-subprocess-private.h 
b/libide/subprocess/ide-breakout-subprocess-private.h
new file mode 100644
index 0000000..301e66b
--- /dev/null
+++ b/libide/subprocess/ide-breakout-subprocess-private.h
@@ -0,0 +1,36 @@
+/* ide-breakout-subprocess-private.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_BREAKOUT_SUBPROCESS_PRIVATE_H
+#define IDE_BREAKOUT_SUBPROCESS_PRIVATE_H
+
+#include "subprocess/ide-breakout-subprocess.h"
+
+G_BEGIN_DECLS
+
+IdeSubprocess *_ide_breakout_subprocess_new (const gchar          *cwd,
+                                             const gchar * const  *argv,
+                                             const gchar * const  *env,
+                                             GSubprocessFlags      flags,
+                                             gboolean              clear_flags,
+                                             GCancellable         *cancellable,
+                                             GError              **error) G_GNUC_INTERNAL;
+
+G_END_DECLS
+
+#endif /* IDE_BREAKOUT_SUBPROCESS_PRIVATE_H */
diff --git a/libide/subprocess/ide-breakout-subprocess.c b/libide/subprocess/ide-breakout-subprocess.c
new file mode 100644
index 0000000..cc28ccb
--- /dev/null
+++ b/libide/subprocess/ide-breakout-subprocess.c
@@ -0,0 +1,1574 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2012, 2013, 2016 Red Hat, Inc.
+ * Copyright © 2012, 2013 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: Colin Walters <walters verbum org>
+ *          Ryan Lortie <desrt desrt ca>
+ *          Alexander Larsson <alexl redhat com>
+ *          Christian Hergert <chergert redhat com>
+ */
+
+#define G_LOG_DOMAIN "ide-breakout-subprocess"
+
+#include <egg-counter.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <gio/gunixfdlist.h>
+#include <glib-unix.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "ide-debug.h"
+#include "ide-macros.h"
+
+#include "subprocess/ide-breakout-subprocess.h"
+
+#ifndef FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV
+# define FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV (1 << 0)
+#endif
+
+EGG_DEFINE_COUNTER (instances, "Subprocess", "HostCommand Instances", "Number of IdeBreakoutSubprocess 
instances")
+
+struct _IdeBreakoutSubprocess
+{
+  GObject parent_instance;
+
+  GDBusConnection *connection;
+
+  GPid client_pid;
+  gint status;
+
+  GSubprocessFlags flags;
+
+  gchar **argv;
+  gchar **env;
+  gchar *cwd;
+
+  gchar *identifier;
+
+  GOutputStream *stdin_pipe;
+  GInputStream *stdout_pipe;
+  GInputStream *stderr_pipe;
+
+  guint sigint_id;
+  guint sigterm_id;
+  guint exited_subscription;
+
+  /* GList of GTasks for wait_async() */
+  GList *waiting;
+
+  /* Mutex/Cond pair guards client_has_exited */
+  GMutex waiter_mutex;
+  GCond waiter_cond;
+
+  guint client_has_exited : 1;
+  guint clear_env : 1;
+};
+
+/* ide_subprocess_communicate implementation below:
+ *
+ * This is a tough problem.  We have to watch 5 things at the same time:
+ *
+ *  - writing to stdin made progress
+ *  - reading from stdout made progress
+ *  - reading from stderr made progress
+ *  - process terminated
+ *  - cancellable being cancelled by caller
+ *
+ * We use a GMainContext for all of these (either as async function
+ * calls or as a GSource (in the case of the cancellable).  That way at
+ * least we don't have to worry about threading.
+ *
+ * For the sync case we use the usual trick of creating a private main
+ * context and iterating it until completion.
+ *
+ * It's very possible that the process will dump a lot of data to stdout
+ * just before it quits, so we can easily have data to read from stdout
+ * and see the process has terminated at the same time.  We want to make
+ * sure that we read all of the data from the pipes first, though, so we
+ * do IO operations at a higher priority than the wait operation (which
+ * is at G_IO_PRIORITY_DEFAULT).  Even in the case that we have to do
+ * multiple reads to get this data, the pipe() will always be polling
+ * as ready and with the async result for the read at a higher priority,
+ * the main context will not dispatch the completion for the wait().
+ *
+ * We keep our own private GCancellable.  In the event that any of the
+ * above suffers from an error condition (including the user cancelling
+ * their cancellable) we immediately dispatch the GTask with the error
+ * result and fire our cancellable to cleanup any pending operations.
+ * In the case that the error is that the user's cancellable was fired,
+ * it's vaguely wasteful to report an error because GTask will handle
+ * this automatically, so we just return FALSE.
+ *
+ * We let each pending sub-operation take a ref on the GTask of the
+ * communicate operation.  We have to be careful that we don't report
+ * the task completion more than once, though, so we keep a flag for
+ * that.
+ */
+typedef struct
+{
+  const gchar *stdin_data;
+  gsize stdin_length;
+  gsize stdin_offset;
+
+  gboolean add_nul;
+
+  GInputStream *stdin_buf;
+  GMemoryOutputStream *stdout_buf;
+  GMemoryOutputStream *stderr_buf;
+
+  GCancellable *cancellable;
+  GSource      *cancellable_source;
+
+  guint         outstanding_ops;
+  gboolean      reported_error;
+} CommunicateState;
+
+enum {
+  PROP_0,
+  PROP_ARGV,
+  PROP_CWD,
+  PROP_ENV,
+  PROP_FLAGS,
+  N_PROPS
+};
+
+static void              ide_breakout_subprocess_sync_setup           (void);
+static void              ide_breakout_subprocess_sync_complete        (GAsyncResult          **result);
+static void              ide_breakout_subprocess_sync_done            (GObject                *object,
+                                                                       GAsyncResult           *result,
+                                                                       gpointer                user_data);
+static CommunicateState *ide_breakout_subprocess_communicate_internal (IdeBreakoutSubprocess  *subprocess,
+                                                                       gboolean                add_nul,
+                                                                       GBytes                 *stdin_buf,
+                                                                       GCancellable           *cancellable,
+                                                                       GAsyncReadyCallback     callback,
+                                                                       gpointer                user_data);
+
+static GParamSpec *properties [N_PROPS];
+
+static GDBusConnection *
+ide_breakout_subprocess_get_shared_connection (GCancellable  *cancellable,
+                                               GError       **error)
+{
+#if 1
+  static GDBusConnection *instance;
+
+  if (instance == NULL)
+    {
+      instance = g_dbus_connection_new_for_address_sync (g_getenv ("DBUS_SESSION_BUS_ADDRESS"),
+                                                         G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | 
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+                                                         NULL,
+                                                         cancellable,
+                                                         error);
+
+
+      if (instance != NULL)
+        {
+          g_dbus_connection_set_exit_on_close (instance, FALSE);
+          g_signal_connect_swapped (instance,
+                                    "closed",
+                                    G_CALLBACK (g_clear_object),
+                                    &instance);
+        }
+    }
+
+  return instance ? g_object_ref (instance) : NULL;
+#else
+  return g_dbus_connection_new_for_address_sync (g_getenv ("DBUS_SESSION_BUS_ADDRESS"),
+                                                 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | 
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+                                                 NULL,
+                                                 cancellable,
+                                                 error);
+#endif
+}
+
+static const gchar *
+ide_breakout_subprocess_get_identifier (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  return self->identifier;
+}
+
+static GInputStream *
+ide_breakout_subprocess_get_stdout_pipe (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  return self->stdout_pipe;
+}
+
+static GInputStream *
+ide_breakout_subprocess_get_stderr_pipe (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  return self->stderr_pipe;
+}
+
+static GOutputStream *
+ide_breakout_subprocess_get_stdin_pipe (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  return self->stdin_pipe;
+}
+
+static gboolean
+ide_breakout_subprocess_wait (IdeSubprocess  *subprocess,
+                              GCancellable   *cancellable,
+                              GError        **error)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  g_object_ref (self);
+
+  /* Completion will broadcast upon completion */
+  g_mutex_lock (&self->waiter_mutex);
+  if (!self->client_has_exited)
+    g_cond_wait (&self->waiter_cond, &self->waiter_mutex);
+  g_mutex_unlock (&self->waiter_mutex);
+
+  g_object_unref (self);
+
+  return self->client_has_exited;
+}
+
+static void
+ide_breakout_subprocess_wait_async (IdeSubprocess       *subprocess,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GMutexLocker) locker = NULL;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_breakout_subprocess_wait_async);
+
+  locker = g_mutex_locker_new (&self->waiter_mutex);
+
+  if (self->client_has_exited)
+    {
+      g_task_return_boolean (task, TRUE);
+      return;
+    }
+
+  self->waiting = g_list_append (self->waiting, g_steal_pointer (&task));
+}
+
+static gboolean
+ide_breakout_subprocess_wait_finish (IdeSubprocess  *subprocess,
+                                     GAsyncResult   *result,
+                                     GError        **error)
+{
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (subprocess));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+ide_subprocess_communicate_utf8_async (IdeSubprocess       *subprocess,
+                                       const char          *stdin_buf,
+                                       GCancellable        *cancellable,
+                                       GAsyncReadyCallback  callback,
+                                       gpointer             user_data)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+  g_autoptr(GBytes) stdin_bytes = NULL;
+  size_t stdin_buf_len = 0;
+
+  g_return_if_fail (IDE_IS_BREAKOUT_SUBPROCESS (subprocess));
+  g_return_if_fail (stdin_buf == NULL || (self->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE));
+  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+  if (stdin_buf != NULL)
+    stdin_buf_len = strlen (stdin_buf);
+  stdin_bytes = g_bytes_new (stdin_buf, stdin_buf_len);
+
+  ide_breakout_subprocess_communicate_internal (self, TRUE, stdin_bytes, cancellable, callback, user_data);
+}
+
+static gboolean
+communicate_result_validate_utf8 (const char            *stream_name,
+                                  char                 **return_location,
+                                  GMemoryOutputStream   *buffer,
+                                  GError               **error)
+{
+  if (return_location == NULL)
+    return TRUE;
+
+  if (buffer)
+    {
+      const char *end;
+      *return_location = g_memory_output_stream_steal_data (buffer);
+      if (!g_utf8_validate (*return_location, -1, &end))
+        {
+          g_free (*return_location);
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Invalid UTF-8 in child %s at offset %lu",
+                       stream_name,
+                       (unsigned long) (end - *return_location));
+          return FALSE;
+        }
+    }
+  else
+    *return_location = NULL;
+
+  return TRUE;
+}
+
+gboolean
+ide_subprocess_communicate_utf8_finish (IdeSubprocess  *subprocess,
+                                        GAsyncResult   *result,
+                                        char          **stdout_buf,
+                                        char          **stderr_buf,
+                                        GError        **error)
+{
+  gboolean ret = FALSE;
+  CommunicateState *state;
+
+  g_return_val_if_fail (IDE_IS_BREAKOUT_SUBPROCESS (subprocess), FALSE);
+  g_return_val_if_fail (g_task_is_valid (result, subprocess), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  g_object_ref (result);
+
+  state = g_task_get_task_data ((GTask*)result);
+  if (!g_task_propagate_boolean ((GTask*)result, error))
+    goto out;
+
+  if (!communicate_result_validate_utf8 ("stdout", stdout_buf, state->stdout_buf, error))
+    goto out;
+
+  if (!communicate_result_validate_utf8 ("stderr", stderr_buf, state->stderr_buf, error))
+    goto out;
+
+  ret = TRUE;
+
+ out:
+  g_object_unref (result);
+
+  return ret;
+}
+
+static gboolean
+ide_breakout_subprocess_communicate_utf8 (IdeSubprocess  *subprocess,
+                                          const char     *stdin_buf,
+                                          GCancellable   *cancellable,
+                                          char          **stdout_buf,
+                                          char          **stderr_buf,
+                                          GError        **error)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+  g_autoptr(GAsyncResult) result = NULL;
+  g_autoptr(GBytes) stdin_bytes = NULL;
+  size_t stdin_buf_len = 0;
+  gboolean success;
+
+  g_return_val_if_fail (IDE_IS_BREAKOUT_SUBPROCESS (subprocess), FALSE);
+  g_return_val_if_fail (stdin_buf == NULL || (self->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE), FALSE);
+  g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  if (stdin_buf != NULL)
+    stdin_buf_len = strlen (stdin_buf);
+  stdin_bytes = g_bytes_new (stdin_buf, stdin_buf_len);
+
+  ide_breakout_subprocess_sync_setup ();
+  ide_breakout_subprocess_communicate_internal (self,
+                                                TRUE,
+                                                stdin_bytes,
+                                                cancellable,
+                                                ide_breakout_subprocess_sync_done,
+                                                &result);
+  ide_breakout_subprocess_sync_complete (&result);
+  success = ide_subprocess_communicate_utf8_finish (subprocess, result, stdout_buf, stderr_buf, error);
+
+  return success;
+}
+
+static gboolean
+ide_breakout_subprocess_get_successful (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  return WIFEXITED (self->status) && WEXITSTATUS (self->status) == 0;
+}
+
+static gboolean
+ide_breakout_subprocess_get_if_exited (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  return WIFEXITED (self->status);
+}
+
+static gint
+ide_breakout_subprocess_get_exit_status (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (self->client_has_exited);
+
+  if (!WIFEXITED (self->status))
+    return 1;
+
+  return WEXITSTATUS (self->status);
+}
+
+static gboolean
+ide_breakout_subprocess_get_if_signaled (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (self->client_has_exited == TRUE);
+
+  return WIFSIGNALED (self->status);
+}
+
+static gint
+ide_breakout_subprocess_get_term_sig (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (self->client_has_exited == TRUE);
+
+  return WTERMSIG (self->status);
+}
+
+static gint
+ide_breakout_subprocess_get_status (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (self->client_has_exited == TRUE);
+
+  return self->status;
+}
+
+static void
+ide_breakout_subprocess_send_signal (IdeSubprocess *subprocess,
+                                     gint           signal_num)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (G_IS_DBUS_CONNECTION (self->connection));
+
+  /* Signal delivery is not guaranteed, so we can drop this on the floor. */
+  if (self->client_has_exited)
+    IDE_EXIT;
+
+  IDE_TRACE_MSG ("Sending signal %d to pid %u", signal_num, (guint)self->client_pid);
+
+  g_dbus_connection_call_sync (self->connection,
+                               "org.freedesktop.Flatpak",
+                               "/org/freedesktop/Flatpak/Development",
+                               "org.freedesktop.Flatpak.Development",
+                               "HostCommandSignal",
+                               g_variant_new ("(uub)", self->client_pid, signal_num, TRUE),
+                               NULL,
+                               G_DBUS_CALL_FLAGS_NONE, -1,
+                               NULL, NULL);
+
+  IDE_EXIT;
+}
+
+static void
+ide_breakout_subprocess_force_exit (IdeSubprocess *subprocess)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  ide_breakout_subprocess_send_signal (subprocess, SIGKILL);
+}
+
+static void
+ide_breakout_subprocess_sync_setup (void)
+{
+  /* Leak ref until ide_breakout_subprocess_sync_complete() */
+  g_main_context_push_thread_default (g_main_context_new ());
+}
+
+static void
+ide_breakout_subprocess_sync_complete (GAsyncResult **result)
+{
+  GMainContext *context = g_main_context_get_thread_default ();
+
+  IDE_ENTRY;
+
+  g_assert (result != NULL);
+  g_assert (*result == NULL || G_IS_ASYNC_RESULT (*result));
+
+  while (*result == NULL)
+    g_main_context_iteration (context, TRUE);
+
+  g_main_context_pop_thread_default (context);
+
+  /* Unref pair for ide_breakout_subprocess_sync_setup() */
+  g_main_context_unref (context);
+
+  IDE_EXIT;
+}
+
+static void
+ide_breakout_subprocess_sync_done (GObject      *object,
+                                   GAsyncResult *result,
+                                   gpointer      user_data)
+{
+  GAsyncResult **ret = user_data;
+
+  IDE_ENTRY;
+
+  g_assert (ret != NULL);
+  g_assert (*ret == NULL);
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  *ret = g_object_ref (result);
+
+  IDE_EXIT;
+}
+
+static void
+ide_subprocess_communicate_state_free (gpointer data)
+{
+  CommunicateState *state = data;
+
+  g_clear_object (&state->cancellable);
+  g_clear_object (&state->stdin_buf);
+  g_clear_object (&state->stdout_buf);
+  g_clear_object (&state->stderr_buf);
+
+  if (state->cancellable_source)
+    {
+      if (!g_source_is_destroyed (state->cancellable_source))
+        g_source_destroy (state->cancellable_source);
+      g_source_unref (state->cancellable_source);
+    }
+
+  g_slice_free (CommunicateState, state);
+}
+
+static gboolean
+ide_subprocess_communicate_cancelled (gpointer user_data)
+{
+  CommunicateState *state = user_data;
+
+  IDE_ENTRY;
+
+  g_assert (state != NULL);
+  g_assert (G_IS_CANCELLABLE (state->cancellable));
+
+  g_cancellable_cancel (state->cancellable);
+
+  IDE_RETURN (G_SOURCE_REMOVE);
+}
+
+static void
+ide_subprocess_communicate_made_progress (GObject      *source_object,
+                                          GAsyncResult *result,
+                                          gpointer      user_data)
+{
+  CommunicateState *state;
+  IdeBreakoutSubprocess *subprocess;
+  GError *error = NULL;
+  gpointer source;
+  GTask *task;
+
+  g_assert (source_object != NULL);
+
+  task = user_data;
+  subprocess = g_task_get_source_object (task);
+  state = g_task_get_task_data (task);
+  source = source_object;
+
+  state->outstanding_ops--;
+
+  if (source == subprocess->stdin_pipe ||
+      source == state->stdout_buf ||
+      source == state->stderr_buf)
+    {
+      if (g_output_stream_splice_finish ((GOutputStream*) source, result, &error) == -1)
+        goto out;
+
+      if (source == state->stdout_buf ||
+          source == state->stderr_buf)
+        {
+          /* This is a memory stream, so it can't be cancelled or return
+           * an error really.
+           */
+          if (state->add_nul)
+            {
+              gsize bytes_written;
+              if (!g_output_stream_write_all (source, "\0", 1, &bytes_written, NULL, &error))
+                goto out;
+            }
+          if (!g_output_stream_close (source, NULL, &error))
+            goto out;
+        }
+    }
+  else if (source == subprocess)
+    {
+      (void) ide_subprocess_wait_finish (IDE_SUBPROCESS (subprocess), result, &error);
+    }
+  else
+    g_assert_not_reached ();
+
+ out:
+  if (error)
+    {
+      /* Only report the first error we see.
+       *
+       * We might be seeing an error as a result of the cancellation
+       * done when the process quits.
+       */
+      if (!state->reported_error)
+        {
+          state->reported_error = TRUE;
+          g_cancellable_cancel (state->cancellable);
+          g_task_return_error (task, error);
+        }
+      else
+        g_error_free (error);
+    }
+  else if (state->outstanding_ops == 0)
+    {
+      g_task_return_boolean (task, TRUE);
+    }
+
+  /* And drop the original ref */
+  g_object_unref (task);
+}
+
+static CommunicateState *
+ide_breakout_subprocess_communicate_internal (IdeBreakoutSubprocess *subprocess,
+                                              gboolean               add_nul,
+                                              GBytes                *stdin_buf,
+                                              GCancellable          *cancellable,
+                                              GAsyncReadyCallback    callback,
+                                              gpointer               user_data)
+{
+  CommunicateState *state;
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (subprocess));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (subprocess, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_breakout_subprocess_communicate_internal);
+
+  state = g_slice_new0 (CommunicateState);
+  g_task_set_task_data (task, state, ide_subprocess_communicate_state_free);
+
+  state->cancellable = g_cancellable_new ();
+  state->add_nul = add_nul;
+
+  if (cancellable)
+    {
+      state->cancellable_source = g_cancellable_source_new (cancellable);
+      /* No ref held here, but we unref the source from state's free function */
+      g_source_set_callback (state->cancellable_source, ide_subprocess_communicate_cancelled, state, NULL);
+      g_source_attach (state->cancellable_source, g_main_context_get_thread_default ());
+    }
+
+  if (subprocess->stdin_pipe)
+    {
+      g_assert (stdin_buf != NULL);
+      state->stdin_buf = g_memory_input_stream_new_from_bytes (stdin_buf);
+      g_output_stream_splice_async (subprocess->stdin_pipe, (GInputStream*)state->stdin_buf,
+                                    G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | 
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                                    G_PRIORITY_DEFAULT, state->cancellable,
+                                    ide_subprocess_communicate_made_progress, g_object_ref (task));
+      state->outstanding_ops++;
+    }
+
+  if (subprocess->stdout_pipe)
+    {
+      state->stdout_buf = (GMemoryOutputStream*)g_memory_output_stream_new_resizable ();
+      g_output_stream_splice_async ((GOutputStream*)state->stdout_buf, subprocess->stdout_pipe,
+                                    G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
+                                    G_PRIORITY_DEFAULT, state->cancellable,
+                                    ide_subprocess_communicate_made_progress, g_object_ref (task));
+      state->outstanding_ops++;
+    }
+
+  if (subprocess->stderr_pipe)
+    {
+      state->stderr_buf = (GMemoryOutputStream*)g_memory_output_stream_new_resizable ();
+      g_output_stream_splice_async ((GOutputStream*)state->stderr_buf, subprocess->stderr_pipe,
+                                    G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
+                                    G_PRIORITY_DEFAULT, state->cancellable,
+                                    ide_subprocess_communicate_made_progress, g_object_ref (task));
+      state->outstanding_ops++;
+    }
+
+  ide_subprocess_wait_async (IDE_SUBPROCESS (subprocess), state->cancellable,
+                             ide_subprocess_communicate_made_progress, g_object_ref (task));
+  state->outstanding_ops++;
+
+  return state;
+}
+
+static void
+ide_breakout_subprocess_communicate_async (IdeSubprocess       *subprocess,
+                                           GBytes              *stdin_buf,
+                                           GCancellable        *cancellable,
+                                           GAsyncReadyCallback  callback,
+                                           gpointer             user_data)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  ide_breakout_subprocess_communicate_internal (self, FALSE, stdin_buf, cancellable, callback, user_data);
+}
+
+static gboolean
+ide_breakout_subprocess_communicate_finish (IdeSubprocess  *subprocess,
+                                            GAsyncResult   *result,
+                                            GBytes        **stdout_buf,
+                                            GBytes        **stderr_buf,
+                                            GError        **error)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+  CommunicateState *state;
+  GTask *task = (GTask *)result;
+  gboolean success;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (G_IS_TASK (task));
+
+  g_object_ref (task);
+
+  state = g_task_get_task_data (task);
+
+  g_assert (state != NULL);
+
+  success = g_task_propagate_boolean (task, error);
+
+  if (success)
+    {
+      if (stdout_buf)
+        *stdout_buf = g_memory_output_stream_steal_as_bytes (state->stdout_buf);
+      if (stderr_buf)
+        *stderr_buf = g_memory_output_stream_steal_as_bytes (state->stderr_buf);
+    }
+
+  g_object_unref (task);
+
+  return success;
+}
+
+static gboolean
+ide_breakout_subprocess_communicate (IdeSubprocess  *subprocess,
+                                     GBytes         *stdin_buf,
+                                     GCancellable   *cancellable,
+                                     GBytes        **stdout_buf,
+                                     GBytes        **stderr_buf,
+                                     GError        **error)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+  g_autoptr(GMainContext) main_context = g_main_context_new ();
+  g_autoptr(GAsyncResult) result = NULL;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  ide_breakout_subprocess_sync_setup ();
+  ide_breakout_subprocess_communicate_internal (self,
+                                                FALSE,
+                                                stdin_buf,
+                                                cancellable,
+                                                ide_breakout_subprocess_sync_done,
+                                                &result);
+  ide_breakout_subprocess_sync_complete (&result);
+
+  return ide_breakout_subprocess_communicate_finish (subprocess, result, stdout_buf, stderr_buf, error);
+}
+
+static void
+subprocess_iface_init (IdeSubprocessInterface *iface)
+{
+  iface->get_identifier = ide_breakout_subprocess_get_identifier;
+  iface->get_stdout_pipe = ide_breakout_subprocess_get_stdout_pipe;
+  iface->get_stderr_pipe = ide_breakout_subprocess_get_stderr_pipe;
+  iface->get_stdin_pipe = ide_breakout_subprocess_get_stdin_pipe;
+  iface->wait = ide_breakout_subprocess_wait;
+  iface->wait_async = ide_breakout_subprocess_wait_async;
+  iface->wait_finish = ide_breakout_subprocess_wait_finish;
+  iface->get_successful = ide_breakout_subprocess_get_successful;
+  iface->get_if_exited = ide_breakout_subprocess_get_if_exited;
+  iface->get_exit_status = ide_breakout_subprocess_get_exit_status;
+  iface->get_if_signaled = ide_breakout_subprocess_get_if_signaled;
+  iface->get_term_sig = ide_breakout_subprocess_get_term_sig;
+  iface->get_status = ide_breakout_subprocess_get_status;
+  iface->send_signal = ide_breakout_subprocess_send_signal;
+  iface->force_exit = ide_breakout_subprocess_force_exit;
+  iface->communicate = ide_breakout_subprocess_communicate;
+  iface->communicate_utf8 = ide_breakout_subprocess_communicate_utf8;
+  iface->communicate_async = ide_breakout_subprocess_communicate_async;
+  iface->communicate_finish = ide_breakout_subprocess_communicate_finish;
+}
+
+static gboolean
+sigterm_handler (gpointer user_data)
+{
+  IdeBreakoutSubprocess *self = user_data;
+  g_autoptr(GDBusConnection) bus = NULL;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  g_dbus_connection_call_sync (self->connection,
+                               "org.freedesktop.Flatpak",
+                               "/org/freedesktop/Flatpak/Development",
+                               "org.freedesktop.Flatpak.Development",
+                               "HostCommandSignal",
+                               g_variant_new ("(uub)", self->client_pid, SIGTERM, TRUE),
+                               NULL,
+                               G_DBUS_CALL_FLAGS_NONE, -1,
+                               NULL, NULL);
+
+  kill (getpid (), SIGTERM);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+sigint_handler (gpointer user_data)
+{
+  IdeBreakoutSubprocess *self = user_data;
+  g_autoptr(GDBusConnection) bus = NULL;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  g_dbus_connection_call_sync (self->connection,
+                               "org.freedesktop.Flatpak",
+                               "/org/freedesktop/Flatpak/Development",
+                               "org.freedesktop.Flatpak.Development",
+                               "HostCommandSignal",
+                               g_variant_new ("(uub)", self->client_pid, SIGINT, TRUE),
+                               NULL,
+                               G_DBUS_CALL_FLAGS_NONE, -1,
+                               NULL, NULL);
+
+  kill (getpid (), SIGINT);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+maybe_create_input_stream (GInputStream **ret,
+                           gint          *fdptr)
+{
+  g_assert (ret != NULL);
+  g_assert (*ret == NULL);
+  g_assert (fdptr != NULL);
+
+  /*
+   * Only create a stream if we aren't merging to stdio.
+   * We are also stealing the file-descriptor while doing so.
+   */
+  if (*fdptr > 2)
+    *ret = g_unix_input_stream_new (*fdptr, TRUE);
+
+  *fdptr = -1;
+}
+
+static void
+maybe_create_output_stream (GOutputStream **ret,
+                            gint           *fdptr)
+{
+  g_assert (ret != NULL);
+  g_assert (*ret == NULL);
+  g_assert (fdptr != NULL);
+
+  /*
+   * Only create a stream if we aren't merging to stdio.
+   * We are also stealing the file-descriptor while doing so.
+   */
+  if (*fdptr > 2)
+    *ret = g_unix_output_stream_new (*fdptr, TRUE);
+
+  *fdptr = -1;
+}
+
+static inline void
+set_error_from_errno (GError **error)
+{
+  g_set_error (error,
+               G_IO_ERROR,
+               g_io_error_from_errno (errno),
+               "%s",
+               g_strerror (errno));
+}
+
+static void
+host_command_exited_cb (GDBusConnection *connection,
+                        const gchar     *sender_name,
+                        const gchar     *object_path,
+                        const gchar     *interface_name,
+                        const gchar     *signal_name,
+                        GVariant        *parameters,
+                        gpointer         user_data)
+{
+  g_autoptr(IdeBreakoutSubprocess) finalize_protect = NULL;
+  IdeBreakoutSubprocess *self = user_data;
+  g_autoptr(GMutexLocker) locker = NULL;
+  GList *waiting;
+  guint32 client_pid = 0;
+  guint32 exit_status = 0;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_DBUS_CONNECTION (connection));
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  finalize_protect = g_object_ref (self);
+
+  if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)")))
+    IDE_EXIT;
+
+  g_variant_get (parameters, "(uu)", &client_pid, &exit_status);
+  if (client_pid != self->client_pid)
+    IDE_EXIT;
+
+  locker = g_mutex_locker_new (&self->waiter_mutex);
+  self->client_has_exited = TRUE;
+
+  IDE_TRACE_MSG ("Host process %u exited with %u",
+                 (guint)self->client_pid,
+                 (guint)exit_status);
+
+  self->status = exit_status;
+
+  /* Clear process identifiers to prevent accidental use by API consumers
+   * after the process has exited.
+   */
+  self->client_pid = 0;
+  g_clear_pointer (&self->identifier, g_free);
+
+  /* We can release our dbus signal handler now */
+  if (self->exited_subscription != 0)
+    {
+      IDE_TRACE_MSG ("Unsubscribing from DBus subscription %d", self->exited_subscription);
+      g_dbus_connection_signal_unsubscribe (self->connection, self->exited_subscription);
+      self->exited_subscription = 0;
+    }
+
+  /* Remove our sources used for signal propagation */
+  ide_clear_source (&self->sigint_id);
+  ide_clear_source (&self->sigterm_id);
+
+  /* Notify all of our waiters that their task has completed. */
+  waiting = self->waiting, self->waiting = NULL;
+  for (GList *iter = waiting; iter != NULL; iter = iter->next)
+    {
+      g_autoptr(GTask) task = iter->data;
+
+      g_task_return_boolean (task, TRUE);
+    }
+  g_list_free (waiting);
+
+  /* Notify synchronous waiters */
+  g_cond_broadcast (&self->waiter_cond);
+
+  IDE_EXIT;
+}
+
+static gboolean
+ide_breakout_subprocess_ping_peer (IdeBreakoutSubprocess  *self,
+                                   GCancellable           *cancellable,
+                                   GError                **error)
+{
+  g_autoptr(GVariant) reply = NULL;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (G_IS_DBUS_CONNECTION (self->connection));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  reply = g_dbus_connection_call_sync (self->connection,
+                                       "org.freedesktop.Flatpak",
+                                       "/org/freedesktop/Flatpak/Development",
+                                       "org.freedesktop.DBus.Peer",
+                                       "Ping",
+                                       NULL,
+                                       NULL,
+                                       G_DBUS_CALL_FLAGS_NONE, -1,
+                                       cancellable, error);
+
+  return (reply != NULL);
+}
+
+static void
+ide_breakout_subprocess_cancelled (IdeBreakoutSubprocess *self,
+                                   GCancellable          *cancellable)
+{
+  IDE_ENTRY;
+
+  g_assert (G_IS_CANCELLABLE (cancellable));
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  ide_subprocess_force_exit (IDE_SUBPROCESS (self));
+
+  IDE_EXIT;
+}
+
+static inline void
+maybe_close (gint *fd)
+{
+  g_assert (fd != NULL);
+  g_assert (*fd >= -1);
+
+  if (*fd > 2)
+    close (*fd);
+
+  *fd = -1;
+}
+
+static gboolean
+ide_breakout_subprocess_initable_init (GInitable     *initable,
+                                       GCancellable  *cancellable,
+                                       GError       **error)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)initable;
+  g_autoptr(GVariantBuilder) fd_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{uh}"));
+  g_autoptr(GVariantBuilder) env_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{ss}"));
+  g_autoptr(GUnixFDList) fd_list = g_unix_fd_list_new ();
+  g_autoptr(GVariant) reply = NULL;
+  g_autoptr(GVariant) params = NULL;
+  guint32 client_pid = 0;
+  gint stdout_pair[2] = { -1, -1 };
+  gint stderr_pair[2] = { -1, -1 };
+  gint stdin_pair[2] = { -1, -1 };
+  gint stdin_handle = -1;
+  gint stdout_handle = -1;
+  gint stderr_handle = -1;
+  gboolean ret = FALSE;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  self->connection = ide_breakout_subprocess_get_shared_connection (cancellable, error);
+  if (self->connection == NULL)
+    IDE_RETURN (FALSE);
+
+
+  /*
+   * Handle STDIN for the process.
+   *
+   * Make sure we handle inherit STDIN, a new pipe (so that the application can
+   * get the stdin stream), or simply redirect to /dev/null.
+   */
+  if (self->flags & G_SUBPROCESS_FLAGS_STDIN_INHERIT)
+    {
+      stdin_pair[0] = STDIN_FILENO;
+    }
+  else if (self->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE)
+    {
+      if (pipe2 (stdin_pair, O_CLOEXEC) != 0)
+        {
+          set_error_from_errno (error);
+          IDE_GOTO (cleanup_fds);
+        }
+    }
+  else
+    {
+      stdin_pair[0] = open ("/dev/null", O_CLOEXEC | O_RDWR, 0);
+
+      if (stdin_pair[0] == -1)
+        IDE_GOTO (cleanup_fds);
+    }
+
+  g_assert (stdin_pair[0] != -1);
+
+  stdin_handle = g_unix_fd_list_append (fd_list, stdin_pair[0], error);
+  if (stdin_handle == -1)
+    IDE_GOTO (cleanup_fds);
+  else
+    maybe_close (&stdin_pair[0]);
+
+
+  /*
+   * Setup STDOUT for the process.
+   *
+   * Make sure we redirect STDOUT to our stdout, unless a pipe was requested
+   * for the application to read. However, if silence was requested, redirect
+   * to /dev/null.
+   */
+  if (self->flags & G_SUBPROCESS_FLAGS_STDOUT_SILENCE)
+    {
+      stdout_pair[1] = open ("/dev/null", O_CLOEXEC | O_RDWR, 0);
+
+      if (stdout_pair[1] == -1)
+        IDE_GOTO (cleanup_fds);
+    }
+  else if (self->flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE)
+    {
+      if (pipe2 (stdout_pair, O_CLOEXEC) != 0)
+        {
+          set_error_from_errno (error);
+          IDE_GOTO (cleanup_fds);
+        }
+    }
+  else
+    {
+      stdout_pair[1] = STDOUT_FILENO;
+    }
+
+  g_assert (stdout_pair[1] != -1);
+
+  stdout_handle = g_unix_fd_list_append (fd_list, stdout_pair[1], error);
+  if (stdout_handle == -1)
+    IDE_GOTO (cleanup_fds);
+  else
+    maybe_close (&stdout_pair[1]);
+
+
+  /*
+   * Handle STDERR for the process.
+   *
+   * If silence is requested, we simply redirect to /dev/null. If the
+   * application requested to read from the subprocesses stderr, then we need
+   * to create a pipe. Otherwose, merge stderr into our own stderr.
+   */
+  if (self->flags & G_SUBPROCESS_FLAGS_STDERR_SILENCE)
+    {
+      stderr_pair[1] = open ("/dev/null", O_CLOEXEC | O_RDWR, 0);
+
+      if (stderr_pair[1] == -1)
+        IDE_GOTO (cleanup_fds);
+    }
+  else if (self->flags & G_SUBPROCESS_FLAGS_STDERR_PIPE)
+    {
+      if (pipe2 (stderr_pair, O_CLOEXEC) != 0)
+        {
+          set_error_from_errno (error);
+          IDE_GOTO (cleanup_fds);
+        }
+    }
+  else
+    {
+      stderr_pair[1] = STDERR_FILENO;
+    }
+
+  g_assert (stderr_pair[1] != -1);
+
+  stderr_handle = g_unix_fd_list_append (fd_list, stderr_pair[1], error);
+  if (stderr_handle == -1)
+    IDE_GOTO (cleanup_fds);
+  else
+    maybe_close (&stderr_pair[1]);
+
+
+  /*
+   * Build our FDs for the message.
+   */
+  g_variant_builder_add (fd_builder, "{uh}", 0, stdin_handle);
+  g_variant_builder_add (fd_builder, "{uh}", 1, stdout_handle);
+  g_variant_builder_add (fd_builder, "{uh}", 2, stderr_handle);
+
+
+  /*
+   * Build streams for our application to use.
+   */
+  maybe_create_output_stream (&self->stdin_pipe, &stdin_pair[1]);
+  maybe_create_input_stream (&self->stdout_pipe, &stdout_pair[0]);
+  maybe_create_input_stream (&self->stderr_pipe, &stderr_pair[0]);
+
+
+  /*
+   * Build our environment variables message.
+   */
+  if (self->env != NULL)
+    {
+      for (guint i = 0; self->env[i]; i++)
+        {
+          const gchar *pair = self->env[i];
+          const gchar *eq = strchr (pair, '=');
+          const gchar *val = eq ? eq + 1 : "";
+          g_autofree gchar *key = eq ? g_strndup (pair, eq - pair) : g_strdup (pair);
+
+          g_variant_builder_add (env_builder, "{ss}", key, val);
+        }
+    }
+
+
+  /*
+   * Register signal handlers for SIGTERM/SIGINT so that we can terminate
+   * the host process with us (which won't be guaranteed since its outside
+   * our cgroup, nor can we use a process group leader).
+   */
+  self->sigterm_id = g_unix_signal_add (SIGTERM, sigterm_handler, self);
+  self->sigint_id = g_unix_signal_add (SIGINT, sigint_handler, self);
+
+
+  /*
+   * Make sure we've closed or stolen all of the FDs that are in play
+   * before calling the DBus service.
+   */
+  g_assert_cmpint (-1, ==, stdin_pair[0]);
+  g_assert_cmpint (-1, ==, stdin_pair[1]);
+  g_assert_cmpint (-1, ==, stdout_pair[0]);
+  g_assert_cmpint (-1, ==, stdout_pair[1]);
+  g_assert_cmpint (-1, ==, stderr_pair[0]);
+  g_assert_cmpint (-1, ==, stderr_pair[1]);
+
+
+  /*
+   * Connect to the HostCommandExited signal so that we can make progress
+   * on all tasks waiting on ide_subprocess_wait() and it's async variants.
+   * We need to do this before spawning the process to avoid the race.
+   */
+  self->exited_subscription = g_dbus_connection_signal_subscribe (self->connection,
+                                                                  NULL,
+                                                                  "org.freedesktop.Flatpak.Development",
+                                                                  "HostCommandExited",
+                                                                  "/org/freedesktop/Flatpak/Development",
+                                                                  NULL,
+                                                                  G_DBUS_SIGNAL_FLAGS_NONE,
+                                                                  host_command_exited_cb,
+                                                                  self,
+                                                                  NULL);
+
+
+  /*
+   * XXX:
+   *
+   * Be paranoid about the peer being available. This is mostly for
+   * debugging purposes and can be removed after we determine why we
+   * are getting our DBus connection closed.
+   */
+  if (!ide_breakout_subprocess_ping_peer (self, cancellable, error))
+    IDE_GOTO (cleanup_fds);
+
+
+  /*
+   * Now call the HostCommand service to execute the process within the host
+   * system. We need to ensure our fd_list is sent across for redirecting
+   * various standard streams.
+   */
+  g_assert_cmpint (3, ==, g_unix_fd_list_get_length (fd_list));
+  params = g_variant_new ("(^ay^aay@a{uh}@a{ss}u)",
+                          self->cwd ?: g_get_home_dir (),
+                          self->argv,
+                          g_variant_builder_end (g_steal_pointer (&fd_builder)),
+                          g_variant_builder_end (g_steal_pointer (&env_builder)),
+                          self->clear_env ? FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV : 0);
+  g_variant_ref_sink (params);
+
+#ifdef IDE_ENABLE_TRACE
+  {
+    g_autofree gchar *str = g_variant_print (params, TRUE);
+    IDE_TRACE_MSG ("Calling HostCommand with %s", str);
+  }
+#endif
+
+  reply = g_dbus_connection_call_with_unix_fd_list_sync (self->connection,
+                                                         "org.freedesktop.Flatpak",
+                                                         "/org/freedesktop/Flatpak/Development",
+                                                         "org.freedesktop.Flatpak.Development",
+                                                         "HostCommand",
+                                                         params,
+                                                         G_VARIANT_TYPE ("(u)"),
+                                                         G_DBUS_CALL_FLAGS_NONE,
+                                                         -1,
+                                                         fd_list,
+                                                         NULL,
+                                                         cancellable,
+                                                         error);
+  if (reply == NULL)
+    IDE_GOTO (cleanup_fds);
+
+  g_variant_get (reply, "(u)", &client_pid);
+
+  self->client_pid = (GPid)client_pid;
+  self->identifier = g_strdup_printf ("%u", client_pid);
+
+  IDE_TRACE_MSG ("HostCommand() spawned client_pid %u", (guint)client_pid);
+
+  if (cancellable != NULL)
+    {
+      g_signal_connect_object (cancellable,
+                               "cancelled",
+                               G_CALLBACK (ide_breakout_subprocess_cancelled),
+                               self,
+                               G_CONNECT_SWAPPED);
+    }
+
+  ret = TRUE;
+
+cleanup_fds:
+
+  /* Close lingering stdin fds */
+  maybe_close (&stdin_pair[0]);
+  maybe_close (&stdin_pair[1]);
+
+  /* Close lingering stdout fds */
+  maybe_close (&stdout_pair[0]);
+  maybe_close (&stdout_pair[1]);
+
+  /* Close lingering stderr fds */
+  maybe_close (&stderr_pair[0]);
+  maybe_close (&stderr_pair[1]);
+
+  IDE_RETURN (ret);
+}
+
+static void
+initiable_iface_init (GInitableIface *iface)
+{
+  iface->init = ide_breakout_subprocess_initable_init;
+}
+
+G_DEFINE_TYPE_EXTENDED (IdeBreakoutSubprocess, ide_breakout_subprocess, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initiable_iface_init)
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_SUBPROCESS, subprocess_iface_init))
+
+static void
+ide_breakout_subprocess_dispose (GObject *object)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)object;
+
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+  if (self->exited_subscription != 0)
+    {
+      if (self->connection != NULL && !g_dbus_connection_is_closed (self->connection))
+        {
+          IDE_TRACE_MSG ("Unsubscribing from DBus subscription %d", self->exited_subscription);
+          g_dbus_connection_signal_unsubscribe (self->connection, self->exited_subscription);
+        }
+
+      self->exited_subscription = 0;
+    }
+
+  if (self->waiting != NULL)
+    g_warning ("improper disposal while async operations are active!");
+
+  ide_clear_source (&self->sigint_id);
+  ide_clear_source (&self->sigterm_id);
+
+  G_OBJECT_CLASS (ide_breakout_subprocess_parent_class)->dispose (object);
+}
+
+static void
+ide_breakout_subprocess_finalize (GObject *object)
+{
+  IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)object;
+
+  IDE_ENTRY;
+
+  g_assert (self->waiting == NULL);
+  g_assert_cmpint (self->sigint_id, ==, 0);
+  g_assert_cmpint (self->sigterm_id, ==, 0);
+  g_assert_cmpint (self->exited_subscription, ==, 0);
+
+  g_clear_pointer (&self->identifier, g_free);
+  g_clear_pointer (&self->cwd, g_free);
+  g_clear_pointer (&self->argv, g_strfreev);
+  g_clear_pointer (&self->env, g_strfreev);
+
+  g_clear_object (&self->stdin_pipe);
+  g_clear_object (&self->stdout_pipe);
+  g_clear_object (&self->stderr_pipe);
+  g_clear_object (&self->connection);
+
+  g_mutex_clear (&self->waiter_mutex);
+  g_cond_clear (&self->waiter_cond);
+
+  G_OBJECT_CLASS (ide_breakout_subprocess_parent_class)->finalize (object);
+
+  EGG_COUNTER_DEC (instances);
+
+  IDE_EXIT;
+}
+
+static void
+ide_breakout_subprocess_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  IdeBreakoutSubprocess *self = IDE_BREAKOUT_SUBPROCESS (object);
+
+  switch (prop_id)
+    {
+    case PROP_CWD:
+      g_value_set_string (value, self->cwd);
+      break;
+
+    case PROP_ARGV:
+      g_value_set_boxed (value, self->argv);
+      break;
+
+    case PROP_ENV:
+      g_value_set_boxed (value, self->env);
+      break;
+
+    case PROP_FLAGS:
+      g_value_set_flags (value, self->flags);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_breakout_subprocess_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  IdeBreakoutSubprocess *self = IDE_BREAKOUT_SUBPROCESS (object);
+
+  switch (prop_id)
+    {
+    case PROP_CWD:
+      self->cwd = g_value_dup_string (value);
+      break;
+
+    case PROP_ARGV:
+      self->argv = g_value_dup_boxed (value);
+      break;
+
+    case PROP_ENV:
+      self->env = g_value_dup_boxed (value);
+      break;
+
+    case PROP_FLAGS:
+      self->flags = g_value_get_flags (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_breakout_subprocess_class_init (IdeBreakoutSubprocessClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_breakout_subprocess_dispose;
+  object_class->finalize = ide_breakout_subprocess_finalize;
+  object_class->get_property = ide_breakout_subprocess_get_property;
+  object_class->set_property = ide_breakout_subprocess_set_property;
+
+  properties [PROP_CWD] =
+    g_param_spec_string ("cwd",
+                         "Current Working Directory",
+                         "The working directory for spawning the process",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ARGV] =
+    g_param_spec_boxed ("argv",
+                        "Argv",
+                        "The arguments for the process, including argv0",
+                        G_TYPE_STRV,
+                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ENV] =
+    g_param_spec_boxed ("env",
+                        "Environment",
+                        "The environment variables for the process",
+                        G_TYPE_STRV,
+                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_FLAGS] =
+    g_param_spec_flags ("flags",
+                        "Flags",
+                        "The subprocess flags to use when spawning",
+                        G_TYPE_SUBPROCESS_FLAGS,
+                        G_SUBPROCESS_FLAGS_NONE,
+                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_breakout_subprocess_init (IdeBreakoutSubprocess *self)
+{
+  IDE_ENTRY;
+
+  EGG_COUNTER_INC (instances);
+
+  g_mutex_init (&self->waiter_mutex);
+  g_cond_init (&self->waiter_cond);
+
+  IDE_EXIT;
+}
+
+IdeSubprocess *
+_ide_breakout_subprocess_new (const gchar          *cwd,
+                              const gchar * const  *argv,
+                              const gchar * const  *env,
+                              GSubprocessFlags      flags,
+                              gboolean              clear_env,
+                              GCancellable         *cancellable,
+                              GError              **error)
+{
+  g_autoptr(IdeBreakoutSubprocess) ret = NULL;
+
+  g_return_val_if_fail (argv != NULL, NULL);
+  g_return_val_if_fail (argv[0] != NULL, NULL);
+
+  ret = g_object_new (IDE_TYPE_BREAKOUT_SUBPROCESS,
+                      "cwd", cwd,
+                      "argv", argv,
+                      "env", env,
+                      "flags", flags,
+                      NULL);
+
+  ret->clear_env = clear_env;
+
+  if (!g_initable_init (G_INITABLE (ret), cancellable, error))
+    return NULL;
+
+  return g_steal_pointer (&ret);
+}
diff --git a/libide/subprocess/ide-breakout-subprocess.h b/libide/subprocess/ide-breakout-subprocess.h
new file mode 100644
index 0000000..3ce2daa
--- /dev/null
+++ b/libide/subprocess/ide-breakout-subprocess.h
@@ -0,0 +1,29 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2012, 2013, 2016 Red Hat, Inc.
+ * Copyright © 2012, 2013 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: Christian Hergert <chergert redhat com>
+ */
+
+#ifndef IDE_BREAKOUT_SUBPROCESS_H
+#define IDE_BREAKOUT_SUBPROCESS_H
+
+#include "subprocess/ide-subprocess.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BREAKOUT_SUBPROCESS (ide_breakout_subprocess_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeBreakoutSubprocess, ide_breakout_subprocess, IDE, BREAKOUT_SUBPROCESS, GObject)
+
+G_END_DECLS
+
+#endif /* IDE_BREAKOUT_SUBPROCESS_H */
diff --git a/libide/subprocess/ide-simple-subprocess.c b/libide/subprocess/ide-simple-subprocess.c
new file mode 100644
index 0000000..98bc99d
--- /dev/null
+++ b/libide/subprocess/ide-simple-subprocess.c
@@ -0,0 +1,310 @@
+/* ide-simple-subprocess.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-simple-subprocess"
+
+#include "ide-simple-subprocess.h"
+
+struct _IdeSimpleSubprocess
+{
+  GObject      parent_instance;
+  GSubprocess *subprocess;
+};
+
+static void subprocess_iface_init (IdeSubprocessInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeSimpleSubprocess, ide_simple_subprocess, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_SUBPROCESS, subprocess_iface_init))
+
+static void
+ide_simple_subprocess_finalize (GObject *object)
+{
+  IdeSimpleSubprocess *self = (IdeSimpleSubprocess *)object;
+
+  g_clear_object (&self->subprocess);
+
+  G_OBJECT_CLASS (ide_simple_subprocess_parent_class)->finalize (object);
+}
+
+static void
+ide_simple_subprocess_class_init (IdeSimpleSubprocessClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_simple_subprocess_finalize;
+}
+
+static void
+ide_simple_subprocess_init (IdeSimpleSubprocess *self)
+{
+}
+
+#define WRAP_INTERFACE_METHOD(name, ...) \
+  g_subprocess_##name(IDE_SIMPLE_SUBPROCESS(subprocess)->subprocess, ## __VA_ARGS__)
+
+static const gchar *
+ide_simple_subprocess_get_identifier (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_identifier);
+}
+
+static GInputStream *
+ide_simple_subprocess_get_stdout_pipe (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_stdout_pipe);
+}
+
+static GInputStream *
+ide_simple_subprocess_get_stderr_pipe (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_stderr_pipe);
+}
+
+static GOutputStream *
+ide_simple_subprocess_get_stdin_pipe (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_stdin_pipe);
+}
+
+static gboolean
+ide_simple_subprocess_wait (IdeSubprocess  *subprocess,
+                            GCancellable   *cancellable,
+                            GError        **error)
+{
+  return WRAP_INTERFACE_METHOD (wait, cancellable, error);
+}
+
+static void
+ide_simple_subprocess_wait_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+  GSubprocess *subprocess = (GSubprocess *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  if (!g_subprocess_wait_finish (subprocess, result, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_simple_subprocess_wait_async (IdeSubprocess       *subprocess,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  IdeSimpleSubprocess *self = (IdeSimpleSubprocess *)subprocess;
+  GTask *task = g_task_new (self, cancellable, callback, user_data);
+  g_subprocess_wait_async (self->subprocess, cancellable, ide_simple_subprocess_wait_cb, task);
+}
+
+static gboolean
+ide_simple_subprocess_wait_finish (IdeSubprocess  *subprocess,
+                                   GAsyncResult   *result,
+                                   GError        **error)
+{
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+ide_simple_subprocess_get_successful (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_successful);
+}
+
+static gboolean
+ide_simple_subprocess_get_if_exited (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_if_exited);
+}
+
+static gint
+ide_simple_subprocess_get_exit_status (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_exit_status);
+}
+
+static gboolean
+ide_simple_subprocess_get_if_signaled (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_if_signaled);
+}
+
+static gint
+ide_simple_subprocess_get_term_sig (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_term_sig);
+}
+
+static gint
+ide_simple_subprocess_get_status (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (get_status);
+}
+
+static void
+ide_simple_subprocess_send_signal (IdeSubprocess *subprocess,
+                                   gint           signal_num)
+{
+  return WRAP_INTERFACE_METHOD (send_signal, signal_num);
+}
+
+static void
+ide_simple_subprocess_force_exit (IdeSubprocess *subprocess)
+{
+  return WRAP_INTERFACE_METHOD (force_exit);
+}
+
+static gboolean
+ide_simple_subprocess_communicate (IdeSubprocess  *subprocess,
+                                   GBytes         *stdin_buf,
+                                   GCancellable   *cancellable,
+                                   GBytes        **stdout_buf,
+                                   GBytes        **stderr_buf,
+                                   GError        **error)
+{
+  return WRAP_INTERFACE_METHOD (communicate, stdin_buf, cancellable, stdout_buf, stderr_buf, error);
+}
+
+static gboolean
+ide_simple_subprocess_communicate_utf8 (IdeSubprocess  *subprocess,
+                                        const gchar    *stdin_buf,
+                                        GCancellable   *cancellable,
+                                        gchar         **stdout_buf,
+                                        gchar         **stderr_buf,
+                                        GError        **error)
+{
+  return WRAP_INTERFACE_METHOD (communicate_utf8, stdin_buf, cancellable, stdout_buf, stderr_buf, error);
+}
+
+static void
+free_object_pair (gpointer data)
+{
+  gpointer *pair = data;
+
+  g_clear_object (&pair[0]);
+  g_clear_object (&pair[1]);
+  g_free (pair);
+}
+
+static void
+ide_simple_subprocess_communicate_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  GSubprocess *subprocess = (GSubprocess *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GBytes) stdout_buf = NULL;
+  g_autoptr(GBytes) stderr_buf = NULL;
+  gpointer *data;
+
+  if (!g_subprocess_communicate_finish (subprocess, result, &stdout_buf, &stderr_buf, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+
+  data = g_new0 (gpointer, 2);
+  data[0] = g_steal_pointer (&stdout_buf);
+  data[1] = g_steal_pointer (&stderr_buf);
+
+  g_task_return_pointer (task, data, free_object_pair);
+}
+
+static void
+ide_simple_subprocess_communicate_async (IdeSubprocess       *subprocess,
+                                         GBytes              *stdin_buf,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data)
+{
+  IdeSimpleSubprocess *self = (IdeSimpleSubprocess *)subprocess;
+  GTask *task = g_task_new (self, cancellable, callback, user_data);
+  g_subprocess_communicate_async (self->subprocess, stdin_buf, cancellable, 
ide_simple_subprocess_communicate_cb, task);
+}
+
+static gboolean
+ide_simple_subprocess_communicate_finish (IdeSubprocess  *subprocess,
+                                          GAsyncResult   *result,
+                                          GBytes        **stdout_buf,
+                                          GBytes        **stderr_buf,
+                                          GError        **error)
+{
+  gpointer *pair;
+
+  pair = g_task_propagate_pointer (G_TASK (result), error);
+
+  if (pair != NULL)
+    {
+      if (stdout_buf != NULL)
+        *stdout_buf = g_steal_pointer (&pair[0]);
+
+      if (stderr_buf != NULL)
+        *stderr_buf = g_steal_pointer (&pair[1]);
+
+      free_object_pair (pair);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+subprocess_iface_init (IdeSubprocessInterface *iface)
+{
+  iface->get_identifier = ide_simple_subprocess_get_identifier;
+  iface->get_stdout_pipe = ide_simple_subprocess_get_stdout_pipe;
+  iface->get_stderr_pipe = ide_simple_subprocess_get_stderr_pipe;
+  iface->get_stdin_pipe = ide_simple_subprocess_get_stdin_pipe;
+  iface->wait = ide_simple_subprocess_wait;
+  iface->wait_async = ide_simple_subprocess_wait_async;
+  iface->wait_finish = ide_simple_subprocess_wait_finish;
+  iface->get_successful = ide_simple_subprocess_get_successful;
+  iface->get_if_exited = ide_simple_subprocess_get_if_exited;
+  iface->get_exit_status = ide_simple_subprocess_get_exit_status;
+  iface->get_if_signaled = ide_simple_subprocess_get_if_signaled;
+  iface->get_term_sig = ide_simple_subprocess_get_term_sig;
+  iface->get_status = ide_simple_subprocess_get_status;
+  iface->send_signal = ide_simple_subprocess_send_signal;
+  iface->force_exit = ide_simple_subprocess_force_exit;
+  iface->communicate = ide_simple_subprocess_communicate;
+  iface->communicate_utf8 = ide_simple_subprocess_communicate_utf8;
+  iface->communicate_async = ide_simple_subprocess_communicate_async;
+  iface->communicate_finish = ide_simple_subprocess_communicate_finish;
+}
+
+/**
+ * ide_simple_subprocess_new:
+ *
+ * Creates a new #IdeSimpleSubprocess wrapping the #GSubprocess.
+ *
+ * Returns: (transfer full): A new #IdeSubprocess
+ */
+IdeSubprocess *
+ide_simple_subprocess_new (GSubprocess *subprocess)
+{
+  IdeSimpleSubprocess *ret;
+
+  g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), NULL);
+
+  ret = g_object_new (IDE_TYPE_SIMPLE_SUBPROCESS, NULL);
+  ret->subprocess = g_object_ref (subprocess);
+
+  return IDE_SUBPROCESS (ret);
+}
diff --git a/libide/subprocess/ide-simple-subprocess.h b/libide/subprocess/ide-simple-subprocess.h
new file mode 100644
index 0000000..dff3e3a
--- /dev/null
+++ b/libide/subprocess/ide-simple-subprocess.h
@@ -0,0 +1,34 @@
+/* ide-simple-subprocess.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_SIMPLE_SUBPROCESS_H
+#define IDE_SIMPLE_SUBPROCESS_H
+
+#include "ide-subprocess.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SIMPLE_SUBPROCESS (ide_simple_subprocess_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeSimpleSubprocess, ide_simple_subprocess, IDE, SIMPLE_SUBPROCESS, GObject)
+
+IdeSubprocess *ide_simple_subprocess_new (GSubprocess *subprocess);
+
+G_END_DECLS
+
+#endif /* IDE_SIMPLE_SUBPROCESS_H */
diff --git a/libide/workers/ide-subprocess-launcher.c b/libide/subprocess/ide-subprocess-launcher.c
similarity index 66%
rename from libide/workers/ide-subprocess-launcher.c
rename to libide/subprocess/ide-subprocess-launcher.c
index 44e6563..a47454b 100644
--- a/libide/workers/ide-subprocess-launcher.c
+++ b/libide/subprocess/ide-subprocess-launcher.c
@@ -28,7 +28,10 @@
 
 #include "buildsystem/ide-environment-variable.h"
 #include "buildsystem/ide-environment.h"
-#include "workers/ide-subprocess-launcher.h"
+#include "subprocess/ide-breakout-subprocess.h"
+#include "subprocess/ide-breakout-subprocess-private.h"
+#include "subprocess/ide-simple-subprocess.h"
+#include "subprocess/ide-subprocess-launcher.h"
 
 typedef struct
 {
@@ -37,15 +40,20 @@ typedef struct
   GPtrArray        *argv;
   gchar            *cwd;
   GPtrArray        *environ;
+
+  guint             run_on_host : 1;
+  guint             clear_env : 1;
 } IdeSubprocessLauncherPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (IdeSubprocessLauncher, ide_subprocess_launcher, G_TYPE_OBJECT)
 
 enum {
   PROP_0,
+  PROP_CLEAR_ENV,
   PROP_CWD,
   PROP_ENVIRON,
   PROP_FLAGS,
+  PROP_RUN_ON_HOST,
   N_PROPS
 };
 
@@ -95,6 +103,20 @@ ide_subprocess_launcher_kill_process_group (GCancellable *cancellable,
 #endif
 }
 
+static void
+ide_subprocess_launcher_kill_host_process (GCancellable  *cancellable,
+                                           IdeSubprocess *subprocess)
+{
+  g_assert (G_IS_CANCELLABLE (cancellable));
+  g_assert (IDE_IS_BREAKOUT_SUBPROCESS (subprocess));
+
+  g_signal_handlers_disconnect_by_func (cancellable,
+                                        G_CALLBACK (ide_subprocess_launcher_kill_host_process),
+                                        subprocess);
+
+  ide_subprocess_force_exit (subprocess);
+}
+
 IdeSubprocessLauncher *
 ide_subprocess_launcher_new (GSubprocessFlags flags)
 {
@@ -103,6 +125,91 @@ ide_subprocess_launcher_new (GSubprocessFlags flags)
                        NULL);
 }
 
+static gboolean
+should_use_breakout_process (IdeSubprocessLauncher *self)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+  static gsize initialized;
+  static gboolean is_contained;
+
+  g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+  if (g_once_init_enter (&initialized))
+    {
+      g_autofree gchar *flatpak_info_path = NULL;
+
+      flatpak_info_path = g_build_filename (g_get_user_runtime_dir (),
+                                            "flatpak-info",
+                                            NULL);
+
+      if (g_file_test (flatpak_info_path, G_FILE_TEST_EXISTS))
+        is_contained = TRUE;
+
+      g_once_init_leave (&initialized, TRUE);
+    }
+
+  if (g_getenv ("IDE_USE_BREAKOUT_SUBPROCESS") != NULL)
+    return TRUE;
+
+  if (!priv->run_on_host)
+    return FALSE;
+
+  return is_contained;
+}
+
+static void
+ide_subprocess_launcher_spawn_host_worker (GTask        *task,
+                                           gpointer      source_object,
+                                           gpointer      task_data,
+                                           GCancellable *cancellable)
+{
+  IdeSubprocessLauncher *self = source_object;
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+  g_autoptr(IdeSubprocess) process = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+#ifdef IDE_ENABLE_TRACE
+  {
+    g_autofree gchar *str = NULL;
+    g_autofree gchar *env = NULL;
+    str = g_strjoinv (" ", (gchar **)priv->argv->pdata);
+    env = g_strjoinv (" ", (gchar **)priv->environ->pdata);
+    IDE_TRACE_MSG ("Launching '%s' with environment %s", str, env);
+  }
+#endif
+
+  process = _ide_breakout_subprocess_new (priv->cwd,
+                                          (const gchar * const *)priv->argv->pdata,
+                                          (const gchar * const *)priv->environ->pdata,
+                                          priv->flags,
+                                          priv->clear_env,
+                                          cancellable,
+                                          &error);
+
+  if (process == NULL)
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  if (cancellable != NULL)
+    {
+      g_signal_connect_object (cancellable,
+                               "cancelled",
+                               G_CALLBACK (ide_subprocess_launcher_kill_host_process),
+                               process,
+                               0);
+    }
+
+  g_task_return_pointer (task, g_steal_pointer (&process), g_object_unref);
+
+  IDE_EXIT;
+}
+
 static void
 ide_subprocess_launcher_spawn_worker (GTask        *task,
                                       gpointer      source_object,
@@ -112,16 +219,19 @@ ide_subprocess_launcher_spawn_worker (GTask        *task,
   IdeSubprocessLauncher *self = source_object;
   IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
   g_autoptr(GSubprocessLauncher) launcher = NULL;
-  GSubprocess *ret;
-  GError *error = NULL;
+  g_autoptr(GSubprocess) real = NULL;
+  g_autoptr(IdeSubprocess) wrapped = NULL;
+  g_autoptr(GError) error = NULL;
 
   g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
 
 #ifdef IDE_ENABLE_TRACE
   {
     g_autofree gchar *str = NULL;
+    g_autofree gchar *env = NULL;
     str = g_strjoinv (" ", (gchar **)priv->argv->pdata);
-    IDE_TRACE_MSG ("Launching '%s'", str);
+    env = g_strjoinv (" ", (gchar **)priv->environ->pdata);
+    IDE_TRACE_MSG ("Launching '%s' with environment %s", str, env);
   }
 #endif
 
@@ -130,30 +240,31 @@ ide_subprocess_launcher_spawn_worker (GTask        *task,
   g_subprocess_launcher_set_cwd (launcher, priv->cwd);
   if (priv->environ->len > 1)
     g_subprocess_launcher_set_environ (launcher, (gchar **)priv->environ->pdata);
-  ret = g_subprocess_launcher_spawnv (launcher,
-                                      (const gchar * const *)priv->argv->pdata,
-                                      &error);
+  real = g_subprocess_launcher_spawnv (launcher,
+                                       (const gchar * const *)priv->argv->pdata,
+                                       &error);
 
-  if (ret == NULL)
+  if (real == NULL)
     {
-      g_task_return_error (task, error);
+      g_task_return_error (task, g_steal_pointer (&error));
       return;
     }
 
   if (cancellable != NULL)
     {
-      g_signal_connect_data (cancellable,
-                             "cancelled",
-                             G_CALLBACK (ide_subprocess_launcher_kill_process_group),
-                             g_object_ref (ret),
-                             (GClosureNotify)g_object_unref,
-                             0);
+      g_signal_connect_object (cancellable,
+                               "cancelled",
+                               G_CALLBACK (ide_subprocess_launcher_kill_process_group),
+                               real,
+                               0);
     }
 
-  g_task_return_pointer (task, ret, g_object_unref);
+  wrapped = ide_simple_subprocess_new (real);
+
+  g_task_return_pointer (task, g_steal_pointer (&wrapped), g_object_unref);
 }
 
-static GSubprocess *
+static IdeSubprocess *
 ide_subprocess_launcher_real_spawn_sync (IdeSubprocessLauncher  *self,
                                          GCancellable           *cancellable,
                                          GError                **error)
@@ -164,7 +275,12 @@ ide_subprocess_launcher_real_spawn_sync (IdeSubprocessLauncher  *self,
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   task = g_task_new (self, cancellable, NULL, NULL);
-  g_task_run_in_thread_sync (task, ide_subprocess_launcher_spawn_worker);
+  g_task_set_source_tag (task, ide_subprocess_launcher_real_spawn_sync);
+
+  if (should_use_breakout_process (self))
+    g_task_run_in_thread_sync (task, ide_subprocess_launcher_spawn_host_worker);
+  else
+    g_task_run_in_thread_sync (task, ide_subprocess_launcher_spawn_worker);
 
   return g_task_propagate_pointer (task, error);
 }
@@ -186,10 +302,15 @@ ide_subprocess_launcher_real_spawn_async (IdeSubprocessLauncher *self,
    */
 
   task = g_task_new (self, cancellable, callback, user_data);
-  g_task_run_in_thread (task, ide_subprocess_launcher_spawn_worker);
+  g_task_set_source_tag (task, ide_subprocess_launcher_real_spawn_async);
+
+  if (should_use_breakout_process (self))
+    g_task_run_in_thread (task, ide_subprocess_launcher_spawn_host_worker);
+  else
+    g_task_run_in_thread (task, ide_subprocess_launcher_spawn_worker);
 }
 
-static GSubprocess *
+static IdeSubprocess *
 ide_subprocess_launcher_real_spawn_finish (IdeSubprocessLauncher  *self,
                                            GAsyncResult           *result,
                                            GError                **error)
@@ -223,6 +344,10 @@ ide_subprocess_launcher_get_property (GObject    *object,
 
   switch (prop_id)
     {
+    case PROP_CLEAR_ENV:
+      g_value_set_boolean (value, ide_subprocess_launcher_get_clear_env (self));
+      break;
+
     case PROP_CWD:
       g_value_set_string (value, ide_subprocess_launcher_get_cwd (self));
       break;
@@ -235,6 +360,10 @@ ide_subprocess_launcher_get_property (GObject    *object,
       g_value_set_boxed (value, ide_subprocess_launcher_get_environ (self));
       break;
 
+    case PROP_RUN_ON_HOST:
+      g_value_set_boolean (value, ide_subprocess_launcher_get_run_on_host (self));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -250,6 +379,10 @@ ide_subprocess_launcher_set_property (GObject      *object,
 
   switch (prop_id)
     {
+    case PROP_CLEAR_ENV:
+      ide_subprocess_launcher_set_clear_env (self, g_value_get_boolean (value));
+      break;
+
     case PROP_CWD:
       ide_subprocess_launcher_set_cwd (self, g_value_get_string (value));
       break;
@@ -262,6 +395,10 @@ ide_subprocess_launcher_set_property (GObject      *object,
       ide_subprocess_launcher_set_environ (self, g_value_get_boxed (value));
       break;
 
+    case PROP_RUN_ON_HOST:
+      ide_subprocess_launcher_set_run_on_host (self, g_value_get_boolean (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -280,6 +417,13 @@ ide_subprocess_launcher_class_init (IdeSubprocessLauncherClass *klass)
   klass->spawn_async = ide_subprocess_launcher_real_spawn_async;
   klass->spawn_finish = ide_subprocess_launcher_real_spawn_finish;
 
+  properties [PROP_CLEAR_ENV] =
+    g_param_spec_boolean ("clean-env",
+                          "Clear Environment",
+                          "If the environment should be cleared before setting environment variables.",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   properties [PROP_CWD] =
     g_param_spec_string ("cwd",
                          "Current Working Directory",
@@ -302,6 +446,13 @@ ide_subprocess_launcher_class_init (IdeSubprocessLauncherClass *klass)
                         G_TYPE_STRV,
                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  properties [PROP_RUN_ON_HOST] =
+    g_param_spec_boolean ("run-on-host",
+                          "Run on Host",
+                          "Run on Host",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
@@ -447,9 +598,9 @@ ide_subprocess_launcher_spawn_async (IdeSubprocessLauncher *self,
  *
  * Complete a request to asynchronously spawn a process.
  *
- * Returns: (transfer full): A #GSubprocess or %NULL upon error.
+ * Returns: (transfer full): A #IdeSubprocess or %NULL upon error.
  */
-GSubprocess *
+IdeSubprocess *
 ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher  *self,
                                       GAsyncResult           *result,
                                       GError                **error)
@@ -465,9 +616,9 @@ ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher  *self,
  *
  * Synchronously spawn a process using the internal state.
  *
- * Returns: (transfer full): A #GSubprocess or %NULL upon error.
+ * Returns: (transfer full): A #IdeSubprocess or %NULL upon error.
  */
-GSubprocess *
+IdeSubprocess *
 ide_subprocess_launcher_spawn_sync (IdeSubprocessLauncher  *self,
                                     GCancellable           *cancellable,
                                     GError                **error)
@@ -566,3 +717,73 @@ ide_subprocess_launcher_pop_argv (IdeSubprocessLauncher *self)
 
   return ret;
 }
+
+/**
+ * ide_subprocess_launcher_get_run_on_host:
+ *
+ * Gets if the process should be executed on the host system. This might be
+ * useful for situations where running in a contained environment is not
+ * sufficient to perform the given task.
+ *
+ * Currently, only flatpak is supported for breaking out of the containment
+ * zone and requires the application was built with --allow=devel.
+ *
+ * Returns: %TRUE if the process should be executed outside the containment zone.
+ */
+gboolean
+ide_subprocess_launcher_get_run_on_host (IdeSubprocessLauncher *self)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), FALSE);
+
+  return priv->run_on_host;
+}
+
+/**
+ * ide_subprocess_launcher_set_run_on_host:
+ *
+ * Sets the #IdeSubprocessLauncher:run-on-host property. See
+ * ide_subprocess_launcher_get_run_on_host() for more information.
+ */
+void
+ide_subprocess_launcher_set_run_on_host (IdeSubprocessLauncher *self,
+                                         gboolean               run_on_host)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  run_on_host = !!run_on_host;
+
+  if (priv->run_on_host != run_on_host)
+    {
+      priv->run_on_host = run_on_host;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUN_ON_HOST]);
+    }
+}
+
+gboolean
+ide_subprocess_launcher_get_clear_env (IdeSubprocessLauncher *self)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), FALSE);
+
+  return priv->clear_env;
+}
+
+void
+ide_subprocess_launcher_set_clear_env (IdeSubprocessLauncher *self,
+                                       gboolean               clear_env)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+  clear_env = !!clear_env;
+
+  if (priv->clear_env != clear_env)
+    {
+      priv->clear_env = clear_env;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLEAR_ENV]);
+    }
+}
diff --git a/libide/workers/ide-subprocess-launcher.h b/libide/subprocess/ide-subprocess-launcher.h
similarity index 72%
rename from libide/workers/ide-subprocess-launcher.h
rename to libide/subprocess/ide-subprocess-launcher.h
index b72813a..4b2e6af 100644
--- a/libide/workers/ide-subprocess-launcher.h
+++ b/libide/subprocess/ide-subprocess-launcher.h
@@ -33,16 +33,25 @@ struct _IdeSubprocessLauncherClass
 {
   GObjectClass parent_class;
 
-  GSubprocess *(*spawn_sync)   (IdeSubprocessLauncher  *self,
-                                GCancellable           *cancellable,
-                                GError                **error);
-  void         (*spawn_async)  (IdeSubprocessLauncher  *self,
-                                GCancellable           *cancellable,
-                                GAsyncReadyCallback     callback,
-                                gpointer                user_data);
-  GSubprocess *(*spawn_finish) (IdeSubprocessLauncher  *self,
-                                GAsyncResult           *result,
-                                GError                **error);
+  IdeSubprocess *(*spawn_sync)   (IdeSubprocessLauncher  *self,
+                                  GCancellable           *cancellable,
+                                  GError                **error);
+  void           (*spawn_async)  (IdeSubprocessLauncher  *self,
+                                  GCancellable           *cancellable,
+                                  GAsyncReadyCallback     callback,
+                                  gpointer                user_data);
+  IdeSubprocess *(*spawn_finish) (IdeSubprocessLauncher  *self,
+                                  GAsyncResult           *result,
+                                  GError                **error);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
 };
 
 IdeSubprocessLauncher *ide_subprocess_launcher_new                 (GSubprocessFlags        flags);
@@ -52,6 +61,12 @@ void                   ide_subprocess_launcher_set_cwd             (IdeSubproces
 GSubprocessFlags       ide_subprocess_launcher_get_flags           (IdeSubprocessLauncher  *self);
 void                   ide_subprocess_launcher_set_flags           (IdeSubprocessLauncher  *self,
                                                                     GSubprocessFlags        flags);
+gboolean               ide_subprocess_launcher_get_run_on_host     (IdeSubprocessLauncher  *self);
+void                   ide_subprocess_launcher_set_run_on_host     (IdeSubprocessLauncher  *self,
+                                                                    gboolean                run_on_host);
+gboolean               ide_subprocess_launcher_get_clear_env       (IdeSubprocessLauncher  *self);
+void                   ide_subprocess_launcher_set_clear_env       (IdeSubprocessLauncher  *self,
+                                                                    gboolean                clear_env);
 const gchar * const   *ide_subprocess_launcher_get_environ         (IdeSubprocessLauncher  *self);
 void                   ide_subprocess_launcher_set_environ         (IdeSubprocessLauncher  *self,
                                                                     const gchar * const    *environ_);
@@ -66,14 +81,14 @@ void                   ide_subprocess_launcher_push_args           (IdeSubproces
 void                   ide_subprocess_launcher_push_argv           (IdeSubprocessLauncher  *self,
                                                                     const gchar            *argv);
 gchar                 *ide_subprocess_launcher_pop_argv            (IdeSubprocessLauncher  *self) 
G_GNUC_WARN_UNUSED_RESULT;
-GSubprocess           *ide_subprocess_launcher_spawn_sync          (IdeSubprocessLauncher  *self,
+IdeSubprocess         *ide_subprocess_launcher_spawn_sync          (IdeSubprocessLauncher  *self,
                                                                     GCancellable           *cancellable,
                                                                     GError                **error);
 void                   ide_subprocess_launcher_spawn_async         (IdeSubprocessLauncher  *self,
                                                                     GCancellable           *cancellable,
                                                                     GAsyncReadyCallback     callback,
                                                                     gpointer                user_data);
-GSubprocess           *ide_subprocess_launcher_spawn_finish        (IdeSubprocessLauncher  *self,
+IdeSubprocess         *ide_subprocess_launcher_spawn_finish        (IdeSubprocessLauncher  *self,
                                                                     GAsyncResult           *result,
                                                                     GError                **error);
 
diff --git a/libide/subprocess/ide-subprocess.c b/libide/subprocess/ide-subprocess.c
new file mode 100644
index 0000000..507c965
--- /dev/null
+++ b/libide/subprocess/ide-subprocess.c
@@ -0,0 +1,328 @@
+/* ide-subprocess.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-subprocess"
+
+#include "ide-subprocess.h"
+
+G_DEFINE_INTERFACE (IdeSubprocess, ide_subprocess, G_TYPE_OBJECT)
+
+static void
+ide_subprocess_default_init (IdeSubprocessInterface *iface)
+{
+}
+
+#define WRAP_INTERFACE_METHOD(self, name, default_return, ...) \
+  ((IDE_SUBPROCESS_GET_IFACE(self)->name != NULL) ? \
+    IDE_SUBPROCESS_GET_IFACE(self)->name (self, ##__VA_ARGS__) : \
+    default_return)
+
+const gchar *
+ide_subprocess_get_identifier (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
+
+  return WRAP_INTERFACE_METHOD (self, get_identifier, NULL);
+}
+
+/**
+ * ide_subprocess_get_stdout_pipe:
+ *
+ * Returns: (transfer none): A #GInputStream or %NULL.
+ */
+GInputStream *
+ide_subprocess_get_stdout_pipe  (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
+
+  return WRAP_INTERFACE_METHOD (self, get_stdout_pipe, NULL);
+}
+
+/**
+ * ide_subprocess_get_stderr_pipe:
+ *
+ * Returns: (transfer none): A #GInputStream or %NULL.
+ */
+GInputStream *
+ide_subprocess_get_stderr_pipe (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
+
+  return WRAP_INTERFACE_METHOD (self, get_stderr_pipe, NULL);
+}
+
+/**
+ * ide_subprocess_get_stdin_pipe:
+ *
+ * Returns: (transfer none): A #GOutputStream or %NULL.
+ */
+GOutputStream *
+ide_subprocess_get_stdin_pipe (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
+
+  return WRAP_INTERFACE_METHOD (self, get_stdin_pipe, NULL);
+}
+
+gboolean
+ide_subprocess_wait (IdeSubprocess  *self,
+                     GCancellable   *cancellable,
+                     GError        **error)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  return WRAP_INTERFACE_METHOD (self, wait, FALSE, cancellable, error);
+}
+
+gboolean
+ide_subprocess_wait_check (IdeSubprocess  *self,
+                           GCancellable   *cancellable,
+                           GError        **error)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  return ide_subprocess_wait (self, cancellable, error) &&
+         ide_subprocess_check_exit_status (self, error);
+}
+
+void
+ide_subprocess_wait_async (IdeSubprocess       *self,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_SUBPROCESS (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  WRAP_INTERFACE_METHOD (self, wait_async, NULL, cancellable, callback, user_data);
+}
+
+gboolean
+ide_subprocess_wait_finish (IdeSubprocess  *self,
+                            GAsyncResult   *result,
+                            GError        **error)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+  return WRAP_INTERFACE_METHOD (self, wait_finish, FALSE, result, error);
+}
+
+static void
+ide_subprocess_wait_check_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  IdeSubprocess *self = (IdeSubprocess *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_SUBPROCESS (self));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_subprocess_wait_finish (self, result, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  if (!ide_subprocess_check_exit_status (self, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  g_task_return_boolean (task, TRUE);
+}
+
+void
+ide_subprocess_wait_check_async (IdeSubprocess       *self,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_SUBPROCESS (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_subprocess_wait_check_async);
+
+  ide_subprocess_wait_async (self,
+                             cancellable,
+                             ide_subprocess_wait_check_cb,
+                             g_steal_pointer (&task));
+}
+
+gboolean
+ide_subprocess_wait_check_finish (IdeSubprocess  *self,
+                                  GAsyncResult   *result,
+                                  GError        **error)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+  g_return_val_if_fail (g_task_is_valid (G_TASK (result), self), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), FALSE);
+}
+
+gboolean
+ide_subprocess_get_successful (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+  return WRAP_INTERFACE_METHOD (self, get_successful, FALSE);
+}
+
+gboolean
+ide_subprocess_get_if_exited (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+  return WRAP_INTERFACE_METHOD (self, get_if_exited, FALSE);
+}
+
+gint
+ide_subprocess_get_exit_status (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), 0);
+
+  return WRAP_INTERFACE_METHOD (self, get_exit_status, 0);
+}
+
+gboolean
+ide_subprocess_get_if_signaled (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+  return WRAP_INTERFACE_METHOD (self, get_if_signaled, FALSE);
+}
+
+gint
+ide_subprocess_get_term_sig (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), 0);
+
+  return WRAP_INTERFACE_METHOD (self, get_term_sig, 0);
+}
+
+gint
+ide_subprocess_get_status (IdeSubprocess *self)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), 0);
+
+  return WRAP_INTERFACE_METHOD (self, get_status, 0);
+}
+
+void
+ide_subprocess_send_signal (IdeSubprocess *self,
+                            gint           signal_num)
+{
+  g_return_if_fail (IDE_IS_SUBPROCESS (self));
+
+  WRAP_INTERFACE_METHOD (self, send_signal, NULL, signal_num);
+}
+
+void
+ide_subprocess_force_exit (IdeSubprocess *self)
+{
+  g_return_if_fail (IDE_IS_SUBPROCESS (self));
+
+  WRAP_INTERFACE_METHOD (self, force_exit, NULL);
+}
+
+gboolean
+ide_subprocess_communicate (IdeSubprocess  *self,
+                            GBytes         *stdin_buf,
+                            GCancellable   *cancellable,
+                            GBytes        **stdout_buf,
+                            GBytes        **stderr_buf,
+                            GError        **error)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  return WRAP_INTERFACE_METHOD (self, communicate, FALSE, stdin_buf, cancellable, stdout_buf, stderr_buf, 
error);
+}
+
+/**
+ * ide_subprocess_communicate_utf8:
+ * @self: an #IdeSubprocess
+ * @stdin_buf: (nullable): input to deliver to the subprocesses stdin stream
+ * @cancellable: (nullable): an optional #GCancellable
+ * @stdout_buf: (out) (nullable): an optional location for the stdout contents
+ * @stderr_buf: (out) (nullable): an optional location for the stderr contents
+ *
+ * This process acts identical to g_subprocess_communicate_utf8().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+ide_subprocess_communicate_utf8 (IdeSubprocess  *self,
+                                 const gchar    *stdin_buf,
+                                 GCancellable   *cancellable,
+                                 gchar         **stdout_buf,
+                                 gchar         **stderr_buf,
+                                 GError        **error)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  return WRAP_INTERFACE_METHOD (self, communicate_utf8, FALSE, stdin_buf, cancellable, stdout_buf, 
stderr_buf, error);
+}
+
+void
+ide_subprocess_communicate_async (IdeSubprocess       *self,
+                                  GBytes              *stdin_buf,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_SUBPROCESS (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  WRAP_INTERFACE_METHOD (self, communicate_async, NULL, stdin_buf, cancellable, callback, user_data);
+}
+
+gboolean
+ide_subprocess_communicate_finish (IdeSubprocess  *self,
+                                   GAsyncResult   *result,
+                                   GBytes        **stdout_buf,
+                                   GBytes        **stderr_buf,
+                                   GError        **error)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return WRAP_INTERFACE_METHOD (self, communicate_finish, FALSE, result, stdout_buf, stderr_buf, error);
+}
+
+gboolean
+ide_subprocess_check_exit_status (IdeSubprocess  *self,
+                                  GError        **error)
+{
+  gint exit_status;
+
+  g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+  exit_status = ide_subprocess_get_exit_status (self);
+
+  return g_spawn_check_exit_status (exit_status, error);
+}
diff --git a/libide/subprocess/ide-subprocess.h b/libide/subprocess/ide-subprocess.h
new file mode 100644
index 0000000..557bc98
--- /dev/null
+++ b/libide/subprocess/ide-subprocess.h
@@ -0,0 +1,141 @@
+/* ide-subprocess.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_SUBPROCESS_H
+#define IDE_SUBPROCESS_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SUBPROCESS (ide_subprocess_get_type())
+
+G_DECLARE_INTERFACE (IdeSubprocess, ide_subprocess, IDE, SUBPROCESS, GObject)
+
+struct _IdeSubprocessInterface
+{
+  GTypeInterface parent_interface;
+
+  const gchar   *(*get_identifier)          (IdeSubprocess        *self);
+  GInputStream  *(*get_stdout_pipe)         (IdeSubprocess        *self);
+  GInputStream  *(*get_stderr_pipe)         (IdeSubprocess        *self);
+  GOutputStream *(*get_stdin_pipe)          (IdeSubprocess        *self);
+  gboolean       (*wait)                    (IdeSubprocess        *self,
+                                             GCancellable         *cancellable,
+                                             GError              **error);
+  void           (*wait_async)              (IdeSubprocess        *self,
+                                             GCancellable         *cancellable,
+                                             GAsyncReadyCallback   callback,
+                                             gpointer              user_data);
+  gboolean       (*wait_finish)             (IdeSubprocess        *self,
+                                             GAsyncResult         *result,
+                                             GError              **error);
+  gboolean       (*get_successful)          (IdeSubprocess        *self);
+  gboolean       (*get_if_exited)           (IdeSubprocess        *self);
+  gint           (*get_exit_status)         (IdeSubprocess        *self);
+  gboolean       (*get_if_signaled)         (IdeSubprocess        *self);
+  gint           (*get_term_sig)            (IdeSubprocess        *self);
+  gint           (*get_status)              (IdeSubprocess        *self);
+  void           (*send_signal)             (IdeSubprocess        *self,
+                                             gint                  signal_num);
+  void           (*force_exit)              (IdeSubprocess        *self);
+  gboolean       (*communicate)             (IdeSubprocess        *self,
+                                             GBytes               *stdin_buf,
+                                             GCancellable         *cancellable,
+                                             GBytes              **stdout_buf,
+                                             GBytes              **stderr_buf,
+                                             GError              **error);
+  gboolean       (*communicate_utf8)        (IdeSubprocess        *self,
+                                             const gchar          *stdin_buf,
+                                             GCancellable         *cancellable,
+                                             gchar               **stdout_buf,
+                                             gchar               **stderr_buf,
+                                             GError              **error);
+  void           (*communicate_async)       (IdeSubprocess        *self,
+                                             GBytes               *stdin_buf,
+                                             GCancellable         *cancellable,
+                                             GAsyncReadyCallback   callback,
+                                             gpointer              user_data);
+  gboolean       (*communicate_finish)      (IdeSubprocess        *self,
+                                             GAsyncResult         *result,
+                                             GBytes              **stdout_buf,
+                                             GBytes              **stderr_buf,
+                                             GError              **error);
+};
+
+const gchar   *ide_subprocess_get_identifier     (IdeSubprocess *self);
+GInputStream  *ide_subprocess_get_stdout_pipe    (IdeSubprocess *self);
+GInputStream  *ide_subprocess_get_stderr_pipe    (IdeSubprocess *self);
+GOutputStream *ide_subprocess_get_stdin_pipe     (IdeSubprocess *self);
+gboolean       ide_subprocess_wait               (IdeSubprocess        *self,
+                                                  GCancellable         *cancellable,
+                                                  GError              **error);
+gboolean       ide_subprocess_wait_check         (IdeSubprocess        *self,
+                                                  GCancellable         *cancellable,
+                                                  GError              **error);
+void           ide_subprocess_wait_async         (IdeSubprocess        *self,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+gboolean       ide_subprocess_wait_finish        (IdeSubprocess        *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
+void           ide_subprocess_wait_check_async   (IdeSubprocess        *self,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+gboolean       ide_subprocess_wait_check_finish  (IdeSubprocess        *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
+gboolean       ide_subprocess_check_exit_status  (IdeSubprocess        *self,
+                                                  GError              **error);
+gboolean       ide_subprocess_get_successful     (IdeSubprocess        *self);
+gboolean       ide_subprocess_get_if_exited      (IdeSubprocess        *self);
+gint           ide_subprocess_get_exit_status    (IdeSubprocess        *self);
+gboolean       ide_subprocess_get_if_signaled    (IdeSubprocess        *self);
+gint           ide_subprocess_get_term_sig       (IdeSubprocess        *self);
+gint           ide_subprocess_get_status         (IdeSubprocess        *self);
+void           ide_subprocess_send_signal        (IdeSubprocess        *self,
+                                                  gint                  signal_num);
+void           ide_subprocess_force_exit         (IdeSubprocess        *self);
+gboolean       ide_subprocess_communicate        (IdeSubprocess        *self,
+                                                  GBytes               *stdin_buf,
+                                                  GCancellable         *cancellable,
+                                                  GBytes              **stdout_buf,
+                                                  GBytes              **stderr_buf,
+                                                  GError              **error);
+gboolean       ide_subprocess_communicate_utf8   (IdeSubprocess        *self,
+                                                  const gchar          *stdin_buf,
+                                                  GCancellable         *cancellable,
+                                                  gchar               **stdout_buf,
+                                                  gchar               **stderr_buf,
+                                                  GError              **error);
+void           ide_subprocess_communicate_async  (IdeSubprocess        *self,
+                                                  GBytes               *stdin_buf,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+gboolean       ide_subprocess_communicate_finish (IdeSubprocess        *self,
+                                                  GAsyncResult         *result,
+                                                  GBytes              **stdout_buf,
+                                                  GBytes              **stderr_buf,
+                                                  GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_SUBPROCESS_H */
diff --git a/plugins/autotools/ide-autotools-build-system.c b/plugins/autotools/ide-autotools-build-system.c
index 4f64866..46cf4a1 100644
--- a/plugins/autotools/ide-autotools-build-system.c
+++ b/plugins/autotools/ide-autotools-build-system.c
@@ -932,11 +932,11 @@ simple_make_command_cb (GObject      *object,
                         GAsyncResult *result,
                         gpointer      user_data)
 {
-  GSubprocess *subprocess = (GSubprocess *)object;
+  IdeSubprocess *subprocess = (IdeSubprocess *)object;
   g_autoptr(GTask) task = user_data;
   GError *error = NULL;
 
-  if (!g_subprocess_wait_check_finish (subprocess, result, &error))
+  if (!ide_subprocess_wait_check_finish (subprocess, result, &error))
     g_task_return_error (task, error);
   else
     g_task_return_boolean (task, TRUE);
@@ -949,7 +949,7 @@ simple_make_command (GFile            *directory,
                      IdeConfiguration *configuration)
 {
   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
   GCancellable *cancellable;
   IdeRuntime *runtime;
   GError *error = NULL;
@@ -994,16 +994,21 @@ simple_make_command (GFile            *directory,
 
   ide_subprocess_launcher_push_argv (launcher, target);
 
+  g_task_set_return_on_cancel (task, FALSE);
+
+  if (g_task_return_error_if_cancelled (task))
+    return;
+
   if (NULL == (subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, &error)))
     {
       g_task_return_error (task, error);
       return;
     }
 
-  g_subprocess_wait_check_async (subprocess,
-                                 g_task_get_cancellable (task),
-                                 simple_make_command_cb,
-                                 g_object_ref (task));
+  ide_subprocess_wait_check_async (subprocess,
+                                   cancellable,
+                                   simple_make_command_cb,
+                                   g_steal_pointer (&task));
 }
 
 static void
diff --git a/plugins/autotools/ide-autotools-build-task.c b/plugins/autotools/ide-autotools-build-task.c
index 8f43973..24b332b 100644
--- a/plugins/autotools/ide-autotools-build-task.c
+++ b/plugins/autotools/ide-autotools-build-task.c
@@ -16,6 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#define G_LOG_DOMAIN "ide-autotools-build-task"
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
@@ -75,30 +77,30 @@ enum {
   LAST_PROP
 };
 
-static GSubprocess *log_and_spawn     (IdeAutotoolsBuildTask  *self,
-                                       IdeSubprocessLauncher  *launcher,
-                                       GCancellable           *cancellable,
-                                       GError                **error,
-                                       const gchar            *argv0,
-                                       ...) G_GNUC_NULL_TERMINATED;
-static gboolean     step_mkdirs       (GTask                  *task,
-                                       IdeAutotoolsBuildTask  *self,
-                                       WorkerState            *state,
-                                       GCancellable           *cancellable);
-static gboolean     step_autogen      (GTask                  *task,
-                                       IdeAutotoolsBuildTask  *self,
-                                       WorkerState            *state,
-                                       GCancellable           *cancellable);
-static gboolean     step_configure    (GTask                  *task,
-                                       IdeAutotoolsBuildTask  *self,
-                                       WorkerState            *state,
-                                       GCancellable           *cancellable);
-static gboolean     step_make_all     (GTask                  *task,
-                                       IdeAutotoolsBuildTask  *self,
-                                       WorkerState            *state,
-                                       GCancellable           *cancellable);
-static void         apply_environment (IdeAutotoolsBuildTask  *self,
-                                       IdeSubprocessLauncher  *launcher);
+static IdeSubprocess *log_and_spawn     (IdeAutotoolsBuildTask  *self,
+                                         IdeSubprocessLauncher  *launcher,
+                                         GCancellable           *cancellable,
+                                         GError                **error,
+                                         const gchar            *argv0,
+                                         ...) G_GNUC_NULL_TERMINATED;
+static gboolean       step_mkdirs       (GTask                  *task,
+                                         IdeAutotoolsBuildTask  *self,
+                                         WorkerState            *state,
+                                         GCancellable           *cancellable);
+static gboolean       step_autogen      (GTask                  *task,
+                                         IdeAutotoolsBuildTask  *self,
+                                         WorkerState            *state,
+                                         GCancellable           *cancellable);
+static gboolean       step_configure    (GTask                  *task,
+                                         IdeAutotoolsBuildTask  *self,
+                                         WorkerState            *state,
+                                         GCancellable           *cancellable);
+static gboolean       step_make_all     (GTask                  *task,
+                                         IdeAutotoolsBuildTask  *self,
+                                         WorkerState            *state,
+                                         GCancellable           *cancellable);
+static void           apply_environment (IdeAutotoolsBuildTask  *self,
+                                         IdeSubprocessLauncher  *launcher);
 
 static GParamSpec *properties [LAST_PROP];
 static WorkStep workSteps [] = {
@@ -704,7 +706,7 @@ ide_autotools_build_task_execute_finish (IdeAutotoolsBuildTask  *self,
   state = g_task_get_task_data (G_TASK (task));
   sequence = ide_configuration_get_sequence (self->configuration);
 
-  if ((state != NULL) &&  (state->sequence == sequence))
+  if ((state != NULL) && (state->sequence == sequence))
     ide_configuration_set_dirty (self->configuration, FALSE);
 
   ret = g_task_propagate_boolean (task, error);
@@ -735,7 +737,7 @@ log_in_main (gpointer data)
   return G_SOURCE_REMOVE;
 }
 
-static GSubprocess *
+static IdeSubprocess *
 log_and_spawn (IdeAutotoolsBuildTask  *self,
                IdeSubprocessLauncher  *launcher,
                GCancellable           *cancellable,
@@ -743,7 +745,7 @@ log_and_spawn (IdeAutotoolsBuildTask  *self,
                const gchar           *argv0,
                ...)
 {
-  GSubprocess *ret;
+  IdeSubprocess *ret;
   struct {
     IdeBuildResult *result;
     gchar *message;
@@ -830,7 +832,7 @@ step_autogen (GTask                 *task,
   g_autofree gchar *autogen_sh_path = NULL;
   g_autofree gchar *configure_path = NULL;
   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(GSubprocess) process = NULL;
+  g_autoptr(IdeSubprocess) process = NULL;
   GError *error = NULL;
 
   g_assert (G_IS_TASK (task));
@@ -889,7 +891,7 @@ step_autogen (GTask                 *task,
 
   ide_build_result_log_subprocess (IDE_BUILD_RESULT (self), process);
 
-  if (!g_subprocess_wait_check (process, cancellable, &error))
+  if (!ide_subprocess_wait_check (process, cancellable, &error))
     {
       g_task_return_error (task, error);
       return FALSE;
@@ -915,7 +917,7 @@ step_configure (GTask                 *task,
                 GCancellable          *cancellable)
 {
   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(GSubprocess) process = NULL;
+  g_autoptr(IdeSubprocess) process = NULL;
   g_autofree gchar *makefile_path = NULL;
   g_autofree gchar *config_log = NULL;
   GError *error = NULL;
@@ -959,7 +961,7 @@ step_configure (GTask                 *task,
 
   ide_build_result_log_subprocess (IDE_BUILD_RESULT (self), process);
 
-  if (!g_subprocess_wait_check (process, cancellable, &error))
+  if (!ide_subprocess_wait_check (process, cancellable, &error))
     {
       g_task_return_error (task, error);
       return FALSE;
@@ -981,7 +983,7 @@ step_make_all  (GTask                 *task,
                 GCancellable          *cancellable)
 {
   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(GSubprocess) process = NULL;
+  g_autoptr(IdeSubprocess) process = NULL;
   const gchar * const *targets;
   const gchar *make = NULL;
   gchar *default_targets[] = { "all", NULL };
@@ -1047,7 +1049,7 @@ step_make_all  (GTask                 *task,
 
       ide_build_result_log_subprocess (IDE_BUILD_RESULT (self), process);
 
-      if (!g_subprocess_wait_check (process, cancellable, &error))
+      if (!ide_subprocess_wait_check (process, cancellable, &error))
         {
           g_task_return_error (task, error);
           return FALSE;
diff --git a/plugins/autotools/ide-makecache.c b/plugins/autotools/ide-makecache.c
index 14bd650..c4ecb82 100644
--- a/plugins/autotools/ide-makecache.c
+++ b/plugins/autotools/ide-makecache.c
@@ -1943,7 +1943,7 @@ ide_makecache_get_build_targets_worker (GTask        *task,
 
   for (guint j = 0; j < makedirs->len; j++)
     {
-      g_autoptr(GSubprocess) subprocess = NULL;
+      g_autoptr(IdeSubprocess) subprocess = NULL;
       g_autoptr(GHashTable) amdirs = NULL;
       g_autofree gchar *path = NULL;
       GFile *makedir;
@@ -1970,7 +1970,7 @@ ide_makecache_get_build_targets_worker (GTask        *task,
        * Write our helper target that will include the Makefile and then print
        * debug variables we care about.
        */
-      if (!g_subprocess_communicate_utf8 (subprocess, PRINT_VARS, NULL, &stdout_buf, NULL, &error))
+      if (!ide_subprocess_communicate_utf8 (subprocess, PRINT_VARS, NULL, &stdout_buf, NULL, &error))
         {
           g_task_return_error (task, error);
           IDE_GOTO (failure);
diff --git a/plugins/flatpak/gbp-flatpak-runtime.c b/plugins/flatpak/gbp-flatpak-runtime.c
index e8e35bc..c2d2211 100644
--- a/plugins/flatpak/gbp-flatpak-runtime.c
+++ b/plugins/flatpak/gbp-flatpak-runtime.c
@@ -61,11 +61,11 @@ get_build_directory (GbpFlatpakRuntime *self)
 
 static gboolean
 gbp_flatpak_runtime_contains_program_in_path (IdeRuntime   *runtime,
-                                          const gchar  *program,
-                                          GCancellable *cancellable)
+                                              const gchar  *program,
+                                              GCancellable *cancellable)
 {
   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
-  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
 
   g_assert (IDE_IS_RUNTIME (runtime));
   g_assert (program != NULL);
@@ -78,14 +78,14 @@ gbp_flatpak_runtime_contains_program_in_path (IdeRuntime   *runtime,
 
   subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, NULL);
 
-  return (subprocess != NULL) && g_subprocess_wait_check (subprocess, cancellable, NULL);
+  return (subprocess != NULL) && ide_subprocess_wait_check (subprocess, cancellable, NULL);
 }
 
 static void
 gbp_flatpak_runtime_prebuild_worker (GTask        *task,
-                                 gpointer      source_object,
-                                 gpointer      task_data,
-                                 GCancellable *cancellable)
+                                     gpointer      source_object,
+                                     gpointer      task_data,
+                                     GCancellable *cancellable)
 {
   GbpFlatpakRuntime *self = source_object;
   g_autofree gchar *build_path = NULL;
@@ -153,9 +153,9 @@ gbp_flatpak_runtime_prebuild_worker (GTask        *task,
 
 static void
 gbp_flatpak_runtime_prebuild_async (IdeRuntime          *runtime,
-                                GCancellable        *cancellable,
-                                GAsyncReadyCallback  callback,
-                                gpointer             user_data)
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
 {
   GbpFlatpakRuntime *self = (GbpFlatpakRuntime *)runtime;
   g_autoptr(GTask) task = NULL;
@@ -169,8 +169,8 @@ gbp_flatpak_runtime_prebuild_async (IdeRuntime          *runtime,
 
 static gboolean
 gbp_flatpak_runtime_prebuild_finish (IdeRuntime    *runtime,
-                                 GAsyncResult  *result,
-                                 GError       **error)
+                                     GAsyncResult  *result,
+                                     GError       **error)
 {
   GbpFlatpakRuntime *self = (GbpFlatpakRuntime *)runtime;
 
@@ -182,7 +182,7 @@ gbp_flatpak_runtime_prebuild_finish (IdeRuntime    *runtime,
 
 static IdeSubprocessLauncher *
 gbp_flatpak_runtime_create_launcher (IdeRuntime  *runtime,
-                                 GError     **error)
+                                     GError     **error)
 {
   IdeSubprocessLauncher *ret;
   GbpFlatpakRuntime *self = (GbpFlatpakRuntime *)runtime;
@@ -198,6 +198,8 @@ gbp_flatpak_runtime_create_launcher (IdeRuntime  *runtime,
       ide_subprocess_launcher_push_argv (ret, "flatpak");
       ide_subprocess_launcher_push_argv (ret, "build");
       ide_subprocess_launcher_push_argv (ret, build_path);
+
+      ide_subprocess_launcher_set_run_on_host (ret, TRUE);
     }
 
   return ret;
@@ -205,7 +207,7 @@ gbp_flatpak_runtime_create_launcher (IdeRuntime  *runtime,
 
 static void
 gbp_flatpak_runtime_prepare_configuration (IdeRuntime       *runtime,
-                                       IdeConfiguration *configuration)
+                                           IdeConfiguration *configuration)
 {
   g_assert (IDE_IS_RUNTIME (runtime));
   g_assert (IDE_IS_CONFIGURATION (configuration));
@@ -215,9 +217,9 @@ gbp_flatpak_runtime_prepare_configuration (IdeRuntime       *runtime,
 
 static void
 gbp_flatpak_runtime_get_property (GObject    *object,
-                              guint       prop_id,
-                              GValue     *value,
-                              GParamSpec *pspec)
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
 {
   GbpFlatpakRuntime *self = GBP_FLATPAK_RUNTIME(object);
 
@@ -242,9 +244,9 @@ gbp_flatpak_runtime_get_property (GObject    *object,
 
 static void
 gbp_flatpak_runtime_set_property (GObject      *object,
-                              guint         prop_id,
-                              const GValue *value,
-                              GParamSpec   *pspec)
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
 {
   GbpFlatpakRuntime *self = GBP_FLATPAK_RUNTIME(object);
 
diff --git a/plugins/jhbuild/jhbuild_plugin.py b/plugins/jhbuild/jhbuild_plugin.py
index be7ad45..47fa79d 100644
--- a/plugins/jhbuild/jhbuild_plugin.py
+++ b/plugins/jhbuild/jhbuild_plugin.py
@@ -43,11 +43,27 @@ class JhbuildRuntime(Ide.Runtime):
             # Rely on search path
             return 'jhbuild'
 
+    def do_create_runner(self, build_target):
+        try:
+            installdir = build_target.props.install_directory
+            name = build_target.props.name
+            binpath = installdir.get_child(name).get_path()
+
+            # The runner uses a launcher from self.create_launcher(), so
+            # most of our settings should already be applied.
+            runner = Ide.Runner.new(self.get_context())
+            runner.append_argv(binpath)
+
+            return runner
+        except Exception as ex:
+            print(ex)
+
     def do_create_launcher(self):
         try:
             launcher = Ide.Runtime.do_create_launcher(self)
             launcher.push_argv(self.get_jhbuild_path())
             launcher.push_argv('run')
+            launcher.set_run_on_host(True)
             return launcher
         except GLib.Error:
             return None


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