[gnome-builder/wip/chergert/runtimes] wip: runtimes



commit f188f651b785551d6870207c756e5cb6af50bdeb
Author: Christian Hergert <chergert redhat com>
Date:   Mon Feb 1 22:39:29 2016 +0100

    wip: runtimes

 configure.ac                                    |    3 +-
 data/ui/ide-omni-search-row.ui                  |    1 +
 libide/Makefile.am                              |   10 +
 libide/ide-build-system.c                       |   62 ++--
 libide/ide-build-system.h                       |    7 +-
 libide/ide-builder.c                            |  175 ++++++++--
 libide/ide-builder.h                            |   29 +-
 libide/ide-configuration.c                      |  368 ++++++++++++++++++
 libide/ide-configuration.h                      |   51 +++
 libide/ide-context.c                            |   42 ++
 libide/ide-context.h                            |    1 +
 libide/ide-device-manager.c                     |    3 +
 libide/ide-device.c                             |   33 +-
 libide/ide-device.h                             |   22 +-
 libide/ide-internal.h                           |    1 +
 libide/ide-runtime-manager.c                    |  239 ++++++++++++
 libide/ide-runtime-manager.h                    |   39 ++
 libide/ide-runtime-provider.c                   |   61 +++
 libide/ide-runtime-provider.h                   |   49 +++
 libide/ide-runtime.c                            |  367 ++++++++++++++++++
 libide/ide-runtime.h                            |   93 +++++
 libide/ide-subprocess-launcher.c                |  461 +++++++++++++++++++++++
 libide/ide-subprocess-launcher.h                |   75 ++++
 libide/ide-types.h                              |    5 +
 libide/ide.h                                    |    4 +
 libide/local/ide-local-device.c                 |   20 +-
 plugins/Makefile.am                             |    1 +
 plugins/autotools/ide-autotools-build-system.c  |   69 +---
 plugins/autotools/ide-autotools-build-task.c    |  425 +++++++++------------
 plugins/autotools/ide-autotools-build-task.h    |    1 +
 plugins/autotools/ide-autotools-builder.c       |  224 +----------
 plugins/autotools/ide-autotools-builder.h       |    7 -
 plugins/build-tools/gbp-build-panel.c           |  124 ++++++-
 plugins/build-tools/gbp-build-panel.ui          |   59 +++-
 plugins/build-tools/gbp-build-tool.c            |   40 ++-
 plugins/build-tools/gbp-build-workbench-addin.c |   88 ++++-
 plugins/xdg-app/Makefile.am                     |   40 ++
 plugins/xdg-app/configure.ac                    |   24 ++
 plugins/xdg-app/gbp-xdg-plugin.c                |   30 ++
 plugins/xdg-app/gbp-xdg-runtime-provider.c      |  242 ++++++++++++
 plugins/xdg-app/gbp-xdg-runtime-provider.h      |   32 ++
 plugins/xdg-app/gbp-xdg-runtime.c               |  317 ++++++++++++++++
 plugins/xdg-app/gbp-xdg-runtime.h               |   32 ++
 plugins/xdg-app/xdg-app.plugin                  |    7 +
 44 files changed, 3334 insertions(+), 649 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 98ac633..5ada452 100644
--- a/configure.ac
+++ b/configure.ac
@@ -239,6 +239,7 @@ m4_include([plugins/sysmon/configure.ac])
 m4_include([plugins/todo/configure.ac])
 m4_include([plugins/terminal/configure.ac])
 m4_include([plugins/vala-pack/configure.ac])
+m4_include([plugins/xdg-app/configure.ac])
 m4_include([plugins/xml-pack/configure.ac])
 
 
