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



commit 35e07ab10c2248ec63dd85a4a563232de693dd33
Author: Christian Hergert <chergert redhat com>
Date:   Sun Jul 17 01:43:15 2016 -0700

    wip

 configure.ac                                    |    2 +
 contrib/egg/egg-binding-group.h                 |   54 +-
 libide/Makefile.am                              |   12 +
 libide/application/ide-application-actions.c    |    2 +-
 libide/buildsystem/OVERVIEW.md                  |    6 +
 libide/buildsystem/ide-build-manager.c          |  868 +++++++++++++++++++++++
 libide/buildsystem/ide-build-manager.h          |   58 ++
 libide/buildsystem/ide-build-system.c           |   61 ++
 libide/buildsystem/ide-build-system.h           |   76 ++-
 libide/buildsystem/ide-build-target.c           |   44 ++
 libide/buildsystem/ide-build-target.h           |   52 ++
 libide/buildsystem/ide-builder.c                |   39 +
 libide/buildsystem/ide-builder.h                |   54 +-
 libide/devices/ide-device.c                     |   11 +
 libide/devices/ide-device.h                     |   21 +-
 libide/directory/ide-directory-vcs.c            |   48 ++-
 libide/ide-context.c                            |   43 ++
 libide/ide-context.h                            |    2 +
 libide/ide-enums.c.in                           |    2 +
 libide/ide-types.h                              |    5 +-
 libide/ide.h                                    |    5 +
 libide/runner/OVERVIEW.md                       |   89 +++
 libide/runner/ide-run-manager.c                 |  351 +++++++++
 libide/runner/ide-run-manager.h                 |   44 ++
 libide/runner/ide-runner-addin.c                |  141 ++++
 libide/runner/ide-runner-addin.h                |   78 ++
 libide/runner/ide-runner.c                      |  653 +++++++++++++++++
 libide/runner/ide-runner.h                      |   43 +-
 libide/runtimes/ide-runtime-manager.c           |   10 +-
 libide/runtimes/ide-runtime.c                   |   72 ++
 libide/runtimes/ide-runtime.h                   |   30 +
 libide/vcs/ide-vcs.c                            |   14 +
 libide/workbench/ide-layout-tab.c               |    9 +
 libide/workbench/ide-omni-bar-row.ui            |    1 +
 libide/workbench/ide-omni-bar.c                 |  627 ++++++++++-------
 libide/workbench/ide-omni-bar.h                 |    5 +-
 libide/workbench/ide-omni-bar.ui                |  188 +++--
 libide/workbench/ide-workbench-header-bar.ui    |    2 +-
 libide/workbench/ide-workbench.c                |    6 +
 plugins/Makefile.am                             |    7 +-
 plugins/autotools/Makefile.am                   |    2 +
 plugins/autotools/ide-autotools-build-system.c  |   90 +++
 plugins/autotools/ide-autotools-build-target.c  |  166 +++++
 plugins/autotools/ide-autotools-build-target.h  |   32 +
 plugins/autotools/ide-autotools-build-task.c    |   72 ++-
 plugins/autotools/ide-autotools-build-task.h    |    3 +-
 plugins/autotools/ide-autotools-builder.c       |   89 +++-
 plugins/autotools/ide-autotools-runner-addin.h  |   32 +
 plugins/autotools/ide-makecache.c               |  391 ++++++++++-
 plugins/autotools/ide-makecache.h               |   55 +-
 plugins/build-tools/gbp-build-workbench-addin.c |  274 +-------
 plugins/flatpak/gbp-flatpak-runtime.c           |    6 +-
 plugins/gcc/gbp-gcc-build-result-addin.c        |    7 +-
 plugins/git/ide-git-vcs.c                       |   42 +-
 plugins/jhbuild/jhbuild_plugin.py               |    5 +-
 plugins/project-tree/gb-project-tree.c          |    3 +-
 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     |  296 ++++++++
 plugins/run-tools/gbp-run-workbench-addin.h     |   32 +
 plugins/run-tools/run-tools.plugin              |   11 +
 62 files changed, 4762 insertions(+), 748 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/contrib/egg/egg-binding-group.h b/contrib/egg/egg-binding-group.h
index b1f18df..83645a6 100644
--- a/contrib/egg/egg-binding-group.h
+++ b/contrib/egg/egg-binding-group.h
@@ -28,35 +28,31 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EggBindingGroup, egg_binding_group, EGG, BINDING_GROUP, GObject)
 
-EggBindingGroup *
-          egg_binding_group_new        (void);
-
-GObject  *egg_binding_group_get_source (EggBindingGroup       *self);
-void      egg_binding_group_set_source (EggBindingGroup       *self,
-                                        gpointer               source);
-
-void      egg_binding_group_bind       (EggBindingGroup       *self,
-                                        const gchar           *source_property,
-                                        gpointer               target,
-                                        const gchar           *target_property,
-                                        GBindingFlags          flags);
-void      egg_binding_group_bind_full  (EggBindingGroup       *self,
-                                        const gchar           *source_property,
-                                        gpointer               target,
-                                        const gchar           *target_property,
-                                        GBindingFlags          flags,
-                                        GBindingTransformFunc  transform_to,
-                                        GBindingTransformFunc  transform_from,
-                                        gpointer               user_data,
-                                        GDestroyNotify         user_data_destroy);
-void           egg_binding_group_bind_with_closures
-                                       (EggBindingGroup       *self,
-                                        const gchar           *source_property,
-                                        gpointer               target,
-                                        const gchar           *target_property,
-                                        GBindingFlags          flags,
-                                        GClosure              *transform_to,
-                                        GClosure              *transform_from);
+EggBindingGroup *egg_binding_group_new                (void);
+GObject         *egg_binding_group_get_source         (EggBindingGroup       *self);
+void             egg_binding_group_set_source         (EggBindingGroup       *self,
+                                                       gpointer               source);
+void             egg_binding_group_bind               (EggBindingGroup       *self,
+                                                       const gchar           *source_property,
+                                                       gpointer               target,
+                                                       const gchar           *target_property,
+                                                       GBindingFlags          flags);
+void             egg_binding_group_bind_full          (EggBindingGroup       *self,
+                                                       const gchar           *source_property,
+                                                       gpointer               target,
+                                                       const gchar           *target_property,
+                                                       GBindingFlags          flags,
+                                                       GBindingTransformFunc  transform_to,
+                                                       GBindingTransformFunc  transform_from,
+                                                       gpointer               user_data,
+                                                       GDestroyNotify         user_data_destroy);
+void             egg_binding_group_bind_with_closures (EggBindingGroup       *self,
+                                                       const gchar           *source_property,
+                                                       gpointer               target,
+                                                       const gchar           *target_property,
+                                                       GBindingFlags          flags,
+                                                       GClosure              *transform_to,
+                                                       GClosure              *transform_from);
 
 G_END_DECLS
 
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 91459fb..7966d44 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -30,9 +30,11 @@ libide_1_0_la_public_headers =                            \
        buffers/ide-buffer.h                              \
        buffers/ide-unsaved-file.h                        \
        buffers/ide-unsaved-files.h                       \
+       buildsystem/ide-build-manager.h                   \
        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 +89,9 @@ libide_1_0_la_public_headers =                            \
        projects/ide-project-miner.h                      \
        projects/ide-project.h                            \
        projects/ide-recent-projects.h                    \
+       runner/ide-run-manager.h                          \
+       runner/ide-runner.h                               \
+       runner/ide-runner-addin.h                         \
        runtimes/ide-runtime-manager.h                    \
        runtimes/ide-runtime-provider.h                   \
        runtimes/ide-runtime.h                            \
@@ -170,9 +175,11 @@ libide_1_0_la_public_sources =                            \
        buffers/ide-buffer.c                              \
        buffers/ide-unsaved-file.c                        \
        buffers/ide-unsaved-files.c                       \
+       buildsystem/ide-build-manager.c                   \
        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 +239,9 @@ libide_1_0_la_public_sources =                            \
        projects/ide-project-miner.c                      \
        projects/ide-project.c                            \
        projects/ide-recent-projects.c                    \
+       runner/ide-run-manager.c                          \
+       runner/ide-runner.c                               \
+       runner/ide-runner-addin.c                         \
        runtimes/ide-runtime-manager.c                    \
        runtimes/ide-runtime-provider.c                   \
        runtimes/ide-runtime.c                            \
@@ -535,10 +545,12 @@ endif
 glib_enum_headers =                        \
        buffers/ide-buffer.h               \
        buildsystem/ide-build-result.h     \
+       devices/ide-device.h               \
        diagnostics/ide-diagnostic.h       \
        doap/ide-doap.h                    \
        files/ide-indent-style.h           \
        highlighting/ide-highlighter.h     \
+       runtimes/ide-runtime.h             \
        sourceview/ide-source-view.h       \
        symbols/ide-symbol.h               \
        threading/ide-thread-pool.h        \
diff --git a/libide/application/ide-application-actions.c b/libide/application/ide-application-actions.c
index 80d4304..4ece3ad 100644
--- a/libide/application/ide-application-actions.c
+++ b/libide/application/ide-application-actions.c
@@ -339,5 +339,5 @@ ide_application_actions_init (IdeApplication *self)
   gtk_application_set_accels_for_action (GTK_APPLICATION (self), "perspective.new-file", new_file);
   gtk_application_set_accels_for_action (GTK_APPLICATION (self), "win.global-search", global_search);
   gtk_application_set_accels_for_action (GTK_APPLICATION (self), "win.show-command-bar", command_bar);
-  gtk_application_set_accels_for_action (GTK_APPLICATION (self), "build-tools.build", build);
+  gtk_application_set_accels_for_action (GTK_APPLICATION (self), "build-manager.build", build);
 }
diff --git a/libide/buildsystem/OVERVIEW.md b/libide/buildsystem/OVERVIEW.md
index 1382a0a..643a506 100644
--- a/libide/buildsystem/OVERVIEW.md
+++ b/libide/buildsystem/OVERVIEW.md
@@ -40,3 +40,9 @@ GListModel to make writing environment editors easier.
 ## ide-environment-variable.c
 
 A single variable within the environment.
