[gnome-builder/wip/chergert/runners] wip



commit 453343aecaecc568a749ebe531cf31378d105394
Author: Christian Hergert <chergert redhat com>
Date:   Thu Jul 7 12:31:04 2016 -0700

    wip

 configure.ac                                    |    2 +
 libide/Makefile.am                              |    6 +
 libide/buildsystem/ide-build-system.c           |   17 +
 libide/buildsystem/ide-build-system.h           |   28 +-
 libide/buildsystem/ide-build-target.c           |   87 ++++
 libide/buildsystem/ide-build-target.h           |   48 ++
 libide/ide-types.h                              |    1 +
 libide/ide.h                                    |    3 +
 libide/runner/OVERVIEW.md                       |   89 ++++
 libide/runner/ide-runner-addin.c                |  141 ++++++
 libide/runner/ide-runner-addin.h                |   78 ++++
 libide/runner/ide-runner.c                      |  535 +++++++++++++++++++++++
 libide/runner/ide-runner.h                      |   43 ++-
 libide/runtimes/ide-runtime.c                   |   23 +
 libide/runtimes/ide-runtime.h                   |   23 +
 plugins/Makefile.am                             |    7 +-
 plugins/autotools/ide-autotools-runner-addin.h  |   32 ++
 plugins/build-tools/gbp-build-workbench-addin.c |   40 --
 plugins/run-tools/Makefile.am                   |   27 ++
 plugins/run-tools/configure.ac                  |   12 +
 plugins/run-tools/gbp-run-plugin.c              |   28 ++
 plugins/run-tools/gbp-run-workbench-addin.c     |  181 ++++++++
 plugins/run-tools/gbp-run-workbench-addin.h     |   32 ++
 plugins/run-tools/run-tools.plugin              |   11 +
 24 files changed, 1432 insertions(+), 62 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 1b7f016..a4a02a1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -301,6 +301,7 @@ m4_include([plugins/mingw/configure.ac])
 m4_include([plugins/project-tree/configure.ac])
 m4_include([plugins/python-gi-imports-completion/configure.ac])
 m4_include([plugins/python-pack/configure.ac])
+m4_include([plugins/run-tools/configure.ac])
 m4_include([plugins/support/configure.ac])
 m4_include([plugins/symbol-tree/configure.ac])
 m4_include([plugins/sysmon/configure.ac])
@@ -593,6 +594,7 @@ echo "  Project Tree ......................... : ${enable_project_tree_plugin}"
 echo "  Python GObject Introspection ......... : ${enable_python_gi_imports_completion_plugin}"
 echo "  Python Jedi Autocompletion ........... : ${enable_jedi_plugin}"
 echo "  Python Language Pack ................. : ${enable_python_pack_plugin}"
+echo "  Run Tools ............................ : ${enable_run_tools_plugin}"
 echo "  Support .............................. : ${enable_support_plugin}"
 echo "  System Monitor ....................... : ${enable_sysmon_plugin}"
 echo "  Symbol Tree .......................... : ${enable_symbol_tree_plugin}"
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 91459fb..9811c30 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -33,6 +33,7 @@ libide_1_0_la_public_headers =                            \
        buildsystem/ide-build-result-addin.h              \
        buildsystem/ide-build-result.h                    \
        buildsystem/ide-build-system.h                    \
+       buildsystem/ide-build-target.h                    \
        buildsystem/ide-builder.h                         \
        buildsystem/ide-configuration-manager.h           \
        buildsystem/ide-configuration.h                   \
@@ -87,6 +88,8 @@ libide_1_0_la_public_headers =                            \
        projects/ide-project-miner.h                      \
        projects/ide-project.h                            \
        projects/ide-recent-projects.h                    \
+       runner/ide-runner.h                               \
+       runner/ide-runner-addin.h                         \
        runtimes/ide-runtime-manager.h                    \
        runtimes/ide-runtime-provider.h                   \
        runtimes/ide-runtime.h                            \
@@ -173,6 +176,7 @@ libide_1_0_la_public_sources =                            \
        buildsystem/ide-build-result-addin.c              \
        buildsystem/ide-build-result.c                    \
        buildsystem/ide-build-system.c                    \
+       buildsystem/ide-build-target.c                    \
        buildsystem/ide-builder.c                         \
        buildsystem/ide-configuration-manager.c           \
        buildsystem/ide-configuration.c                   \
@@ -232,6 +236,8 @@ libide_1_0_la_public_sources =                            \
        projects/ide-project-miner.c                      \
        projects/ide-project.c                            \
        projects/ide-recent-projects.c                    \
+       runner/ide-runner.c                               \
+       runner/ide-runner-addin.c                         \
        runtimes/ide-runtime-manager.c                    \
        runtimes/ide-runtime-provider.c                   \
        runtimes/ide-runtime.c                            \
diff --git a/libide/buildsystem/ide-build-system.c b/libide/buildsystem/ide-build-system.c
index a2697cb..4c1632f 100644
--- a/libide/buildsystem/ide-build-system.c
+++ b/libide/buildsystem/ide-build-system.c
@@ -242,3 +242,20 @@ ide_build_system_get_builder (IdeBuildSystem    *system,
   return IDE_BUILD_SYSTEM_GET_IFACE (system)->get_builder (system, configuration, error);
 }
 