@@ -249,7 +250,6 @@ enable_python_scripting=no
 AS_IF([test "x$have_pygobject" = "xyes"],[
        AM_PATH_PYTHON([3.2.3])
        AC_PATH_TOOL(PYTHON3_CONFIG, "python3-config")
-
        AS_IF([test -z "${PYTHON3_CONFIG}"],[
                AC_MSG_RESULT([Failed to locate python3-config.])
        ],[
@@ -503,6 +503,7 @@ echo "  Symbol Tree .......................... : ${enable_symbol_tree_plugin}"
 echo "  Todo ................................. : ${enable_todo_plugin}"
 echo "  Terminal ............................. : ${enable_terminal_plugin}"
 echo "  Vala Language Pack ................... : ${enable_vala_pack_plugin}"
+echo "  Xdg-App .............................. : ${enable_xdg_app_plugin}"
 echo "  XML Language Pack .................... : ${enable_xml_pack_plugin}"
 echo ""
 echo " Templates"
diff --git a/data/ui/ide-omni-search-row.ui b/data/ui/ide-omni-search-row.ui
index bfb167c..f9b08fa 100644
--- a/data/ui/ide-omni-search-row.ui
+++ b/data/ui/ide-omni-search-row.ui
@@ -15,6 +15,7 @@
         </child>
         <child>
           <object class="GtkLabel" id="title">
+            <property name="ellipsize">middle</property>
             <property name="visible">true</property>
             <property name="xalign">0.0</property>
           </object>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 657d914..2564a10 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -61,6 +61,8 @@ libide_1_0_la_public_sources = \
        ide-completion-provider.h \
        ide-completion-results.c \
        ide-completion-results.h \
+       ide-configuration.c \
+       ide-configuration.h \
        ide-context.c \
        ide-context.h \
        ide-debugger.c \
@@ -150,6 +152,12 @@ libide_1_0_la_public_sources = \
        ide-project.h \
        ide-recent-projects.c \
        ide-recent-projects.h \
+       ide-runtime.c \
+       ide-runtime.h \
+       ide-runtime-manager.c \
+       ide-runtime-manager.h \
+       ide-runtime-provider.c \
+       ide-runtime-provider.h \
        ide-refactory.c \
        ide-refactory.h \
        ide-script-manager.c \
@@ -188,6 +196,8 @@ libide_1_0_la_public_sources = \
        ide-source-view-mode.h \
        ide-source-view.c \
        ide-source-view.h \
+       ide-subprocess-launcher.c \
+       ide-subprocess-launcher.h \
        ide-symbol-resolver.c \
        ide-symbol-resolver.h \
        ide-symbol.c \
diff --git a/libide/ide-build-system.c b/libide/ide-build-system.c
index 90b7f33..fb9c2fb 100644
--- a/libide/ide-build-system.c
+++ b/libide/ide-build-system.c
@@ -21,8 +21,11 @@
 #include "ide-build-system.h"
 #include "ide-context.h"
 #include "ide-device.h"
+#include "ide-device-manager.h"
 #include "ide-file.h"
 #include "ide-object.h"
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
 
 typedef struct
 {
@@ -104,9 +107,28 @@ ide_build_system_get_build_flags_finish (IdeBuildSystem  *self,
   return g_new0 (gchar*, 1);
 }
 
+static IdeBuilder *
+ide_build_system_real_get_builder (IdeBuildSystem    *self,
+                                   IdeConfiguration  *configuration,
+                                   GError           **error)
+{
+  g_assert (IDE_IS_BUILD_SYSTEM (self));
+  g_assert (IDE_IS_CONFIGURATION (configuration));
+
+  g_set_error (error,
+               G_IO_ERROR,
+               G_IO_ERROR_NOT_SUPPORTED,
+               _("%s() is not supported on %s build system."),
+               G_STRFUNC, g_type_name (G_TYPE_FROM_INSTANCE (self)));
+
+  return NULL;
+}
+
 static void
 ide_build_system_default_init (IdeBuildSystemInterface *iface)
 {
+  iface->get_builder = ide_build_system_real_get_builder;
+
   properties [PROP_PROJECT_FILE] =
     g_param_spec_object ("project-file",
                          "Project File",
@@ -196,43 +218,23 @@ ide_build_system_new_finish (GAsyncResult  *result,
 /**
  * ide_build_system_get_builder:
  * @system: The #IdeBuildSystem to perform the build.
- * @config: (nullable): The configuration options for the build.
- * @device: The #IdeDevice the result should be able to run on.
+ * @configuration: An #IdeConfiguration.
  *
- * This function should return an #IdeBuilder that can be used to perform a
- * build of the project using the configuration specified. @device may be
- * a non-local device, for which cross-compilation may be necessary.
+ * This function returns an #IdeBuilder that can be used to perform a
+ * build of the project using the configuration specified.
+ *
+ * See ide_builder_build_async() for more information.
  *
  * Returns: (transfer full): An #IdeBuilder or %NULL and @error is set.
  */
 IdeBuilder *
-ide_build_system_get_builder (IdeBuildSystem  *system,
-                              GKeyFile        *config,
-                              IdeDevice       *device,
-                              GError         **error)
+ide_build_system_get_builder (IdeBuildSystem    *system,
+                              IdeConfiguration  *configuration,
+                              GError           **error)
 {
-  IdeBuildSystemInterface *iface;
-  IdeBuilder *ret = NULL;
-  g_autoptr(GKeyFile) local = NULL;
-
   g_return_val_if_fail (IDE_IS_BUILD_SYSTEM (system), NULL);
-  g_return_val_if_fail (IDE_IS_DEVICE (device), NULL);
-
-  if (config == NULL)
-    config = local = g_key_file_new ();
-
-  iface = IDE_BUILD_SYSTEM_GET_IFACE (system);
-
-  if (iface->get_builder)
-    ret = iface->get_builder (system, config, device, error);
-  else
-    g_set_error (error,
-                 G_IO_ERROR,
-                 G_IO_ERROR_NOT_SUPPORTED,
-                 _("%s() is not supported on %s build system."),
-                 G_STRFUNC,
-                 g_type_name (G_TYPE_FROM_INSTANCE (system)));
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (configuration), NULL);
 
-  return ret;
+  return IDE_BUILD_SYSTEM_GET_IFACE (system)->get_builder (system, configuration, error);
 }
 
diff --git a/libide/ide-build-system.h b/libide/ide-build-system.h
index ef286c6..bd8bf7f 100644
--- a/libide/ide-build-system.h
+++ b/libide/ide-build-system.h
@@ -21,6 +21,7 @@
 
 #include <gio/gio.h>
 
+#include "ide-configuration.h"
 #include "ide-object.h"
 
 G_BEGIN_DECLS
@@ -35,8 +36,7 @@ struct _IdeBuildSystemInterface
 
   gint        (*get_priority)           (IdeBuildSystem       *system);
   IdeBuilder *(*get_builder)            (IdeBuildSystem       *system,
-                                         GKeyFile             *config,
-                                         IdeDevice            *device,
+                                         IdeConfiguration     *configuration,
                                          GError              **error);
   void        (*get_build_flags_async)  (IdeBuildSystem       *self,
                                          IdeFile              *file,
@@ -65,8 +65,7 @@ void            ide_build_system_new_async              (IdeContext           *c
 IdeBuildSystem *ide_build_system_new_finish             (GAsyncResult         *result,
                                                          GError              **error);
 IdeBuilder     *ide_build_system_get_builder            (IdeBuildSystem       *system,
-                                                         GKeyFile             *config,
-                                                         IdeDevice            *device,
+                                                         IdeConfiguration     *configuration,
                                                          GError              **error);
 
 G_END_DECLS
diff --git a/libide/ide-builder.c b/libide/ide-builder.c
index 520e177..982f0d2 100644
--- a/libide/ide-builder.c
+++ b/libide/ide-builder.c
@@ -20,13 +20,90 @@
 
 #include "ide-build-result.h"
 #include "ide-builder.h"
+#include "ide-configuration.h"
 
-G_DEFINE_ABSTRACT_TYPE (IdeBuilder, ide_builder, IDE_TYPE_OBJECT)
+typedef struct
+{
+  IdeConfiguration *configuration;
+} IdeBuilderPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (IdeBuilder, ide_builder, IDE_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_CONFIGURATION,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+/**
+ * ide_builder_get_configuration:
+ * @self: An #IdeBuilder.
+ *
+ * Gets the configuration to use for the builder.
+ *
+ * Returns: (transfer none): An #IdeConfiguration.
+ */
+IdeConfiguration *
+ide_builder_get_configuration (IdeBuilder *self)
+{
+  IdeBuilderPrivate *priv = ide_builder_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_BUILDER (self), NULL);
+
+  return priv->configuration;
+}
+
+static void
+ide_builder_set_configuration (IdeBuilder       *self,
+                               IdeConfiguration *configuration)
+{
+  IdeBuilderPrivate *priv = ide_builder_get_instance_private (self);
+
+  g_assert (IDE_IS_BUILDER (self));
+  g_assert (!configuration || IDE_IS_CONFIGURATION (configuration));
+
+  if (g_set_object (&priv->configuration, configuration))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONFIGURATION]);
+}
+
+static void
+ide_builder_real_build_async (IdeBuilder            *self,
+                              IdeBuilderBuildFlags   flags,
+                              IdeBuildResult       **result,
+                              GCancellable          *cancellable,
+                              GAsyncReadyCallback    callback,
+                              gpointer               user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_BUILDER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (!result || *result == NULL);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_return_new_error (task,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           _("%s does not support building"),
+                           G_OBJECT_TYPE_NAME (self));
+}
+
+static IdeBuildResult *
+ide_builder_real_build_finish (IdeBuilder    *self,
+                               GAsyncResult  *result,
+                               GError       **error)
+{
+  g_assert (IDE_IS_BUILDER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
 
 /**
  * ide_builder_build_async:
  * @result: (out) (transfer none): A location for an #IdeBuildResult.
- *
  */
 void
 ide_builder_build_async (IdeBuilder           *builder,
@@ -36,29 +113,13 @@ ide_builder_build_async (IdeBuilder           *builder,
                          GAsyncReadyCallback   callback,
                          gpointer              user_data)
 {
-  IdeBuilderClass *klass;
-
   g_return_if_fail (IDE_IS_BUILDER (builder));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  if (result)
+  if (result != NULL)
     *result = NULL;
 
-  klass = IDE_BUILDER_GET_CLASS (builder);
-
-  if (klass->build_async)
-    {
-      klass->build_async (builder, flags, result, cancellable, callback, user_data);
-      return;
-    }
-
-  g_warning (_("%s does not implement build_async()"),
-             g_type_name (G_TYPE_FROM_INSTANCE (builder)));
-
-  g_task_report_new_error (builder, callback, user_data,
-                           ide_builder_build_async,
-                           G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
-                           _("No implementation of build_async()"));
+  IDE_BUILDER_GET_CLASS (builder)->build_async (builder, flags, result, cancellable, callback, user_data);
 }
 
 /**
@@ -73,18 +134,12 @@ ide_builder_build_finish (IdeBuilder    *builder,
                           GAsyncResult  *result,
                           GError       **error)
 {
-  IdeBuilderClass *klass;
   IdeBuildResult *ret = NULL;
 
   g_return_val_if_fail (IDE_IS_BUILDER (builder), NULL);
   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
 
-  klass = IDE_BUILDER_GET_CLASS (builder);
-
-  if (klass->build_finish)
-    ret = klass->build_finish (builder, result, error);
-  else if (G_IS_TASK (result))
-    ret = g_task_propagate_pointer (G_TASK (result), error);
+  ret = IDE_BUILDER_GET_CLASS (builder)->build_finish (builder, result, error);
 
   g_return_val_if_fail (!ret || IDE_IS_BUILD_RESULT (ret), NULL);
 
@@ -92,8 +147,74 @@ ide_builder_build_finish (IdeBuilder    *builder,
 }
 
 static void
+ide_builder_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  IdeBuilder *self = IDE_BUILDER(object);
+
+  switch (prop_id)
+    {
+    case PROP_CONFIGURATION:
+      g_value_set_object (value, ide_builder_get_configuration (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+ide_builder_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  IdeBuilder *self = IDE_BUILDER(object);
+
+  switch (prop_id)
+    {
+    case PROP_CONFIGURATION:
+      ide_builder_set_configuration (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+ide_builder_finalize (GObject *object)
+{
+  IdeBuilder *self = (IdeBuilder *)object;
+  IdeBuilderPrivate *priv = ide_builder_get_instance_private (self);
+
+  g_clear_object (&priv->configuration);
+
+  G_OBJECT_CLASS (ide_builder_parent_class)->finalize (object);
+}
+
+static void
 ide_builder_class_init (IdeBuilderClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_builder_finalize;
+  object_class->get_property = ide_builder_get_property;
+  object_class->set_property = ide_builder_set_property;
+
+  klass->build_async = ide_builder_real_build_async;
+  klass->build_finish = ide_builder_real_build_finish;
+
+  properties [PROP_CONFIGURATION] =
+    g_param_spec_object ("configuration",
+                         "Configuration",
+                         "Configuration",
+                         IDE_TYPE_CONFIGURATION,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
 }
 
 static void
diff --git a/libide/ide-builder.h b/libide/ide-builder.h
index 0232c6f..36604e3 100644
--- a/libide/ide-builder.h
+++ b/libide/ide-builder.h
@@ -29,11 +29,11 @@ G_DECLARE_DERIVABLE_TYPE (IdeBuilder, ide_builder, IDE, BUILDER, IdeObject)
 
 typedef enum
 {
-  IDE_BUILDER_BUILD_FLAGS_NONE          = 0,
-  IDE_BUILDER_BUILD_FLAGS_FORCE_REBUILD = 1 << 0,
-
-  /* TODO: this belongs as a vfunc instead */
-  IDE_BUILDER_BUILD_FLAGS_CLEAN         = 1 << 1,
+  IDE_BUILDER_BUILD_FLAGS_NONE            = 0,
+  IDE_BUILDER_BUILD_FLAGS_FORCE_BOOTSTRAP = 1 << 0,
+  IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN     = 1 << 1,
+  IDE_BUILDER_BUILD_FLAGS_NO_BUILD        = 1 << 2,
+  IDE_BUILDER_BUILD_FLAGS_NO_CONFIGURE    = 1 << 3,
 } IdeBuilderBuildFlags;
 
 struct _IdeBuilderClass
@@ -51,15 +51,16 @@ struct _IdeBuilderClass
                                    GError              **error);
 };
 
-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           *builder,
+                                                IdeBuilderBuildFlags   flags,
+                                                IdeBuildResult       **result,
+                                                GCancellable          *cancellable,
+                                                GAsyncReadyCallback    callback,
+                                                gpointer               user_data);
+IdeBuildResult   *ide_builder_build_finish     (IdeBuilder            *builder,
+                                                GAsyncResult          *result,
+                                                GError               **error);
 
 G_END_DECLS
 
diff --git a/libide/ide-configuration.c b/libide/ide-configuration.c
new file mode 100644
index 0000000..58e28ab
--- /dev/null
+++ b/libide/ide-configuration.c
@@ -0,0 +1,368 @@
+/* ide-configuration.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 <string.h>
+
+#include "ide-configuration.h"
+#include "ide-device.h"
+#include "ide-runtime.h"
+
+struct _IdeConfiguration
+{
+  GObject     parent_instance;
+
+  gchar      *id;
+  IdeDevice  *device;
+  GPtrArray  *environ;
+  IdeRuntime *runtime;
+};
+
+G_DEFINE_TYPE (IdeConfiguration, ide_configuration, IDE_TYPE_OBJECT)
+
+static void ide_configuration_set_device  (IdeConfiguration *self,
+                                           IdeDevice        *device);
+static void ide_configuration_set_runtime (IdeConfiguration *self,
+                                           IdeRuntime       *runtime);
+static void ide_configuration_set_id      (IdeConfiguration *self,
+                                           const gchar      *id);
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  PROP_ENVIRON,
+  PROP_ID,
+  PROP_RUNTIME,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_configuration_constructed (GObject *object)
+{
+  IdeConfiguration *self = (IdeConfiguration *)object;
+
+  G_OBJECT_CLASS (ide_configuration_parent_class)->constructed (object);
+
+  if (self->device != NULL)
+    ide_device_prepare_configuration (self->device, self);
+
+  if (self->runtime != NULL)
+    ide_runtime_prepare_configuration (self->runtime, self);
+}
+
+static void
+ide_configuration_finalize (GObject *object)
+{
+  IdeConfiguration *self = (IdeConfiguration *)object;
+
+  g_clear_object (&self->device);
+  g_clear_pointer (&self->environ, g_ptr_array_unref);
+  g_clear_pointer (&self->id, g_free);
+  g_clear_object (&self->runtime);
+
+  G_OBJECT_CLASS (ide_configuration_parent_class)->finalize (object);
+}
+
+static void
+ide_configuration_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeConfiguration *self = IDE_CONFIGURATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      g_value_set_object (value, ide_configuration_get_device (self));
+      break;
+
+    case PROP_ENVIRON:
+      g_value_set_boxed (value, ide_configuration_get_environ (self));
+      break;
+
+    case PROP_ID:
+      g_value_set_string (value, ide_configuration_get_id (self));
+      break;
+
+    case PROP_RUNTIME:
+      g_value_set_object (value, ide_configuration_get_runtime (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_configuration_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  IdeConfiguration *self = IDE_CONFIGURATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      ide_configuration_set_device (self, g_value_get_object (value));
+      break;
+
+    case PROP_ID:
+      ide_configuration_set_id (self, g_value_get_string (value));
+      break;
+
+    case PROP_RUNTIME:
+      ide_configuration_set_runtime (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_configuration_class_init (IdeConfigurationClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_configuration_constructed;
+  object_class->finalize = ide_configuration_finalize;
+  object_class->get_property = ide_configuration_get_property;
+  object_class->set_property = ide_configuration_set_property;
+
+  properties [PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "Device",
+                         IDE_TYPE_DEVICE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ENVIRON] =
+    g_param_spec_boxed ("environ",
+                        "Environ",
+                        "Environ",
+                        G_TYPE_STRV,
+                        (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "Id",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_RUNTIME] =
+    g_param_spec_object ("runtime",
+                         "Runtime",
+                         "Runtime",
+                         IDE_TYPE_RUNTIME,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_configuration_init (IdeConfiguration *self)
+{
+  self->environ = g_ptr_array_new_with_free_func (g_free);
+  g_ptr_array_add (self->environ, NULL);
+}
+
+IdeConfiguration *
+ide_configuration_new (const gchar *id,
+                       IdeDevice   *device,
+                       IdeRuntime  *runtime)
+{
+  g_return_val_if_fail (id != NULL, NULL);
+  g_return_val_if_fail (IDE_IS_DEVICE (device), NULL);
+  g_return_val_if_fail (IDE_IS_RUNTIME (runtime), NULL);
+
+  return g_object_new (IDE_TYPE_CONFIGURATION,
+                       "device", device,
+                       "id", id,
+                       "runtime", runtime,
+                       NULL);
+}
+
+/**
+ * ide_configuration_get_device:
+ * @self: An #IdeConfiguration
+ *
+ * Gets the device for the configuration.
+ *
+ * Returns: (transfer none): An #IdeDevice.
+ */
+IdeDevice *
+ide_configuration_get_device (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->device;
+}
+
+static void
+ide_configuration_set_device (IdeConfiguration *self,
+                              IdeDevice        *device)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (IDE_IS_DEVICE (device));
+
+  g_set_object (&self->device, device);
+}
+
+/**
+ * ide_configuration_get_runtime:
+ * @self: An #IdeConfiguration
+ *
+ * Gets the runtime for the configuration.
+ *
+ * Returns: (transfer none): An #ideRuntime
+ */
+IdeRuntime *
+ide_configuration_get_runtime (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->runtime;
+}
+
+static void
+ide_configuration_set_runtime (IdeConfiguration *self,
+                               IdeRuntime       *runtime)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (IDE_IS_RUNTIME (runtime));
+
+  g_set_object (&self->runtime, runtime);
+}
+
+const gchar * const *
+ide_configuration_get_environ (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return (const gchar * const *)self->environ->pdata;
+}
+
+static gint
+ide_configuration_find_env (IdeConfiguration *self,
+                            const gchar      *key)
+{
+  g_assert (IDE_IS_CONFIGURATION (self));
+  g_assert (key != NULL);
+
+  for (gint i = 0; i < self->environ->len; i++)
+    {
+      const gchar *pair = g_ptr_array_index (self->environ, i);
+      const gchar *eq;
+
+      if (pair == NULL)
+        break;
+
+      if (NULL == (eq = strchr (pair, '=')))
+        {
+          if (strcmp (pair, key) == 0)
+            return i;
+          return -1;
+        }
+
+      if (strncmp (eq, key, (eq - pair)) == 0)
+        return i;
+    }
+
+  return -1;
+}
+
+const gchar *
+ide_configuration_getenv (IdeConfiguration *self,
+                          const gchar      *key)
+{
+  gint i;
+
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  i = ide_configuration_find_env (self, key);
+
+  if (i >= 0)
+    {
+      const gchar *pair = g_ptr_array_index (self->environ, i);
+      const gchar *eq = strchr (pair, '=');
+
+      if (eq != NULL)
+        return eq + 1;
+    }
+
+  return NULL;
+}
+
+void
+ide_configuration_setenv (IdeConfiguration *self,
+                          const gchar      *key,
+                          const gchar      *value)
+{
+  gint i;
+
+  g_assert (IDE_IS_CONFIGURATION (self));
+  g_assert (key != NULL);
+
+  i = ide_configuration_find_env (self, key);
+
+  if (i >= 0)
+    {
+      if (value == NULL)
+        {
+          g_ptr_array_remove_index (self->environ, i);
+        }
+      else
+        {
+          g_free (g_ptr_array_index (self->environ, i));
+          g_ptr_array_index (self->environ, i) = g_strdup (value);
+        }
+    }
+  else
+    {
+      g_ptr_array_index (self->environ, self->environ->len) = g_strdup_printf ("%s=%s", key, value);
+      g_ptr_array_add (self->environ, NULL);
+    }
+}
+
+const gchar *
+ide_configuration_get_id (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->id;
+}
+
+static void
+ide_configuration_set_id (IdeConfiguration *self,
+                          const gchar      *id)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (id != NULL);
+
+  if (g_strcmp0 (id, self->id) != 0)
+    {
+      g_free (self->id);
+      self->id = g_strdup (id);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ID]);
+    }
+}
diff --git a/libide/ide-configuration.h b/libide/ide-configuration.h
new file mode 100644
index 0000000..2953cf1
--- /dev/null
+++ b/libide/ide-configuration.h
@@ -0,0 +1,51 @@
+/* ide-configuration.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_CONFIGURATION_H
+#define IDE_CONFIGURATION_H
+
+#include <gio/gio.h>
+
+#include "ide-object.h"
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CONFIGURATION (ide_configuration_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeConfiguration, ide_configuration, IDE, CONFIGURATION, IdeObject)
+
+IdeConfiguration    *ide_configuration_new              (const gchar       *id,
+                                                         IdeDevice         *device,
+                                                         IdeRuntime        *runtime);
+const gchar         *ide_configuration_get_id           (IdeConfiguration  *self);
+const gchar         *ide_configuration_get_display_name (IdeConfiguration  *self);
+void                 ide_configuration_set_display_name (IdeConfiguration  *self,
+                                                         const gchar       *display_name);
+IdeRuntime          *ide_configuration_get_runtime      (IdeConfiguration  *self);
+IdeDevice           *ide_configuration_get_device       (IdeConfiguration  *self);
+const gchar * const *ide_configuration_get_environ      (IdeConfiguration  *self);
+const gchar         *ide_configuration_getenv           (IdeConfiguration  *self,
+                                                         const gchar       *key);
+void                 ide_configuration_setenv           (IdeConfiguration  *self,
+                                                         const gchar       *key,
+                                                         const gchar       *value);
+
+G_END_DECLS
+
+#endif /* IDE_CONFIGURATION_H */
diff --git a/libide/ide-context.c b/libide/ide-context.c
index 5196892..8f1b673 100644
--- a/libide/ide-context.c
+++ b/libide/ide-context.c
@@ -35,6 +35,7 @@
 #include "ide-project.h"
 #include "ide-project-item.h"
 #include "ide-project-files.h"
+#include "ide-runtime-manager.h"
 #include "ide-script-manager.h"
 #include "ide-search-engine.h"
 #include "ide-search-provider.h"
@@ -60,6 +61,7 @@ struct _IdeContext
   IdeDeviceManager         *device_manager;
   IdeDoap                  *doap;
   GtkRecentManager         *recent_manager;
+  IdeRuntimeManager        *runtime_manager;
   IdeScriptManager         *script_manager;
   IdeSearchEngine          *search_engine;
   IdeSourceSnippetsManager *snippets_manager;
@@ -95,6 +97,7 @@ enum {
   PROP_PROJECT_FILE,
   PROP_PROJECT,
   PROP_ROOT_BUILD_DIR,
+  PROP_RUNTIME_MANAGER,
   PROP_SCRIPT_MANAGER,
   PROP_SEARCH_ENGINE,
   PROP_SNIPPETS_MANAGER,
@@ -536,6 +539,7 @@ ide_context_finalize (GObject *object)
   g_clear_object (&self->project);
   g_clear_object (&self->project_file);
   g_clear_object (&self->recent_manager);
+  g_clear_object (&self->runtime_manager);
   g_clear_object (&self->unsaved_files);
   g_clear_object (&self->vcs);
 
@@ -586,6 +590,10 @@ ide_context_get_property (GObject    *object,
       g_value_set_string (value, ide_context_get_root_build_dir (self));
       break;
 
+    case PROP_RUNTIME_MANAGER:
+      g_value_set_object (value, ide_context_get_runtime_manager (self));
+      break;
+
     case PROP_SCRIPT_MANAGER:
       g_value_set_object (value, ide_context_get_script_manager (self));
       break;
@@ -695,6 +703,13 @@ ide_context_class_init (IdeContextClass *klass)
                          NULL,
                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
+  properties [PROP_RUNTIME_MANAGER] =
+    g_param_spec_object ("runtime-manager",
+                         "Runtime Manager",
+                         "Runtime Manager",
+                         IDE_TYPE_RUNTIME_MANAGER,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
   properties [PROP_SCRIPT_MANAGER] =
     g_param_spec_object ("script-manager",
                          "Script Manager",
@@ -786,6 +801,10 @@ ide_context_init (IdeContext *self)
                                 "context", self,
                                 NULL);
 
+  self->runtime_manager = g_object_new (IDE_TYPE_RUNTIME_MANAGER,
+                                        "context", self,
+                                        NULL);
+
   self->unsaved_files = g_object_new (IDE_TYPE_UNSAVED_FILES,
                                       "context", self,
                                       NULL);
@@ -1618,6 +1637,9 @@ ide_context_do_unload_locked (IdeContext *self)
   task = self->delayed_unload_task;
   self->delayed_unload_task = NULL;
 
+  g_clear_object (&self->device_manager);
+  g_clear_object (&self->runtime_manager);
+
   ide_async_helper_run (self,
                         g_task_get_cancellable (task),
                         ide_context_unload_cb,
@@ -1915,3 +1937,23 @@ ide_context_release (IdeContext *self)
 
   g_object_unref (self);
 }
+
+/**
+ * ide_context_get_runtime_manager:
+ * @self: An #IdeContext
+ *
+ * Gets the #IdeRuntimeManager for the LibIDE context.
+ *
+ * The runtime manager provies access to #IdeRuntime instances via the
+ * #GListModel interface. These can provide support for building projects
+ * in various runtimes such as xdg-app.
+ *
+ * Returns: (transfer none): An #IdeRuntimeManager.
+ */
+IdeRuntimeManager *
+ide_context_get_runtime_manager (IdeContext *self)
+{
+  g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
+
+  return self->runtime_manager;
+}
diff --git a/libide/ide-context.h b/libide/ide-context.h
index ef249ee..d1c6604 100644
--- a/libide/ide-context.h
+++ b/libide/ide-context.h
@@ -37,6 +37,7 @@ IdeBuildSystem           *ide_context_get_build_system      (IdeContext
 IdeDeviceManager         *ide_context_get_device_manager    (IdeContext           *self);
 IdeProject               *ide_context_get_project           (IdeContext           *self);
 GtkRecentManager         *ide_context_get_recent_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);
 IdeSettings              *ide_context_get_settings          (IdeContext           *self,
diff --git a/libide/ide-device-manager.c b/libide/ide-device-manager.c
index 58a952b..8df4f56 100644
--- a/libide/ide-device-manager.c
+++ b/libide/ide-device-manager.c
@@ -128,6 +128,9 @@ ide_device_manager__provider_device_removed (IdeDeviceManager  *self,
   g_return_if_fail (IDE_IS_DEVICE (device));
   g_return_if_fail (IDE_IS_DEVICE_PROVIDER (provider));
 
+  if (self->devices == NULL)
+    return;
+
   for (i = 0; i < self->devices->len; i++)
     {
       IdeDevice *current = g_ptr_array_index (self->devices, i);
diff --git a/libide/ide-device.c b/libide/ide-device.c
index 897a658..13d67be 100644
--- a/libide/ide-device.c
+++ b/libide/ide-device.c
@@ -18,6 +18,7 @@
 
 #include <glib/gi18n.h>
 
+#include "ide-configuration.h"
 #include "ide-device.h"
 
 typedef struct
@@ -39,27 +40,6 @@ enum {
 static GParamSpec *properties [LAST_PROP];
 
 /**
- * ide_device_get_config:
- * @device: A #IdeDevice.
- *
- * Retrieves any custom configuration that is required to build for the
- * device. Such values might include additional options to autoconf
- * or paths to cross-compilers.
- *
- * Returns: (transfer none) (nullable): A #GKeyFile or %NULL.
- */
-GKeyFile *
-ide_device_get_config (IdeDevice *device)
-{
-  g_return_val_if_fail (IDE_IS_DEVICE (device), NULL);
-
-  if (IDE_DEVICE_GET_CLASS (device)->get_config)
-    return IDE_DEVICE_GET_CLASS (device)->get_config (device);
-
-  return NULL;
-}
-
-/**
  * ide_device_get_display_name:
  *
  * This function returns the name of the device. If no name has been set, then
@@ -257,3 +237,14 @@ static void
 ide_device_init (IdeDevice *self)
 {
 }
+
+void
+ide_device_prepare_configuration (IdeDevice        *self,
+                                  IdeConfiguration *configuration)
+{
+  g_assert (IDE_IS_DEVICE (self));
+  g_assert (IDE_IS_CONFIGURATION (configuration));
+
+  if (IDE_DEVICE_GET_CLASS (self)->prepare_configuration)
+    IDE_DEVICE_GET_CLASS (self)->prepare_configuration (self, configuration);
+}
diff --git a/libide/ide-device.h b/libide/ide-device.h
index f9bfd11..484e655 100644
--- a/libide/ide-device.h
+++ b/libide/ide-device.h
@@ -31,18 +31,20 @@ struct _IdeDeviceClass
 {
   IdeObjectClass parent;
 
-  GKeyFile    *(*get_config)      (IdeDevice *device);
-  const gchar *(*get_system_type) (IdeDevice *device);
+  const gchar *(*get_system_type)       (IdeDevice        *self);
+  void         (*prepare_configuration) (IdeDevice        *self,
+                                         IdeConfiguration *configuration);
 };
 
-GKeyFile    *ide_device_get_config       (IdeDevice   *device);
-const gchar *ide_device_get_display_name (IdeDevice   *device);
-void         ide_device_set_display_name (IdeDevice   *device,
-                                          const gchar *display_name);
-const gchar *ide_device_get_id           (IdeDevice   *device);
-void         ide_device_set_id           (IdeDevice   *device,
-                                          const gchar *id);
-const gchar *ide_device_get_system_type  (IdeDevice   *device);
+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);
 
 G_END_DECLS
 
diff --git a/libide/ide-internal.h b/libide/ide-internal.h
index c9c5c9b..c040fde 100644
--- a/libide/ide-internal.h
+++ b/libide/ide-internal.h
@@ -59,6 +59,7 @@ IdeFixit           *_ide_fixit_new                          (IdeSourceRange
                                                              const gchar           *replacement_text);
 void                _ide_project_set_name                   (IdeProject            *project,
                                                              const gchar           *name);
+void                _ide_runtime_manager_unload             (IdeRuntimeManager     *self);
 void                _ide_search_context_add_provider        (IdeSearchContext      *context,
                                                              IdeSearchProvider     *provider,
                                                              gsize                  max_results);
diff --git a/libide/ide-runtime-manager.c b/libide/ide-runtime-manager.c
new file mode 100644
index 0000000..6554452
--- /dev/null
+++ b/libide/ide-runtime-manager.c
@@ -0,0 +1,239 @@
+/* ide-runtime-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-runtime-manager"
+
+#include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
+#include "ide-runtime-provider.h"
+
+struct _IdeRuntimeManager
+{
+  IdeObject         parent_instance;
+  PeasExtensionSet *extensions;
+  GPtrArray        *runtimes;
+};
+
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeRuntimeManager, ide_runtime_manager, IDE_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static void
+ide_runtime_manager_extension_added (PeasExtensionSet *set,
+                                     PeasPluginInfo   *plugin_info,
+                                     PeasExtension    *exten,
+                                     gpointer          user_data)
+{
+  IdeRuntimeManager *self = user_data;
+  IdeRuntimeProvider *provider = (IdeRuntimeProvider *)exten;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUNTIME_PROVIDER (provider));
+
+  ide_runtime_provider_load (provider, self);
+}
+
+static void
+ide_runtime_manager_extension_removed (PeasExtensionSet *set,
+                                       PeasPluginInfo   *plugin_info,
+                                       PeasExtension    *exten,
+                                       gpointer          user_data)
+{
+  IdeRuntimeManager *self = user_data;
+  IdeRuntimeProvider *provider = (IdeRuntimeProvider *)exten;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_RUNTIME_PROVIDER (provider));
+
+  ide_runtime_provider_unload (provider, self);
+}
+
+static void
+ide_runtime_manager_constructed (GObject *object)
+{
+  IdeRuntimeManager *self = (IdeRuntimeManager *)object;
+  IdeContext *context;
+
+  G_OBJECT_CLASS (ide_runtime_manager_parent_class)->constructed (object);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  self->extensions = peas_extension_set_new (peas_engine_get_default (),
+                                             IDE_TYPE_RUNTIME_PROVIDER,
+                                             NULL);
+
+  g_signal_connect (self->extensions,
+                    "extension-added",
+                    G_CALLBACK (ide_runtime_manager_extension_added),
+                    self);
+
+  g_signal_connect (self->extensions,
+                    "extension-removed",
+                    G_CALLBACK (ide_runtime_manager_extension_removed),
+                    self);
+
+  peas_extension_set_foreach (self->extensions,
+                              ide_runtime_manager_extension_added,
+                              self);
+
+  ide_runtime_manager_add (self, ide_runtime_new (context, "system", _("Host")));
+}
+
+void
+_ide_runtime_manager_unload (IdeRuntimeManager *self)
+{
+  g_return_if_fail (IDE_IS_RUNTIME_MANAGER (self));
+
+  g_clear_object (&self->extensions);
+}
+
+static void
+ide_runtime_manager_dispose (GObject *object)
+{
+  IdeRuntimeManager *self = (IdeRuntimeManager *)object;
+
+  g_clear_object (&self->extensions);
+  g_clear_pointer (&self->runtimes, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (ide_runtime_manager_parent_class)->dispose (object);
+}
+
+static void
+ide_runtime_manager_class_init (IdeRuntimeManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_runtime_manager_constructed;
+  object_class->dispose = ide_runtime_manager_dispose;
+}
+
+static void
+ide_runtime_manager_init (IdeRuntimeManager *self)
+{
+  self->runtimes = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static GType
+ide_runtime_manager_get_item_type (GListModel *model)
+{
+  return IDE_TYPE_RUNTIME;
+}
+
+static guint
+ide_runtime_manager_get_n_items (GListModel *model)
+{
+  IdeRuntimeManager *self = (IdeRuntimeManager *)model;
+
+  g_return_val_if_fail (IDE_IS_RUNTIME_MANAGER (self), 0);
+
+  return self->runtimes->len;
+}
+
+static gpointer
+ide_runtime_manager_get_item (GListModel *model,
+                              guint       position)
+{
+  IdeRuntimeManager *self = (IdeRuntimeManager *)model;
+
+  g_return_val_if_fail (IDE_IS_RUNTIME_MANAGER (self), NULL);
+  g_return_val_if_fail (position < self->runtimes->len, NULL);
+
+  return g_object_ref (g_ptr_array_index (self->runtimes, position));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = ide_runtime_manager_get_item_type;
+  iface->get_n_items = ide_runtime_manager_get_n_items;
+  iface->get_item = ide_runtime_manager_get_item;
+}
+
+void
+ide_runtime_manager_add (IdeRuntimeManager *self,
+                         IdeRuntime        *runtime)
+{
+  guint idx;
+
+  g_return_if_fail (IDE_IS_RUNTIME_MANAGER (self));
+  g_return_if_fail (IDE_IS_RUNTIME (runtime));
+
+  idx = self->runtimes->len;
+  g_ptr_array_add (self->runtimes, g_object_ref (runtime));
+  g_list_model_items_changed (G_LIST_MODEL (self), idx, 0, 1);
+}
+
+void
+ide_runtime_manager_remove (IdeRuntimeManager *self,
+                            IdeRuntime        *runtime)
+{
+  guint i;
+
+  g_return_if_fail (IDE_IS_RUNTIME_MANAGER (self));
+  g_return_if_fail (IDE_IS_RUNTIME (runtime));
+
+  for (i = 0; i < self->runtimes->len; i++)
+    {
+      IdeRuntime *item = g_ptr_array_index (self->runtimes, i);
+
+      if (runtime == item)
+        {
+          g_ptr_array_remove_index (self->runtimes, i);
+          g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0);
+          break;
+        }
+    }
+}
+
+/**
+ * ide_runtime_manager_get_runtime:
+ * @self: An #IdeRuntimeManager
+ * @id: the identifier of the runtime
+ *
+ * Gets the runtime by it's internal identifier.
+ *
+ * Returns: (transfer none): An #IdeRuntime.
+ */
+IdeRuntime *
+ide_runtime_manager_get_runtime (IdeRuntimeManager *self,
+                                 const gchar       *id)
+{
+  guint i;
+
+  g_return_val_if_fail (IDE_IS_RUNTIME_MANAGER (self), NULL);
+  g_return_val_if_fail (id != NULL, NULL);
+
+  for (i = 0; i < self->runtimes->len; i++)
+    {
+      IdeRuntime *runtime = g_ptr_array_index (self->runtimes, i);
+      const gchar *runtime_id;
+
+      runtime_id = ide_runtime_get_id (runtime);
+
+      if (g_strcmp0 (runtime_id, id) == 0)
+        return runtime;
+    }
+
+  return NULL;
+}
diff --git a/libide/ide-runtime-manager.h b/libide/ide-runtime-manager.h
new file mode 100644
index 0000000..f7fd591
--- /dev/null
+++ b/libide/ide-runtime-manager.h
@@ -0,0 +1,39 @@
+/* ide-runtime-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_RUNTIME_MANAGER_H
+#define IDE_RUNTIME_MANAGER_H
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUNTIME_MANAGER (ide_runtime_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeRuntimeManager, ide_runtime_manager, IDE, RUNTIME_MANAGER, IdeObject)
+
+IdeRuntime *ide_runtime_manager_get_runtime (IdeRuntimeManager *self,
+                                             const gchar       *id);
+void        ide_runtime_manager_add         (IdeRuntimeManager *self,
+                                             IdeRuntime        *runtime);
+void        ide_runtime_manager_remove      (IdeRuntimeManager *self,
+                                             IdeRuntime        *runtime);
+
+G_END_DECLS
+
+#endif /* IDE_RUNTIME_MANAGER_H */
diff --git a/libide/ide-runtime-provider.c b/libide/ide-runtime-provider.c
new file mode 100644
index 0000000..41483e6
--- /dev/null
+++ b/libide/ide-runtime-provider.c
@@ -0,0 +1,61 @@
+/* ide-runtime-provider.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-runtime-manager.h"
+#include "ide-runtime-provider.h"
+
+G_DEFINE_INTERFACE (IdeRuntimeProvider, ide_runtime_provider, G_TYPE_OBJECT)
+
+static void
+ide_runtime_provider_real_load (IdeRuntimeProvider *self,
+                                IdeRuntimeManager  *manager)
+{
+}
+
+static void
+ide_runtime_provider_real_unload (IdeRuntimeProvider *self,
+                                  IdeRuntimeManager  *manager)
+{
+}
+
+static void
+ide_runtime_provider_default_init (IdeRuntimeProviderInterface *iface)
+{
+  iface->load = ide_runtime_provider_real_load;
+  iface->unload = ide_runtime_provider_real_unload;
+}
+
+void
+ide_runtime_provider_load (IdeRuntimeProvider *self,
+                           IdeRuntimeManager  *manager)
+{
+  g_return_if_fail (IDE_IS_RUNTIME_PROVIDER (self));
+  g_return_if_fail (IDE_IS_RUNTIME_MANAGER (manager));
+
+  IDE_RUNTIME_PROVIDER_GET_IFACE (self)->load (self, manager);
+}
+
+void
+ide_runtime_provider_unload (IdeRuntimeProvider *self,
+                             IdeRuntimeManager  *manager)
+{
+  g_return_if_fail (IDE_IS_RUNTIME_PROVIDER (self));
+  g_return_if_fail (IDE_IS_RUNTIME_MANAGER (manager));
+
+  IDE_RUNTIME_PROVIDER_GET_IFACE (self)->unload (self, manager);
+}
diff --git a/libide/ide-runtime-provider.h b/libide/ide-runtime-provider.h
new file mode 100644
index 0000000..15098d9
--- /dev/null
+++ b/libide/ide-runtime-provider.h
@@ -0,0 +1,49 @@
+/* ide-runtime-provider.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_RUNTIME_PROVIDER_H
+#define IDE_RUNTIME_PROVIDER_H
+
+#include <gio/gio.h>
+
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUNTIME_PROVIDER (ide_runtime_provider_get_type ())
+
+G_DECLARE_INTERFACE (IdeRuntimeProvider, ide_runtime_provider, IDE, RUNTIME_PROVIDER, GObject)
+
+struct _IdeRuntimeProviderInterface
+{
+  GTypeInterface parent;
+
+  void   (*load)         (IdeRuntimeProvider *self,
+                          IdeRuntimeManager  *manager);
+  void   (*unload)       (IdeRuntimeProvider *self,
+                          IdeRuntimeManager  *manager);
+};
+
+void ide_runtime_provider_load   (IdeRuntimeProvider *self,
+                                  IdeRuntimeManager  *manager);
+void ide_runtime_provider_unload (IdeRuntimeProvider *self,
+                                  IdeRuntimeManager  *manager);
+
+G_END_DECLS
+
+#endif /* IDE_RUNTIME_PROVIDER_H */
diff --git a/libide/ide-runtime.c b/libide/ide-runtime.c
new file mode 100644
index 0000000..e664460
--- /dev/null
+++ b/libide/ide-runtime.c
@@ -0,0 +1,367 @@
+/* ide-runtime.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-runtime"
+
+#include "ide-builder.h"
+#include "ide-configuration.h"
+#include "ide-runtime.h"
+
+typedef struct
+{
+  gchar *id;
+  gchar *title;
+} IdeRuntimePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeRuntime, ide_runtime, IDE_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_ID,
+  PROP_TITLE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_runtime_real_prebuild_async (IdeRuntime          *self,
+                                 GCancellable        *cancellable,
+                                 GAsyncReadyCallback  callback,
+                                 gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_RUNTIME (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+ide_runtime_real_prebuild_finish (IdeRuntime    *self,
+                                  GAsyncResult  *result,
+                                  GError       **error)
+{
+  g_assert (IDE_IS_RUNTIME (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_runtime_real_postbuild_async (IdeRuntime          *self,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_RUNTIME (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+ide_runtime_real_postbuild_finish (IdeRuntime    *self,
+                                   GAsyncResult  *result,
+                                   GError       **error)
+{
+  g_assert (IDE_IS_RUNTIME (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static IdeSubprocessLauncher *
+ide_runtime_real_create_launcher (IdeRuntime  *self,
+                                  GError     **error)
+{
+  return ide_subprocess_launcher_new (0);
+}
+
+static gboolean
+ide_runtime_real_contains_program_in_path (IdeRuntime   *self,
+                                           const gchar  *program,
+                                           GCancellable *cancellable)
+{
+  gchar *path;
+  gboolean ret;
+
+  g_assert (IDE_IS_RUNTIME (self));
+  g_assert (program != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  path = g_find_program_in_path (program);
+  ret = path != NULL;
+  g_free (path);
+
+  return ret;
+}
+
+gboolean
+ide_runtime_contains_program_in_path (IdeRuntime   *self,
+                                      const gchar  *program,
+                                      GCancellable *cancellable)
+{
+  g_return_val_if_fail (IDE_IS_RUNTIME (self), FALSE);
+  g_return_val_if_fail (program != NULL, FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  return IDE_RUNTIME_GET_CLASS (self)->contains_program_in_path (self, program, cancellable);
+}
+
+static void
+ide_runtime_finalize (GObject *object)
+{
+  IdeRuntime *self = (IdeRuntime *)object;
+  IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+  g_clear_pointer (&priv->title, g_free);
+
+  G_OBJECT_CLASS (ide_runtime_parent_class)->finalize (object);
+}
+
+static void
+ide_runtime_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  IdeRuntime *self = IDE_RUNTIME (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      g_value_set_string (value, ide_runtime_get_id (self));
+      break;
+
+    case PROP_TITLE:
+      g_value_set_string (value, ide_runtime_get_title (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_runtime_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  IdeRuntime *self = IDE_RUNTIME (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      ide_runtime_set_id (self, g_value_get_string (value));
+      break;
+
+    case PROP_TITLE:
+      ide_runtime_set_title (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_runtime_class_init (IdeRuntimeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_runtime_finalize;
+  object_class->get_property = ide_runtime_get_property;
+  object_class->set_property = ide_runtime_set_property;
+
+  klass->prebuild_async = ide_runtime_real_prebuild_async;
+  klass->prebuild_finish = ide_runtime_real_prebuild_finish;
+  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->contains_program_in_path = ide_runtime_real_contains_program_in_path;
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "The runtime identifier",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "Title",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_runtime_init (IdeRuntime *self)
+{
+}
+
+const gchar *
+ide_runtime_get_id (IdeRuntime  *self)
+{
+  IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_RUNTIME (self), NULL);
+
+  return priv->id;
+}
+
+void
+ide_runtime_set_id (IdeRuntime  *self,
+                    const gchar *id)
+{
+  IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RUNTIME (self));
+  g_return_if_fail (id != NULL);
+
+  if (0 != g_strcmp0 (id, priv->id))
+    {
+      g_free (priv->id);
+      priv->id = g_strdup (id);
+    }
+}
+
+const gchar *
+ide_runtime_get_title (IdeRuntime *self)
+{
+  IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_RUNTIME (self), NULL);
+
+  return priv->title;
+}
+
+void
+ide_runtime_set_title (IdeRuntime  *self,
+                       const gchar *title)
+{
+  IdeRuntimePrivate *priv = ide_runtime_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RUNTIME (self));
+  g_return_if_fail (title != NULL);
+
+  if (g_strcmp0 (title, priv->title) != 0)
+    {
+      g_free (priv->title);
+      priv->title = g_strdup (title);
+    }
+}
+
+IdeRuntime *
+ide_runtime_new (IdeContext  *context,
+                 const gchar *id,
+                 const gchar *title)
+{
+  return g_object_new (IDE_TYPE_RUNTIME,
+                       "context", context,
+                       "id", id,
+                       "title", title,
+                       NULL);
+}
+
+void
+ide_runtime_prebuild_async (IdeRuntime          *self,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_RUNTIME (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_RUNTIME_GET_CLASS (self)->prebuild_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runtime_prebuild_finish (IdeRuntime    *self,
+                             GAsyncResult  *result,
+                             GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNTIME (self), FALSE);
+
+  return IDE_RUNTIME_GET_CLASS (self)->prebuild_finish (self, result, error);
+}
+
+void
+ide_runtime_postbuild_async (IdeRuntime          *self,
+                             GCancellable        *cancellable,
+                             GAsyncReadyCallback  callback,
+                             gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_RUNTIME (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_RUNTIME_GET_CLASS (self)->postbuild_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_runtime_postbuild_finish (IdeRuntime    *self,
+                              GAsyncResult  *result,
+                              GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNTIME (self), FALSE);
+
+  return IDE_RUNTIME_GET_CLASS (self)->postbuild_finish (self, result, error);
+}
+
+/**
+ * ide_runtime_create_launcher:
+ *
+ * Creates a launcher for the runtime.
+ *
+ * This can be used to execute a command within a runtime.
+ * If you are doing a build, you probably want to ensure you call
+ * ide_runtime_prebuild_async() before using the launcher.
+ *
+ * It is important that this function can be run from a thread without
+ * side effects.
+ *
+ * Returns: (transfer full): An #IdeSubprocessLauncher or %NULL upon failure.
+ */
+IdeSubprocessLauncher *
+ide_runtime_create_launcher (IdeRuntime  *self,
+                             GError     **error)
+{
+  g_return_val_if_fail (IDE_IS_RUNTIME (self), NULL);
+
+  return IDE_RUNTIME_GET_CLASS (self)->create_launcher (self, error);
+}
+
+void
+ide_runtime_prepare_configuration (IdeRuntime       *self,
+                                   IdeConfiguration *configuration)
+{
+  g_return_if_fail (IDE_IS_RUNTIME (self));
+  g_return_if_fail (IDE_IS_CONFIGURATION (configuration));
+
+  if (IDE_RUNTIME_GET_CLASS (self)->prepare_configuration)
+    IDE_RUNTIME_GET_CLASS (self)->prepare_configuration (self, configuration);
+}
diff --git a/libide/ide-runtime.h b/libide/ide-runtime.h
new file mode 100644
index 0000000..4c2d3f3
--- /dev/null
+++ b/libide/ide-runtime.h
@@ -0,0 +1,93 @@
+/* ide-runtime.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_RUNTIME_H
+#define IDE_RUNTIME_H
+
+#include <gio/gio.h>
+
+#include "ide-object.h"
+#include "ide-subprocess-launcher.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUNTIME (ide_runtime_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeRuntime, ide_runtime, IDE, RUNTIME, IdeObject)
+
+struct _IdeRuntimeClass
+{
+  IdeObjectClass parent;
+
+  void                   (*prebuild_async)           (IdeRuntime           *self,
+                                                      GCancellable         *cancellable,
+                                                      GAsyncReadyCallback   callback,
+                                                      gpointer              user_data);
+  gboolean               (*prebuild_finish)          (IdeRuntime           *self,
+                                                      GAsyncResult         *result,
+                                                      GError              **error);
+  void                   (*postbuild_async)          (IdeRuntime           *self,
+                                                      GCancellable         *cancellable,
+                                                      GAsyncReadyCallback   callback,
+                                                      gpointer              user_data);
+  gboolean               (*postbuild_finish)         (IdeRuntime           *self,
+                                                      GAsyncResult         *result,
+                                                      GError              **error);
+  gboolean               (*contains_program_in_path) (IdeRuntime           *self,
+                                                      const gchar          *program,
+                                                      GCancellable         *cancellable);
+  IdeSubprocessLauncher *(*create_launcher)          (IdeRuntime           *self,
+                                                      GError              **error);
+  void                   (*prepare_configuration)    (IdeRuntime           *self,
+                                                      IdeConfiguration     *configuration);
+};
+
+void                   ide_runtime_prebuild_async           (IdeRuntime           *self,
+                                                             GCancellable         *cancellable,
+                                                             GAsyncReadyCallback   callback,
+                                                             gpointer              user_data);
+gboolean               ide_runtime_prebuild_finish          (IdeRuntime           *self,
+                                                             GAsyncResult         *result,
+                                                             GError              **error);
+void                   ide_runtime_postbuild_async          (IdeRuntime           *self,
+                                                             GCancellable         *cancellable,
+                                                             GAsyncReadyCallback   callback,
+                                                             gpointer              user_data);
+gboolean               ide_runtime_postbuild_finish         (IdeRuntime           *self,
+                                                             GAsyncResult         *result,
+                                                             GError              **error);
+gboolean               ide_runtime_contains_program_in_path (IdeRuntime           *self,
+                                                             const gchar          *program,
+                                                             GCancellable         *cancellable);
+IdeSubprocessLauncher *ide_runtime_create_launcher          (IdeRuntime           *self,
+                                                             GError              **error);
+void                   ide_runtime_prepare_configuration    (IdeRuntime           *self,
+                                                             IdeConfiguration     *configuration);
+IdeRuntime            *ide_runtime_new                      (IdeContext           *context,
+                                                             const gchar          *id,
+                                                             const gchar          *title);
+const gchar           *ide_runtime_get_id                   (IdeRuntime           *self);
+void                   ide_runtime_set_id                   (IdeRuntime           *self,
+                                                             const gchar          *id);
+const gchar           *ide_runtime_get_title                (IdeRuntime           *self);
+void                   ide_runtime_set_title                (IdeRuntime           *self,
+                                                             const gchar          *title);
+
+G_END_DECLS
+
+#endif /* IDE_RUNTIME_H */
diff --git a/libide/ide-subprocess-launcher.c b/libide/ide-subprocess-launcher.c
new file mode 100644
index 0000000..c674c39
--- /dev/null
+++ b/libide/ide-subprocess-launcher.c
@@ -0,0 +1,461 @@
+/* ide-subprocess-launcher.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 <string.h>
+
+#include "ide-macros.h"
+#include "ide-subprocess-launcher.h"
+
+typedef struct
+{
+  GSubprocessFlags  flags;
+  guint             freeze_check : 1;
+
+  GPtrArray        *argv;
+  gchar            *cwd;
+  GPtrArray        *environ;
+} IdeSubprocessLauncherPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeSubprocessLauncher, ide_subprocess_launcher, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_CWD,
+  PROP_ENVIRON,
+  PROP_FLAGS,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+IdeSubprocessLauncher *
+ide_subprocess_launcher_new (GSubprocessFlags flags)
+{
+  return g_object_new (IDE_TYPE_SUBPROCESS_LAUNCHER,
+                       "flags", flags,
+                       NULL);
+}
+
+static void
+ide_subprocess_launcher_spawn_worker (GTask        *task,
+                                      gpointer      source_object,
+                                      gpointer      task_data,
+                                      GCancellable *cancellable)
+{
+  IdeSubprocessLauncher *self = source_object;
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  GSubprocess *ret;
+  GError *error = NULL;
+
+  g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+  {
+    gchar *str;
+
+    str = g_strjoinv (" ", (gchar **)priv->argv->pdata);
+    g_print (">> %s\n", str);
+    g_free (str);
+  }
+
+  launcher = g_subprocess_launcher_new (priv->flags);
+  g_subprocess_launcher_set_environ (launcher, (gchar **)priv->environ->pdata);
+  g_subprocess_launcher_set_cwd (launcher, priv->cwd);
+  ret = g_subprocess_launcher_spawnv (launcher,
+                                      (const gchar * const *)priv->argv->pdata,
+                                      &error);
+
+  if (ret == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_return_pointer (task, ret, g_object_unref);
+}
+
+static GSubprocess *
+ide_subprocess_launcher_real_spawn_sync (IdeSubprocessLauncher  *self,
+                                         GCancellable           *cancellable,
+                                         GError                **error)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  priv->freeze_check = TRUE;
+
+  task = g_task_new (self, cancellable, NULL, NULL);
+  g_task_run_in_thread_sync (task, ide_subprocess_launcher_spawn_worker);
+
+  return g_task_propagate_pointer (task, error);
+}
+
+static void
+ide_subprocess_launcher_real_spawn_async (IdeSubprocessLauncher *self,
+                                          GCancellable          *cancellable,
+                                          GAsyncReadyCallback    callback,
+                                          gpointer               user_data)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  priv->freeze_check = TRUE;
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_run_in_thread (task, ide_subprocess_launcher_spawn_worker);
+}
+
+static GSubprocess *
+ide_subprocess_launcher_real_spawn_finish (IdeSubprocessLauncher  *self,
+                                           GAsyncResult           *result,
+                                           GError                **error)
+{
+  g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+ide_subprocess_launcher_finalize (GObject *object)
+{
+  IdeSubprocessLauncher *self = (IdeSubprocessLauncher *)object;
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_clear_pointer (&priv->argv, g_ptr_array_unref);
+  g_clear_pointer (&priv->cwd, g_free);
+  g_clear_pointer (&priv->environ, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (ide_subprocess_launcher_parent_class)->finalize (object);
+}
+
+static void
+ide_subprocess_launcher_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  IdeSubprocessLauncher *self = IDE_SUBPROCESS_LAUNCHER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CWD:
+      g_value_set_string (value, ide_subprocess_launcher_get_cwd (self));
+      break;
+
+    case PROP_FLAGS:
+      g_value_set_flags (value, ide_subprocess_launcher_get_flags (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_subprocess_launcher_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  IdeSubprocessLauncher *self = IDE_SUBPROCESS_LAUNCHER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CWD:
+      ide_subprocess_launcher_set_cwd (self, g_value_get_string (value));
+      break;
+
+    case PROP_FLAGS:
+      ide_subprocess_launcher_set_flags (self, g_value_get_flags (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_subprocess_launcher_class_init (IdeSubprocessLauncherClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_subprocess_launcher_finalize;
+  object_class->get_property = ide_subprocess_launcher_get_property;
+  object_class->set_property = ide_subprocess_launcher_set_property;
+
+  klass->spawn_sync = ide_subprocess_launcher_real_spawn_sync;
+  klass->spawn_async = ide_subprocess_launcher_real_spawn_async;
+  klass->spawn_finish = ide_subprocess_launcher_real_spawn_finish;
+
+  properties [PROP_CWD] =
+    g_param_spec_string ("cwd",
+                         "Current Working Directory",
+                         "Current Working Directory",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_FLAGS] =
+    g_param_spec_flags ("flags",
+                        "Flags",
+                        "Flags",
+                        G_TYPE_SUBPROCESS_FLAGS,
+                        G_SUBPROCESS_FLAGS_NONE,
+                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ENVIRON] =
+    g_param_spec_boxed ("environ",
+                        "Environ",
+                        "Environ",
+                        G_TYPE_STRV,
+                        (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_subprocess_launcher_init (IdeSubprocessLauncher *self)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  priv->environ = g_ptr_array_new_with_free_func (g_free);
+  g_ptr_array_add (priv->environ, NULL);
+
+  priv->argv = g_ptr_array_new_with_free_func (g_free);
+  g_ptr_array_add (priv->argv, NULL);
+
+  priv->cwd = g_strdup (".");
+}
+
+void
+ide_subprocess_launcher_set_flags (IdeSubprocessLauncher *self,
+                                   GSubprocessFlags       flags)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+  if (priv->freeze_check)
+    {
+      g_warning ("process launcher is already frozen");
+      return;
+    }
+
+  if (flags != priv->flags)
+    {
+      priv->flags = flags;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FLAGS]);
+    }
+}
+
+GSubprocessFlags
+ide_subprocess_launcher_get_flags (IdeSubprocessLauncher *self)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), 0);
+
+  return priv->flags;
+}
+
+const gchar * const *
+ide_subprocess_launcher_get_environ (IdeSubprocessLauncher *self)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
+
+  return (const gchar * const *)priv->environ->pdata;
+}
+
+void
+ide_subprocess_launcher_set_environ (IdeSubprocessLauncher *self,
+                                     const gchar * const   *environ_)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+  guint i;
+
+  g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+  if (priv->freeze_check)
+    {
+      g_warning ("process launcher is already frozen");
+      return;
+    }
+
+  g_ptr_array_remove_range (priv->environ, 0, priv->environ->len);
+
+  if (environ_ != NULL)
+    {
+      for (i = 0; environ_ [i]; i++)
+        g_ptr_array_add (priv->environ, g_strdup (environ_ [i]));
+    }
+
+  g_ptr_array_add (priv->environ, NULL);
+}
+
+void
+ide_subprocess_launcher_setenv (IdeSubprocessLauncher *self,
+                                const gchar           *key,
+                                const gchar           *value,
+                                gboolean               replace)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+  gchar *str;
+  guint i;
+
+  g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+  g_return_if_fail (key != NULL);
+
+  if (priv->freeze_check)
+    {
+      g_warning ("process launcher is already frozen");
+      return;
+    }
+
+  if (value == NULL)
+    value = "";
+
+  for (i = 0; i < priv->environ->len; i++)
+    {
+      gchar *item_key = g_ptr_array_index (priv->environ, i);
+      const gchar *eq;
+
+      if (item_key == NULL)
+        break;
+
+      if (NULL == (eq = strchr (item_key, '=')))
+        continue;
+
+      if (strncmp (item_key, key, eq - item_key) == 0)
+        {
+          if (replace)
+            {
+              g_free (item_key);
+              g_ptr_array_index (priv->environ, i) = g_strdup_printf ("%s=%s", key, value);
+            }
+          return;
+        }
+    }
+
+  str = g_strdup_printf ("%s=%s", key, value);
+  g_ptr_array_index (priv->environ, priv->environ->len - 1) = str;
+  g_ptr_array_add (priv->environ, NULL);
+}
+
+void
+ide_subprocess_launcher_push_argv (IdeSubprocessLauncher *self,
+                                   const gchar           *argv)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+  g_return_if_fail (argv != NULL);
+
+  if (priv->freeze_check)
+    {
+      g_warning ("process launcher is already frozen");
+      return;
+    }
+
+  g_ptr_array_index (priv->argv, priv->argv->len - 1) = g_strdup (argv);
+  g_ptr_array_add (priv->argv, NULL);
+}
+
+void
+ide_subprocess_launcher_spawn_async (IdeSubprocessLauncher *self,
+                                     GCancellable          *cancellable,
+                                     GAsyncReadyCallback    callback,
+                                     gpointer               user_data)
+{
+  g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_SUBPROCESS_LAUNCHER_GET_CLASS (self)->spawn_async (self, cancellable, callback, user_data);
+}
+
+/**
+ * ide_subprocess_launcher_spawn_finish:
+ *
+ * Complete a request to asynchronously spawn a process.
+ *
+ * Returns: (transfer full): A #GSubprocess or %NULL upon error.
+ */
+GSubprocess *
+ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher  *self,
+                                      GAsyncResult           *result,
+                                      GError                **error)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  return IDE_SUBPROCESS_LAUNCHER_GET_CLASS (self)->spawn_finish (self, result, error);
+}
+
+/**
+ * ide_subprocess_launcher_spawn_sync:
+ *
+ * Synchronously spawn a process using the internal state.
+ *
+ * Returns: (transfer full): A #GSubprocess or %NULL upon error.
+ */
+GSubprocess *
+ide_subprocess_launcher_spawn_sync (IdeSubprocessLauncher  *self,
+                                    GCancellable           *cancellable,
+                                    GError                **error)
+{
+  g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
+
+  return IDE_SUBPROCESS_LAUNCHER_GET_CLASS (self)->spawn_sync (self, cancellable, error);
+}
+
+void
+ide_subprocess_launcher_set_cwd (IdeSubprocessLauncher *self,
+                                 const gchar           *cwd)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+  if (ide_str_empty0 (cwd))
+    cwd = ".";
+
+  if (!ide_str_equal0 (priv->cwd, cwd))
+    {
+      g_free (priv->cwd);
+      priv->cwd = g_strdup (cwd);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CWD]);
+    }
+}
+
+const gchar *
+ide_subprocess_launcher_get_cwd (IdeSubprocessLauncher *self)
+{
+  IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), NULL);
+
+  return priv->cwd;
+}
diff --git a/libide/ide-subprocess-launcher.h b/libide/ide-subprocess-launcher.h
new file mode 100644
index 0000000..e98267c
--- /dev/null
+++ b/libide/ide-subprocess-launcher.h
@@ -0,0 +1,75 @@
+/* ide-subprocess-launcher.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_SUBPROCESS_LAUNCHER_H
+#define IDE_SUBPROCESS_LAUNCHER_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SUBPROCESS_LAUNCHER (ide_subprocess_launcher_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeSubprocessLauncher, ide_subprocess_launcher, IDE, SUBPROCESS_LAUNCHER, GObject)
+
+struct _IdeSubprocessLauncherClass
+{
+  GObjectClass parent_class;
+
+  GSubprocess *(*spawn_sync)   (IdeSubprocessLauncher  *self,
+                                GCancellable           *cancellable,
+                                GError                **error);
+  void         (*spawn_async)  (IdeSubprocessLauncher  *self,
+                                GCancellable           *cancellable,
+                                GAsyncReadyCallback     callback,
+                                gpointer                user_data);
+  GSubprocess *(*spawn_finish) (IdeSubprocessLauncher  *self,
+                                GAsyncResult           *result,
+                                GError                **error);
+};
+
+IdeSubprocessLauncher *ide_subprocess_launcher_new          (GSubprocessFlags       flags);
+const gchar           *ide_subprocess_launcher_get_cwd      (IdeSubprocessLauncher *self);
+void                   ide_subprocess_launcher_set_cwd      (IdeSubprocessLauncher *self,
+                                                             const gchar           *cwd);
+GSubprocessFlags       ide_subprocess_launcher_get_flags    (IdeSubprocessLauncher *self);
+void                   ide_subprocess_launcher_set_flags    (IdeSubprocessLauncher *self,
+                                                             GSubprocessFlags       flags);
+const gchar * const   *ide_subprocess_launcher_get_environ  (IdeSubprocessLauncher *self);
+void                   ide_subprocess_launcher_set_environ  (IdeSubprocessLauncher *self,
+                                                             const gchar * const   *environ_);
+void                   ide_subprocess_launcher_setenv       (IdeSubprocessLauncher *self,
+                                                             const gchar           *key,
+                                                             const gchar           *value,
+                                                             gboolean               replace);
+void                   ide_subprocess_launcher_push_argv    (IdeSubprocessLauncher *self,
+                                                             const gchar           *argv);
+GSubprocess           *ide_subprocess_launcher_spawn_sync   (IdeSubprocessLauncher  *self,
+                                                             GCancellable           *cancellable,
+                                                             GError                **error);
+void                   ide_subprocess_launcher_spawn_async  (IdeSubprocessLauncher *self,
+                                                             GCancellable          *cancellable,
+                                                             GAsyncReadyCallback    callback,
+                                                             gpointer               user_data);
+GSubprocess           *ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher  *self,
+                                                             GAsyncResult           *result,
+                                                             GError                **error);
+
+G_END_DECLS
+
+#endif /* IDE_SUBPROCESS_LAUNCHER_H */
diff --git a/libide/ide-types.h b/libide/ide-types.h
index 43ce39a..857df5f 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -41,6 +41,8 @@ typedef struct _IdeBuildResult                 IdeBuildResult;
 
 typedef struct _IdeBuildSystem                 IdeBuildSystem;
 
+typedef struct _IdeConfiguration               IdeConfiguration;
+
 typedef struct _IdeContext                     IdeContext;
 
 typedef struct _IdeDebugger                    IdeDebugger;
@@ -98,6 +100,9 @@ typedef struct _IdeProjectFiles                IdeProjectFiles;
 typedef struct _IdeRefactory                   IdeRefactory;
 typedef struct _IdeRefactoryInterface          IdeRefactoryInterface;
 
+typedef struct _IdeRuntime                     IdeRuntime;
+typedef struct _IdeRuntimeManager              IdeRuntimeManager;
+
 typedef struct _IdeScript                      IdeScript;
 
 typedef struct _IdeScriptManager               IdeScriptManager;
diff --git a/libide/ide.h b/libide/ide.h
index 7ec2f35..f05767a 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -80,6 +80,9 @@ G_BEGIN_DECLS
 #include "ide-project-item.h"
 #include "ide-recent-projects.h"
 #include "ide-refactory.h"
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
+#include "ide-runtime-provider.h"
 #include "ide-script.h"
 #include "ide-script-manager.h"
 #include "ide-search-context.h"
@@ -97,6 +100,7 @@ G_BEGIN_DECLS
 #include "ide-source-snippets-manager.h"
 #include "ide-source-snippets.h"
 #include "ide-source-view.h"
+#include "ide-subprocess-launcher.h"
 #include "ide-symbol-resolver.h"
 #include "ide-symbol.h"
 #include "ide-target.h"
diff --git a/libide/local/ide-local-device.c b/libide/local/ide-local-device.c
index b80a22c..9314cbf 100644
--- a/libide/local/ide-local-device.c
+++ b/libide/local/ide-local-device.c
@@ -23,8 +23,7 @@
 
 typedef struct
 {
-  GKeyFile *config;
-  gchar    *system_type;
+  gchar *system_type;
 } IdeLocalDevicePrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (IdeLocalDevice, ide_local_device, IDE_TYPE_DEVICE)
@@ -71,25 +70,12 @@ ide_local_device_get_system_type (IdeDevice *device)
   return priv->system_type;
 }
 