+
+## ide-build-manager.c
+
+The BuildManager provides a convenient place to manage a single build process
+for the context. Many build systems do not allow concurrent builds, so this
+is a good way to ensure that two plugins do not race to use the build system.
diff --git a/libide/buildsystem/ide-build-manager.c b/libide/buildsystem/ide-build-manager.c
new file mode 100644
index 0000000..4ce6c24
--- /dev/null
+++ b/libide/buildsystem/ide-build-manager.c
@@ -0,0 +1,868 @@
+/* ide-build-manager.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-build-manager"
+
+#include <egg-signal-group.h>
+#include <glib/gi18n.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+
+#include "buildsystem/ide-builder.h"
+#include "buildsystem/ide-build-manager.h"
+#include "buildsystem/ide-build-result.h"
+#include "buildsystem/ide-build-system.h"
+#include "buildsystem/ide-build-target.h"
+#include "buildsystem/ide-configuration.h"
+#include "buildsystem/ide-configuration-manager.h"
+
+struct _IdeBuildManager
+{
+  IdeObject             parent_instance;
+
+  EggSignalGroup       *signals;
+  IdeBuildResult       *build_result;
+  GCancellable         *cancellable;
+  GDateTime            *last_build_time;
+  GSimpleActionGroup   *actions;
+
+  guint                 has_diagnostics : 1;
+};
+
+static void action_group_iface_init (GActionGroupInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeBuildManager, ide_build_manager, IDE_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP, action_group_iface_init))
+
+enum {
+  PROP_0,
+  PROP_BUSY,
+  PROP_HAS_DIAGNOSTICS,
+  PROP_LAST_BUILD_TIME,
+  PROP_MESSAGE,
+  PROP_RUNNING_TIME,
+  N_PROPS
+};
+
+enum {
+  BUILD_STARTED,
+  BUILD_FINISHED,
+  BUILD_FAILED,
+  N_SIGNALS
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+ide_build_manager__build_result__notify_mode (IdeBuildManager *self,
+                                              GParamSpec      *mode_pspec,
+                                              IdeBuildResult  *build_result)
+{
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
+}
+
+static void
+ide_build_manager__build_result__notify_running (IdeBuildManager *self,
+                                                 GParamSpec      *running_pspec,
+                                                 IdeBuildResult  *build_result)
+{
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+}
+
+static void
+ide_build_manager__build_result__notify_running_time (IdeBuildManager *self,
+                                                      GParamSpec      *running_time_pspec,
+                                                      IdeBuildResult  *build_result)
+{
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUNNING_TIME]);
+}
+
+static void
+ide_build_manager__build_result__diagnostic (IdeBuildManager *self,
+                                             IdeDiagnostic   *diagnostic,
+                                             IdeBuildResult  *build_result)
+{
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+  g_assert (diagnostic != NULL);
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  if (self->has_diagnostics == FALSE)
+    {
+      self->has_diagnostics = TRUE;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_DIAGNOSTICS]);
+    }
+}
+
+static void
+ide_build_manager_build_activate (GSimpleAction *action,
+                                  GVariant      *parameter,
+                                  gpointer       user_data)
+{
+  IdeBuildManager *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  ide_build_manager_build_async (self,
+                                 NULL,
+                                 IDE_BUILDER_BUILD_FLAGS_NONE,
+                                 NULL,
+                                 NULL,
+                                 NULL);
+}
+
+static void
+ide_build_manager_rebuild_activate (GSimpleAction *action,
+                                    GVariant      *parameter,
+                                    gpointer       user_data)
+{
+  IdeBuildManager *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  ide_build_manager_build_async (self,
+                                 NULL,
+                                 IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN,
+                                 NULL,
+                                 NULL,
+                                 NULL);
+}
+
+static void
+ide_build_manager_cancel_activate (GSimpleAction *action,
+                                   GVariant      *parameter,
+                                   gpointer       user_data)
+{
+  IdeBuildManager *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  ide_build_manager_cancel (self);
+}
+
+static void
+ide_build_manager_clean_activate (GSimpleAction *action,
+                                  GVariant      *parameter,
+                                  gpointer       user_data)
+{
+  IdeBuildManager *self = user_data;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  ide_build_manager_build_async (self,
+                                 NULL,
+                                 (IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN |
+                                  IDE_BUILDER_BUILD_FLAGS_NO_BUILD),
+                                 NULL,
+                                 NULL,
+                                 NULL);
+}
+
+static void
+ide_build_manager__build_result__notify_failed (IdeBuildManager *self,
+                                                GParamSpec      *pspec,
+                                                IdeBuildResult  *build_result)
+{
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+
+  if (ide_build_result_get_failed (build_result))
+    g_signal_emit (self, signals [BUILD_FAILED], 0, build_result);
+}
+
+static void
+ide_build_manager_real_build_started (IdeBuildManager *self,
+                                      IdeBuildResult  *build_result)
+{
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+}
+
+static void
+ide_build_manager_real_build_failed (IdeBuildManager *self,
+                                     IdeBuildResult  *build_result)
+{
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+}
+
+static void
+ide_build_manager_real_build_finished (IdeBuildManager *self,
+                                       IdeBuildResult  *build_result)
+{
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+}
+
+static void
+ide_build_manager_finalize (GObject *object)
+{
+  IdeBuildManager *self = (IdeBuildManager *)object;
+
+  g_clear_object (&self->build_result);
+  g_clear_object (&self->signals);
+  g_clear_object (&self->actions);
+  g_clear_object (&self->cancellable);
+  g_clear_pointer (&self->last_build_time, g_date_time_unref);
+
+  G_OBJECT_CLASS (ide_build_manager_parent_class)->finalize (object);
+}
+
+static void
+ide_build_manager_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeBuildManager *self = IDE_BUILD_MANAGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUSY:
+      g_value_set_boolean (value, ide_build_manager_get_busy (self));
+      break;
+
+    case PROP_LAST_BUILD_TIME:
+      g_value_set_boxed (value, ide_build_manager_get_last_build_time (self));
+      break;
+
+    case PROP_HAS_DIAGNOSTICS:
+      g_value_set_boolean (value, self->has_diagnostics);
+      break;
+
+    case PROP_MESSAGE:
+      g_value_take_string (value, ide_build_manager_get_message (self));
+      break;
+
+    case PROP_RUNNING_TIME:
+      g_value_set_int64 (value, ide_build_manager_get_running_time (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_build_manager_class_init (IdeBuildManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_build_manager_finalize;
+  object_class->get_property = ide_build_manager_get_property;
+
+  properties [PROP_BUSY] =
+    g_param_spec_boolean ("busy",
+                          "Busy",
+                          "If the build manager is busy building",
+                          FALSE,
+                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LAST_BUILD_TIME] =
+    g_param_spec_boxed ("last-build-time",
+                        "Last Build Time",
+                        "The time the last build was submitted",
+                        G_TYPE_DATE_TIME,
+                        (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_HAS_DIAGNOSTICS] =
+    g_param_spec_boolean ("has-diagnostics",
+                          "Has Diagnostics",
+                          "If the build result has diagnostics",
+                          FALSE,
+                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_MESSAGE] =
+    g_param_spec_string ("message",
+                         "Message",
+                         "The current build message",
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_RUNNING_TIME] =
+    g_param_spec_int64 ("running-time",
+                        "Running Time",
+                        "The duration of the build as a GTimeSpan",
+                        0,
+                        G_MAXINT64,
+                        0,
+                        (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [BUILD_STARTED] =
+    g_signal_new_class_handler ("build-started",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST,
+                                G_CALLBACK (ide_build_manager_real_build_started),
+                                NULL,
+                                NULL,
+                                NULL,
+                                G_TYPE_NONE, 1, IDE_TYPE_BUILD_RESULT);
+
+  signals [BUILD_FAILED] =
+    g_signal_new_class_handler ("build-failed",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST,
+                                G_CALLBACK (ide_build_manager_real_build_failed),
+                                NULL,
+                                NULL,
+                                NULL,
+                                G_TYPE_NONE, 1, IDE_TYPE_BUILD_RESULT);
+
+  signals [BUILD_FINISHED] =
+    g_signal_new_class_handler ("build-finished",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST,
+                                G_CALLBACK (ide_build_manager_real_build_finished),
+                                NULL,
+                                NULL,
+                                NULL,
+                                G_TYPE_NONE, 1, IDE_TYPE_BUILD_RESULT);
+}
+
+static void
+ide_build_manager_init (IdeBuildManager *self)
+{
+  static const GActionEntry action_entries[] = {
+    { "build", ide_build_manager_build_activate },
+    { "cancel", ide_build_manager_cancel_activate },
+    { "clean", ide_build_manager_clean_activate },
+    { "rebuild", ide_build_manager_rebuild_activate },
+  };
+
+  self->signals = egg_signal_group_new (IDE_TYPE_BUILD_RESULT);
+
+  egg_signal_group_connect_object (self->signals,
+                                   "notify::failed",
+                                   G_CALLBACK (ide_build_manager__build_result__notify_failed),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  egg_signal_group_connect_object (self->signals,
+                                   "notify::mode",
+                                   G_CALLBACK (ide_build_manager__build_result__notify_mode),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  egg_signal_group_connect_object (self->signals,
+                                   "notify::running",
+                                   G_CALLBACK (ide_build_manager__build_result__notify_running),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  egg_signal_group_connect_object (self->signals,
+                                   "notify::running-time",
+                                   G_CALLBACK (ide_build_manager__build_result__notify_running_time),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  egg_signal_group_connect_object (self->signals,
+                                   "diagnostic",
+                                   G_CALLBACK (ide_build_manager__build_result__diagnostic),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  self->actions = g_simple_action_group_new ();
+
+  g_action_map_add_action_entries (G_ACTION_MAP (self->actions),
+                                   action_entries,
+                                   G_N_ELEMENTS (action_entries),
+                                   self);
+
+  g_object_bind_property (self,
+                          "busy",
+                          g_action_map_lookup_action (G_ACTION_MAP (self->actions), "build"),
+                          "enabled",
+                          G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+  g_object_bind_property (self,
+                          "busy",
+                          g_action_map_lookup_action (G_ACTION_MAP (self->actions), "rebuild"),
+                          "enabled",
+                          G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+  g_object_bind_property (self,
+                          "busy",
+                          g_action_map_lookup_action (G_ACTION_MAP (self->actions), "clean"),
+                          "enabled",
+                          G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+  g_object_bind_property (self,
+                          "busy",
+                          g_action_map_lookup_action (G_ACTION_MAP (self->actions), "cancel"),
+                          "enabled",
+                          G_BINDING_SYNC_CREATE);
+
+  g_signal_connect_object (self->actions,
+                           "action-enabled-changed",
+                           G_CALLBACK (g_action_group_action_enabled_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+ide_build_manager_set_build_result (IdeBuildManager *self,
+                                    IdeBuildResult  *build_result)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (!build_result || IDE_IS_BUILD_RESULT (build_result));
+
+  if (g_set_object (&self->build_result, build_result))
+    {
+      egg_signal_group_set_target (self->signals, build_result);
+
+      self->has_diagnostics = FALSE;
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LAST_BUILD_TIME]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUNNING_TIME]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_DIAGNOSTICS]);
+
+      g_signal_emit (self, signals [BUILD_STARTED], 0, build_result);
+    }
+
+  IDE_EXIT;
+}
+
+static gboolean
+ide_build_manager_check_busy (IdeBuildManager  *self,
+                              GError          **error)
+{
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  if (ide_build_manager_get_busy (self))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_BUSY,
+                   "%s",
+                   _("A build is already in progress"));
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static IdeBuilder *
+ide_build_manager_get_builder (IdeBuildManager  *self,
+                               GError          **error)
+{
+  IdeConfigurationManager *config_manager;
+  IdeConfiguration *config;
+  IdeBuildSystem *build_system;
+  IdeContext *context;
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  config_manager = ide_context_get_configuration_manager (context);
+  config = ide_configuration_manager_get_current (config_manager);
+
+  build_system = ide_context_get_build_system (context);
+
+  return ide_build_system_get_builder (build_system, config, error);
+}
+
+static void
+ide_build_manager_build_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  IdeBuilder *builder = (IdeBuilder *)object;
+  g_autoptr(IdeBuildResult) build_result = NULL;
+  g_autoptr(GTask) task = user_data;
+  IdeBuildManager *self;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUILDER (builder));
+  g_assert (G_IS_TASK (task));
+
+  self = g_task_get_source_object (task);
+  build_result = ide_builder_build_finish (builder, result, &error);
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (!build_result || IDE_IS_BUILD_RESULT (build_result));
+
+  if (self->build_result != NULL)
+    g_signal_emit (self, signals [BUILD_FINISHED], 0, self->build_result);
+
+  if (build_result == NULL)
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  g_task_return_boolean (task, TRUE);
+
+failure:
+  IDE_EXIT;
+}
+
+void
+ide_build_manager_build_async (IdeBuildManager      *self,
+                               IdeBuildTarget       *build_target,
+                               IdeBuilderBuildFlags  build_flags,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(IdeBuilder) builder = NULL;
+  g_autoptr(IdeBuildResult) build_result = NULL;
+  g_autoptr(GCancellable) local_cancellable = NULL;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_BUILD_MANAGER (self));
+  g_return_if_fail (!build_target || IDE_IS_BUILD_TARGET (build_target));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (cancellable == NULL)
+    cancellable = local_cancellable = g_cancellable_new ();
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_build_manager_build_async);
+
+  if (ide_build_manager_check_busy (self, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_EXIT;
+    }
+
+  if (NULL == (builder = ide_build_manager_get_builder (self, &error)))
+    {
+      g_task_return_error (task, error);
+      IDE_EXIT;
+    }
+
+  g_set_object (&self->cancellable, cancellable);
+
+  /*
+   * TODO: We need to add support to IdeBuilder to allow specifying what
+   *       build target we want to ensure is built. That way, we can possibly
+   *       reduce how much we build in the future. Probably something like:
+   *       ide_builder_add_build_target(builder, build_target);
+   */
+
+  ide_builder_build_async (builder,
+                           build_flags,
+                           &build_result,
+                           cancellable,
+                           ide_build_manager_build_cb,
+                           g_object_ref (task));
+
+  ide_build_manager_set_build_result (self, build_result);
+
+  /*
+   * Update our last build time.
+   */
+  g_clear_pointer (&self->last_build_time, g_date_time_unref);
+  self->last_build_time = g_date_time_new_now_local ();
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LAST_BUILD_TIME]);
+
+  IDE_EXIT;
+}
+
+gboolean
+ide_build_manager_build_finish (IdeBuildManager  *self,
+                                GAsyncResult     *result,
+                                GError          **error)
+{
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_BUILD_MANAGER (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);
+
+  ret = g_task_propagate_boolean (G_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static void
+ide_build_manager_install_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  IdeBuilder *builder = (IdeBuilder *)object;
+  g_autoptr(IdeBuildResult) build_result = NULL;
+  g_autoptr(GTask) task = user_data;
+  IdeBuildManager *self;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUILDER (builder));
+
+  self = g_task_get_source_object (task);
+  build_result = ide_builder_install_finish (builder, result, &error);
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (!build_result || IDE_IS_BUILD_RESULT (build_result));
+
+  if (self->build_result != NULL)
+    g_signal_emit (self, signals [BUILD_FINISHED], 0, self->build_result);
+
+  if (build_result == NULL)
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  g_task_return_boolean (task, TRUE);
+
+failure:
+  IDE_EXIT;
+}
+
+void
+ide_build_manager_install_async (IdeBuildManager     *self,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(IdeBuilder) builder = NULL;
+  g_autoptr(IdeBuildResult) build_result = NULL;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_BUILD_MANAGER (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_build_manager_install_async);
+
+  if (ide_build_manager_check_busy (self, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_EXIT;
+    }
+
+  if (NULL == (builder = ide_build_manager_get_builder (self, &error)))
+    {
+      g_task_return_error (task, error);
+      IDE_EXIT;
+    }
+
+  g_set_object (&self->cancellable, cancellable);
+
+  /*
+   * We might be able to save some build time if we can limit the target
+   * that needs to be installed. However, it's unclear that we want that
+   * because it could result in incomplete installation unless the build
+   * system compensates for it.
+   */
+
+  ide_builder_install_async (builder,
+                             &build_result,
+                             cancellable,
+                             ide_build_manager_install_cb,
+                             g_object_ref (task));
+
+  ide_build_manager_set_build_result (self, build_result);
+
+  /*
+   * Update our last build time.
+   */
+  g_clear_pointer (&self->last_build_time, g_date_time_unref);
+  self->last_build_time = g_date_time_new_now_local ();
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LAST_BUILD_TIME]);
+
+  IDE_EXIT;
+}
+
+gboolean
+ide_build_manager_install_finish (IdeBuildManager  *self,
+                                  GAsyncResult     *result,
+                                  GError          **error)
+{
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_BUILD_MANAGER (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);
+
+  ret = g_task_propagate_boolean (G_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+gboolean
+ide_build_manager_get_busy (IdeBuildManager *self)
+{
+  g_return_val_if_fail (IDE_IS_BUILD_MANAGER (self), FALSE);
+
+  if (self->build_result != NULL)
+    return ide_build_result_get_running (self->build_result);
+
+  return FALSE;
+}
+
+void
+ide_build_manager_cancel (IdeBuildManager *self)
+{
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_BUILD_MANAGER (self));
+
+  if (self->cancellable != NULL)
+    g_cancellable_cancel (self->cancellable);
+
+  IDE_EXIT;
+}
+
+gchar *
+ide_build_manager_get_message (IdeBuildManager *self)
+{
+  g_return_val_if_fail (IDE_IS_BUILD_MANAGER (self), NULL);
+
+  if (self->build_result != NULL)
+    return ide_build_result_get_mode (self->build_result);
+
+  return g_strdup (_("Ready"));
+}
+
+/**
+ * ide_build_manager_get_last_build_time:
+ * @self: An #IdeBuildManager
+ *
+ * Gets the time the last build was started. This is %NULL until a build
+ * has been executed in the context.
+ *
+ * Returns: (nullable) (transfer none): A #GDateTime or %NULL.
+ */
+GDateTime *
+ide_build_manager_get_last_build_time (IdeBuildManager *self)
+{
+  g_return_val_if_fail (IDE_IS_BUILD_MANAGER (self), NULL);
+
+  return self->last_build_time;
+}
+
+GTimeSpan
+ide_build_manager_get_running_time (IdeBuildManager *self)
+{
+  g_return_val_if_fail (IDE_IS_BUILD_MANAGER (self), 0);
+
+  if (self->build_result == NULL)
+    return 0;
+
+  return ide_build_result_get_running_time (self->build_result);
+}
+
+static gchar **
+ide_build_manager_list_actions (GActionGroup *action_group)
+{
+  IdeBuildManager *self = (IdeBuildManager *)action_group;
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  return g_action_group_list_actions (G_ACTION_GROUP (self->actions));
+}
+
+static gboolean
+ide_build_manager_query_action (GActionGroup        *action_group,
+                                const gchar         *action_name,
+                                gboolean            *enabled,
+                                const GVariantType **parameter_type,
+                                const GVariantType **state_type,
+                                GVariant           **state_hint,
+                                GVariant           **state)
+{
+  IdeBuildManager *self = (IdeBuildManager *)action_group;
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (action_name != NULL);
+
+  return g_action_group_query_action (G_ACTION_GROUP (self->actions),
+                                      action_name,
+                                      enabled,
+                                      parameter_type,
+                                      state_type,
+                                      state_hint,
+                                      state);
+}
+
+static void
+ide_build_manager_change_action_state (GActionGroup *action_group,
+                                       const gchar  *action_name,
+                                       GVariant     *value)
+{
+  IdeBuildManager *self = (IdeBuildManager *)action_group;
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (action_name != NULL);
+
+  g_action_group_change_action_state (G_ACTION_GROUP (self->actions), action_name, value);
+}
+
+static void
+ide_build_manager_activate_action (GActionGroup *action_group,
+                                   const gchar  *action_name,
+                                   GVariant     *parameter)
+{
+  IdeBuildManager *self = (IdeBuildManager *)action_group;
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (action_name != NULL);
+
+  g_action_group_activate_action (G_ACTION_GROUP (self->actions), action_name, parameter);
+}
+
+static void
+action_group_iface_init (GActionGroupInterface *iface)
+{
+  iface->list_actions = ide_build_manager_list_actions;
+  iface->query_action = ide_build_manager_query_action;
+  iface->change_action_state = ide_build_manager_change_action_state;
+  iface->activate_action = ide_build_manager_activate_action;
+}
diff --git a/libide/buildsystem/ide-build-manager.h b/libide/buildsystem/ide-build-manager.h
new file mode 100644
index 0000000..ffec27e
--- /dev/null
+++ b/libide/buildsystem/ide-build-manager.h
@@ -0,0 +1,58 @@
+/* ide-build-manager.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BUILD_MANAGER_H
+#define IDE_BUILD_MANAGER_H
+
+#include <gio/gio.h>
+
+#include "ide-object.h"
+
+#include "buildsystem/ide-builder.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BUILD_MANAGER (ide_build_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeBuildManager, ide_build_manager, IDE, BUILD_MANAGER, IdeObject)
+
+gboolean   ide_build_manager_get_busy            (IdeBuildManager       *self);
+gchar     *ide_build_manager_get_message         (IdeBuildManager       *self);
+GDateTime *ide_build_manager_get_last_build_time (IdeBuildManager       *self);
+GTimeSpan  ide_build_manager_get_running_time    (IdeBuildManager       *self);
+void       ide_build_manager_cancel              (IdeBuildManager       *self);
+void       ide_build_manager_build_async         (IdeBuildManager       *self,
+                                                  IdeBuildTarget        *build_target,
+                                                  IdeBuilderBuildFlags   build_flags,
+                                                  GCancellable          *cancellable,
+                                                  GAsyncReadyCallback    callback,
+                                                  gpointer               user_data);
+gboolean   ide_build_manager_build_finish        (IdeBuildManager       *self,
+                                                  GAsyncResult          *result,
+                                                  GError               **error);
+void       ide_build_manager_install_async       (IdeBuildManager       *self,
+                                                  GCancellable          *cancellable,
+                                                  GAsyncReadyCallback    callback,
+                                                  gpointer               user_data);
+gboolean   ide_build_manager_install_finish      (IdeBuildManager       *self,
+                                                  GAsyncResult          *result,
+                                                  GError               **error);
+
+G_END_DECLS
+
+#endif /* IDE_BUILD_MANAGER_H */
diff --git a/libide/buildsystem/ide-build-system.c b/libide/buildsystem/ide-build-system.c
index a2697cb..546bd58 100644
--- a/libide/buildsystem/ide-build-system.c
+++ b/libide/buildsystem/ide-build-system.c
@@ -129,9 +129,42 @@ ide_build_system_real_get_builder (IdeBuildSystem    *self,
 }
 
 static void
+ide_build_system_real_get_build_targets_async (IdeBuildSystem      *self,
+                                               GCancellable        *cancellable,
+                                               GAsyncReadyCallback  callback,
+                                               gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_BUILD_SYSTEM (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_build_system_real_get_build_targets_async);
+  g_task_return_pointer (task, g_ptr_array_new (), (GDestroyNotify)g_ptr_array_unref);
+}
+
+static GPtrArray *
+ide_build_system_real_get_build_targets_finish (IdeBuildSystem  *self,
+                                                GAsyncResult    *result,
+                                                GError         **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_assert (IDE_IS_BUILD_SYSTEM (self));
+  g_assert (G_IS_TASK (task));
+  g_assert (g_task_is_valid (task, self));
+  g_assert (g_task_get_source_tag (task) ==  ide_build_system_real_get_build_targets_async);
+
+  return g_task_propagate_pointer (task, error);
+}
+
+static void
 ide_build_system_default_init (IdeBuildSystemInterface *iface)
 {
   iface->get_builder = ide_build_system_real_get_builder;
+  iface->get_build_targets_async = ide_build_system_real_get_build_targets_async;
+  iface->get_build_targets_finish = ide_build_system_real_get_build_targets_finish;
 
   properties [PROP_PROJECT_FILE] =
     g_param_spec_object ("project-file",
@@ -242,3 +275,31 @@ ide_build_system_get_builder (IdeBuildSystem    *system,
   return IDE_BUILD_SYSTEM_GET_IFACE (system)->get_builder (system, configuration, error);
 }
 
+void
+ide_build_system_get_build_targets_async (IdeBuildSystem      *self,
+                                          GCancellable        *cancellable,
+                                          GAsyncReadyCallback  callback,
+                                          gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_BUILD_SYSTEM (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_BUILD_SYSTEM_GET_IFACE (self)->get_build_targets_async (self, cancellable, callback, user_data);
+}
+
+/**
+ * ide_build_system_get_build_targets_finish:
+ *
+ * Returns: (transfer container) (element-type Ide.BuildTarget): An array of build targets
+ *   or %NULL upon failure and @error is set.
+ */
+GPtrArray *
+ide_build_system_get_build_targets_finish (IdeBuildSystem  *self,
+                                           GAsyncResult    *result,
+                                           GError         **error)
+{
+  g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (self), NULL);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+  return IDE_BUILD_SYSTEM_GET_IFACE (self)->get_build_targets_finish (self, result, error);
+}
diff --git a/libide/buildsystem/ide-build-system.h b/libide/buildsystem/ide-build-system.h
index 3cb9e98..760a0ba 100644
--- a/libide/buildsystem/ide-build-system.h
+++ b/libide/buildsystem/ide-build-system.h
@@ -33,39 +33,53 @@ 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);
+  void             (*get_build_targets_async)  (IdeBuildSystem       *self,
+                                                GCancellable         *cancellable,
+                                                GAsyncReadyCallback   callback,
+                                                gpointer              user_data);
+  GPtrArray       *(*get_build_targets_finish) (IdeBuildSystem       *self,
+                                                GAsyncResult         *result,
+                                                GError              **error);
 };
 
-gint            ide_build_system_get_priority           (IdeBuildSystem       *self);
-void            ide_build_system_get_build_flags_async  (IdeBuildSystem       *self,
-                                                         IdeFile              *file,
-                                                         GCancellable         *cancellable,
-                                                         GAsyncReadyCallback   callback,
-                                                         gpointer              user_data);
-gchar         **ide_build_system_get_build_flags_finish (IdeBuildSystem       *self,
-                                                         GAsyncResult         *result,
-                                                         GError              **error);
-void            ide_build_system_new_async              (IdeContext           *context,
-                                                         GFile                *project_file,
-                                                         GCancellable         *cancellable,
-                                                         GAsyncReadyCallback   callback,
-                                                         gpointer              user_data);
-IdeBuildSystem *ide_build_system_new_finish             (GAsyncResult         *result,
-                                                         GError              **error);
-IdeBuilder     *ide_build_system_get_builder            (IdeBuildSystem       *system,
-                                                         IdeConfiguration     *configuration,
-                                                         GError              **error);
+gint            ide_build_system_get_priority             (IdeBuildSystem       *self);
+void            ide_build_system_get_build_flags_async    (IdeBuildSystem       *self,
+                                                           IdeFile              *file,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+gchar         **ide_build_system_get_build_flags_finish   (IdeBuildSystem       *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
+void            ide_build_system_new_async                (IdeContext           *context,
+                                                           GFile                *project_file,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+IdeBuildSystem *ide_build_system_new_finish               (GAsyncResult         *result,
+                                                           GError              **error);
+IdeBuilder     *ide_build_system_get_builder              (IdeBuildSystem       *system,
+                                                           IdeConfiguration     *configuration,
+                                                           GError              **error);
+void            ide_build_system_get_build_targets_async  (IdeBuildSystem       *self,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+GPtrArray      *ide_build_system_get_build_targets_finish (IdeBuildSystem       *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
 
 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..939fde8
--- /dev/null
+++ b/libide/buildsystem/ide-build-target.c
@@ -0,0 +1,44 @@
+/* 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-build-target"
+
+#include "ide-build-target.h"
+
+G_DEFINE_INTERFACE (IdeBuildTarget, ide_build_target, IDE_TYPE_OBJECT)
+
+static void
+ide_build_target_default_init (IdeBuildTargetInterface *iface)
+{
+}
+
+/**
+ * ide_build_target_get_install_directory:
+ *
+ * Returns: (nullable) (transfer full): A #GFile or %NULL.
+ */
+GFile *
+ide_build_target_get_install_directory (IdeBuildTarget *self)
+{
+  g_return_val_if_fail (IDE_IS_BUILD_TARGET (self), NULL);
+
+  if (IDE_BUILD_TARGET_GET_IFACE (self)->get_install_directory)
+    return IDE_BUILD_TARGET_GET_IFACE (self)->get_install_directory (self);
+
+  return NULL;
+}
diff --git a/libide/buildsystem/ide-build-target.h b/libide/buildsystem/ide-build-target.h
new file mode 100644
index 0000000..bc756d4
--- /dev/null
+++ b/libide/buildsystem/ide-build-target.h
@@ -0,0 +1,52 @@
+/* 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_INTERFACE (IdeBuildTarget, ide_build_target, IDE, BUILD_TARGET, IdeObject)
+
+struct _IdeBuildTargetInterface
+{
+  GTypeInterface parent_iface;
+
+  GFile *(*get_install_directory) (IdeBuildTarget *self);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+GFile *ide_build_target_get_install_directory (IdeBuildTarget *self);
+
+G_END_DECLS
+
+#endif /* IDE_BUILD_TARGET_H */
diff --git a/libide/buildsystem/ide-builder.c b/libide/buildsystem/ide-builder.c
index 982f0d2..165d58e 100644
--- a/libide/buildsystem/ide-builder.c
+++ b/libide/buildsystem/ide-builder.c
@@ -221,3 +221,42 @@ static void
 ide_builder_init (IdeBuilder *self)
 {
 }
+
+void
+ide_builder_install_async (IdeBuilder           *self,
+                           IdeBuildResult      **result,
+                           GCancellable         *cancellable,
+                           GAsyncReadyCallback   callback,
+                           gpointer              user_data)
+{
+  g_return_if_fail (IDE_IS_BUILDER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (result != NULL)
+    *result = NULL;
+
+  IDE_BUILDER_GET_CLASS (self)->install_async (self, result, cancellable, callback, user_data);
+}
+
+/**
+ * ide_builder_install_finish:
+ *
+ * Completes an asynchronous call to ide_builder_install_async().
+ *
+ * Returns: (transfer none): An #IdeBuildResult.
+ */
+IdeBuildResult *
+ide_builder_install_finish (IdeBuilder    *self,
+                            GAsyncResult  *result,
+                            GError       **error)
+{
+  IdeBuildResult *ret;
+
+  g_return_val_if_fail (IDE_IS_BUILDER (self), NULL);
+
+  ret = IDE_BUILDER_GET_CLASS (self)->install_finish (self, result, error);
+
+  g_return_val_if_fail (!ret || IDE_IS_BUILD_RESULT (ret), NULL);
+
+  return ret;
+}
diff --git a/libide/buildsystem/ide-builder.h b/libide/buildsystem/ide-builder.h
index 36604e3..3903615 100644
--- a/libide/buildsystem/ide-builder.h
+++ b/libide/buildsystem/ide-builder.h
@@ -40,27 +40,43 @@ struct _IdeBuilderClass
 {
   IdeObjectClass parent;
 
-  void            (*build_async)  (IdeBuilder           *builder,
-                                   IdeBuilderBuildFlags  flags,
-                                   IdeBuildResult      **result,
-                                   GCancellable         *cancellable,
-                                   GAsyncReadyCallback   callback,
-                                   gpointer              user_data);
-  IdeBuildResult *(*build_finish) (IdeBuilder           *builder,
-                                   GAsyncResult         *result,
-                                   GError              **error);
+  void            (*build_async)    (IdeBuilder            *self,
+                                     IdeBuilderBuildFlags   flags,
+                                     IdeBuildResult       **result,
+                                     GCancellable          *cancellable,
+                                     GAsyncReadyCallback    callback,
+                                     gpointer               user_data);
+  IdeBuildResult *(*build_finish)   (IdeBuilder            *self,
+                                     GAsyncResult          *result,
+                                     GError               **error);
+  void            (*install_async)  (IdeBuilder            *self,
+                                     IdeBuildResult       **result,
+                                     GCancellable          *cancellable,
+                                     GAsyncReadyCallback    callback,
+                                     gpointer               user_data);
+  IdeBuildResult *(*install_finish) (IdeBuilder            *self,
+                                     GAsyncResult          *result,
+                                     GError               **error);
 };
 
-IdeConfiguration *ide_builder_get_configuration (IdeBuilder           *self);
-void              ide_builder_build_async       (IdeBuilder           *builder,
-                                                IdeBuilderBuildFlags   flags,
-                                                IdeBuildResult       **result,
-                                                GCancellable          *cancellable,
-                                                GAsyncReadyCallback    callback,
-                                                gpointer               user_data);
-IdeBuildResult   *ide_builder_build_finish     (IdeBuilder            *builder,
-                                                GAsyncResult          *result,
-                                                GError               **error);
+IdeConfiguration *ide_builder_get_configuration (IdeBuilder            *self);
+void              ide_builder_build_async       (IdeBuilder            *self,
+                                                 IdeBuilderBuildFlags   flags,
+                                                 IdeBuildResult       **result,
+                                                 GCancellable          *cancellable,
+                                                 GAsyncReadyCallback    callback,
+                                                 gpointer               user_data);
+IdeBuildResult   *ide_builder_build_finish      (IdeBuilder            *self,
+                                                 GAsyncResult          *result,
+                                                 GError               **error);
+void              ide_builder_install_async     (IdeBuilder            *self,
+                                                 IdeBuildResult       **result,
+                                                 GCancellable          *cancellable,
+                                                 GAsyncReadyCallback    callback,
+                                                 gpointer               user_data);
+IdeBuildResult   *ide_builder_install_finish    (IdeBuilder            *self,
+                                                 GAsyncResult          *result,
+                                                 GError               **error);
 
 G_END_DECLS
 
diff --git a/libide/devices/ide-device.c b/libide/devices/ide-device.c
index e3e8220..70aba6f 100644
--- a/libide/devices/ide-device.c
+++ b/libide/devices/ide-device.c
@@ -250,3 +250,14 @@ ide_device_prepare_configuration (IdeDevice        *self,
   if (IDE_DEVICE_GET_CLASS (self)->prepare_configuration)
     IDE_DEVICE_GET_CLASS (self)->prepare_configuration (self, configuration);
 }
+
+GQuark
+ide_device_error_quark (void)
+{
+  static GQuark quark = 0;
+
+  if G_UNLIKELY (quark == 0)
+    quark = g_quark_from_static_string ("ide_device_error_quark");
+
+  return quark;
+}
diff --git a/libide/devices/ide-device.h b/libide/devices/ide-device.h
index 595436c..8215084 100644
--- a/libide/devices/ide-device.h
+++ b/libide/devices/ide-device.h
@@ -24,7 +24,13 @@
 
 G_BEGIN_DECLS
 
-#define IDE_TYPE_DEVICE (ide_device_get_type())
+typedef enum
+{
+  IDE_DEVICE_ERROR_NO_SUCH_DEVICE = 1,
+} IdeDeviceError;
+
+#define IDE_TYPE_DEVICE  (ide_device_get_type())
+#define IDE_DEVICE_ERROR (ide_device_error_quark())
 
 G_DECLARE_DERIVABLE_TYPE (IdeDevice, ide_device, IDE, DEVICE, IdeObject)
 
@@ -37,12 +43,13 @@ struct _IdeDeviceClass
                                          IdeConfiguration *configuration);
 };
 
-const gchar *ide_device_get_display_name      (IdeDevice   *self);
-void         ide_device_set_display_name      (IdeDevice   *self,
-                                               const gchar *display_name);
-const gchar *ide_device_get_id                (IdeDevice   *self);
-void         ide_device_set_id                (IdeDevice   *self,
-                                               const gchar *id);
+GQuark       ide_device_error_quark           (void) G_GNUC_CONST;
+const gchar *ide_device_get_display_name      (IdeDevice        *self);
+void         ide_device_set_display_name      (IdeDevice        *self,
+                                               const gchar      *display_name);
+const gchar *ide_device_get_id                (IdeDevice        *self);
+void         ide_device_set_id                (IdeDevice        *self,
+                                               const gchar      *id);
 const gchar *ide_device_get_system_type       (IdeDevice        *self);
 void         ide_device_prepare_configuration (IdeDevice        *self,
                                                IdeConfiguration *configuration);
diff --git a/libide/directory/ide-directory-vcs.c b/libide/directory/ide-directory-vcs.c
index 0c4d487..3d81d6a 100644
--- a/libide/directory/ide-directory-vcs.c
+++ b/libide/directory/ide-directory-vcs.c
@@ -40,6 +40,21 @@ G_DEFINE_TYPE_EXTENDED (IdeDirectoryVcs, ide_directory_vcs, IDE_TYPE_OBJECT, 0,
                         G_IMPLEMENT_INTERFACE (IDE_TYPE_VCS, vcs_iface_init)
                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init))
 
+enum {
+  PROP_0,
+  N_PROPS,
+
+  /* Override Properties */
+  PROP_BRANCH_NAME,
+  PROP_WORKING_DIRECTORY,
+};
+
+static gchar *
+ide_directory_vcs_get_branch_name (IdeVcs *vcs)
+{
+  return g_strdup (_("unversioned"));
+}
+
 static GFile *
 ide_directory_vcs_get_working_directory (IdeVcs *vcs)
 {
@@ -94,11 +109,38 @@ ide_directory_vcs_dispose (GObject *object)
 }
 
 static void
+ide_directory_vcs_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeDirectoryVcs *self = IDE_DIRECTORY_VCS (object);
+
+  switch (prop_id)
+    {
+    case PROP_BRANCH_NAME:
+      g_value_take_string (value, ide_directory_vcs_get_branch_name (IDE_VCS (self)));
+      break;
+
+    case PROP_WORKING_DIRECTORY:
+      g_value_set_object (value, ide_directory_vcs_get_working_directory (IDE_VCS (self)));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
 ide_directory_vcs_class_init (IdeDirectoryVcsClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
   object_class->dispose = ide_directory_vcs_dispose;
+  object_class->get_property = ide_directory_vcs_get_property;
+
+  g_object_class_override_property (object_class, PROP_BRANCH_NAME, "branch-name");
+  g_object_class_override_property (object_class, PROP_WORKING_DIRECTORY, "working-directory");
 }
 
 static void
@@ -193,12 +235,6 @@ ide_directory_vcs_get_priority (IdeVcs *vcs)
   return G_MAXINT;
 }
 
-static gchar *
-ide_directory_vcs_get_branch_name (IdeVcs *vcs)
-{
-  return g_strdup (_("unversioned"));
-}
-
 static void
 vcs_iface_init (IdeVcsInterface *iface)
 {
diff --git a/libide/ide-context.c b/libide/ide-context.c
index f279e57..633f75e 100644
--- a/libide/ide-context.c
+++ b/libide/ide-context.c
@@ -31,6 +31,7 @@
 #include "buffers/ide-buffer.h"
 #include "buffers/ide-unsaved-file.h"
 #include "buffers/ide-unsaved-files.h"
+#include "buildsystem/ide-build-manager.h"
 #include "buildsystem/ide-build-system.h"
 #include "buildsystem/ide-configuration-manager.h"
 #include "devices/ide-device-manager.h"
@@ -41,6 +42,7 @@
 #include "projects/ide-project-item.h"
 #include "projects/ide-project.h"
 #include "projects/ide-recent-projects.h"
+#include "runner/ide-run-manager.h"
 #include "runtimes/ide-runtime-manager.h"
 #include "scripting/ide-script-manager.h"
 #include "search/ide-search-engine.h"
@@ -59,11 +61,13 @@ struct _IdeContext
 
   IdeBackForwardList       *back_forward_list;
   IdeBufferManager         *buffer_manager;
+  IdeBuildManager          *build_manager;
   IdeBuildSystem           *build_system;
   IdeConfigurationManager  *configuration_manager;
   IdeDeviceManager         *device_manager;
   IdeDoap                  *doap;
   GtkRecentManager         *recent_manager;
+  IdeRunManager            *run_manager;
   IdeRuntimeManager        *runtime_manager;
   IdeScriptManager         *script_manager;
   IdeSearchEngine          *search_engine;
@@ -173,6 +177,19 @@ ide_context_get_buffer_manager (IdeContext *self)
 }
 
 /**
+ * ide_context_get_build_manager:
+ *
+ * Returns: (transfer none): An #IdeBuildManager.
+ */
+IdeBuildManager *
+ide_context_get_build_manager (IdeContext *self)
+{
+  g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
+
+  return self->build_manager;
+}
+
+/**
  * ide_context_get_build_system:
  *
  * Fetches the "build-system" property of @context.
@@ -811,6 +828,10 @@ ide_context_init (IdeContext *self)
                                        "context", self,
                                        NULL);
 
+  self->build_manager = g_object_new (IDE_TYPE_BUILD_MANAGER,
+                                      "context", self,
+                                      NULL);
+
   self->device_manager = g_object_new (IDE_TYPE_DEVICE_MANAGER,
                                        "context", self,
                                        NULL);
@@ -823,6 +844,10 @@ ide_context_init (IdeContext *self)
                                 "context", self,
                                 NULL);
 
+  self->run_manager = g_object_new (IDE_TYPE_RUN_MANAGER,
+                                    "context", self,
+                                    NULL);
+
   self->runtime_manager = g_object_new (IDE_TYPE_RUNTIME_MANAGER,
                                         "context", self,
                                         NULL);
@@ -2152,3 +2177,21 @@ ide_context_warning (IdeContext  *self,
   g_logv ("Ide", G_LOG_LEVEL_WARNING, format, args);
   va_end (args);
 }
+
+/**
+ * ide_context_get_run_manager:
+ *
+ * Gets the #IdeRunManager for the context. This manager object simplifies
+ * the process of running an #IdeBuildTarget from the build system. Primarily,
+ * it enforces that only a single target may be run at a time, since that is
+ * what the UI will expect.
+ *
+ * Returns: (transfer none): An #IdeRunManager.
+ */
+IdeRunManager *
+ide_context_get_run_manager (IdeContext *self)
+{
+  g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
+
+  return self->run_manager;
+}
diff --git a/libide/ide-context.h b/libide/ide-context.h
index 31d9158..96ea370 100644
--- a/libide/ide-context.h
+++ b/libide/ide-context.h
@@ -33,11 +33,13 @@ G_DECLARE_FINAL_TYPE (IdeContext, ide_context, IDE, CONTEXT, GObject)
 IdeBackForwardList       *ide_context_get_back_forward_list     (IdeContext           *self);
 GFile                    *ide_context_get_project_file          (IdeContext           *self);
 IdeBufferManager         *ide_context_get_buffer_manager        (IdeContext           *self);
+IdeBuildManager          *ide_context_get_build_manager         (IdeContext           *self);
 IdeBuildSystem           *ide_context_get_build_system          (IdeContext           *self);
 IdeConfigurationManager  *ide_context_get_configuration_manager (IdeContext           *self);
 IdeDeviceManager         *ide_context_get_device_manager        (IdeContext           *self);
 IdeProject               *ide_context_get_project               (IdeContext           *self);
 GtkRecentManager         *ide_context_get_recent_manager        (IdeContext           *self);
+IdeRunManager            *ide_context_get_run_manager           (IdeContext           *self);
 IdeRuntimeManager        *ide_context_get_runtime_manager       (IdeContext           *self);
 IdeScriptManager         *ide_context_get_script_manager        (IdeContext           *self);
 IdeSearchEngine          *ide_context_get_search_engine         (IdeContext           *self);
diff --git a/libide/ide-enums.c.in b/libide/ide-enums.c.in
index 3e37734..0f71244 100644
--- a/libide/ide-enums.c.in
+++ b/libide/ide-enums.c.in
@@ -6,10 +6,12 @@
 
 #include "buffers/ide-buffer.h"
 #include "buildsystem/ide-build-result.h"
+#include "devices/ide-device.h"
 #include "diagnostics/ide-diagnostic.h"
 #include "doap/ide-doap.h"
 #include "files/ide-indent-style.h"
 #include "highlighting/ide-highlighter.h"
+#include "runtimes/ide-runtime.h"
 #include "sourceview/ide-source-view.h"
 #include "symbols/ide-symbol.h"
 #include "threading/ide-thread-pool.h"
diff --git a/libide/ide-types.h b/libide/ide-types.h
index 7040331..89dedea 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -36,10 +36,10 @@ typedef struct _IdeBufferChangeMonitor         IdeBufferChangeMonitor;
 typedef struct _IdeBufferManager               IdeBufferManager;
 
 typedef struct _IdeBuilder                     IdeBuilder;
-
+typedef struct _IdeBuildManager                IdeBuildManager;
 typedef struct _IdeBuildResult                 IdeBuildResult;
-
 typedef struct _IdeBuildSystem                 IdeBuildSystem;
+typedef struct _IdeBuildTarget                 IdeBuildTarget;
 
 typedef struct _IdeConfiguration               IdeConfiguration;
 typedef struct _IdeConfigurationManager        IdeConfigurationManager;
@@ -83,6 +83,7 @@ typedef struct _IdeProjectFile                 IdeProjectFile;
 
 typedef struct _IdeProjectFiles                IdeProjectFiles;
 
+typedef struct _IdeRunManager                  IdeRunManager;
 typedef struct _IdeRuntime                     IdeRuntime;
 typedef struct _IdeRuntimeManager              IdeRuntimeManager;
 typedef struct _IdeRuntimeProvider             IdeRuntimeProvider;
diff --git a/libide/ide.h b/libide/ide.h
index 51ea85f..eaad665 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -35,9 +35,11 @@ G_BEGIN_DECLS
 #include "buffers/ide-buffer.h"
 #include "buffers/ide-unsaved-file.h"
 #include "buffers/ide-unsaved-files.h"
+#include "buildsystem/ide-build-manager.h"
 #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 +85,9 @@ G_BEGIN_DECLS
 #include "projects/ide-project-miner.h"
 #include "projects/ide-project.h"
 #include "projects/ide-recent-projects.h"
+#include "runner/ide-run-manager.h"
+#include "runner/ide-runner.h"
+#include "runner/ide-runner-addin.h"
 #include "runtimes/ide-runtime-manager.h"
 #include "runtimes/ide-runtime-provider.h"
 #include "runtimes/ide-runtime.h"
diff --git a/libide/runner/OVERVIEW.md b/libide/runner/OVERVIEW.md
new file mode 100644
index 0000000..cf5801a
--- /dev/null
+++ b/libide/runner/OVERVIEW.md
@@ -0,0 +1,89 @@
+# Running Projects
+
+Running a project target in Builder needs to accomidate a few difficult
+to plumb components. For example, Builder supports runtimes which might
+be different than the current host (such as org.gnome.Platform 3.22).
+Additionally, we might need to attach a debugger. The build system might
+also need to perform an installation of the application bits into a
+runtime so that the project can run as it's "installed state".
+
+All of these complexities results in project execution being abstracted
+into an “IdeRunner” object and series of “IdeRunnerAddin” extensions.
+This allows the various plugins involved (build system, runtime, possible
+debugger or profilers) to hook in to various stages and modify things as
+necessary.
+
+# IdeRunner
+
+This is our core runner structure. It manages some basic process execution
+stuff, but most things get tweaked and configured by the addins.
+
+The process is launched by the IdeSubprocessLauncher created by the configured
+runtime. However, addins can tweak things as necessary.
+
+## IdeRunnerAddin
+
+Plugins should implement this interface so that they can modify the runner
+as necessary. This should be used for custom extensoin points that are always
+needed, not to add integration points like debugger, profiler, etc.
+
+## Debugger Integration
+
+Debuggers may need to hook into the runtime pid namespace (and possibly mount
+namespace to bring along tooling). This could also mean starting the process as
+an inferior of something like `gdb`. In the flatpak scenario, this could be
+
+  flatpak reaper → gdb → inferior
+
+Additionally, the debugger will need to be able to IPC with the gdb instance
+inside the runtime.
+
+While we don't have support for this yet, I think the design we can use is that
+the `IdeRunner` can have API to bring in "sidecars" to the runtime which
+contain the debugging tooling. For the local system, this would be a
+passthrough. For something like flatpak, it would need to enter the namespace
+and add a mountpoint for the tooling. (This is probably something that needs to
+be implemented in bubblewrap to some degree).
+
+When the debugger sets up the runner, it will need to be able to modify argv
+similar to our IdeSubprocessLauncher. This will allow it to add gdb to the
+prefix of the command.
+
+Getting a runner will be somewhat consistent with:
+
+  BuildSystem = context.get_build_system()
+  ConfigManager = context.get_configuration_manager()
+
+  config = ConfigManager.get_current()
+  runtime = config.get_runtime()
+  target = BuildSystem.get_default_run_command(config)
+  runner = runtime.create_runner(config, target)
+
+    -> the build system, at this point, may have added a prehook via
+       the runner addin that will install the project before it can
+       be run.
+
+And the debugger might do something like:
+
+  runner.prepend_argv('gdbserver')
+  runner.prepend_argv('--once')
+  runner.prepend_argv('--args')
+
+When runner.run() is called, the implementation might choose to prepend
+additional argv parameters. This allows gdb to simply prepend the command
+line, but still have flatpak spawn the container process (bubblewrap).
+
+## Profiling Integration
+
+A profiler (such as sysprof) will need to know the process identifier of the
+spawned process (and possibly children processes). This is somewhat similar
+to debugging except that it isn't strictly necessary that the process stops
+before reaching `main()`.
+
+To synchronize on startup, sysprof might spawn a helper process that
+communicates with the listener process, and then execs the child command.
+
+So it might need to do something like:
+
+  runner.prepend_argv('sysprof-spawner')
+
diff --git a/libide/runner/ide-run-manager.c b/libide/runner/ide-run-manager.c
new file mode 100644
index 0000000..b86d93a
--- /dev/null
+++ b/libide/runner/ide-run-manager.c
@@ -0,0 +1,351 @@
+/* ide-run-manager.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-run-manager"
+
+#include <glib/gi18n.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+
+#include "buildsystem/ide-build-manager.h"
+#include "buildsystem/ide-build-target.h"
+#include "buildsystem/ide-configuration.h"
+#include "buildsystem/ide-configuration-manager.h"
+#include "runner/ide-run-manager.h"
+#include "runner/ide-runner.h"
+#include "runtimes/ide-runtime.h"
+
+struct _IdeRunManager
+{
+  IdeObject           parent_instance;
+
+  GCancellable       *cancellable;
+  GSimpleActionGroup *actions;
+
+  guint               busy : 1;
+};
+
+G_DEFINE_TYPE (IdeRunManager, ide_run_manager, IDE_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_BUSY,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_run_manager_finalize (GObject *object)
+{
+  IdeRunManager *self = (IdeRunManager *)object;
+
+  g_clear_object (&self->cancellable);
+  g_clear_object (&self->actions);
+
+  G_OBJECT_CLASS (ide_run_manager_parent_class)->finalize (object);
+}
+
+static void
+ide_run_manager_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  IdeRunManager *self = IDE_RUN_MANAGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUSY:
+      g_value_set_boolean (value, ide_run_manager_get_busy (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_run_manager_class_init (IdeRunManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_run_manager_finalize;
+  object_class->get_property = ide_run_manager_get_property;
+
+  properties [PROP_BUSY] =
+    g_param_spec_boolean ("busy",
+                          "Busy",
+                          "Busy",
+                          FALSE,
+                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_run_manager_init (IdeRunManager *self)
+{
+}
+
+gboolean
+ide_run_manager_get_busy (IdeRunManager *self)
+{
+  g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), FALSE);
+
+  return self->busy;
+}
+
+static gboolean
+ide_run_manager_check_busy (IdeRunManager  *self,
+                            GError        **error)
+{
+  g_assert (IDE_IS_RUN_MANAGER (self));
+  g_assert (error != NULL);
+
+  if (ide_run_manager_get_busy (self))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_BUSY,
+                   "%s",
+                   _("Cannot run target, another target is running"));
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+ide_run_manager_run_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  IdeRunner *runner = (IdeRunner *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_RUNNER (runner));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_runner_run_finish (runner, result, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  g_task_return_boolean (task, TRUE);
+
+failure:
+  IDE_EXIT;
+}
+
+static void
+ide_run_manager_install_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  IdeBuildManager *build_manager = (IdeBuildManager *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(IdeRunner) runner = NULL;
+  IdeConfigurationManager *config_manager;
+  IdeConfiguration *config;
+  IdeBuildTarget *build_target;
+  IdeRunManager *self;
+  GCancellable *cancellable;
+  IdeContext *context;
+  IdeRuntime *runtime;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUILD_MANAGER (build_manager));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_build_manager_build_finish (build_manager, result, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  build_target = g_task_get_task_data (task);
+  self = g_task_get_source_object (task);
+
+  g_assert (IDE_IS_BUILD_TARGET (build_target));
+  g_assert (IDE_IS_RUN_MANAGER (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  g_assert (IDE_IS_CONTEXT (context));
+
+  config_manager = ide_context_get_configuration_manager (context);
+  config = ide_configuration_manager_get_current (config_manager);
+  runtime = ide_configuration_get_runtime (config);
+
+  if (runtime == NULL)
+    {
+      g_task_return_new_error (task,
+                               IDE_RUNTIME_ERROR,
+                               IDE_RUNTIME_ERROR_NO_SUCH_RUNTIME,
+                               "%s “%s”",
+                               _("Failed to locate runtime"),
+                               ide_configuration_get_runtime_id (config));
+      IDE_GOTO (failure);
+    }
+
+  runner = ide_runtime_create_runner (runtime, build_target);
+  cancellable = g_task_get_cancellable (task);
+
+  g_assert (IDE_IS_RUNNER (runner));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  ide_runner_run_async (runner,
+                        cancellable,
+                        ide_run_manager_run_cb,
+                        g_steal_pointer (&task));
+
+failure:
+  IDE_EXIT;
+}
+
+static void
+ide_run_manager_task_completed (IdeRunManager *self,
+                                GParamSpec    *pspec,
+                                GTask         *task)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_RUN_MANAGER (self));
+  g_assert (pspec != NULL);
+  g_assert (G_IS_TASK (task));
+
+  self->busy = FALSE;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+
+  IDE_EXIT;
+}
+
+void
+ide_run_manager_run_async (IdeRunManager       *self,
+                           IdeBuildTarget      *build_target,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GCancellable) local_cancellable = NULL;
+  IdeBuildManager *build_manager;
+  IdeContext *context;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_RUN_MANAGER (self));
+  g_return_if_fail (IDE_IS_BUILD_TARGET (build_target));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (cancellable == NULL)
+    cancellable = local_cancellable = g_cancellable_new ();
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_run_manager_run_async);
+  g_task_set_task_data (task, g_object_ref (build_target), g_object_unref);
+
+  if (ide_run_manager_check_busy (self, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  /*
+   * First we need to make sure the target is up to date and installed
+   * so that all the dependent resources are available.
+   */
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  build_manager = ide_context_get_build_manager (context);
+
+  self->busy = TRUE;
+
+  g_set_object (&self->cancellable, cancellable);
+
+  g_signal_connect_object (task,
+                           "notify::completed",
+                           G_CALLBACK (ide_run_manager_task_completed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_build_manager_install_async (build_manager,
+                                   cancellable,
+                                   ide_run_manager_install_cb,
+                                   g_steal_pointer (&task));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+
+failure:
+  IDE_EXIT;
+}
+
+gboolean
+ide_run_manager_run_finish (IdeRunManager  *self,
+                            GAsyncResult   *result,
+                            GError        **error)
+{
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  ret = g_task_propagate_boolean (G_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static gboolean
+do_cancel_in_timeout (gpointer user_data)
+{
+  GCancellable *cancellable = user_data;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_CANCELLABLE (cancellable));
+
+  if (!g_cancellable_is_cancelled (cancellable))
+    g_cancellable_cancel (cancellable);
+
+  IDE_RETURN (G_SOURCE_REMOVE);
+}
+
+void
+ide_run_manager_cancel (IdeRunManager *self)
+{
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_RUN_MANAGER (self));
+
+  if (self->cancellable != NULL)
+    g_timeout_add (0, do_cancel_in_timeout, g_object_ref (self->cancellable));
+
+  IDE_EXIT;
+}
diff --git a/libide/runner/ide-run-manager.h b/libide/runner/ide-run-manager.h
new file mode 100644
index 0000000..49a3e3f
--- /dev/null
+++ b/libide/runner/ide-run-manager.h
@@ -0,0 +1,44 @@
+/* ide-run-manager.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_RUN_MANAGER_H
+#define IDE_RUN_MANAGER_H
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUN_MANAGER (ide_run_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeRunManager, ide_run_manager, IDE, RUN_MANAGER, IdeObject)
+
+void     ide_run_manager_cancel     (IdeRunManager        *self);
+gboolean ide_run_manager_get_busy   (IdeRunManager        *self);
+void     ide_run_manager_run_async  (IdeRunManager        *self,
+                                     IdeBuildTarget       *build_target,
+                                     GCancellable         *cancellable,
+                                     GAsyncReadyCallback   callback,
+                                     gpointer              user_data);
+gboolean ide_run_manager_run_finish (IdeRunManager        *self,
+                                     GAsyncResult         *result,
+                                     GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_RUN_MANAGER_H */
+
diff --git a/libide/runner/ide-runner-addin.c b/libide/runner/ide-runner-addin.c
new file mode 100644
index 0000000..d2a455b
--- /dev/null
+++ b/libide/runner/ide-runner-addin.c
@@ -0,0 +1,141 @@
+/* ide-runner-addin.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-runner-addin"
+
+#include "ide-runner-addin.h"
+
+G_DEFINE_INTERFACE (IdeRunnerAddin, ide_runner_addin, G_TYPE_OBJECT)
+
+static void
+ide_runner_addin_real_load (IdeRunnerAddin *self,
+                            IdeRunner      *runner)
+{
+}
+
+static void
+ide_runner_addin_real_unload (IdeRunnerAddin *self,
+                              IdeRunner      *runner)
+{
+}
+
+static void
+dummy_async (IdeRunnerAddin      *self,
+             GCancellable        *cancellable,
+             GAsyncReadyCallback  callback,
+             gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (callback == NULL)
+    return;
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+dummy_finish (IdeRunnerAddin  *self,
+              GAsyncResult    *result,
+              GError         **error)
+{
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_runner_addin_default_init (IdeRunnerAddinInterface *iface)
+{
+  iface->load = ide_runner_addin_real_load;
+  iface->unload = ide_runner_addin_real_unload;
+  iface->prehook_async = dummy_async;
+  iface->prehook_finish = dummy_finish;
+  iface->posthook_async = dummy_async;
+  iface->posthook_finish = dummy_finish;
+}
+
+void
+ide_runner_addin_load (IdeRunnerAddin *self,
+                       IdeRunner      *runner)
+{
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (IDE_IS_RUNNER (runner));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->load (self, runner);
+}
+
+void
+ide_runner_addin_unload (IdeRunnerAddin *self,
+                         IdeRunner      *runner)
+{
+  g_assert (IDE_IS_RUNNER_ADDIN (self));
+  g_assert (IDE_IS_RUNNER (runner));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->unload (self, runner);
+}
+
+void
+ide_runner_addin_prehook_async (IdeRunnerAddin      *self,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_RUNNER_ADDIN (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->prehook_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runner_addin_prehook_finish (IdeRunnerAddin  *self,
+                                 GAsyncResult    *result,
+                                 GError         **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER_ADDIN (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_RUNNER_ADDIN_GET_IFACE (self)->prehook_finish (self, result, error);
+}
+
+void
+ide_runner_addin_posthook_async (IdeRunnerAddin      *self,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_RUNNER_ADDIN (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_RUNNER_ADDIN_GET_IFACE (self)->posthook_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runner_addin_posthook_finish (IdeRunnerAddin  *self,
+                                  GAsyncResult    *result,
+                                  GError         **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER_ADDIN (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_RUNNER_ADDIN_GET_IFACE (self)->posthook_finish (self, result, error);
+}
diff --git a/libide/runner/ide-runner-addin.h b/libide/runner/ide-runner-addin.h
new file mode 100644
index 0000000..60e6a17
--- /dev/null
+++ b/libide/runner/ide-runner-addin.h
@@ -0,0 +1,78 @@
+/* ide-runner-addin.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_RUNNER_ADDIN_H
+#define IDE_RUNNER_ADDIN_H
+
+#include <gio/gio.h>
+
+#include "ide-types.h"
+#include "ide-runner.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUNNER_ADDIN (ide_runner_addin_get_type())
+
+G_DECLARE_INTERFACE (IdeRunnerAddin, ide_runner_addin, IDE, RUNNER_ADDIN, GObject)
+
+struct _IdeRunnerAddinInterface
+{
+  GTypeInterface parent_interface;
+
+  void     (*load)            (IdeRunnerAddin       *self,
+                               IdeRunner            *runner);
+  void     (*unload)          (IdeRunnerAddin       *self,
+                               IdeRunner            *runner);
+  void     (*prehook_async)   (IdeRunnerAddin       *self,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data);
+  gboolean (*prehook_finish)  (IdeRunnerAddin       *self,
+                               GAsyncResult         *result,
+                               GError              **error);
+  void     (*posthook_async)  (IdeRunnerAddin       *self,
+                               GCancellable         *cancellable,
+                               GAsyncReadyCallback   callback,
+                               gpointer              user_data);
+  gboolean (*posthook_finish) (IdeRunnerAddin       *self,
+                               GAsyncResult         *result,
+                               GError              **error);
+};
+
+void     ide_runner_addin_load            (IdeRunnerAddin       *self,
+                                           IdeRunner            *runner);
+void     ide_runner_addin_unload          (IdeRunnerAddin       *self,
+                                           IdeRunner            *runner);
+void     ide_runner_addin_prehook_async   (IdeRunnerAddin       *self,
+                                           GCancellable         *cancellable,
+                                           GAsyncReadyCallback   callback,
+                                           gpointer              user_data);
+gboolean ide_runner_addin_prehook_finish  (IdeRunnerAddin       *self,
+                                           GAsyncResult         *result,
+                                           GError              **error);
+void     ide_runner_addin_posthook_async  (IdeRunnerAddin       *self,
+                                           GCancellable         *cancellable,
+                                           GAsyncReadyCallback   callback,
+                                           gpointer              user_data);
+gboolean ide_runner_addin_posthook_finish (IdeRunnerAddin       *self,
+                                           GAsyncResult         *result,
+                                           GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_RUNNER_ADDIN_H */
diff --git a/libide/runner/ide-runner.c b/libide/runner/ide-runner.c
new file mode 100644
index 0000000..8b4f1e0
--- /dev/null
+++ b/libide/runner/ide-runner.c
@@ -0,0 +1,653 @@
+/* ide-runner.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-runner"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+#include <stdlib.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+
+#include "runner/ide-runner.h"
+#include "runner/ide-runner-addin.h"
+#include "workers/ide-subprocess-launcher.h"
+
+typedef struct
+{
+  PeasExtensionSet *addins;
+  GQueue argv;
+} IdeRunnerPrivate;
+
+typedef struct
+{
+  GSList *prehook_queue;
+  GSList *posthook_queue;
+} IdeRunnerRunState;
+
+enum {
+  PROP_0,
+  PROP_ARGV,
+  N_PROPS
+};
+
+static void ide_runner_tick_posthook (GTask *task);
+static void ide_runner_tick_prehook  (GTask *task);
+static void ide_runner_tick_run      (GTask *task);
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeRunner, ide_runner, IDE_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static IdeRunnerAddin *
+pop_runner_addin (GSList **list)
+{
+  IdeRunnerAddin *ret;
+
+  g_assert (list != NULL);
+  g_assert (*list != NULL);
+
+  ret = (*list)->data;
+
+  *list = g_slist_delete_link (*list, *list);
+
+  return ret;
+}
+
+static void
+ide_runner_run_state_free (gpointer data)
+{
+  IdeRunnerRunState *state = data;
+
+  g_slist_foreach (state->prehook_queue, (GFunc)g_object_unref, NULL);
+  g_slist_free (state->prehook_queue);
+
+  g_slist_foreach (state->posthook_queue, (GFunc)g_object_unref, NULL);
+  g_slist_free (state->posthook_queue);
+
+  g_slice_free (IdeRunnerRunState, state);
+}
+
+static void
+ide_runner_run_wait_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  GSubprocess *subprocess = (GSubprocess *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SUBPROCESS (subprocess));
+
+  if (!g_subprocess_wait_finish (subprocess, result, &error))
+    {
+      g_task_return_error (task, error);
+      IDE_EXIT;
+    }
+
+  if (g_subprocess_get_if_exited (subprocess))
+    {
+      gint exit_code;
+
+      exit_code = g_subprocess_get_exit_status (subprocess);
+
+      if (exit_code == EXIT_SUCCESS)
+        {
+          g_task_return_boolean (task, TRUE);
+          IDE_EXIT;
+        }
+    }
+
+  g_task_return_new_error (task,
+                           G_IO_ERROR,
+                           G_IO_ERROR_FAILED,
+                           "%s",
+                           _("Process quit unexpectedly"));
+
+  IDE_EXIT;
+}
+
+static void
+ide_runner_real_run_async (IdeRunner           *self,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autoptr(GSubprocess) subprocess = NULL;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_runner_real_run_async);
+
+  launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+
+  for (GList *iter = priv->argv.head; iter != NULL; iter = iter->next)
+    ide_subprocess_launcher_push_argv (launcher, iter->data);
+
+  ide_subprocess_launcher_set_cwd (launcher, g_get_home_dir ());
+
+  subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, &error);
+
+  g_assert (subprocess == NULL || G_IS_SUBPROCESS (subprocess));
+
+  if (subprocess == NULL)
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  g_subprocess_wait_async (subprocess,
+                           cancellable,
+                           ide_runner_run_wait_cb,
+                           g_steal_pointer (&task));
+
+failure:
+  IDE_EXIT;
+}
+
+static gboolean
+ide_runner_real_run_finish (IdeRunner     *self,
+                            GAsyncResult  *result,
+                            GError       **error)
+{
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (G_IS_TASK (result));
+  g_assert (g_task_is_valid (G_TASK (result), self));
+  g_assert (g_task_get_source_tag (G_TASK (result)) == ide_runner_real_run_async);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_runner_extension_added (PeasExtensionSet *set,
+                            PeasPluginInfo   *plugin_info,
+                            PeasExtension    *exten,
+                            gpointer          user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
+  IdeRunner *self = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (IDE_IS_RUNNER (self));
+
+  ide_runner_addin_load (addin, self);
+}
+
+static void
+ide_runner_extension_removed (PeasExtensionSet *set,
+                              PeasPluginInfo   *plugin_info,
+                              PeasExtension    *exten,
+                              gpointer          user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
+  IdeRunner *self = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (IDE_IS_RUNNER (self));
+
+  ide_runner_addin_unload (addin, self);
+}
+
+static void
+ide_runner_constructed (GObject *object)
+{
+  IdeRunner *self = (IdeRunner *)object;
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  G_OBJECT_CLASS (ide_runner_parent_class)->constructed (object);
+
+  priv->addins = peas_extension_set_new (peas_engine_get_default (),
+                                         IDE_TYPE_RUNNER_ADDIN,
+                                         NULL);
+
+  g_signal_connect (priv->addins,
+                    "extension-added",
+                    G_CALLBACK (ide_runner_extension_added),
+                    self);
+
+  g_signal_connect (priv->addins,
+                    "extension-removed",
+                    G_CALLBACK (ide_runner_extension_removed),
+                    self);
+
+  peas_extension_set_foreach (priv->addins,
+                              ide_runner_extension_added,
+                              self);
+}
+
+static void
+ide_runner_finalize (GObject *object)
+{
+  IdeRunner *self = (IdeRunner *)object;
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
+  g_queue_clear (&priv->argv);
+
+  G_OBJECT_CLASS (ide_runner_parent_class)->finalize (object);
+}
+
+static void
+ide_runner_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  IdeRunner *self = IDE_RUNNER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ARGV:
+      g_value_take_boxed (value, ide_runner_get_argv (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_runner_set_property (GObject      *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  IdeRunner *self = IDE_RUNNER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ARGV:
+      ide_runner_set_argv (self, g_value_get_boxed (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_runner_class_init (IdeRunnerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_runner_constructed;
+  object_class->finalize = ide_runner_finalize;
+  object_class->get_property = ide_runner_get_property;
+  object_class->set_property = ide_runner_set_property;
+
+  klass->run_async = ide_runner_real_run_async;
+  klass->run_finish = ide_runner_real_run_finish;
+
+  properties [PROP_ARGV] =
+    g_param_spec_boxed ("argv",
+                        "Argv",
+                        "The argument list for the command",
+                        G_TYPE_STRV,
+                        (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_runner_init (IdeRunner *self)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_queue_init (&priv->argv);
+}
+
+/**
+ * ide_runner_get_stdin:
+ *
+ * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
+ */
+GInputStream *
+ide_runner_get_stdin (IdeRunner *self)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  return IDE_RUNNER_GET_CLASS (self)->get_stdin (self);
+}
+
+/**
+ * ide_runner_get_stdout:
+ *
+ * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
+ */
+GOutputStream *
+ide_runner_get_stdout (IdeRunner *self)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  return IDE_RUNNER_GET_CLASS (self)->get_stdout (self);
+}
+
+/**
+ * ide_runner_get_stderr:
+ *
+ * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
+ */
+GOutputStream *
+ide_runner_get_stderr (IdeRunner *self)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  return IDE_RUNNER_GET_CLASS (self)->get_stderr (self);
+}
+
+void
+ide_runner_force_quit (IdeRunner *self)
+{
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  IDE_RUNNER_GET_CLASS (self)->force_quit (self);
+}
+
+void
+ide_runner_set_argv (IdeRunner           *self,
+                     const gchar * const *argv)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+  guint i;
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
+  g_queue_clear (&priv->argv);
+
+  if (argv != NULL)
+    {
+      for (i = 0; argv [i]; i++)
+        g_queue_push_tail (&priv->argv, g_strdup (argv [i]));
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
+}
+
+/**
+ * ide_runner_get_argv:
+ *
+ * Gets the argument list as a newly allocated string array.
+ *
+ * Returns: (transfer full): A newly allocated string array that should
+ *   be freed with g_strfreev().
+ */
+gchar **
+ide_runner_get_argv (IdeRunner *self)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+  GPtrArray *ar;
+  GList *iter;
+
+  g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
+
+  ar = g_ptr_array_new ();
+
+  for (iter = priv->argv.head; iter != NULL; iter = iter->next)
+    {
+      const gchar *param = iter->data;
+
+      g_ptr_array_add (ar, g_strdup (param));
+    }
+
+  g_ptr_array_add (ar, NULL);
+
+  return (gchar **)g_ptr_array_free (ar, FALSE);
+}
+
+static void
+ide_runner_collect_addins_cb (PeasExtensionSet *set,
+                              PeasPluginInfo   *plugin_info,
+                              PeasExtension    *exten,
+                              gpointer          user_data)
+{
+  GSList **list = user_data;
+
+  *list = g_slist_prepend (*list, exten);
+}
+
+static void
+ide_runner_collect_addins (IdeRunner  *self,
+                           GSList    **list)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (list != NULL);
+
+  peas_extension_set_foreach (priv->addins,
+                              ide_runner_collect_addins_cb,
+                              list);
+}
+
+static void
+ide_runner_posthook_cb (GObject      *object,
+                        GAsyncResult *result,
+                        gpointer      user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!ide_runner_addin_posthook_finish (addin, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_runner_tick_posthook (task);
+}
+
+static void
+ide_runner_tick_posthook (GTask *task)
+{
+  IdeRunnerRunState *state;
+
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+
+  if (state->posthook_queue != NULL)
+    {
+      g_autoptr(IdeRunnerAddin) addin = NULL;
+
+      addin = pop_runner_addin (&state->posthook_queue);
+      ide_runner_addin_posthook_async (addin,
+                                       g_task_get_cancellable (task),
+                                       ide_runner_posthook_cb,
+                                       g_object_ref (task));
+      return;
+    }
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_runner_run_cb (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  IdeRunner *self = (IdeRunner *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNNER (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!IDE_RUNNER_GET_CLASS (self)->run_finish (self, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_runner_tick_posthook (task);
+}
+
+static void
+ide_runner_tick_run (GTask *task)
+{
+  IdeRunner *self;
+
+  g_assert (G_IS_TASK (task));
+
+  self = g_task_get_source_object (task);
+
+  IDE_RUNNER_GET_CLASS (self)->run_async (self,
+                                          g_task_get_cancellable (task),
+                                          ide_runner_run_cb,
+                                          g_object_ref (task));
+}
+
+static void
+ide_runner_prehook_cb (GObject      *object,
+                       GAsyncResult *result,
+                       gpointer      user_data)
+{
+  IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNNER_ADDIN (addin));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!ide_runner_addin_prehook_finish (addin, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_runner_tick_prehook (task);
+}
+
+static void
+ide_runner_tick_prehook (GTask *task)
+{
+  IdeRunnerRunState *state;
+
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+
+  if (state->prehook_queue != NULL)
+    {
+      g_autoptr(IdeRunnerAddin) addin = NULL;
+
+      addin = pop_runner_addin (&state->prehook_queue);
+      ide_runner_addin_prehook_async (addin,
+                                      g_task_get_cancellable (task),
+                                      ide_runner_prehook_cb,
+                                      g_object_ref (task));
+      return;
+    }
+
+  ide_runner_tick_run (task);
+}
+
+void
+ide_runner_run_async (IdeRunner           *self,
+                      GCancellable        *cancellable,
+                      GAsyncReadyCallback  callback,
+                      gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  IdeRunnerRunState *state;
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_runner_run_async);
+  g_task_set_check_cancellable (task, FALSE);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+
+  /*
+   * We need to run the prehook functions for each addin first before we
+   * can call our IdeRunnerClass.run vfunc.  Since these are async, we
+   * have to bring some state along with us.
+   */
+  state = g_slice_new0 (IdeRunnerRunState);
+  ide_runner_collect_addins (self, &state->prehook_queue);
+  ide_runner_collect_addins (self, &state->posthook_queue);
+  g_task_set_task_data (task, state, ide_runner_run_state_free);
+
+  ide_runner_tick_prehook (task);
+}
+
+gboolean
+ide_runner_run_finish (IdeRunner     *self,
+                       GAsyncResult  *result,
+                       GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+ide_runner_append_argv (IdeRunner   *self,
+                        const gchar *param)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+  g_return_if_fail (param != NULL);
+
+  g_queue_push_tail (&priv->argv, g_strdup (param));
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
+}
+
+void
+ide_runner_prepend_argv (IdeRunner   *self,
+                         const gchar *param)
+{
+  IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RUNNER (self));
+  g_return_if_fail (param != NULL);
+
+  g_queue_push_head (&priv->argv, g_strdup (param));
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
+}
+
+IdeRunner *
+ide_runner_new (IdeContext *context)
+{
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+  return g_object_new (IDE_TYPE_RUNNER,
+                       "context", context,
+                       NULL);
+}
diff --git a/libide/runner/ide-runner.h b/libide/runner/ide-runner.h
index 94f2c12..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-manager.c b/libide/runtimes/ide-runtime-manager.c
index 6f76442..4568bba 100644
--- a/libide/runtimes/ide-runtime-manager.c
+++ b/libide/runtimes/ide-runtime-manager.c
@@ -21,9 +21,11 @@
 #include <glib/gi18n.h>
 #include <libpeas/peas.h>
 
-#include "ide-runtime.h"
-#include "ide-runtime-manager.h"
-#include "ide-runtime-provider.h"
+#include "ide-context.h"
+
+#include "runtimes/ide-runtime.h"
+#include "runtimes/ide-runtime-manager.h"
+#include "runtimes/ide-runtime-provider.h"
 
 struct _IdeRuntimeManager
 {
@@ -79,6 +81,8 @@ ide_runtime_manager_constructed (GObject *object)
 
   context = ide_object_get_context (IDE_OBJECT (self));
 
+  g_assert (IDE_IS_CONTEXT (context));
+
   self->extensions = peas_extension_set_new (peas_engine_get_default (),
                                              IDE_TYPE_RUNTIME_PROVIDER,
                                              NULL);
diff --git a/libide/runtimes/ide-runtime.c b/libide/runtimes/ide-runtime.c
index a67f9d1..b9fff8e 100644
--- a/libide/runtimes/ide-runtime.c
+++ b/libide/runtimes/ide-runtime.c
@@ -169,12 +169,48 @@ ide_runtime_real_prepare_configuration (IdeRuntime       *self,
   ide_configuration_set_prefix (configuration, install_path);
 }
 
+static IdeRunner *
+ide_runtime_real_create_runner (IdeRuntime     *self,
+                                IdeBuildTarget *build_target)
+{
+  g_autofree gchar *name = NULL;
+  g_autofree gchar *binpath = NULL;
+  g_autoptr(GFile) installdir = NULL;
+  g_autoptr(GFile) bin = NULL;
+  IdeContext *context;
+  IdeRunner *runner;
+
+  g_assert (IDE_IS_RUNTIME (self));
+  g_assert (IDE_IS_BUILD_TARGET (build_target));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  g_assert (IDE_IS_CONTEXT (context));
+
+  runner = ide_runner_new (context);
+
+  g_assert (IDE_IS_RUNNER (runner));
+
+  g_object_get (build_target,
+                "install-directory", &installdir,
+                "name", &name,
+                NULL);
+
+  bin = g_file_get_child (installdir, name);
+  binpath = g_file_get_path (bin);
+
+  ide_runner_append_argv (runner, binpath);
+
+  return runner;
+}
+
 static void
 ide_runtime_finalize (GObject *object)
 {
   IdeRuntime *self = (IdeRuntime *)object;
   IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
 
+  g_clear_pointer (&priv->id, g_free);
   g_clear_pointer (&priv->display_name, g_free);
 
   G_OBJECT_CLASS (ide_runtime_parent_class)->finalize (object);
@@ -240,6 +276,7 @@ ide_runtime_class_init (IdeRuntimeClass *klass)
   klass->postbuild_async = ide_runtime_real_postbuild_async;
   klass->postbuild_finish = ide_runtime_real_postbuild_finish;
   klass->create_launcher = ide_runtime_real_create_launcher;
+  klass->create_runner = ide_runtime_real_create_runner;
   klass->contains_program_in_path = ide_runtime_real_contains_program_in_path;
   klass->prepare_configuration = ide_runtime_real_prepare_configuration;
 
@@ -324,6 +361,10 @@ ide_runtime_new (IdeContext  *context,
                  const gchar *id,
                  const gchar *display_name)
 {
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+  g_return_val_if_fail (id != NULL, NULL);
+  g_return_val_if_fail (display_name != NULL, NULL);
+
   return g_object_new (IDE_TYPE_RUNTIME,
                        "context", context,
                        "id", id,
@@ -407,3 +448,34 @@ ide_runtime_prepare_configuration (IdeRuntime       *self,
 
   IDE_RUNTIME_GET_CLASS (self)->prepare_configuration (self, configuration);
 }
+
+/**
+ * ide_runtime_create_runner:
+ *
+ * Creates a new runner that can be used to execute the build target within
+ * the runtime. This should be used to implement such features as "run target"
+ * or "run unit test" inside the target runtime.
+ *
+ * Returns: (transfer full) (nullable): An #IdeRunner if successful, otherwise
+ *   %NULL and @error is set.
+ */
+IdeRunner *
+ide_runtime_create_runner (IdeRuntime     *self,
+                           IdeBuildTarget *build_target)
+{
+  g_return_val_if_fail (IDE_IS_RUNTIME (self), NULL);
+  g_return_val_if_fail (IDE_IS_BUILD_TARGET (build_target), NULL);
+
+  return IDE_RUNTIME_GET_CLASS (self)->create_runner (self, build_target);
+}
+
+GQuark
+ide_runtime_error_quark (void)
+{
+  static GQuark quark = 0;
+
+  if G_UNLIKELY (quark == 0)
+    quark = g_quark_from_static_string ("ide_runtime_error_quark");
+
+  return quark;
+}
diff --git a/libide/runtimes/ide-runtime.h b/libide/runtimes/ide-runtime.h
index daa0fe0..cccdc3a 100644
--- a/libide/runtimes/ide-runtime.h
+++ b/libide/runtimes/ide-runtime.h
@@ -23,11 +23,19 @@
 
 #include "ide-object.h"
 
+#include "buildsystem/ide-build-target.h"
+#include "runner/ide-runner.h"
 #include "workers/ide-subprocess-launcher.h"
 
 G_BEGIN_DECLS
 
+typedef enum
+{
+  IDE_RUNTIME_ERROR_NO_SUCH_RUNTIME = 1,
+} IdeRuntimeError;
+
 #define IDE_TYPE_RUNTIME (ide_runtime_get_type())
+#define IDE_RUNTIME_ERROR (ide_runtime_error_quark())
 
 G_DECLARE_DERIVABLE_TYPE (IdeRuntime, ide_runtime, IDE, RUNTIME, IdeObject)
 
@@ -56,8 +64,28 @@ struct _IdeRuntimeClass
                                                       GError              **error);
   void                   (*prepare_configuration)    (IdeRuntime           *self,
                                                       IdeConfiguration     *configuration);
+  IdeRunner             *(*create_runner)            (IdeRuntime           *self,
+                                                      IdeBuildTarget       *build_target);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+  gpointer _reserved9;
+  gpointer _reserved10;
+  gpointer _reserved11;
+  gpointer _reserved12;
+  gpointer _reserved13;
+  gpointer _reserved14;
+  gpointer _reserved15;
+  gpointer _reserved16;
 };
 
+GQuark                 ide_runtime_error_quark              (void) G_GNUC_CONST;
 void                   ide_runtime_prebuild_async           (IdeRuntime           *self,
                                                              GCancellable         *cancellable,
                                                              GAsyncReadyCallback   callback,
@@ -77,6 +105,8 @@ gboolean               ide_runtime_contains_program_in_path (IdeRuntime
                                                              GCancellable         *cancellable);
 IdeSubprocessLauncher *ide_runtime_create_launcher          (IdeRuntime           *self,
                                                              GError              **error);
+IdeRunner             *ide_runtime_create_runner            (IdeRuntime           *self,
+                                                             IdeBuildTarget       *build_target);
 void                   ide_runtime_prepare_configuration    (IdeRuntime           *self,
                                                              IdeConfiguration     *configuration);
 IdeRuntime            *ide_runtime_new                      (IdeContext           *context,
diff --git a/libide/vcs/ide-vcs.c b/libide/vcs/ide-vcs.c
index f2033a1..1e0cb7d 100644
--- a/libide/vcs/ide-vcs.c
+++ b/libide/vcs/ide-vcs.c
@@ -43,6 +43,20 @@ ide_vcs_default_init (IdeVcsInterface *iface)
                                                             IDE_TYPE_CONTEXT,
                                                             (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | 
G_PARAM_STATIC_STRINGS)));
 
+  g_object_interface_install_property (iface,
+                                       g_param_spec_string ("branch-name",
+                                                            "Branch Name",
+                                                            "The current name of the branch",
+                                                            NULL,
+                                                            (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
+
+  g_object_interface_install_property (iface,
+                                       g_param_spec_object ("working-directory",
+                                                            "Working Directory",
+                                                            "The working directory for the VCS",
+                                                            G_TYPE_FILE,
+                                                            (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)));
+
   /**
    * IdeVcs::changed:
    *
diff --git a/libide/workbench/ide-layout-tab.c b/libide/workbench/ide-layout-tab.c
index 696426e..9eb39db 100644
--- a/libide/workbench/ide-layout-tab.c
+++ b/libide/workbench/ide-layout-tab.c
@@ -73,6 +73,11 @@ ide_layout_tab_connect (IdeLayoutTab *self)
   if (controls != NULL)
     gtk_container_add (GTK_CONTAINER (self->controls_container), controls);
 
+  g_signal_connect (self->view,
+                    "destroy",
+                    G_CALLBACK (gtk_widget_destroyed),
+                    &self->view);
+
   gtk_widget_set_visible (self->close_button, TRUE);
 }
 
@@ -92,6 +97,10 @@ ide_layout_tab_disconnect (IdeLayoutTab *self)
 {
   g_assert (IDE_IS_LAYOUT_TAB (self));
 
+  g_signal_handlers_disconnect_by_func (self->view,
+                                        G_CALLBACK (gtk_widget_destroyed),
+                                        &self->view);
+
   gtk_container_foreach (GTK_CONTAINER (self->controls_container),
                          ide_layout_tab_remove_control,
                          self);
diff --git a/libide/workbench/ide-omni-bar-row.ui b/libide/workbench/ide-omni-bar-row.ui
index 8412262..119acd5 100644
--- a/libide/workbench/ide-omni-bar-row.ui
+++ b/libide/workbench/ide-omni-bar-row.ui
@@ -100,6 +100,7 @@
               <object class="GtkLabel" id="runtime_title">
                 <property name="visible">true</property>
                 <property name="hexpand">true</property>
+                <property name="use-markup">true</property>
                 <property name="xalign">0.0</property>
                 <attributes>
                   <attribute name="scale" value=".7"/>
diff --git a/libide/workbench/ide-omni-bar.c b/libide/workbench/ide-omni-bar.c
index 7e04c19..7feded2 100644
--- a/libide/workbench/ide-omni-bar.c
+++ b/libide/workbench/ide-omni-bar.c
@@ -19,11 +19,13 @@
 #define G_LOG_DOMAIN "ide-omni-bar"
 
 #include <glib/gi18n.h>
+#include <egg-binding-group.h>
 #include <egg-signal-group.h>
 
 #include "ide-context.h"
 #include "ide-debug.h"
 
+#include "buildsystem/ide-build-manager.h"
 #include "buildsystem/ide-build-result.h"
 #include "buildsystem/ide-configuration.h"
 #include "buildsystem/ide-configuration-manager.h"
@@ -34,24 +36,80 @@
 #include "workbench/ide-omni-bar-row.h"
 
 #define LOOPER_INTERVAL_SECONDS 5
+#define SETTLE_MESSAGE_COUNT 2
 
 struct _IdeOmniBar
 {
-  GtkBox          parent_instance;
+  GtkBox parent_instance;
 
-  EggSignalGroup *build_result_signals;
-  GSource        *looper_source;
-  GtkGesture     *gesture;
+  /*
+   * This source is used to loop through the various messages that are
+   * available. It runs on a regular interval (LOOPER_INTERVAL_SECONDS).
+   * It isn't very smart, it doesn't even reset when the messages are
+   * changed.
+   */
+  GSource *looper_source;
+
+  /*
+   * This gesture is used to track "clicks" inside the omnibar. Upon
+   * click, the popover is displayed (or hidden) as necessary.
+   */
+  GtkGesture *gesture;
+
+  /*
+   * This manages the bindings we need for the IdeBuildManager instance.
+   * This includes various label text and state tracking to determine
+   * what actions we can apply and when.
+   */
+  EggBindingGroup *build_manager_bindings;
 
-  guint           seen_count;
+  /*
+   * This manages the signals we need for the IdeBuildManager instance.
+   * This includes tracking build start/failure/finished.
+   */
+  EggSignalGroup *build_manager_signals;
+
+  /*
+   * This manages the bindings we need for the IdeConfigurationManager
+   * such as the current configuration name.
+   */
+  EggBindingGroup *config_manager_bindings;
+
+  /*
+   * This manages the signals we need from the IdeConfigurationManager
+   * such as when the current configuration has been changed.
+   */
+  EggSignalGroup *config_manager_signals;
 
+  /*
+   * THis manages the bindings we need for the IdeVcs such as the
+   * current branch name.
+   */
+  EggBindingGroup *vcs_bindings;
+
+  /*
+   * This is the IdeBuildResult for the last requested built.
+   */
+  IdeBuildResult *build_result;
+
+  /*
+   * This tracks the number of times we have show the current build
+   * message while looping between the various messages. After our
+   * SETTLE_MESSAGE_COUNT has been reached, we stop flapping between
+   * messages.
+   */
+  guint seen_count;
+
+  /*
+   * The following are template children from the GtkBuilder template.
+   */
   GtkLabel       *branch_label;
   GtkEventBox    *event_box;
   GtkLabel       *project_label;
   GtkLabel       *build_result_mode_label;
   GtkImage       *build_result_diagnostics_image;
   GtkButton      *build_button;
-  GtkImage       *build_button_image;
+  GtkButton      *cancel_button;
   GtkLabel       *config_name_label;
   GtkStack       *message_stack;
   GtkPopover     *popover;
@@ -60,14 +118,102 @@ struct _IdeOmniBar
   GtkLabel       *popover_build_mode_label;
   GtkLabel       *popover_build_running_time_label;
   GtkListBox     *popover_configuration_list_box;
+  GtkRevealer    *popover_details_revealer;
   GtkLabel       *popover_failed_label;
   GtkLabel       *popover_last_build_time_label;
+  GtkStack       *popover_time_stack;
   GtkButton      *popover_view_output_button;
   GtkLabel       *popover_project_label;
 };
 
 G_DEFINE_TYPE (IdeOmniBar, ide_omni_bar, GTK_TYPE_BOX)
 
+static gboolean
+date_time_to_label (GBinding     *binding,
+                    const GValue *from_value,
+                    GValue       *to_value,
+                    gpointer      user_data)
+{
+  GDateTime *dt;
+
+  g_assert (G_IS_BINDING (binding));
+  g_assert (from_value != NULL);
+  g_assert (G_VALUE_HOLDS (from_value, G_TYPE_DATE_TIME));
+  g_assert (to_value != NULL);
+  g_assert (G_VALUE_HOLDS (to_value, G_TYPE_STRING));
+
+  if (NULL != (dt = g_value_get_boxed (from_value)))
+    g_value_take_string (to_value,
+                         g_date_time_format (dt, "%a %B %e, %X"));
+
+  return TRUE;
+}
+
+static gboolean
+time_span_to_label (GBinding     *binding,
+                    const GValue *from_value,
+                    GValue       *to_value,
+                    gpointer      user_data)
+{
+  GTimeSpan span;
+
+  g_assert (G_IS_BINDING (binding));
+  g_assert (from_value != NULL);
+  g_assert (G_VALUE_HOLDS (from_value, G_TYPE_INT64));
+  g_assert (to_value != NULL);
+  g_assert (G_VALUE_HOLDS (to_value, G_TYPE_STRING));
+
+  if (0 != (span = g_value_get_int64 (from_value)))
+    {
+      guint hours;
+      guint minutes;
+      guint seconds;
+
+      hours = span / G_TIME_SPAN_HOUR;
+      minutes = (span % G_TIME_SPAN_HOUR) / G_TIME_SPAN_MINUTE;
+      seconds = (span % G_TIME_SPAN_MINUTE) / G_TIME_SPAN_SECOND;
+
+      g_value_take_string (to_value,
+                           g_strdup_printf ("%02u:%02u:%02u", hours, minutes, seconds));
+    }
+
+  return TRUE;
+}
+
+static gboolean
+file_to_relative_path (GBinding     *binding,
+                       const GValue *from_value,
+                       GValue       *to_value,
+                       gpointer      user_data)
+{
+  GFile *file;
+
+  g_assert (G_IS_BINDING (binding));
+  g_assert (from_value != NULL);
+  g_assert (G_VALUE_HOLDS (from_value, G_TYPE_FILE));
+  g_assert (to_value != NULL);
+  g_assert (G_VALUE_HOLDS (to_value, G_TYPE_STRING));
+
+  if (NULL != (file = g_value_get_object (from_value)))
+    {
+      g_autoptr(GFile) home = NULL;
+      gchar *path;
+
+      home = g_file_new_for_path (g_get_home_dir ());
+
+      if (g_file_has_prefix (file, home))
+        path = g_file_get_relative_path (home, file);
+      else if (g_file_is_native (file))
+        path = g_file_get_path (file);
+      else
+        path = g_file_get_uri (file);
+
+      g_value_take_string (to_value, path);
+    }
+
+  return TRUE;
+}
+
 static void
 on_configure_row (IdeOmniBar    *self,
                   IdeOmniBarRow *row)
@@ -82,11 +228,13 @@ on_configure_row (IdeOmniBar    *self,
   id = ide_configuration_get_id (config);
 
   /*
-   * TODO: This can be removed once GtkPopover can activate actions
-   *       that are resolved via the GtkPopover:relative-to property,
-   *       or it gets a proper parent that is not the toplevel.
+   * TODO: This can be removed once GtkListBoxRow can activate actions
+   *       in the "activate" signal (using something like action-name).
    *
-   *       https://bugzilla.gnome.org/show_bug.cgi?id=768023
+   *       This code is basically a layer violation since build-tools
+   *       is provided by a plugin and we are simply activating it.
+   *       However, it's an internal plugin and should always be there,
+   *       so not the end of the world.
    */
 
   ide_widget_action (GTK_WIDGET (self),
@@ -164,9 +312,9 @@ ide_omni_bar_select_current_config (GtkWidget *widget,
 }
 
 static void
-ide_omni_bar_current_changed (IdeOmniBar              *self,
-                              GParamSpec              *pspec,
-                              IdeConfigurationManager *config_manager)
+ide_omni_bar__config_manager__notify_current (IdeOmniBar              *self,
+                                              GParamSpec              *pspec,
+                                              IdeConfigurationManager *config_manager)
 {
   IdeConfiguration *current;
 
@@ -225,6 +373,9 @@ ide_omni_bar_context_set (GtkWidget  *widget,
                           IdeContext *context)
 {
   IdeOmniBar *self = (IdeOmniBar *)widget;
+  IdeConfigurationManager *config_manager = NULL;
+  IdeBuildManager *build_manager = NULL;
+  IdeVcs *vcs = NULL;
 
   IDE_ENTRY;
 
@@ -235,51 +386,26 @@ ide_omni_bar_context_set (GtkWidget  *widget,
 
   if (context != NULL)
     {
-      IdeConfigurationManager *configs;
-      g_autofree gchar *path = NULL;
-      g_autoptr(GFile) home = NULL;
-      GFile *workdir;
-      IdeVcs *vcs;
-
-      configs = ide_context_get_configuration_manager (context);
       vcs = ide_context_get_vcs (context);
-      workdir = ide_vcs_get_working_directory (vcs);
-      home = g_file_new_for_path (g_get_home_dir ());
-
-      if (g_file_has_prefix (workdir, home))
-        path = g_file_get_relative_path (home, workdir);
-      else if (g_file_is_native (workdir))
-        path = g_file_get_path (workdir);
-      else
-        path = g_file_get_uri (workdir);
-
-      gtk_label_set_label (self->popover_project_label, path);
-
-      g_signal_connect_object (vcs,
-                               "changed",
-                               G_CALLBACK (ide_omni_bar_update),
-                               self,
-                               G_CONNECT_SWAPPED);
+      build_manager = ide_context_get_build_manager (context);
+      config_manager = ide_context_get_configuration_manager (context);
+    }
 
-      g_object_bind_property_full (configs, "current-display-name",
-                                   self->config_name_label, "label",
-                                   G_BINDING_SYNC_CREATE,
-                                   add_target_prefix_transform,
-                                   NULL, NULL, NULL);
+  egg_binding_group_set_source (self->build_manager_bindings, build_manager);
+  egg_signal_group_set_target (self->build_manager_signals, build_manager);
+  egg_binding_group_set_source (self->config_manager_bindings, config_manager);
+  egg_signal_group_set_target (self->config_manager_signals, config_manager);
+  egg_binding_group_set_source (self->vcs_bindings, vcs);
 
+  if (config_manager != NULL)
+    {
       gtk_list_box_bind_model (self->popover_configuration_list_box,
-                               G_LIST_MODEL (configs),
+                               G_LIST_MODEL (config_manager),
                                create_configuration_row,
                                self,
                                NULL);
 
-      g_signal_connect_object (configs,
-                               "notify::current",
-                               G_CALLBACK (ide_omni_bar_current_changed),
-                               self,
-                               G_CONNECT_SWAPPED);
-
-      ide_omni_bar_current_changed (self, NULL, configs);
+      ide_omni_bar__config_manager__notify_current (self, NULL, config_manager);
     }
 
   IDE_EXIT;
@@ -326,133 +452,12 @@ event_box_leave_notify (IdeOmniBar  *self,
 }
 
 static void
-ide_omni_bar_build_result_notify_mode (IdeOmniBar     *self,
-                                       GParamSpec     *pspec,
-                                       IdeBuildResult *result)
-{
-  g_autofree gchar *mode = NULL;
-
-  g_assert (IDE_IS_OMNI_BAR (self));
-  g_assert (pspec != NULL);
-  g_assert (IDE_IS_BUILD_RESULT (result));
-
-  mode = ide_build_result_get_mode (result);
-
-  gtk_label_set_label (self->build_result_mode_label, mode);
-
-  if (ide_build_result_get_running (result))
-    gtk_label_set_label (self->popover_build_mode_label, mode);
-  else
-    gtk_label_set_label (self->popover_build_mode_label, _("Last Build"));
-}
-
-static void
-ide_omni_bar_build_result_notify_failed (IdeOmniBar     *self,
-                                         GParamSpec     *pspec,
-                                         IdeBuildResult *result)
-{
-  gboolean failed;
-
-  g_assert (IDE_IS_OMNI_BAR (self));
-  g_assert (pspec != NULL);
-  g_assert (IDE_IS_BUILD_RESULT (result));
-
-  failed = ide_build_result_get_failed (result);
-
-  gtk_widget_set_visible (GTK_WIDGET (self->popover_failed_label), failed);
-}
-
-static void
-ide_omni_bar_build_result_notify_running_time (IdeOmniBar     *self,
-                                               GParamSpec     *pspec,
-                                               IdeBuildResult *result)
-{
-  g_autofree gchar *text = NULL;
-  GTimeSpan span;
-  guint hours;
-  guint minutes;
-  guint seconds;
-
-  g_assert (IDE_IS_OMNI_BAR (self));
-  g_assert (IDE_IS_BUILD_RESULT (result));
-
-  span = ide_build_result_get_running_time (result);
-
-  hours = span / G_TIME_SPAN_HOUR;
-  minutes = (span % G_TIME_SPAN_HOUR) / G_TIME_SPAN_MINUTE;
-  seconds = (span % G_TIME_SPAN_MINUTE) / G_TIME_SPAN_SECOND;
-
-  text = g_strdup_printf ("%02u:%02u:%02u", hours, minutes, seconds);
-  gtk_label_set_label (self->popover_build_running_time_label, text);
-}
-
-static void
-ide_omni_bar_build_result_notify_running (IdeOmniBar     *self,
-                                          GParamSpec     *pspec,
-                                          IdeBuildResult *result)
-{
-  g_assert (IDE_IS_OMNI_BAR (self));
-  g_assert (pspec != NULL);
-  g_assert (IDE_IS_BUILD_RESULT (result));
-
-  if (ide_build_result_get_running (result))
-    {
-      g_object_set (self->build_button_image,
-                    "icon-name", "process-stop-symbolic",
-                    NULL);
-      g_object_set (self->build_button,
-                    "action-name", "build-tools.cancel-build",
-                    NULL);
-
-      gtk_stack_set_visible_child (self->message_stack,
-                                   GTK_WIDGET (self->build_result_mode_label));
-
-      gtk_widget_hide (GTK_WIDGET (self->popover_last_build_time_label));
-
-      gtk_widget_show (GTK_WIDGET (self->popover_build_cancel_button));
-      gtk_widget_show (GTK_WIDGET (self->popover_build_mode_label));
-      gtk_widget_show (GTK_WIDGET (self->popover_build_running_time_label));
-    }
-  else
-    {
-      g_object_set (self->build_button_image,
-                    "icon-name", "system-run-symbolic",
-                    NULL);
-      g_object_set (self->build_button,
-                    "action-name", "build-tools.build",
-                    NULL);
-
-      gtk_label_set_label (self->popover_build_mode_label, _("Last Build"));
-
-      gtk_widget_hide (GTK_WIDGET (self->popover_build_cancel_button));
-      gtk_widget_hide (GTK_WIDGET (self->popover_build_running_time_label));
-
-      gtk_widget_show (GTK_WIDGET (self->popover_build_mode_label));
-      gtk_widget_show (GTK_WIDGET (self->popover_last_build_time_label));
-    }
-}
-
-static void
-ide_omni_bar_build_result_diagnostic (IdeOmniBar     *self,
-                                      IdeDiagnostic  *diagnostic,
-                                      IdeBuildResult *result)
-{
-  g_assert (IDE_IS_OMNI_BAR (self));
-  g_assert (diagnostic != NULL);
-  g_assert (IDE_IS_BUILD_RESULT (result));
-
-  gtk_widget_show (GTK_WIDGET (self->build_result_diagnostics_image));
-}
-
-static void
 ide_omni_bar_next_message (IdeOmniBar *self)
 {
-  IdeBuildResult *build_result;
   const gchar *name;
 
   g_assert (IDE_IS_OMNI_BAR (self));
 
-  build_result = ide_omni_bar_get_build_result (self);
   name = gtk_stack_get_visible_child_name (self->message_stack);
 
   /*
@@ -467,10 +472,10 @@ ide_omni_bar_next_message (IdeOmniBar *self)
       /* Only rotate to build result if we have one and we haven't
        * flapped too many times.
        */
-      if (build_result != NULL && self->seen_count < 2)
+      if (self->build_result != NULL && self->seen_count < 2)
         gtk_stack_set_visible_child_name (self->message_stack, "build");
     }
-  else if (!ide_build_result_get_running (build_result))
+  else if (!ide_build_result_get_running (self->build_result))
     {
       self->seen_count++;
       gtk_stack_set_visible_child_name (self->message_stack, "config");
@@ -545,11 +550,67 @@ ide_omni_bar_popover_closed (IdeOmniBar *self,
 }
 
 static void
+ide_omni_bar__build_manager__build_started (IdeOmniBar      *self,
+                                            IdeBuildResult  *build_result,
+                                            IdeBuildManager *build_manager)
+{
+  g_assert (IDE_IS_OMNI_BAR (self));
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+  g_assert (IDE_IS_BUILD_MANAGER (build_manager));
+
+  gtk_widget_hide (GTK_WIDGET (self->popover_failed_label));
+  gtk_widget_show (GTK_WIDGET (self->popover_build_cancel_button));
+
+  g_set_object (&self->build_result, build_result);
+
+  self->seen_count = 0;
+
+  gtk_stack_set_visible_child_name (self->popover_time_stack, "current-build");
+
+  gtk_revealer_set_reveal_child (self->popover_details_revealer, TRUE);
+}
+
+static void
+ide_omni_bar__build_manager__build_failed (IdeOmniBar      *self,
+                                           IdeBuildResult  *build_result,
+                                           IdeBuildManager *build_manager)
+{
+  g_assert (IDE_IS_OMNI_BAR (self));
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+  g_assert (IDE_IS_BUILD_MANAGER (build_manager));
+
+  gtk_widget_set_visible (GTK_WIDGET (self->popover_failed_label), TRUE);
+
+  gtk_stack_set_visible_child_name (self->popover_time_stack, "last-build");
+
+  gtk_widget_hide (GTK_WIDGET (self->popover_build_cancel_button));
+}
+
+static void
+ide_omni_bar__build_manager__build_finished (IdeOmniBar      *self,
+                                             IdeBuildResult  *build_result,
+                                             IdeBuildManager *build_manager)
+{
+  g_assert (IDE_IS_OMNI_BAR (self));
+  g_assert (IDE_IS_BUILD_RESULT (build_result));
+  g_assert (IDE_IS_BUILD_MANAGER (build_manager));
+
+  gtk_widget_hide (GTK_WIDGET (self->popover_build_cancel_button));
+
+  gtk_stack_set_visible_child_name (self->popover_time_stack, "last-build");
+}
+
+static void
 ide_omni_bar_finalize (GObject *object)
 {
   IdeOmniBar *self = (IdeOmniBar *)object;
 
-  g_clear_object (&self->build_result_signals);
+  g_clear_object (&self->build_result);
+  g_clear_object (&self->build_manager_bindings);
+  g_clear_object (&self->build_manager_signals);
+  g_clear_object (&self->config_manager_bindings);
+  g_clear_object (&self->config_manager_signals);
+  g_clear_object (&self->vcs_bindings);
 
   G_OBJECT_CLASS (ide_omni_bar_parent_class)->finalize (object);
 }
@@ -582,9 +643,9 @@ ide_omni_bar_class_init (IdeOmniBarClass *klass)
   gtk_widget_class_set_css_name (widget_class, "omnibar");
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, branch_label);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, build_button);
-  gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, build_button_image);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, build_result_diagnostics_image);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, build_result_mode_label);
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, cancel_button);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, config_name_label);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, event_box);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, message_stack);
@@ -594,9 +655,11 @@ ide_omni_bar_class_init (IdeOmniBarClass *klass)
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, popover_build_mode_label);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, popover_build_running_time_label);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, popover_configuration_list_box);
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, popover_details_revealer);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, popover_failed_label);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, popover_last_build_time_label);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, popover_project_label);
+  gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, popover_time_stack);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, popover_view_output_button);
   gtk_widget_class_bind_template_child (widget_class, IdeOmniBar, project_label);
 }