+/**
+ * ide_build_system_get_default_run_target:
+ *
+ * Returns: (transfer full) (nullable): A #IdeBuildTarget or %NULL.
+ */
+IdeBuildTarget *
+ide_build_system_get_default_run_target (IdeBuildSystem   *self,
+                                         IdeConfiguration *configuration)
+{
+  g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), NULL);
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (configuration), NULL);
+
+  if (IDE_BUILD_SYSTEM_GET_IFACE (self)->get_default_run_target)
+    return IDE_BUILD_SYSTEM_GET_IFACE (self)->get_default_run_target (self, configuration);
+
+  return NULL;
+}
diff --git a/libide/buildsystem/ide-build-system.h b/libide/buildsystem/ide-build-system.h
index 3cb9e98..2469d55 100644
--- a/libide/buildsystem/ide-build-system.h
+++ b/libide/buildsystem/ide-build-system.h
@@ -33,18 +33,20 @@ struct _IdeBuildSystemInterface
 {
   GTypeInterface parent_iface;
 
-  gint        (*get_priority)           (IdeBuildSystem       *system);
-  IdeBuilder *(*get_builder)            (IdeBuildSystem       *system,
-                                         IdeConfiguration     *configuration,
-                                         GError              **error);
-  void        (*get_build_flags_async)  (IdeBuildSystem       *self,
-                                         IdeFile              *file,
-                                         GCancellable         *cancellable,
-                                         GAsyncReadyCallback   callback,
-                                         gpointer              user_data);
-  gchar     **(*get_build_flags_finish) (IdeBuildSystem       *self,
-                                         GAsyncResult         *result,
-                                         GError              **error);
+  gint             (*get_priority)           (IdeBuildSystem       *system);
+  IdeBuilder      *(*get_builder)            (IdeBuildSystem       *system,
+                                              IdeConfiguration     *configuration,
+                                              GError              **error);
+  void             (*get_build_flags_async)  (IdeBuildSystem       *self,
+                                              IdeFile              *file,
+                                              GCancellable         *cancellable,
+                                              GAsyncReadyCallback   callback,
+                                              gpointer              user_data);
+  gchar          **(*get_build_flags_finish) (IdeBuildSystem       *self,
+                                              GAsyncResult         *result,
+                                              GError              **error);
+  IdeBuildTarget  *(*get_default_run_target) (IdeBuildSystem       *self,
+                                              IdeConfiguration     *configuration);
 };
 
 gint            ide_build_system_get_priority           (IdeBuildSystem       *self);