-static GKeyFile *
-ide_local_device_get_config (IdeDevice *device)
-{
-  IdeLocalDevice *self = (IdeLocalDevice *)device;
-  IdeLocalDevicePrivate *priv = ide_local_device_get_instance_private (self);
-
-  g_return_val_if_fail (IDE_IS_LOCAL_DEVICE (device), NULL);
-  g_return_val_if_fail (IDE_IS_LOCAL_DEVICE (self), NULL);
-
-  return priv->config;
-}
-
 static void
 ide_local_device_finalize (GObject *object)
 {
   IdeLocalDevice *self = (IdeLocalDevice *)object;
   IdeLocalDevicePrivate *priv = ide_local_device_get_instance_private (self);
 
-  g_clear_pointer (&priv->config, g_key_file_unref);
   g_clear_pointer (&priv->system_type, g_free);
 
   G_OBJECT_CLASS (ide_local_device_parent_class)->finalize (object);
@@ -103,7 +89,6 @@ ide_local_device_class_init (IdeLocalDeviceClass *klass)
 
   object_class->finalize = ide_local_device_finalize;
 
-  device_class->get_config = ide_local_device_get_config;
   device_class->get_system_type = ide_local_device_get_system_type;
 }
 
@@ -113,9 +98,6 @@ ide_local_device_init (IdeLocalDevice *self)
   IdeLocalDevicePrivate *priv = ide_local_device_get_instance_private (self);
 
   priv->system_type = get_system_type ();