@@ -606,6 +669,138 @@ ide_omni_bar_init (IdeOmniBar *self)
 {
   gtk_widget_init_template (GTK_WIDGET (self));
 
+  /*
+   * IdeBuildManager bindings and signals.
+   */
+
+  self->build_manager_bindings = egg_binding_group_new ();
+
+  egg_binding_group_bind (self->build_manager_bindings,
+                          "busy",
+                          self->cancel_button,
+                          "visible",
+                          G_BINDING_SYNC_CREATE);
+
+  egg_binding_group_bind (self->build_manager_bindings,
+                          "busy",
+                          self->build_button,
+                          "visible",
+                          G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+  egg_binding_group_bind (self->build_manager_bindings,
+                          "has-diagnostics",
+                          self->build_result_diagnostics_image,
+                          "visible",
+                          G_BINDING_SYNC_CREATE);
+
+  egg_binding_group_bind_full (self->build_manager_bindings,
+                               "last-build-time",
+                               self->popover_last_build_time_label,
+                               "label",
+                               G_BINDING_SYNC_CREATE,
+                               date_time_to_label,
+                               NULL,
+                               NULL,
+                               NULL);
+
+  egg_binding_group_bind (self->build_manager_bindings,
+                          "message",
+                          self->build_result_mode_label,
+                          "label",
+                          G_BINDING_SYNC_CREATE);
+
+  egg_binding_group_bind (self->build_manager_bindings,
+                          "message",
+                          self->popover_build_mode_label,
+                          "label",
+                          G_BINDING_SYNC_CREATE);
+
+  egg_binding_group_bind_full (self->build_manager_bindings,
+                               "running-time",
+                               self->popover_build_running_time_label,
+                               "label",
+                               G_BINDING_SYNC_CREATE,
+                               time_span_to_label,
+                               NULL,
+                               NULL,
+                               NULL);
+
+  self->build_manager_signals = egg_signal_group_new (IDE_TYPE_BUILD_MANAGER);
+
+  egg_signal_group_connect_object (self->build_manager_signals,
+                                   "build-started",
+                                   G_CALLBACK (ide_omni_bar__build_manager__build_started),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  egg_signal_group_connect_object (self->build_manager_signals,
+                                   "build-failed",
+                                   G_CALLBACK (ide_omni_bar__build_manager__build_failed),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  egg_signal_group_connect_object (self->build_manager_signals,
+                                   "build-finished",
+                                   G_CALLBACK (ide_omni_bar__build_manager__build_finished),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  /*
+   * IdeVcs bindings and signals.
+   */
+
+  self->vcs_bindings = egg_binding_group_new ();
+
+  egg_binding_group_bind (self->vcs_bindings,
+                          "branch-name",
+                          self->popover_branch_label,
+                          "label",
+                          G_BINDING_SYNC_CREATE);
+
+  egg_binding_group_bind_full (self->vcs_bindings,
+                               "working-directory",
+                               self->popover_project_label,
+                               "label",
+                               G_BINDING_SYNC_CREATE,
+                               file_to_relative_path,
+                               NULL,
+                               NULL,
+                               NULL);
+
+  /*
+   * IdeConfigurationManager bindings and signals.
+   */
+
+  self->config_manager_bindings = egg_binding_group_new ();
+
+  egg_binding_group_bind_full (self->config_manager_bindings,
+                               "current-display-name",
+                               self->config_name_label,
+                               "label",
+                               G_BINDING_SYNC_CREATE,
+                               add_target_prefix_transform,
+                               NULL,
+                               NULL,
+                               NULL);
+
+  self->config_manager_signals = egg_signal_group_new (IDE_TYPE_CONFIGURATION_MANAGER);
+
+  egg_signal_group_connect_object (self->config_manager_signals,
+                                   "notify::current",
+                                   G_CALLBACK (ide_omni_bar__config_manager__notify_current),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->popover_configuration_list_box,
+                           "row-activated",
+                           G_CALLBACK (ide_omni_bar_row_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  /*
+   * Enable various events for state tracking.
+   */
+
   gtk_widget_add_events (GTK_WIDGET (self->event_box), GDK_BUTTON_PRESS_MASK);
 
   g_signal_connect_object (self->event_box,
@@ -613,6 +808,7 @@ ide_omni_bar_init (IdeOmniBar *self)
                            G_CALLBACK (event_box_enter_notify),
                            self,
                            G_CONNECT_SWAPPED);
+
   g_signal_connect_object (self->event_box,
                            "leave-notify-event",
                            G_CALLBACK (event_box_leave_notify),
@@ -628,40 +824,9 @@ ide_omni_bar_init (IdeOmniBar *self)
   self->gesture = gtk_gesture_multi_press_new (GTK_WIDGET (self->event_box));
   g_signal_connect (self->gesture, "pressed", G_CALLBACK (multipress_pressed_cb), self);
 
-  self->build_result_signals = egg_signal_group_new (IDE_TYPE_BUILD_RESULT);
-
-  egg_signal_group_connect_object (self->build_result_signals,
-                                   "notify::failed",
-                                   G_CALLBACK (ide_omni_bar_build_result_notify_failed),
-                                   self,
-                                   G_CONNECT_SWAPPED);
-  egg_signal_group_connect_object (self->build_result_signals,
-                                   "notify::mode",
-                                   G_CALLBACK (ide_omni_bar_build_result_notify_mode),
-                                   self,
-                                   G_CONNECT_SWAPPED);
-  egg_signal_group_connect_object (self->build_result_signals,
-                                   "notify::running",
-                                   G_CALLBACK (ide_omni_bar_build_result_notify_running),
-                                   self,
-                                   G_CONNECT_SWAPPED);
-  egg_signal_group_connect_object (self->build_result_signals,
-                                   "notify::running-time",
-                                   G_CALLBACK (ide_omni_bar_build_result_notify_running_time),
-                                   self,
-                                   G_CONNECT_SWAPPED);
-  egg_signal_group_connect_object (self->build_result_signals,
-                                   "diagnostic",
-                                   G_CALLBACK (ide_omni_bar_build_result_diagnostic),
-                                   self,
-                                   G_CONNECT_SWAPPED);
-
-  g_signal_connect_object (self->popover_configuration_list_box,
-                           "row-activated",
-                           G_CALLBACK (ide_omni_bar_row_activated),
-                           self,
-                           G_CONNECT_SWAPPED);
-
+  /*
+   * Register to be notified of IdeWorkbench:context set.
+   */
   ide_widget_set_context_handler (self, ide_omni_bar_context_set);
 }
 
@@ -670,47 +835,3 @@ ide_omni_bar_new (void)
 {
   return g_object_new (IDE_TYPE_OMNI_BAR, NULL);
 }
-
-/**
- * ide_omni_bar_get_build_result:
- *
- * Gets the current build result that is being visualized in the omni bar.
- *
- * Returns: (nullable) (transfer none): An #IdeBuildResult or %NULL.
- */
-IdeBuildResult *
-ide_omni_bar_get_build_result (IdeOmniBar *self)
-{
-  g_return_val_if_fail (IDE_IS_OMNI_BAR (self), NULL);
-
-  return egg_signal_group_get_target (self->build_result_signals);
-}
-
-void
-ide_omni_bar_set_build_result (IdeOmniBar     *self,
-                               IdeBuildResult *build_result)
-{
-  g_autoptr(GDateTime) now = NULL;
-  g_autofree gchar *nowstr = NULL;
-  gboolean failed = FALSE;
-
-  g_return_if_fail (IDE_IS_OMNI_BAR (self));
-  g_return_if_fail (!build_result || IDE_IS_BUILD_RESULT (build_result));
-
-  gtk_widget_hide (GTK_WIDGET (self->build_result_diagnostics_image));
-  egg_signal_group_set_target (self->build_result_signals, build_result);
-
-  self->seen_count = 0;
-
-  gtk_stack_set_visible_child_name (self->message_stack, "build");
-
-  now = g_date_time_new_now_local ();
-  nowstr = g_date_time_format (now, "%a %B %e, %X");
-  gtk_label_set_label (self->popover_last_build_time_label, nowstr);
-
-  gtk_widget_show (GTK_WIDGET (self->popover_view_output_button));
-
-  if (build_result)
-    failed = ide_build_result_get_failed (build_result);
-  gtk_widget_set_visible (GTK_WIDGET (self->popover_failed_label), failed);
-}
diff --git a/libide/workbench/ide-omni-bar.h b/libide/workbench/ide-omni-bar.h
index 956a37c..be63489 100644
--- a/libide/workbench/ide-omni-bar.h
+++ b/libide/workbench/ide-omni-bar.h
@@ -29,10 +29,7 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeOmniBar, ide_omni_bar, IDE, OMNI_BAR, GtkBox)
 
-GtkWidget      *ide_omni_bar_new              (void);
-IdeBuildResult *ide_omni_bar_get_build_result (IdeOmniBar     *self);
-void            ide_omni_bar_set_build_result (IdeOmniBar     *self,
-                                               IdeBuildResult *build_result);
+GtkWidget *ide_omni_bar_new (void);
 
 G_END_DECLS
 
diff --git a/libide/workbench/ide-omni-bar.ui b/libide/workbench/ide-omni-bar.ui
index dea94cb..280db99 100644
--- a/libide/workbench/ide-omni-bar.ui
+++ b/libide/workbench/ide-omni-bar.ui
@@ -92,14 +92,11 @@
     </child>
     <child>
       <object class="GtkButton" id="build_button">
-        <property name="action-name">build-tools.build</property>
+        <property name="action-name">build-manager.build</property>
         <property name="focus-on-click">false</property>
         <property name="visible">true</property>
-        <style>
-          <class name="linked"/>
-        </style>
         <child>
-          <object class="GtkImage" id="build_button_image">
+          <object class="GtkImage">
             <property name="icon-name">system-run-symbolic</property>
             <property name="tooltip-text" translatable="yes">Build project (Ctrl+F7)</property>
             <property name="visible">true</property>
@@ -111,6 +108,24 @@
         <property name="position">0</property>
       </packing>
     </child>
+    <child>
+      <object class="GtkButton" id="cancel_button">
+        <property name="action-name">build-manager.cancel</property>
+        <property name="focus-on-click">false</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkImage">
+            <property name="icon-name">process-stop-symbolic</property>
+            <property name="tooltip-text" translatable="yes">Cancel build</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="pack-type">end</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
   </template>
   <object class="GtkSizeGroup">
     <property name="mode">GTK_SIZE_GROUP_VERTICAL</property>
@@ -229,83 +244,116 @@
               </object>
             </child>
             <child>
-              <object class="GtkBox">
-                <property name="spacing">12</property>
+              <object class="GtkRevealer" id="popover_details_revealer">
                 <property name="visible">true</property>
+                <property name="reveal-child">false</property>
                 <child>
                   <object class="GtkBox">
-                    <property name="orientation">vertical</property>
-                    <property name="valign">center</property>
+                    <property name="spacing">12</property>
                     <property name="visible">true</property>
                     <child>
-                      <object class="GtkLabel" id="popover_build_mode_label">
-                        <property name="xalign">0.0</property>
+                      <object class="GtkStack" id="popover_time_stack">
+                        <property name="interpolate-size">true</property>
+                        <property name="visible">true</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="orientation">vertical</property>
+                            <property name="valign">center</property>
+                            <property name="visible">true</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">true</property>
+                                <property name="label" translatable="yes">Last build</property>
+                                <property name="xalign">0.0</property>
+                                <attributes>
+                                  <attribute name="weight" value="bold"/>
+                                </attributes>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="popover_last_build_time_label">
+                                <property name="xalign">0.0</property>
+                                <property name="visible">true</property>
+                                <attributes>
+                                  <attribute name="scale" value="0.8333"/>
+                                </attributes>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="name">last-build</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="orientation">horizontal</property>
+                            <property name="hexpand">true</property>
+                            <property name="visible">true</property>
+                            <child>
+                              <object class="GtkLabel" id="popover_build_mode_label">
+                                <property name="visible">true</property>
+                                <property name="xalign">0.0</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkLabel" id="popover_build_running_time_label">
+                                <property name="xalign">1.0</property>
+                                <property name="margin-end">8</property>
+                                <property name="margin-start">8</property>
+                                <property name="hexpand">true</property>
+                                <property name="visible">true</property>
+                              </object>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="name">current-build</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="popover_failed_label">
+                        <property name="label" translatable="yes">Failed</property>
+                        <property name="xalign">1.0</property>
+                        <property name="margin-end">8</property>
+                        <property name="margin-start">8</property>
                         <attributes>
                           <attribute name="weight" value="bold"/>
+                          <attribute name="foreground" value="red"/>
                         </attributes>
                       </object>
+                      <packing>
+                        <property name="position">2</property>
+                        <property name="pack-type">end</property>
+                      </packing>
                     </child>
                     <child>
-                      <object class="GtkLabel" id="popover_last_build_time_label">
-                        <property name="xalign">0.0</property>
-                        <attributes>
-                          <attribute name="scale" value="0.8333"/>
-                        </attributes>
+                      <object class="GtkButton" id="popover_view_output_button">
+                        <property name="action-name">build-tools.view-output</property>
+                        <property name="label" translatable="yes">View Output</property>
+                        <property name="visible">true</property>
                       </object>
+                      <packing>
+                        <property name="position">1</property>
+                        <property name="pack-type">end</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="popover_build_cancel_button">
+                        <property name="action-name">build-manager.cancel</property>
+                        <property name="label" translatable="yes">_Cancel</property>
+                        <property name="use-underline">true</property>
+                        <style>
+                          <class name="destructive-action"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="position">0</property>
+                        <property name="pack-type">end</property>
+                      </packing>
                     </child>
                   </object>
                 </child>
-                <child>
-                  <object class="GtkLabel" id="popover_build_running_time_label">
-                    <property name="xalign">1.0</property>
-                    <property name="margin-end">8</property>
-                    <property name="margin-start">8</property>
-                  </object>
-                  <packing>
-                    <property name="position">1</property>
-                    <property name="pack-type">start</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="popover_failed_label">
-                    <property name="label" translatable="yes">Failed</property>
-                    <property name="xalign">1.0</property>
-                    <property name="margin-end">8</property>
-                    <property name="margin-start">8</property>
-                    <attributes>
-                      <attribute name="weight" value="bold"/>
-                      <attribute name="foreground" value="red"/>
-                    </attributes>
-                  </object>
-                  <packing>
-                    <property name="position">2</property>
-                    <property name="pack-type">end</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="popover_view_output_button">
-                    <property name="action-name">build-tools.view-output</property>
-                    <property name="label" translatable="yes">View Output</property>
-                  </object>
-                  <packing>
-                    <property name="position">1</property>
-                    <property name="pack-type">end</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkButton" id="popover_build_cancel_button">
-                    <property name="action-name">build-tools.cancel-build</property>
-                    <property name="label" translatable="yes">_Cancel</property>
-                    <property name="use-underline">true</property>
-                    <style>
-                      <class name="destructive-action"/>
-                    </style>
-                  </object>
-                  <packing>
-                    <property name="position">0</property>
-                    <property name="pack-type">end</property>
-                  </packing>
-                </child>
               </object>
             </child>
           </object>
@@ -318,7 +366,7 @@
             </style>
             <child>
               <object class="GtkButton">
-                <property name="action-name">build-tools.build</property>
+                <property name="action-name">build-manager.build</property>
                 <property name="label" translatable="yes">Build</property>
                 <property name="visible">true</property>
               </object>
@@ -328,7 +376,7 @@
             </child>
             <child>
               <object class="GtkButton">
-                <property name="action-name">build-tools.rebuild</property>
+                <property name="action-name">build-manager.rebuild</property>
                 <property name="label" translatable="yes">Rebuild</property>
                 <property name="visible">true</property>
               </object>
@@ -338,7 +386,7 @@
             </child>
             <child>
               <object class="GtkButton">
-                <property name="action-name">build-tools.clean</property>
+                <property name="action-name">build-manager.clean</property>
                 <property name="label" translatable="yes">Clean</property>
                 <property name="visible">true</property>
               </object>
diff --git a/libide/workbench/ide-workbench-header-bar.ui b/libide/workbench/ide-workbench-header-bar.ui
index 4d947d7..87ac0eb 100644
--- a/libide/workbench/ide-workbench-header-bar.ui
+++ b/libide/workbench/ide-workbench-header-bar.ui
@@ -46,7 +46,7 @@
         </child>
         <child>
           <object class="IdeOmniSearchEntry" id="search_entry">
-            <property name="max-width-chars">25</property>
+            <property name="max-width-chars">30</property>
             <property name="placeholder-text" translatable="yes">Press Ctrl+. to search</property>
             <property name="visible">true</property>
           </object>
diff --git a/libide/workbench/ide-workbench.c b/libide/workbench/ide-workbench.c
index 51f90c4..3b1419e 100644
--- a/libide/workbench/ide-workbench.c
+++ b/libide/workbench/ide-workbench.c
@@ -567,6 +567,7 @@ ide_workbench_set_context (IdeWorkbench *self,
                            IdeContext   *context)
 {
   g_autoptr(GSettings) settings = NULL;
+  IdeBuildManager *build_manager;
   IdeProject *project;
   guint delay_msec;
 
@@ -586,6 +587,11 @@ ide_workbench_set_context (IdeWorkbench *self,
                                G_BINDING_SYNC_CREATE,
                                transform_title, NULL, NULL, NULL);
 
+  build_manager = ide_context_get_build_manager (context);
+  gtk_widget_insert_action_group (GTK_WIDGET (self),
+                                  "build-manager",
+                                  G_ACTION_GROUP (build_manager));
+
   self->addins = peas_extension_set_new (peas_engine_get_default (),
                                          IDE_TYPE_WORKBENCH_ADDIN,
                                          NULL);
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/Makefile.am b/plugins/autotools/Makefile.am
index 195a390..24481ad 100644
--- a/plugins/autotools/Makefile.am
+++ b/plugins/autotools/Makefile.am
@@ -10,6 +10,8 @@ libautotools_plugin_la_SOURCES = \
        ide-autotools-builder.h \
        ide-autotools-build-system.c \
        ide-autotools-build-system.h \
+       ide-autotools-build-target.c \
+       ide-autotools-build-target.h \
        ide-autotools-build-task.c \
        ide-autotools-build-task.h \
        ide-autotools-project-miner.c \
diff --git a/plugins/autotools/ide-autotools-build-system.c b/plugins/autotools/ide-autotools-build-system.c
index 12c9f97..9eb1807 100644
--- a/plugins/autotools/ide-autotools-build-system.c
+++ b/plugins/autotools/ide-autotools-build-system.c
@@ -546,6 +546,94 @@ ide_autotools_build_system_constructed (GObject *object)
                            G_CONNECT_SWAPPED);
 }
 
+static void
+ide_autotools_build_system_get_build_targets_cb2 (GObject      *object,
+                                                  GAsyncResult *result,
+                                                  gpointer      user_data)
+{
+  IdeMakecache *makecache = (IdeMakecache *)object;
+  g_autoptr(GTask) task = user_data;
+  GPtrArray *ret;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_MAKECACHE (makecache));
+  g_assert (G_IS_TASK (task));
+
+  ret = ide_makecache_get_build_targets_finish (makecache, result, &error);
+
+  if (ret == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_return_pointer (task, ret, (GDestroyNotify)g_ptr_array_unref);
+}
+
+static void
+ide_autotools_build_system_get_build_targets_cb (GObject      *object,
+                                                 GAsyncResult *result,
+                                                 gpointer      user_data)
+{
+  IdeAutotoolsBuildSystem *self = (IdeAutotoolsBuildSystem *)object;
+  g_autoptr(IdeMakecache) makecache = NULL;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (self));
+  g_assert (G_IS_TASK (task));
+
+  makecache = ide_autotools_build_system_get_makecache_finish (self, result, &error);
+
+  if (makecache == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_makecache_get_build_targets_async (makecache,
+                                         g_task_get_cancellable (task),
+                                         ide_autotools_build_system_get_build_targets_cb2,
+                                         g_object_ref (task));
+}
+
+static void
+ide_autotools_build_system_get_build_targets_async (IdeBuildSystem      *build_system,
+                                                    GCancellable        *cancellable,
+                                                    GAsyncReadyCallback  callback,
+                                                    gpointer             user_data)
+{
+  IdeAutotoolsBuildSystem *self = (IdeAutotoolsBuildSystem *)build_system;
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_autotools_build_system_get_build_targets_async);
+
+  ide_autotools_build_system_get_makecache_async (self,
+                                                  cancellable,
+                                                  ide_autotools_build_system_get_build_targets_cb,
+                                                  g_object_ref (task));
+}
+
+static GPtrArray *
+ide_autotools_build_system_get_build_targets_finish (IdeBuildSystem  *build_system,
+                                                     GAsyncResult    *result,
+                                                     GError         **error)
+{
+  IdeAutotoolsBuildSystem *self = (IdeAutotoolsBuildSystem *)build_system;
+  GTask *task = (GTask *)result;
+
+  g_assert (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (self));
+  g_assert (G_IS_TASK (task));
+  g_assert (g_task_is_valid (task, self));
+  g_assert (g_task_get_source_tag (task) == ide_autotools_build_system_get_build_targets_async);
+
+  return g_task_propagate_pointer (task, error);
+}
+
 static gint
 ide_autotools_build_system_get_priority (IdeBuildSystem *system)
 {
@@ -614,6 +702,8 @@ build_system_iface_init (IdeBuildSystemInterface *iface)
   iface->get_builder = ide_autotools_build_system_get_builder;
   iface->get_build_flags_async = ide_autotools_build_system_get_build_flags_async;
   iface->get_build_flags_finish = ide_autotools_build_system_get_build_flags_finish;
+  iface->get_build_targets_async = ide_autotools_build_system_get_build_targets_async;
+  iface->get_build_targets_finish = ide_autotools_build_system_get_build_targets_finish;
 }
 
 static void
diff --git a/plugins/autotools/ide-autotools-build-target.c b/plugins/autotools/ide-autotools-build-target.c
new file mode 100644
index 0000000..faa3a7d
--- /dev/null
+++ b/plugins/autotools/ide-autotools-build-target.c
@@ -0,0 +1,166 @@
+/* ide-autotools-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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-autotools-build-target"
+
+#include "ide-autotools-build-target.h"
+
+struct _IdeAutotoolsBuildTarget
+{
+  IdeObject parent_instance;
+
+  GFile *build_directory;
+  GFile *install_directory;
+  gchar *name;
+};
+
+enum {
+  PROP_0,
+  PROP_BUILD_DIRECTORY,
+  PROP_INSTALL_DIRECTORY,
+  PROP_NAME,
+  N_PROPS
+};
+
+static void build_target_iface_init (IdeBuildTargetInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeAutotoolsBuildTarget, ide_autotools_build_target, IDE_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_BUILD_TARGET, build_target_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_autotools_build_target_finalize (GObject *object)
+{
+  IdeAutotoolsBuildTarget *self = (IdeAutotoolsBuildTarget *)object;
+
+  g_clear_object (&self->build_directory);
+  g_clear_object (&self->install_directory);
+  g_clear_pointer (&self->name, g_free);
+
+  G_OBJECT_CLASS (ide_autotools_build_target_parent_class)->finalize (object);
+}
+
+static void
+ide_autotools_build_target_get_property (GObject    *object,
+                                         guint       prop_id,
+                                         GValue     *value,
+                                         GParamSpec *pspec)
+{
+  IdeAutotoolsBuildTarget *self = IDE_AUTOTOOLS_BUILD_TARGET (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUILD_DIRECTORY:
+      g_value_set_object (value, self->build_directory);
+      break;
+
+    case PROP_INSTALL_DIRECTORY:
+      g_value_set_object (value, self->install_directory);
+      break;
+
+    case PROP_NAME:
+      g_value_set_string (value, self->name);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_autotools_build_target_set_property (GObject      *object,
+                                         guint         prop_id,
+                                         const GValue *value,
+                                         GParamSpec   *pspec)
+{
+  IdeAutotoolsBuildTarget *self = IDE_AUTOTOOLS_BUILD_TARGET (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUILD_DIRECTORY:
+      self->build_directory = g_value_dup_object (value);
+      break;
+
+    case PROP_INSTALL_DIRECTORY:
+      self->install_directory = g_value_dup_object (value);
+      break;
+
+    case PROP_NAME:
+      self->name = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_autotools_build_target_class_init (IdeAutotoolsBuildTargetClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_autotools_build_target_finalize;
+  object_class->get_property = ide_autotools_build_target_get_property;
+  object_class->set_property = ide_autotools_build_target_set_property;
+
+  properties [PROP_BUILD_DIRECTORY] =
+    g_param_spec_object ("build-directory",
+                         NULL,
+                         NULL,
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_INSTALL_DIRECTORY] =
+    g_param_spec_object ("install-directory",
+                         NULL,
+                         NULL,
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         NULL,
+                         NULL,
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_autotools_build_target_init (IdeAutotoolsBuildTarget *self)
+{
+}
+
+static GFile *
+ide_autotools_build_target_get_install_directory (IdeBuildTarget *target)
+{
+  IdeAutotoolsBuildTarget *self = (IdeAutotoolsBuildTarget *)target;
+
+  if (self->install_directory != NULL)
+    return g_object_ref (self->install_directory);
+
+  return NULL;
+}
+
+static void
+build_target_iface_init (IdeBuildTargetInterface *iface)
+{
+  iface->get_install_directory = ide_autotools_build_target_get_install_directory;
+}
diff --git a/plugins/autotools/ide-autotools-build-target.h b/plugins/autotools/ide-autotools-build-target.h
new file mode 100644
index 0000000..986e17a
--- /dev/null
+++ b/plugins/autotools/ide-autotools-build-target.h
@@ -0,0 +1,32 @@
+/* ide-autotools-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_AUTOTOOLS_BUILD_TARGET_H
+#define IDE_AUTOTOOLS_BUILD_TARGET_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_AUTOTOOLS_BUILD_TARGET (ide_autotools_build_target_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeAutotoolsBuildTarget, ide_autotools_build_target, IDE, AUTOTOOLS_BUILD_TARGET, 
IdeObject)
+
+G_END_DECLS
+
+#endif /* IDE_AUTOTOOLS_BUILD_TARGET_H */
diff --git a/plugins/autotools/ide-autotools-build-task.c b/plugins/autotools/ide-autotools-build-task.c
index 062700b..f63634d 100644
--- a/plugins/autotools/ide-autotools-build-task.c
+++ b/plugins/autotools/ide-autotools-build-task.c
@@ -217,6 +217,7 @@ ide_autotools_build_task_finalize (GObject *object)
 
   g_clear_object (&self->directory);
   g_clear_object (&self->configuration);
+  g_clear_pointer (&self->extra_targets, g_ptr_array_unref);
 
   G_OBJECT_CLASS (ide_autotools_build_task_parent_class)->finalize (object);
 }
@@ -394,8 +395,9 @@ gen_configure_argv (IdeAutotoolsBuildTask *self,
 }
 
 static WorkerState *
-worker_state_new (IdeAutotoolsBuildTask *self,
-                  IdeBuilderBuildFlags   flags)
+worker_state_new (IdeAutotoolsBuildTask  *self,
+                  IdeBuilderBuildFlags    flags,
+                  GError                **error)
 {
   g_autofree gchar *name = NULL;
   IdeContext *context;
@@ -416,6 +418,28 @@ worker_state_new (IdeAutotoolsBuildTask *self,
   device = ide_configuration_get_device (self->configuration);
   runtime = ide_configuration_get_runtime (self->configuration);
 
+  if (device == NULL)
+    {
+      g_set_error (error,
+                   IDE_DEVICE_ERROR,
+                   IDE_DEVICE_ERROR_NO_SUCH_DEVICE,
+                   "%s “%s”",
+                   _("Failed to locate device"),
+                   ide_configuration_get_device_id (self->configuration));
+      return NULL;
+    }
+
+  if (runtime == NULL)
+    {
+      g_set_error (error,
+                   IDE_RUNTIME_ERROR,
+                   IDE_RUNTIME_ERROR_NO_SUCH_RUNTIME,
+                   "%s “%s”",
+                   _("Failed to locate runtime"),
+                   ide_configuration_get_runtime_id (self->configuration));
+      return NULL;
+    }
+
   name = g_file_get_basename (project_file);
 
   if (g_str_has_prefix (name, "configure."))
@@ -456,6 +480,16 @@ worker_state_new (IdeAutotoolsBuildTask *self,
   if (FLAG_UNSET (flags, IDE_BUILDER_BUILD_FLAGS_NO_BUILD))
     g_ptr_array_add (make_targets, g_strdup ("all"));
 
+  if (self->extra_targets != NULL)
+    {
+      for (guint i = 0; i < self->extra_targets->len; i++)
+        {
+          const gchar *target = g_ptr_array_index (self->extra_targets, i);
+
+          g_ptr_array_add (make_targets, g_strdup (target));
+        }
+    }
+
   g_ptr_array_add (make_targets, NULL);
 
   state->make_targets = (gchar **)g_ptr_array_free (make_targets, FALSE);
@@ -543,25 +577,34 @@ ide_autotools_build_task_execute_async (IdeAutotoolsBuildTask *self,
 {
   g_autoptr(GTask) task = NULL;
   WorkerState *state;
+  GError *error = NULL;
 
   g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (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_autotools_build_task_execute_async);
+
   if (self->executed)
     {
-      g_task_report_new_error (self, callback, user_data,
-                               ide_autotools_build_task_execute_async,
+      g_task_return_new_error (task,
                                G_IO_ERROR,
                                G_IO_ERROR_FAILED,
-                               _("Cannot execute build task more than once."));
+                               "%s",
+                               _("Cannot execute build task more than once"));
       return;
     }
 
   self->executed = TRUE;
 
-  state = worker_state_new (self, flags);
+  state = worker_state_new (self, flags, &error);
+
+  if (state == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
 
-  task = g_task_new (self, cancellable, callback, user_data);
   g_task_set_task_data (task, state, worker_state_free);
 
   /*
@@ -598,6 +641,8 @@ ide_autotools_build_task_execute_finish (IdeAutotoolsBuildTask  *self,
   if (ret == FALSE)
     ide_build_result_set_failed (IDE_BUILD_RESULT (self), TRUE);
 
+  ide_build_result_set_running (IDE_BUILD_RESULT (self), FALSE);
+
   return ret;
 }
 
@@ -952,3 +997,16 @@ apply_environment (IdeAutotoolsBuildTask *self,
   environment = ide_configuration_get_environment (self->configuration);
   ide_subprocess_launcher_overlay_environment (launcher, environment);
 }
+
+void
+ide_autotools_build_task_add_target (IdeAutotoolsBuildTask *self,
+                                     const gchar           *target)
+{
+  g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
+  g_return_if_fail (target != NULL);
+
+  if (self->extra_targets == NULL)
+    self->extra_targets = g_ptr_array_new_with_free_func (g_free);
+
+  g_ptr_array_add (self->extra_targets, g_strdup (target));
+}
diff --git a/plugins/autotools/ide-autotools-build-task.h b/plugins/autotools/ide-autotools-build-task.h
index f9ac97d..3bc8a33 100644
--- a/plugins/autotools/ide-autotools-build-task.h
+++ b/plugins/autotools/ide-autotools-build-task.h
@@ -29,7 +29,8 @@ G_BEGIN_DECLS
 G_DECLARE_FINAL_TYPE (IdeAutotoolsBuildTask, ide_autotools_build_task, IDE, AUTOTOOLS_BUILD_TASK, 
IdeBuildResult)
 
 GFile    *ide_autotools_build_task_get_directory  (IdeAutotoolsBuildTask  *self);
-void      ide_autotools_build_task_add_target     (IdeAutotoolsBuildTask  *self);
+void      ide_autotools_build_task_add_target     (IdeAutotoolsBuildTask  *self,
+                                                   const gchar            *target);
 void      ide_autotools_build_task_execute_async  (IdeAutotoolsBuildTask  *self,
                                                    IdeBuilderBuildFlags    flags,
                                                    GCancellable           *cancellable,
diff --git a/plugins/autotools/ide-autotools-builder.c b/plugins/autotools/ide-autotools-builder.c
index 2d86554..b6d0a41 100644
--- a/plugins/autotools/ide-autotools-builder.c
+++ b/plugins/autotools/ide-autotools-builder.c
@@ -41,8 +41,6 @@ ide_autotools_builder_build_cb (GObject      *object,
   g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (build_result));
   g_return_if_fail (G_IS_TASK (task));
 
-  ide_build_result_set_running (IDE_BUILD_RESULT (build_result), FALSE);
-
   if (!ide_autotools_build_task_execute_finish (build_result, result, &error))
     {
       if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
@@ -162,7 +160,7 @@ ide_autotools_builder_build_async (IdeBuilder           *builder,
                                "running", TRUE,
                                NULL);
 
-  if (result)
+  if (result != NULL)
     *result = g_object_ref (build_result);
 
   ide_autotools_build_task_execute_async (build_result,
@@ -186,12 +184,97 @@ ide_autotools_builder_build_finish (IdeBuilder    *builder,
 }
 
 static void
+ide_autotools_builder_install_cb (GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer      user_data)
+{
+  IdeAutotoolsBuildTask *build_task = (IdeAutotoolsBuildTask *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (build_task));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_autotools_build_task_execute_finish (build_task, result, &error))
+    {
+      if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        ide_build_result_set_mode (IDE_BUILD_RESULT (build_task), _("Install cancelled"));
+      else
+        ide_build_result_set_mode (IDE_BUILD_RESULT (build_task), _("Install failed"));
+
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ide_build_result_set_mode (IDE_BUILD_RESULT (build_task), _("Install successful"));
+
+  g_task_return_pointer (task, g_object_ref (build_task), g_object_unref);
+}
+
+static void
+ide_autotools_builder_install_async (IdeBuilder           *builder,
+                                     IdeBuildResult      **result,
+                                     GCancellable         *cancellable,
+                                     GAsyncReadyCallback   callback,
+                                     gpointer              user_data)
+{
+  IdeAutotoolsBuilder *self = (IdeAutotoolsBuilder *)builder;
+  g_autoptr(IdeAutotoolsBuildTask) build_result = NULL;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GFile) directory = NULL;
+  IdeConfiguration *configuration;
+  IdeContext *context;
+
+  g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (builder));
+  g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  context = ide_object_get_context (IDE_OBJECT (builder));
+  configuration = ide_builder_get_configuration (IDE_BUILDER (self));
+  directory = ide_autotools_builder_get_build_directory (self);
+  build_result = g_object_new (IDE_TYPE_AUTOTOOLS_BUILD_TASK,
+                               "context", context,
+                               "configuration", configuration,
+                               "directory", directory,
+                               "mode", _("Building…"),
+                               "running", TRUE,
+                               NULL);
+
+  ide_autotools_build_task_add_target (build_result, "install");
+
+  if (result != NULL)
+    *result = g_object_ref (build_result);
+
+  ide_autotools_build_task_execute_async (build_result,
+                                          IDE_BUILDER_BUILD_FLAGS_NONE,
+                                          cancellable,
+                                          ide_autotools_builder_install_cb,
+                                          g_object_ref (task));
+}
+
+static IdeBuildResult *
+ide_autotools_builder_install_finish (IdeBuilder    *builder,
+                                      GAsyncResult  *result,
+                                      GError       **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILDER (builder), FALSE);
+  g_return_val_if_fail (G_IS_TASK (task), FALSE);
+
+  return g_task_propagate_pointer (task, error);
+}
+
+static void
 ide_autotools_builder_class_init (IdeAutotoolsBuilderClass *klass)
 {
   IdeBuilderClass *builder_class = IDE_BUILDER_CLASS (klass);
 
   builder_class->build_async = ide_autotools_builder_build_async;
   builder_class->build_finish = ide_autotools_builder_build_finish;
+  builder_class->install_async = ide_autotools_builder_install_async;
+  builder_class->install_finish = ide_autotools_builder_install_finish;
 }
 
 static void
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/autotools/ide-makecache.c b/plugins/autotools/ide-makecache.c
index 95cf869..9a61c62 100644
--- a/plugins/autotools/ide-makecache.c
+++ b/plugins/autotools/ide-makecache.c
@@ -35,16 +35,18 @@
 #include <unistd.h>
 #include <ide.h>
 
+#include "ide-autotools-build-target.h"
 #include "ide-makecache.h"
 #include "ide-makecache-target.h"
 
-#define FAKE_CC    "__LIBIDE_FAKE_CC__"
-#define FAKE_CXX   "__LIBIDE_FAKE_CXX__"
-#define FAKE_VALAC "__LIBIDE_FAKE_VALAC__"
+#define FAKE_CC      "__LIBIDE_FAKE_CC__"
+#define FAKE_CXX     "__LIBIDE_FAKE_CXX__"
+#define FAKE_VALAC   "__LIBIDE_FAKE_VALAC__"
+#define PRINT_VARS   "include Makefile\nprint-%: ; @echo $* = $($*)\n"
 
 struct _IdeMakecache
 {
-  IdeObject    parent_instance;
+  IdeObject     parent_instance;
 
   GFile        *makefile;
   GFile        *parent;
@@ -52,6 +54,7 @@ struct _IdeMakecache
   GMappedFile  *mapped;
   EggTaskCache *file_targets_cache;
   EggTaskCache *file_flags_cache;
+  GPtrArray    *build_targets;
 };
 
 typedef struct
@@ -1391,6 +1394,7 @@ ide_makecache_finalize (GObject *object)
   g_clear_object (&self->file_targets_cache);
   g_clear_object (&self->file_flags_cache);
   g_clear_pointer (&self->llvm_flags, g_free);
+  g_clear_pointer (&self->build_targets, g_ptr_array_unref);
 
   G_OBJECT_CLASS (ide_makecache_parent_class)->finalize (object);
 
@@ -1707,3 +1711,382 @@ ide_makecache_get_file_flags_finish (IdeMakecache  *self,
 
   IDE_RETURN (ret);
 }
+
+static gboolean
+_find_make_directories (IdeMakecache  *self,
+                        GFile         *dir,
+                        GPtrArray     *ret,
+                        GCancellable  *cancellable,
+                        GError       **error)
+{
+  g_autoptr(GFileEnumerator) enumerator = NULL;
+  g_autoptr(GPtrArray) dirs = NULL;
+  gboolean has_makefile = FALSE;
+  gboolean has_makefile_am = FALSE;
+  GError *local_error = NULL;
+  gpointer infoptr;
+  guint i;
+
+  g_assert (IDE_IS_MAKECACHE (self));
+  g_assert (G_IS_FILE (dir));
+  g_assert (ret != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  enumerator = g_file_enumerate_children (dir,
+                                          G_FILE_ATTRIBUTE_STANDARD_NAME","
+                                          G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                          G_FILE_QUERY_INFO_NONE,
+                                          cancellable,
+                                          error);
+
+  dirs = g_ptr_array_new_with_free_func (g_object_unref);
+
+  while (NULL != (infoptr = g_file_enumerator_next_file (enumerator, cancellable, &local_error)))
+    {
+      g_autoptr(GFileInfo) info = infoptr;
+      const gchar *name;
+      GFileType type;
+
+      name = g_file_info_get_name (info);
+      type = g_file_info_get_file_type (info);
+
+      if (g_strcmp0 (name, "Makefile") == 0)
+        has_makefile = TRUE;
+      if (g_strcmp0 (name, "Makefile.am") == 0)
+        has_makefile_am = TRUE;
+      else if (type == G_FILE_TYPE_DIRECTORY)
+        g_ptr_array_add (dirs, g_file_get_child (dir, name));
+    }
+
+  if (local_error != NULL)
+    {
+      g_propagate_error (error, local_error);
+      return FALSE;
+    }
+
+  if (has_makefile && has_makefile_am)
+    g_ptr_array_add (ret, g_object_ref (dir));
+
+  if (!g_file_enumerator_close (enumerator, cancellable, error))
+    return FALSE;
+
+  for (i = 0; i < dirs->len; i++)
+    {
+      GFile *item = g_ptr_array_index (dirs, i);
+
+      if (!_find_make_directories (self, item, ret, cancellable, error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static GPtrArray *
+find_make_directories (IdeMakecache  *self,
+                       GFile         *root,
+                       GCancellable  *cancellable,
+                       GError       **error)
+{
+  g_autoptr(GPtrArray) ret = NULL;
+
+  g_assert (IDE_IS_MAKECACHE (self));
+  g_assert (G_IS_FILE (root));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  /*
+   * TODO: Make this work for builddir != srcdir.
+   */
+
+  ret = g_ptr_array_new_with_free_func (g_object_unref);
+
+  if (!_find_make_directories (self, root, ret, cancellable, error))
+    return NULL;
+
+  if (ret->len == 0)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_FOUND,
+                   "No targets were found");
+      return NULL;
+    }
+
+  return g_steal_pointer (&ret);
+}
+
+static GFile *
+find_install_dir (const gchar *key,
+                  GHashTable  *dirs)
+{
+  g_auto(GStrv) parts = g_strsplit (key, "_", 2);
+  g_autofree gchar *lookup = g_strdup_printf ("%sdir", parts[0]);
+  const gchar *path = g_hash_table_lookup (dirs, lookup);
+
+  if (path != NULL)
+    return g_file_new_for_path (path);
+
+  return NULL;
+}
+
+static void
+ide_makecache_get_build_targets_worker (GTask        *task,
+                                        gpointer      source_object,
+                                        gpointer      task_data,
+                                        GCancellable *cancellable)
+{
+  IdeMakecache *self = source_object;
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autoptr(GPtrArray) makedirs = NULL;
+  g_autoptr(GPtrArray) targets = NULL;
+  g_autofree gchar *stdout_buf = NULL;
+  IdeConfigurationManager *configmgr;
+  IdeConfiguration *config;
+  const gchar *make_name = "make";
+  IdeContext *context;
+  IdeRuntime *runtime;
+  IdeVcs *vcs;
+  GFile *workdir;
+  GError *error = NULL;
+  gchar *line;
+  gsize line_len;
+  IdeLineReader reader;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IDE_IS_MAKECACHE (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  /*
+   * This works by performing a dry run using the fake install path.  We then
+   * extract things that are installed into locations that look like they could
+   * be binaries. It's not foolproof, but generally gets the job done.
+   *
+   * We don't pass the tasks #GCancellable into many operations because we
+   * don't want the operation to fail. This is because we cache the results of
+   * this function and want them to be available for future access.
+   */
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  configmgr = ide_context_get_configuration_manager (context);
+  config = ide_configuration_manager_get_current (configmgr);
+  runtime = ide_configuration_get_runtime (config);
+  vcs = ide_context_get_vcs (context);
+  workdir = ide_vcs_get_working_directory (vcs);
+
+  if (runtime != NULL)
+    launcher = ide_runtime_create_launcher (runtime, NULL);
+
+  if (launcher == NULL)
+    {
+      g_autofree gchar *path = NULL;
+      path = g_file_get_path (workdir);
+
+      launcher = ide_subprocess_launcher_new (0);
+      ide_subprocess_launcher_set_cwd (launcher, path);
+    }
+
+  ide_subprocess_launcher_set_flags (launcher,
+                                     (G_SUBPROCESS_FLAGS_STDIN_PIPE |
+                                      G_SUBPROCESS_FLAGS_STDOUT_PIPE));
+
+  /* Default to "make" in runtimes other than the host, since we cannot
+   * rely on our configure-time check for the path there. This isn't totally
+   * correct, since we could be in jhbuild.
+   *
+   * TODO: We might want to rely on the runtime to discover basic utilities
+   *       like GNU Make.
+   */
+  if (g_strcmp0 (ide_configuration_get_runtime_id (config), "host") == 0)
+    make_name = GNU_MAKE_NAME;
+
+  ide_subprocess_launcher_push_argv (launcher, make_name);
+  ide_subprocess_launcher_push_argv (launcher, "-f");
+  ide_subprocess_launcher_push_argv (launcher, "-");
+  ide_subprocess_launcher_push_argv (launcher, "print-bindir");
+  ide_subprocess_launcher_push_argv (launcher, "print-libexecdir");
+  ide_subprocess_launcher_push_argv (launcher, "print-bin_PROGRAMS");
+  ide_subprocess_launcher_push_argv (launcher, "print-noinst_PROGRAMS");
+  ide_subprocess_launcher_push_argv (launcher, "print-libexec_PROGRAMS");
+
+  /*
+   * We need to extract the common automake targets from each of the
+   * directories that we know there is a standalone Makefile within.
+   */
+
+  makedirs = find_make_directories (self, workdir, cancellable, &error);
+
+  if (makedirs == NULL)
+    {
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  /*
+   * We need to extract various programs/libraries/targets from each of
+   * our make directories containing a Makefile.am (translated into a Makefile
+   * so that we can know what targets are available. With that knowledge, we
+   * can build our targets list and cache it for later.
+   */
+
+  targets = g_ptr_array_new_with_free_func (g_object_unref);
+
+  for (guint j = 0; j < makedirs->len; j++)
+    {
+      g_autoptr(GSubprocess) subprocess = NULL;
+      g_autoptr(GHashTable) amdirs = NULL;
+      g_autofree gchar *path = NULL;
+      GFile *makedir;
+
+      /*
+       * Make sure we are running within the directory containing the
+       * Makefile.am that we care about.
+       */
+      makedir = g_ptr_array_index (makedirs, j);
+      path = g_file_get_path (makedir);
+      ide_subprocess_launcher_set_cwd (launcher, path);
+
+      /*
+       * Spawn make, waiting for our stdin input which will add our debug
+       * printf target.
+       */
+      if (NULL == (subprocess = ide_subprocess_launcher_spawn_sync (launcher, NULL, &error)))
+        {
+          g_task_return_error (task, error);
+          IDE_GOTO (failure);
+        }
+
+      /*
+       * 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))
+        {
+          g_task_return_error (task, error);
+          IDE_GOTO (failure);
+        }
+
+      amdirs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+      /*
+       * Read through the output from make, and parse the installation targets
+       * that we care about.
+       */
+      ide_line_reader_init (&reader, stdout_buf, -1);
+
+      while (NULL != (line = ide_line_reader_next (&reader, &line_len)))
+        {
+          g_auto(GStrv) parts = NULL;
+          g_auto(GStrv) names = NULL;
+          const gchar *key;
+
+          line [line_len] = '\0';
+
+          parts = g_strsplit (line, "=", 2);
+
+          if (!parts[0] || !parts[1])
+            continue;
+
+          g_strstrip (parts [0]);
+          g_strstrip (parts [1]);
+
+          key = parts [0];
+
+          if (g_str_has_suffix (key, "dir"))
+            {
+              g_hash_table_insert (amdirs, g_strdup (key), g_strdup (parts [1]));
+              continue;
+            }
+
+          names = g_strsplit (parts [1], " ", 0);
+
+          for (guint i = 0; names [i]; i++)
+            {
+              g_autoptr(IdeBuildTarget) target = NULL;
+              g_autoptr(GFile) installdir = NULL;
+              const gchar *name = names [i];
+
+              installdir = find_install_dir (key, amdirs);
+
+              target = g_object_new (IDE_TYPE_AUTOTOOLS_BUILD_TARGET,
+                                     "build-directory", makedir,
+                                     "context", context,
+                                     "install-directory", installdir,
+                                     "name", name,
+                                     NULL);
+
+              g_ptr_array_add (targets, g_steal_pointer (&target));
+            }
+        }
+    }
+
+  g_task_return_pointer (task, g_steal_pointer (&targets), (GDestroyNotify)g_ptr_array_unref);
+
+failure:
+  IDE_EXIT;
+}
+
+void
+ide_makecache_get_build_targets_async (IdeMakecache        *self,
+                                       GCancellable        *cancellable,
+                                       GAsyncReadyCallback  callback,
+                                       gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  GPtrArray *ret;
+  guint i;
+
+  g_return_if_fail (IDE_IS_MAKECACHE (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_makecache_get_build_targets_async);
+  g_task_set_check_cancellable (task, FALSE);
+
+  if (self->build_targets == NULL)
+    {
+      g_task_run_in_thread (task, ide_makecache_get_build_targets_worker);
+      return;
+    }
+
+  ret = g_ptr_array_new_with_free_func (g_object_unref);
+
+  for (i = 0; i < self->build_targets->len; i++)
+    {
+      IdeBuildTarget *target = g_ptr_array_index (self->build_targets, i);
+
+      g_ptr_array_add (ret, g_object_ref (target));
+    }
+
+  g_task_return_pointer (task, ret, (GDestroyNotify)g_ptr_array_unref);
+}
+
+GPtrArray *
+ide_makecache_get_build_targets_finish (IdeMakecache  *self,
+                                        GAsyncResult  *result,
+                                        GError       **error)
+{
+  GPtrArray *ret;
+
+  g_return_val_if_fail (IDE_IS_MAKECACHE (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  ret = g_task_propagate_pointer (G_TASK (result), error);
+
+  /*
+   * Save a copy of all the build targets for future lookups.
+   */
+  if (ret != NULL && self->build_targets == NULL)
+    {
+      self->build_targets = g_ptr_array_new_with_free_func (g_object_unref);
+
+      for (guint i = 0; i < ret->len; i++)
+        {
+          IdeBuildTarget *item = g_ptr_array_index (ret, i);
+
+          g_ptr_array_add (self->build_targets, g_object_ref (item));
+        }
+    }
+
+  return ret;
+}
diff --git a/plugins/autotools/ide-makecache.h b/plugins/autotools/ide-makecache.h
index 42ed3d0..9fba3c2 100644
--- a/plugins/autotools/ide-makecache.h
+++ b/plugins/autotools/ide-makecache.h
@@ -29,30 +29,37 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeMakecache, ide_makecache, IDE, MAKECACHE, IdeObject)
 
-void                 ide_makecache_new_for_makefile_async  (IdeContext           *context,
-                                                            GFile                *makefile,
-                                                            GCancellable         *cancellable,
-                                                            GAsyncReadyCallback   callback,
-                                                            gpointer              user_data);
-IdeMakecache        *ide_makecache_new_for_makefile_finish (GAsyncResult         *result,
-                                                            GError              **error);
-GFile               *ide_makecache_get_makefile            (IdeMakecache         *self);
-void                 ide_makecache_get_file_flags_async    (IdeMakecache         *self,
-                                                            GFile                *file,
-                                                            GCancellable         *cancellable,
-                                                            GAsyncReadyCallback   callback,
-                                                            gpointer              user_data);
-gchar              **ide_makecache_get_file_flags_finish   (IdeMakecache         *self,
-                                                            GAsyncResult         *result,
-                                                            GError              **error);
-void                 ide_makecache_get_file_targets_async  (IdeMakecache         *self,
-                                                            GFile                *file,
-                                                            GCancellable         *cancellable,
-                                                            GAsyncReadyCallback   callback,
-                                                            gpointer              user_data);
-GPtrArray           *ide_makecache_get_file_targets_finish (IdeMakecache         *self,
-                                                            GAsyncResult         *result,
-                                                            GError              **error);
+void                 ide_makecache_new_for_makefile_async   (IdeContext           *context,
+                                                             GFile                *makefile,
+                                                             GCancellable         *cancellable,
+                                                             GAsyncReadyCallback   callback,
+                                                             gpointer              user_data);
+IdeMakecache        *ide_makecache_new_for_makefile_finish  (GAsyncResult         *result,
+                                                             GError              **error);
+GFile               *ide_makecache_get_makefile             (IdeMakecache         *self);
+void                 ide_makecache_get_file_flags_async     (IdeMakecache         *self,
+                                                             GFile                *file,
+                                                             GCancellable         *cancellable,
+                                                             GAsyncReadyCallback   callback,
+                                                             gpointer              user_data);
+gchar              **ide_makecache_get_file_flags_finish    (IdeMakecache         *self,
+                                                             GAsyncResult         *result,
+                                                             GError              **error);
+void                 ide_makecache_get_file_targets_async   (IdeMakecache         *self,
+                                                             GFile                *file,
+                                                             GCancellable         *cancellable,
+                                                             GAsyncReadyCallback   callback,
+                                                             gpointer              user_data);
+GPtrArray           *ide_makecache_get_file_targets_finish  (IdeMakecache         *self,
+                                                             GAsyncResult         *result,
+                                                             GError              **error);
+void                 ide_makecache_get_build_targets_async  (IdeMakecache         *self,
+                                                             GCancellable         *cancellable,
+                                                             GAsyncReadyCallback   callback,
+                                                             gpointer              user_data);
+GPtrArray           *ide_makecache_get_build_targets_finish (IdeMakecache         *self,
+                                                             GAsyncResult         *result,
+                                                             GError              **error);
 
 G_END_DECLS
 
diff --git a/plugins/build-tools/gbp-build-workbench-addin.c b/plugins/build-tools/gbp-build-workbench-addin.c
index 9511e32..80fbe4b 100644
--- a/plugins/build-tools/gbp-build-workbench-addin.c
+++ b/plugins/build-tools/gbp-build-workbench-addin.c
@@ -36,10 +36,8 @@ struct _GbpBuildWorkbenchAddin
   GbpBuildPerspective *build_perspective;
 
   /* Owned */
-  EggBindingGroup     *bindings;
   IdeBuildResult      *result;
   GSimpleActionGroup  *actions;
-  GCancellable        *cancellable;
 };
 
 static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
@@ -59,194 +57,19 @@ static void
 gbp_build_workbench_addin_set_result (GbpBuildWorkbenchAddin *self,
                                       IdeBuildResult         *result)
 {
-  IdeWorkbenchHeaderBar *headerbar;
-  IdeOmniBar *omnibar;
-
   g_return_if_fail (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
   g_return_if_fail (!result || IDE_IS_BUILD_RESULT (result));
   g_return_if_fail (self->workbench != NULL);
 
-  headerbar = ide_workbench_get_headerbar (self->workbench);
-  omnibar = ide_workbench_header_bar_get_omni_bar (headerbar);
-
   if (g_set_object (&self->result, result))
     {
-      egg_binding_group_set_source (self->bindings, result);
-      ide_omni_bar_set_build_result (omnibar, result);
+      gbp_build_log_panel_set_result (self->build_log_panel, result);
+      gtk_widget_show (GTK_WIDGET (self->build_log_panel));
       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RESULT]);
     }
 }
 
 static void
-gbp_build_workbench_addin_build_cb (GObject      *object,
-                                    GAsyncResult *result,
-                                    gpointer      user_data)
-{
-  IdeBuilder *builder = (IdeBuilder *)object;
-  g_autoptr(GbpBuildWorkbenchAddin) self = user_data;
-  g_autoptr(IdeBuildResult) build_result = NULL;
-  g_autoptr(GError) error = NULL;
-
-  g_assert (IDE_IS_BUILDER (builder));
-  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
-
-  build_result = ide_builder_build_finish (builder, result, &error);
-
-  if (error != NULL && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-    g_message ("%s", error->message);
-}
-
-static void
-gbp_build_workbench_addin_save_all_cb (GObject      *object,
-                                       GAsyncResult *result,
-                                       gpointer      user_data)
-{
-  IdeBufferManager *bufmgr = (IdeBufferManager *)object;
-  g_autoptr(IdeBuildResult) build_result = NULL;
-  struct {
-    GbpBuildWorkbenchAddin *self;
-    IdeBuilder *builder;
-    IdeBuilderBuildFlags flags;
-  } *state = user_data;
-
-  g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (state->self));
-
-  ide_buffer_manager_save_all_finish (bufmgr, result, NULL);
-
-  ide_builder_build_async (state->builder,
-                           state->flags,
-                           &build_result,
-                           state->self->cancellable,
-                           gbp_build_workbench_addin_build_cb,
-                           g_object_ref (state->self));
-
-  gbp_build_workbench_addin_set_result (state->self, build_result);
-  gbp_build_log_panel_set_result (state->self->build_log_panel, build_result);
-
-  g_object_unref (state->self);
-  g_object_unref (state->builder);
-  g_slice_free1 (sizeof *state, state);
-}
-
-static void
-gbp_build_workbench_addin_do_build (GbpBuildWorkbenchAddin *self,
-                                    IdeBuilderBuildFlags    flags)
-{
-  g_autoptr(IdeBuilder) builder = NULL;
-  g_autoptr(GError) error = NULL;
-  IdeConfigurationManager *config_manager;
-  IdeConfiguration *configuration;
-  IdeBuildSystem *build_system;
-  IdeWorkbench *workbench;
-  IdeContext *context;
-  struct {
-    GbpBuildWorkbenchAddin *self;
-    IdeBuilder *builder;
-    IdeBuilderBuildFlags flags;
-  } *state;
-
-  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
-
-  gbp_build_workbench_addin_set_result (self, NULL);
-
-  workbench = ide_widget_get_workbench (GTK_WIDGET (self->panel));
-  context = ide_workbench_get_context (workbench);
-  build_system = ide_context_get_build_system (context);
-  config_manager = ide_context_get_configuration_manager (context);
-  configuration = ide_configuration_manager_get_current (config_manager);
-
-  builder = ide_build_system_get_builder (build_system, configuration, &error);
-
-  if (error != NULL)
-    {
-      gbp_build_panel_add_error (self->panel, error->message);
-      return;
-    }
-
-  g_clear_object (&self->cancellable);
-  self->cancellable = g_cancellable_new ();
-
-  state = g_slice_alloc0 (sizeof *state);
-  state->self = g_object_ref (self);
-  state->builder = g_object_ref (builder);
-  state->flags = flags;
-
-  ide_buffer_manager_save_all_async (ide_context_get_buffer_manager (context),
-                                     self->cancellable,
-                                     gbp_build_workbench_addin_save_all_cb,
-                                     state);
-
-  /*
-   * No need to focus the build output, since the user can show that
-   * from the omnibar relatively quickly. We just need to make sure
-   * it is visible should they choose to view it.
-   */
-  gtk_widget_show (GTK_WIDGET (self->build_log_panel));
-
-  /*
-   * There is also no need to show the build result panel immediately.
-   * We can display the panel when a diagnostic has been discovered.
-   * Otherwise, it doesn't provide any additional information.
-   */
-}
-
-static void
-gbp_build_workbench_addin_build (GSimpleAction *action,
-                                 GVariant      *param,
-                                 gpointer       user_data)
-{
-  GbpBuildWorkbenchAddin *self = user_data;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
-
-  gbp_build_workbench_addin_do_build (self, 0);
-}
-
-static void
-gbp_build_workbench_addin_rebuild (GSimpleAction *action,
-                                   GVariant      *param,
-                                   gpointer       user_data)
-{
-  GbpBuildWorkbenchAddin *self = user_data;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
-
-  gbp_build_workbench_addin_do_build (self, IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN);
-}
-
-static void
-gbp_build_workbench_addin_clean (GSimpleAction *action,
-                                 GVariant      *param,
-                                 gpointer       user_data)
-{
-  GbpBuildWorkbenchAddin *self = user_data;
-
-  g_assert (G_IS_SIMPLE_ACTION (action));
-  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
-
-  gbp_build_workbench_addin_do_build (self,
-                                      (IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN |
-                                       IDE_BUILDER_BUILD_FLAGS_NO_BUILD));
-}
-
-static void
-gbp_build_workbench_addin_cancel (GSimpleAction *action,
-                                  GVariant      *param,
-                                  gpointer       user_data)
-{
-  GbpBuildWorkbenchAddin *self = user_data;
-
-  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
-
-  if (self->cancellable)
-    g_cancellable_cancel (self->cancellable);
-}
-
-static void
 gbp_build_workbench_addin_view_output (GSimpleAction *action,
                                        GVariant      *param,
                                        gpointer       user_data)
@@ -285,11 +108,7 @@ gbp_build_workbench_addin_configure (GSimpleAction *action,
     gbp_build_perspective_set_configuration (self->build_perspective, config);
 }
 
-static const GActionEntry actions[] = {
-  { "build", gbp_build_workbench_addin_build },
-  { "rebuild", gbp_build_workbench_addin_rebuild },
-  { "clean", gbp_build_workbench_addin_clean },
-  { "cancel-build", gbp_build_workbench_addin_cancel },
+static const GActionEntry actions_entries[] = {
   { "configure", gbp_build_workbench_addin_configure, "s" },
   { "view-output", gbp_build_workbench_addin_view_output },
 };
@@ -300,8 +119,8 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
 {
   IdeConfigurationManager *configuration_manager;
   GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)addin;
-  IdeWorkbenchHeaderBar *header;
   IdeConfiguration *configuration;
+  IdeBuildManager *build_manager;
   IdePerspective *editor;
   IdeContext *context;
   GtkWidget *pane;
@@ -313,6 +132,15 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
   self->workbench = workbench;
 
   context = ide_workbench_get_context (workbench);
+
+  build_manager = ide_context_get_build_manager (context);
+
+  g_signal_connect_object (build_manager,
+                           "build-started",
+                           G_CALLBACK (gbp_build_workbench_addin_set_result),
+                           self,
+                           G_CONNECT_SWAPPED);
+
   configuration_manager = ide_context_get_configuration_manager (context);
   configuration = ide_configuration_manager_get_current (configuration_manager);
 
@@ -332,23 +160,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,
@@ -367,11 +178,6 @@ gbp_build_workbench_addin_unload (IdeWorkbenchAddin *addin,
   g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
   g_assert (IDE_IS_WORKBENCH (workbench));
 
-  if (self->cancellable)
-    g_cancellable_cancel (self->cancellable);
-
-  g_clear_object (&self->cancellable);
-
   gtk_widget_insert_action_group (GTK_WIDGET (workbench), "build-tools", NULL);
 
   gtk_widget_destroy (GTK_WIDGET (self->panel));
@@ -402,10 +208,8 @@ gbp_build_workbench_addin_finalize (GObject *object)
 {
   GbpBuildWorkbenchAddin *self = (GbpBuildWorkbenchAddin *)object;
 
-  g_clear_object (&self->bindings);
   g_clear_object (&self->actions);
   g_clear_object (&self->result);
-  g_clear_object (&self->cancellable);
 
   G_OBJECT_CLASS (gbp_build_workbench_addin_parent_class)->finalize (object);
 }
@@ -431,57 +235,12 @@ gbp_build_workbench_addin_class_init (GbpBuildWorkbenchAddinClass *klass)
 static void
 gbp_build_workbench_addin_init (GbpBuildWorkbenchAddin *self)
 {
-  gint i;
-  static const struct {
-    const gchar   *property;
-    const gchar   *action;
-    GBindingFlags  flags;
-  } bindings[] = {
-    { "running", "build", G_BINDING_INVERT_BOOLEAN },
-    { "running", "rebuild", G_BINDING_INVERT_BOOLEAN },
-    { "running", "clean", G_BINDING_INVERT_BOOLEAN },
-    { "running", "cancel-build", 0 },
-    { NULL }
-  };
-
   self->actions = g_simple_action_group_new ();
+
   g_action_map_add_action_entries (G_ACTION_MAP (self->actions),
-                                   actions, G_N_ELEMENTS (actions),
+                                   actions_entries,
+                                   G_N_ELEMENTS (actions_entries),
                                    self);
-
-  self->bindings = egg_binding_group_new ();
-
-  for (i = 0; bindings [i].property; i++)
-    {
-      GActionMap *map = G_ACTION_MAP (self->actions);
-      GAction *action;
-
-      action = g_action_map_lookup_action (map, bindings [i].action);
-      egg_binding_group_bind (self->bindings, bindings [i].property,
-                              action, "enabled",
-                              G_BINDING_SYNC_CREATE | bindings [i].flags);
-    }
-}
-
-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
@@ -489,5 +248,4 @@ 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/flatpak/gbp-flatpak-runtime.c b/plugins/flatpak/gbp-flatpak-runtime.c
index 9b96410..7ed90c7 100644
--- a/plugins/flatpak/gbp-flatpak-runtime.c
+++ b/plugins/flatpak/gbp-flatpak-runtime.c
@@ -54,7 +54,7 @@ get_build_directory (GbpFlatpakRuntime *self)
                            "gnome-builder",
                            "builds",
                            ide_project_get_name (project),
-                           "flatpak-app",
+                           "flatpak",
                            ide_runtime_get_id (IDE_RUNTIME (self)),
                            NULL);
 }
@@ -121,7 +121,7 @@ gbp_flatpak_runtime_prebuild_worker (GTask        *task,
 
   launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
   subprocess = g_subprocess_launcher_spawn (launcher, &error,
-                                            "flatpak-app",
+                                            "flatpak",
                                             "build-init",
                                             build_path,
                                             /* XXX: Fake name, probably okay, but
@@ -181,7 +181,7 @@ gbp_flatpak_runtime_create_launcher (IdeRuntime  *runtime,
     {
       g_autofree gchar *build_path = get_build_directory (self);
 
-      ide_subprocess_launcher_push_argv (ret, "flatpak-app");
+      ide_subprocess_launcher_push_argv (ret, "flatpak");
       ide_subprocess_launcher_push_argv (ret, "build");
       ide_subprocess_launcher_push_argv (ret, build_path);
     }
diff --git a/plugins/gcc/gbp-gcc-build-result-addin.c b/plugins/gcc/gbp-gcc-build-result-addin.c
index 6609c6a..2453d8a 100644
--- a/plugins/gcc/gbp-gcc-build-result-addin.c
+++ b/plugins/gcc/gbp-gcc-build-result-addin.c
@@ -100,10 +100,15 @@ create_diagnostic (GbpGccBuildResultAddin *self,
   g_assert (GBP_IS_GCC_BUILD_RESULT_ADDIN (self));
   g_assert (match_info != NULL);
 
+  message = g_match_info_fetch_named (match_info, "message");
+
+  /* Ignore _FORTIFY_SOURCE warnings which require optimization */
+  if (message != NULL && (strncmp (message, "#warning _FORTIFY_SOURCE requires compiling with optimization", 
61) == 0))
+    return NULL;
+
   filename = g_match_info_fetch_named (match_info, "filename");
   line = g_match_info_fetch_named (match_info, "line");
   column = g_match_info_fetch_named (match_info, "column");
-  message = g_match_info_fetch_named (match_info, "message");
   level = g_match_info_fetch_named (match_info, "level");
 
   parsed.line = g_ascii_strtoll (line, NULL, 10);
diff --git a/plugins/git/ide-git-vcs.c b/plugins/git/ide-git-vcs.c
index f2e64a3..7659d2e 100644
--- a/plugins/git/ide-git-vcs.c
+++ b/plugins/git/ide-git-vcs.c
@@ -62,7 +62,11 @@ G_DEFINE_TYPE_EXTENDED (IdeGitVcs, ide_git_vcs, IDE_TYPE_OBJECT, 0,
 enum {
   PROP_0,
   PROP_REPOSITORY,
-  LAST_PROP
+  LAST_PROP,
+
+  /* Override properties */
+  PROP_BRANCH_NAME,
+  PROP_WORKING_DIRECTORY,
 };
 
 enum {
@@ -372,6 +376,16 @@ ide_git_vcs_get_branch_name (IdeVcs *vcs)
 }
 
 static void
+ide_git_vcs_real_reloaded (IdeGitVcs      *self,
+                           GgitRepository *repository)
+{
+  g_assert (IDE_IS_GIT_VCS (self));
+  g_assert (GGIT_IS_REPOSITORY (repository));
+
+  g_object_notify (G_OBJECT (self), "branch-name");
+}
+
+static void
 ide_git_vcs_dispose (GObject *object)
 {
   IdeGitVcs *self = (IdeGitVcs *)object;
@@ -410,10 +424,18 @@ ide_git_vcs_get_property (GObject    *object,
 
   switch (prop_id)
     {
+    case PROP_BRANCH_NAME:
+      g_value_set_string (value, ide_git_vcs_get_branch_name (IDE_VCS (self)));
+      break;
+
     case PROP_REPOSITORY:
       g_value_set_object (value, ide_git_vcs_get_repository (self));
       break;
 
+    case PROP_WORKING_DIRECTORY:
+      g_value_set_object (value, ide_git_vcs_get_working_directory (IDE_VCS (self)));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -437,6 +459,9 @@ ide_git_vcs_class_init (IdeGitVcsClass *klass)
   object_class->dispose = ide_git_vcs_dispose;
   object_class->get_property = ide_git_vcs_get_property;
 
+  g_object_class_override_property (object_class, PROP_BRANCH_NAME, "branch-name");
+  g_object_class_override_property (object_class, PROP_WORKING_DIRECTORY, "working-directory");
+
   /**
    * IdeGitVcs:repository:
    *
@@ -469,14 +494,13 @@ ide_git_vcs_class_init (IdeGitVcsClass *klass)
    * be used directly except in very specific situations. The gutter change renderer uses this
    * instance in a threaded manner.
    */
-  signals [RELOADED] = g_signal_new ("reloaded",
-                                      G_TYPE_FROM_CLASS (klass),
-                                      G_SIGNAL_RUN_LAST,
-                                      0,
-                                      NULL, NULL, NULL,
-                                      G_TYPE_NONE,
-                                      1,
-                                      GGIT_TYPE_REPOSITORY);
+  signals [RELOADED] =
+    g_signal_new_class_handler ("reloaded",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST,
+                                G_CALLBACK (ide_git_vcs_real_reloaded),
+                                NULL, NULL, NULL,
+                                G_TYPE_NONE, 1, GGIT_TYPE_REPOSITORY);
 }
 
 static void
diff --git a/plugins/jhbuild/jhbuild_plugin.py b/plugins/jhbuild/jhbuild_plugin.py
index 3a7483c..be7ad45 100644
--- a/plugins/jhbuild/jhbuild_plugin.py
+++ b/plugins/jhbuild/jhbuild_plugin.py
@@ -100,7 +100,10 @@ class JhbuildRuntimeProvider(GObject.Object, Ide.RuntimeProvider):
 
     def do_load(self, manager):
         if self._is_jhbuild_installed():
-            runtime = JhbuildRuntime(id="jhbuild", display_name="JHBuild")
+            context = manager.get_context()
+            runtime = JhbuildRuntime(context=context,
+                                     id="jhbuild",
+                                     display_name="JHBuild")
             manager.add(runtime)
             self.runtimes.append(runtime)
 
diff --git a/plugins/project-tree/gb-project-tree.c b/plugins/project-tree/gb-project-tree.c
index 8ba8c5e..0f41f05 100644
--- a/plugins/project-tree/gb-project-tree.c
+++ b/plugins/project-tree/gb-project-tree.c
@@ -149,7 +149,8 @@ gb_project_tree_vcs_changed (GbProjectTree *self,
 
   ide_tree_rebuild (IDE_TREE (self));
 
-  gb_project_tree_reveal (self, file);
+  if (file != NULL)
+    gb_project_tree_reveal (self, file);
 }
 
 void
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..d9a04b8
--- /dev/null
+++ b/plugins/run-tools/gbp-run-workbench-addin.c
@@ -0,0 +1,296 @@
+/* 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;
+
+  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)
+{
+  IdeRunManager *run_manager = (IdeRunManager *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_RUN_MANAGER (run_manager));
+  g_assert (G_IS_TASK (task));
+
+  if (!ide_run_manager_run_finish (run_manager, result, &error))
+    {
+      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        g_warning ("%s", error->message);
+      g_task_return_error (task, error);
+      IDE_GOTO (failure);
+    }
+
+  g_task_return_boolean (task, TRUE);
+
+failure:
+  IDE_EXIT;
+}
+
+static IdeBuildTarget *
+find_best_target (GPtrArray *targets)
+{
+  IdeBuildTarget *ret = NULL;
+  guint i;
+
+  g_assert (targets != NULL);
+
+  for (i = 0; i < targets->len; i++)
+    {
+      IdeBuildTarget *target = g_ptr_array_index (targets, i);
+      g_autoptr(GFile) installdir = NULL;
+
+      installdir = ide_build_target_get_install_directory (target);
+
+      if (installdir == NULL)
+        continue;
+
+      if (ret == NULL)
+        ret = target;
+
+      /* TODO: Compare likelyhood of primary binary */
+    }
+
+  return ret;
+}
+
+static void
+gbp_run_workbench_addin_get_build_targets_cb (GObject      *object,
+                                              GAsyncResult *result,
+                                              gpointer      user_data)
+{
+  IdeBuildSystem *build_system = (IdeBuildSystem *)object;
+  GbpRunWorkbenchAddin *self;
+  g_autoptr(GPtrArray) targets = NULL;
+  g_autoptr(GTask) task = user_data;
+  IdeBuildTarget *best_match;
+  IdeRunManager *run_manager;
+  GCancellable *cancellable;
+  IdeContext *context;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUILD_SYSTEM (build_system));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  self = g_task_get_source_object (task);
+  cancellable = g_task_get_cancellable (task);
+
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (self->workbench));
+
+  targets = ide_build_system_get_build_targets_finish (build_system, result, &error);
+
+  if (targets == NULL)
+    {
+      g_task_return_error (task, error);
+      IDE_EXIT;
+    }
+
+  best_match = find_best_target (targets);
+
+  if (best_match == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_FAILED,
+                               "%s",
+                               _("Failed to locate build target"));
+      IDE_EXIT;
+    }
+
+  context = ide_workbench_get_context (self->workbench);
+  run_manager = ide_context_get_run_manager (context);
+
+  ide_run_manager_run_async (run_manager,
+                             best_match,
+                             cancellable,
+                             gbp_run_workbench_addin_run_cb,
+                             g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_run_workbench_addin_run (GSimpleAction *action,
+                             GVariant      *param,
+                             gpointer       user_data)
+{
+  GbpRunWorkbenchAddin *self = user_data;
+  g_autoptr(GTask) task = NULL;
+  IdeBuildSystem *build_system;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
+
+  context = ide_workbench_get_context (self->workbench);
+  build_system = ide_context_get_build_system (context);
+
+  task = g_task_new (self, NULL, NULL, NULL);
+  g_task_set_source_tag (task, gbp_run_workbench_addin_run);
+
+  ide_build_system_get_build_targets_async (build_system,
+                                            NULL,
+                                            gbp_run_workbench_addin_get_build_targets_cb,
+                                            g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_run_workbench_addin_stop (GSimpleAction *action,
+                              GVariant      *param,
+                              gpointer       user_data)
+{
+  GbpRunWorkbenchAddin *self = user_data;
+  IdeRunManager *run_manager;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SIMPLE_ACTION (action));
+  g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
+
+  context = ide_workbench_get_context (self->workbench);
+  run_manager = ide_context_get_run_manager (context);
+
+  ide_run_manager_cancel (run_manager);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_run_workbench_addin_load (IdeWorkbenchAddin *addin,
+                              IdeWorkbench      *workbench)
+{
+  GbpRunWorkbenchAddin *self = (GbpRunWorkbenchAddin *)addin;
+  g_autoptr(GSimpleActionGroup) group = NULL;
+  IdeWorkbenchHeaderBar *headerbar;
+  IdeRunManager *run_manager;
+  IdeContext *context;
+  GtkWidget *button;
+  static const GActionEntry entries[] = {
+    { "run", gbp_run_workbench_addin_run },
+    { "stop", gbp_run_workbench_addin_stop },
+  };
+
+  g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  self->workbench = workbench;
+
+  context = ide_workbench_get_context (workbench);
+  run_manager = ide_context_get_run_manager (context);
+
+  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),
+                         NULL);
+  g_object_bind_property (run_manager, "busy", button, "visible", G_BINDING_SYNC_CREATE | 
G_BINDING_INVERT_BOOLEAN);
+  ide_widget_add_style_class (button, "image-button");
+  ide_workbench_header_bar_insert_right (headerbar, button, GTK_PACK_START, 0);
+
+  button = g_object_new (GTK_TYPE_BUTTON,
+                         "action-name", "run-tools.stop",
+                         "focus-on-click", FALSE,
+                         "child", g_object_new (GTK_TYPE_IMAGE,
+                                                "icon-name", "media-playback-stop-symbolic",
+                                                "visible", TRUE,
+                                                NULL),
+                         NULL);
+  g_object_bind_property (run_manager, "busy", button, "visible", G_BINDING_SYNC_CREATE);
+  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));
+
+  g_object_bind_property (run_manager,
+                          "busy",
+                          g_action_map_lookup_action (G_ACTION_MAP (group), "run"),
+                          "enabled",
+                          G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+
+  g_object_bind_property (run_manager,
+                          "busy",
+                          g_action_map_lookup_action (G_ACTION_MAP (group), "stop"),
+                          "enabled",
+                          G_BINDING_SYNC_CREATE);
+}
+
+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;
+}
+
+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]