@@ -66,6 +68,8 @@ IdeBuildSystem *ide_build_system_new_finish             (GAsyncResult         *r
 IdeBuilder     *ide_build_system_get_builder            (IdeBuildSystem       *system,
                                                          IdeConfiguration     *configuration,
                                                          GError              **error);
+IdeBuildTarget *ide_build_system_get_default_run_target (IdeBuildSystem       *self,
+                                                         IdeConfiguration     *configuration);
 
 G_END_DECLS
 
diff --git a/libide/buildsystem/ide-build-target.c b/libide/buildsystem/ide-build-target.c
new file mode 100644
index 0000000..739c7c0
--- /dev/null
+++ b/libide/buildsystem/ide-build-target.c
@@ -0,0 +1,87 @@
+/* ide-build-target.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/>.
+ */
+
+#include "ide-build-target.h"
+
+typedef struct
+{
+  gpointer dummy;
+} IdeBuildTargetPrivate;
+
+enum {
+  PROP_0,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeBuildTarget, ide_build_target, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_build_target_finalize (GObject *object)
+{
+  IdeBuildTarget *self = (IdeBuildTarget *)object;
+  IdeBuildTargetPrivate *priv = ide_build_target_get_instance_private (self);
+
+  G_OBJECT_CLASS (ide_build_target_parent_class)->finalize (object);
+}
+
+static void
+ide_build_target_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  IdeBuildTarget *self = IDE_BUILD_TARGET (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_build_target_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  IdeBuildTarget *self = IDE_BUILD_TARGET (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_build_target_class_init (IdeBuildTargetClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_build_target_finalize;
+  object_class->get_property = ide_build_target_get_property;
+  object_class->set_property = ide_build_target_set_property;
+}
+
+static void
+ide_build_target_init (IdeBuildTarget *self)
+{
+}
diff --git a/libide/buildsystem/ide-build-target.h b/libide/buildsystem/ide-build-target.h
new file mode 100644
index 0000000..e514366
--- /dev/null
+++ b/libide/buildsystem/ide-build-target.h
@@ -0,0 +1,48 @@
+/* ide-build-target.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_BUILD_TARGET_H
+#define IDE_BUILD_TARGET_H
+
+#include <glib-object.h>
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BUILD_TARGET (ide_build_target_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeBuildTarget, ide_build_target, IDE, BUILD_TARGET, IdeObject)
+
+struct _IdeBuildTargetClass
+{
+  GObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+G_END_DECLS
+
+#endif /* IDE_BUILD_TARGET_H */
diff --git a/libide/ide-types.h b/libide/ide-types.h
index 7040331..b694b9d 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -40,6 +40,7 @@ typedef struct _IdeBuilder                     IdeBuilder;
 typedef struct _IdeBuildResult                 IdeBuildResult;
 
 typedef struct _IdeBuildSystem                 IdeBuildSystem;
+typedef struct _IdeBuildTarget                 IdeBuildTarget;
 
 typedef struct _IdeConfiguration               IdeConfiguration;
 typedef struct _IdeConfigurationManager        IdeConfigurationManager;
diff --git a/libide/ide.h b/libide/ide.h
index 51ea85f..317f316 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -38,6 +38,7 @@ G_BEGIN_DECLS
 #include "buildsystem/ide-build-result-addin.h"
 #include "buildsystem/ide-build-result.h"
 #include "buildsystem/ide-build-system.h"
+#include "buildsystem/ide-build-target.h"
 #include "buildsystem/ide-builder.h"
 #include "buildsystem/ide-configuration-manager.h"
 #include "buildsystem/ide-configuration.h"
@@ -83,6 +84,8 @@ G_BEGIN_DECLS
 #include "projects/ide-project-miner.h"
 #include "projects/ide-project.h"
 #include "projects/ide-recent-projects.h"
+#include "runner/ide-runner.h"
+#include "runner/ide-runner-addin.h"
 #include "runtimes/ide-runtime-manager.h"
 #include "runtimes/ide-runtime-provider.h"
 #include "runtimes/ide-runtime.h"
diff --git a/libide/runner/OVERVIEW.md b/libide/runner/OVERVIEW.md
new file mode 100644
index 0000000..cf5801a
--- /dev/null
+++ b/libide/runner/OVERVIEW.md
@@ -0,0 +1,89 @@
+# Running Projects
+
+Running a project target in Builder needs to accomidate a few difficult
+to plumb components. For example, Builder supports runtimes which might
+be different than the current host (such as org.gnome.Platform 3.22).
+Additionally, we might need to attach a debugger. The build system might
+also need to perform an installation of the application bits into a
+runtime so that the project can run as it's "installed state".
+
+All of these complexities results in project execution being abstracted
+into an “IdeRunner” object and series of “IdeRunnerAddin” extensions.
+This allows the various plugins involved (build system, runtime, possible
+debugger or profilers) to hook in to various stages and modify things as
+necessary.
+
+# IdeRunner
+
+This is our core runner structure. It manages some basic process execution
+stuff, but most things get tweaked and configured by the addins.
+
+The process is launched by the IdeSubprocessLauncher created by the configured
+runtime. However, addins can tweak things as necessary.
+
+## IdeRunnerAddin
+
+Plugins should implement this interface so that they can modify the runner
+as necessary. This should be used for custom extensoin points that are always
+needed, not to add integration points like debugger, profiler, etc.
+
+## Debugger Integration
+
+Debuggers may need to hook into the runtime pid namespace (and possibly mount
+namespace to bring along tooling). This could also mean starting the process as
+an inferior of something like `gdb`. In the flatpak scenario, this could be
+
+  flatpak reaper → gdb → inferior
+
+Additionally, the debugger will need to be able to IPC with the gdb instance
+inside the runtime.
+
+While we don't have support for this yet, I think the design we can use is that
+the `IdeRunner` can have API to bring in "sidecars" to the runtime which
+contain the debugging tooling. For the local system, this would be a
+passthrough. For something like flatpak, it would need to enter the namespace
+and add a mountpoint for the tooling. (This is probably something that needs to
+be implemented in bubblewrap to some degree).
+
+When the debugger sets up the runner, it will need to be able to modify argv
+similar to our IdeSubprocessLauncher. This will allow it to add gdb to the
+prefix of the command.
+
+Getting a runner will be somewhat consistent with:
+
+  BuildSystem = context.get_build_system()
+  ConfigManager = context.get_configuration_manager()
+
+  config = ConfigManager.get_current()
+  runtime = config.get_runtime()
+  target = BuildSystem.get_default_run_command(config)
+  runner = runtime.create_runner(config, target)
+
+    -> the build system, at this point, may have added a prehook via
+       the runner addin that will install the project before it can
+       be run.
+
+And the debugger might do something like:
+
+  runner.prepend_argv('gdbserver')
+  runner.prepend_argv('--once')
+  runner.prepend_argv('--args')
+
+When runner.run() is called, the implementation might choose to prepend
+additional argv parameters. This allows gdb to simply prepend the command
+line, but still have flatpak spawn the container process (bubblewrap).
+
+## Profiling Integration
+
+A profiler (such as sysprof) will need to know the process identifier of the
+spawned process (and possibly children processes). This is somewhat similar
+to debugging except that it isn't strictly necessary that the process stops
+before reaching `main()`.
+
+To synchronize on startup, sysprof might spawn a helper process that
+communicates with the listener process, and then execs the child command.
+
+So it might need to do something like:
+
+  runner.prepend_argv('sysprof-spawner')
+
diff --git a/libide/runner/ide-runner-addin.c b/libide/runner/ide-runner-addin.c
new file mode 100644
index 0000000..d2a455b
--- /dev/null
+++ b/libide/runner/ide-runner-addin.c
@@ -0,0 +1,141 @@
+/* ide-runner-addin.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-runner-addin"
+
+#include "ide-runner-addin.h"
+
+G_DEFINE_INTERFACE (IdeRunnerAddin, ide_runner_addin, G_TYPE_OBJECT)
+
+static void
+ide_runner_addin_real_load (IdeRunnerAddin *self,
+                            IdeRunner      *runner)
+{
+}
+
+static void
+ide_runner_addin_real_unload (IdeRunnerAddin *self,
+                              IdeRunner      *runner)
+{
+}
+
+static void
+dummy_async (IdeRunnerAddin      *self,
+             GCancellable        *cancellable,
+             GAsyncReadyCallback  callback,
+             gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (callback == NULL)
+    return;
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+dummy_finish (IdeRunnerAddin  *self,
+              GAsyncResult    *result,
+              GError         **error)
+{
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_runner_addin_default_init (IdeRunnerAddinInterface *iface)
+{
+  iface->load = ide_runner_addin_real_load;
+  iface->unload = ide_runner_addin_real_unload;
+  iface->prehook_async = dummy_async;
+  iface->prehook_finish = dummy_finish;
+  iface->posthook_async = dummy_async;
+  iface->posthook_finish = dummy_finish;
+}
+
+void
+ide_runner_addin_load (IdeRunnerAddin *self,
+                       IdeRunner      *runner)
+{
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (IDE_IS_RUNNER (runner));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->load (self, runner);
+}
+
+void
+ide_runner_addin_unload (IdeRunnerAddin *self,
+                         IdeRunner      *runner)
+{
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (IDE_IS_RUNNER (runner));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->unload (self, runner);
+}
+
+void
+ide_runner_addin_prehook_async (IdeRunnerAddin      *self,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_RUNNER_ADDIN (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->prehook_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runner_addin_prehook_finish (IdeRunnerAddin  *self,
+                                 GAsyncResult    *result,
+                                 GError         **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER_ADDIN (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_RUNNER_ADDIN_GET_IFACE (self)->prehook_finish (self, result, error);
+}
+
+void
+ide_runner_addin_posthook_async (IdeRunnerAddin      *self,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_RUNNER_ADDIN (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->posthook_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runner_addin_posthook_finish (IdeRunnerAddin  *self,
+                                  GAsyncResult    *result,
+                                  GError         **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER_ADDIN (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_RUNNER_ADDIN_GET_IFACE (self)->posthook_finish (self, result, error);
+}
diff --git a/libide/runner/ide-runner-addin.h b/libide/runner/ide-runner-addin.h
new file mode 100644
index 0000000..60e6a17
--- /dev/null
+++ b/libide/runner/ide-runner-addin.h
@@ -0,0 +1,78 @@
+/* ide-runner-addin.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_RUNNER_ADDIN_H
+#define IDE_RUNNER_ADDIN_H
+
+#include <gio/gio.h>
+
+#include "ide-types.h"
+#include "ide-runner.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUNNER_ADDIN (ide_runner_addin_get_type())
+
+G_DECLARE_INTERFACE (IdeRunnerAddin, ide_runner_addin, IDE, RUNNER_ADDIN, GObject)
+
+struct _IdeRunnerAddinInterface
+{
+  GTypeInterface parent_interface;
+
+  void     (*load)            (IdeRunnerAddin       *self,
+                               IdeRunner            *runner);
+  void     (*unload)          (IdeRunnerAddin       *self,
+                               IdeRunner            *runner);
+  void     (*prehook_async)   (IdeRunnerAddin       *self,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data);
+  gboolean (*prehook_finish)  (IdeRunnerAddin       *self,
+                               GAsyncResult         *result,
+                               GError              **error);
+  void     (*posthook_async)  (IdeRunnerAddin       *self,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data);
+  gboolean (*posthook_finish) (IdeRunnerAddin       *self,
+                               GAsyncResult         *result,
+                               GError              **error);
+};
+
+void     ide_runner_addin_load            (IdeRunnerAddin       *self,
+                                           IdeRunner            *runner);
+void     ide_runner_addin_unload          (IdeRunnerAddin       *self,
+                                           IdeRunner            *runner);
+void     ide_runner_addin_prehook_async   (IdeRunnerAddin       *self,
+                                           GCancellable         *cancellable,
+                                           GAsyncReadyCallback   callback,
+                                           gpointer              user_data);
+gboolean ide_runner_addin_prehook_finish  (IdeRunnerAddin       *self,
+                                           GAsyncResult         *result,
+                                           GError              **error);
+void     ide_runner_addin_posthook_async  (IdeRunnerAddin       *self,
+                                           GCancellable         *cancellable,
+                                           GAsyncReadyCallback   callback,
+                                           gpointer              user_data);
+gboolean ide_runner_addin_posthook_finish (IdeRunnerAddin       *self,
+                                           GAsyncResult         *result,
+                                           GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_RUNNER_ADDIN_H */
diff --git a/libide/runner/ide-runner.c b/libide/runner/ide-runner.c
new file mode 100644
index 0000000..9a4c3a7
--- /dev/null
+++ b/libide/runner/ide-runner.c
@@ -0,0 +1,535 @@
+/* ide-runner.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-runner"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include "ide-runner.h"
+#include "ide-runner-addin.h"
+
+typedef struct
+{
+  PeasExtensionSet *addins;
+  GQueue argv;
+} IdeRunnerPrivate;
+
+typedef struct
+{
+  GSList *prehook_queue;
+  GSList *posthook_queue;
+} IdeRunnerRunState;
+
+enum {
+  PROP_0,
+  PROP_ARGV,
+  N_PROPS
+};
+
+static void ide_runner_tick_posthook (GTask *task);
+static void ide_runner_tick_prehook  (GTask *task);
+static void ide_runner_tick_run      (GTask *task);
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeRunner, ide_runner, IDE_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static IdeRunnerAddin *
+pop_runner_addin (GSList **list)
+{
+  IdeRunnerAddin *ret;
+
+  g_assert (list != NULL);
+  g_assert (*list != NULL);
+
+  ret = (*list)->data;
+
+  *list = g_slist_delete_link (*list, *list);
+
+  return ret;
+}
+
+static void
+ide_runner_run_state_free (gpointer data)
+{
+  IdeRunnerRunState *state = data;
+
+  g_slist_foreach (state->prehook_queue, (GFunc)g_object_unref, NULL);
+  g_slist_free (state->prehook_queue);
+
+  g_slist_foreach (state->posthook_queue, (GFunc)g_object_unref, NULL);
+  g_slist_free (state->posthook_queue);
+
+  g_slice_free (IdeRunnerRunState, state);
+}
+
+static void
+ide_runner_extension_added (PeasExtensionSet *set,
+                            PeasPluginInfo   *plugin_info,
+                            PeasExtension    *exten,
+                            gpointer          user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
+  IdeRunner *self = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (IDE_IS_RUNNER (self));
+
+  ide_runner_addin_load (addin, self);
+}
+
+static void
+ide_runner_extension_removed (PeasExtensionSet *set,
+                              PeasPluginInfo   *plugin_info,
+                              PeasExtension    *exten,
+                              gpointer          user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
+  IdeRunner *self = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (IDE_IS_RUNNER (self));
+
+  ide_runner_addin_unload (addin, self);
+}
+
+static void
+ide_runner_constructed (GObject *object)
+{
+  IdeRunner *self = (IdeRunner *)object;
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  G_OBJECT_CLASS (ide_runner_parent_class)->constructed (object);
+
+  priv->addins = peas_extension_set_new (peas_engine_get_default (),
+                                         IDE_TYPE_RUNNER_ADDIN,
+                                         NULL);
+
+  g_signal_connect (priv->addins,
+                    "extension-added",
+                    G_CALLBACK (ide_runner_extension_added),
+                    self);
+
+  g_signal_connect (priv->addins,
+                    "extension-removed",
+                    G_CALLBACK (ide_runner_extension_removed),
+                    self);
+
+  peas_extension_set_foreach (priv->addins,
+                              ide_runner_extension_added,
+                              self);
+}
+
+static void
+ide_runner_finalize (GObject *object)
+{
+  IdeRunner *self = (IdeRunner *)object;
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
+  g_queue_clear (&priv->argv);
+
+  G_OBJECT_CLASS (ide_runner_parent_class)->finalize (object);
+}
+
+static void
+ide_runner_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  IdeRunner *self = IDE_RUNNER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ARGV:
+      g_value_take_boxed (value, ide_runner_get_argv (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_runner_set_property (GObject      *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  IdeRunner *self = IDE_RUNNER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ARGV:
+      ide_runner_set_argv (self, g_value_get_boxed (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_runner_class_init (IdeRunnerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_runner_constructed;
+  object_class->finalize = ide_runner_finalize;
+  object_class->get_property = ide_runner_get_property;
+  object_class->set_property = ide_runner_set_property;
+
+  properties [PROP_ARGV] =
+    g_param_spec_boxed ("argv",
+                        "Argv",
+                        "The argument list for the command",
+                        G_TYPE_STRV,
+                        (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_runner_init (IdeRunner *self)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_queue_init (&priv->argv);
+}
+
+/**
+ * ide_runner_get_stdin:
+ *
+ * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
+ */
+GInputStream *
+ide_runner_get_stdin (IdeRunner *self)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  return IDE_RUNNER_GET_CLASS (self)->get_stdin (self);
+}
+
+/**
+ * ide_runner_get_stdout:
+ *
+ * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
+ */
+GOutputStream *
+ide_runner_get_stdout (IdeRunner *self)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  return IDE_RUNNER_GET_CLASS (self)->get_stdout (self);
+}
+
+/**
+ * ide_runner_get_stderr:
+ *
+ * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
+ */
+GOutputStream *
+ide_runner_get_stderr (IdeRunner *self)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  return IDE_RUNNER_GET_CLASS (self)->get_stderr (self);
+}
+
+void
+ide_runner_force_quit (IdeRunner *self)
+{
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  IDE_RUNNER_GET_CLASS (self)->force_quit (self);
+}
+
+void
+ide_runner_set_argv (IdeRunner           *self,
+                     const gchar * const *argv)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+  guint i;
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
+  g_queue_clear (&priv->argv);
+
+  if (argv != NULL)
+    {
+      for (i = 0; argv [i]; i++)
+        g_queue_push_tail (&priv->argv, g_strdup (argv [i]));
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
+}
+
+/**
+ * ide_runner_get_argv:
+ *
+ * Gets the argument list as a newly allocated string array.
+ *
+ * Returns: (transfer full): A newly allocated string array that should
+ *   be freed with g_strfreev().
+ */
+gchar **
+ide_runner_get_argv (IdeRunner *self)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+  GPtrArray *ar;
+  GList *iter;
+
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  ar = g_ptr_array_new ();
+
+  for (iter = priv->argv.head; iter != NULL; iter = iter->next)
+    {
+      const gchar *param = iter->data;
+
+      g_ptr_array_add (ar, g_strdup (param));
+    }
+
+  g_ptr_array_add (ar, NULL);
+
+  return (gchar **)g_ptr_array_free (ar, FALSE);
+}
+
+static void
+ide_runner_collect_addins_cb (PeasExtensionSet *set,
+                              PeasPluginInfo   *plugin_info,
+                              PeasExtension    *exten,
+                              gpointer          user_data)
+{
+  GSList **list = user_data;
+
+  *list = g_slist_prepend (*list, exten);
+}
+
+static void
+ide_runner_collect_addins (IdeRunner  *self,
+                           GSList    **list)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (list != NULL);
+
+  peas_extension_set_foreach (priv->addins,
+                              ide_runner_collect_addins_cb,
+                              list);
+}
+
+static void
+ide_runner_posthook_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!ide_runner_addin_posthook_finish (addin, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_runner_tick_posthook (task);
+}
+
+static void
+ide_runner_tick_posthook (GTask *task)
+{
+  IdeRunnerRunState *state;
+
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+
+  if (state->posthook_queue != NULL)
+    {
+      g_autoptr(IdeRunnerAddin) addin = NULL;
+
+      addin = pop_runner_addin (&state->posthook_queue);
+      ide_runner_addin_posthook_async (addin,
+                                       g_task_get_cancellable (task),
+                                       ide_runner_posthook_cb,
+                                       g_object_ref (task));
+      return;
+    }
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_runner_run_cb (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  IdeRunner *self = (IdeRunner *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!IDE_RUNNER_GET_CLASS (self)->run_finish (self, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_runner_tick_posthook (task);
+}
+
+static void
+ide_runner_tick_run (GTask *task)
+{
+  IdeRunner *self;
+
+  g_assert (G_IS_TASK (task));
+
+  self = g_task_get_source_object (task);
+
+  IDE_RUNNER_GET_CLASS (self)->run_async (self,
+                                          g_task_get_cancellable (task),
+                                          ide_runner_run_cb,
+                                          g_object_ref (task));
+}
+
+static void
+ide_runner_prehook_cb (GObject      *object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!ide_runner_addin_prehook_finish (addin, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_runner_tick_prehook (task);
+}
+
+static void
+ide_runner_tick_prehook (GTask *task)
+{
+  IdeRunnerRunState *state;
+
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+
+  if (state->prehook_queue != NULL)
+    {
+      g_autoptr(IdeRunnerAddin) addin = NULL;
+
+      addin = pop_runner_addin (&state->prehook_queue);
+      ide_runner_addin_prehook_async (addin,
+                                      g_task_get_cancellable (task),
+                                      ide_runner_prehook_cb,
+                                      g_object_ref (task));
+      return;
+    }
+
+  ide_runner_tick_run (task);
+}
+
+void
+ide_runner_run_async (IdeRunner           *self,
+                      GCancellable        *cancellable,
+                      GAsyncReadyCallback  callback,
+                      gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  IdeRunnerRunState *state;
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_runner_run_async);
+  g_task_set_check_cancellable (task, FALSE);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+
+  /*
+   * We need to run the prehook functions for each addin first before we
+   * can call our IdeRunnerClass.run vfunc.  Since these are async, we
+   * have to bring some state along with us.
+   */
+  state = g_slice_new0 (IdeRunnerRunState);
+  ide_runner_collect_addins (self, &state->prehook_queue);
+  ide_runner_collect_addins (self, &state->posthook_queue);
+  g_task_set_task_data (task, state, ide_runner_run_state_free);
+
+  ide_runner_tick_prehook (task);
+}
+
+gboolean
+ide_runner_run_finish (IdeRunner     *self,
+                       GAsyncResult  *result,
+                       GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+ide_runner_append_argv (IdeRunner   *self,
+                        const gchar *param)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+  g_return_if_fail (param != NULL);
+
+  g_queue_push_tail (&priv->argv, g_strdup (param));
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
+}
+
+void
+ide_runner_prepend_argv (IdeRunner   *self,
+                         const gchar *param)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+  g_return_if_fail (param != NULL);
+
+  g_queue_push_head (&priv->argv, g_strdup (param));
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
+}
diff --git a/libide/runner/ide-runner.h b/libide/runner/ide-runner.h
index 94f2c12..ff7aa12 100644
--- a/libide/runner/ide-runner.h
+++ b/libide/runner/ide-runner.h
@@ -19,7 +19,9 @@
 #ifndef IDE_RUNNER_H
 #define IDE_RUNNER_H
 
-#include "ide-types.h"
+#include <gio/gio.h>
+
+#include "ide-object.h"
 
 G_BEGIN_DECLS
 
@@ -27,7 +29,9 @@ typedef enum
 {
   IDE_RUNNER_INVALID,
   IDE_RUNNER_READY,
+  IDE_RUNNER_PREHOOK,
   IDE_RUNNER_RUNNING,
+  IDE_RUNNER_POSTHOOK,
   IDE_RUNNER_EXITED,
   IDE_RUNNER_FAILED,
 } IdeRunnerState;
@@ -38,15 +42,40 @@ G_DECLARE_DERIVABLE_TYPE (IdeRunner, ide_runner, IDE, RUNNER, IdeObject)
 
 struct _IdeRunnerClass
 {
-  IdeObject parent_instance;
+  IdeObjectClass parent;
 
-  void (*force_quit) (IdeRunner *self);
-  void (*run)        (IdeRunner *self);
+  void           (*force_quit) (IdeRunner            *self);
+  GInputStream  *(*get_stdin)  (IdeRunner            *self);
+  GOutputStream *(*get_stdout) (IdeRunner            *self);
+  GOutputStream *(*get_stderr) (IdeRunner            *self);
+  void           (*run_async)  (IdeRunner            *self,
+                                GCancellable         *cancellable,
+                                GAsyncReadyCallback   callback,
+                                gpointer              user_data);
+  gboolean       (*run_finish) (IdeRunner            *self,
+                                GAsyncResult         *result,
+                                GError              **error);
 };
 
-IdeRunner *ide_runner_new        (IdeContext *context);
-void       ide_runner_force_quit (IdeRunner  *self);
-void       ide_runner_run        (IdeRunner  *self);
+IdeRunner      *ide_runner_new          (IdeContext           *context);
+void            ide_runner_force_quit   (IdeRunner            *self);
+void            ide_runner_run_async    (IdeRunner            *self,
+                                         GCancellable         *cancellable,
+                                         GAsyncReadyCallback   callback,
+                                         gpointer              user_data);
+gboolean        ide_runner_run_finish   (IdeRunner            *self,
+                                         GAsyncResult         *result,
+                                         GError              **error);
+void            ide_runner_prepend_argv (IdeRunner            *self,
+                                         const gchar          *param);
+void            ide_runner_append_argv  (IdeRunner            *self,
+                                         const gchar          *param);
+gchar         **ide_runner_get_argv     (IdeRunner            *self);
+void            ide_runner_set_argv     (IdeRunner            *self,
+                                         const gchar * const  *argv);
+GInputStream   *ide_runner_get_stdin    (IdeRunner            *self);
+GOutputStream  *ide_runner_get_stdout   (IdeRunner            *self);
+GOutputStream  *ide_runner_get_stderr   (IdeRunner            *self);
 
 G_END_DECLS
 
diff --git a/libide/runtimes/ide-runtime.c b/libide/runtimes/ide-runtime.c
index a67f9d1..034fbfd 100644
--- a/libide/runtimes/ide-runtime.c
+++ b/libide/runtimes/ide-runtime.c
@@ -407,3 +407,26 @@ ide_runtime_prepare_configuration (IdeRuntime       *self,
 
   IDE_RUNTIME_GET_CLASS (self)->prepare_configuration (self, configuration);
 }
+
+/**
+ * ide_runtime_create_runner:
+ *
+ * Creates a new runner that can be used to execute the build target within
+ * the runtime. This should be used to implement such features as "run target"
+ * or "run unit test" inside the target runtime.
+ *
+ * Returns: (transfer full) (nullable): An #IdeRunner if successful, otherwise
+ *   %NULL and @error is set.
+ */
+IdeRunner *
+ide_runtime_create_runner (IdeRuntime     *self,
+                           IdeBuildTarget *build_target)
+{
+  g_return_val_if_fail (IDE_IS_RUNTIME (self), NULL);
+  g_return_val_if_fail (IDE_IS_BUILD_TARGET (build_target), NULL);
+
+  if (IDE_RUNTIME_GET_CLASS (self)->create_runner)
+    return IDE_RUNTIME_GET_CLASS (self)->create_runner (self, build_target);
+
+  return NULL;
+}
diff --git a/libide/runtimes/ide-runtime.h b/libide/runtimes/ide-runtime.h
index daa0fe0..58ef1b0 100644
--- a/libide/runtimes/ide-runtime.h
+++ b/libide/runtimes/ide-runtime.h
@@ -23,6 +23,8 @@
 
 #include "ide-object.h"
 
+#include "buildsystem/ide-build-target.h"
+#include "runner/ide-runner.h"
 #include "workers/ide-subprocess-launcher.h"
 
 G_BEGIN_DECLS
@@ -56,6 +58,25 @@ struct _IdeRuntimeClass
                                                       GError              **error);
   void                   (*prepare_configuration)    (IdeRuntime           *self,
                                                       IdeConfiguration     *configuration);
+  IdeRunner             *(*create_runner)            (IdeRuntime           *self,
+                                                      IdeBuildTarget       *build_target);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+  gpointer _reserved9;
+  gpointer _reserved10;
+  gpointer _reserved11;
+  gpointer _reserved12;
+  gpointer _reserved13;
+  gpointer _reserved14;
+  gpointer _reserved15;
+  gpointer _reserved16;
 };
 
 void                   ide_runtime_prebuild_async           (IdeRuntime           *self,
@@ -77,6 +98,8 @@ gboolean               ide_runtime_contains_program_in_path (IdeRuntime
                                                              GCancellable         *cancellable);
 IdeSubprocessLauncher *ide_runtime_create_launcher          (IdeRuntime           *self,
                                                              GError              **error);
+IdeRunner             *ide_runtime_create_runner            (IdeRuntime           *self,
+                                                             IdeBuildTarget       *build_target);
 void                   ide_runtime_prepare_configuration    (IdeRuntime           *self,
                                                              IdeConfiguration     *configuration);
 IdeRuntime            *ide_runtime_new                      (IdeContext           *context,
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index ca2af88..d1617c8 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -2,11 +2,11 @@ SUBDIRS = \
        autotools \
        autotools-templates \
        build-tools \
+       c-pack \
        clang \
        command-bar \
-       contributing \
-       c-pack \
        comment-code \
+       contributing \
        create-project \
        ctags \
        devhelp \
@@ -22,10 +22,11 @@ SUBDIRS = \
        html-preview \
        jedi \
        jhbuild \
+       mingw \
        project-tree \
        python-gi-imports-completion \
-       mingw \
        python-pack \
+       run-tools \
        support \
        symbol-tree \
        sysmon \
diff --git a/plugins/autotools/ide-autotools-runner-addin.h b/plugins/autotools/ide-autotools-runner-addin.h
new file mode 100644
index 0000000..0220d33
--- /dev/null
+++ b/plugins/autotools/ide-autotools-runner-addin.h
@@ -0,0 +1,32 @@
+/* ide-autotools-runner-addin.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_AUTOTOOLS_RUNNER_ADDIN_H
+#define IDE_AUTOTOOLS_RUNNER_ADDIN_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_AUTOTOOLS_RUNNER_ADDIN (ide_autotools_runner_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeAutotoolsRunnerAddin, ide_autotools_runner_addin, IDE, AUTOTOOLS_RUNNER_ADDIN, 
GObject)
+
+G_END_DECLS
+
+#endif /* IDE_AUTOTOOLS_RUNNER_ADDIN_H */
diff --git a/plugins/build-tools/gbp-build-workbench-addin.c b/plugins/build-tools/gbp-build-workbench-addin.c
index 9511e32..1b6c80a 100644
--- a/plugins/build-tools/gbp-build-workbench-addin.c
+++ b/plugins/build-tools/gbp-build-workbench-addin.c
@@ -300,7 +300,6 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
 {
   IdeConfigurationManager *configuration_manager;
   GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)addin;
-  IdeWorkbenchHeaderBar *header;
   IdeConfiguration *configuration;
   IdePerspective *editor;
   IdeContext *context;
@@ -332,23 +331,6 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
 
   g_object_bind_property (self, "result", self->panel, "result", 0);
 
-  header = ide_workbench_get_headerbar (workbench);
-
-  /* XXX: Button is hidden until we add run support */
-  self->run_button = g_object_new (GTK_TYPE_BUTTON,
-                                   "child", g_object_new (GTK_TYPE_IMAGE,
-                                                          "icon-name", "media-playback-start-symbolic",
-                                                          "visible", TRUE,
-                                                          NULL),
-                                   "visible", FALSE,
-                                   NULL);
-  ide_widget_add_style_class (self->run_button, "image-button");
-
-  ide_workbench_header_bar_insert_right (header,
-                                         self->run_button,
-                                         GTK_PACK_START,
-                                         0);
-
   self->build_perspective = g_object_new (GBP_TYPE_BUILD_PERSPECTIVE,
                                           "configuration-manager", configuration_manager,
                                           "configuration", configuration,
@@ -464,30 +446,8 @@ gbp_build_workbench_addin_init (GbpBuildWorkbenchAddin *self)
 }
 
 static void
-gbp_build_workbench_addin_perpsective_set (IdeWorkbenchAddin *addin,
-                                           IdePerspective    *perspective)
-{
-  GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)addin;
-
-  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
-
-  /* XXX: Hidden until we add run support */
-#if 0
-  if (IDE_IS_EDITOR_PERSPECTIVE (perspective))
-    {
-      gtk_widget_show (self->run_button);
-    }
-  else
-    {
-      gtk_widget_hide (self->run_button);
-    }
-#endif
-}
-
-static void
 workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
 {
   iface->load = gbp_build_workbench_addin_load;
   iface->unload = gbp_build_workbench_addin_unload;
-  iface->perspective_set = gbp_build_workbench_addin_perpsective_set;
 }
diff --git a/plugins/run-tools/Makefile.am b/plugins/run-tools/Makefile.am
new file mode 100644
index 0000000..c3dc7cf
--- /dev/null
+++ b/plugins/run-tools/Makefile.am
@@ -0,0 +1,27 @@
+if ENABLE_RUN_TOOLS_PLUGIN
+
+DISTCLEANFILES =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = librun-tools-plugin.la
+dist_plugin_DATA = run-tools.plugin
+
+librun_tools_plugin_la_CFLAGS = \
+       $(PLUGIN_CFLAGS) \
+       -DG_LOG_DOMAIN="\"run-tools\"" \
+       $(NULL)
+librun_tools_plugin_la_LDFLAGS = $(PLUGIN_LDFLAGS)
+librun_tools_plugin_la_SOURCES = \
+       gbp-run-plugin.c \
+       gbp-run-workbench-addin.c \
+       gbp-run-workbench-addin.h \
+       $(NULL)
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/run-tools/configure.ac b/plugins/run-tools/configure.ac
new file mode 100644
index 0000000..61b1378
--- /dev/null
+++ b/plugins/run-tools/configure.ac
@@ -0,0 +1,12 @@
+# --enable-run-tools-plugin=yes/no
+AC_ARG_ENABLE([run-tools-plugin],
+              [AS_HELP_STRING([--enable-run-tools-plugin=@<:@yes/no@:>@],
+                              [Run with support for run tools and panels.])],
+              [enable_run_tools_plugin=$enableval],
+              [enable_run_tools_plugin=yes])
+
+# for if ENABLE_RUN_TOOLS_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_RUN_TOOLS_PLUGIN, test x$enable_run_tools_plugin != xno)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/run-tools/Makefile])
diff --git a/plugins/run-tools/gbp-run-plugin.c b/plugins/run-tools/gbp-run-plugin.c
new file mode 100644
index 0000000..cae5a1d
--- /dev/null
+++ b/plugins/run-tools/gbp-run-plugin.c
@@ -0,0 +1,28 @@
+/* gbp-run-plugin.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/>.
+ */
+
+#include <ide.h>
+#include <libpeas/peas.h>
+
+#include "gbp-run-workbench-addin.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module, IDE_TYPE_WORKBENCH_ADDIN, 
GBP_TYPE_RUN_WORKBENCH_ADDIN);
+}
diff --git a/plugins/run-tools/gbp-run-workbench-addin.c b/plugins/run-tools/gbp-run-workbench-addin.c
new file mode 100644
index 0000000..660f996
--- /dev/null
+++ b/plugins/run-tools/gbp-run-workbench-addin.c
@@ -0,0 +1,181 @@
+/* gbp-run-workbench-addin.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+
+#include "gbp-run-workbench-addin.h"
+
+struct _GbpRunWorkbenchAddin
+{
+  GObject       parent_instance;
+
+  GCancellable *cancellable;
+  IdeWorkbench *workbench;
+};
+
+static void workbench_addin_init_iface (IdeWorkbenchAddinInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GbpRunWorkbenchAddin, gbp_run_workbench_addin, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN,
+                                               workbench_addin_init_iface))
+
+static void
+gbp_run_workbench_addin_run_cb (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer      user_data)
+{
+  IdeRunner *runner = (IdeRunner *)object;
+  g_autoptr(GbpRunWorkbenchAddin) self = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_RUNNER (runner));
+  g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
+
+  if (!ide_runner_run_finish (runner, result, &error))
+    {
+      /* TODO: Use notification api */
+      g_warning ("%s", error->message);
+    }
+
+  /* Update stop command/state */
+}
+
+static void
+gbp_run_workbench_addin_run (GSimpleAction *action,
+                             GVariant      *param,
+                             gpointer       user_data)
+{
+  GbpRunWorkbenchAddin *self = user_data;
+  g_autoptr(IdeRunner) runner = NULL;
+  IdeConfigurationManager *config_manager;
+  IdeConfiguration *config;
+  IdeBuildSystem *build_system;
+  IdeBuildTarget *build_target;
+  IdeContext *context;
+  IdeRuntime *runtime;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
+
+  context = ide_workbench_get_context (self->workbench);
+
+  config_manager = ide_context_get_configuration_manager (context);
+  config = ide_configuration_manager_get_current (config_manager);
+  runtime = ide_configuration_get_runtime (config);
+
+  if (runtime == NULL)
+    {
+      /* TODO: Use notification API */
+      ide_context_warning (context,
+                           "%s “%s”",
+                           _("Failed to locate runtime"),
+                           ide_configuration_get_runtime_id (config));
+      return;
+    }
+
+  build_system = ide_context_get_build_system (context);
+  build_target = ide_build_system_get_default_run_target (build_system, config);
+
+  if (build_target == NULL)
+    {
+      ide_context_warning (context, "%s", _("Failed to locate build target"));
+      return;
+    }
+
+  runner = ide_runtime_create_runner (runtime, build_target);
+
+  g_clear_object (&self->cancellable);
+  self->cancellable = g_cancellable_new ();
+
+  ide_runner_run_async (runner,
+                        self->cancellable,
+                        gbp_run_workbench_addin_run_cb,
+                        g_object_ref (self));
+}
+
+static void
+gbp_run_workbench_addin_load (IdeWorkbenchAddin *addin,
+                              IdeWorkbench      *workbench)
+{
+  GbpRunWorkbenchAddin *self = (GbpRunWorkbenchAddin *)addin;
+  IdeWorkbenchHeaderBar *headerbar;
+  g_autoptr(GSimpleActionGroup) group = NULL;
+  GtkWidget *button;
+  static const GActionEntry entries[] = {
+    { "run", gbp_run_workbench_addin_run },
+  };
+
+  g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  self->workbench = workbench;
+
+  headerbar = ide_workbench_get_headerbar (workbench);
+  button = g_object_new (GTK_TYPE_BUTTON,
+                         "action-name", "run-tools.run",
+                         "focus-on-click", FALSE,
+                         "child", g_object_new (GTK_TYPE_IMAGE,
+                                                "icon-name", "media-playback-start-symbolic",
+                                                "visible", TRUE,
+                                                NULL),
+                         "visible", TRUE,
+                         NULL);
+  ide_widget_add_style_class (button, "image-button");
+  ide_workbench_header_bar_insert_right (headerbar, button, GTK_PACK_START, 0);
+
+  group = g_simple_action_group_new ();
+  g_action_map_add_action_entries (G_ACTION_MAP (group), entries, G_N_ELEMENTS (entries), self);
+  gtk_widget_insert_action_group (GTK_WIDGET (workbench), "run-tools", G_ACTION_GROUP (group));
+}
+
+static void
+gbp_run_workbench_addin_unload (IdeWorkbenchAddin *addin,
+                                IdeWorkbench      *workbench)
+{
+  GbpRunWorkbenchAddin *self = (GbpRunWorkbenchAddin *)addin;
+
+  g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+  g_assert (workbench == self->workbench);
+
+  self->workbench = NULL;
+
+  if (self->cancellable != NULL)
+    {
+      if (!g_cancellable_is_cancelled (self->cancellable))
+        g_cancellable_cancel (self->cancellable);
+      g_clear_object (&self->cancellable);
+    }
+}
+
+static void
+workbench_addin_init_iface (IdeWorkbenchAddinInterface *iface)
+{
+  iface->load = gbp_run_workbench_addin_load;
+  iface->unload = gbp_run_workbench_addin_unload;
+}
+
+static void
+gbp_run_workbench_addin_class_init (GbpRunWorkbenchAddinClass *klass)
+{
+}
+
+static void
+gbp_run_workbench_addin_init (GbpRunWorkbenchAddin *self)
+{
+}
diff --git a/plugins/run-tools/gbp-run-workbench-addin.h b/plugins/run-tools/gbp-run-workbench-addin.h
new file mode 100644
index 0000000..d7c655d
--- /dev/null
+++ b/plugins/run-tools/gbp-run-workbench-addin.h
@@ -0,0 +1,32 @@
+/* gbp-run-workbench-addin.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GBP_RUN_WORKBENCH_ADDIN_H
+#define GBP_RUN_WORKBENCH_ADDIN_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_RUN_WORKBENCH_ADDIN (gbp_run_workbench_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpRunWorkbenchAddin, gbp_run_workbench_addin, GBP, RUN_WORKBENCH_ADDIN, GObject)
+
+G_END_DECLS
+
+#endif /* GBP_RUN_WORKBENCH_ADDIN_H */
diff --git a/plugins/run-tools/run-tools.plugin b/plugins/run-tools/run-tools.plugin
new file mode 100644
index 0000000..6116bb1
--- /dev/null
+++ b/plugins/run-tools/run-tools.plugin
@@ -0,0 +1,11 @@
+[Plugin]
+Module=run-tools-plugin
+Name=Run Tools
+Description=Tools to run a project
+Authors=Christian Hergert <chergert redhat com>
+Copyright=Copyright © 2016 Christian Hergert
+Depends=editor
+Hidden=true
+Builtin=true
+X-Tool-Name=run
+X-Tool-Description=Run a project


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