-  priv->config = g_key_file_new ();
-
-  g_key_file_set_string (priv->config, "autoconf", "--host", priv->system_type);
 
   ide_device_set_display_name (IDE_DEVICE (self), g_get_host_name ());
   ide_device_set_id (IDE_DEVICE (self), "local");
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index b316c78..22e70c4 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -27,6 +27,7 @@ SUBDIRS = \
        terminal \
        todo \
        vala-pack \
+       xdg-app \
        xml-pack \
        $(NULL)
 
diff --git a/plugins/autotools/ide-autotools-build-system.c b/plugins/autotools/ide-autotools-build-system.c
index 3df3824..adaf9a9 100644
--- a/plugins/autotools/ide-autotools-build-system.c
+++ b/plugins/autotools/ide-autotools-build-system.c
@@ -39,6 +39,8 @@
 #include "ide-file.h"
 #include "ide-internal.h"
 #include "ide-makecache.h"
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
 #include "ide-tags-builder.h"
 
 #define MAKECACHE_KEY "makecache"
@@ -84,24 +86,21 @@ ide_autotools_build_system_get_tarball_name (IdeAutotoolsBuildSystem *self)
 }
 
 static IdeBuilder *
-ide_autotools_build_system_get_builder (IdeBuildSystem  *build_system,
-                                        GKeyFile        *config,
-                                        IdeDevice       *device,
-                                        GError         **error)
+ide_autotools_build_system_get_builder (IdeBuildSystem    *build_system,
+                                        IdeConfiguration  *configuration,
+                                        GError           **error)
 {
   IdeBuilder *ret;
   IdeContext *context;
 
-  g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (build_system), NULL);
-  g_return_val_if_fail (config, NULL);
-  g_return_val_if_fail (IDE_IS_DEVICE (device), NULL);
+  g_assert (IDE_IS_AUTOTOOLS_BUILD_SYSTEM (build_system));
+  g_assert (IDE_IS_CONFIGURATION (configuration));
 
   context = ide_object_get_context (IDE_OBJECT (build_system));
 
   ret = g_object_new (IDE_TYPE_AUTOTOOLS_BUILDER,
                       "context", context,
-                      "config", config,
-                      "device", device,
+                      "configuration", configuration,
                       NULL);
 
   return ret;
@@ -213,32 +212,6 @@ ide_autotools_build_system_discover_file_finish (IdeAutotoolsBuildSystem  *syste
 }
 
 static void
