[gnome-builder] libide-foundry: modernize build pipeline around run contexts



commit 54f1b8b2b9c6cbc8fc592427cdc9748596260cd9
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 21:12:07 2022 -0700

    libide-foundry: modernize build pipeline around run contexts
    
    This also cleans up how we process devices and deploy strategies, moves
    away from libdazzle, simplifies PTY usage, among more.

 src/libide/foundry/ide-build-manager.c         | 480 ++++++++++++++++++++-----
 src/libide/foundry/ide-build-manager.h         |   9 +
 src/libide/foundry/ide-deploy-strategy.c       | 145 +++-----
 src/libide/foundry/ide-deploy-strategy.h       | 116 +++---
 src/libide/foundry/ide-device-manager.c        | 407 ++-------------------
 src/libide/foundry/ide-device-manager.h        |  56 ++-
 src/libide/foundry/ide-device-provider.c       |  14 -
 src/libide/foundry/ide-pipeline-stage-mkdirs.c |   8 +-
 src/libide/foundry/ide-pipeline-stage.c        |   8 +-
 src/libide/foundry/ide-pipeline-stage.h        |   2 +-
 src/libide/foundry/ide-pipeline.c              | 464 +++++++++++++-----------
 src/libide/foundry/ide-pipeline.h              |   8 +
 src/libide/foundry/meson.build                 |   1 -
 13 files changed, 836 insertions(+), 882 deletions(-)
---
diff --git a/src/libide/foundry/ide-build-manager.c b/src/libide/foundry/ide-build-manager.c
index 132a39a54..2c9a5b478 100644
--- a/src/libide/foundry/ide-build-manager.c
+++ b/src/libide/foundry/ide-build-manager.c
@@ -22,14 +22,19 @@
 
 #include "config.h"
 
-#include <dazzle.h>
 #include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
+#include <libide-core.h>
 #include <libide-code.h>
+#include <libide-plugins.h>
 #include <libide-threading.h>
 #include <libide-vcs.h>
 
 #include "ide-build-manager.h"
 #include "ide-build-private.h"
+#include "ide-build-target.h"
+#include "ide-build-target-provider.h"
 #include "ide-config-manager.h"
 #include "ide-config.h"
 #include "ide-device-info.h"
@@ -37,12 +42,12 @@
 #include "ide-device.h"
 #include "ide-foundry-compat.h"
 #include "ide-pipeline.h"
-#include "ide-run-manager.h"
 #include "ide-runtime-manager.h"
 #include "ide-runtime-private.h"
 #include "ide-runtime.h"
 #include "ide-toolchain-manager.h"
 #include "ide-toolchain-private.h"
+#include "ide-triplet.h"
 
 /**
  * SECTION:ide-build-manager
@@ -70,9 +75,20 @@ struct _IdeBuildManager
 
   IdePipeline      *pipeline;
   GDateTime        *last_build_time;
-  DzlSignalGroup   *pipeline_signals;
+  IdeSignalGroup   *pipeline_signals;
+
+  IdeExtensionSetAdapter
+                   *build_target_providers;
+
+  char             *branch_name;
 
-  gchar            *branch_name;
+  /* The name of the default build target to build if no targets
+   * are specified. Setting to NULL (or empty string) implies that
+   * no target should be specified and therefore the build system
+   * should attempt a "full build" such as you would get by running
+   * `make` or `ninja`.
+   */
+  char             *default_build_target;
 
   GTimer           *running_time;
 
@@ -90,29 +106,40 @@ struct _IdeBuildManager
   guint             has_configured : 1;
 };
 
-static void initable_iface_init              (GInitableIface  *iface);
-static void ide_build_manager_set_can_build  (IdeBuildManager *self,
-                                              gboolean         can_build);
-static void ide_build_manager_action_build   (IdeBuildManager *self,
-                                              GVariant        *param);
-static void ide_build_manager_action_rebuild (IdeBuildManager *self,
-                                              GVariant        *param);
-static void ide_build_manager_action_cancel  (IdeBuildManager *self,
-                                              GVariant        *param);
-static void ide_build_manager_action_clean   (IdeBuildManager *self,
-                                              GVariant        *param);
-static void ide_build_manager_action_export  (IdeBuildManager *self,
-                                              GVariant        *param);
-static void ide_build_manager_action_install (IdeBuildManager *self,
-                                              GVariant        *param);
-
-DZL_DEFINE_ACTION_GROUP (IdeBuildManager, ide_build_manager, {
+typedef struct
+{
+  IdePipeline      *pipeline;
+  GPtrArray        *targets;
+  char             *default_target;
+  IdePipelinePhase  phase;
+} BuildState;
+
+static void initable_iface_init                           (GInitableIface  *iface);
+static void ide_build_manager_set_can_build               (IdeBuildManager *self,
+                                                           gboolean         can_build);
+static void ide_build_manager_action_build                (IdeBuildManager *self,
+                                                           GVariant        *param);
+static void ide_build_manager_action_rebuild              (IdeBuildManager *self,
+                                                           GVariant        *param);
+static void ide_build_manager_action_cancel               (IdeBuildManager *self,
+                                                           GVariant        *param);
+static void ide_build_manager_action_clean                (IdeBuildManager *self,
+                                                           GVariant        *param);
+static void ide_build_manager_action_export               (IdeBuildManager *self,
+                                                           GVariant        *param);
+static void ide_build_manager_action_install              (IdeBuildManager *self,
+                                                           GVariant        *param);
+static void ide_build_manager_action_default_build_target (IdeBuildManager *self,
+                                                           GVariant        *param);
+
+IDE_DEFINE_ACTION_GROUP (IdeBuildManager, ide_build_manager, {
   { "build", ide_build_manager_action_build },
   { "cancel", ide_build_manager_action_cancel },
   { "clean", ide_build_manager_action_clean },
   { "export", ide_build_manager_action_export },
   { "install", ide_build_manager_action_install },
   { "rebuild", ide_build_manager_action_rebuild },
+  { "default-build-target", ide_build_manager_action_default_build_target, "s", "''" },
 })
 
 G_DEFINE_TYPE_EXTENDED (IdeBuildManager, ide_build_manager, IDE_TYPE_OBJECT, G_TYPE_FLAG_FINAL,
@@ -144,6 +171,39 @@ enum {
 static GParamSpec *properties [N_PROPS];
 static guint signals [N_SIGNALS];
 
+static void
+build_state_free (BuildState *state)
+{
+  g_clear_pointer (&state->default_target, g_free);
+  g_clear_pointer (&state->targets, g_ptr_array_unref);
+  g_clear_object (&state->pipeline);
+  g_slice_free (BuildState, state);
+}
+
+static void
+ide_build_manager_action_default_build_target (IdeBuildManager *self,
+                                               GVariant        *param)
+{
+  const char *str;
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (param != NULL);
+  g_assert (g_variant_is_of_type (param, G_VARIANT_TYPE_STRING));
+
+  str = g_variant_get_string (param, NULL);
+  if (ide_str_empty0 (str))
+    str = NULL;
+
+  if (g_strcmp0 (str, self->default_build_target) != 0)
+    {
+      g_free (self->default_build_target);
+      self->default_build_target = g_strdup (str);
+      ide_build_manager_set_action_state (self,
+                                          "default-build-target",
+                                          g_variant_new_string (str ? str : ""));
+    }
+}
+
 static void
 ide_build_manager_rediagnose (IdeBuildManager *self)
 {
@@ -192,14 +252,7 @@ ide_build_manager_start_timer (IdeBuildManager *self)
   else
     self->running_time = g_timer_new ();
 
-  /*
-   * We use the DzlFrameSource for our timer callback because we only want to
-   * update at a rate somewhat close to a typical monitor refresh rate.
-   * Additionally, we want to handle drift (which that source does) so that we
-   * don't constantly fall behind.
-   */
   self->timer_source = g_timeout_add_seconds (1, timer_callback, self);
-
   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUNNING_TIME]);
 
   IDE_EXIT;
@@ -212,7 +265,7 @@ ide_build_manager_stop_timer (IdeBuildManager *self)
 
   g_assert (IDE_IS_BUILD_MANAGER (self));
 
-  dzl_clear_source (&self->timer_source);
+  g_clear_handle_id (&self->timer_source, g_source_remove);
 
   if (self->running_time != NULL)
     {
@@ -582,7 +635,6 @@ ide_build_manager_invalidate_pipeline (IdeBuildManager *self)
   g_autoptr(IdeTask) task = NULL;
   IdeConfigManager *config_manager;
   IdeDeviceManager *device_manager;
-  IdeRunManager *run_manager;
   IdeConfig *config;
   IdeContext *context;
   IdeDevice *device;
@@ -604,16 +656,10 @@ ide_build_manager_invalidate_pipeline (IdeBuildManager *self)
       g_assert (self->pipeline != NULL);
 
       self->building = FALSE;
-      dzl_clear_source (&self->timer_source);
+      g_clear_handle_id (&self->timer_source, g_source_remove);
       g_signal_emit (self, signals [BUILD_FAILED], 0, self->pipeline);
     }
 
-  /*
-   * Clear any cached build targets from the run manager.
-   */
-  run_manager = ide_run_manager_from_context (context);
-  ide_run_manager_set_build_target (run_manager, NULL);
-
   /*
    * Cancel and clear our previous pipeline and associated components
    * as they are not invalide.
@@ -654,7 +700,7 @@ ide_build_manager_invalidate_pipeline (IdeBuildManager *self)
                                  "device", device,
                                  NULL);
   ide_object_append (IDE_OBJECT (self), IDE_OBJECT (self->pipeline));
-  dzl_signal_group_set_target (self->pipeline_signals, self->pipeline);
+  ide_signal_group_set_target (self->pipeline_signals, self->pipeline);
 
   /*
    * Create a task to manage our async pipeline initialization state.
@@ -819,14 +865,15 @@ ide_build_manager_finalize (GObject *object)
 {
   IdeBuildManager *self = (IdeBuildManager *)object;
 
+  ide_clear_and_destroy_object (&self->build_target_providers);
   ide_clear_and_destroy_object (&self->pipeline);
   g_clear_object (&self->pipeline_signals);
   g_clear_object (&self->cancellable);
   g_clear_pointer (&self->last_build_time, g_date_time_unref);
   g_clear_pointer (&self->running_time, g_timer_destroy);
   g_clear_pointer (&self->branch_name, g_free);
-
-  dzl_clear_source (&self->timer_source);
+  g_clear_pointer (&self->default_build_target, g_free);
+  g_clear_handle_id (&self->timer_source, g_source_remove);
 
   G_OBJECT_CLASS (ide_build_manager_parent_class)->finalize (object);
 }
@@ -1166,33 +1213,33 @@ ide_build_manager_init (IdeBuildManager *self)
   self->cancellable = g_cancellable_new ();
   self->needs_rediagnose = TRUE;
 
-  self->pipeline_signals = dzl_signal_group_new (IDE_TYPE_PIPELINE);
+  self->pipeline_signals = ide_signal_group_new (IDE_TYPE_PIPELINE);
 
-  dzl_signal_group_connect_object (self->pipeline_signals,
+  ide_signal_group_connect_object (self->pipeline_signals,
                                    "diagnostic",
                                    G_CALLBACK (ide_build_manager_handle_diagnostic),
                                    self,
                                    G_CONNECT_SWAPPED);
 
-  dzl_signal_group_connect_object (self->pipeline_signals,
+  ide_signal_group_connect_object (self->pipeline_signals,
                                    "notify::busy",
                                    G_CALLBACK (ide_build_manager_notify_busy),
                                    self,
                                    G_CONNECT_SWAPPED);
 
-  dzl_signal_group_connect_object (self->pipeline_signals,
+  ide_signal_group_connect_object (self->pipeline_signals,
                                    "notify::message",
                                    G_CALLBACK (ide_build_manager_notify_message),
                                    self,
                                    G_CONNECT_SWAPPED);
 
-  dzl_signal_group_connect_object (self->pipeline_signals,
+  ide_signal_group_connect_object (self->pipeline_signals,
                                    "started",
                                    G_CALLBACK (ide_build_manager_pipeline_started),
                                    self,
                                    G_CONNECT_SWAPPED);
 
-  dzl_signal_group_connect_object (self->pipeline_signals,
+  ide_signal_group_connect_object (self->pipeline_signals,
                                    "finished",
                                    G_CALLBACK (ide_build_manager_pipeline_finished),
                                    self,
@@ -1381,49 +1428,127 @@ failure:
 }
 
 static void
-ide_build_manager_save_all_cb (GObject      *object,
-                               GAsyncResult *result,
-                               gpointer      user_data)
+ide_build_manager_build_list_targets_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
 {
-  IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+  IdeBuildManager *self = (IdeBuildManager *)object;
+  g_autoptr(GListModel) targets = NULL;
   g_autoptr(IdeTask) task = user_data;
   g_autoptr(GError) error = NULL;
-  IdeBuildManager *self;
-  GCancellable *cancellable;
-  GPtrArray *targets;
-  IdePipelinePhase phase;
+  BuildState *state;
 
   IDE_ENTRY;
 
-  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (IDE_IS_TASK (task));
 
-  self = ide_task_get_source_object (task);
-  cancellable = ide_task_get_cancellable (task);
-  targets = ide_task_get_task_data (task);
+  state = ide_task_get_task_data (task);
 
-  g_assert (IDE_IS_BUILD_MANAGER (self));
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (state != NULL);
+  g_assert (state->targets == NULL);
+  g_assert (state->default_target != NULL);
+  g_assert (IDE_IS_PIPELINE (state->pipeline));
 
-  if (!ide_buffer_manager_save_all_finish (buffer_manager, result, &error))
+  if ((targets = ide_build_manager_list_targets_finish (self, result, &error)))
     {
-      ide_task_return_error (task, g_steal_pointer (&error));
-      IDE_EXIT;
+      guint n_items = g_list_model_get_n_items (targets);
+
+      for (guint i = 0; i < n_items; i++)
+        {
+          g_autoptr(IdeBuildTarget) target = g_list_model_get_item (targets, i);
+          const char *name = ide_build_target_get_name (target);
+
+          if (g_strcmp0 (name, state->default_target) == 0)
+            {
+              state->targets = g_ptr_array_new_with_free_func (g_object_unref);
+              g_ptr_array_add (state->targets, g_steal_pointer (&target));
+              break;
+            }
+        }
     }
 
-  phase = ide_pipeline_get_requested_phase (self->pipeline);
+  if (error != NULL && !ide_error_ignore (error))
+    g_warning ("Failed to list build targets: %s", error->message);
 
-  ide_pipeline_build_targets_async (self->pipeline,
-                                    phase,
-                                    targets,
-                                    cancellable,
+  ide_pipeline_build_targets_async (state->pipeline,
+                                    state->phase,
+                                    state->targets,
+                                    ide_task_get_cancellable (task),
                                     ide_build_manager_build_targets_cb,
-                                    g_steal_pointer (&task));
+                                    g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+static void
+ide_build_manager_build_after_save (IdeTask *task)
+{
+  IdeBuildManager *self;
+  BuildState *state;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_TASK (task));
+
+  self = ide_task_get_source_object (task);
+  state = ide_task_get_task_data (task);
+
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+  g_assert (state != NULL);
+  g_assert (IDE_IS_PIPELINE (state->pipeline));
+
+  /* If a default build target was preferred instead of the build system
+   * default then we need to go fetch that from the build target providers.
+   * However, we can only do this if we are just building. Anything requiring
+   * us to install means that we have to do regular builds as that will happen
+   * anyway as part of the install process.
+   */
+  if (state->targets == NULL &&
+      state->default_target != NULL &&
+      state->phase < IDE_PIPELINE_PHASE_INSTALL)
+    ide_build_manager_list_targets_async (self,
+                                          ide_task_get_cancellable (task),
+                                          ide_build_manager_build_list_targets_cb,
+                                          g_object_ref (task));
+  else
+    ide_pipeline_build_targets_async (state->pipeline,
+                                      state->phase,
+                                      state->targets,
+                                      ide_task_get_cancellable (task),
+                                      ide_build_manager_build_targets_cb,
+                                      g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+static void
+ide_build_manager_save_all_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+  IdeBufferManager *buffer_manager = (IdeBufferManager *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  IdeBuildManager *self;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
+  g_assert (IDE_IS_TASK (task));
+
+  self = ide_task_get_source_object (task);
 
   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_DIAGNOSTICS]);
   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LAST_BUILD_TIME]);
   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUNNING_TIME]);
 
+  if (!ide_buffer_manager_save_all_finish (buffer_manager, result, &error))
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_build_manager_build_after_save (task);
+
   IDE_EXIT;
 }
 
@@ -1451,8 +1576,7 @@ ide_build_manager_build_async (IdeBuildManager     *self,
                                gpointer             user_data)
 {
   g_autoptr(IdeTask) task = NULL;
-  IdeBufferManager *buffer_manager;
-  IdeContext *context;
+  BuildState *state;
 
   IDE_ENTRY;
 
@@ -1460,16 +1584,13 @@ ide_build_manager_build_async (IdeBuildManager     *self,
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
   g_return_if_fail (!g_cancellable_is_cancelled (self->cancellable));
 
-  cancellable = dzl_cancellable_chain (cancellable, self->cancellable);
+  cancellable = ide_cancellable_chain (cancellable, self->cancellable);
 
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_source_tag (task, ide_build_manager_build_async);
   ide_task_set_priority (task, G_PRIORITY_LOW);
   ide_task_set_return_on_cancel (task, TRUE);
 
-  if (targets != NULL)
-    ide_task_set_task_data (task, _g_ptr_array_copy_objects (targets), g_ptr_array_unref);
-
   if (self->pipeline == NULL ||
       self->can_build == FALSE ||
       !ide_pipeline_is_ready (self->pipeline))
@@ -1487,6 +1608,17 @@ ide_build_manager_build_async (IdeBuildManager     *self,
       IDE_EXIT;
     }
 
+  /* Setup our state for the build process. We try to cache everything
+   * we need up front so that we don't need to deal with races between
+   * asynchronous operations.
+   */
+  state = g_slice_new0 (BuildState);
+  state->phase = phase;
+  state->default_target = g_strdup (self->default_build_target);
+  state->targets = targets ? _g_ptr_array_copy_objects (targets) : NULL;
+  state->pipeline = g_object_ref (self->pipeline);
+  ide_task_set_task_data (task, state, build_state_free);
+
   /*
    * Only update our "build time" if we are advancing to IDE_PIPELINE_PHASE_BUILD,
    * we don't really care about "builds" for configure stages and less.
@@ -1510,8 +1642,9 @@ ide_build_manager_build_async (IdeBuildManager     *self,
    */
   if ((phase & IDE_PIPELINE_PHASE_MASK) >= IDE_PIPELINE_PHASE_BUILD)
     {
-      context = ide_object_get_context (IDE_OBJECT (self));
-      buffer_manager = ide_buffer_manager_from_context (context);
+      IdeContext *context = ide_object_get_context (IDE_OBJECT (self));
+      IdeBufferManager *buffer_manager = ide_buffer_manager_from_context (context);
+
       ide_buffer_manager_save_all_async (buffer_manager,
                                          NULL,
                                          ide_build_manager_save_all_cb,
@@ -1519,12 +1652,7 @@ ide_build_manager_build_async (IdeBuildManager     *self,
       IDE_EXIT;
     }
 
-  ide_pipeline_build_targets_async (self->pipeline,
-                                    phase,
-                                    targets,
-                                    cancellable,
-                                    ide_build_manager_build_targets_cb,
-                                    g_steal_pointer (&task));
+  ide_build_manager_build_after_save (task);
 
   IDE_EXIT;
 }
@@ -1606,7 +1734,7 @@ ide_build_manager_clean_async (IdeBuildManager     *self,
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
   g_return_if_fail (!g_cancellable_is_cancelled (self->cancellable));
 
-  cancellable = dzl_cancellable_chain (cancellable, self->cancellable);
+  cancellable = ide_cancellable_chain (cancellable, self->cancellable);
 
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_source_tag (task, ide_build_manager_clean_async);
@@ -1715,7 +1843,7 @@ ide_build_manager_rebuild_async (IdeBuildManager     *self,
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
   g_return_if_fail (!g_cancellable_is_cancelled (self->cancellable));
 
-  cancellable = dzl_cancellable_chain (cancellable, self->cancellable);
+  cancellable = ide_cancellable_chain (cancellable, self->cancellable);
 
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_source_tag (task, ide_build_manager_rebuild_async);
@@ -1844,3 +1972,185 @@ _ide_build_manager_start (IdeBuildManager *self)
 
   ide_build_manager_invalidate (self);
 }
+
+static void
+ensure_build_target_providers (IdeBuildManager *self)
+{
+  g_assert (IDE_IS_BUILD_MANAGER (self));
+
+  if (self->build_target_providers != NULL)
+    return;
+
+  self->build_target_providers =
+    ide_extension_set_adapter_new (IDE_OBJECT (self),
+                                   peas_engine_get_default (),
+                                   IDE_TYPE_BUILD_TARGET_PROVIDER,
+                                   NULL, NULL);
+}
+
+typedef struct
+{
+  GListStore *store;
+  guint n_active;
+} ListTargets;
+
+static void
+list_targets_free (ListTargets *state)
+{
+  g_clear_object (&state->store);
+  g_slice_free (ListTargets, state);
+}
+
+static void
+ide_build_manager_list_targets_cb (GObject      *object,
+                                   GAsyncResult *result,
+                                   gpointer      user_data)
+{
+  IdeBuildTargetProvider *provider = (IdeBuildTargetProvider *)object;
+  g_autoptr(GPtrArray) targets = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  ListTargets *state;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_BUILD_TARGET_PROVIDER (provider));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  state = ide_task_get_task_data (task);
+
+  g_assert (state != NULL);
+  g_assert (G_IS_LIST_STORE (state->store));
+
+  targets = ide_build_target_provider_get_targets_finish (provider, result, &error);
+  IDE_PTR_ARRAY_SET_FREE_FUNC (targets, g_object_unref);
+
+  if (targets != NULL)
+    {
+      for (guint i = 0; i < targets->len; i++)
+        {
+          IdeBuildTarget *target = g_ptr_array_index (targets, i);
+
+          g_list_store_append (state->store, target);
+        }
+    }
+
+  state->n_active--;
+
+  if (state->n_active == 0)
+    {
+      if (g_list_model_get_n_items (G_LIST_MODEL (state->store)) > 0)
+        ide_task_return_object (task, g_steal_pointer (&state->store));
+      else
+        ide_task_return_new_error (task,
+                                   G_IO_ERROR,
+                                   G_IO_ERROR_NOT_SUPPORTED,
+                                   "No build targets could be located, perhaps project needs to be 
configured");
+    }
+
+  IDE_EXIT;
+}
+
+static void
+ide_build_manager_list_targets_foreach_cb (IdeExtensionSetAdapter *set,
+                                           PeasPluginInfo         *plugin_info,
+                                           PeasExtension          *exten,
+                                           gpointer                user_data)
+{
+  IdeBuildTargetProvider *provider = (IdeBuildTargetProvider *)exten;
+  IdeTask *task = user_data;
+  ListTargets *state;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
+  g_assert (IDE_IS_BUILD_TARGET_PROVIDER (provider));
+  g_assert (IDE_IS_TASK (task));
+
+  state = ide_task_get_task_data (task);
+
+  g_assert (state != NULL);
+  g_assert (G_IS_LIST_STORE (state->store));
+
+  state->n_active++;
+
+  ide_build_target_provider_get_targets_async (provider,
+                                               ide_task_get_cancellable (task),
+                                               ide_build_manager_list_targets_cb,
+                                               g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+void
+ide_build_manager_list_targets_async (IdeBuildManager     *self,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+  ListTargets *state;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_BUILD_MANAGER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  state = g_slice_new0 (ListTargets);
+  state->store = g_list_store_new (IDE_TYPE_BUILD_TARGET);
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_build_manager_list_targets_async);
+  ide_task_set_task_data (task, state, list_targets_free);
+
+  ensure_build_target_providers (self);
+
+  ide_extension_set_adapter_foreach (self->build_target_providers,
+                                     ide_build_manager_list_targets_foreach_cb,
+                                     task);
+
+  if (state->n_active == 0)
+    ide_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_NOT_SUPPORTED,
+                               "No build target providers found");
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_build_manager_list_targets_finish:
+ * @self: a #IdeBuildManager
+ * @error: a location for a #GError
+ *
+ * Lists available build targets.
+ *
+ * Completes a request to list available build targets that was started with
+ * ide_build_manager_list_targets_async(). If no build targetproviders were
+ * discovered or no build targets were found, this will return %NULL and @error
+ * will be set to %G_IO_ERROR_NOT_SUPPORTED.
+ *
+ * Otherwise, a non-empty #GListModel of #IdeBuildTarget will be returned.
+ *
+ * Returns: (transfer full): a #GListModel of #IdeBuildTarget if successful;
+ *   otherwise %NULL and @error is set.
+ */
+GListModel *
+ide_build_manager_list_targets_finish (IdeBuildManager  *self,
+                                       GAsyncResult     *result,
+                                       GError          **error)
+{
+  GListModel *ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_BUILD_MANAGER (self), NULL);
+  g_return_val_if_fail (IDE_IS_TASK (result), NULL);
+
+  ret = ide_task_propagate_object (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
diff --git a/src/libide/foundry/ide-build-manager.h b/src/libide/foundry/ide-build-manager.h
index a6812c7f2..f0774962c 100644
--- a/src/libide/foundry/ide-build-manager.h
+++ b/src/libide/foundry/ide-build-manager.h
@@ -93,5 +93,14 @@ IDE_AVAILABLE_IN_ALL
 gboolean          ide_build_manager_clean_finish        (IdeBuildManager      *self,
                                                          GAsyncResult         *result,
                                                          GError              **error);
+IDE_AVAILABLE_IN_ALL
+void              ide_build_manager_list_targets_async  (IdeBuildManager      *self,
+                                                         GCancellable         *cancellable,
+                                                         GAsyncReadyCallback   callback,
+                                                         gpointer              user_data);
+IDE_AVAILABLE_IN_ALL
+GListModel       *ide_build_manager_list_targets_finish (IdeBuildManager      *self,
+                                                         GAsyncResult         *result,
+                                                         GError              **error);
 
 G_END_DECLS
diff --git a/src/libide/foundry/ide-deploy-strategy.c b/src/libide/foundry/ide-deploy-strategy.c
index bc0a976c8..84a2d4e18 100644
--- a/src/libide/foundry/ide-deploy-strategy.c
+++ b/src/libide/foundry/ide-deploy-strategy.c
@@ -22,14 +22,16 @@
 
 #include "config.h"
 
-#include "ide-pipeline.h"
 #include "ide-deploy-strategy.h"
+#include "ide-pipeline.h"
+#include "ide-run-context.h"
+#include "ide-runtime.h"
 
 G_DEFINE_ABSTRACT_TYPE (IdeDeployStrategy, ide_deploy_strategy, IDE_TYPE_OBJECT)
 
 static void
 ide_deploy_strategy_real_load_async (IdeDeployStrategy   *self,
-                                     IdePipeline    *pipeline,
+                                     IdePipeline         *pipeline,
                                      GCancellable        *cancellable,
                                      GAsyncReadyCallback  callback,
                                      gpointer             user_data)
@@ -45,18 +47,21 @@ ide_deploy_strategy_real_load_async (IdeDeployStrategy   *self,
 static gboolean
 ide_deploy_strategy_real_load_finish (IdeDeployStrategy  *self,
                                       GAsyncResult       *result,
+                                      int                *priority,
                                       GError            **error)
 {
   g_assert (IDE_IS_DEPLOY_STRATEGY (self));
   g_assert (G_IS_TASK (result));
   g_assert (g_task_is_valid (G_TASK (result), self));
 
+  *priority = G_MAXINT;
+
   return g_task_propagate_boolean (G_TASK (result), error);
 }
 
 static void
 ide_deploy_strategy_real_deploy_async (IdeDeployStrategy     *self,
-                                       IdePipeline      *pipeline,
+                                       IdePipeline           *pipeline,
                                        GFileProgressCallback  progress,
                                        gpointer               progress_data,
                                        GDestroyNotify         progress_data_destroy,
@@ -85,36 +90,27 @@ ide_deploy_strategy_real_deploy_finish (IdeDeployStrategy  *self,
 }
 
 static void
-ide_deploy_strategy_real_create_runner_async (IdeDeployStrategy   *self,
-                                              IdePipeline         *pipeline,
-                                              GCancellable        *cancellable,
-                                              GAsyncReadyCallback  callback,
-                                              gpointer             user_data)
+ide_deploy_strategy_real_prepare_run_context (IdeDeployStrategy *self,
+                                              IdePipeline       *pipeline,
+                                              IdeRunContext     *run_context)
 {
-  g_autoptr(IdeTask) task = NULL;
-
-  g_return_if_fail (IDE_IS_DEPLOY_STRATEGY (self));
-  g_return_if_fail (IDE_IS_PIPELINE (pipeline));
-  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  task = ide_task_new (self, cancellable, callback, user_data);
-  ide_task_return_new_error (task,
-                             G_IO_ERROR,
-                             G_IO_ERROR_NOT_SUPPORTED,
-                             "Not supported");
+  IdeRuntime *runtime;
 
-}
+  IDE_ENTRY;
 
-static IdeRunner *
-ide_deploy_strategy_real_create_runner_finish (IdeDeployStrategy  *self,
-                                               GAsyncResult       *result,
-                                               GError            **error)
-{
   g_assert (IDE_IS_DEPLOY_STRATEGY (self));
-  g_assert (IDE_IS_TASK (result));
-  g_assert (ide_task_is_valid (G_TASK (result), self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
+  g_assert (IDE_IS_RUN_CONTEXT (run_context));
+
+  /* In the default implementation, for running locally, we just defer to
+   * the pipeline's runtime for how to create a run context.
+   */
+  if ((runtime = ide_pipeline_get_runtime (pipeline)))
+    ide_runtime_prepare_to_run (runtime, pipeline, run_context);
+  else
+    g_return_if_reached ();
 
-  return g_task_propagate_pointer (G_TASK (result), error);
+  IDE_EXIT;
 }
 
 static void
@@ -124,8 +120,7 @@ ide_deploy_strategy_class_init (IdeDeployStrategyClass *klass)
   klass->load_finish = ide_deploy_strategy_real_load_finish;
   klass->deploy_async = ide_deploy_strategy_real_deploy_async;
   klass->deploy_finish = ide_deploy_strategy_real_deploy_finish;
-  klass->create_runner_async = ide_deploy_strategy_real_create_runner_async;
-  klass->create_runner_finish = ide_deploy_strategy_real_create_runner_finish;
+  klass->prepare_run_context = ide_deploy_strategy_real_prepare_run_context;
 }
 
 static void
@@ -151,12 +146,10 @@ ide_deploy_strategy_init (IdeDeployStrategy *self)
  * get the install data out of the pipeline. Given so many moving parts
  * in build systems, how to determine that is an implementation detail of
  * the specific #IdeDeployStrategy.
- *
- * Since: 3.32
  */
 void
 ide_deploy_strategy_load_async (IdeDeployStrategy   *self,
-                                IdePipeline    *pipeline,
+                                IdePipeline         *pipeline,
                                 GCancellable        *cancellable,
                                 GAsyncReadyCallback  callback,
                                 gpointer             user_data)
@@ -182,12 +175,11 @@ ide_deploy_strategy_load_async (IdeDeployStrategy   *self,
  *
  * Returns: %TRUE if successful and the pipeline was supported; otherwise
  *   %FALSE and @error is set.
- *
- * Since: 3.32
  */
 gboolean
 ide_deploy_strategy_load_finish (IdeDeployStrategy  *self,
                                  GAsyncResult       *result,
+                                 int                *priority,
                                  GError            **error)
 {
   gboolean ret;
@@ -196,8 +188,9 @@ ide_deploy_strategy_load_finish (IdeDeployStrategy  *self,
 
   g_assert (IDE_IS_DEPLOY_STRATEGY (self));
   g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (priority != NULL);
 
-  ret = IDE_DEPLOY_STRATEGY_GET_CLASS (self)->load_finish (self, result, error);
+  ret = IDE_DEPLOY_STRATEGY_GET_CLASS (self)->load_finish (self, result, priority, error);
 
   IDE_RETURN (ret);
 }
@@ -219,12 +212,10 @@ ide_deploy_strategy_load_finish (IdeDeployStrategy  *self,
  *
  * If supported, the strategy will call @progress with periodic updates as
  * the application is deployed.
- *
- * Since: 3.32
  */
 void
 ide_deploy_strategy_deploy_async (IdeDeployStrategy     *self,
-                                  IdePipeline      *pipeline,
+                                  IdePipeline           *pipeline,
                                   GFileProgressCallback  progress,
                                   gpointer               progress_data,
                                   GDestroyNotify         progress_data_destroy,
@@ -260,8 +251,6 @@ ide_deploy_strategy_deploy_async (IdeDeployStrategy     *self,
  * build pipeline's device.
  *
  * Returns: %TRUE if successful; otherwise %FALSE and @error is set
- *
- * Since: 3.32
  */
 gboolean
 ide_deploy_strategy_deploy_finish (IdeDeployStrategy  *self,
@@ -272,8 +261,8 @@ ide_deploy_strategy_deploy_finish (IdeDeployStrategy  *self,
 
   IDE_ENTRY;
 
-  g_assert (IDE_IS_DEPLOY_STRATEGY (self));
-  g_assert (G_IS_ASYNC_RESULT (result));
+  g_return_val_if_fail (IDE_IS_DEPLOY_STRATEGY (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
 
   ret = IDE_DEPLOY_STRATEGY_GET_CLASS (self)->deploy_finish (self, result, error);
 
@@ -281,66 +270,28 @@ ide_deploy_strategy_deploy_finish (IdeDeployStrategy  *self,
 }
 
 /**
- * ide_deploy_strategy_create_runner_async:
+ * ide_deploy_strategy_prepare_run_context:
  * @self: a #IdeDeployStrategy
  * @pipeline: an #IdePipeline
- * @cancellable: (nullable): a #GCancellable or %NULL
- * @callback: (closure user_data): a callback to execute upon completion
- * @user_data: closure data for @callback
+ * @run_context: an #IdeRunContext
  *
- * Gets an #IdeRunner that runs apps deployed to the device, if a
- * runner other than the default is needed.
+ * Prepare an #IdeRunContext to run on a device.
  *
- * Since: 41
- */
-void
-ide_deploy_strategy_create_runner_async (IdeDeployStrategy   *self,
-                                         IdePipeline         *pipeline,
-                                         GCancellable        *cancellable,
-                                         GAsyncReadyCallback  callback,
-                                         gpointer             user_data)
-{
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_DEPLOY_STRATEGY (self));
-  g_assert (IDE_IS_PIPELINE (pipeline));
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  IDE_DEPLOY_STRATEGY_GET_CLASS (self)->create_runner_async (self,
-                                                             pipeline,
-                                                             cancellable,
-                                                             callback,
-                                                             user_data);
-
-  IDE_EXIT;
-}
-
-/**
- * ide_deploy_strategy_create_runner_finish:
- * @self: an #IdeDeployStrategy
- * @result: a #GAsyncResult provided to callback
- * @error: a location for a #GError, or %NULL
+ * This virtual function should be implemented by device strategies to prepare
+ * a run context for running on a device or deployment situation.
  *
- * Completes an asynchronous request to get an #IdeRunner for the current
- * device.
- *
- * Returns: (transfer full): An #IdeRunner or %NULL
- *
- * Since: 41
+ * Typically this is either nothing (in the case of running locally) or pushing
+ * a layer into the run context which is a command to deliver the command to
+ * another device/container/simulator/etc.
  */
-IdeRunner *
-ide_deploy_strategy_create_runner_finish (IdeDeployStrategy  *self,
-                                          GAsyncResult       *result,
-                                          GError            **error)
+void
+ide_deploy_strategy_prepare_run_context (IdeDeployStrategy *self,
+                                         IdePipeline       *pipeline,
+                                         IdeRunContext     *run_context)
 {
-  IdeRunner *ret;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_DEPLOY_STRATEGY (self));
-  g_assert (G_IS_ASYNC_RESULT (result));
-
-  ret = IDE_DEPLOY_STRATEGY_GET_CLASS (self)->create_runner_finish (self, result, error);
+  g_return_if_fail (IDE_IS_DEPLOY_STRATEGY (self));
+  g_return_if_fail (IDE_IS_PIPELINE (pipeline));
+  g_return_if_fail (IDE_IS_RUN_CONTEXT (run_context));
 
-  IDE_RETURN (ret);
+  IDE_DEPLOY_STRATEGY_GET_CLASS (self)->prepare_run_context (self, pipeline, run_context);
 }
diff --git a/src/libide/foundry/ide-deploy-strategy.h b/src/libide/foundry/ide-deploy-strategy.h
index ccede7f87..546260da3 100644
--- a/src/libide/foundry/ide-deploy-strategy.h
+++ b/src/libide/foundry/ide-deploy-strategy.h
@@ -25,82 +25,72 @@
 #endif
 
 #include <libide-core.h>
+
 #include "ide-foundry-types.h"
 
 G_BEGIN_DECLS
 
 #define IDE_TYPE_DEPLOY_STRATEGY (ide_deploy_strategy_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_DERIVABLE_TYPE (IdeDeployStrategy, ide_deploy_strategy, IDE, DEPLOY_STRATEGY, IdeObject)
 
 struct _IdeDeployStrategyClass
 {
   IdeObjectClass parent;
 
-  void     (*load_async)    (IdeDeployStrategy     *self,
-                             IdePipeline      *pipeline,
-                             GCancellable          *cancellable,
-                             GAsyncReadyCallback    callback,
-                             gpointer               user_data);
-  gboolean (*load_finish)   (IdeDeployStrategy     *self,
-                             GAsyncResult          *result,
-                             GError               **error);
-  void     (*deploy_async)  (IdeDeployStrategy     *self,
-                             IdePipeline      *pipeline,
-                             GFileProgressCallback  progress,
-                             gpointer               progress_data,
-                             GDestroyNotify         progress_data_destroy,
-                             GCancellable          *cancellable,
-                             GAsyncReadyCallback    callback,
-                             gpointer               user_data);
-  gboolean (*deploy_finish) (IdeDeployStrategy     *self,
-                             GAsyncResult          *result,
-                             GError               **error);
-  void       (*create_runner_async)  (IdeDeployStrategy   *self,
-                                      IdePipeline         *pipeline,
-                                      GCancellable        *cancellable,
-                                      GAsyncReadyCallback  callback,
-                                      gpointer             user_data);
-  IdeRunner *(*create_runner_finish) (IdeDeployStrategy   *self,
-                                      GAsyncResult        *result,
-                                      GError             **error);
-
-  gpointer _reserved[16];
+  void           (*load_async)           (IdeDeployStrategy      *self,
+                                          IdePipeline            *pipeline,
+                                          GCancellable           *cancellable,
+                                          GAsyncReadyCallback     callback,
+                                          gpointer                user_data);
+  gboolean       (*load_finish)          (IdeDeployStrategy      *self,
+                                          GAsyncResult           *result,
+                                          int                    *priority,
+                                          GError                **error);
+  void           (*deploy_async)         (IdeDeployStrategy      *self,
+                                          IdePipeline            *pipeline,
+                                          GFileProgressCallback   progress,
+                                          gpointer                progress_data,
+                                          GDestroyNotify          progress_data_destroy,
+                                          GCancellable           *cancellable,
+                                          GAsyncReadyCallback     callback,
+                                          gpointer                user_data);
+  gboolean       (*deploy_finish)        (IdeDeployStrategy      *self,
+                                          GAsyncResult           *result,
+                                          GError                **error);
+  void           (*prepare_run_context)  (IdeDeployStrategy      *self,
+                                          IdePipeline            *pipeline,
+                                          IdeRunContext          *run_context);
 };
 
-IDE_AVAILABLE_IN_3_32
-void     ide_deploy_strategy_load_async    (IdeDeployStrategy      *self,
-                                            IdePipeline       *pipeline,
-                                            GCancellable           *cancellable,
-                                            GAsyncReadyCallback     callback,
-                                            gpointer                user_data);
-IDE_AVAILABLE_IN_3_32
-gboolean ide_deploy_strategy_load_finish   (IdeDeployStrategy      *self,
-                                            GAsyncResult           *result,
-                                            GError                **error);
-IDE_AVAILABLE_IN_3_32
-void     ide_deploy_strategy_deploy_async  (IdeDeployStrategy      *self,
-                                            IdePipeline       *pipeline,
-                                            GFileProgressCallback   progress,
-                                            gpointer                progress_data,
-                                            GDestroyNotify          progress_data_destroy,
-                                            GCancellable           *cancellable,
-                                            GAsyncReadyCallback     callback,
-                                            gpointer                user_data);
-IDE_AVAILABLE_IN_3_32
-gboolean ide_deploy_strategy_deploy_finish (IdeDeployStrategy      *self,
-                                            GAsyncResult           *result,
-                                            GError                **error);
-IDE_AVAILABLE_IN_41
-void       ide_deploy_strategy_create_runner_async  (IdeDeployStrategy   *self,
-                                                     IdePipeline         *pipeline,
-                                                     GCancellable        *cancellable,
-                                                     GAsyncReadyCallback  callback,
-                                                     gpointer             user_data);
-IDE_AVAILABLE_IN_41
-IdeRunner *ide_deploy_strategy_create_runner_finish (IdeDeployStrategy  *self,
-                                                     GAsyncResult       *result,
-                                                     GError            **error);
+IDE_AVAILABLE_IN_ALL
+void     ide_deploy_strategy_load_async          (IdeDeployStrategy      *self,
+                                                  IdePipeline            *pipeline,
+                                                  GCancellable           *cancellable,
+                                                  GAsyncReadyCallback     callback,
+                                                  gpointer                user_data);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_deploy_strategy_load_finish         (IdeDeployStrategy      *self,
+                                                  GAsyncResult           *result,
+                                                  int                    *priority,
+                                                  GError                **error);
+IDE_AVAILABLE_IN_ALL
+void     ide_deploy_strategy_deploy_async        (IdeDeployStrategy      *self,
+                                                  IdePipeline            *pipeline,
+                                                  GFileProgressCallback   progress,
+                                                  gpointer                progress_data,
+                                                  GDestroyNotify          progress_data_destroy,
+                                                  GCancellable           *cancellable,
+                                                  GAsyncReadyCallback     callback,
+                                                  gpointer                user_data);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_deploy_strategy_deploy_finish       (IdeDeployStrategy      *self,
+                                                  GAsyncResult           *result,
+                                                  GError                **error);
+IDE_AVAILABLE_IN_ALL
+void     ide_deploy_strategy_prepare_run_context (IdeDeployStrategy      *self,
+                                                  IdePipeline            *pipeline,
+                                                  IdeRunContext          *run_context);
 
 G_END_DECLS
diff --git a/src/libide/foundry/ide-device-manager.c b/src/libide/foundry/ide-device-manager.c
index 2f920bdc7..e6a2100b5 100644
--- a/src/libide/foundry/ide-device-manager.c
+++ b/src/libide/foundry/ide-device-manager.c
@@ -23,9 +23,10 @@
 #include "config.h"
 
 #include <glib/gi18n.h>
+#include <libpeas/peas.h>
+
 #include <libide-plugins.h>
 #include <libide-threading.h>
-#include <libpeas/peas.h>
 
 #include "ide-build-manager.h"
 #include "ide-pipeline.h"
@@ -36,6 +37,7 @@
 #include "ide-device.h"
 #include "ide-foundry-compat.h"
 #include "ide-local-device.h"
+#include "ide-triplet.h"
 
 struct _IdeDeviceManager
 {
@@ -75,12 +77,6 @@ struct _IdeDeviceManager
   guint loading : 1;
 };
 
-typedef struct
-{
-  IdeObjectArray   *strategies;
-  IdePipeline *pipeline;
-} DeployState;
-
 typedef struct
 {
   gint n_active;
@@ -92,18 +88,17 @@ static void ide_device_manager_action_device (IdeDeviceManager    *self,
                                               GVariant            *param);
 static void ide_device_manager_action_deploy (IdeDeviceManager    *self,
                                               GVariant            *param);
-static void ide_device_manager_deploy_tick   (IdeTask             *task);
 
-DZL_DEFINE_ACTION_GROUP (IdeDeviceManager, ide_device_manager, {
+IDE_DEFINE_ACTION_GROUP (IdeDeviceManager, ide_device_manager, {
   { "device", ide_device_manager_action_device, "s", "'local'" },
   { "deploy", ide_device_manager_action_deploy },
 })
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (IdeDeviceManager, ide_device_manager, IDE_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
-                                                ide_device_manager_init_action_group)
-                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_init_iface)
-                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_init_interface))
+                               G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
+                                                      ide_device_manager_init_action_group)
+                               G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_init_iface)
+                               G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_init_interface))
 
 enum {
   PROP_0,
@@ -121,14 +116,6 @@ enum {
 static GParamSpec *properties [N_PROPS];
 static guint signals [N_SIGNALS];
 
-static void
-deploy_state_free (DeployState *state)
-{
-  g_clear_object (&state->pipeline);
-  g_clear_pointer (&state->strategies, ide_object_array_unref);
-  g_slice_free (DeployState, state);
-}
-
 static void
 ide_device_manager_provider_device_added_cb (IdeDeviceManager  *self,
                                              IdeDevice         *device,
@@ -495,8 +482,6 @@ ide_device_manager_class_init (IdeDeviceManagerClass *klass)
    * The "device" property indicates the currently selected device by the
    * user. This is the device we will try to deploy to when running, and
    * execute the application on.
-   *
-   * Since: 3.32
    */
   properties [PROP_DEVICE] =
     g_param_spec_object ("device",
@@ -510,8 +495,6 @@ ide_device_manager_class_init (IdeDeviceManagerClass *klass)
    *
    * The "progress" property is updated with a value between 0.0 and 1.0 while
    * the deployment is in progress.
-   *
-   * Since: 3.32
    */
   properties [PROP_PROGRESS] =
     g_param_spec_double ("progress",
@@ -561,8 +544,6 @@ ide_device_manager_init (IdeDeviceManager *self)
  * Fetches the first device that matches the device identifier @device_id.
  *
  * Returns: (transfer none): An #IdeDevice or %NULL.
- *
- * Since: 3.32
  */
 IdeDevice *
 ide_device_manager_get_device_by_id (IdeDeviceManager *self,
@@ -593,8 +574,6 @@ ide_device_manager_get_device_by_id (IdeDeviceManager *self,
  * Usually, this is an #IdeLocalDevice.
  *
  * Returns: (transfer none) (not nullable): an #IdeDevice
- *
- * Since: 3.32
  */
 IdeDevice *
 ide_device_manager_get_device (IdeDeviceManager *self)
@@ -628,8 +607,6 @@ ide_device_manager_get_device (IdeDeviceManager *self)
  * the devices architecture and operating system.
  *
  * If @device is %NULL, the local device will be used.
- *
- * Since: 3.32
  */
 void
 ide_device_manager_set_device (IdeDeviceManager *self,
@@ -752,31 +729,14 @@ deploy_progress_cb (goffset  current_num_bytes,
   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROGRESS]);
 }
 
-static void
-collect_strategies (PeasExtensionSet *set,
-                    PeasPluginInfo   *plugin_info,
-                    PeasExtension    *exten,
-                    gpointer          user_data)
-{
-  IdeObjectArray *strategies = user_data;
-  IdeDeployStrategy *strategy = (IdeDeployStrategy *)exten;
-
-  g_assert (PEAS_IS_EXTENSION_SET (set));
-  g_assert (plugin_info != NULL);
-  g_assert (IDE_IS_DEPLOY_STRATEGY (strategy));
-  g_assert (strategies != NULL);
-
-  ide_object_array_add (strategies, strategy);
-}
-
 static void
 ide_device_manager_deploy_cb (GObject      *object,
                               GAsyncResult *result,
                               gpointer      user_data)
 {
   IdeDeployStrategy *strategy = (IdeDeployStrategy *)object;
-  g_autoptr(GError) error = NULL;
   g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
 
   IDE_ENTRY;
 
@@ -792,94 +752,13 @@ ide_device_manager_deploy_cb (GObject      *object,
   IDE_EXIT;
 }
 
-static void
-ide_device_manager_deploy_load_cb (GObject      *object,
-                                   GAsyncResult *result,
-                                   gpointer      user_data)
-{
-  IdeDeployStrategy *strategy = (IdeDeployStrategy *)object;
-  g_autoptr(GError) error = NULL;
-  g_autoptr(IdeTask) task = user_data;
-  IdeDeviceManager *self;
-  DeployState *state;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_DEPLOY_STRATEGY (strategy));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
-
-  if (!ide_deploy_strategy_load_finish (strategy, result, &error))
-    {
-      g_debug ("Deploy strategy failed to load: %s", error->message);
-      ide_object_destroy (IDE_OBJECT (strategy));
-      ide_device_manager_deploy_tick (task);
-      IDE_EXIT;
-    }
-
-  /* Okay, we found a match. Now deploy to the device. */
-
-  self = ide_task_get_source_object (task);
-  state = ide_task_get_task_data (task);
-
-  g_assert (IDE_IS_DEVICE_MANAGER (self));
-  g_assert (state != NULL);
-  g_assert (state->strategies != NULL);
-  g_assert (IDE_IS_PIPELINE (state->pipeline));
-
-  ide_deploy_strategy_deploy_async (strategy,
-                                    state->pipeline,
-                                    deploy_progress_cb,
-                                    g_object_ref (self),
-                                    g_object_unref,
-                                    ide_task_get_cancellable (task),
-                                    ide_device_manager_deploy_cb,
-                                    g_object_ref (task));
-
-  IDE_EXIT;
-}
-
-static void
-ide_device_manager_deploy_tick (IdeTask *task)
-{
-  g_autoptr(IdeDeployStrategy) strategy = NULL;
-  DeployState *state;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_TASK (task));
-
-  state = ide_task_get_task_data (task);
-
-  g_assert (state != NULL);
-  g_assert (state->strategies != NULL);
-  g_assert (IDE_IS_PIPELINE (state->pipeline));
-
-  if (state->strategies->len == 0)
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_NOT_SUPPORTED,
-                                 "Failed to locate deployment strategy for device");
-      IDE_EXIT;
-    }
-
-  strategy = ide_object_array_steal_index (state->strategies, 0);
-
-  ide_deploy_strategy_load_async (strategy,
-                                  state->pipeline,
-                                  ide_task_get_cancellable (task),
-                                  ide_device_manager_deploy_load_cb,
-                                  g_object_ref (task));
-
-  IDE_EXIT;
-}
-
 static void
 ide_device_manager_deploy_completed (IdeDeviceManager *self,
                                      GParamSpec       *pspec,
                                      IdeTask          *task)
 {
+  IDE_ENTRY;
+
   g_assert (IDE_IS_DEVICE_MANAGER (self));
   g_assert (IDE_IS_TASK (task));
 
@@ -890,6 +769,8 @@ ide_device_manager_deploy_completed (IdeDeviceManager *self,
     }
 
   g_signal_emit (self, signals [DEPLOY_FINISHED], 0);
+
+  IDE_EXIT;
 }
 
 /**
@@ -903,20 +784,16 @@ ide_device_manager_deploy_completed (IdeDeviceManager *self,
  * Requests that the application be deployed to the device. This may need to
  * be done before running the application so that the device has the most
  * up to date build.
- *
- * Since: 3.32
  */
 void
 ide_device_manager_deploy_async (IdeDeviceManager    *self,
-                                 IdePipeline    *pipeline,
+                                 IdePipeline         *pipeline,
                                  GCancellable        *cancellable,
                                  GAsyncReadyCallback  callback,
                                  gpointer             user_data)
 {
-  g_autoptr(PeasExtensionSet) set = NULL;
   g_autoptr(IdeTask) task = NULL;
-  DeployState *state;
-  IdeDevice *device;
+  IdeDeployStrategy *strategy;
 
   IDE_ENTRY;
 
@@ -938,37 +815,20 @@ ide_device_manager_deploy_async (IdeDeviceManager    *self,
                            self,
                            G_CONNECT_SWAPPED);
 
-  if (!(device = ide_pipeline_get_device (pipeline)))
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_FAILED,
-                                 "Missing device in pipeline");
-      IDE_EXIT;
-    }
-
-  if (IDE_IS_LOCAL_DEVICE (device))
-    {
-      ide_task_return_boolean (task, TRUE);
-      IDE_EXIT;
-    }
-
-  state = g_slice_new0 (DeployState);
-  state->pipeline = g_object_ref (pipeline);
-  state->strategies = ide_object_array_new ();
-  ide_task_set_task_data (task, state, deploy_state_free);
-
-  set = peas_extension_set_new (peas_engine_get_default (),
-                                IDE_TYPE_DEPLOY_STRATEGY,
-                                NULL);
-  peas_extension_set_foreach (set, collect_strategies, state->strategies);
-
-  /* Root the addins as children of us so that they get context access */
-  for (guint i = 0; i < state->strategies->len; i++)
-    ide_object_append (IDE_OBJECT (self),
-                       ide_object_array_index (state->strategies, i));
-
-  ide_device_manager_deploy_tick (task);
+  if (!(strategy = ide_pipeline_get_deploy_strategy (pipeline)))
+    ide_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_FAILED,
+                               "Missing device in pipeline, cannot deploy");
+  else
+    ide_deploy_strategy_deploy_async (strategy,
+                                      pipeline,
+                                      deploy_progress_cb,
+                                      g_object_ref (self),
+                                      g_object_unref,
+                                      cancellable,
+                                      ide_device_manager_deploy_cb,
+                                      g_steal_pointer (&task));
 
   IDE_EXIT;
 }
@@ -982,8 +842,6 @@ ide_device_manager_deploy_async (IdeDeviceManager    *self,
  * Completes a request to deploy the application to the device.
  *
  * Returns: %TRUE if successful; otherwise %FALSE and @error is set
- *
- * Since: 3.32
  */
 gboolean
 ide_device_manager_deploy_finish (IdeDeviceManager  *self,
@@ -1003,213 +861,6 @@ ide_device_manager_deploy_finish (IdeDeviceManager  *self,
   IDE_RETURN (ret);
 }
 
-static void
-ide_device_manager_create_runner_cb (GObject      *object,
-                                     GAsyncResult *result,
-                                     gpointer      user_data)
-{
-  IdeDeployStrategy *strategy = (IdeDeployStrategy *)object;
-  g_autoptr(GError) error = NULL;
-  g_autoptr(IdeTask) task = user_data;
-  IdeRunner *runner;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_DEPLOY_STRATEGY (strategy));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
-
-  runner = ide_deploy_strategy_create_runner_finish (strategy, result, &error);
-
-  if (error)
-    ide_task_return_error (task, g_steal_pointer (&error));
-  else
-    ide_task_return_pointer (task, runner, g_object_unref);
-
-  ide_object_destroy (IDE_OBJECT (strategy));
-
-  IDE_EXIT;
-}
-
-static void
-ide_device_manager_create_runner_load_cb (GObject      *object,
-                                          GAsyncResult *result,
-                                          gpointer      user_data)
-{
-  IdeDeployStrategy *strategy = (IdeDeployStrategy *)object;
-  g_autoptr(GError) error = NULL;
-  g_autoptr(IdeTask) task = user_data;
-  IdeDeviceManager *self;
-  DeployState *state;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_DEPLOY_STRATEGY (strategy));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (IDE_IS_TASK (task));
-
-  if (!ide_deploy_strategy_load_finish (strategy, result, &error))
-    {
-      g_debug ("Deploy strategy failed to load: %s", error->message);
-      ide_object_destroy (IDE_OBJECT (strategy));
-      ide_device_manager_deploy_tick (task);
-      IDE_EXIT;
-    }
-
-  /* Okay, we found a match. Now run on the device. */
-
-  self = ide_task_get_source_object (task);
-  state = ide_task_get_task_data (task);
-
-  g_assert (IDE_IS_DEVICE_MANAGER (self));
-  g_assert (state != NULL);
-  g_assert (state->strategies != NULL);
-  g_assert (IDE_IS_PIPELINE (state->pipeline));
-
-  ide_deploy_strategy_create_runner_async (strategy,
-                                           state->pipeline,
-                                           ide_task_get_cancellable (task),
-                                           ide_device_manager_create_runner_cb,
-                                           g_object_ref (task));
-
-  IDE_EXIT;
-}
-
-static void
-ide_device_manager_create_runner_tick (IdeTask *task)
-{
-  g_autoptr(IdeDeployStrategy) strategy = NULL;
-  DeployState *state;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_TASK (task));
-
-  state = ide_task_get_task_data (task);
-
-  g_assert (state != NULL);
-  g_assert (state->strategies != NULL);
-  g_assert (IDE_IS_PIPELINE (state->pipeline));
-
-  if (state->strategies->len == 0)
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_NOT_SUPPORTED,
-                                 "Failed to locate deployment strategy for device");
-      IDE_EXIT;
-    }
-
-  strategy = ide_object_array_steal_index (state->strategies, 0);
-
-  ide_deploy_strategy_load_async (strategy,
-                                  state->pipeline,
-                                  ide_task_get_cancellable (task),
-                                  ide_device_manager_create_runner_load_cb,
-                                  g_object_ref (task));
-
-  IDE_EXIT;
-}
-
-/**
- * ide_device_manager_create_runner_async:
- * @self: a #IdeDeviceManager
- * @pipeline: an #IdePipeline
- * @cancellable: a #GCancellable, or %NULL
- * @callback: a #GAsyncReadyCallback
- * @user_data: closure data for @callback
- *
- * Requests an #IdeRunner that runs on the current device, if a runner
- * other than the default is required.
- *
- * Since: 41
- */
-void
-ide_device_manager_create_runner_async (IdeDeviceManager    *self,
-                                        IdePipeline         *pipeline,
-                                        GCancellable        *cancellable,
-                                        GAsyncReadyCallback  callback,
-                                        gpointer             user_data)
-{
-  g_autoptr(PeasExtensionSet) set = NULL;
-  g_autoptr(IdeTask) task = NULL;
-  DeployState *state;
-  IdeDevice *device;
-
-  IDE_ENTRY;
-
-  g_return_if_fail (IDE_IS_DEVICE_MANAGER (self));
-  g_return_if_fail (IDE_IS_PIPELINE (pipeline));
-  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  task = ide_task_new (self, cancellable, callback, user_data);
-  ide_task_set_source_tag (task, ide_device_manager_create_runner_async);
-
-  if (!(device = ide_pipeline_get_device (pipeline)))
-    {
-      ide_task_return_new_error (task,
-                                 G_IO_ERROR,
-                                 G_IO_ERROR_FAILED,
-                                 "Missing device in pipeline");
-      IDE_EXIT;
-    }
-
-  if (IDE_IS_LOCAL_DEVICE (device))
-    {
-      (ide_task_return_pointer) (task, NULL, NULL);
-      IDE_EXIT;
-    }
-
-  state = g_slice_new0 (DeployState);
-  state->pipeline = g_object_ref (pipeline);
-  state->strategies = ide_object_array_new ();
-  ide_task_set_task_data (task, state, deploy_state_free);
-
-  set = peas_extension_set_new (peas_engine_get_default (),
-                                IDE_TYPE_DEPLOY_STRATEGY,
-                                NULL);
-  peas_extension_set_foreach (set, collect_strategies, state->strategies);
-
-  /* Root the addins as children of us so that they get context access */
-  for (guint i = 0; i < state->strategies->len; i++)
-    ide_object_append (IDE_OBJECT (self),
-                       ide_object_array_index (state->strategies, i));
-
-  ide_device_manager_create_runner_tick (task);
-
-  IDE_EXIT;
-}
-
-/**
- * ide_device_manager_create_runner_finish:
- * @self: a #IdeDeviceManager
- * @result: a #GAsyncResult provided to callback
- * @error: a location for a #GError, or %NULL
- *
- * Completes a request to create an #IdeRunner to run on the device.
- *
- * Returns: (transfer full): An #IdeRunner or %NULL.
- *
- * Since: 41
- */
-IdeRunner *
-ide_device_manager_create_runner_finish (IdeDeviceManager  *self,
-                                         GAsyncResult      *result,
-                                         GError           **error)
-{
-  IdeRunner *ret;
-
-  IDE_ENTRY;
-
-  g_return_val_if_fail (IDE_IS_DEVICE_MANAGER (self), FALSE);
-  g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
-  g_return_val_if_fail (ide_task_is_valid (IDE_TASK (result), self), FALSE);
-
-  ret = ide_task_propagate_pointer (IDE_TASK (result), error);
-
-  IDE_RETURN (ret);
-}
-
 gdouble
 ide_device_manager_get_progress (IdeDeviceManager *self)
 {
diff --git a/src/libide/foundry/ide-device-manager.h b/src/libide/foundry/ide-device-manager.h
index 1e8f1ee20..e3f680f06 100644
--- a/src/libide/foundry/ide-device-manager.h
+++ b/src/libide/foundry/ide-device-manager.h
@@ -32,40 +32,30 @@ G_BEGIN_DECLS
 
 #define IDE_TYPE_DEVICE_MANAGER (ide_device_manager_get_type())
 
-IDE_AVAILABLE_IN_3_32
+IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeDeviceManager, ide_device_manager, IDE, DEVICE_MANAGER, IdeObject)
 
-IDE_AVAILABLE_IN_3_32
-IdeDeviceManager *ide_device_manager_from_context     (IdeContext           *context);
-IDE_AVAILABLE_IN_3_32
-gdouble           ide_device_manager_get_progress     (IdeDeviceManager     *self);
-IDE_AVAILABLE_IN_3_32
-IdeDevice        *ide_device_manager_get_device       (IdeDeviceManager     *self);
-IDE_AVAILABLE_IN_3_32
-void              ide_device_manager_set_device       (IdeDeviceManager     *self,
-                                                       IdeDevice            *device);
-IDE_AVAILABLE_IN_3_32
-IdeDevice        *ide_device_manager_get_device_by_id (IdeDeviceManager     *self,
-                                                       const gchar          *device_id);
-IDE_AVAILABLE_IN_3_32
-void              ide_device_manager_deploy_async     (IdeDeviceManager     *self,
-                                                       IdePipeline     *pipeline,
-                                                       GCancellable         *cancellable,
-                                                       GAsyncReadyCallback   callback,
-                                                       gpointer              user_data);
-IDE_AVAILABLE_IN_3_32
-gboolean          ide_device_manager_deploy_finish    (IdeDeviceManager     *self,
-                                                       GAsyncResult         *result,
-                                                       GError              **error);
-IDE_AVAILABLE_IN_41
-void              ide_device_manager_create_runner_async  (IdeDeviceManager    *self,
-                                                           IdePipeline         *pipeline,
-                                                           GCancellable        *cancellable,
-                                                           GAsyncReadyCallback  callback,
-                                                           gpointer             user_data);
-IDE_AVAILABLE_IN_41
-IdeRunner        *ide_device_manager_create_runner_finish (IdeDeviceManager  *self,
-                                                           GAsyncResult      *result,
-                                                           GError           **error);
+IDE_AVAILABLE_IN_ALL
+IdeDeviceManager *ide_device_manager_from_context         (IdeContext           *context);
+IDE_AVAILABLE_IN_ALL
+gdouble           ide_device_manager_get_progress         (IdeDeviceManager     *self);
+IDE_AVAILABLE_IN_ALL
+IdeDevice        *ide_device_manager_get_device           (IdeDeviceManager     *self);
+IDE_AVAILABLE_IN_ALL
+void              ide_device_manager_set_device           (IdeDeviceManager     *self,
+                                                           IdeDevice            *device);
+IDE_AVAILABLE_IN_ALL
+IdeDevice        *ide_device_manager_get_device_by_id     (IdeDeviceManager     *self,
+                                                           const gchar          *device_id);
+IDE_AVAILABLE_IN_ALL
+void              ide_device_manager_deploy_async         (IdeDeviceManager     *self,
+                                                           IdePipeline          *pipeline,
+                                                           GCancellable         *cancellable,
+                                                           GAsyncReadyCallback   callback,
+                                                           gpointer              user_data);
+IDE_AVAILABLE_IN_ALL
+gboolean          ide_device_manager_deploy_finish        (IdeDeviceManager     *self,
+                                                           GAsyncResult         *result,
+                                                           GError              **error);
 
 G_END_DECLS
diff --git a/src/libide/foundry/ide-device-provider.c b/src/libide/foundry/ide-device-provider.c
index e83787a66..cb5440b03 100644
--- a/src/libide/foundry/ide-device-provider.c
+++ b/src/libide/foundry/ide-device-provider.c
@@ -128,8 +128,6 @@ ide_device_provider_class_init (IdeDeviceProviderClass *klass)
    *
    * Subclasses of #IdeDeviceManager must chain-up if they override the
    * #IdeDeviceProviderClass.device_added vfunc.
-   *
-   * Since: 3.32
    */
   signals [DEVICE_ADDED] =
     g_signal_new ("device-added",
@@ -153,8 +151,6 @@ ide_device_provider_class_init (IdeDeviceProviderClass *klass)
    *
    * Subclasses of #IdeDeviceManager must chain-up if they override the
    * #IdeDeviceProviderClass.device_removed vfunc.
-   *
-   * Since: 3.32
    */
   signals [DEVICE_REMOVED] =
     g_signal_new ("device-removed",
@@ -181,8 +177,6 @@ ide_device_provider_init (IdeDeviceProvider *self)
  *
  * This should only be called by subclasses of #IdeDeviceProvider when
  * a new device has been discovered.
- *
- * Since: 3.32
  */
 void
 ide_device_provider_emit_device_added (IdeDeviceProvider *provider,
@@ -201,8 +195,6 @@ ide_device_provider_emit_device_added (IdeDeviceProvider *provider,
  *
  * This should only be called by subclasses of #IdeDeviceProvider when
  * a previously added device has been removed.
- *
- * Since: 3.32
  */
 void
 ide_device_provider_emit_device_removed (IdeDeviceProvider *provider,
@@ -231,8 +223,6 @@ ide_device_provider_emit_device_removed (IdeDeviceProvider *provider,
  * That should be done for known devices before returning from the asynchronous
  * operation so that the device manager does not need to wait for additional
  * devices to enter the "settled" state.
- *
- * Since: 3.32
  */
 void
 ide_device_provider_load_async (IdeDeviceProvider   *self,
@@ -256,8 +246,6 @@ ide_device_provider_load_async (IdeDeviceProvider   *self,
  * ide_device_provider_load_async().
  *
  * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
- *
- * Since: 3.32
  */
 gboolean
 ide_device_provider_load_finish (IdeDeviceProvider  *self,
@@ -279,8 +267,6 @@ ide_device_provider_load_finish (IdeDeviceProvider  *self,
  *
  * Returns: (transfer full) (element-type Ide.Device) (not nullable):
  *   a #GPtrArray of #IdeDevice.
- *
- * Since: 3.32
  */
 GPtrArray *
 ide_device_provider_get_devices (IdeDeviceProvider *self)
diff --git a/src/libide/foundry/ide-pipeline-stage-mkdirs.c b/src/libide/foundry/ide-pipeline-stage-mkdirs.c
index a3be032ae..0184ece6c 100644
--- a/src/libide/foundry/ide-pipeline-stage-mkdirs.c
+++ b/src/libide/foundry/ide-pipeline-stage-mkdirs.c
@@ -135,14 +135,14 @@ ide_pipeline_stage_mkdirs_build (IdePipelineStage     *stage,
 }
 
 static void
-ide_pipeline_stage_mkdirs_reap (IdePipelineStage      *stage,
-                             DzlDirectoryReaper *reaper)
+ide_pipeline_stage_mkdirs_reap (IdePipelineStage   *stage,
+                                IdeDirectoryReaper *reaper)
 {
   IdePipelineStageMkdirs *self = (IdePipelineStageMkdirs *)stage;
   IdePipelineStageMkdirsPrivate *priv = ide_pipeline_stage_mkdirs_get_instance_private (self);
 
   g_assert (IDE_IS_PIPELINE_STAGE_MKDIRS (self));
-  g_assert (DZL_IS_DIRECTORY_REAPER (reaper));
+  g_assert (IDE_IS_DIRECTORY_REAPER (reaper));
 
   ide_pipeline_stage_set_active (stage, TRUE);
 
@@ -153,7 +153,7 @@ ide_pipeline_stage_mkdirs_reap (IdePipelineStage      *stage,
       if (path->remove_on_rebuild)
         {
           g_autoptr(GFile) file = g_file_new_for_path (path->path);
-          dzl_directory_reaper_add_directory (reaper, file, 0);
+          ide_directory_reaper_add_directory (reaper, file, 0);
         }
     }
 
diff --git a/src/libide/foundry/ide-pipeline-stage.c b/src/libide/foundry/ide-pipeline-stage.c
index 8522be277..526913ab4 100644
--- a/src/libide/foundry/ide-pipeline-stage.c
+++ b/src/libide/foundry/ide-pipeline-stage.c
@@ -539,7 +539,7 @@ ide_pipeline_stage_class_init (IdePipelineStageClass *klass)
   /**
    * IdePipelineStage::reap:
    * @self: An #IdePipelineStage
-   * @reaper: An #DzlDirectoryReaper
+   * @reaper: An #IdeDirectoryReaper
    *
    * This signal is emitted when a request to rebuild the project has
    * occurred. This allows build stages to ensure that certain files are
@@ -553,7 +553,7 @@ ide_pipeline_stage_class_init (IdePipelineStageClass *klass)
                   G_SIGNAL_RUN_LAST,
                   G_STRUCT_OFFSET (IdePipelineStageClass, reap),
                   NULL, NULL, NULL,
-                  G_TYPE_NONE, 1, DZL_TYPE_DIRECTORY_REAPER);
+                  G_TYPE_NONE, 1, IDE_TYPE_DIRECTORY_REAPER);
 }
 
 static void
@@ -1059,12 +1059,12 @@ ide_pipeline_stage_clean_finish (IdePipelineStage  *self,
 
 void
 ide_pipeline_stage_emit_reap (IdePipelineStage   *self,
-                              DzlDirectoryReaper *reaper)
+                              IdeDirectoryReaper *reaper)
 {
   IDE_ENTRY;
 
   g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
-  g_return_if_fail (DZL_IS_DIRECTORY_REAPER (reaper));
+  g_return_if_fail (IDE_IS_DIRECTORY_REAPER (reaper));
 
   g_signal_emit (self, signals [REAP], 0, reaper);
 
diff --git a/src/libide/foundry/ide-pipeline-stage.h b/src/libide/foundry/ide-pipeline-stage.h
index da0a1e6fd..a66f39b89 100644
--- a/src/libide/foundry/ide-pipeline-stage.h
+++ b/src/libide/foundry/ide-pipeline-stage.h
@@ -197,6 +197,6 @@ IDE_AVAILABLE_IN_ALL
 void         ide_pipeline_stage_unpause          (IdePipelineStage     *self);
 IDE_AVAILABLE_IN_ALL
 void         ide_pipeline_stage_emit_reap        (IdePipelineStage     *self,
-                                                  DzlDirectoryReaper   *reaper);
+                                                  IdeDirectoryReaper   *reaper);
 
 G_END_DECLS
diff --git a/src/libide/foundry/ide-pipeline.c b/src/libide/foundry/ide-pipeline.c
index f2504f5d3..693b12f3a 100644
--- a/src/libide/foundry/ide-pipeline.c
+++ b/src/libide/foundry/ide-pipeline.c
@@ -23,8 +23,6 @@
 #include "config.h"
 
 #include <glib/gi18n.h>
-#include <dazzle.h>
-#include <libide-plugins.h>
 #include <libpeas/peas.h>
 #include <string.h>
 #include <vte/vte.h>
@@ -37,7 +35,8 @@
 #include <libide-threading.h>
 
 #include "ide-build-log-private.h"
-#include "ide-build-log.h"
+#include "ide-config.h"
+#include "ide-deploy-strategy.h"
 #include "ide-pipeline-addin.h"
 #include "ide-pipeline.h"
 #include "ide-build-private.h"
@@ -49,17 +48,20 @@
 #include "ide-device.h"
 #include "ide-foundry-compat.h"
 #include "ide-foundry-enums.h"
+#include "ide-local-deploy-strategy.h"
+#include "ide-local-device.h"
+#include "ide-run-command.h"
+#include "ide-run-context.h"
 #include "ide-run-manager-private.h"
 #include "ide-runtime.h"
 #include "ide-toolchain-manager.h"
 #include "ide-toolchain.h"
 #include "ide-triplet.h"
 
-DZL_DEFINE_COUNTER (Instances, "Pipeline", "N Pipelines", "Number of Pipeline instances")
 G_DEFINE_QUARK (ide_build_error, ide_build_error)
 
 /**
- * SECTION:idebuildpipeline
+ * SECTION:idepipeline
  * @title: IdePipeline
  * @short_description: Pluggable build pipeline
  * @include: ide.h
@@ -96,22 +98,20 @@ G_DEFINE_QUARK (ide_build_error, ide_build_error)
  * ide_pipeline_stage_set_transient(). This may be useful to perform operations
  * such as an "export tarball" stage which should only run once as determined
  * by the user requesting a "make dist" style operation.
- *
- * Since: 3.32
  */
 
 typedef struct
 {
-  guint          id;
+  guint             id;
   IdePipelinePhase  phase;
-  gint           priority;
+  int               priority;
   IdePipelineStage *stage;
 } PipelineEntry;
 
 typedef struct
 {
   IdePipeline *self;
-  GPtrArray        *addins;
+  GPtrArray   *addins;
 } IdleLoadState;
 
 typedef struct
@@ -138,6 +138,15 @@ struct _IdePipeline
    */
   IdeExtensionSetAdapter *addins;
 
+  /*
+   * Deployment stategies help discover how to make a deployment to
+   * a device which might require sending data to another system such
+   * as a phone or tablet.
+   */
+  IdeExtensionSetAdapter *deploy_strategies;
+  IdeDeployStrategy *best_strategy;
+  int best_strategy_priority;
+
   /*
    * This is the configuration for the build. It is a snapshot of
    * the real configuration so that we do not need to synchronize
@@ -214,18 +223,17 @@ struct _IdePipeline
   guint   errfmt_seqnum;
 
   /*
-   * The VtePty is used to connect to a VteTerminal. It's basically
-   * just a wrapper around a PTY master. We then add a IdePtyIntercept
-   * to proxy PTY data while allowing us to tap into the content being
-   * transmitted. We can use that to run regexes against and perform
-   * additional error extraction. Finally, pty_slave is the PTY device
-   * we created that will get attached to stdin/stdout/stderr in our
-   * spawned subprocesses. It is a slave to the PTY master owned by
-   * the IdePtyIntercept.
+   * The VtePty is used to connect to a VteTerminal. It's basically just a
+   * wrapper around a PTY consumer. We then add a IdePtyIntercept to proxy
+   * PTY data while allowing us to tap into the content being transmitted.
+   * We can use that to run regexes against and perform additional error
+   * extraction. Finally, pty_producer is the PTY device we created that
+   * will get attached to stdin/stdout/stderr in our spawned subprocesses.
+   * It is a producer to the PTY consumer owned by the IdePtyIntercept.
    */
   VtePty          *pty;
   IdePtyIntercept  intercept;
-  IdePtyFd         pty_slave;
+  IdePtyFd         pty_producer;
 
   /*
    * If the terminal interpreting our Pty has received a terminal
@@ -765,11 +773,11 @@ ide_pipeline_log_observer (IdeBuildLogStream  stream,
 }
 
 static void
-ide_pipeline_intercept_pty_master_cb (const IdePtyIntercept     *intercept,
-                                      const IdePtyInterceptSide *side,
-                                      const guint8              *data,
-                                      gsize                      len,
-                                      gpointer                   user_data)
+ide_pipeline_intercept_pty_consumer_cb (const IdePtyIntercept     *intercept,
+                                        const IdePtyInterceptSide *side,
+                                        const guint8              *data,
+                                        gsize                      len,
+                                        gpointer                   user_data)
 {
   IdePipeline *self = user_data;
 
@@ -843,8 +851,6 @@ ide_pipeline_check_ready (IdePipeline *self,
  *
  * Gets the current phase that is executing. This is only useful during
  * execution of the pipeline.
- *
- * Since: 3.32
  */
 IdePipelinePhase
 ide_pipeline_get_phase (IdePipeline *self)
@@ -867,8 +873,6 @@ ide_pipeline_get_phase (IdePipeline *self)
  * Gets the #IdeConfig to use for the pipeline.
  *
  * Returns: (transfer none): An #IdeConfig
- *
- * Since: 3.32
  */
 IdeConfig *
 ide_pipeline_get_config (IdePipeline *self)
@@ -1061,6 +1065,7 @@ register_build_commands_stage (IdePipeline *self,
   g_autofree gchar *rundir_path = NULL;
   GFile *rundir;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_PIPELINE (self));
   g_assert (IDE_IS_CONTEXT (context));
   g_assert (IDE_IS_CONFIG (self->config));
@@ -1165,6 +1170,60 @@ collect_pipeline_addins (IdeExtensionSetAdapter *set,
   g_ptr_array_add (addins, g_object_ref (exten));
 }
 
+static void
+ide_pipeline_deploy_strategy_load_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  IdeDeployStrategy *strategy = (IdeDeployStrategy *)object;
+  g_autoptr(IdePipeline) self = user_data;
+  g_autoptr(GError) error = NULL;
+  int priority = 0;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_DEPLOY_STRATEGY (strategy));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_PIPELINE (self));
+
+  if (ide_deploy_strategy_load_finish (strategy, result, &priority, &error))
+    {
+      if (self->best_strategy == NULL || priority < self->best_strategy_priority)
+        {
+          g_set_object (&self->best_strategy, strategy);
+          self->best_strategy_priority = priority;
+          IDE_EXIT;
+        }
+    }
+
+  IDE_EXIT;
+}
+
+static void
+ide_pipeline_deploy_strategy_added_cb (IdeExtensionSetAdapter *set,
+                                       PeasPluginInfo         *plugin_info,
+                                       PeasExtension          *exten,
+                                       gpointer                user_data)
+{
+  IdePipeline *self = user_data;
+  IdeDeployStrategy *strategy = (IdeDeployStrategy *)exten;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_DEPLOY_STRATEGY (strategy));
+  g_assert (IDE_IS_PIPELINE (self));
+
+  ide_deploy_strategy_load_async (strategy,
+                                  self,
+                                  self->cancellable,
+                                  ide_pipeline_deploy_strategy_load_cb,
+                                  g_object_ref (self));
+
+  IDE_EXIT;
+}
+
 static gboolean
 ide_pipeline_load_cb (IdleLoadState *state)
 {
@@ -1200,6 +1259,15 @@ ide_pipeline_load_cb (IdleLoadState *state)
         return G_SOURCE_CONTINUE;
     }
 
+  /* Now setup deployment strategies */
+  g_signal_connect (state->self->deploy_strategies,
+                    "extension-added",
+                    G_CALLBACK (ide_pipeline_deploy_strategy_added_cb),
+                    state->self);
+  ide_extension_set_adapter_foreach (state->self->deploy_strategies,
+                                     ide_pipeline_deploy_strategy_added_cb,
+                                     state->self);
+
   state->self->loaded = TRUE;
   state->self->idle_addins_load_source = 0;
 
@@ -1218,13 +1286,10 @@ ide_pipeline_load_cb (IdleLoadState *state)
  * enable/disable the pipeline as the IdeConfig:ready property changes.
  * This could happen when the device or runtime is added/removed while the
  * application is running.
- *
- * Since: 3.32
  */
 static void
 ide_pipeline_load (IdePipeline *self)
 {
-  g_autoptr(GPtrArray) addins = NULL;
   IdleLoadState *state;
   IdeContext *context;
 
@@ -1240,42 +1305,39 @@ ide_pipeline_load (IdePipeline *self)
   register_build_commands_stage (self, context);
   register_post_install_commands_stage (self, context);
 
+  /* Setup pipeline addins */
   self->addins = ide_extension_set_adapter_new (IDE_OBJECT (self),
                                                 peas_engine_get_default (),
                                                 IDE_TYPE_PIPELINE_ADDIN,
                                                 NULL, NULL);
-
   g_signal_connect (self->addins,
                     "extension-added",
                     G_CALLBACK (ide_pipeline_extension_prepare),
                     self);
-
   ide_extension_set_adapter_foreach (self->addins,
                                      ide_pipeline_extension_prepare,
                                      self);
-
   g_signal_connect_after (self->addins,
                           "extension-added",
                           G_CALLBACK (ide_pipeline_extension_added),
                           self);
-
   g_signal_connect (self->addins,
                     "extension-removed",
                     G_CALLBACK (ide_pipeline_extension_removed),
                     self);
 
-  /* Collect our addins so we can incrementally load them in an
-   * idle callback to reduce chances of stalling the main loop.
-   */
-  addins = g_ptr_array_new_with_free_func (g_object_unref);
-  ide_extension_set_adapter_foreach (self->addins,
-                                     collect_pipeline_addins,
-                                     addins);
+  /* Create deployment strategies */
+  self->deploy_strategies = ide_extension_set_adapter_new (IDE_OBJECT (self),
+                                                           peas_engine_get_default (),
+                                                           IDE_TYPE_DEPLOY_STRATEGY,
+                                                           NULL, NULL);
 
   state = g_slice_new0 (IdleLoadState);
   state->self = g_object_ref (self);
-  state->addins = g_steal_pointer (&addins);
-
+  state->addins = g_ptr_array_new_with_free_func (g_object_unref);
+  ide_extension_set_adapter_foreach (self->addins,
+                                     collect_pipeline_addins,
+                                     state->addins);
   self->idle_addins_load_source =
     g_idle_add_full (G_PRIORITY_LOW,
                      (GSourceFunc) ide_pipeline_load_cb,
@@ -1350,8 +1412,6 @@ ide_pipeline_begin_load (IdePipeline *self)
  * This function is safe to run even if load has not been called. We will not
  * clean things up if the pipeline is currently executing (we can wait until
  * its finished or dispose/finalize to cleanup up further.
- *
- * Since: 3.32
  */
 static void
 ide_pipeline_unload (IdePipeline *self)
@@ -1360,7 +1420,10 @@ ide_pipeline_unload (IdePipeline *self)
 
   g_assert (IDE_IS_PIPELINE (self));
 
+  g_clear_object (&self->best_strategy);
+
   ide_clear_and_destroy_object (&self->addins);
+  ide_clear_and_destroy_object (&self->deploy_strategies);
 
   IDE_EXIT;
 }
@@ -1423,8 +1486,6 @@ ide_pipeline_finalize (GObject *object)
 
   G_OBJECT_CLASS (ide_pipeline_parent_class)->finalize (object);
 
-  DZL_COUNTER_DEC (Instances);
-
   IDE_EXIT;
 }
 
@@ -1445,7 +1506,7 @@ ide_pipeline_destroy (IdeObject *object)
   g_clear_pointer (&self->message, g_free);
 
   g_clear_object (&self->pty);
-  fd = pty_fd_steal (&self->pty_slave);
+  fd = pty_fd_steal (&self->pty_producer);
 
   if (IDE_IS_PTY_INTERCEPT (&self->intercept))
     ide_pty_intercept_clear (&self->intercept);
@@ -1461,7 +1522,7 @@ ide_pipeline_initable_init (GInitable     *initable,
                             GError       **error)
 {
   IdePipeline *self = (IdePipeline *)initable;
-  IdePtyFd master_fd;
+  IdePtyFd consumer_fd;
 
   IDE_ENTRY;
 
@@ -1491,9 +1552,9 @@ ide_pipeline_initable_init (GInitable     *initable,
 
   vte_pty_set_utf8 (self->pty, TRUE, NULL);
 
-  master_fd = vte_pty_get_fd (self->pty);
+  consumer_fd = vte_pty_get_fd (self->pty);
 
-  if (!ide_pty_intercept_init (&self->intercept, master_fd, NULL))
+  if (!ide_pty_intercept_init (&self->intercept, consumer_fd, NULL))
     {
       g_set_error_literal (error,
                            G_IO_ERROR,
@@ -1503,9 +1564,9 @@ ide_pipeline_initable_init (GInitable     *initable,
     }
 
   ide_pty_intercept_set_callback (&self->intercept,
-                              &self->intercept.master,
-                              ide_pipeline_intercept_pty_master_cb,
-                              self);
+                                  &self->intercept.consumer,
+                                  ide_pipeline_intercept_pty_consumer_cb,
+                                  self);
 
   g_signal_connect_object (self->config,
                            "notify::ready",
@@ -1630,8 +1691,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    * IdePipeline:busy:
    *
    * Gets the "busy" property. If %TRUE, the pipeline is busy executing.
-   *
-   * Since: 3.32
    */
   properties [PROP_BUSY] =
     g_param_spec_boolean ("busy",
@@ -1644,8 +1703,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    * IdePipeline:configuration:
    *
    * The configuration to use for the build pipeline.
-   *
-   * Since: 3.32
    */
   properties [PROP_CONFIG] =
     g_param_spec_object ("config",
@@ -1658,8 +1715,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    * IdePipeline:device:
    *
    * The "device" property is the device we are compiling for.
-   *
-   * Since: 3.32
    */
   properties [PROP_DEVICE] =
     g_param_spec_object ("device",
@@ -1673,8 +1728,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    *
    * The "message" property is descriptive text about what the the
    * pipeline is doing or it's readiness status.
-   *
-   * Since: 3.32
    */
   properties [PROP_MESSAGE] =
     g_param_spec_string ("message",
@@ -1687,8 +1740,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    * IdePipeline:phase:
    *
    * The current build phase during execution of the pipeline.
-   *
-   * Since: 3.32
    */
   properties [PROP_PHASE] =
     g_param_spec_flags ("phase",
@@ -1703,8 +1754,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    *
    * The "pty" property is the #VtePty that is used by build stages that
    * build subprocesses with a pseudo terminal.
-   *
-   * Since: 3.32
    */
   properties [PROP_PTY] =
     g_param_spec_object ("pty",
@@ -1722,8 +1771,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    *
    * This signal is emitted when a plugin has detected a diagnostic while
    * building the pipeline.
-   *
-   * Since: 3.32
    */
   signals [DIAGNOSTIC] =
     g_signal_new_class_handler ("diagnostic",
@@ -1739,8 +1786,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    *
    * This signal is emitted when the pipeline has started executing in
    * response to ide_pipeline_build_async() being called.
-   *
-   * Since: 3.32
    */
   signals [STARTED] =
     g_signal_new_class_handler ("started",
@@ -1758,8 +1803,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    * This signal is emitted when the build process has finished executing.
    * If the build failed to complete all requested stages, then @failed will
    * be set to %TRUE, otherwise %FALSE.
-   *
-   * Since: 3.32
    */
   signals [FINISHED] =
     g_signal_new_class_handler ("finished",
@@ -1774,8 +1817,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    *
    * The "loaded" signal is emitted after the pipeline has finished
    * loading addins.
-   *
-   * Since: 3.32
    */
   signals [LOADED] =
     g_signal_new_class_handler ("loaded",
@@ -1793,8 +1834,6 @@ ide_pipeline_class_init (IdePipelineClass *klass)
    * #IdeSubprocessLauncher is created by the pipeline. This may be useful
    * to plugins that wan to modify the launcher in a consistent way for all
    * pipeline consumers.
-   *
-   * Since: 3.34
    */
   signals [LAUNCHER_CREATED] =
     g_signal_new_class_handler ("launcher-created",
@@ -1811,12 +1850,13 @@ ide_pipeline_class_init (IdePipelineClass *klass)
 static void
 ide_pipeline_init (IdePipeline *self)
 {
-  DZL_COUNTER_INC (Instances);
-
   self->cancellable = g_cancellable_new ();
 
   self->position = -1;
-  self->pty_slave = -1;
+  self->pty_producer = -1;
+
+  self->best_strategy_priority = G_MAXINT;
+  self->best_strategy = ide_local_deploy_strategy_new ();
 
   self->pipeline = g_array_new (FALSE, FALSE, sizeof (PipelineEntry));
   g_array_set_clear_func (self->pipeline, clear_pipeline_entry);
@@ -1831,8 +1871,8 @@ ide_pipeline_init (IdePipeline *self)
 
 static void
 ide_pipeline_stage_build_cb (GObject      *object,
-                                     GAsyncResult *result,
-                                     gpointer      user_data)
+                             GAsyncResult *result,
+                             gpointer      user_data)
 {
   IdePipelineStage *stage = (IdePipelineStage *)object;
   IdePipeline *self;
@@ -2121,8 +2161,6 @@ ide_pipeline_task_notify_completed (IdePipeline *self,
  * Upon completion, @callback will be buildd and should call
  * ide_pipeline_build_finish() to get the status of the
  * operation.
- *
- * Since: 3.32
  */
 void
 ide_pipeline_build_targets_async (IdePipeline         *self,
@@ -2140,7 +2178,7 @@ ide_pipeline_build_targets_async (IdePipeline         *self,
   g_return_if_fail (IDE_IS_PIPELINE (self));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  cancellable = dzl_cancellable_chain (cancellable, self->cancellable);
+  cancellable = ide_cancellable_chain (cancellable, self->cancellable);
 
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_source_tag (task, ide_pipeline_build_targets_async);
@@ -2204,8 +2242,6 @@ short_circuit:
  * Returns: %TRUE if the build stages were buildd successfully
  *   up to the requested build phase provided to
  *   ide_pipeline_build_targets_async().
- *
- * Since: 3.32
  */
 gboolean
 ide_pipeline_build_targets_finish (IdePipeline   *self,
@@ -2247,12 +2283,10 @@ ide_pipeline_build_targets_finish (IdePipeline   *self,
  * Upon completion, @callback will be buildd and should call
  * ide_pipeline_build_finish() to get the status of the
  * operation.
- *
- * Since: 3.32
  */
 void
 ide_pipeline_build_async (IdePipeline         *self,
-                          IdePipelinePhase        phase,
+                          IdePipelinePhase     phase,
                           GCancellable        *cancellable,
                           GAsyncReadyCallback  callback,
                           gpointer             user_data)
@@ -2272,8 +2306,6 @@ ide_pipeline_build_async (IdePipeline         *self,
  * Returns: %TRUE if the build stages were buildd successfully
  *   up to the requested build phase provided to
  *   ide_pipeline_build_async().
- *
- * Since: 3.32
  */
 gboolean
 ide_pipeline_build_finish (IdePipeline   *self,
@@ -2466,10 +2498,7 @@ ide_pipeline_queue_flush (IdePipeline *self)
 
   g_assert (IDE_IS_PIPELINE (self));
 
-  gdk_threads_add_idle_full (G_PRIORITY_LOW,
-                             ide_pipeline_do_flush,
-                             g_object_ref (self),
-                             g_object_unref);
+  g_idle_add_full (G_PRIORITY_LOW, ide_pipeline_do_flush, g_object_ref (self), g_object_unref);
 
   IDE_EXIT;
 }
@@ -2487,8 +2516,6 @@ ide_pipeline_queue_flush (IdePipeline *self)
  * stages that are part of the same phase.
  *
  * Returns: A stage_id that may be passed to ide_pipeline_detach().
- *
- * Since: 3.32
  */
 guint
 ide_pipeline_attach (IdePipeline      *self,
@@ -2585,8 +2612,6 @@ cleanup:
  * function.
  *
  * Returns: A stage_id that may be passed to ide_pipeline_remove().
- *
- * Since: 3.32
  */
 guint
 ide_pipeline_attach_launcher (IdePipeline           *self,
@@ -2618,8 +2643,6 @@ ide_pipeline_attach_launcher (IdePipeline           *self,
  * including all stages that were previously invalidated.
  *
  * Returns: %TRUE if a stage is known to require execution.
- *
- * Since: 3.32
  */
 gboolean
 ide_pipeline_request_phase (IdePipeline      *self,
@@ -2699,8 +2722,6 @@ cleanup:
  * the location that build systems will use for out-of-tree builds.
  *
  * Returns: the path of the build directory
- *
- * Since: 3.32
  */
 const gchar *
 ide_pipeline_get_builddir (IdePipeline *self)
@@ -2718,8 +2739,6 @@ ide_pipeline_get_builddir (IdePipeline *self)
  * IdeVcs:working-directory property as a string.
  *
  * Returns: the path of the source directory
- *
- * Since: 3.32
  */
 const gchar *
 ide_pipeline_get_srcdir (IdePipeline *self)
@@ -2759,8 +2778,6 @@ ide_pipeline_build_path_va_list (const gchar *prefix,
  * working directory of the source tree.
  *
  * Returns: (transfer full): A newly allocated string.
- *
- * Since: 3.32
  */
 gchar *
 ide_pipeline_build_srcdir_path (IdePipeline *self,
@@ -2791,8 +2808,6 @@ ide_pipeline_build_srcdir_path (IdePipeline *self,
  * result of ide_pipeline_get_builddir() as the first parameter.
  *
  * Returns: (transfer full): A newly allocated string.
- *
- * Since: 3.32
  */
 gchar *
 ide_pipeline_build_builddir_path (IdePipeline *self,
@@ -2824,8 +2839,6 @@ ide_pipeline_build_builddir_path (IdePipeline *self,
  *
  * Plugins should use this function to remove their stages when the plugin
  * is unloading.
- *
- * Since: 3.32
  */
 void
 ide_pipeline_detach (IdePipeline *self,
@@ -2863,12 +2876,10 @@ ide_pipeline_detach (IdePipeline *self,
  * upon discovering its state is no longer valid. Such an example might be
  * invalidating the %IDE_PIPELINE_PHASE_AUTOGEN phase when the an autotools
  * projects autogen.sh file has been changed.
- *
- * Since: 3.32
  */
 void
-ide_pipeline_invalidate_phase (IdePipeline *self,
-                                     IdePipelinePhase     phases)
+ide_pipeline_invalidate_phase (IdePipeline      *self,
+                               IdePipelinePhase  phases)
 {
   g_return_if_fail (IDE_IS_PIPELINE (self));
 
@@ -2891,8 +2902,6 @@ ide_pipeline_invalidate_phase (IdePipeline *self,
  *
  * Returns: (transfer none) (nullable): An #IdePipelineStage or %NULL if the
  *   stage could not be found.
- *
- * Since: 3.32
  */
 IdePipelineStage *
 ide_pipeline_get_stage_by_id (IdePipeline *self,
@@ -2918,8 +2927,6 @@ ide_pipeline_get_stage_by_id (IdePipeline *self,
  * A convenience function to get the runtime for a build pipeline.
  *
  * Returns: (transfer none) (nullable): An #IdeRuntime or %NULL
- *
- * Since: 3.32
  */
 IdeRuntime *
 ide_pipeline_get_runtime (IdePipeline *self)
@@ -2936,8 +2943,6 @@ ide_pipeline_get_runtime (IdePipeline *self)
  * A convenience function to get the toolchain for a build pipeline.
  *
  * Returns: (transfer none): An #IdeToolchain
- *
- * Since: 3.32
  */
 IdeToolchain *
 ide_pipeline_get_toolchain (IdePipeline *self)
@@ -2955,22 +2960,20 @@ ide_pipeline_get_toolchain (IdePipeline *self)
  * using the configuration and runtime associated with the pipeline.
  *
  * Returns: (transfer full): An #IdeSubprocessLauncher.
- *
- * Since: 3.32
  */
 IdeSubprocessLauncher *
 ide_pipeline_create_launcher (IdePipeline  *self,
                               GError      **error)
 {
   g_autoptr(IdeSubprocessLauncher) ret = NULL;
+  g_autoptr(IdeRunContext) run_context = NULL;
+  g_auto(GStrv) environ = NULL;
   IdeRuntime *runtime;
 
   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
 
-  runtime = ide_config_get_runtime (self->config);
-
-  if (runtime == NULL)
+  if (!(runtime = ide_config_get_runtime (self->config)))
     {
       g_set_error (error,
                    G_IO_ERROR,
@@ -2980,17 +2983,19 @@ ide_pipeline_create_launcher (IdePipeline  *self,
       return NULL;
     }
 
-  ret = ide_runtime_create_launcher (runtime, error);
+  environ = ide_environment_get_environ (ide_config_get_environment (self->config));
+
+  run_context = ide_run_context_new ();
+  ide_runtime_prepare_to_build (runtime, self, run_context);
+  ide_run_context_set_cwd (run_context, ide_pipeline_get_builddir (self));
+  ide_run_context_add_environ (run_context, (const char * const *)environ);
+  /* Always ignore V=1 from configurations */
+  ide_run_context_setenv (run_context, "V", "0");
+
+  ret = ide_run_context_end (run_context, error);
 
   if (ret != NULL)
     {
-      IdeEnvironment *env = ide_config_get_environment (self->config);
-
-      ide_subprocess_launcher_set_clear_env (ret, TRUE);
-      ide_subprocess_launcher_overlay_environment (ret, env);
-      /* Always ignore V=1 from configurations */
-      ide_subprocess_launcher_setenv (ret, "V", "0", TRUE);
-      ide_subprocess_launcher_set_cwd (ret, ide_pipeline_get_builddir (self));
       ide_subprocess_launcher_set_flags (ret,
                                          (G_SUBPROCESS_FLAGS_STDERR_PIPE |
                                           G_SUBPROCESS_FLAGS_STDOUT_PIPE));
@@ -3010,25 +3015,23 @@ ide_pipeline_create_launcher (IdePipeline  *self,
  * Attaches a PTY to stdin/stdout/stderr of the #IdeSubprocessLauncher.
  * This is useful if the application can take advantage of a PTY for
  * features like colors and other escape sequences.
- *
- * Since: 3.32
  */
 void
-ide_pipeline_attach_pty (IdePipeline      *self,
-                               IdeSubprocessLauncher *launcher)
+ide_pipeline_attach_pty (IdePipeline           *self,
+                         IdeSubprocessLauncher *launcher)
 {
   GSubprocessFlags flags;
 
   g_return_if_fail (IDE_IS_PIPELINE (self));
   g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (launcher));
 
-  if (self->pty_slave == -1)
+  if (self->pty_producer == -1)
     {
-      IdePtyFd master_fd = ide_pty_intercept_get_fd (&self->intercept);
-      self->pty_slave = ide_pty_intercept_create_slave (master_fd, TRUE);
+      IdePtyFd consumer_fd = ide_pty_intercept_get_fd (&self->intercept);
+      self->pty_producer = ide_pty_intercept_create_producer (consumer_fd, TRUE);
     }
 
-  if (self->pty_slave == -1)
+  if (self->pty_producer == -1)
     {
       ide_object_warning (self, _("Pseudo terminal creation failed. Terminal features will be limited."));
       return;
@@ -3041,10 +3044,10 @@ ide_pipeline_attach_pty (IdePipeline      *self,
              G_SUBPROCESS_FLAGS_STDIN_PIPE);
   ide_subprocess_launcher_set_flags (launcher, flags);
 
-  /* Assign slave device */
-  ide_subprocess_launcher_take_stdin_fd (launcher, dup (self->pty_slave));
-  ide_subprocess_launcher_take_stdout_fd (launcher, dup (self->pty_slave));
-  ide_subprocess_launcher_take_stderr_fd (launcher, dup (self->pty_slave));
+  /* Assign producer device */
+  ide_subprocess_launcher_take_stdin_fd (launcher, dup (self->pty_producer));
+  ide_subprocess_launcher_take_stdout_fd (launcher, dup (self->pty_producer));
+  ide_subprocess_launcher_take_stderr_fd (launcher, dup (self->pty_producer));
 
   /* Ensure a terminal type is set */
   ide_subprocess_launcher_setenv (launcher, "TERM", "xterm-256color", FALSE);
@@ -3060,8 +3063,6 @@ ide_pipeline_attach_pty (IdePipeline      *self,
  * guaranteed to happen at object creation time.
  *
  * Returns: (transfer none) (nullable): a #VtePty or %NULL
- *
- * Since: 3.32
  */
 VtePty *
 ide_pipeline_get_pty (IdePipeline *self)
@@ -3141,8 +3142,6 @@ ide_pipeline_emit_diagnostic (IdePipeline   *self,
  *
  * Returns: an error format id that may be passed to
  *   ide_pipeline_remove_error_format().
- *
- * Since: 3.32
  */
 guint
 ide_pipeline_add_error_format (IdePipeline        *self,
@@ -3178,8 +3177,6 @@ ide_pipeline_add_error_format (IdePipeline        *self,
  * ide_pipeline_add_error_format().
  *
  * Returns: %TRUE if the error format was removed.
- *
- * Since: 3.32
  */
 gboolean
 ide_pipeline_remove_error_format (IdePipeline *self,
@@ -3220,8 +3217,6 @@ ide_pipeline_get_busy (IdePipeline *self)
  *
  * Returns: (nullable) (transfer full): A string representing the
  *   current stage of the build, or %NULL.
- *
- * Since: 3.32
  */
 gchar *
 ide_pipeline_get_message (IdePipeline *self)
@@ -3325,8 +3320,6 @@ ide_pipeline_get_message (IdePipeline *self)
  *
  * This function will call @stage_callback for every #IdePipelineStage registered
  * in the pipeline.
- *
- * Since: 3.32
  */
 void
 ide_pipeline_foreach_stage (IdePipeline *self,
@@ -3469,7 +3462,7 @@ ide_pipeline_clean_async (IdePipeline         *self,
   if (!ide_pipeline_check_ready (self, task))
     return;
 
-  dzl_cancellable_chain (cancellable, self->cancellable);
+  ide_cancellable_chain (cancellable, self->cancellable);
 
   td = task_data_new (task, TASK_CLEAN);
   td->phase = phase;
@@ -3600,7 +3593,7 @@ ide_pipeline_reaper_cb (GObject      *object,
                         GAsyncResult *result,
                         gpointer      user_data)
 {
-  DzlDirectoryReaper *reaper = (DzlDirectoryReaper *)object;
+  IdeDirectoryReaper *reaper = (IdeDirectoryReaper *)object;
   IdePipeline *self;
   g_autoptr(IdeTask) task = user_data;
   g_autoptr(GError) error = NULL;
@@ -3608,7 +3601,7 @@ ide_pipeline_reaper_cb (GObject      *object,
 
   IDE_ENTRY;
 
-  g_assert (DZL_IS_DIRECTORY_REAPER (reaper));
+  g_assert (IDE_IS_DIRECTORY_REAPER (reaper));
   g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (IDE_IS_TASK (task));
 
@@ -3622,7 +3615,7 @@ ide_pipeline_reaper_cb (GObject      *object,
   g_assert (IDE_IS_PIPELINE (self));
 
   /* Make sure our reaper completed or else we bail */
-  if (!dzl_directory_reaper_execute_finish (reaper, result, &error))
+  if (!ide_directory_reaper_execute_finish (reaper, result, &error))
     {
       ide_task_return_error (task, g_steal_pointer (&error));
       IDE_EXIT;
@@ -3642,9 +3635,9 @@ ide_pipeline_reaper_cb (GObject      *object,
 
 static void
 ide_pipeline_tick_rebuild (IdePipeline *self,
-                                 IdeTask          *task)
+                           IdeTask     *task)
 {
-  g_autoptr(DzlDirectoryReaper) reaper = NULL;
+  g_autoptr(IdeDirectoryReaper) reaper = NULL;
   GCancellable *cancellable;
 
   IDE_ENTRY;
@@ -3662,7 +3655,7 @@ ide_pipeline_tick_rebuild (IdePipeline *self,
   }
 #endif
 
-  reaper = dzl_directory_reaper_new ();
+  reaper = ide_directory_reaper_new ();
 
   /*
    * Check if we can remove the builddir. We don't want to do this if it is the
@@ -3672,7 +3665,7 @@ ide_pipeline_tick_rebuild (IdePipeline *self,
     {
       g_autoptr(GFile) builddir = g_file_new_for_path (self->builddir);
 
-      dzl_directory_reaper_add_directory (reaper, builddir, 0);
+      ide_directory_reaper_add_directory (reaper, builddir, 0);
     }
 
   /*
@@ -3691,7 +3684,7 @@ ide_pipeline_tick_rebuild (IdePipeline *self,
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   /* Now build the reaper to clean up the build files. */
-  dzl_directory_reaper_execute_async (reaper,
+  ide_directory_reaper_execute_async (reaper,
                                       cancellable,
                                       ide_pipeline_reaper_cb,
                                       g_object_ref (task));
@@ -3711,8 +3704,6 @@ ide_pipeline_tick_rebuild (IdePipeline *self,
  *
  * Asynchronously starts the build pipeline after cleaning any
  * existing build artifacts.
- *
- * Since: 3.32
  */
 void
 ide_pipeline_rebuild_async (IdePipeline         *self,
@@ -3733,7 +3724,7 @@ ide_pipeline_rebuild_async (IdePipeline         *self,
 
   drop_caches (self);
 
-  cancellable = dzl_cancellable_chain (cancellable, self->cancellable);
+  cancellable = ide_cancellable_chain (cancellable, self->cancellable);
 
   task = ide_task_new (self, cancellable, callback, user_data);
   ide_task_set_priority (task, G_PRIORITY_LOW);
@@ -3755,9 +3746,9 @@ ide_pipeline_rebuild_async (IdePipeline         *self,
 }
 
 gboolean
-ide_pipeline_rebuild_finish (IdePipeline  *self,
-                                   GAsyncResult      *result,
-                                   GError           **error)
+ide_pipeline_rebuild_finish (IdePipeline   *self,
+                             GAsyncResult  *result,
+                             GError       **error)
 {
   gboolean ret;
 
@@ -3781,8 +3772,6 @@ ide_pipeline_rebuild_finish (IdePipeline  *self,
  * sensitivity of a button.
  *
  * Returns: %TRUE if there are export pipeline stages.
- *
- * Since: 3.32
  */
 gboolean
 ide_pipeline_get_can_export (IdePipeline *self)
@@ -3805,7 +3794,7 @@ ide_pipeline_get_can_export (IdePipeline *self)
 
 void
 _ide_pipeline_set_message (IdePipeline *self,
-                                 const gchar      *message)
+                           const gchar *message)
 {
   g_return_if_fail (IDE_IS_PIPELINE (self));
 
@@ -3851,8 +3840,6 @@ _ide_pipeline_cancel (IdePipeline *self)
  * the configure stage has been reached.
  *
  * Returns: %TRUE if %IDE_PIPELINE_PHASE_CONFIGURE has been reached.
- *
- * Since: 3.32
  */
 gboolean
 ide_pipeline_has_configured (IdePipeline *self)
@@ -3940,7 +3927,7 @@ ide_pipeline_get_n_items (GListModel *model)
 
 static gpointer
 ide_pipeline_get_item (GListModel *model,
-                             guint       position)
+                       guint       position)
 {
   IdePipeline *self = (IdePipeline *)model;
   const PipelineEntry *entry;
@@ -3970,8 +3957,6 @@ list_model_iface_init (GListModelInterface *iface)
  * get an idea of where the build pipeline will attempt to advance.
  *
  * Returns: an #IdePipelinePhase
- *
- * Since: 3.32
  */
 IdePipelinePhase
 ide_pipeline_get_requested_phase (IdePipeline *self)
@@ -3999,18 +3984,18 @@ ide_pipeline_get_requested_phase (IdePipeline *self)
 
 void
 _ide_pipeline_set_pty_size (IdePipeline *self,
-                                  guint             rows,
-                                  guint             columns)
+                            guint        rows,
+                            guint        columns)
 {
   g_return_if_fail (IDE_IS_PIPELINE (self));
 
-  if (self->pty_slave != IDE_PTY_FD_INVALID)
+  if (self->pty_producer != IDE_PTY_FD_INVALID)
     ide_pty_intercept_set_size (&self->intercept, rows, columns);
 }
 
 void
 _ide_pipeline_set_runtime (IdePipeline *self,
-                                 IdeRuntime       *runtime)
+                           IdeRuntime  *runtime)
 {
   g_return_if_fail (IDE_IS_PIPELINE (self));
   g_return_if_fail (!runtime || IDE_IS_RUNTIME (runtime));
@@ -4029,8 +4014,8 @@ _ide_pipeline_set_runtime (IdePipeline *self,
 }
 
 void
-_ide_pipeline_set_toolchain (IdePipeline *self,
-                                   IdeToolchain     *toolchain)
+_ide_pipeline_set_toolchain (IdePipeline  *self,
+                             IdeToolchain *toolchain)
 {
   g_return_if_fail (IDE_IS_PIPELINE (self));
   g_return_if_fail (!toolchain || IDE_IS_TOOLCHAIN (toolchain));
@@ -4048,8 +4033,6 @@ _ide_pipeline_set_toolchain (IdePipeline *self,
  * Thread-safe variant of ide_pipeline_get_toolchain().
  *
  * Returns: (transfer full) (nullable): an #IdeToolchain or %NULL
- *
- * Since: 3.32
  */
 IdeToolchain *
 ide_pipeline_ref_toolchain (IdePipeline *self)
@@ -4130,8 +4113,6 @@ _ide_pipeline_check_toolchain (IdePipeline   *self,
  * Gets the device that the pipeline is building for.
  *
  * Returns: (transfer none): an #IdeDevice.
- *
- * Since: 3.32
  */
 IdeDevice *
 ide_pipeline_get_device (IdePipeline *self)
@@ -4148,8 +4129,6 @@ ide_pipeline_get_device (IdePipeline *self)
  * Gets the device info for the current device.
  *
  * Returns: (nullable) (transfer none): an #IdeDeviceInfo or %NULL
- *
- * Since: 3.32
  */
 IdeDeviceInfo *
 ide_pipeline_get_device_info (IdePipeline *self)
@@ -4167,8 +4146,6 @@ ide_pipeline_get_device_info (IdePipeline *self)
  * due to various initialization routines that need to complete.
  *
  * Returns: %TRUE if the pipeline has loaded, otherwise %FALSE
- *
- * Since: 3.32
  */
 gboolean
 ide_pipeline_is_ready (IdePipeline *self)
@@ -4188,8 +4165,6 @@ ide_pipeline_is_ready (IdePipeline *self)
  * set for the build pipeline.
  *
  * Returns: (transfer none): an #IdeTriplet
- *
- * Since: 3.32
  */
 IdeTriplet *
 ide_pipeline_get_host_triplet (IdePipeline *self)
@@ -4208,8 +4183,6 @@ ide_pipeline_get_host_triplet (IdePipeline *self)
  * work by avoiding some cross-compiling work.
  *
  * Returns: %FALSE if we're possibly cross-compiling, otherwise %TRUE
- *
- * Since: 3.32
  */
 gboolean
 ide_pipeline_is_native (IdePipeline *self)
@@ -4278,8 +4251,6 @@ contains_in_runtime_with_alt_path (IdeRuntime *runtime,
  * @name that may be executed.
  *
  * Returns: %TRUE if @name was found; otherwise %FALSE
- *
- * Since: 3.34
  */
 gboolean
 ide_pipeline_contains_program_in_path (IdePipeline  *self,
@@ -4333,6 +4304,25 @@ ide_pipeline_contains_program_in_path (IdePipeline  *self,
   return FALSE;
 }
 
+/**
+ * ide_pipeline_get_deploy_strategy:
+ * @self: a #IdePipeline
+ *
+ * Gets the best discovered deployment strategry.
+ *
+ * Returns: (transfer none) (nullable): the best deployment strategy
+ *   if any are supported for the current configuration.
+ */
+IdeDeployStrategy *
+ide_pipeline_get_deploy_strategy (IdePipeline *self)
+{
+  g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
+  g_return_val_if_fail (!self->best_strategy ||
+                        IDE_IS_DEPLOY_STRATEGY (self->best_strategy), NULL);
+
+  return self->best_strategy;
+}
+
 /**
  * ide_pipeline_addin_find_by_module_name:
  * @pipeline: an #IdePipeline
@@ -4341,8 +4331,6 @@ ide_pipeline_contains_program_in_path (IdePipeline  *self,
  * Finds the addin (if any) matching the plugin's @module_name.
  *
  * Returns: (transfer none) (nullable): an #IdePipelineAddin or %NULL
- *
- * Since: 3.40
  */
 IdePipelineAddin *
 ide_pipeline_addin_find_by_module_name (IdePipeline *pipeline,
@@ -4366,3 +4354,75 @@ ide_pipeline_addin_find_by_module_name (IdePipeline *pipeline,
 
   return IDE_PIPELINE_ADDIN (ret);
 }
+
+/**
+ * ide_pipeline_prepare_run_context:
+ * @self: a #IdePipeline
+ * @run_context: an #IdeRunContext
+ *
+ * Prepares #IdeRunContext to build within the pipeline.
+ *
+ * You should use this to prepare a new #IdeRunContext to run within the
+ * build pipeline environment before adding arguments and other settings
+ * to the context.
+ *
+ * The runtime will be consulted to modify any commands necessary.
+ */
+void
+ide_pipeline_prepare_run_context (IdePipeline   *self,
+                                  IdeRunContext *run_context)
+{
+  IdeRuntime *runtime;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_PIPELINE (self));
+  g_return_if_fail (IDE_IS_RUN_CONTEXT (run_context));
+
+  if (!(runtime = ide_pipeline_get_runtime (self)))
+    {
+      g_critical ("Attempt to prepare a run context before pipeline has a runtime!");
+      return;
+    }
+
+  ide_runtime_prepare_to_build (runtime, self, run_context);
+
+  ide_run_context_setenv (run_context, "BUILDDIR", ide_pipeline_get_builddir (self));
+  ide_run_context_setenv (run_context, "SRCDIR", ide_pipeline_get_srcdir (self));
+  ide_run_context_set_cwd (run_context, ide_pipeline_get_builddir (self));
+}
+
+/**
+ * ide_pipeline_create_run_context:
+ * @self: a #IdePipeline
+ * @run_command: an #IdeRunCommand
+ *
+ * Creates a new #IdeRunContext to run @run_command.
+ *
+ * This helper is generally meant to be used by pipeline stages to create
+ * a run context that will execute within the pipeline to run the command
+ * described in @run_command.
+ *
+ * The run context is first prepared using ide_pipeline_prepare_run_context()
+ * after which the run command's ide_run_command_prepare_to_run() is used.
+ *
+ * Returns: (transfer full): an #IdeRunContext
+ */
+IdeRunContext *
+ide_pipeline_create_run_context (IdePipeline   *self,
+                                 IdeRunCommand *run_command)
+{
+  g_autoptr(IdeRunContext) run_context = NULL;
+  IdeContext *context;
+
+  g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
+  g_return_val_if_fail (IDE_IS_RUN_COMMAND (run_command), NULL);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+  run_context = ide_run_context_new ();
+  ide_pipeline_prepare_run_context (self, run_context);
+  ide_run_command_prepare_to_run (run_command, run_context, context);
+
+  return g_steal_pointer (&run_context);
+}
diff --git a/src/libide/foundry/ide-pipeline.h b/src/libide/foundry/ide-pipeline.h
index 4de95798c..05a3ead93 100644
--- a/src/libide/foundry/ide-pipeline.h
+++ b/src/libide/foundry/ide-pipeline.h
@@ -92,6 +92,12 @@ IDE_AVAILABLE_IN_ALL
 IdeSubprocessLauncher *ide_pipeline_create_launcher          (IdePipeline            *self,
                                                               GError                **error);
 IDE_AVAILABLE_IN_ALL
+IdeRunContext         *ide_pipeline_create_run_context       (IdePipeline            *self,
+                                                              IdeRunCommand          *run_command);
+IDE_AVAILABLE_IN_ALL
+void                   ide_pipeline_prepare_run_context      (IdePipeline            *self,
+                                                              IdeRunContext          *run_context);
+IDE_AVAILABLE_IN_ALL
 gchar                 *ide_pipeline_build_srcdir_path        (IdePipeline            *self,
                                                               const gchar            *first_part,
                                                               ...) G_GNUC_NULL_TERMINATED;
@@ -196,5 +202,7 @@ IDE_AVAILABLE_IN_ALL
 gboolean               ide_pipeline_contains_program_in_path (IdePipeline            *self,
                                                               const gchar            *name,
                                                               GCancellable           *cancellable);
+IDE_AVAILABLE_IN_ALL
+IdeDeployStrategy     *ide_pipeline_get_deploy_strategy      (IdePipeline            *self);
 
 G_END_DECLS
diff --git a/src/libide/foundry/meson.build b/src/libide/foundry/meson.build
index 0595eeb9f..e8155ec93 100644
--- a/src/libide/foundry/meson.build
+++ b/src/libide/foundry/meson.build
@@ -176,7 +176,6 @@ libide_foundry_generated_headers += [libide_foundry_enums[1]]
 libide_foundry_deps = [
   libgio_dep,
   libgtk_dep,
-  libdazzle_dep,
   libpeas_dep,
   libvte_dep,
   libjson_glib_dep,


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