-ide_autotools_build_system__bootstrap_cb (GObject      *object,
-                                          GAsyncResult *result,
-                                          gpointer      user_data)
-{
-  IdeAutotoolsBuilder *builder = (IdeAutotoolsBuilder *)object;
-  g_autoptr(GTask) task = user_data;
-  g_autoptr(GFile) build_directory = NULL;
-  g_autoptr(GFile) makefile = NULL;
-  GError *error = NULL;
-
-  g_assert (IDE_IS_AUTOTOOLS_BUILDER (builder));
-  g_assert (G_IS_TASK (task));
-
-  if (!ide_autotools_builder_bootstrap_finish (builder, result, &error))
-    {
-      g_task_return_error (task, error);
-      return;
-    }
-
-  build_directory = ide_autotools_builder_get_build_directory (builder);
-  makefile = g_file_get_child (build_directory, "Makefile");
-
-  g_task_return_pointer (task, g_object_ref (makefile), g_object_unref);
-}
-
-static void
 ide_autotools_build_system_get_local_makefile_async (IdeAutotoolsBuildSystem *self,
                                                      GCancellable            *cancellable,
                                                      GAsyncReadyCallback      callback,
@@ -247,9 +220,11 @@ ide_autotools_build_system_get_local_makefile_async (IdeAutotoolsBuildSystem *se
   IdeContext *context;
   IdeDeviceManager *device_manager;
   IdeDevice *device;
+  IdeRuntimeManager *runtime_manager;
+  IdeRuntime *runtime;
+  g_autoptr(IdeConfiguration) configuration = NULL;
   g_autoptr(GTask) task = NULL;
   g_autoptr(IdeBuilder) builder = NULL;
-  g_autoptr(GKeyFile) config = NULL;
   g_autoptr(GFile) build_directory = NULL;
   g_autoptr(GFile) makefile = NULL;
   GError *error = NULL;
@@ -260,10 +235,16 @@ ide_autotools_build_system_get_local_makefile_async (IdeAutotoolsBuildSystem *se
   task = g_task_new (self, cancellable, callback, user_data);
 
   context = ide_object_get_context (IDE_OBJECT (self));
+
   device_manager = ide_context_get_device_manager (context);
   device = ide_device_manager_get_device (device_manager, "local");
-  config = g_key_file_new ();
-  builder = ide_autotools_build_system_get_builder (IDE_BUILD_SYSTEM (self), config, device, &error);
+
+  runtime_manager = ide_context_get_runtime_manager (context);
+  runtime = ide_runtime_manager_get_runtime (runtime_manager, "system");
+
+  configuration = ide_configuration_new ("autotools-bootstrap", device, runtime);
+
+  builder = ide_autotools_build_system_get_builder (IDE_BUILD_SYSTEM (self), configuration, &error);
 
   if (builder == NULL)
     {
@@ -271,18 +252,6 @@ ide_autotools_build_system_get_local_makefile_async (IdeAutotoolsBuildSystem *se
       return;
     }
 
-  /*
-   * If we haven't yet bootstrapped the project, let's go ahead and do that now.
-   */
-  if (ide_autotools_builder_get_needs_bootstrap (IDE_AUTOTOOLS_BUILDER (builder)))
-    {
-      ide_autotools_builder_bootstrap_async (IDE_AUTOTOOLS_BUILDER (builder),
-                                             cancellable,
-                                             ide_autotools_build_system__bootstrap_cb,
-                                             g_object_ref (task));
-      return;
-    }
-
   build_directory = ide_autotools_builder_get_build_directory (IDE_AUTOTOOLS_BUILDER (builder));
   makefile = g_file_get_child (build_directory, "Makefile");
 
diff --git a/plugins/autotools/ide-autotools-build-task.c b/plugins/autotools/ide-autotools-build-task.c
index b84fe78..10329c4 100644
--- a/plugins/autotools/ide-autotools-build-task.c
+++ b/plugins/autotools/ide-autotools-build-task.c
@@ -22,34 +22,33 @@
 
 #include <fcntl.h>
 #include <glib/gi18n.h>
+#include <ide.h>
+#include <stdlib.h>
 #include <unistd.h>
 
 #include "ide-autotools-build-task.h"
-#include "ide-context.h"
-#include "ide-device.h"
-#include "ide-project.h"
 
 typedef struct
 {
-  GKeyFile  *config;
-  IdeDevice *device;
-  GFile     *directory;
-  guint      require_autogen : 1;
-  guint      require_configure : 1;
-  guint      executed : 1;
+  IdeConfiguration *configuration;
+  GFile            *directory;
+  guint             require_autogen : 1;
+  guint             require_configure : 1;
+  guint             executed : 1;
 } IdeAutotoolsBuildTaskPrivate;
 
 typedef struct
 {
-  gchar  *directory_path;
-  gchar  *project_path;
-  gchar  *parallel;
-  gchar  *system_type;
-  gchar **configure_argv;
-  gchar **make_targets;
-  guint   require_autogen : 1;
-  guint   require_configure : 1;
-  guint   bootstrap_only : 1;
+  gchar       *directory_path;
+  gchar       *project_path;
+  gchar       *parallel;
+  gchar       *system_type;
+  gchar      **configure_argv;
+  gchar      **make_targets;
+  IdeRuntime  *runtime;
+  guint        require_autogen : 1;
+  guint        require_configure : 1;
+  guint        bootstrap_only : 1;
 } WorkerState;
 
 typedef gboolean (*WorkStep) (GTask                 *task,
@@ -62,8 +61,7 @@ G_DEFINE_TYPE_WITH_PRIVATE (IdeAutotoolsBuildTask, ide_autotools_build_task,
 
 enum {
   PROP_0,
-  PROP_CONFIG,
-  PROP_DEVICE,
+  PROP_CONFIGURATION,
   PROP_DIRECTORY,
   PROP_REQUIRE_AUTOGEN,
   PROP_REQUIRE_CONFIGURE,
@@ -71,23 +69,23 @@ enum {
 };
 
 static GSubprocess *log_and_spawn  (IdeAutotoolsBuildTask  *self,
-                                    GSubprocessLauncher    *launcher,
+                                    IdeSubprocessLauncher  *launcher,
                                     GError                **error,
                                     const gchar            *argv0,
                                     ...) G_GNUC_NULL_TERMINATED;
-static gboolean step_mkdirs        (GTask                  *task,
+static gboolean     step_mkdirs    (GTask                  *task,
                                     IdeAutotoolsBuildTask  *self,
                                     WorkerState            *state,
                                     GCancellable           *cancellable);
-static gboolean step_autogen       (GTask                  *task,
+static gboolean     step_autogen   (GTask                  *task,
                                     IdeAutotoolsBuildTask  *self,
                                     WorkerState            *state,
                                     GCancellable           *cancellable);
-static gboolean step_configure     (GTask                  *task,
+static gboolean     step_configure (GTask                  *task,
                                     IdeAutotoolsBuildTask  *self,
                                     WorkerState            *state,
                                     GCancellable           *cancellable);
-static gboolean step_make_all      (GTask                  *task,
+static gboolean     step_make_all  (GTask                  *task,
                                     IdeAutotoolsBuildTask  *self,
                                     WorkerState            *state,
                                     GCancellable           *cancellable);
@@ -152,81 +150,6 @@ ide_autotools_build_task_set_require_configure (IdeAutotoolsBuildTask *task,
 }
 
 /**
- * ide_autotools_build_task_get_config:
- * @self: A #IdeAutotoolsBuildTask.
- *
- * Gets the "config" property of the task. This is the overlay config to be
- * applied on top of the device config when compiling.
- *
- * Returns: (transfer none) (nullable): A #GKeyFile or %NULL.
- */
-GKeyFile *
-ide_autotools_build_task_get_config (IdeAutotoolsBuildTask *self)
-{
-  IdeAutotoolsBuildTaskPrivate *priv;
-
-  g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
-
-  priv = ide_autotools_build_task_get_instance_private (self);
-
-  return priv->config;
-}
-
-static void
-ide_autotools_build_task_set_config (IdeAutotoolsBuildTask *self,
-                                     GKeyFile              *config)
-{
-  IdeAutotoolsBuildTaskPrivate *priv;
-
-  g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
-
-  priv = ide_autotools_build_task_get_instance_private (self);
-
-  if (priv->config != config)
-    {
-      g_clear_pointer (&priv->config, g_key_file_unref);
-      priv->config = config ? g_key_file_ref (config) : NULL;
-      g_object_notify_by_pspec (G_OBJECT (self),
-                                properties [PROP_CONFIG]);
-    }
-}
-
-/**
- * ide_autotools_build_task_get_device:
- * @self: A #IdeAutotoolsBuildTask.
- *
- * Gets the "device" property. This is the device we are compiling for,
- * which may involve cross-compiling.
- *
- * Returns: (transfer none): An #IdeDevice.
- */
-IdeDevice *
-ide_autotools_build_task_get_device (IdeAutotoolsBuildTask *self)
-{
-  IdeAutotoolsBuildTaskPrivate *priv;
-
-  g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
-
-  priv = ide_autotools_build_task_get_instance_private (self);
-
-  return priv->device;
-}
-
-static void
-ide_autotools_build_task_set_device (IdeAutotoolsBuildTask *self,
-                                     IdeDevice             *device)
-{
-  IdeAutotoolsBuildTaskPrivate *priv;
-
-  g_return_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
-
-  priv = ide_autotools_build_task_get_instance_private (self);
-
-  if (g_set_object (&priv->device, device))
-    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE]);
-}
-
-/**
  * ide_autotools_build_task_get_directory:
  *
  * Fetches the build directory that was used.
@@ -278,6 +201,35 @@ ide_autotools_build_task_set_directory (IdeAutotoolsBuildTask *self,
                                 properties [PROP_DIRECTORY]);
 }
 
+/**
+ * ide_autotools_build_task_get_configuration:
+ * @self: An #IdeAutotoolsBuildTask
+ *
+ * Gets the configuration to use for the build.
+ */
+IdeConfiguration *
+ide_autotools_build_task_get_configuration (IdeAutotoolsBuildTask *self)
+{
+  IdeAutotoolsBuildTaskPrivate *priv = ide_autotools_build_task_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
+
+  return priv->configuration;
+}
+
+static void
+ide_autotools_build_task_set_configuration (IdeAutotoolsBuildTask *self,
+                                            IdeConfiguration      *configuration)
+{
+  IdeAutotoolsBuildTaskPrivate *priv = ide_autotools_build_task_get_instance_private (self);
+
+  g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
+  g_assert (IDE_IS_CONFIGURATION (configuration));
+
+  if (g_set_object (&priv->configuration, configuration))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONFIGURATION]);
+}
+
 static void
 ide_autotools_build_task_finalize (GObject *object)
 {
@@ -286,9 +238,8 @@ ide_autotools_build_task_finalize (GObject *object)
 
   priv = ide_autotools_build_task_get_instance_private (self);
 
-  g_clear_object (&priv->device);
   g_clear_object (&priv->directory);
-  g_clear_pointer (&priv->config, g_key_file_unref);
+  g_clear_object (&priv->configuration);
 
   G_OBJECT_CLASS (ide_autotools_build_task_parent_class)->finalize (object);
 }
@@ -303,12 +254,8 @@ ide_autotools_build_task_get_property (GObject    *object,
 
   switch (prop_id)
     {
-    case PROP_CONFIG:
-      g_value_set_object (value, ide_autotools_build_task_get_config (self));
-      break;
-
-    case PROP_DEVICE:
-      g_value_set_object (value, ide_autotools_build_task_get_device (self));
+    case PROP_CONFIGURATION:
+      g_value_set_object (value, ide_autotools_build_task_get_configuration (self));
       break;
 
     case PROP_DIRECTORY:
@@ -338,12 +285,8 @@ ide_autotools_build_task_set_property (GObject      *object,
 
   switch (prop_id)
     {
-    case PROP_CONFIG:
-      ide_autotools_build_task_set_config (self, g_value_get_boxed (value));
-      break;
-
-    case PROP_DEVICE:
-      ide_autotools_build_task_set_device (self, g_value_get_object (value));
+    case PROP_CONFIGURATION:
+      ide_autotools_build_task_set_configuration (self, g_value_get_object (value));
       break;
 
     case PROP_DIRECTORY:
@@ -372,24 +315,15 @@ ide_autotools_build_task_class_init (IdeAutotoolsBuildTaskClass *klass)
   object_class->get_property = ide_autotools_build_task_get_property;
   object_class->set_property = ide_autotools_build_task_set_property;
 
-  properties [PROP_CONFIG] =
-    g_param_spec_boxed ("config",
-                        "Config",
-                        "The overlay config for the compilation.",
-                        G_TYPE_KEY_FILE,
+  properties [PROP_CONFIGURATION] =
+    g_param_spec_object ("configuration",
+                        "Configuration",
+                        "The configuration for this build.",
+                        IDE_TYPE_CONFIGURATION,
                         (G_PARAM_READWRITE |
                          G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));
 
-  properties [PROP_DEVICE] =
-    g_param_spec_object ("device",
-                         "Device",
-                         "The device to build for.",
-                         IDE_TYPE_DEVICE,
-                         (G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT_ONLY |
-                          G_PARAM_STATIC_STRINGS));
-
   properties [PROP_DIRECTORY] =
     g_param_spec_object ("directory",
                          "Directory",
@@ -429,97 +363,67 @@ static gchar **
 gen_configure_argv (IdeAutotoolsBuildTask *self,
                     WorkerState           *state)
 {
-  IdeAutotoolsBuildTaskPrivate *priv;
+  IdeAutotoolsBuildTaskPrivate *priv = ide_autotools_build_task_get_instance_private (self);
   IdeDevice *device;
-  const gchar *system_type;
-  GKeyFile *configs[2];
   GPtrArray *ar;
-  GHashTable *ht;
-  gpointer k, v;
-  GHashTableIter iter;
+  const gchar *opts;
+  const gchar *system_type;
+  gchar *prefix;
   gchar *configure_path;
-  guint j;
 
-  g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
-
-  priv = ide_autotools_build_task_get_instance_private (self);
-
-  ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-
-  configs [0] = ide_device_get_config (priv->device);
-  configs [1] = priv->config;
+  g_assert (IDE_IS_AUTOTOOLS_BUILD_TASK (self));
+  g_assert (state != NULL);
 
-  for (j = 0; j < G_N_ELEMENTS (configs); j++)
-    {
-      GKeyFile *config = configs [j];
+  ar = g_ptr_array_new_with_free_func (g_free);
 
-      if (config)
-        {
-          if (g_key_file_has_group (config, "autoconf"))
-            {
-              gchar **keys;
-              gsize len;
-              gsize i;
-
-              keys = g_key_file_get_keys (config, "autoconf", &len, NULL);
-
-              for (i = 0; i < len; i++)
-                {
-                  gchar *value;
-
-                  if (*keys [i] == '-')
-                    {
-                      value = g_key_file_get_string (config,
-                                                     "autoconf", keys [i],
-                                                     NULL);
-                      if (value)
-                        g_hash_table_replace (ht, g_strdup (keys [i]), value);
-                    }
-                }
-
-              g_strfreev (keys);
-            }
-        }
-    }
-
-  ar = g_ptr_array_new ();
+  /* ./configure */
   configure_path = g_build_filename (state->project_path, "configure", NULL);
   g_ptr_array_add (ar, configure_path);
 
-  g_hash_table_iter_init (&iter, ht);
+  /* --prefix /app */
+  if (!(prefix = g_strdup (ide_configuration_getenv (priv->configuration, "PREFIX"))))
+    prefix = g_build_filename (state->project_path, "_install", NULL);
+  g_ptr_array_add (ar, g_strdup_printf ("--prefix=%s", prefix));
+  g_free (prefix);
 
-  while (g_hash_table_iter_next (&iter, &k, &v))
-    {
-      g_ptr_array_add (ar, g_strdup (k));
-      if (v && *(gchar *)v)
-        g_ptr_array_add (ar, g_strdup (v));
-    }
+  /* --host=triplet */
+  device = ide_configuration_get_device (priv->configuration);
+  system_type = ide_device_get_system_type (device);
+  g_ptr_array_add (ar, g_strdup_printf ("--host=%s", system_type));
 
-  if (!g_hash_table_lookup (ht, "--prefix"))
+  if (NULL != (opts = ide_configuration_getenv (priv->configuration, "CONFIGURE_ARGS")))
     {
-      gchar *prefix;
+      GError *error = NULL;
+      gint argc;
+      gchar **argv;
 
-      prefix = g_build_filename (state->project_path, "_install", NULL);
-      g_ptr_array_add (ar, g_strdup_printf ("--prefix=%s", prefix));
-      g_free (prefix);
+      if (g_shell_parse_argv (opts, &argc, &argv, &error))
+        {
+          for (guint i = 0; i < argc; i++)
+            g_ptr_array_add (ar, argv [i]);
+          g_free (argv);
+        }
+      else
+        {
+          g_warning ("%s", error->message);
+          g_clear_error (&error);
+        }
     }
 
-  device = ide_autotools_build_task_get_device (self);
-  system_type = ide_device_get_system_type (device);
-  g_ptr_array_add (ar, g_strdup_printf ("--host=%s", system_type));
-
   g_ptr_array_add (ar, NULL);
-  g_hash_table_unref (ht);
 
   return (gchar **)g_ptr_array_free (ar, FALSE);
 }
 
 static WorkerState *
-worker_state_new (IdeAutotoolsBuildTask *self)
+worker_state_new (IdeAutotoolsBuildTask *self,
+                  IdeBuilderBuildFlags   flags)
 {
-  IdeAutotoolsBuildTaskPrivate *priv;
+  IdeAutotoolsBuildTaskPrivate *priv = ide_autotools_build_task_get_instance_private (self);
   g_autofree gchar *name = NULL;
   IdeContext *context;
+  IdeDevice *device;
+  IdeRuntime *runtime;
   GPtrArray *make_targets;
   GFile *project_dir;
   GFile *project_file;
@@ -527,12 +431,14 @@ worker_state_new (IdeAutotoolsBuildTask *self)
   gint val32;
 
   g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILD_TASK (self), NULL);
-
-  priv = ide_autotools_build_task_get_instance_private (self);
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (priv->configuration), NULL);
 
   context = ide_object_get_context (IDE_OBJECT (self));
   project_file = ide_context_get_project_file (context);
 
+  device = ide_configuration_get_device (priv->configuration);
+  runtime = ide_configuration_get_runtime (priv->configuration);
+
   name = g_file_get_basename (project_file);
 
   if (g_str_has_prefix (name, "configure."))
@@ -541,13 +447,14 @@ worker_state_new (IdeAutotoolsBuildTask *self)
     project_dir = g_object_ref (project_file);
 
   state = g_slice_new0 (WorkerState);
-  state->require_autogen = priv->require_autogen;
-  state->require_configure = priv->require_configure;
+  state->require_autogen = priv->require_autogen || !!(flags & IDE_BUILDER_BUILD_FLAGS_FORCE_BOOTSTRAP);
+  state->require_configure = priv->require_configure || (state->require_autogen && !(flags & 
IDE_BUILDER_BUILD_FLAGS_NO_CONFIGURE));
   state->directory_path = g_file_get_path (priv->directory);
   state->project_path = g_file_get_path (project_dir);
-  state->system_type = g_strdup (ide_device_get_system_type (priv->device));
+  state->system_type = g_strdup (ide_device_get_system_type (device));
+  state->runtime = g_object_ref (runtime);
 
-  val32 = g_key_file_get_integer (priv->config, "parallel", "workers", NULL);
+  val32 = atoi (ide_configuration_getenv (priv->configuration, "PARALLEL") ?: "-1");
 
   if (val32 == -1)
     state->parallel = g_strdup_printf ("-j%u", g_get_num_processors () + 1);
@@ -558,22 +465,21 @@ worker_state_new (IdeAutotoolsBuildTask *self)
 
   make_targets = g_ptr_array_new ();
 
-  if (priv->config && g_key_file_get_boolean (priv->config, "autotools", "rebuild", NULL))
+  if (0 != (flags & IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN))
     {
       state->require_autogen = TRUE;
       state->require_configure = TRUE;
       g_ptr_array_add (make_targets, g_strdup ("clean"));
     }
 
-  if (priv->config && g_key_file_get_boolean (priv->config, "autotools", "clean-only", NULL))
-    g_ptr_array_add (make_targets, g_strdup ("clean"));
-  else
+  if (0 == (flags & IDE_BUILDER_BUILD_FLAGS_NO_BUILD))
     g_ptr_array_add (make_targets, g_strdup ("all"));
 
   g_ptr_array_add (make_targets, NULL);
+
   state->make_targets = (gchar **)g_ptr_array_free (make_targets, FALSE);
 
-  if (g_key_file_get_boolean (priv->config, "autotools", "bootstrap-only", NULL))
+  if (0 != (flags & IDE_BUILDER_BUILD_FLAGS_NO_CONFIGURE))
     {
       state->require_autogen = TRUE;
       state->require_configure = TRUE;
@@ -597,6 +503,7 @@ worker_state_free (void *data)
   g_free (state->parallel);
   g_strfreev (state->configure_argv);
   g_strfreev (state->make_targets);
+  g_clear_object (&state->runtime);
   g_slice_free (WorkerState, state);
 }
 
@@ -625,8 +532,30 @@ ide_autotools_build_task_execute_worker (GTask        *task,
   g_task_return_boolean (task, TRUE);
 }
 
+static void
+ide_autotools_build_task_prebuild_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  IdeRuntime *runtime = (IdeRuntime *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUNTIME (runtime));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!ide_runtime_prebuild_finish (runtime, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_run_in_thread (task, ide_autotools_build_task_execute_worker);
+}
+
 void
 ide_autotools_build_task_execute_async (IdeAutotoolsBuildTask *self,
+                                        IdeBuilderBuildFlags   flags,
                                         GCancellable          *cancellable,
                                         GAsyncReadyCallback    callback,
                                         gpointer               user_data)
@@ -652,11 +581,18 @@ ide_autotools_build_task_execute_async (IdeAutotoolsBuildTask *self,
 
   priv->executed = TRUE;
 
-  state = worker_state_new (self);
+  state = worker_state_new (self, flags);
 
   task = g_task_new (self, cancellable, callback, user_data);
   g_task_set_task_data (task, state, worker_state_free);
-  g_task_run_in_thread (task, ide_autotools_build_task_execute_worker);
+
+  /*
+   * Execute the pre-hook for the runtime before we start building.
+   */
+  ide_runtime_prebuild_async (state->runtime,
+                              cancellable,
+                              ide_autotools_build_task_prebuild_cb,
+                              g_object_ref (task));
 }
 
 gboolean
@@ -675,40 +611,30 @@ ide_autotools_build_task_execute_finish (IdeAutotoolsBuildTask  *self,
 
 static GSubprocess *
 log_and_spawn (IdeAutotoolsBuildTask  *self,
-               GSubprocessLauncher    *launcher,
+               IdeSubprocessLauncher  *launcher,
                GError                **error,
                const gchar           *argv0,
                ...)
 {
   GSubprocess *ret;
-  GPtrArray *argv;
   GString *log;
   gchar *item;
   va_list args;
 
-  log = g_string_new (NULL);
-  g_string_append (log, argv0);
-
-  argv = g_ptr_array_new ();
-  g_ptr_array_add (argv, (gchar *)argv0);
+  log = g_string_new (argv0);
+  ide_subprocess_launcher_push_argv (launcher, argv0);
 
   va_start (args, argv0);
-  while ((item = va_arg (args, gchar *)))
+  while (NULL != (item = va_arg (args, gchar *)))
     {
-      g_ptr_array_add (argv, item);
+      ide_subprocess_launcher_push_argv (launcher, item);
       g_string_append_printf (log, " '%s'", item);
     }
   va_end (args);
 
-  g_ptr_array_add (argv, NULL);
-
   ide_build_result_log_stdout (IDE_BUILD_RESULT (self), "%s", log->str);
-  ret = g_subprocess_launcher_spawnv (launcher,
-                                      (const gchar * const *)argv->pdata,
-                                      error);
-
+  ret = ide_subprocess_launcher_spawn_sync (launcher, NULL, error);
   g_string_free (log, TRUE);
-  g_ptr_array_unref (argv);
 
   return ret;
 }
@@ -756,7 +682,7 @@ step_autogen (GTask                 *task,
 {
   g_autofree gchar *autogen_sh_path = NULL;
   g_autofree gchar *configure_path = NULL;
-  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
   g_autoptr(GSubprocess) process = NULL;
   GError *error = NULL;
 
@@ -795,11 +721,15 @@ step_autogen (GTask                 *task,
 
   ide_build_result_set_mode (IDE_BUILD_RESULT (self), _("Autogening…"));
 
-  launcher = g_subprocess_launcher_new ((G_SUBPROCESS_FLAGS_STDOUT_PIPE |
-                                         G_SUBPROCESS_FLAGS_STDERR_PIPE));
-  g_subprocess_launcher_set_cwd (launcher, state->project_path);
-  g_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
-  g_subprocess_launcher_setenv (launcher, "NOCONFIGURE", "1", TRUE);
+  if (NULL == (launcher = ide_runtime_create_launcher (state->runtime, &error)))
+    {
+      g_task_return_error (task, error);
+      return FALSE;
+    }
+
+  ide_subprocess_launcher_set_cwd (launcher, state->project_path);
+  ide_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
+  ide_subprocess_launcher_setenv (launcher, "NOCONFIGURE", "1", TRUE);
 
   process = log_and_spawn (self, launcher, &error, autogen_sh_path, NULL);
 
@@ -901,9 +831,10 @@ step_make_all  (GTask                 *task,
                 WorkerState           *state,
                 GCancellable          *cancellable)
 {
-  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
   g_autoptr(GSubprocess) process = NULL;
   const gchar * const *targets;
+  const gchar *make = NULL;
   gchar *default_targets[] = { "all", NULL };
   GError *error = NULL;
   guint i;
@@ -913,10 +844,32 @@ step_make_all  (GTask                 *task,
   g_assert (state);
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  launcher = g_subprocess_launcher_new ((G_SUBPROCESS_FLAGS_STDERR_PIPE |
-                                         G_SUBPROCESS_FLAGS_STDOUT_PIPE));
-  g_subprocess_launcher_set_cwd (launcher, state->directory_path);
-  g_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
+  if (NULL == (launcher = ide_runtime_create_launcher (state->runtime, &error)))
+    {
+      g_task_return_error (task, error);
+      return FALSE;
+    }
+
+  ide_subprocess_launcher_set_flags  (launcher,
+                                      (G_SUBPROCESS_FLAGS_STDERR_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE));
+  ide_subprocess_launcher_set_cwd (launcher, state->directory_path);
+  ide_subprocess_launcher_setenv (launcher, "LANG", "C", TRUE);
+
+  /*
+   * Try to locate GNU make within the runtime.
+   */
+  if (ide_runtime_contains_program_in_path (state->runtime, "gmake", cancellable))
+    make = "gmake";
+  else if (ide_runtime_contains_program_in_path (state->runtime, "make", cancellable))
+    make = "make";
+  else
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_NOT_FOUND,
+                               "Failed to locate make.");
+      return FALSE;
+    }
 
   if (!g_strv_length (state->make_targets))
     targets = (const gchar * const *)default_targets;
@@ -932,7 +885,7 @@ step_make_all  (GTask                 *task,
       else
         ide_build_result_set_mode (IDE_BUILD_RESULT (self), _("Building…"));
 
-      process = log_and_spawn (self, launcher, &error, GNU_MAKE_NAME, target, state->parallel, NULL);
+      process = log_and_spawn (self, launcher, &error, make, target, state->parallel, NULL);
 
       if (!process)
         {
diff --git a/plugins/autotools/ide-autotools-build-task.h b/plugins/autotools/ide-autotools-build-task.h
index d9b801a..9f5a7a2 100644
--- a/plugins/autotools/ide-autotools-build-task.h
+++ b/plugins/autotools/ide-autotools-build-task.h
@@ -37,6 +37,7 @@ struct _IdeAutotoolsBuildTask
 
 GFile    *ide_autotools_build_task_get_directory  (IdeAutotoolsBuildTask  *self);
 void      ide_autotools_build_task_execute_async  (IdeAutotoolsBuildTask  *self,
+                                                   IdeBuilderBuildFlags    flags,
                                                    GCancellable           *cancellable,
                                                    GAsyncReadyCallback     callback,
                                                    gpointer                user_data);
diff --git a/plugins/autotools/ide-autotools-builder.c b/plugins/autotools/ide-autotools-builder.c
index 41c63b3..8d655e6 100644
--- a/plugins/autotools/ide-autotools-builder.c
+++ b/plugins/autotools/ide-autotools-builder.c
@@ -17,99 +17,18 @@
  */
 
 #include <glib/gi18n.h>
+#include <ide.h>
 
 #include "ide-autotools-build-task.h"
 #include "ide-autotools-builder.h"
-#include "ide-build-result.h"
-#include "ide-context.h"
-#include "ide-device.h"
-#include "ide-project.h"
-#include "ide-vcs.h"
 
 struct _IdeAutotoolsBuilder
 {
-  IdeObject  parent_instance;
-
-  GKeyFile  *config;
-  IdeDevice *device;
+  IdeObject parent_instance;
 };
 
 G_DEFINE_TYPE (IdeAutotoolsBuilder, ide_autotools_builder, IDE_TYPE_BUILDER)
 
-enum {
-  PROP_0,
-  PROP_CONFIG,
-  PROP_DEVICE,
-  LAST_PROP
-};
-
-static GParamSpec *properties [LAST_PROP];
-
-static void
-ide_autotools_builder_merge_defaults (IdeAutotoolsBuilder *self,
-                                      GKeyFile            *key_file)
-{
-  g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
-  g_return_if_fail (key_file != NULL);
-
-  if (!g_key_file_has_key (key_file, "parallel", "workers", NULL))
-    {
-      g_autoptr(GSettings) settings = g_settings_new ("org.gnome.builder.build");
-
-      g_key_file_set_integer (key_file,
-                              "parallel", "workers",
-                              g_settings_get_int (settings, "parallel"));
-    }
-}
-
-GKeyFile *
-ide_autotools_builder_get_config (IdeAutotoolsBuilder *self)
-{
-  g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self), NULL);
-
-  return self->config;
-}
-
-static void
-ide_autotools_builder_set_config (IdeAutotoolsBuilder *self,
-                                  GKeyFile            *config)
-{
-  g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
-
-  if (self->config != config)
-    {
-      g_clear_pointer (&self->config, g_key_file_unref);
-
-      if (config != NULL)
-        {
-          self->config = g_key_file_ref (config);
-          ide_autotools_builder_merge_defaults (self, config);
-        }
-
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONFIG]);
-    }
-}
-
-IdeDevice *
-ide_autotools_builder_get_device (IdeAutotoolsBuilder *self)
-{
-  g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self), NULL);
-
-  return self->device;
-}
-
-static void
-ide_autotools_builder_set_device (IdeAutotoolsBuilder *self,
-                                  IdeDevice           *device)
-{
-  g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
-  g_return_if_fail (!device || IDE_IS_DEVICE (device));
-
-  if (self->device != device)
-    if (g_set_object (&self->device, device))
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE]);
-}
-
 static void
 ide_autotools_builder_build_cb (GObject      *object,
                                 GAsyncResult *result,
@@ -151,8 +70,10 @@ GFile *
 ide_autotools_builder_get_build_directory (IdeAutotoolsBuilder *self)
 {
   g_autofree gchar *path = NULL;
+  IdeConfiguration *configuration;
   IdeContext *context;
   IdeProject *project;
+  IdeDevice *device;
   const gchar *root_build_dir;
   const gchar *project_name;
   const gchar *device_id;
@@ -161,7 +82,11 @@ ide_autotools_builder_get_build_directory (IdeAutotoolsBuilder *self)
   g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self), NULL);
 
   context = ide_object_get_context (IDE_OBJECT (self));
-  device_id = ide_device_get_id (self->device);
+
+  configuration = ide_builder_get_configuration (IDE_BUILDER (self));
+
+  device = ide_configuration_get_device (configuration);
+  device_id = ide_device_get_id (device);
 
   /*
    * If this is the local device, we have a special workaround for building within the project
@@ -196,7 +121,7 @@ ide_autotools_builder_get_build_directory (IdeAutotoolsBuilder *self)
 
   project = ide_context_get_project (context);
   root_build_dir = ide_context_get_root_build_dir (context);
-  system_type = ide_device_get_system_type (self->device);
+  system_type = ide_device_get_system_type (device);
   project_name = ide_project_get_name (project);
   path = g_build_filename (root_build_dir, project_name, device_id, system_type, NULL);
 
@@ -215,29 +140,23 @@ ide_autotools_builder_build_async (IdeBuilder           *builder,
   g_autoptr(IdeAutotoolsBuildTask) build_result = NULL;
   g_autoptr(GTask) task = NULL;
   g_autoptr(GFile) directory = NULL;
+  IdeConfiguration *configuration;
   IdeContext *context;
-  IdeDevice *device;
 
   g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (builder));
   g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
 
-  if (flags & IDE_BUILDER_BUILD_FLAGS_FORCE_REBUILD)
-    g_key_file_set_boolean (self->config, "autotools", "rebuild", TRUE);
-
-  /* TODO: This belongs as its own vfunc */
-  if (flags & IDE_BUILDER_BUILD_FLAGS_CLEAN)
-    g_key_file_set_boolean (self->config, "autotools", "clean-only", TRUE);
+  if (ide_autotools_builder_get_needs_bootstrap (self))
+    flags |= IDE_BUILDER_BUILD_FLAGS_FORCE_BOOTSTRAP;
 
   task = g_task_new (self, cancellable, callback, user_data);
 
   context = ide_object_get_context (IDE_OBJECT (builder));
-  device = ide_autotools_builder_get_device (self);
+  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,
-                               "config", self->config,
-                               "device", device,
+                               "configuration", configuration,
                                "directory", directory,
                                "mode", _("Building…"),
                                "running", TRUE,
@@ -247,6 +166,7 @@ ide_autotools_builder_build_async (IdeBuilder           *builder,
     *result = g_object_ref (build_result);
 
   ide_autotools_build_task_execute_async (build_result,
+                                          flags,
                                           cancellable,
                                           ide_autotools_builder_build_cb,
                                           g_object_ref (task));
@@ -266,94 +186,12 @@ ide_autotools_builder_build_finish (IdeBuilder    *builder,
 }
 
 static void
-ide_autotools_builder_finalize (GObject *object)
-{
-  IdeAutotoolsBuilder *self = (IdeAutotoolsBuilder *)object;
-
-  g_clear_pointer (&self->config, g_key_file_unref);
-  g_clear_object (&self->device);
-
-  G_OBJECT_CLASS (ide_autotools_builder_parent_class)->finalize (object);
-}
-
-static void
-ide_autotools_builder_get_property (GObject    *object,
-                                    guint       prop_id,
-                                    GValue     *value,
-                                    GParamSpec *pspec)
-{
-  IdeAutotoolsBuilder *self = IDE_AUTOTOOLS_BUILDER (object);
-
-  switch (prop_id)
-    {
-    case PROP_CONFIG:
-      g_value_set_boxed (value, ide_autotools_builder_get_config (self));
-      break;
-
-    case PROP_DEVICE:
-      g_value_set_object (value, ide_autotools_builder_get_device (self));
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
-ide_autotools_builder_set_property (GObject      *object,
-                                    guint         prop_id,
-                                    const GValue *value,
-                                    GParamSpec   *pspec)
-{
-  IdeAutotoolsBuilder *self = IDE_AUTOTOOLS_BUILDER (object);
-
-  switch (prop_id)
-    {
-    case PROP_CONFIG:
-      ide_autotools_builder_set_config (self, g_value_get_boxed (value));
-      break;
-
-    case PROP_DEVICE:
-      ide_autotools_builder_set_device (self, g_value_get_object (value));
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
-}
-
-static void
 ide_autotools_builder_class_init (IdeAutotoolsBuilderClass *klass)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
   IdeBuilderClass *builder_class = IDE_BUILDER_CLASS (klass);
 
-  object_class->finalize = ide_autotools_builder_finalize;
-  object_class->get_property = ide_autotools_builder_get_property;
-  object_class->set_property = ide_autotools_builder_set_property;
-
   builder_class->build_async = ide_autotools_builder_build_async;
   builder_class->build_finish = ide_autotools_builder_build_finish;
-
-  properties [PROP_CONFIG] =
-    g_param_spec_boxed ("config",
-                        "Config",
-                        "The configuration for the build.",
-                        G_TYPE_KEY_FILE,
-                        (G_PARAM_READWRITE |
-                         G_PARAM_CONSTRUCT_ONLY |
-                         G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_DEVICE] =
-    g_param_spec_object ("device",
-                         "Device",
-                         "The device to build for.",
-                         IDE_TYPE_DEVICE,
-                         (G_PARAM_READWRITE |
-                          G_PARAM_CONSTRUCT_ONLY |
-                          G_PARAM_STATIC_STRINGS));
-
-  g_object_class_install_properties (object_class, LAST_PROP, properties);
 }
 
 static void
@@ -389,33 +227,3 @@ ide_autotools_builder_get_needs_bootstrap (IdeAutotoolsBuilder *self)
 
   return FALSE;
 }
-
-void
-ide_autotools_builder_bootstrap_async (IdeAutotoolsBuilder *self,
-                                       GCancellable        *cancellable,
-                                       GAsyncReadyCallback  callback,
-                                       gpointer             user_data)
-{
-  g_autoptr(GTask) task = NULL;
-
-  g_return_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self));
-  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  task = g_task_new (self, cancellable, callback, user_data);
-
-  g_key_file_set_boolean (self->config, "autotools", "bootstrap", TRUE);
-
-  g_task_return_boolean (task, TRUE);
-}
-
-gboolean
-ide_autotools_builder_bootstrap_finish (IdeAutotoolsBuilder  *self,
-                                        GAsyncResult         *result,
-                                        GError              **error)
-{
-  GTask *task = (GTask *)result;
-
-  g_return_val_if_fail (IDE_IS_AUTOTOOLS_BUILDER (self), FALSE);
-
-  return g_task_propagate_boolean (task, error);
-}
diff --git a/plugins/autotools/ide-autotools-builder.h b/plugins/autotools/ide-autotools-builder.h
index 04f8821..d6558cf 100644
--- a/plugins/autotools/ide-autotools-builder.h
+++ b/plugins/autotools/ide-autotools-builder.h
@@ -29,13 +29,6 @@ G_DECLARE_FINAL_TYPE (IdeAutotoolsBuilder, ide_autotools_builder, IDE, AUTOTOOLS
 
 GFile    *ide_autotools_builder_get_build_directory (IdeAutotoolsBuilder  *self);
 gboolean  ide_autotools_builder_get_needs_bootstrap (IdeAutotoolsBuilder  *self);
-void      ide_autotools_builder_bootstrap_async     (IdeAutotoolsBuilder  *self,
-                                                     GCancellable         *cancellable,
-                                                     GAsyncReadyCallback   callback,
-                                                     gpointer              user_data);
-gboolean  ide_autotools_builder_bootstrap_finish    (IdeAutotoolsBuilder   *self,
-                                                     GAsyncResult          *result,
-                                                     GError               **error);
 
 G_END_DECLS
 
diff --git a/plugins/build-tools/gbp-build-panel.c b/plugins/build-tools/gbp-build-panel.c
index 4a84da0..481da28 100644
--- a/plugins/build-tools/gbp-build-panel.c
+++ b/plugins/build-tools/gbp-build-panel.c
@@ -34,6 +34,7 @@ struct _GbpBuildPanel
   EggBindingGroup  *bindings;
 
   IdeDevice        *device;
+  IdeRuntime       *runtime;
 
   GtkListBox       *diagnostics;
   GtkRevealer      *status_revealer;
@@ -44,6 +45,9 @@ struct _GbpBuildPanel
   GtkListBox       *devices;
   GtkPopover       *device_popover;
   GtkLabel         *errors_label;
+  GtkListBox       *runtimes;
+  GtkLabel         *runtime_label;
+  GtkPopover       *runtime_popover;
   GtkLabel         *warnings_label;
 
   guint             running_time_source;
@@ -58,6 +62,8 @@ enum {
   PROP_0,
   PROP_DEVICE,
   PROP_DEVICE_MANAGER,
+  PROP_RUNTIME,
+  PROP_RUNTIME_MANAGER,
   PROP_RESULT,
   LAST_PROP
 };
@@ -65,6 +71,36 @@ enum {
 static GParamSpec *properties [LAST_PROP];
 
 static GtkWidget *
+create_runtime_row (gpointer item,
+                    gpointer user_data)
+{
+  IdeRuntime *runtime = item;
+  GtkListBoxRow *row;
+  const gchar *name;
+  const gchar *id;
+  GtkLabel *label;
+
+  g_assert (IDE_IS_RUNTIME (runtime));
+
+  row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+                      "visible", TRUE,
+                      NULL);
+
+  id = ide_runtime_get_id (runtime);
+  g_object_set_data_full (G_OBJECT (row), "IDE_RUNTIME_ID", g_strdup (id), g_free);
+
+  name = ide_runtime_get_title (runtime);
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "label", name,
+                        "xalign", 0.0f,
+                        "visible", TRUE,
+                        NULL);
+  gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (label));
+
+  return GTK_WIDGET (row);
+}
+
+static GtkWidget *
 create_device_row (gpointer item,
                    gpointer user_data)
 {
@@ -123,14 +159,43 @@ static void
 gbp_build_panel_set_device_manager (GbpBuildPanel    *self,
                                     IdeDeviceManager *device_manager)
 {
-  g_return_if_fail (GBP_IS_BUILD_PANEL (self));
-  g_return_if_fail (!device_manager || IDE_IS_DEVICE_MANAGER (device_manager));
+  g_assert (GBP_IS_BUILD_PANEL (self));
+  g_assert (!device_manager || IDE_IS_DEVICE_MANAGER (device_manager));
 
   gtk_list_box_bind_model (self->devices,
                            G_LIST_MODEL (device_manager),
                            create_device_row, NULL, NULL);
 }
 
+static void
+gbp_build_panel_set_runtime (GbpBuildPanel *self,
+                             IdeRuntime    *runtime)
+{
+  g_return_if_fail (GBP_IS_BUILD_PANEL (self));
+  g_return_if_fail (!runtime || IDE_IS_RUNTIME (runtime));
+
+  if (g_set_object (&self->runtime, runtime))
+    {
+      const gchar *name = NULL;
+
+      if (runtime != NULL)
+        name = ide_runtime_get_title (runtime);
+      gtk_label_set_label (self->runtime_label, name);
+    }
+}
+
+static void
+gbp_build_panel_set_runtime_manager (GbpBuildPanel     *self,
+                                     IdeRuntimeManager *runtime_manager)
+{
+  g_assert (GBP_IS_BUILD_PANEL (self));
+  g_assert (IDE_IS_RUNTIME_MANAGER (runtime_manager));
+
+  gtk_list_box_bind_model (self->runtimes,
+                           G_LIST_MODEL (runtime_manager),
+                           create_runtime_row, NULL, NULL);
+}
+
 void
 gbp_build_panel_add_error (GbpBuildPanel *self,
                            const gchar   *message)
@@ -300,6 +365,25 @@ gbp_build_panel_device_activated (GbpBuildPanel *self,
 }
 
 static void
+gbp_build_panel_runtime_activated (GbpBuildPanel *self,
+                                   GtkListBoxRow *row,
+                                   GtkListBox    *list_box)
+{
+  const gchar *id;
+
+  g_assert (GBP_IS_BUILD_PANEL (self));
+  g_assert (GTK_IS_LIST_BOX_ROW (row));
+  g_assert (GTK_IS_LIST_BOX (list_box));
+
+  if ((id = g_object_get_data (G_OBJECT (row), "IDE_RUNTIME_ID")))
+    ide_widget_action (GTK_WIDGET (self),
+                       "build-tools", "runtime",
+                       g_variant_new_string (id));
+
+  gtk_widget_hide (GTK_WIDGET (self->runtime_popover));
+}
+
+static void
 gbp_build_panel_diagnostic_activated (GbpBuildPanel *self,
                                       GtkListBoxRow *row,
                                       GtkListBox    *list_box)
@@ -340,6 +424,7 @@ gbp_build_panel_destroy (GtkWidget *widget)
   g_clear_object (&self->bindings);
   g_clear_object (&self->signals);
   g_clear_object (&self->device);
+  g_clear_object (&self->runtime);
 
   GTK_WIDGET_CLASS (gbp_build_panel_parent_class)->destroy (widget);
 }
@@ -362,6 +447,10 @@ gbp_build_panel_get_property (GObject    *object,
       g_value_set_object (value, self->result);
       break;
 
+    case PROP_RUNTIME:
+      g_value_set_object (value, self->runtime);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
     }
@@ -389,6 +478,14 @@ gbp_build_panel_set_property (GObject      *object,
       gbp_build_panel_set_result (self, g_value_get_object (value));
       break;
 
+    case PROP_RUNTIME_MANAGER:
+      gbp_build_panel_set_runtime_manager (self, g_value_get_object (value));
+      break;
+
+    case PROP_RUNTIME:
+      gbp_build_panel_set_runtime (self, g_value_get_object (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
     }
@@ -419,6 +516,20 @@ gbp_build_panel_class_init (GbpBuildPanelClass *klass)
                          IDE_TYPE_DEVICE_MANAGER,
                          (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
 
+  properties [PROP_RUNTIME_MANAGER] =
+    g_param_spec_object ("runtime-manager",
+                         "Runtime Manager",
+                         "Runtime Manager",
+                         IDE_TYPE_RUNTIME_MANAGER,
+                         (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_RUNTIME] =
+    g_param_spec_object ("runtime",
+                         "Runtime",
+                         "Runtime",
+                         IDE_TYPE_RUNTIME,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   properties [PROP_RESULT] =
     g_param_spec_object ("result",
                          "Result",
@@ -437,6 +548,9 @@ gbp_build_panel_class_init (GbpBuildPanelClass *klass)
   gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, diagnostics);
   gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, errors_label);
   gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, running_time_label);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, runtimes);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, runtime_label);
+  gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, runtime_popover);
   gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, status_label);
   gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, status_revealer);
   gtk_widget_class_bind_template_child (widget_class, GbpBuildPanel, warnings_label);
@@ -467,6 +581,12 @@ gbp_build_panel_init (GbpBuildPanel *self)
                            self,
                            G_CONNECT_SWAPPED);
 
+  g_signal_connect_object (self->runtimes,
+                           "row-activated",
+                           G_CALLBACK (gbp_build_panel_runtime_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+
   g_signal_connect_object (self->diagnostics,
                            "row-activated",
                            G_CALLBACK (gbp_build_panel_diagnostic_activated),
diff --git a/plugins/build-tools/gbp-build-panel.ui b/plugins/build-tools/gbp-build-panel.ui
index 8cbc219..a28464c 100644
--- a/plugins/build-tools/gbp-build-panel.ui
+++ b/plugins/build-tools/gbp-build-panel.ui
@@ -27,9 +27,9 @@
             </child>
             <child>
               <object class="GtkLabel" id="label2">
-                <property name="label" translatable="yes">Framework:</property>
+                <property name="label" translatable="yes">Runtime:</property>
                 <property name="xalign">1.0</property>
-                <property name="visible">false</property>
+                <property name="visible">true</property>
                 <style>
                   <class name="dim-label"/>
                 </style>
@@ -120,6 +120,9 @@
                         <property name="visible">true</property>
                         <property name="hexpand">false</property>
                         <property name="xalign">0.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
                       </object>
                     </child>
                   </object>
@@ -132,8 +135,37 @@
             </child>
             <child>
               <object class="GtkMenuButton">
-                <property name="label" translatable="yes">framework</property>
-                <property name="visible">false</property>
+                <property name="focus-on-click">false</property>
+                <property name="popover">runtime_popover</property>
+                <property name="visible">true</property>
+                <style>
+                  <class name="flat"/>
+                </style>
+                <child>
+                  <object class="GtkBox">
+                    <property name="spacing">6</property>
+                    <property name="visible">true</property>
+                    <child>
+                      <object class="GtkLabel" id="runtime_label">
+                        <property name="visible">true</property>
+                        <property name="ellipsize">end</property>
+                        <property name="hexpand">false</property>
+                        <property name="xalign">0.0</property>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="icon-name">pan-down-symbolic</property>
+                        <property name="visible">true</property>
+                        <property name="hexpand">false</property>
+                        <property name="xalign">0.0</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                </child>
               </object>
               <packing>
                 <property name="left-attach">1</property>
@@ -359,4 +391,23 @@
       </object>
     </child>
   </object>
+  <object class="GtkPopover" id="runtime_popover">
+    <child>
+      <object class="EggScrolledWindow">
+        <property name="min-content-width">100</property>
+        <property name="max-content-width">300</property>
+        <property name="max-content-height">300</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkListBox" id="runtimes">
+            <property name="selection-mode">none</property>
+            <property name="visible">true</property>
+            <style>
+              <class name="buildpanel"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
 </interface>
diff --git a/plugins/build-tools/gbp-build-tool.c b/plugins/build-tools/gbp-build-tool.c
index 784f62c..fb71b02 100644
--- a/plugins/build-tools/gbp-build-tool.c
+++ b/plugins/build-tools/gbp-build-tool.c
@@ -30,6 +30,8 @@ struct _GbpBuildTool
   gint64  build_start;
 };
 
+static gint parallel = -1;
+
 static void application_tool_init (IdeApplicationToolInterface *iface);
 
 G_DEFINE_TYPE_EXTENDED (GbpBuildTool, gbp_build_tool, G_TYPE_OBJECT, 0,
@@ -155,12 +157,15 @@ gbp_build_tool_new_context_cb (GObject      *object,
   g_autoptr(IdeBuilder) builder = NULL;
   g_autoptr(IdeBuildResult) build_result = NULL;
   g_autoptr(IdeDevice) device = NULL;
+  g_autoptr(IdeConfiguration) configuration = NULL;
   IdeDeviceManager *device_manager;
+  IdeRuntimeManager *runtime_manager;
   IdeBuildSystem *build_system;
   GbpBuildTool *self;
   IdeBuilderBuildFlags flags;
-  GKeyFile *config;
+  IdeRuntime *runtime;
   const gchar *device_id;
+  const gchar *runtime_id;
   GError *error = NULL;
 
   g_assert (G_IS_TASK (task));
@@ -175,13 +180,16 @@ gbp_build_tool_new_context_cb (GObject      *object,
       return;
     }
 
-  config = g_object_get_data (G_OBJECT (task), "CONFIG");
   flags = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "FLAGS"));
 
   device_id = g_object_get_data (G_OBJECT (task), "DEVICE_ID");
   device_manager = ide_context_get_device_manager (context);
   device = ide_device_manager_get_device (device_manager, device_id);
 
+  runtime_id = g_object_get_data (G_OBJECT (task), "RUNTIME_ID");
+  runtime_manager = ide_context_get_runtime_manager (context);
+  runtime = ide_runtime_manager_get_runtime (runtime_manager, runtime_id);
+
   if (device == NULL)
     {
       /* TODO: Wait for devices to settle. */
@@ -193,12 +201,20 @@ gbp_build_tool_new_context_cb (GObject      *object,
       return;
     }
 
+  configuration = ide_configuration_new ("command-line-build", device, runtime);
+
+  if (parallel > -1)
+    {
+      g_autofree gchar *str = g_strdup_printf ("%d", parallel);
+      ide_configuration_setenv (configuration, "PARALLEL", str);
+    }
+
   print_build_info (context, device);
 
   /* TODO: Support custom configs */
 
   build_system = ide_context_get_build_system (context);
-  builder = ide_build_system_get_builder (build_system, config, device, &error);
+  builder = ide_build_system_get_builder (build_system, configuration, &error);
 
   if (builder == NULL)
     {
@@ -241,12 +257,11 @@ gbp_build_tool_run_async (IdeApplicationTool  *tool,
   g_autoptr(GTask) task = NULL;
   g_autofree gchar *project_path = NULL;
   g_autofree gchar *device_id = NULL;
+  g_autofree gchar *runtime_id = NULL;
   g_autoptr(GFile) project_file = NULL;
   g_autoptr(GOptionContext) opt_context = NULL;
-  g_autoptr(GKeyFile) config = NULL;
   g_auto(GStrv) strv = NULL;
   gboolean clean = FALSE;
-  gint parallel = -1;
   IdeBuilderBuildFlags flags = 0;
   GError *error = NULL;
   const GOptionEntry entries[] = {
@@ -255,6 +270,9 @@ gbp_build_tool_run_async (IdeApplicationTool  *tool,
     { "device", 'd', 0, G_OPTION_ARG_STRING, &device_id,
       N_("The id of the device to build for"),
       N_("local") },
+    { "runtime", 'd', 0, G_OPTION_ARG_STRING, &runtime_id,
+      N_("The runtime to use for building"),
+      N_("system") },
     { "parallel", 'j', 0, G_OPTION_ARG_INT, &parallel,
       N_("Number of workers to use when building"),
       N_("N") },
@@ -288,16 +306,14 @@ gbp_build_tool_run_async (IdeApplicationTool  *tool,
   if (device_id == NULL)
     device_id = g_strdup ("local");
 
-  config = g_key_file_new ();
-
-  if (parallel >= -1)
-    g_key_file_set_integer (config, "parallel", "workers", parallel);
-
   if (clean)
-    flags |= IDE_BUILDER_BUILD_FLAGS_CLEAN;
+    {
+      flags |= IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN;
+      flags |= IDE_BUILDER_BUILD_FLAGS_NO_BUILD;
+    }
 
   g_object_set_data_full (G_OBJECT (task), "DEVICE_ID", g_strdup (device_id), g_free);
-  g_object_set_data_full (G_OBJECT (task), "CONFIG", g_key_file_ref (config), 
(GDestroyNotify)g_key_file_unref);
+  g_object_set_data_full (G_OBJECT (task), "RUNTIME_ID", g_strdup (runtime_id), g_free);
   g_object_set_data (G_OBJECT (task), "FLAGS", GINT_TO_POINTER (flags));
 
   ide_context_new_async (project_file,
diff --git a/plugins/build-tools/gbp-build-workbench-addin.c b/plugins/build-tools/gbp-build-workbench-addin.c
index df558f8..8735653 100644
--- a/plugins/build-tools/gbp-build-workbench-addin.c
+++ b/plugins/build-tools/gbp-build-workbench-addin.c
@@ -39,6 +39,7 @@ struct _GbpBuildWorkbenchAddin
   GSimpleActionGroup *actions;
   GCancellable       *cancellable;
   IdeDevice          *device;
+  IdeRuntime         *runtime;
 };
 
 static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
@@ -51,6 +52,7 @@ enum {
   PROP_0,
   PROP_DEVICE,
   PROP_RESULT,
+  PROP_RUNTIME,
   LAST_PROP
 };
 
@@ -76,6 +78,25 @@ gbp_build_workbench_addin_set_device (GbpBuildWorkbenchAddin *self,
 }
 
 static void
+gbp_build_workbench_addin_set_runtime (GbpBuildWorkbenchAddin *self,
+                                       IdeRuntime             *runtime)
+{
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_RUNTIME (runtime));
+
+  if (g_set_object (&self->runtime, runtime))
+    {
+      const gchar *id = ide_runtime_get_id (runtime);
+      GAction *action;
+
+      action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), "runtime");
+      g_simple_action_set_state (G_SIMPLE_ACTION (action), g_variant_new_string (id));
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUNTIME]);
+    }
+}
+
+static void
 gbp_build_workbench_addin_set_result (GbpBuildWorkbenchAddin *self,
                                       IdeBuildResult         *result)
 {
@@ -147,6 +168,7 @@ gbp_build_workbench_addin_do_build (GbpBuildWorkbenchAddin *self,
                                     IdeBuilderBuildFlags    flags)
 {
   g_autoptr(IdeBuilder) builder = NULL;
+  g_autoptr(IdeConfiguration) configuration = NULL;
   g_autoptr(GError) error = NULL;
   IdeBuildSystem *build_system;
   IdeWorkbench *workbench;
@@ -164,7 +186,13 @@ gbp_build_workbench_addin_do_build (GbpBuildWorkbenchAddin *self,
   workbench = ide_widget_get_workbench (GTK_WIDGET (self->panel));
   context = ide_workbench_get_context (workbench);
   build_system = ide_context_get_build_system (context);
-  builder = ide_build_system_get_builder (build_system, NULL, self->device, &error);
+
+  /*
+   * TODO: Use a configuration stored on the workbench.
+   */
+  configuration = ide_configuration_new ("fixme.save.config.for.later", self->device, self->runtime);
+
+  builder = ide_build_system_get_builder (build_system, configuration, &error);
 
   if (error != NULL)
     {
@@ -217,7 +245,7 @@ gbp_build_workbench_addin_rebuild (GSimpleAction *action,
   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_REBUILD);
+  gbp_build_workbench_addin_do_build (self, IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN);
 }
 
 static void
@@ -230,7 +258,9 @@ gbp_build_workbench_addin_clean (GSimpleAction *action,
   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_CLEAN);
+  gbp_build_workbench_addin_do_build (self,
+                                      (IDE_BUILDER_BUILD_FLAGS_FORCE_CLEAN |
+                                       IDE_BUILDER_BUILD_FLAGS_NO_BUILD));
 }
 
 static void
@@ -288,6 +318,31 @@ gbp_build_workbench_addin_device (GSimpleAction *action,
   gbp_build_workbench_addin_set_device (self, device);
 }
 
+static void
+gbp_build_workbench_addin_runtime (GSimpleAction *action,
+                                   GVariant      *param,
+                                   gpointer       user_data)
+{
+  GbpBuildWorkbenchAddin *self = user_data;
+  IdeRuntimeManager *runtime_manager;
+  IdeContext *context;
+  IdeRuntime *runtime;
+  const gchar *id;
+
+  g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (self->workbench));
+
+  id = g_variant_get_string (param, NULL);
+  if (id == NULL)
+    id = "system";
+
+  context = ide_workbench_get_context (self->workbench);
+  runtime_manager = ide_context_get_runtime_manager (context);
+  runtime = ide_runtime_manager_get_runtime (runtime_manager, id);
+
+  gbp_build_workbench_addin_set_runtime (self, runtime);
+}
+
 static const GActionEntry actions[] = {
   { "build", gbp_build_workbench_addin_build },
   { "rebuild", gbp_build_workbench_addin_rebuild },
@@ -296,6 +351,7 @@ static const GActionEntry actions[] = {
   { "deploy", gbp_build_workbench_addin_deploy },
   { "export", gbp_build_workbench_addin_export },
   { "device", NULL, "s", "'local'", gbp_build_workbench_addin_device },
+  { "runtime", NULL, "s", "'system'", gbp_build_workbench_addin_runtime },
 };
 
 static void
@@ -308,6 +364,8 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
   IdeContext *context;
   IdeDeviceManager *device_manager;
   IdeDevice *device;
+  IdeRuntimeManager *runtime_manager;
+  IdeRuntime *runtime;
 
   g_assert (IDE_IS_WORKBENCH_ADDIN (addin));
   g_assert (GBP_IS_BUILD_WORKBENCH_ADDIN (self));
@@ -318,12 +376,16 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
   context = ide_workbench_get_context (workbench);
   device_manager = ide_context_get_device_manager (context);
   device = ide_device_manager_get_device (device_manager, "local");
+  runtime_manager = ide_context_get_runtime_manager (context);
+  runtime = ide_runtime_manager_get_runtime (runtime_manager, "system");
 
   editor = ide_workbench_get_perspective_by_name (workbench, "editor");
   pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
   self->panel = g_object_new (GBP_TYPE_BUILD_PANEL,
-                              "device", device,
                               "device-manager", device_manager,
+                              "device", device,
+                              "runtime-manager", runtime_manager,
+                              "runtime", runtime,
                               "visible", TRUE,
                               NULL);
   ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane),
@@ -341,8 +403,10 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
 
   g_object_bind_property (self, "result", self->panel, "result", 0);
   g_object_bind_property (self, "device", self->panel, "device", 0);
+  g_object_bind_property (self, "runtime", self->panel, "runtime", 0);
 
   gbp_build_workbench_addin_set_device (self, device);
+  gbp_build_workbench_addin_set_runtime (self, runtime);
 }
 
 static void
@@ -387,6 +451,10 @@ gbp_build_workbench_addin_get_property (GObject    *object,
       g_value_set_object (value, self->result);
       break;
 
+    case PROP_RUNTIME:
+      g_value_set_object (value, self->runtime);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
     }
@@ -406,6 +474,10 @@ gbp_build_workbench_addin_set_property (GObject      *object,
       gbp_build_workbench_addin_set_device (self, g_value_get_object (value));
       break;
 
+    case PROP_RUNTIME:
+      gbp_build_workbench_addin_set_runtime (self, g_value_get_object (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
     }
@@ -420,6 +492,7 @@ gbp_build_workbench_addin_finalize (GObject *object)
   g_clear_object (&self->bindings);
   g_clear_object (&self->actions);
   g_clear_object (&self->result);
+  g_clear_object (&self->runtime);
   g_clear_object (&self->cancellable);
 
   G_OBJECT_CLASS (gbp_build_workbench_addin_parent_class)->finalize (object);
@@ -448,6 +521,13 @@ gbp_build_workbench_addin_class_init (GbpBuildWorkbenchAddinClass *klass)
                          IDE_TYPE_BUILD_RESULT,
                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
+  properties [PROP_RUNTIME] =
+    g_param_spec_object ("runtime",
+                         "Runtime",
+                         "Runtime",
+                         IDE_TYPE_RUNTIME,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_properties (object_class, LAST_PROP, properties);
 }
 
diff --git a/plugins/xdg-app/Makefile.am b/plugins/xdg-app/Makefile.am
new file mode 100644
index 0000000..4300f6b
--- /dev/null
+++ b/plugins/xdg-app/Makefile.am
@@ -0,0 +1,40 @@
+if ENABLE_XDG_APP_PLUGIN
+
+DISTCLEANFILES =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libxdg-app-plugin.la
+dist_plugin_DATA = xdg-app.plugin
+
+libxdg_app_plugin_la_SOURCES = \
+       gbp-xdg-runtime-provider.c \
+       gbp-xdg-runtime-provider.h \
+       gbp-xdg-runtime.c \
+       gbp-xdg-runtime.h \
+       gbp-xdg-plugin.c \
+       $(NULL)
+
+libxdg_app_plugin_la_CFLAGS = \
+       $(LIBIDE_CFLAGS) \
+       $(XDG_APP_CFLAGS) \
+       $(OPTIMIZE_CFLAGS) \
+       -I$(top_srcdir)/libide \
+       $(NULL)
+
+libxdg_app_plugin_la_LIBADD = $(XDG_APP_LIBS)
+
+libxdg_app_plugin_la_LDFLAGS = \
+       $(OPTIMIZE_LDFLAGS) \
+       -avoid-version \
+       -module \
+       -export-regex peas_register_types \
+       $(NULL)
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/xdg-app/configure.ac b/plugins/xdg-app/configure.ac
new file mode 100644
index 0000000..53875d6
--- /dev/null
+++ b/plugins/xdg-app/configure.ac
@@ -0,0 +1,24 @@
+m4_define([xdg_app_required_version], [0.4.8])
+
+# --enable-xdg-app-plugin=yes/no/auto
+AC_ARG_ENABLE([xdg-app-plugin],
+              [AS_HELP_STRING([--enable-xdg-app-plugin=@<:@yes/no/auto@:>@],
+                              [Build with support for xdg-app.])],
+              [enable_xdg_app_plugin=$enableval],
+              [enable_xdg_app_plugin=auto])
+
+AS_IF([test "$enable_xdg_app_plugin" != no],[
+       PKG_CHECK_MODULES(XDG_APP,
+                         [xdg-app >= xdg_app_required_version],
+                         [have_xdg_app=yes],
+                         [have_xdg_app=no])
+
+       AS_IF([test "$enable_xdg_app_plugin" = "yes" && "$have_xdg_app" = "no"],[
+              AC_MSG_ERROR([--enable-xdg-app-plugin requires xdg-app >= xdg_app_required_version])
+       ])
+
+       enable_xdg_app_plugin=yes
+])
+
+AM_CONDITIONAL(ENABLE_XDG_APP_PLUGIN, test x$enable_xdg_app_plugin = xyes)
+AC_CONFIG_FILES([plugins/xdg-app/Makefile])
diff --git a/plugins/xdg-app/gbp-xdg-plugin.c b/plugins/xdg-app/gbp-xdg-plugin.c
new file mode 100644
index 0000000..5fb941f
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-plugin.c
@@ -0,0 +1,30 @@
+/* gbp-xdg-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 <libpeas/peas.h>
+#include <ide.h>
+
+#include "gbp-xdg-runtime-provider.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_RUNTIME_PROVIDER,
+                                              GBP_TYPE_XDG_RUNTIME_PROVIDER);
+}
diff --git a/plugins/xdg-app/gbp-xdg-runtime-provider.c b/plugins/xdg-app/gbp-xdg-runtime-provider.c
new file mode 100644
index 0000000..b2ab5df
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-runtime-provider.c
@@ -0,0 +1,242 @@
+/* gbp-xdg-runtime-provider.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 <string.h>
+#include <xdg-app.h>
+
+#include "gbp-xdg-runtime.h"
+#include "gbp-xdg-runtime-provider.h"
+
+struct _GbpXdgRuntimeProvider
+{
+  GObject             parent_instance;
+  IdeRuntimeManager  *manager;
+  XdgAppInstallation *installation;
+  GCancellable       *cancellable;
+  GPtrArray          *runtimes;
+};
+
+static void runtime_provider_iface_init (IdeRuntimeProviderInterface *);
+
+G_DEFINE_TYPE_EXTENDED (GbpXdgRuntimeProvider, gbp_xdg_runtime_provider, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_RUNTIME_PROVIDER,
+                                               runtime_provider_iface_init))
+
+static inline void
+sanitize_name (gchar *name)
+{
+  gchar *tmp = strchr (name, '/');
+
+  if (tmp != NULL)
+    *tmp = '\0';
+}
+
+static void
+gbp_xdg_runtime_provider_load_worker (GTask        *task,
+                                      gpointer      source_object,
+                                      gpointer      task_data,
+                                      GCancellable *cancellable)
+{
+  GbpXdgRuntimeProvider *self = source_object;
+  IdeContext *context;
+  GPtrArray *ret;
+  GPtrArray *ar;
+  GError *error = NULL;
+  guint i;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (GBP_IS_XDG_RUNTIME_PROVIDER (self));
+  g_assert (IDE_IS_RUNTIME_MANAGER (self->manager));
+
+  context = ide_object_get_context (IDE_OBJECT (self->manager));
+
+  self->installation = xdg_app_installation_new_user (cancellable, &error);
+
+  if (self->installation == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ar = xdg_app_installation_list_installed_refs_by_kind (self->installation,
+                                                         XDG_APP_REF_KIND_RUNTIME,
+                                                         cancellable,
+                                                         &error);
+
+  if (ar == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  ret = g_ptr_array_new_with_free_func (g_object_unref);
+
+  for (i = 0; i < ar->len; i++)
+    {
+      XdgAppInstalledRef *ref = g_ptr_array_index (ar, i);
+      g_autofree gchar *str = NULL;
+      g_autofree gchar *id = NULL;
+      g_autofree gchar *name = NULL;
+      const gchar *arch;
+      const gchar *branch;
+      g_autofree gchar *metadata = NULL;
+      g_autofree gchar *sdk = NULL;
+      g_autoptr(GKeyFile) key_file = NULL;
+
+      g_assert (XDG_APP_IS_INSTALLED_REF (ref));
+
+      name = g_strdup (xdg_app_ref_get_name (XDG_APP_REF (ref)));
+
+      sanitize_name (name);
+
+      arch = xdg_app_ref_get_arch (XDG_APP_REF (ref));
+      branch = xdg_app_ref_get_branch (XDG_APP_REF (ref));
+
+      id = g_strdup_printf ("%s-%s-%s", name, branch, arch);
+      str = g_strdup_printf ("%s %s (%s)", name, branch, arch);
+
+      metadata = xdg_app_installed_ref_load_metadata (XDG_APP_INSTALLED_REF (ref),
+                                                      cancellable, &error);
+
+      if (metadata == NULL)
+        {
+          g_warning ("%s", error->message);
+          g_clear_error (&error);
+          continue;
+        }
+
+      key_file = g_key_file_new ();
+
+      if (!g_key_file_load_from_data (key_file, metadata, -1, G_KEY_FILE_NONE, &error))
+        {
+          /*
+           * If this is not really a runtime, but something like a locale, then
+           * the metadata file will not exist.
+           */
+          if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+            {
+              g_clear_error (&error);
+              continue;
+            }
+
+          g_warning ("%s", error->message);
+          g_clear_error (&error);
+          continue;
+        }
+
+      if (!(sdk = g_key_file_get_string (key_file, "Runtime", "sdk", NULL)))
+        sdk = g_strdup (name);
+
+      sanitize_name (sdk);
+
+      g_ptr_array_add (ret,
+                       g_object_new (GBP_TYPE_XDG_RUNTIME,
+                                     "branch", branch,
+                                     "sdk", sdk,
+                                     "platform", name,
+                                     "context", context,
+                                     "id", id,
+                                     "title", str,
+                                     NULL));
+    }
+
+  g_ptr_array_unref (ar);
+
+  g_task_return_pointer (task, ret, (GDestroyNotify)g_ptr_array_unref);
+}
+
+static void
+gbp_xdg_runtime_provider_load_cb (GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer      user_data)
+{
+  GbpXdgRuntimeProvider *self = (GbpXdgRuntimeProvider *)object;
+  GPtrArray *ret;
+  GError *error = NULL;
+  guint i;
+
+  g_assert (GBP_IS_XDG_RUNTIME_PROVIDER (self));
+  g_assert (G_IS_TASK (result));
+
+  if (!(ret = g_task_propagate_pointer (G_TASK (result), &error)))
+    {
+      g_warning ("%s", error->message);
+      g_clear_error (&error);
+      return;
+    }
+
+  for (i = 0; i < ret->len; i++)
+    {
+      IdeRuntime *runtime = g_ptr_array_index (ret, i);
+
+      ide_runtime_manager_add (self->manager, runtime);
+    }
+
+  self->runtimes = ret;
+}
+
+static void
+gbp_xdg_runtime_provider_load (IdeRuntimeProvider *provider,
+                               IdeRuntimeManager  *manager)
+{
+  GbpXdgRuntimeProvider *self = (GbpXdgRuntimeProvider *)provider;
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (GBP_IS_XDG_RUNTIME_PROVIDER (self));
+  g_assert (IDE_IS_RUNTIME_MANAGER (manager));
+
+  ide_set_weak_pointer (&self->manager, manager);
+
+  self->cancellable = g_cancellable_new ();
+
+  task = g_task_new (self, self->cancellable, gbp_xdg_runtime_provider_load_cb, NULL);
+  g_task_run_in_thread (task, gbp_xdg_runtime_provider_load_worker);
+}
+
+static void
+gbp_xdg_runtime_provider_unload (IdeRuntimeProvider *provider,
+                                 IdeRuntimeManager  *manager)
+{
+  GbpXdgRuntimeProvider *self = (GbpXdgRuntimeProvider *)provider;
+
+  g_assert (GBP_IS_XDG_RUNTIME_PROVIDER (self));
+  g_assert (IDE_IS_RUNTIME_MANAGER (manager));
+
+  if (self->cancellable != NULL)
+    g_cancellable_cancel (self->cancellable);
+
+  ide_clear_weak_pointer (&self->manager);
+  g_clear_object (&self->cancellable);
+}
+
+static void
+gbp_xdg_runtime_provider_class_init (GbpXdgRuntimeProviderClass *klass)
+{
+}
+
+static void
+gbp_xdg_runtime_provider_init (GbpXdgRuntimeProvider *self)
+{
+}
+
+static void
+runtime_provider_iface_init (IdeRuntimeProviderInterface *iface)
+{
+  iface->load = gbp_xdg_runtime_provider_load;
+  iface->unload = gbp_xdg_runtime_provider_unload;
+}
diff --git a/plugins/xdg-app/gbp-xdg-runtime-provider.h b/plugins/xdg-app/gbp-xdg-runtime-provider.h
new file mode 100644
index 0000000..d1440e6
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-runtime-provider.h
@@ -0,0 +1,32 @@
+/* gbp-xdg-runtime-provider.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_XDG_RUNTIME_PROVIDER_H
+#define GBP_XDG_RUNTIME_PROVIDER_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_XDG_RUNTIME_PROVIDER (gbp_xdg_runtime_provider_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpXdgRuntimeProvider, gbp_xdg_runtime_provider, GBP, XDG_RUNTIME_PROVIDER, GObject)
+
+G_END_DECLS
+
+#endif /* GBP_XDG_RUNTIME_PROVIDER_H */
diff --git a/plugins/xdg-app/gbp-xdg-runtime.c b/plugins/xdg-app/gbp-xdg-runtime.c
new file mode 100644
index 0000000..6b0f133
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-runtime.c
@@ -0,0 +1,317 @@
+/* gb-xdg-runtime.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 "gbp-xdg-runtime.h"
+
+struct _GbpXdgRuntime
+{
+  IdeRuntime parent_instance;
+
+  gchar *sdk;
+  gchar *platform;
+  gchar *branch;
+};
+
+G_DEFINE_TYPE (GbpXdgRuntime, gbp_xdg_runtime, IDE_TYPE_RUNTIME)
+
+enum {
+  PROP_0,
+  PROP_BRANCH,
+  PROP_PLATFORM,
+  PROP_SDK,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static gchar *
+get_build_directory (GbpXdgRuntime *self)
+{
+  IdeContext *context;
+  IdeProject *project;
+
+  g_assert (GBP_IS_XDG_RUNTIME (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  project = ide_context_get_project (context);
+
+  return g_build_filename (g_get_user_cache_dir (),
+                           "gnome-builder",
+                           "builds",
+                           ide_project_get_name (project),
+                           "xdg-app",
+                           ide_runtime_get_id (IDE_RUNTIME (self)),
+                           NULL);
+}
+
+static gboolean
+gbp_xdg_runtime_contains_program_in_path (IdeRuntime   *runtime,
+                                          const gchar  *program,
+                                          GCancellable *cancellable)
+{
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autoptr(GSubprocess) subprocess = NULL;
+
+  g_assert (IDE_IS_RUNTIME (runtime));
+  g_assert (program != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  launcher = ide_runtime_create_launcher (runtime, 0);
+
+  ide_subprocess_launcher_push_argv (launcher, "which");
+  ide_subprocess_launcher_push_argv (launcher, program);
+
+  subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, NULL);
+
+  return (subprocess != NULL) && g_subprocess_wait_check (subprocess, cancellable, NULL);
+}
+
+static void
+gbp_xdg_runtime_prebuild_worker (GTask        *task,
+                                 gpointer      source_object,
+                                 gpointer      task_data,
+                                 GCancellable *cancellable)
+{
+  GbpXdgRuntime *self = source_object;
+  g_autofree gchar *build_path = NULL;
+  g_autoptr(GFile) build_dir = NULL;
+  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(GFile) parent = NULL;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (GBP_IS_XDG_RUNTIME (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  build_path = get_build_directory (self);
+  build_dir = g_file_new_for_path (build_path);
+
+  if (g_file_query_exists (build_dir, cancellable))
+    {
+      g_task_return_boolean (task, TRUE);
+      return;
+    }
+
+  parent = g_file_get_parent (build_dir);
+
+  if (!g_file_query_exists (parent, cancellable))
+    {
+      if (!g_file_make_directory_with_parents (parent, cancellable, &error))
+        {
+          g_task_return_error (task, error);
+          return;
+        }
+    }
+
+  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+  subprocess = g_subprocess_launcher_spawn (launcher, &error,
+                                            "xdg-app",
+                                            "build-init",
+                                            build_path,
+                                            /* XXX: Fake name, probably okay, but
+                                             * can be proper once we get IdeConfiguration
+                                             * in place.
+                                             */
+                                            "org.gnome.Builder.XdgApp.Build",
+                                            self->sdk,
+                                            self->platform,
+                                            self->branch,
+                                            NULL);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+gbp_xdg_runtime_prebuild_async (IdeRuntime          *runtime,
+                                GCancellable        *cancellable,
+                                GAsyncReadyCallback  callback,
+                                gpointer             user_data)
+{
+  GbpXdgRuntime *self = (GbpXdgRuntime *)runtime;
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (GBP_IS_XDG_RUNTIME (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_run_in_thread (task, gbp_xdg_runtime_prebuild_worker);
+}
+
+static gboolean
+gbp_xdg_runtime_prebuild_finish (IdeRuntime    *runtime,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  GbpXdgRuntime *self = (GbpXdgRuntime *)runtime;
+
+  g_assert (GBP_IS_XDG_RUNTIME (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static IdeSubprocessLauncher *
+gbp_xdg_runtime_create_launcher (IdeRuntime  *runtime,
+                                 GError     **error)
+{
+  IdeSubprocessLauncher *ret;
+  GbpXdgRuntime *self = (GbpXdgRuntime *)runtime;
+
+  g_return_val_if_fail (GBP_IS_XDG_RUNTIME (self), NULL);
+
+  ret = IDE_RUNTIME_CLASS (gbp_xdg_runtime_parent_class)->create_launcher (runtime, error);
+
+  if (ret != NULL)
+    {
+      g_autofree gchar *build_path = get_build_directory (self);
+
+      ide_subprocess_launcher_push_argv (ret, "xdg-app");
+      ide_subprocess_launcher_push_argv (ret, "build");
+      ide_subprocess_launcher_push_argv (ret, build_path);
+    }
+
+  return ret;
+}
+
+static void
+gbp_xdg_runtime_prepare_configuration (IdeRuntime       *runtime,
+                                       IdeConfiguration *configuration)
+{
+  g_assert (IDE_IS_RUNTIME (runtime));
+  g_assert (IDE_IS_CONFIGURATION (configuration));
+
+  ide_configuration_setenv (configuration, "PREFIX", "/app");
+}
+
+static void
+gbp_xdg_runtime_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  GbpXdgRuntime *self = GBP_XDG_RUNTIME(object);
+
+  switch (prop_id)
+    {
+    case PROP_BRANCH:
+      g_value_set_string (value, self->branch);
+      break;
+
+    case PROP_PLATFORM:
+      g_value_set_string (value, self->platform);
+      break;
+
+    case PROP_SDK:
+      g_value_set_string (value, self->sdk);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_xdg_runtime_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  GbpXdgRuntime *self = GBP_XDG_RUNTIME(object);
+
+  switch (prop_id)
+    {
+    case PROP_BRANCH:
+      self->branch = g_value_dup_string (value);
+      break;
+
+    case PROP_PLATFORM:
+      self->platform = g_value_dup_string (value);
+      break;
+
+    case PROP_SDK:
+      self->sdk = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_xdg_runtime_finalize (GObject *object)
+{
+  GbpXdgRuntime *self = (GbpXdgRuntime *)object;
+
+  g_clear_pointer (&self->sdk, g_free);
+  g_clear_pointer (&self->platform, g_free);
+  g_clear_pointer (&self->branch, g_free);
+
+  G_OBJECT_CLASS (gbp_xdg_runtime_parent_class)->finalize (object);
+}
+
+static void
+gbp_xdg_runtime_class_init (GbpXdgRuntimeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeRuntimeClass *runtime_class = IDE_RUNTIME_CLASS (klass);
+
+  object_class->finalize = gbp_xdg_runtime_finalize;
+  object_class->get_property = gbp_xdg_runtime_get_property;
+  object_class->set_property = gbp_xdg_runtime_set_property;
+
+  runtime_class->prebuild_async = gbp_xdg_runtime_prebuild_async;
+  runtime_class->prebuild_finish = gbp_xdg_runtime_prebuild_finish;
+  runtime_class->create_launcher = gbp_xdg_runtime_create_launcher;
+  runtime_class->contains_program_in_path = gbp_xdg_runtime_contains_program_in_path;
+  runtime_class->prepare_configuration = gbp_xdg_runtime_prepare_configuration;
+
+  properties [PROP_BRANCH] =
+    g_param_spec_string ("branch",
+                         "Branch",
+                         "Branch",
+                         "master",
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PLATFORM] =
+    g_param_spec_string ("platform",
+                         "Platform",
+                         "Platform",
+                         "org.gnome.Platform",
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SDK] =
+    g_param_spec_string ("sdk",
+                         "Sdk",
+                         "Sdk",
+                         "org.gnome.Sdk",
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+gbp_xdg_runtime_init (GbpXdgRuntime *self)
+{
+}
diff --git a/plugins/xdg-app/gbp-xdg-runtime.h b/plugins/xdg-app/gbp-xdg-runtime.h
new file mode 100644
index 0000000..d2abc7a
--- /dev/null
+++ b/plugins/xdg-app/gbp-xdg-runtime.h
@@ -0,0 +1,32 @@
+/* gbp-xdg-runtime.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_XDG_RUNTIME_H
+#define GBP_XDG_RUNTIME_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_XDG_RUNTIME (gbp_xdg_runtime_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpXdgRuntime, gbp_xdg_runtime, GBP, XDG_RUNTIME, IdeRuntime)
+
+G_END_DECLS
+
+#endif /* GBP_XDG_RUNTIME_H */
diff --git a/plugins/xdg-app/xdg-app.plugin b/plugins/xdg-app/xdg-app.plugin
new file mode 100644
index 0000000..b8b3d19
--- /dev/null
+++ b/plugins/xdg-app/xdg-app.plugin
@@ -0,0 +1,7 @@
+[Plugin]
+Module=xdg-app-plugin
+Name=Xdg-App
+Description=Provides support for building with Xdg-App
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2016 Christian Hergert
+Builtin=true


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