[gnome-builder] config: refactor config providers to be less racey



commit 1ab088f0e613790119237582844a28758a7bca44
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jan 24 03:06:01 2018 -0800

    config: refactor config providers to be less racey
    
    We had a number of issues in practice with configuration providers where
    things would race and as well as some unsafe threading/false-sharing.
    
    This redesigns those components to avoid a number of issues in thread
    safety.
    
    There doesn't seem to be any regressions. However, it has pointed out
    a few things that are/were broken in the flatpak configuration provider.
    We will address those as part of a revamped build preferences that is
    more pluggable (See #344 and #352).
    
    Another piece that would be nice to apply on top of this is tracking
    the last selected configuration so when restarting Builder we keep
    the same config active (See #338).
    
    Fixes #359

 po/POTFILES.in                                     |   1 +
 .../ide-buildconfig-configuration-provider.c       | 906 +++++++++++----------
 .../ide-buildconfig-configuration-provider.h       |  12 +-
 .../buildconfig/ide-buildconfig-configuration.c    |   3 +-
 src/libide/buildui/buildui.plugin                  |   2 -
 src/libide/buildui/ide-build-perspective.c         |  10 +-
 src/libide/buildui/ide-build-plugin.c              |   3 -
 src/libide/buildui/ide-build-tool.c                | 337 --------
 src/libide/buildui/meson.build                     |   2 -
 src/libide/config/ide-configuration-manager.c      | 492 +++++++----
 src/libide/config/ide-configuration-manager.h      |  12 +-
 src/libide/config/ide-configuration-provider.c     | 239 +++++-
 src/libide/config/ide-configuration-provider.h     |  99 ++-
 src/libide/config/ide-configuration.c              | 180 +---
 src/libide/config/ide-configuration.h              |   7 -
 .../flatpak/gbp-flatpak-configuration-provider.c   | 360 ++++----
 16 files changed, 1282 insertions(+), 1383 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 45365e1f5..acceb51d6 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -30,6 +30,7 @@ src/libide/application/ide-application-shortcuts.c
 src/libide/buffers/ide-buffer.c
 src/libide/buffers/ide-buffer-manager.c
 src/libide/buffers/ide-unsaved-files.c
+src/libide/buildconfig/ide-buildconfig-configuration-provider.c
 src/libide/buildsystem/ide-build-manager.c
 src/libide/buildsystem/ide-build-pipeline.c
 src/libide/buildsystem/ide-build-stage-transfer.c
diff --git a/src/libide/buildconfig/ide-buildconfig-configuration-provider.c 
b/src/libide/buildconfig/ide-buildconfig-configuration-provider.c
index 13d535d63..acdce8846 100644
--- a/src/libide/buildconfig/ide-buildconfig-configuration-provider.c
+++ b/src/libide/buildconfig/ide-buildconfig-configuration-provider.c
@@ -20,6 +20,8 @@
 
 #include <dazzle.h>
 #include <gio/gio.h>
+#include <glib/gi18n.h>
+#include <string.h>
 
 #include "ide-context.h"
 #include "ide-debug.h"
@@ -33,39 +35,289 @@
 #include "vcs/ide-vcs.h"
 
 #define DOT_BUILDCONFIG ".buildconfig"
-#define WRITEBACK_TIMEOUT_SECS 2
 
 struct _IdeBuildconfigConfigurationProvider
 {
-  IdeObject                parent_instance;
+  IdeObject  parent_instance;
 
-  IdeConfigurationManager *manager;
-  GPtrArray               *configurations;
-  GKeyFile                *key_file;
+  /*
+   * A GPtrArray of IdeBuildconfigConfiguration that have been registered.
+   * We append/remove to/from this array in our default signal handler for
+   * the ::added and ::removed signals.
+   */
+  GPtrArray *configs;
+
+  /*
+   * The GKeyFile that was parsed from disk. We keep this around so that
+   * we can persist the changes back without destroying comments.
+   */
+  GKeyFile *key_file;
 
-  guint                    writeback_handler;
-  guint                    change_count;
+  /*
+   * If we removed items from the keyfile, we need to know that so that
+   * we persist it back to disk. We only persist back to disk if this bit
+   * is set or if any of our registered configs are "dirty".
+   *
+   * We try hard to avoid writing .buildconfig files unless we know the
+   * user did something to change a config. Otherwise we would liter
+   * everyone's projects with .buildconfig files.
+   */
+  guint key_file_dirty : 1;
 };
 
-static void configuration_provider_iface_init (IdeConfigurationProviderInterface *);
+static gchar *
+gen_next_id (const gchar *id)
+{
+  g_auto(GStrv) parts = g_strsplit (id, "-", 0);
+  guint len = g_strv_length (parts);
+  const gchar *end;
+  guint64 n64;
 
-G_DEFINE_TYPE_WITH_CODE (IdeBuildconfigConfigurationProvider,
-                         ide_buildconfig_configuration_provider,
-                         IDE_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (IDE_TYPE_CONFIGURATION_PROVIDER,
-                                                configuration_provider_iface_init))
+  if (len == 0)
+    goto add_suffix;
+
+  end = parts[len - 1];
+
+  n64 = g_ascii_strtoull (end, (gchar **)&end, 10);
+  if (n64 == 0 || n64 == G_MAXUINT64 || *end != 0)
+    goto add_suffix;
+
+  g_free (g_steal_pointer (&parts[len -1]));
+  parts[len -1] = g_strdup_printf ("%"G_GUINT64_FORMAT, n64+1);
+  return g_strjoinv ("-", parts);
+
+add_suffix:
+  return g_strdup_printf ("%s-2", id);
+}
+
+static gchar *
+get_next_id (IdeConfigurationManager *manager,
+             const gchar             *id)
+{
+  g_autoptr(GPtrArray) tries = NULL;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
+
+  tries = g_ptr_array_new_with_free_func (g_free);
+
+  while (ide_configuration_manager_get_configuration (manager, id))
+    {
+      g_autofree gchar *next = gen_next_id (id);
+      id = next;
+      g_ptr_array_add (tries, g_steal_pointer (&next));
+    }
+
+  return g_strdup (id);
+}
+
+static void
+load_string (IdeConfiguration *config,
+             GKeyFile         *key_file,
+             const gchar      *group,
+             const gchar      *key,
+             const gchar      *property)
+{
+  g_assert (IDE_IS_CONFIGURATION (config));
+  g_assert (key_file != NULL);
+  g_assert (group != NULL);
+  g_assert (key != NULL);
+
+  if (g_key_file_has_key (key_file, group, key, NULL))
+    {
+      g_auto(GValue) value = G_VALUE_INIT;
+
+      g_value_init (&value, G_TYPE_STRING);
+      g_value_take_string (&value, g_key_file_get_string (key_file, group, key, NULL));
+      g_object_set_property (G_OBJECT (config), property, &value);
+    }
+}
+
+static void
+load_strv (IdeConfiguration *config,
+           GKeyFile         *key_file,
+           const gchar      *group,
+           const gchar      *key,
+           const gchar      *property)
+{
+  g_assert (IDE_IS_CONFIGURATION (config));
+  g_assert (key_file != NULL);
+  g_assert (group != NULL);
+  g_assert (key != NULL);
+
+  if (g_key_file_has_key (key_file, group, key, NULL))
+    {
+      g_auto(GStrv) strv = NULL;
+      g_auto(GValue) value = G_VALUE_INIT;
+
+      strv = g_key_file_get_string_list (key_file, group, key, NULL, NULL);
+      g_value_init (&value, G_TYPE_STRV);
+      g_value_take_boxed (&value, g_steal_pointer (&strv));
+      g_object_set_property (G_OBJECT (config), property, &value);
+    }
+}
+
+static void
+load_environ (IdeConfiguration *config,
+              GKeyFile         *key_file,
+              const gchar      *group)
+{
+  IdeEnvironment *environment;
+  g_auto(GStrv) keys = NULL;
+  gsize len = 0;
+
+  g_assert (IDE_IS_CONFIGURATION (config));
+  g_assert (key_file != NULL);
+  g_assert (group != NULL);
+
+  environment = ide_configuration_get_environment (config);
+  keys = g_key_file_get_keys (key_file, group, &len, NULL);
+
+  for (gsize i = 0; i < len; i++)
+    {
+      g_autofree gchar *value = NULL;
+
+      value = g_key_file_get_string (key_file, group, keys[i], NULL);
+      if (value != NULL)
+        ide_environment_setenv (environment, keys [i], value);
+    }
+}
+
+static IdeConfiguration *
+ide_buildconfig_configuration_provider_create (IdeBuildconfigConfigurationProvider *self,
+                                               const gchar                         *config_id)
+{
+  g_autoptr(IdeConfiguration) config = NULL;
+  g_autofree gchar *env_group = NULL;
+  IdeContext *context;
+
+  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
+  g_assert (self->key_file != NULL);
+  g_assert (config_id != NULL);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIGURATION,
+                         "context", context,
+                         "id", config_id,
+                         NULL);
+
+  load_string (config, self->key_file, config_id, "config-opts", "config-opts");
+  load_string (config, self->key_file, config_id, "device", "device-id");
+  load_string (config, self->key_file, config_id, "name", "display-name");
+  load_string (config, self->key_file, config_id, "run-opts", "run-opts");
+  load_string (config, self->key_file, config_id, "runtime", "runtime-id");
+  load_string (config, self->key_file, config_id, "prefix", "prefix");
+  load_string (config, self->key_file, config_id, "app-id", "app-id");
+  load_strv (config, self->key_file, config_id, "prebuild", "prebuild");
+  load_strv (config, self->key_file, config_id, "postbuild", "postbuild");
+
+  env_group = g_strdup_printf ("%s.environment", config_id);
+  if (g_key_file_has_group (self->key_file, env_group))
+    load_environ (config, self->key_file, env_group);
+
+  return g_steal_pointer (&config);
+}
+
+static void
+ide_buildconfig_configuration_provider_load_async (IdeConfigurationProvider *provider,
+                                                   GCancellable             *cancellable,
+                                                   GAsyncReadyCallback       callback,
+                                                   gpointer                  user_data)
+{
+  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
+  g_autoptr(IdeConfiguration) fallback = NULL;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *path = NULL;
+  g_auto(GStrv) groups = NULL;
+  IdeContext *context;
+  gsize len;
+
+  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
+  g_assert (self->key_file == NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_buildconfig_configuration_provider_load_async);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+
+  self->key_file = g_key_file_new ();
+
+  /*
+   * We could do this in a thread, but it's not really worth it. We want these
+   * configs loaded ASAP, and nothing can really progress until it's loaded
+   * anyway.
+   */
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  path = ide_context_build_filename (context, DOT_BUILDCONFIG, NULL);
+  if (!g_file_test (path, G_FILE_TEST_IS_REGULAR))
+    goto add_default;
+
+  if (!g_key_file_load_from_file (self->key_file, path, G_KEY_FILE_KEEP_COMMENTS, &error))
+    {
+      g_warning ("Failed to load .buildconfig: %s", error->message);
+      goto add_default;
+    }
+
+  groups = g_key_file_get_groups (self->key_file, &len);
+
+  for (gsize i = 0; i < len; i++)
+    {
+      g_autoptr(IdeConfiguration) config = NULL;
+      const gchar *group = groups[i];
+
+      if (strchr (group, '.') != NULL)
+        continue;
+
+      config = ide_buildconfig_configuration_provider_create (self, group);
+      ide_configuration_set_dirty (config, FALSE);
+      ide_configuration_provider_emit_added (provider, config);
+    }
+
+  if (self->configs->len > 0)
+    goto complete;
+
+add_default:
+  /* "Default" is not translated because .buildconfig can be checked in */
+  fallback = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIGURATION,
+                           "context", context,
+                           "device-id", "local",
+                           "display-name", "Default",
+                           "id", "default",
+                           "runtime-id", "host",
+                           NULL);
+  ide_configuration_set_dirty (fallback, FALSE);
+  ide_configuration_provider_emit_added (provider, fallback);
+
+complete:
+  g_task_return_boolean (task, TRUE);
+}
+
+static gboolean
+ide_buildconfig_configuration_provider_load_finish (IdeConfigurationProvider  *provider,
+                                                    GAsyncResult              *result,
+                                                    GError                   **error)
+{
+  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (provider));
+  g_assert (G_IS_TASK (result));
+  g_assert (g_task_is_valid (G_TASK (result), provider));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
 
 static void
 ide_buildconfig_configuration_provider_save_cb (GObject      *object,
                                                 GAsyncResult *result,
                                                 gpointer      user_data)
 {
+  GFile *file = (GFile *)object;
   g_autoptr(GTask) task = user_data;
   g_autoptr(GError) error = NULL;
-  GFile *file = (GFile *)object;
 
   g_assert (G_IS_FILE (file));
   g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
 
   if (!g_file_replace_contents_finish (file, result, NULL, &error))
     g_task_return_error (task, g_steal_pointer (&error));
@@ -73,7 +325,7 @@ ide_buildconfig_configuration_provider_save_cb (GObject      *object,
     g_task_return_boolean (task, TRUE);
 }
 
-void
+static void
 ide_buildconfig_configuration_provider_save_async (IdeConfigurationProvider *provider,
                                                    GCancellable             *cancellable,
                                                    GAsyncReadyCallback       callback,
@@ -82,75 +334,86 @@ ide_buildconfig_configuration_provider_save_async (IdeConfigurationProvider *pro
   IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
   g_autoptr(GHashTable) group_names = NULL;
   g_autoptr(GTask) task = NULL;
-  g_auto(GStrv) groups = NULL;
   g_autoptr(GFile) file = NULL;
   g_autoptr(GBytes) bytes = NULL;
   g_autoptr(GError) error = NULL;
-  gchar *data;
-  gsize length = 0;
+  g_auto(GStrv) groups = NULL;
+  g_autofree gchar *path = NULL;
+  g_autofree gchar *data = NULL;
+  IdeConfigurationManager *manager;
   IdeContext *context;
-  IdeVcs *vcs;
-  GFile *workdir;
-
-  IDE_ENTRY;
+  gboolean dirty = FALSE;
+  gsize length = 0;
 
   g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (self->key_file != NULL);
 
   task = g_task_new (self, cancellable, callback, user_data);
   g_task_set_source_tag (task, ide_buildconfig_configuration_provider_save_async);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+
+  dirty = self->key_file_dirty;
+
+  /* If no configs are dirty, short circuit to avoid writing any files to disk. */
+  for (guint i = 0; !dirty && i < self->configs->len; i++)
+    {
+      IdeConfiguration *config = g_ptr_array_index (self->configs, i);
+      dirty |= ide_configuration_get_dirty (config);
+    }
 
-  if (self->configurations == NULL || self->change_count == 0)
+  if (!dirty)
     {
       g_task_return_boolean (task, TRUE);
       return;
     }
 
-  self->change_count = 0;
-
-  context = ide_object_get_context (IDE_OBJECT (self->manager));
-  vcs = ide_context_get_vcs (context);
-  workdir = ide_vcs_get_working_directory (vcs);
-  file = g_file_get_child (workdir, DOT_BUILDCONFIG);
+  context = ide_object_get_context (IDE_OBJECT (self));
+  manager = ide_context_get_configuration_manager (context);
+  path = ide_context_build_filename (context, DOT_BUILDCONFIG, NULL);
+  file = g_file_new_for_path (path);
 
   /*
-   * NOTE:
+   * We keep the GKeyFile around from when we parsed .buildconfig, so that we
+   * can try to preserve comments and such when writing back.
    *
-   * We keep the GKeyFile around from when we parsed .buildconfig, so that
-   * we can try to preserve comments and such when writing back.
-   *
-   * This means that we need to fill in all our known configuration
-   * sections, and then remove any that were removed since we were
-   * parsed it last.
+   * This means that we need to fill in all our known configuration sections,
+   * and then remove any that were removed since we were parsed it last.
    */
 
-  if (self->key_file == NULL)
-    self->key_file = g_key_file_new ();
-
   group_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
 
-  for (guint i = 0; i < self->configurations->len; i++)
+  for (guint i = 0; i < self->configs->len; i++)
     {
-      IdeConfiguration *configuration = g_ptr_array_index (self->configurations, i);
-      IdeEnvironment *environment;
+      IdeConfiguration *config = g_ptr_array_index (self->configs, i);
+      g_autofree gchar *env_group = NULL;
+      const gchar *config_id;
+      IdeEnvironment *env;
       guint n_items;
-      guint j;
-      gchar *group;
-      gchar *group_environ;
 
-      group = g_strdup (ide_configuration_get_id (configuration));
-      group_environ = g_strdup_printf ("%s.environment", group);
+      if (!ide_configuration_get_dirty (config))
+        continue;
+
+      config_id = ide_configuration_get_id (config);
+      env_group = g_strdup_printf ("%s.environment", config_id);
+      env = ide_configuration_get_environment (config);
 
       /*
        * Track our known group names, so we can remove missing names after
        * we've updated the GKeyFile.
        */
-      g_hash_table_insert (group_names, group, NULL);
-      g_hash_table_insert (group_names, group_environ, NULL);
+      g_hash_table_insert (group_names, g_strdup (config_id), NULL);
+      g_hash_table_insert (group_names, g_strdup (env_group), NULL);
 
 #define PERSIST_STRING_KEY(key, getter) \
-      g_key_file_set_string (self->key_file, group, key, \
-                             ide_configuration_##getter (configuration) ?: "")
+      g_key_file_set_string (self->key_file, config_id, key, \
+                             ide_configuration_##getter (config) ?: "")
+#define PERSIST_STRV_KEY(key, getter) G_STMT_START { \
+      const gchar * const *val = ide_buildconfig_configuration_##getter (IDE_BUILDCONFIG_CONFIGURATION 
(config)); \
+      gsize vlen = val ? g_strv_length ((gchar **)val) : 0; \
+      g_key_file_set_string_list (self->key_file, config_id, key, val, vlen); \
+} G_STMT_END
+
       PERSIST_STRING_KEY ("name", get_display_name);
       PERSIST_STRING_KEY ("device", get_device_id);
       PERSIST_STRING_KEY ("runtime", get_runtime_id);
@@ -158,54 +421,58 @@ ide_buildconfig_configuration_provider_save_async (IdeConfigurationProvider *pro
       PERSIST_STRING_KEY ("run-opts", get_run_opts);
       PERSIST_STRING_KEY ("prefix", get_prefix);
       PERSIST_STRING_KEY ("app-id", get_app_id);
+      PERSIST_STRV_KEY ("postbuild", get_postbuild);
+      PERSIST_STRV_KEY ("prebuild", get_prebuild);
+
 #undef PERSIST_STRING_KEY
+#undef PERSIST_STRV_KEY
 
-      if (configuration == ide_configuration_manager_get_current (self->manager))
-        g_key_file_set_boolean (self->key_file, group, "default", TRUE);
+      if (config == ide_configuration_manager_get_current (manager))
+        g_key_file_set_boolean (self->key_file, config_id, "default", TRUE);
       else
-        g_key_file_remove_key (self->key_file, group, "default", NULL);
+        g_key_file_remove_key (self->key_file, config_id, "default", NULL);
 
-      environment = ide_configuration_get_environment (configuration);
+      env = ide_configuration_get_environment (config);
 
       /*
        * Remove all environment keys that are no longer specified in the
        * environment. This allows us to just do a single pass of additions
        * from the environment below.
        */
-      if (g_key_file_has_group (self->key_file, group_environ))
+      if (g_key_file_has_group (self->key_file, env_group))
         {
           g_auto(GStrv) keys = NULL;
 
-          if (NULL != (keys = g_key_file_get_keys (self->key_file, group_environ, NULL, NULL)))
+          if (NULL != (keys = g_key_file_get_keys (self->key_file, env_group, NULL, NULL)))
             {
-              for (j = 0; keys [j]; j++)
+              for (guint j = 0; keys [j]; j++)
                 {
-                  if (!ide_environment_getenv (environment, keys [j]))
-                    g_key_file_remove_key (self->key_file, group_environ, keys [j], NULL);
+                  if (!ide_environment_getenv (env, keys [j]))
+                    g_key_file_remove_key (self->key_file, env_group, keys [j], NULL);
                 }
             }
         }
 
-      n_items = g_list_model_get_n_items (G_LIST_MODEL (environment));
+      n_items = g_list_model_get_n_items (G_LIST_MODEL (env));
 
-      for (j = 0; j < n_items; j++)
+      for (guint j = 0; j < n_items; j++)
         {
           g_autoptr(IdeEnvironmentVariable) var = NULL;
           const gchar *key;
           const gchar *value;
 
-          var = g_list_model_get_item (G_LIST_MODEL (environment), j);
+          var = g_list_model_get_item (G_LIST_MODEL (env), j);
           key = ide_environment_variable_get_key (var);
           value = ide_environment_variable_get_value (var);
 
           if (!dzl_str_empty0 (key))
-            g_key_file_set_string (self->key_file, group_environ, key, value ?: "");
+            g_key_file_set_string (self->key_file, env_group, key, value ?: "");
         }
+
+      ide_configuration_set_dirty (config, FALSE);
     }
 
-  /*
-   * Now truncate any old groups in the keyfile.
-   */
+  /* Now truncate any old groups in the keyfile. */
   if (NULL != (groups = g_key_file_get_groups (self->key_file, NULL)))
     {
       for (guint i = 0; groups [i]; i++)
@@ -215,13 +482,23 @@ ide_buildconfig_configuration_provider_save_async (IdeConfigurationProvider *pro
         }
     }
 
-  if (NULL == (data = g_key_file_to_data (self->key_file, &length, &error)))
+  if (!(data = g_key_file_to_data (self->key_file, &length, &error)))
     {
       g_task_return_error (task, g_steal_pointer (&error));
-      IDE_EXIT;
+      return;
     }
 
-  bytes = g_bytes_new_take (data, length);
+  self->key_file_dirty = FALSE;
+
+  if (length == 0)
+    {
+      /* Remove the file if it exists, since it would be empty */
+      g_file_delete (file, cancellable, NULL);
+      g_task_return_boolean (task, TRUE);
+      return;
+    }
+
+  bytes = g_bytes_new_take (g_steal_pointer (&data), length);
 
   g_file_replace_contents_bytes_async (file,
                                        bytes,
@@ -231,432 +508,243 @@ ide_buildconfig_configuration_provider_save_async (IdeConfigurationProvider *pro
                                        cancellable,
                                        ide_buildconfig_configuration_provider_save_cb,
                                        g_steal_pointer (&task));
-
-  IDE_EXIT;
 }
 
-gboolean
+static gboolean
 ide_buildconfig_configuration_provider_save_finish (IdeConfigurationProvider  *provider,
                                                     GAsyncResult              *result,
                                                     GError                   **error)
 {
-  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
-
-  g_return_val_if_fail (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self), FALSE);
-  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (provider));
+  g_assert (G_IS_TASK (result));
+  g_assert (g_task_is_valid (G_TASK (result), provider));
 
   return g_task_propagate_boolean (G_TASK (result), error);
 }
 
-static gboolean
-ide_buildconfig_configuration_provider_do_writeback (gpointer data)
-{
-  IdeBuildconfigConfigurationProvider *self = data;
-
-  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-
-  self->writeback_handler = 0;
-
-  ide_buildconfig_configuration_provider_save_async (IDE_CONFIGURATION_PROVIDER (self), NULL, NULL, NULL);
-
-  return G_SOURCE_REMOVE;
-}
-
 static void
-ide_buildconfig_configuration_provider_queue_writeback (IdeBuildconfigConfigurationProvider *self)
+ide_buildconfig_configuration_provider_delete (IdeConfigurationProvider *provider,
+                                               IdeConfiguration         *config)
 {
-  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-
-  IDE_ENTRY;
-
-  if (self->writeback_handler != 0)
-    g_source_remove (self->writeback_handler);
-
-  self->writeback_handler = g_timeout_add_seconds (WRITEBACK_TIMEOUT_SECS,
-                                                   ide_buildconfig_configuration_provider_do_writeback,
-                                                   self);
-
-  IDE_EXIT;
-}
+  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
+  g_autoptr(IdeConfiguration) hold = NULL;
+  g_autofree gchar *env = NULL;
+  const gchar *config_id;
+  gboolean had_group;
 
-static void
-ide_buildconfig_configuration_provider_changed (IdeBuildconfigConfigurationProvider *self,
-                                                IdeConfiguration *configuration)
-{
   g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION (configuration));
-
-  self->change_count++;
+  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION (config));
+  g_assert (self->key_file != NULL);
+  g_assert (self->configs->len > 0);
 
-  ide_buildconfig_configuration_provider_queue_writeback (self);
-}
+  hold = g_object_ref (config);
 
-static void
-load_string (IdeConfiguration *configuration,
-             GKeyFile         *key_file,
-             const gchar      *group,
-             const gchar      *key,
-             const gchar      *property)
-{
-  g_assert (IDE_IS_CONFIGURATION (configuration));
-  g_assert (key_file != NULL);
-  g_assert (group != NULL);
-  g_assert (key != NULL);
-
-  if (g_key_file_has_key (key_file, group, key, NULL))
+  if (!g_ptr_array_remove (self->configs, hold))
     {
-      g_auto(GValue) value = G_VALUE_INIT;
-
-      g_value_init (&value, G_TYPE_STRING);
-      g_value_take_string (&value, g_key_file_get_string (key_file, group, key, NULL));
-      g_object_set_property (G_OBJECT (configuration), property, &value);
+      g_critical ("No such configuration %s",
+                  ide_configuration_get_id (hold));
+      return;
     }
-}
 
-static void
-load_environ (IdeConfiguration *configuration,
-              GKeyFile         *key_file,
-              const gchar      *group)
-{
-  IdeEnvironment *environment;
-  g_auto(GStrv) keys = NULL;
+  config_id = ide_configuration_get_id (config);
+  had_group = g_key_file_has_group (self->key_file, config_id);
+  env = g_strdup_printf ("%s.environment", config_id);
+  g_key_file_remove_group (self->key_file, config_id, NULL);
+  g_key_file_remove_group (self->key_file, env, NULL);
 
-  g_assert (IDE_IS_CONFIGURATION (configuration));
-  g_assert (key_file != NULL);
-  g_assert (group != NULL);
-
-  environment = ide_configuration_get_environment (configuration);
-  keys = g_key_file_get_keys (key_file, group, NULL, NULL);
+  self->key_file_dirty = had_group;
 
-  if (keys != NULL)
+  /*
+   * If we removed our last buildconfig, synthesize a new one to replace it so
+   * that we never have no configurations available. We add it before we remove
+   * @config so that we never have zero configurations available.
+   *
+   * At some point in the future we might want a read only NULL configuration
+   * for fallback, and group configs by type or something.  But until we have
+   * designs for that, this will do.
+   */
+  if (self->configs->len == 0)
     {
-      guint i;
+      g_autoptr(IdeConfiguration) new_config = NULL;
+      IdeContext *context = ide_object_get_context (IDE_OBJECT (self));
+
+      /* "Default" is not translated because .buildconfig can be checked in */
+      new_config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIGURATION,
+                                 "context", context,
+                                 "device-id", "local",
+                                 "display-name", "Default",
+                                 "id", "default",
+                                 "runtime-id", "host",
+                                 NULL);
 
-      for (i = 0; keys [i]; i++)
-        {
-          g_autofree gchar *value = NULL;
-
-          value = g_key_file_get_string (key_file, group, keys [i], NULL);
-
-          if (value != NULL)
-            ide_environment_setenv (environment, keys [i], value);
-        }
+      /*
+       * Only persist this back if there was data in the keyfile
+       * before we were requested to delete the build-config.
+       */
+      ide_configuration_set_dirty (new_config, had_group);
+      ide_configuration_provider_emit_added (provider, new_config);
     }
+
+  ide_configuration_provider_emit_removed (provider, hold);
 }
 
-static gboolean
-ide_buildconfig_configuration_provider_load_group (IdeBuildconfigConfigurationProvider  *self,
-                                                   GKeyFile                             *key_file,
-                                                   const gchar                          *group,
-                                                   GPtrArray                            *configs,
-                                                   GError                              **error)
+static void
+ide_buildconfig_configuration_provider_duplicate (IdeConfigurationProvider *provider,
+                                                  IdeConfiguration         *config)
 {
-  g_autoptr(IdeConfiguration) configuration = NULL;
-  g_autofree gchar *env_group = NULL;
+  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
+  g_autoptr(IdeConfiguration) new_config = NULL;
+  g_autofree GParamSpec **pspecs = NULL;
+  g_autofree gchar *new_config_id = NULL;
+  g_autofree gchar *new_name = NULL;
+  IdeConfigurationManager *manager;
+  const gchar *config_id;
+  const gchar *name;
   IdeContext *context;
+  guint n_pspecs = 0;
 
   g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-  g_assert (key_file != NULL);
-  g_assert (group != NULL);
-
-  context = ide_object_get_context (IDE_OBJECT (self->manager));
-
-  configuration = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIGURATION,
-                                "id", group,
-                                "context", context,
-                                NULL);
-
-  load_string (configuration, key_file, group, "config-opts", "config-opts");
-  load_string (configuration, key_file, group, "device", "device-id");
-  load_string (configuration, key_file, group, "name", "display-name");
-  load_string (configuration, key_file, group, "run-opts", "run-opts");
-  load_string (configuration, key_file, group, "runtime", "runtime-id");
-  load_string (configuration, key_file, group, "prefix", "prefix");
-  load_string (configuration, key_file, group, "app-id", "app-id");
-
-  if (g_key_file_has_key (key_file, group, "prebuild", NULL))
-    {
-      g_auto(GStrv) commands = NULL;
-
-      commands = g_key_file_get_string_list (key_file, group, "prebuild", NULL, NULL);
-      ide_buildconfig_configuration_set_prebuild (IDE_BUILDCONFIG_CONFIGURATION (configuration),
-                                                  (const gchar * const *)commands);
-    }
-
-  if (g_key_file_has_key (key_file, group, "postbuild", NULL))
-    {
-      g_auto(GStrv) commands = NULL;
-
-      commands = g_key_file_get_string_list (key_file, group, "postbuild", NULL, NULL);
-      ide_buildconfig_configuration_set_postbuild (IDE_BUILDCONFIG_CONFIGURATION (configuration),
-                                                   (const gchar * const *)commands);
-    }
-
-  env_group = g_strdup_printf ("%s.environment", group);
+  g_assert (IDE_IS_CONFIGURATION (config));
+  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION (config));
 
-  if (g_key_file_has_group (key_file, env_group))
-    load_environ (configuration, key_file, env_group);
+  context = ide_object_get_context (IDE_OBJECT (self));
+  g_assert (IDE_IS_CONTEXT (context));
 
-  ide_configuration_set_dirty (configuration, FALSE);
+  manager = ide_context_get_configuration_manager (context);
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
 
-  if (g_key_file_get_boolean (key_file, group, "default", NULL))
-    g_object_set_data (G_OBJECT (configuration), "WAS_DEFAULT", GINT_TO_POINTER (TRUE));
+  config_id = ide_configuration_get_id (config);
+  g_return_if_fail (config_id != NULL);
 
-  g_signal_connect_object (configuration,
-                           "changed",
-                           G_CALLBACK (ide_buildconfig_configuration_provider_changed),
-                           self,
-                           G_CONNECT_SWAPPED);
+  new_config_id = get_next_id (manager, config_id);
+  g_return_if_fail (new_config_id != NULL);
 
-  g_ptr_array_add (configs, g_steal_pointer (&configuration));
+  name = ide_configuration_get_display_name (config);
+  /* translators: %s is replaced with the name of the configuration */
+  new_name = g_strdup_printf (_("%s (Copy)"), name);
 
-  return TRUE;
-}
-
-static gboolean
-ide_buildconfig_configuration_provider_restore (IdeBuildconfigConfigurationProvider  *self,
-                                                GFile                                *file,
-                                                GPtrArray                            *configs,
-                                                GCancellable                         *cancellable,
-                                                GError                              **error)
-{
-  g_autofree gchar *contents = NULL;
-  g_auto(GStrv) groups = NULL;
-  gsize length = 0;
-  guint i;
+  new_config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIGURATION,
+                             "id", new_config_id,
+                             "context", context,
+                             "display-name", new_name,
+                             NULL);
 
-  IDE_ENTRY;
+  pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (new_config), &n_pspecs);
 
-  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-  g_assert (self->key_file == NULL);
-  g_assert (G_IS_FILE (file));
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  self->key_file = g_key_file_new ();
-
-  if (!g_file_load_contents (file, cancellable, &contents, &length, NULL, error))
-    IDE_RETURN (FALSE);
+  for (guint i = 0; i < n_pspecs; i++)
+    {
+      GParamSpec *pspec = pspecs[i];
 
-  if (!g_key_file_load_from_data (self->key_file,
-                                  contents,
-                                  length,
-                                  G_KEY_FILE_KEEP_COMMENTS,
-                                  error))
-    IDE_RETURN (FALSE);
+      if (g_str_equal (pspec->name, "context") ||
+          g_str_equal (pspec->name, "id") ||
+          g_str_equal (pspec->name, "display-name") ||
+          g_type_is_a (pspec->value_type, G_TYPE_BOXED) ||
+          g_type_is_a (pspec->value_type, G_TYPE_OBJECT))
+        continue;
 
-  groups = g_key_file_get_groups (self->key_file, NULL);
 
-  for (i = 0; groups [i]; i++)
-    {
-      if (g_str_has_suffix (groups [i], ".environment"))
-        continue;
+      if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
+          (pspec->flags & G_PARAM_CONSTRUCT_ONLY) == 0)
+        {
+          GValue value = G_VALUE_INIT;
 
-      if (!ide_buildconfig_configuration_provider_load_group (self, self->key_file, groups [i], configs, 
error))
-        IDE_RETURN (FALSE);
+          g_value_init (&value, pspec->value_type);
+          g_object_get_property (G_OBJECT (config), pspec->name, &value);
+          g_object_set_property (G_OBJECT (new_config), pspec->name, &value);
+        }
     }
 
-  IDE_RETURN (TRUE);
+  ide_configuration_set_dirty (new_config, TRUE);
+  ide_configuration_provider_emit_added (provider, new_config);
 }
 
 static void
-ide_buildconfig_configuration_provider_load_worker (GTask        *task,
-                                                    gpointer      source_object,
-                                                    gpointer      task_data,
-                                                    GCancellable *cancellable)
+ide_buildconfig_configuration_provider_unload (IdeConfigurationProvider *provider)
 {
-  IdeBuildconfigConfigurationProvider *self = source_object;
-  g_autoptr(GFile) settings_file = NULL;
-  g_autoptr(GError) error = NULL;
+  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
   g_autoptr(GPtrArray) configs = NULL;
-  IdeConfigurationManager *manager = task_data;
-  IdeContext *context;
-  IdeVcs *vcs;
-  GFile *workdir;
-
-  IDE_ENTRY;
 
-  g_assert (G_IS_TASK (task));
   g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (self->configs != NULL);
 
-  configs = g_ptr_array_new_with_free_func (g_object_unref);
+  configs = g_steal_pointer (&self->configs);
+  self->configs = g_ptr_array_new_with_free_func (g_object_unref);
 
-  context = ide_object_get_context (IDE_OBJECT (manager));
-  vcs = ide_context_get_vcs (context);
-  workdir = ide_vcs_get_working_directory (vcs);
-  settings_file = g_file_get_child (workdir, DOT_BUILDCONFIG);
-
-  if (g_file_query_exists (settings_file, cancellable))
+  for (guint i = 0; i < configs->len; i++)
     {
-      if (!ide_buildconfig_configuration_provider_restore (self, settings_file, configs, cancellable, 
&error))
-        {
-          g_task_return_error (task, g_steal_pointer (&error));
-          IDE_EXIT;
-        }
+      IdeConfiguration *config = g_ptr_array_index (configs, i);
+      ide_configuration_provider_emit_removed (provider, config);
     }
-
-  g_task_return_pointer (task, g_steal_pointer (&configs), (GDestroyNotify)g_ptr_array_unref);
-
-  IDE_EXIT;
 }
 
 static void
-ide_buildconfig_configuration_provider_load_cb (GObject      *object,
-                                                GAsyncResult *result,
-                                                gpointer      user_data)
+ide_buildconfig_configuration_provider_added (IdeConfigurationProvider *provider,
+                                              IdeConfiguration         *config)
 {
-  IdeBuildconfigConfigurationProvider *self;
-  g_autoptr(GPtrArray) ar = NULL;
-  g_autoptr(GError) error = NULL;
-  g_autoptr(GTask) task = user_data;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (object));
-  g_assert (G_IS_TASK (result));
-  g_assert (G_IS_TASK (task));
-
-  ar = g_task_propagate_pointer (G_TASK (result), &error);
-  self = g_task_get_source_object (G_TASK (result));
+  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
 
   g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-  g_assert (self->configurations == NULL);
-
-  if (ar != NULL && self->manager != NULL)
-    {
-      for (guint i = 0; i < ar->len; i++)
-        {
-          IdeConfiguration *config = g_ptr_array_index (ar, i);
-
-          g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION (config));
-
-          ide_configuration_manager_add (self->manager, config);
-
-          if (g_object_get_data (G_OBJECT (config), "WAS_DEFAULT"))
-            {
-              ide_configuration_manager_set_current (self->manager, config);
-              g_object_set_data (G_OBJECT (config), "WAS_DEFAULT", NULL);
-            }
-        }
-    }
+  g_assert (IDE_IS_CONFIGURATION (config));
+  g_assert (self->configs != NULL);
 
-  self->configurations = g_steal_pointer (&ar);
-
-  if (error != NULL)
-    g_task_return_error (task, g_steal_pointer (&error));
-  else
-    g_task_return_boolean (task, TRUE);
-
-  IDE_EXIT;
+  g_ptr_array_add (self->configs, g_object_ref (config));
 }
 
 static void
-ide_buildconfig_configuration_provider_load_async (IdeConfigurationProvider *provider,
-                                                   IdeConfigurationManager  *manager,
-                                                   GCancellable             *cancellable,
-                                                   GAsyncReadyCallback       callback,
-                                                   gpointer                  user_data)
+ide_buildconfig_configuration_provider_removed (IdeConfigurationProvider *provider,
+                                                IdeConfiguration         *config)
 {
   IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
-  g_autoptr(GTask) parent_task = NULL;
-  g_autoptr(GTask) task = NULL;
-
-  IDE_ENTRY;
 
   g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  dzl_set_weak_pointer (&self->manager, manager);
-
-  /* This task is needed so the caller knows when the load finishes */
-  parent_task = g_task_new (self, cancellable, callback, user_data);
+  g_assert (IDE_IS_CONFIGURATION (config));
+  g_assert (self->configs != NULL);
 
-  /* This task is used to run the load_worker in its own thread */
-  task = g_task_new (self, cancellable, ide_buildconfig_configuration_provider_load_cb, g_steal_pointer 
(&parent_task));
-  g_task_set_source_tag (task, ide_buildconfig_configuration_provider_load_async);
-  g_task_set_task_data (task, g_object_ref (manager), g_object_unref);
-  g_task_run_in_thread (task, ide_buildconfig_configuration_provider_load_worker);
-
-  IDE_EXIT;
-}
-
-gboolean
-ide_buildconfig_configuration_provider_load_finish (IdeConfigurationProvider  *provider,
-                                                    GAsyncResult              *result,
-                                                    GError                   **error)
-{
-  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
-
-  g_return_val_if_fail (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self), FALSE);
-  g_return_val_if_fail (G_IS_TASK (result), FALSE);
-
-  return g_task_propagate_boolean (G_TASK (result), error);
+  /* It's possible we already removed it by now */
+  g_ptr_array_remove (self->configs, config);
 }
 
 static void
-ide_buildconfig_configuration_provider_unload (IdeConfigurationProvider *provider,
-                                               IdeConfigurationManager  *manager)
+configuration_provider_iface_init (IdeConfigurationProviderInterface *iface)
 {
-  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
-
-  dzl_clear_source (&self->writeback_handler);
-
-  if (self->configurations != NULL)
-    {
-      for (guint i= 0; i < self->configurations->len; i++)
-        {
-          IdeConfiguration *configuration = g_ptr_array_index (self->configurations, i);
-
-          ide_configuration_manager_remove (manager, configuration);
-        }
-    }
-
-  g_clear_pointer (&self->configurations, g_ptr_array_unref);
-
-  dzl_clear_weak_pointer (&self->manager);
-
-  IDE_EXIT;
+  iface->added = ide_buildconfig_configuration_provider_added;
+  iface->removed = ide_buildconfig_configuration_provider_removed;
+  iface->load_async = ide_buildconfig_configuration_provider_load_async;
+  iface->load_finish = ide_buildconfig_configuration_provider_load_finish;
+  iface->save_async = ide_buildconfig_configuration_provider_save_async;
+  iface->save_finish = ide_buildconfig_configuration_provider_save_finish;
+  iface->delete = ide_buildconfig_configuration_provider_delete;
+  iface->duplicate = ide_buildconfig_configuration_provider_duplicate;
+  iface->unload = ide_buildconfig_configuration_provider_unload;
 }
 
-void
-ide_buildconfig_configuration_provider_track_config (IdeBuildconfigConfigurationProvider *self,
-                                                     IdeBuildconfigConfiguration         *config)
+G_DEFINE_TYPE_WITH_CODE (IdeBuildconfigConfigurationProvider,
+                         ide_buildconfig_configuration_provider,
+                         IDE_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_CONFIGURATION_PROVIDER,
+                                                configuration_provider_iface_init))
+
+static void
+ide_buildconfig_configuration_provider_finalize (GObject *object)
 {
-  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION (config));
+  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)object;
 
-  g_signal_connect_object (config,
-                           "changed",
-                           G_CALLBACK (ide_buildconfig_configuration_provider_changed),
-                           self,
-                           G_CONNECT_SWAPPED);
+  g_clear_pointer (&self->configs, g_ptr_array_unref);
+  g_clear_pointer (&self->key_file, g_key_file_free);
 
-  g_ptr_array_add (self->configurations, g_object_ref (config));
+  G_OBJECT_CLASS (ide_buildconfig_configuration_provider_parent_class)->finalize (object);
 }
 
 static void
 ide_buildconfig_configuration_provider_class_init (IdeBuildconfigConfigurationProviderClass *klass)
 {
-}
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-static void
-ide_buildconfig_configuration_provider_init (IdeBuildconfigConfigurationProvider *self)
-{
+  object_class->finalize = ide_buildconfig_configuration_provider_finalize;
 }
 
 static void
-configuration_provider_iface_init (IdeConfigurationProviderInterface *iface)
+ide_buildconfig_configuration_provider_init (IdeBuildconfigConfigurationProvider *self)
 {
-  iface->load_async = ide_buildconfig_configuration_provider_load_async;
-  iface->load_finish = ide_buildconfig_configuration_provider_load_finish;
-  iface->unload = ide_buildconfig_configuration_provider_unload;
-  iface->save_async = ide_buildconfig_configuration_provider_save_async;
-  iface->save_finish = ide_buildconfig_configuration_provider_save_finish;
+  self->configs = g_ptr_array_new_with_free_func (g_object_unref);
 }
diff --git a/src/libide/buildconfig/ide-buildconfig-configuration-provider.h 
b/src/libide/buildconfig/ide-buildconfig-configuration-provider.h
index 836c03dd0..ce8f3e49d 100644
--- a/src/libide/buildconfig/ide-buildconfig-configuration-provider.h
+++ b/src/libide/buildconfig/ide-buildconfig-configuration-provider.h
@@ -18,13 +18,7 @@
 
 #pragma once
 
-#include <glib.h>
-
-#include "ide-version-macros.h"
-
-#include "ide-types.h"
-
-#include "buildconfig/ide-buildconfig-configuration.h"
+#include "ide-object.h"
 
 G_BEGIN_DECLS
 
@@ -32,8 +26,4 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeBuildconfigConfigurationProvider, ide_buildconfig_configuration_provider, IDE, 
BUILDCONFIG_CONFIGURATION_PROVIDER, IdeObject)
 
-IDE_AVAILABLE_IN_ALL
-void ide_buildconfig_configuration_provider_track_config (IdeBuildconfigConfigurationProvider *self,
-                                                          IdeBuildconfigConfiguration         *config);
-
 G_END_DECLS
diff --git a/src/libide/buildconfig/ide-buildconfig-configuration.c 
b/src/libide/buildconfig/ide-buildconfig-configuration.c
index c08b7ec27..293fb5fe9 100644
--- a/src/libide/buildconfig/ide-buildconfig-configuration.c
+++ b/src/libide/buildconfig/ide-buildconfig-configuration.c
@@ -23,6 +23,7 @@
 struct _IdeBuildconfigConfiguration
 {
   IdeConfiguration   parent_instance;
+
   gchar            **prebuild;
   gchar            **postbuild;
 };
@@ -113,7 +114,7 @@ ide_buildconfig_configuration_class_init (IdeBuildconfigConfigurationClass *klas
     g_param_spec_boxed ("postbuild", NULL, NULL,
                         G_TYPE_STRV,
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
-  
+
   g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
diff --git a/src/libide/buildui/buildui.plugin b/src/libide/buildui/buildui.plugin
index 31524ca32..9b10a2ca1 100644
--- a/src/libide/buildui/buildui.plugin
+++ b/src/libide/buildui/buildui.plugin
@@ -8,5 +8,3 @@ Depends=editor
 Hidden=true
 Builtin=true
 Embedded=ide_build_tool_register_types
-X-Tool-Name=build
-X-Tool-Description=Build a project
diff --git a/src/libide/buildui/ide-build-perspective.c b/src/libide/buildui/ide-build-perspective.c
index f59166dbc..3694d8b42 100644
--- a/src/libide/buildui/ide-build-perspective.c
+++ b/src/libide/buildui/ide-build-perspective.c
@@ -211,10 +211,12 @@ duplicate_configuration (GSimpleAction *action,
 
   if (self->configuration != NULL)
     {
-      g_autoptr(IdeConfiguration) copy = NULL;
+      IdeContext *context;
+      IdeConfigurationManager *config_manager;
 
-      copy = ide_configuration_duplicate (self->configuration);
-      ide_configuration_manager_add (self->configuration_manager, copy);
+      context = ide_widget_get_context (GTK_WIDGET (self));
+      config_manager = ide_context_get_configuration_manager (context);
+      ide_configuration_manager_duplicate (config_manager, self->configuration);
     }
 }
 
@@ -236,7 +238,7 @@ delete_configuration (GSimpleAction *action,
        * self->configuration will change during this call.
        */
       config = g_object_ref (self->configuration);
-      ide_configuration_manager_remove (self->configuration_manager, config);
+      ide_configuration_manager_delete (self->configuration_manager, config);
 
       /*
        * Switch to the first configuration in the list. The configuration
diff --git a/src/libide/buildui/ide-build-plugin.c b/src/libide/buildui/ide-build-plugin.c
index dd8ba146b..b17ececd8 100644
--- a/src/libide/buildui/ide-build-plugin.c
+++ b/src/libide/buildui/ide-build-plugin.c
@@ -27,9 +27,6 @@
 void
 ide_build_tool_register_types (PeasObjectModule *module)
 {
-  peas_object_module_register_extension_type (module,
-                                              IDE_TYPE_APPLICATION_TOOL,
-                                              IDE_TYPE_BUILD_TOOL);
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_WORKBENCH_ADDIN,
                                               IDE_TYPE_BUILD_WORKBENCH_ADDIN);
diff --git a/src/libide/buildui/meson.build b/src/libide/buildui/meson.build
index cbedb1f06..e82edc676 100644
--- a/src/libide/buildui/meson.build
+++ b/src/libide/buildui/meson.build
@@ -12,8 +12,6 @@ buildui_private_sources = [
   'ide-build-plugin.c',
   'ide-build-stage-row.c',
   'ide-build-stage-row.h',
-  'ide-build-tool.c',
-  'ide-build-tool.h',
   'ide-build-workbench-addin.c',
   'ide-build-workbench-addin.h',
   'ide-environment-editor-row.c',
diff --git a/src/libide/config/ide-configuration-manager.c b/src/libide/config/ide-configuration-manager.c
index 89baafa75..eefc1df41 100644
--- a/src/libide/config/ide-configuration-manager.c
+++ b/src/libide/config/ide-configuration-manager.c
@@ -24,22 +24,33 @@
 #include "ide-context.h"
 #include "ide-debug.h"
 
+#include "application/ide-application.h"
 #include "config/ide-configuration-manager.h"
 #include "config/ide-configuration.h"
 #include "config/ide-configuration-provider.h"
-
 #include "buildconfig/ide-buildconfig-configuration.h"
 #include "buildconfig/ide-buildconfig-configuration-provider.h"
 
+#define WRITEBACK_DELAY_SEC 2
+
 struct _IdeConfigurationManager
 {
   GObject           parent_instance;
 
-  GPtrArray        *configurations;
+  GCancellable     *cancellable;
+  GArray           *configs;
   IdeConfiguration *current;
-  PeasExtensionSet *extensions;
+  PeasExtensionSet *providers;
+
+  guint             queued_save_source;
 };
 
+typedef struct
+{
+  IdeConfigurationProvider *provider;
+  IdeConfiguration         *config;
+} ConfigInfo;
+
 static void async_initable_iface_init           (GAsyncInitableIface *iface);
 static void list_model_iface_init               (GListModelInterface *iface);
 static void ide_configuration_manager_save_tick (GTask               *task);
@@ -64,47 +75,12 @@ static GParamSpec *properties [LAST_PROP];
 static guint signals [N_SIGNALS];
 
 static void
-ide_configuration_manager_track_buildconfig (PeasExtensionSet *set,
-                                             PeasPluginInfo   *plugin_info,
-                                             PeasExtension    *exten,
-                                             gpointer          user_data)
-{
-  IdeConfigurationProvider *provider = (IdeConfigurationProvider *)exten;
-  IdeConfiguration *config = user_data;
-
-  g_assert (PEAS_IS_EXTENSION_SET (set));
-  g_assert (plugin_info != NULL);
-  g_assert (IDE_IS_CONFIGURATION_PROVIDER (provider));
-  g_assert (!config || IDE_IS_BUILDCONFIG_CONFIGURATION (config));
-
-  if (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (provider) && config != NULL)
-    ide_buildconfig_configuration_provider_track_config (IDE_BUILDCONFIG_CONFIGURATION_PROVIDER (provider),
-                                                         IDE_BUILDCONFIG_CONFIGURATION (config));
-}
-
-static void
-ide_configuration_manager_add_default (IdeConfigurationManager *self)
+config_info_clear (gpointer data)
 {
-  g_autoptr(IdeBuildconfigConfiguration) config = NULL;
-  IdeContext *context;
-
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  ConfigInfo *info = data;
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-  config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIGURATION,
-                         "id", "default",
-                         "context", context,
-                         "device-id", "local",
-                         "runtime-id", "host",
-                         NULL);
-  ide_configuration_set_display_name (IDE_CONFIGURATION (config), _("Default"));
-  ide_configuration_manager_add (self, IDE_CONFIGURATION (config));
-  if (self->configurations->len == 1)
-    ide_configuration_manager_set_current (self, IDE_CONFIGURATION (config));
-
-  peas_extension_set_foreach (self->extensions,
-                              ide_configuration_manager_track_buildconfig,
-                              config);
+  g_clear_object (&info->config);
+  g_clear_object (&info->provider);
 }
 
 static void
@@ -186,8 +162,8 @@ ide_configuration_manager_save_async (IdeConfigurationManager *self,
                                       GAsyncReadyCallback      callback,
                                       gpointer                 user_data)
 {
-  g_autoptr(GTask) task = NULL;
   g_autoptr(GPtrArray) providers = NULL;
+  g_autoptr(GTask) task = NULL;
 
   IDE_ENTRY;
 
@@ -196,22 +172,18 @@ ide_configuration_manager_save_async (IdeConfigurationManager *self,
 
   task = g_task_new (self, cancellable, callback, user_data);
   g_task_set_source_tag (task, ide_configuration_manager_save_async);
+  g_task_set_priority (task, G_PRIORITY_LOW);
 
   providers = g_ptr_array_new_with_free_func (g_object_unref);
-
-  peas_extension_set_foreach (self->extensions,
+  peas_extension_set_foreach (self->providers,
                               ide_configuration_manager_collect_providers,
                               providers);
+  g_task_set_task_data (task, g_ptr_array_ref (providers), (GDestroyNotify)g_ptr_array_unref);
 
   if (providers->len == 0)
-    {
-      g_task_return_boolean (task, TRUE);
-      IDE_EXIT;
-    }
-
-  g_task_set_task_data (task, g_steal_pointer (&providers), (GDestroyNotify)g_ptr_array_unref);
-
-  ide_configuration_manager_save_tick (task);
+    g_task_return_boolean (task, TRUE);
+  else
+    ide_configuration_manager_save_tick (task);
 
   IDE_EXIT;
 }
@@ -244,23 +216,30 @@ ide_configuration_manager_get_configuration (IdeConfigurationManager *self,
   g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), NULL);
   g_return_val_if_fail (id != NULL, NULL);
 
-  for (guint i = 0; i < self->configurations->len; i++)
+  for (guint i = 0; i < self->configs->len; i++)
     {
-      IdeConfiguration *config = g_ptr_array_index (self->configurations, i);
-      const gchar *config_id;
-
-      g_assert (config != NULL);
-      g_assert (IDE_IS_CONFIGURATION (config));
+      const ConfigInfo *info = &g_array_index (self->configs, ConfigInfo, i);
 
-      config_id = ide_configuration_get_id (config);
+      g_assert (IDE_IS_CONFIGURATION (info->config));
 
-      if (dzl_str_equal0 (config_id, id))
-        return config;
+      if (dzl_str_equal0 (id, ide_configuration_get_id (info->config)))
+        return info->config;
     }
 
   return NULL;
 }
 
+static const gchar *
+ide_configuration_manager_get_display_name (IdeConfigurationManager *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), NULL);
+
+  if (self->current != NULL)
+    return ide_configuration_get_display_name (self->current);
+
+  return "";
+}
+
 static void
 ide_configuration_manager_notify_display_name (IdeConfigurationManager *self,
                                                GParamSpec              *pspec,
@@ -272,12 +251,23 @@ ide_configuration_manager_notify_display_name (IdeConfigurationManager *self,
   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CURRENT_DISPLAY_NAME]);
 }
 
+static void
+ide_configuration_manager_dispose (GObject *object)
+{
+  IdeConfigurationManager *self = (IdeConfigurationManager *)object;
+
+  g_cancellable_cancel (self->cancellable);
+
+  G_OBJECT_CLASS (ide_configuration_manager_parent_class)->dispose (object);
+}
+
 static void
 ide_configuration_manager_finalize (GObject *object)
 {
   IdeConfigurationManager *self = (IdeConfigurationManager *)object;
 
-  g_clear_pointer (&self->configurations, g_ptr_array_unref);
+  g_clear_object (&self->cancellable);
+  g_clear_pointer (&self->configs, g_array_unref);
 
   if (self->current != NULL)
     {
@@ -306,8 +296,7 @@ ide_configuration_manager_get_property (GObject    *object,
 
     case PROP_CURRENT_DISPLAY_NAME:
       {
-        IdeConfiguration *current = ide_configuration_manager_get_current (self);
-        g_value_set_string (value, ide_configuration_get_display_name (current));
+        g_value_set_string (value, ide_configuration_manager_get_display_name (self));
         break;
       }
 
@@ -340,6 +329,7 @@ ide_configuration_manager_class_init (IdeConfigurationManagerClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
+  object_class->dispose = ide_configuration_manager_dispose;
   object_class->finalize = ide_configuration_manager_finalize;
   object_class->get_property = ide_configuration_manager_get_property;
   object_class->set_property = ide_configuration_manager_set_property;
@@ -349,7 +339,7 @@ ide_configuration_manager_class_init (IdeConfigurationManagerClass *klass)
                          "Current",
                          "The current configuration for the context",
                          IDE_TYPE_CONFIGURATION,
-                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
 
   properties [PROP_CURRENT_DISPLAY_NAME] =
     g_param_spec_string ("current-display-name",
@@ -362,6 +352,7 @@ ide_configuration_manager_class_init (IdeConfigurationManagerClass *klass)
 
   /**
    * IdeConfigurationManager::invalidate:
+   * @self: an #IdeConfigurationManager
    *
    * This signal is emitted any time a new configuration is selected or the
    * currently selected configurations state changes.
@@ -370,13 +361,17 @@ ide_configuration_manager_class_init (IdeConfigurationManagerClass *klass)
     g_signal_new ("invalidate",
                   G_TYPE_FROM_CLASS (klass),
                   G_SIGNAL_RUN_LAST,
-                  0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+                  0, NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
 }
 
 static void
 ide_configuration_manager_init (IdeConfigurationManager *self)
 {
-  self->configurations = g_ptr_array_new_with_free_func (g_object_unref);
+  self->cancellable = g_cancellable_new ();
+  self->configs = g_array_new (FALSE, FALSE, sizeof (ConfigInfo));
+  g_array_set_clear_func (self->configs, config_info_clear);
 }
 
 static GType
@@ -391,8 +386,9 @@ ide_configuration_manager_get_n_items (GListModel *model)
   IdeConfigurationManager *self = (IdeConfigurationManager *)model;
 
   g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_assert (self->configs != NULL);
 
-  return self->configurations->len;
+  return self->configs->len;
 }
 
 static gpointer
@@ -400,11 +396,14 @@ ide_configuration_manager_get_item (GListModel *model,
                                     guint       position)
 {
   IdeConfigurationManager *self = (IdeConfigurationManager *)model;
+  const ConfigInfo *info;
 
   g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), NULL);
-  g_return_val_if_fail (position < self->configurations->len, NULL);
+  g_return_val_if_fail (position < self->configs->len, NULL);
+
+  info = &g_array_index (self->configs, ConfigInfo, position);
 
-  return g_object_ref (g_ptr_array_index (self->configurations, position));
+  return g_object_ref (info->config);
 }
 
 static void
@@ -415,12 +414,107 @@ list_model_iface_init (GListModelInterface *iface)
   iface->get_item = ide_configuration_manager_get_item;
 }
 
+static gboolean
+ide_configuration_manager_do_save (gpointer data)
+{
+  IdeConfigurationManager *self = data;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+
+  self->queued_save_source = 0;
+
+  ide_configuration_manager_save_async (self, NULL, NULL, NULL);
+
+  IDE_RETURN (G_SOURCE_REMOVE);
+}
+
 static void
-ide_configuration_manager_load_cb (GObject      *object,
-                                   GAsyncResult *result,
-                                   gpointer      user_data)
+ide_configuration_manager_changed (IdeConfigurationManager *self,
+                                   IdeConfiguration        *config)
+{
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_assert (IDE_IS_CONFIGURATION (config));
+
+  g_signal_emit (self, signals [INVALIDATE], 0);
+
+  dzl_clear_source (&self->queued_save_source);
+  self->queued_save_source =
+    g_timeout_add_seconds_full (G_PRIORITY_LOW,
+                                WRITEBACK_DELAY_SEC,
+                                ide_configuration_manager_do_save,
+                                g_object_ref (self),
+                                g_object_unref);
+}
+
+static void
+ide_configuration_manager_config_added (IdeConfigurationManager  *self,
+                                        IdeConfiguration         *config,
+                                        IdeConfigurationProvider *provider)
+{
+  ConfigInfo info = {0};
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_assert (IDE_IS_CONFIGURATION (config));
+  g_assert (IDE_IS_CONFIGURATION_PROVIDER (provider));
+
+  g_signal_connect_object (config,
+                           "changed",
+                           G_CALLBACK (ide_configuration_manager_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  info.provider = g_object_ref (provider);
+  info.config = g_object_ref (config);
+  g_array_append_val (self->configs, info);
+
+  g_list_model_items_changed (G_LIST_MODEL (self), self->configs->len - 1, 0, 1);
+
+  if (self->current == NULL)
+    ide_configuration_manager_set_current (self, config);
+
+  IDE_EXIT;
+}
+
+static void
+ide_configuration_manager_config_removed (IdeConfigurationManager  *self,
+                                          IdeConfiguration         *config,
+                                          IdeConfigurationProvider *provider)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_assert (IDE_IS_CONFIGURATION (config));
+  g_assert (IDE_IS_CONFIGURATION_PROVIDER (provider));
+
+  for (guint i = 0; i < self->configs->len; i++)
+    {
+      const ConfigInfo *info = &g_array_index (self->configs, ConfigInfo, i);
+
+      if (info->provider == provider && info->config == config)
+        {
+          g_signal_handlers_disconnect_by_func (config,
+                                                G_CALLBACK (ide_configuration_manager_changed),
+                                                self);
+          g_array_remove_index (self->configs, i);
+          g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0);
+          break;
+        }
+    }
+
+  IDE_EXIT;
+}
+
+static void
+ide_configuration_manager_provider_load_cb (GObject      *object,
+                                            GAsyncResult *result,
+                                            gpointer      user_data)
 {
   IdeConfigurationProvider *provider = (IdeConfigurationProvider *)object;
+  IdeContext *context;
   g_autoptr(IdeConfigurationManager) self = user_data;
   g_autoptr(GError) error = NULL;
 
@@ -430,47 +524,105 @@ ide_configuration_manager_load_cb (GObject      *object,
   g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
   g_assert (G_IS_TASK (result));
 
+  context = ide_object_get_context (IDE_OBJECT (self));
+
   if (!ide_configuration_provider_load_finish (provider, result, &error))
-    g_warning ("%s failed to initialize: %s",
-               G_OBJECT_TYPE_NAME (provider), error->message);
+    ide_context_warning (context,
+                         "Failed to initialize config provider: %s: %s",
+                         G_OBJECT_TYPE_NAME (provider), error->message);
 
   IDE_EXIT;
 }
 
 static void
-ide_configuration_manager_extension_added (PeasExtensionSet *set,
-                                           PeasPluginInfo   *plugin_info,
-                                           PeasExtension    *exten,
-                                           gpointer          user_data)
+provider_connect (IdeConfigurationManager  *self,
+                  IdeConfigurationProvider *provider)
+{
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_assert (IDE_IS_CONFIGURATION_PROVIDER (provider));
+
+  g_signal_connect_object (provider,
+                           "added",
+                           G_CALLBACK (ide_configuration_manager_config_added),
+                           self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (provider,
+                           "removed",
+                           G_CALLBACK (ide_configuration_manager_config_removed),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+provider_disconnect (IdeConfigurationManager  *self,
+                     IdeConfigurationProvider *provider)
+{
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_assert (IDE_IS_CONFIGURATION_PROVIDER (provider));
+
+  g_signal_handlers_disconnect_by_func (provider,
+                                        G_CALLBACK (ide_configuration_manager_config_added),
+                                        self);
+  g_signal_handlers_disconnect_by_func (provider,
+                                        G_CALLBACK (ide_configuration_manager_config_removed),
+                                        self);
+}
+
+static void
+ide_configuration_manager_provider_added (PeasExtensionSet *set,
+                                          PeasPluginInfo   *plugin_info,
+                                          PeasExtension    *exten,
+                                          gpointer          user_data)
 {
   IdeConfigurationManager *self = user_data;
   IdeConfigurationProvider *provider = (IdeConfigurationProvider *)exten;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (PEAS_IS_EXTENSION_SET (set));
   g_assert (plugin_info != NULL);
   g_assert (IDE_IS_CONFIGURATION_PROVIDER (provider));
 
+  provider_connect (self, provider);
+
   ide_configuration_provider_load_async (provider,
-                                         self,
-                                         NULL,
-                                         ide_configuration_manager_load_cb,
+                                         self->cancellable,
+                                         ide_configuration_manager_provider_load_cb,
                                          g_object_ref (self));
 }
 
 static void
-ide_configuration_manager_extension_removed (PeasExtensionSet *set,
-                                             PeasPluginInfo   *plugin_info,
-                                             PeasExtension    *exten,
-                                             gpointer          user_data)
+ide_configuration_manager_provider_removed (PeasExtensionSet *set,
+                                            PeasPluginInfo   *plugin_info,
+                                            PeasExtension    *exten,
+                                            gpointer          user_data)
 {
   IdeConfigurationManager *self = user_data;
   IdeConfigurationProvider *provider = (IdeConfigurationProvider *)exten;
+  g_autoptr(IdeConfigurationProvider) hold = NULL;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (PEAS_IS_EXTENSION_SET (set));
   g_assert (plugin_info != NULL);
   g_assert (IDE_IS_CONFIGURATION_PROVIDER (provider));
 
-  ide_configuration_provider_unload (provider, self);
+  hold = g_object_ref (provider);
+
+  ide_configuration_provider_unload (provider);
+
+  provider_disconnect (self, provider);
+
+  for (guint i = self->configs->len; i > 0; i--)
+    {
+      const ConfigInfo *info = &g_array_index (self->configs, ConfigInfo, i - 1);
+
+      if (info->provider == provider)
+        {
+          g_warning ("%s failed to remove configuration \"%s\"",
+                     G_OBJECT_TYPE_NAME (provider),
+                     ide_configuration_get_id (info->config));
+          g_array_remove_index (self->configs, i);
+        }
+    }
 }
 
 static void
@@ -483,9 +635,11 @@ ide_configuration_manager_init_load_cb (GObject      *object,
   g_autoptr(GError) error = NULL;
   g_autoptr(GTask) task = user_data;
   GPtrArray *providers;
+  IdeContext *context;
 
   IDE_ENTRY;
 
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_CONFIGURATION_PROVIDER (provider));
   g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (G_IS_TASK (task));
@@ -493,18 +647,24 @@ ide_configuration_manager_init_load_cb (GObject      *object,
   self = g_task_get_source_object (task);
   g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
 
+  context = ide_object_get_context (IDE_OBJECT (self));
+  g_assert (IDE_IS_CONTEXT (context));
+
   if (!ide_configuration_provider_load_finish (provider, result, &error))
-    g_warning ("%s failed to initialize: %s",
-               G_OBJECT_TYPE_NAME (provider), error->message);
+    {
+      g_print ("%s\n", G_OBJECT_TYPE_NAME (provider));
+      g_assert (error != NULL);
+      ide_context_warning (context,
+                           "Failed to initialize config provider: %s: %s",
+                           G_OBJECT_TYPE_NAME (provider), error->message);
+    }
 
   providers = g_task_get_task_data (task);
   g_assert (providers != NULL);
   g_assert (providers->len > 0);
 
-  g_ptr_array_remove (providers, provider);
-
-  if (self->configurations->len == 0)
-    ide_configuration_manager_add_default (self);
+  if (!g_ptr_array_remove (providers, provider))
+    g_critical ("Failed to locate provider in active set");
 
   if (providers->len == 0)
     g_task_return_boolean (task, TRUE);
@@ -520,8 +680,8 @@ ide_configuration_manager_init_async (GAsyncInitable      *initable,
                                       gpointer             user_data)
 {
   IdeConfigurationManager *self = (IdeConfigurationManager *)initable;
-  g_autoptr(GTask) task = NULL;
   g_autoptr(GPtrArray) providers = NULL;
+  g_autoptr(GTask) task = NULL;
   IdeContext *context;
 
   g_assert (G_IS_ASYNC_INITABLE (self));
@@ -534,44 +694,43 @@ ide_configuration_manager_init_async (GAsyncInitable      *initable,
   context = ide_object_get_context (IDE_OBJECT (self));
   g_assert (IDE_IS_CONTEXT (context));
 
-  self->extensions = peas_extension_set_new (peas_engine_get_default (),
-                                             IDE_TYPE_CONFIGURATION_PROVIDER,
-                                             NULL);
+  self->providers = peas_extension_set_new (peas_engine_get_default (),
+                                            IDE_TYPE_CONFIGURATION_PROVIDER,
+                                            "context", context,
+                                            NULL);
 
-  g_signal_connect (self->extensions,
+  g_signal_connect (self->providers,
                     "extension-added",
-                    G_CALLBACK (ide_configuration_manager_extension_added),
+                    G_CALLBACK (ide_configuration_manager_provider_added),
                     self);
 
-  g_signal_connect (self->extensions,
+  g_signal_connect (self->providers,
                     "extension-removed",
-                    G_CALLBACK (ide_configuration_manager_extension_removed),
+                    G_CALLBACK (ide_configuration_manager_provider_removed),
                     self);
 
   providers = g_ptr_array_new_with_free_func (g_object_unref);
-  peas_extension_set_foreach (self->extensions,
+  peas_extension_set_foreach (self->providers,
                               ide_configuration_manager_collect_providers,
                               providers);
-  g_task_set_task_data (task,
-                        g_ptr_array_ref (providers),
-                        (GDestroyNotify)g_ptr_array_unref);
+  g_task_set_task_data (task, g_ptr_array_ref (providers), (GDestroyNotify)g_ptr_array_unref);
 
   for (guint i = 0; i < providers->len; i++)
     {
       IdeConfigurationProvider *provider = g_ptr_array_index (providers, i);
 
+      g_assert (IDE_IS_CONFIGURATION_PROVIDER (provider));
+
+      provider_connect (self, provider);
+
       ide_configuration_provider_load_async (provider,
-                                             self,
                                              cancellable,
                                              ide_configuration_manager_init_load_cb,
                                              g_object_ref (task));
     }
 
   if (providers->len == 0)
-    {
-      ide_configuration_manager_add_default (self);
-      g_task_return_boolean (task, TRUE);
-    }
+    g_task_return_boolean (task, TRUE);
 }
 
 static gboolean
@@ -579,6 +738,7 @@ ide_configuration_manager_init_finish (GAsyncInitable  *initable,
                                        GAsyncResult    *result,
                                        GError         **error)
 {
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_CONFIGURATION_MANAGER (initable));
   g_assert (G_IS_TASK (result));
 
@@ -596,6 +756,7 @@ void
 ide_configuration_manager_set_current (IdeConfigurationManager *self,
                                        IdeConfiguration        *current)
 {
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
   g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (self));
   g_return_if_fail (!current || IDE_IS_CONFIGURATION (current));
 
@@ -643,93 +804,78 @@ IdeConfiguration *
 ide_configuration_manager_get_current (IdeConfigurationManager *self)
 {
   g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), NULL);
+  g_return_val_if_fail (self->current != NULL || self->configs->len > 0, NULL);
 
-  if ((self->current == NULL) && (self->configurations->len > 0))
-    return g_ptr_array_index (self->configurations, 0);
+  if (self->current != NULL)
+    return self->current;
 
-  return self->current;
-}
+  if (self->configs->len > 0)
+    {
+      const ConfigInfo *info = &g_array_index (self->configs, ConfigInfo, 0);
 
-static void
-ide_configuration_manager_changed (IdeConfigurationManager *self,
-                                   IdeConfiguration        *configuration)
-{
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
-  g_assert (IDE_IS_CONFIGURATION (configuration));
+      g_assert (IDE_IS_CONFIGURATION_PROVIDER (info->provider));
+      g_assert (IDE_IS_CONFIGURATION (info->config));
 
-  g_signal_emit (self, signals [INVALIDATE], 0);
+      return info->config;
+    }
+
+  g_critical ("Failed to locate activate configuration. This should not happen.");
+
+  return NULL;
 }
 
 void
-ide_configuration_manager_add (IdeConfigurationManager *self,
-                               IdeConfiguration        *configuration)
+ide_configuration_manager_duplicate (IdeConfigurationManager *self,
+                                     IdeConfiguration        *config)
 {
-  const gchar *config_id;
-  guint position;
-
   g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (self));
-  g_return_if_fail (IDE_IS_CONFIGURATION (configuration));
+  g_return_if_fail (IDE_IS_CONFIGURATION (config));
 
-  for (guint i = 0; i < self->configurations->len; i++)
+  for (guint i = 0; i < self->configs->len; i++)
     {
-      IdeConfiguration *ele = g_ptr_array_index (self->configurations, i);
-
-      /* Do nothing if we already have this. Unlikely to happen but might
-       * be if we got into a weird race with registering default configurations
-       * and receiving a default from a provider.
-       */
-      if (configuration == ele)
-        return;
-    }
+      const ConfigInfo *info = &g_array_index (self->configs, ConfigInfo, i);
 
-  config_id = ide_configuration_get_id (configuration);
+      g_assert (IDE_IS_CONFIGURATION_PROVIDER (info->provider));
+      g_assert (IDE_IS_CONFIGURATION (info->config));
 
-  /* Allow the default config to be overridden by one from a provider */
-  if (dzl_str_equal0 ("default", config_id))
-    {
-      IdeConfiguration *def = ide_configuration_manager_get_configuration (self, "default");
-
-      g_assert (def != configuration);
+      if (info->config == config)
+        {
+          g_autoptr(IdeConfigurationProvider) provider = g_object_ref (info->provider);
 
-      if (def != NULL)
-        g_ptr_array_remove_fast (self->configurations, def);
+          info = NULL; /* info becomes invalid */
+          ide_configuration_provider_duplicate (provider, config);
+          ide_configuration_provider_save_async (provider, NULL, NULL, NULL);
+          break;
+        }
     }
-
-  position = self->configurations->len;
-  g_ptr_array_add (self->configurations, g_object_ref (configuration));
-  g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
-
-  g_signal_connect_object (configuration,
-                           "changed",
-                           G_CALLBACK (ide_configuration_manager_changed),
-                           self,
-                           G_CONNECT_SWAPPED);
 }
 
 void
-ide_configuration_manager_remove (IdeConfigurationManager *self,
-                                  IdeConfiguration        *configuration)
+ide_configuration_manager_delete (IdeConfigurationManager *self,
+                                  IdeConfiguration        *config)
 {
-  guint i;
+  g_autoptr(IdeConfiguration) hold = NULL;
 
   g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (self));
-  g_return_if_fail (IDE_IS_CONFIGURATION (configuration));
+  g_return_if_fail (IDE_IS_CONFIGURATION (config));
+
+  hold = g_object_ref (config);
 
-  for (i = 0; i < self->configurations->len; i++)
+  for (guint i = 0; i < self->configs->len; i++)
     {
-      IdeConfiguration *item = g_ptr_array_index (self->configurations, i);
+      const ConfigInfo *info = &g_array_index (self->configs, ConfigInfo, i);
+      g_autoptr(IdeConfigurationProvider) provider = NULL;
 
-      if (item == configuration)
+      g_assert (IDE_IS_CONFIGURATION_PROVIDER (info->provider));
+      g_assert (IDE_IS_CONFIGURATION (info->config));
+
+      provider = g_object_ref (info->provider);
+
+      if (info->config == config)
         {
-          g_signal_handlers_disconnect_by_func (configuration,
-                                                G_CALLBACK (ide_configuration_manager_changed),
-                                                self);
-          g_ptr_array_remove_index (self->configurations, i);
-          g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0);
-          if (self->configurations->len == 0)
-            ide_configuration_manager_add_default (self);
-          if (self->current == configuration)
-            ide_configuration_manager_set_current (self, NULL);
+          info = NULL; /* info becomes invalid */
+          ide_configuration_provider_delete (provider, config);
+          ide_configuration_provider_save_async (provider, NULL, NULL, NULL);
           break;
         }
     }
diff --git a/src/libide/config/ide-configuration-manager.h b/src/libide/config/ide-configuration-manager.h
index b7997d32b..f6d0ef43d 100644
--- a/src/libide/config/ide-configuration-manager.h
+++ b/src/libide/config/ide-configuration-manager.h
@@ -39,12 +39,12 @@ void              ide_configuration_manager_set_current       (IdeConfigurationM
 IDE_AVAILABLE_IN_ALL
 IdeConfiguration *ide_configuration_manager_get_configuration (IdeConfigurationManager  *self,
                                                                const gchar              *id);
-IDE_AVAILABLE_IN_ALL
-void              ide_configuration_manager_add               (IdeConfigurationManager  *self,
-                                                               IdeConfiguration         *configuration);
-IDE_AVAILABLE_IN_ALL
-void              ide_configuration_manager_remove            (IdeConfigurationManager  *self,
-                                                               IdeConfiguration         *configuration);
+IDE_AVAILABLE_IN_3_28
+void              ide_configuration_manager_duplicate         (IdeConfigurationManager  *self,
+                                                               IdeConfiguration         *config);
+IDE_AVAILABLE_IN_3_28
+void              ide_configuration_manager_delete            (IdeConfigurationManager  *self,
+                                                               IdeConfiguration         *config);
 IDE_AVAILABLE_IN_ALL
 void              ide_configuration_manager_save_async        (IdeConfigurationManager  *self,
                                                                GCancellable             *cancellable,
diff --git a/src/libide/config/ide-configuration-provider.c b/src/libide/config/ide-configuration-provider.c
index ed70947a1..8383f2f00 100644
--- a/src/libide/config/ide-configuration-provider.c
+++ b/src/libide/config/ide-configuration-provider.c
@@ -19,21 +19,28 @@
 #define G_LOG_DOMAIN "ide-configuration-provider"
 
 #include "application/ide-application.h"
+#include "config/ide-configuration.h"
 #include "config/ide-configuration-manager.h"
 #include "config/ide-configuration-provider.h"
 
 G_DEFINE_INTERFACE (IdeConfigurationProvider, ide_configuration_provider, IDE_TYPE_OBJECT)
 
+enum {
+  ADDED,
+  REMOVED,
+  N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
 static void
 ide_configuration_provider_real_load_async (IdeConfigurationProvider *self,
-                                            IdeConfigurationManager  *manager,
                                             GCancellable             *cancellable,
                                             GAsyncReadyCallback       callback,
                                             gpointer                  user_data)
 {
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   g_task_report_new_error (self, callback, user_data,
@@ -58,12 +65,21 @@ ide_configuration_provider_real_load_finish (IdeConfigurationProvider  *self,
 }
 
 static void
-ide_configuration_provider_real_unload (IdeConfigurationProvider *self,
-                                        IdeConfigurationManager  *manager)
+ide_configuration_provider_real_duplicate (IdeConfigurationProvider *self,
+                                           IdeConfiguration         *config)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_CONFIGURATION_PROVIDER (self));
+  g_assert (IDE_IS_CONFIGURATION (config));
+
+}
+
+static void
+ide_configuration_provider_real_unload (IdeConfigurationProvider *self)
 {
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
+
 }
 
 void
@@ -102,26 +118,96 @@ ide_configuration_provider_default_init (IdeConfigurationProviderInterface *ifac
 {
   iface->load_async = ide_configuration_provider_real_load_async;
   iface->load_finish = ide_configuration_provider_real_load_finish;
+  iface->duplicate = ide_configuration_provider_real_duplicate;
   iface->unload = ide_configuration_provider_real_unload;
   iface->save_async = ide_configuration_provider_real_save_async;
   iface->save_finish = ide_configuration_provider_real_save_finish;
+
+  /**
+   * IdeConfigurationProvider:added:
+   * @self: an #IdeConfigurationProvider
+   * @config: an #IdeConfiguration
+   *
+   * The "added" signal is emitted when a configuration
+   * has been added to a configuration provider.
+   *
+   * Since: 3.28
+   */
+  signals [ADDED] =
+    g_signal_new ("added",
+                  G_TYPE_FROM_INTERFACE (iface),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeConfigurationProviderInterface, added),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__OBJECT,
+                  G_TYPE_NONE, 1, IDE_TYPE_CONFIGURATION);
+  g_signal_set_va_marshaller (signals [ADDED],
+                              G_TYPE_FROM_INTERFACE (iface),
+                              g_cclosure_marshal_VOID__OBJECTv);
+
+  /**
+   * IdeConfigurationProvider:removed:
+   * @self: an #IdeConfigurationProvider
+   * @config: an #IdeConfiguration
+   *
+   * The "removed" signal is emitted when a configuration
+   * has been removed from a configuration provider.
+   *
+   * Since: 3.28
+   */
+  signals [REMOVED] =
+    g_signal_new ("removed",
+                  G_TYPE_FROM_INTERFACE (iface),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (IdeConfigurationProviderInterface, removed),
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__OBJECT,
+                  G_TYPE_NONE, 1, IDE_TYPE_CONFIGURATION);
+  g_signal_set_va_marshaller (signals [REMOVED],
+                              G_TYPE_FROM_INTERFACE (iface),
+                              g_cclosure_marshal_VOID__OBJECTv);
+
 }
 
+/**
+ * ide_configuration_provider_load_async:
+ * @self: a #IdeConfigurationProvider
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: a callback to execute upon completion
+ * @user_data: closure data for @callback
+ *
+ * This function is called to initialize the configuration provider after
+ * the plugin instance has been created. The provider should locate any
+ * build configurations within the project and call
+ * ide_configuration_provider_emit_added() before completing the
+ * asynchronous function so that the configuration manager may be made
+ * aware of the configurations.
+ *
+ * Since: 3.28
+ */
 void
 ide_configuration_provider_load_async (IdeConfigurationProvider *self,
-                                       IdeConfigurationManager  *manager,
                                        GCancellable             *cancellable,
                                        GAsyncReadyCallback       callback,
                                        gpointer                  user_data)
 {
   g_return_if_fail (IDE_IS_MAIN_THREAD ());
   g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
-  g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (manager));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->load_async (self, manager, cancellable, callback, user_data);
+  IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->load_async (self, cancellable, callback, user_data);
 }
 
+/**
+ * ide_configuration_provider_load_finish:
+ * @self: a #IdeConfigurationProvider
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to ide_configuration_provider_load_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
 gboolean
 ide_configuration_provider_load_finish (IdeConfigurationProvider  *self,
                                         GAsyncResult              *result,
@@ -134,17 +220,39 @@ ide_configuration_provider_load_finish (IdeConfigurationProvider  *self,
   return IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->load_finish (self, result, error);
 }
 
+/**
+ * ide_configuration_provider_unload:
+ * @self: a #IdeConfigurationProvider
+ *
+ * Requests that the configuration provider unload any state. This is called
+ * shortly before the configuration provider is finalized.
+ *
+ * Implementations of #IdeConfigurationProvider should emit removed
+ * for every configuration they have registered so that the
+ * #IdeConfigurationManager has correct information.
+ */
 void
-ide_configuration_provider_unload (IdeConfigurationProvider *self,
-                                   IdeConfigurationManager  *manager)
+ide_configuration_provider_unload (IdeConfigurationProvider *self)
 {
   g_return_if_fail (IDE_IS_MAIN_THREAD ());
   g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
-  g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (manager));
 
-  IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->unload (self, manager);
+  IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->unload (self);
 }
 
+/**
+ * ide_configuration_provider_save_async:
+ * @self: a #IdeConfigurationProvider
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: a callback to execute upon completion
+ * @user_data: closure data for @callback
+ *
+ * This function is called to request that the configuration provider
+ * persist any changed configurations back to disk.
+ *
+ * This function will be called before unloading the configuration provider
+ * so that it has a chance to persist any outstanding changes.
+ */
 void
 ide_configuration_provider_save_async (IdeConfigurationProvider *self,
                                        GCancellable             *cancellable,
@@ -158,6 +266,16 @@ ide_configuration_provider_save_async (IdeConfigurationProvider *self,
   IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->save_async (self, cancellable, callback, user_data);
 }
 
+/**
+ * ide_configuration_provider_save_finish:
+ * @self: a #IdeConfigurationProvider
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to ide_configuration_provider_save_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
 gboolean
 ide_configuration_provider_save_finish (IdeConfigurationProvider  *self,
                                         GAsyncResult              *result,
@@ -169,3 +287,100 @@ ide_configuration_provider_save_finish (IdeConfigurationProvider  *self,
 
   return IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->save_finish (self, result, error);
 }
+
+/**
+ * ide_configuration_provider_emit_added:
+ * @self: an #IdeConfigurationProvider
+ * @config: an #IdeConfiguration
+ *
+ * #IdeConfigurationProvider implementations should call this function with
+ * a @config when it has discovered a new configuration.
+ *
+ * Since: 3.28
+ */
+void
+ide_configuration_provider_emit_added (IdeConfigurationProvider *self,
+                                       IdeConfiguration         *config)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
+  g_return_if_fail (IDE_IS_CONFIGURATION (config));
+
+  g_signal_emit (self, signals [ADDED], 0, config);
+}
+
+/**
+ * ide_configuration_provider_emit_removed:
+ * @self: an #IdeConfigurationProvider
+ * @config: an #IdeConfiguration
+ *
+ * #IdeConfigurationProvider implementations should call this function with
+ * a @config when it has discovered it was removed.
+ *
+ * Since: 3.28
+ */
+void
+ide_configuration_provider_emit_removed (IdeConfigurationProvider *self,
+                                         IdeConfiguration         *config)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
+  g_return_if_fail (IDE_IS_CONFIGURATION (config));
+
+  g_signal_emit (self, signals [REMOVED], 0, config);
+}
+
+/**
+ * ide_configuration_provider_delete:
+ * @self: a #IdeConfigurationProvider
+ * @config: an #IdeConfiguration owned by the provider
+ *
+ * Requests that the configuration provider delete the configuration.
+ *
+ * ide_configuration_provider_save_async() will be called by the
+ * #IdeConfigurationManager after calling this function.
+ *
+ * Since: 3.28
+ */
+void
+ide_configuration_provider_delete (IdeConfigurationProvider *self,
+                                   IdeConfiguration         *config)
+{
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
+  g_return_if_fail (IDE_IS_CONFIGURATION (config));
+
+  if (IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->delete)
+    IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->delete (self, config);
+  else
+    g_warning ("Cannot delete configuration %s",
+               ide_configuration_get_id (config));
+}
+
+/**
+ * ide_configuration_provider_duplicate:
+ * @self: an #IdeConfigurationProvider
+ * @config: an #IdeConfiguration
+ *
+ * Requests that the configuration provider duplicate the configuration.
+ *
+ * This is useful when the user wants to experiment with alternate settings
+ * without breaking a previous configuration.
+ *
+ * The configuration provider does not need to persist the configuration
+ * in this function, ide_configuration_provider_save_async() will be called
+ * afterwards to persist configurations to disk.
+ *
+ * It is expected that the #IdeConfigurationProvider will emit
+ * #IdeConfigurationProvider::added with the new configuration.
+ *
+ * Since: 3.28
+ */
+void
+ide_configuration_provider_duplicate (IdeConfigurationProvider *self,
+                                      IdeConfiguration         *config)
+{
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_CONFIGURATION_PROVIDER (self));
+  g_return_if_fail (IDE_IS_CONFIGURATION (config));
+
+  IDE_CONFIGURATION_PROVIDER_GET_IFACE (self)->duplicate (self, config);
+}
diff --git a/src/libide/config/ide-configuration-provider.h b/src/libide/config/ide-configuration-provider.h
index b1e7dea12..0b0509e74 100644
--- a/src/libide/config/ide-configuration-provider.h
+++ b/src/libide/config/ide-configuration-provider.h
@@ -1,6 +1,7 @@
 /* ide-configuration-provider.h
  *
  * Copyright © 2016 Matthew Leeds <mleeds redhat com>
+ * Copyright © 2018 Christian Hergert <chergert redhat com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -20,9 +21,8 @@
 
 #include <gio/gio.h>
 
-#include "ide-version-macros.h"
-
 #include "ide-types.h"
+#include "ide-version-macros.h"
 
 G_BEGIN_DECLS
 
@@ -32,48 +32,63 @@ G_DECLARE_INTERFACE (IdeConfigurationProvider, ide_configuration_provider, IDE,
 
 struct _IdeConfigurationProviderInterface
 {
-  GTypeInterface parent;
+  GTypeInterface parent_iface;
 
-  void     (*load_async)  (IdeConfigurationProvider  *self,
-                           IdeConfigurationManager   *manager,
-                           GCancellable              *cancellable,
-                           GAsyncReadyCallback        callback,
-                           gpointer                   user_data);
-  gboolean (*load_finish) (IdeConfigurationProvider  *self,
-                           GAsyncResult              *result,
-                           GError                   **error);
-  void     (*unload)      (IdeConfigurationProvider  *self,
-                           IdeConfigurationManager   *manager);
-  void     (*save_async)  (IdeConfigurationProvider  *self,
-                           GCancellable              *cancellable,
-                           GAsyncReadyCallback        callback,
-                           gpointer                   user_data);
-  gboolean (*save_finish) (IdeConfigurationProvider  *self,
-                           GAsyncResult              *result,
-                           GError                   **error);
+  void     (*added)          (IdeConfigurationProvider  *self,
+                              IdeConfiguration          *config);
+  void     (*removed)        (IdeConfigurationProvider  *self,
+                              IdeConfiguration          *config);
+  void     (*load_async)     (IdeConfigurationProvider  *self,
+                              GCancellable              *cancellable,
+                              GAsyncReadyCallback        callback,
+                              gpointer                   user_data);
+  gboolean (*load_finish)    (IdeConfigurationProvider  *self,
+                              GAsyncResult              *result,
+                              GError                   **error);
+  void     (*save_async)     (IdeConfigurationProvider  *self,
+                              GCancellable              *cancellable,
+                              GAsyncReadyCallback        callback,
+                              gpointer                   user_data);
+  gboolean (*save_finish)    (IdeConfigurationProvider  *self,
+                              GAsyncResult              *result,
+                              GError                   **error);
+  void     (*delete)         (IdeConfigurationProvider  *self,
+                              IdeConfiguration          *config);
+  void     (*duplicate)      (IdeConfigurationProvider  *self,
+                              IdeConfiguration          *config);
+  void     (*unload)         (IdeConfigurationProvider  *self);
 };
 
-IDE_AVAILABLE_IN_ALL
-void     ide_configuration_provider_load_async  (IdeConfigurationProvider  *self,
-                                                 IdeConfigurationManager   *manager,
-                                                 GCancellable              *cancellable,
-                                                 GAsyncReadyCallback        callback,
-                                                 gpointer                   user_data);
-IDE_AVAILABLE_IN_ALL
-gboolean ide_configuration_provider_load_finish (IdeConfigurationProvider  *self,
-                                                 GAsyncResult              *result,
-                                                 GError                   **error);
-IDE_AVAILABLE_IN_ALL
-void     ide_configuration_provider_unload      (IdeConfigurationProvider  *self,
-                                                 IdeConfigurationManager   *manager);
-IDE_AVAILABLE_IN_ALL
-void     ide_configuration_provider_save_async  (IdeConfigurationProvider  *self,
-                                                 GCancellable              *cancellable,
-                                                 GAsyncReadyCallback        callback,
-                                                 gpointer                   user_data);
-IDE_AVAILABLE_IN_ALL
-gboolean ide_configuration_provider_save_finish (IdeConfigurationProvider  *self,
-                                                 GAsyncResult              *result,
-                                                 GError                   **error);
+IDE_AVAILABLE_IN_3_28
+void     ide_configuration_provider_emit_added    (IdeConfigurationProvider  *self,
+                                                   IdeConfiguration          *config);
+IDE_AVAILABLE_IN_3_28
+void     ide_configuration_provider_emit_removed  (IdeConfigurationProvider  *self,
+                                                   IdeConfiguration          *config);
+IDE_AVAILABLE_IN_3_28
+void     ide_configuration_provider_load_async    (IdeConfigurationProvider  *self,
+                                                   GCancellable              *cancellable,
+                                                   GAsyncReadyCallback        callback,
+                                                   gpointer                   user_data);
+IDE_AVAILABLE_IN_3_28
+gboolean ide_configuration_provider_load_finish   (IdeConfigurationProvider  *self,
+                                                   GAsyncResult              *result,
+                                                   GError                   **error);
+IDE_AVAILABLE_IN_3_28
+void     ide_configuration_provider_save_async    (IdeConfigurationProvider  *self,
+                                                   GCancellable              *cancellable,
+                                                   GAsyncReadyCallback        callback,
+                                                   gpointer                   user_data);
+IDE_AVAILABLE_IN_3_28
+gboolean ide_configuration_provider_save_finish   (IdeConfigurationProvider  *self,
+                                                   GAsyncResult              *result,
+                                                   GError                   **error);
+IDE_AVAILABLE_IN_3_28
+void     ide_configuration_provider_delete        (IdeConfigurationProvider  *self,
+                                                   IdeConfiguration          *config);
+void     ide_configuration_provider_duplicate     (IdeConfigurationProvider  *self,
+                                                   IdeConfiguration          *config);
+IDE_AVAILABLE_IN_3_28
+void     ide_configuration_provider_unload        (IdeConfigurationProvider  *self);
 
 G_END_DECLS
diff --git a/src/libide/config/ide-configuration.c b/src/libide/config/ide-configuration.c
index ccb144475..950eba7df 100644
--- a/src/libide/config/ide-configuration.c
+++ b/src/libide/config/ide-configuration.c
@@ -56,7 +56,6 @@ typedef struct
 
   guint           dirty : 1;
   guint           debug : 1;
-  guint           is_snapshot : 1;
 
   /*
    * These are used to determine if we can make progress building
@@ -69,7 +68,7 @@ typedef struct
   IdeBuildLocality locality : 3;
 } IdeConfigurationPrivate;
 
-G_DEFINE_TYPE_WITH_PRIVATE (IdeConfiguration, ide_configuration, IDE_TYPE_OBJECT)
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (IdeConfiguration, ide_configuration, IDE_TYPE_OBJECT)
 
 enum {
   PROP_0,
@@ -126,20 +125,6 @@ _value_new (GType type)
   return value;
 }
 
-static GValue *
-_value_copy (const GValue *value)
-{
-  GValue *dst;
-
-  g_assert (value != NULL);
-
-  dst = g_slice_new0 (GValue);
-  g_value_init (dst, G_VALUE_TYPE (value));
-  g_value_copy (value, dst);
-
-  return dst;
-}
-
 static void
 ide_configuration_emit_changed (IdeConfiguration *self)
 {
@@ -385,6 +370,10 @@ ide_configuration_get_property (GObject    *object,
       g_value_set_object (value, ide_configuration_get_device (self));
       break;
 
+    case PROP_DEVICE_ID:
+      g_value_set_string (value, ide_configuration_get_device_id (self));
+      break;
+
     case PROP_DIRTY:
       g_value_set_boolean (value, ide_configuration_get_dirty (self));
       break;
@@ -710,25 +699,6 @@ ide_configuration_init (IdeConfiguration *self)
                            G_CONNECT_SWAPPED);
 }
 
-IdeConfiguration *
-ide_configuration_new (IdeContext  *context,
-                       const gchar *id,
-                       const gchar *device_id,
-                       const gchar *runtime_id)
-{
-  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
-  g_return_val_if_fail (id != NULL, NULL);
-  g_return_val_if_fail (device_id != NULL, NULL);
-  g_return_val_if_fail (runtime_id != NULL, NULL);
-
-  return g_object_new (IDE_TYPE_CONFIGURATION,
-                       "context", context,
-                       "device-id", device_id,
-                       "id", id,
-                       "runtime-id", runtime_id,
-                       NULL);
-}
-
 const gchar *
 ide_configuration_get_device_id (IdeConfiguration *self)
 {
@@ -748,6 +718,9 @@ ide_configuration_set_device_id (IdeConfiguration *self,
   g_return_if_fail (IDE_IS_CONFIGURATION (self));
   g_return_if_fail (device_id != NULL);
 
+  if (device_id == NULL)
+    device_id = "local";
+
   if (g_strcmp0 (device_id, priv->device_id) != 0)
     {
       IdeContext *context;
@@ -819,11 +792,13 @@ ide_configuration_set_app_id (IdeConfiguration *self,
   IdeConfigurationPrivate *priv = ide_configuration_get_instance_private (self);
 
   g_return_if_fail (IDE_IS_CONFIGURATION (self));
-  g_return_if_fail (app_id != NULL);
-
-  g_free (priv->app_id);
 
-  priv->app_id = g_strdup (app_id);
+  if (priv->app_id != app_id)
+    {
+      g_free (priv->app_id);
+      priv->app_id = g_strdup (app_id);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_APP_ID]);
+    }
 }
 
 const gchar *
@@ -843,7 +818,9 @@ ide_configuration_set_runtime_id (IdeConfiguration *self,
   IdeConfigurationPrivate *priv = ide_configuration_get_instance_private (self);
 
   g_return_if_fail (IDE_IS_CONFIGURATION (self));
-  g_return_if_fail (runtime_id != NULL);
+
+  if (runtime_id == NULL)
+    runtime_id = "host";
 
   if (g_strcmp0 (runtime_id, priv->runtime_id) != 0)
     {
@@ -1074,37 +1051,6 @@ ide_configuration_get_dirty (IdeConfiguration *self)
   return priv->dirty;
 }
 
-static gboolean
-propagate_dirty_bit (gpointer user_data)
-{
-  g_autofree gpointer *data = user_data;
-  g_autoptr(IdeContext) context = NULL;
-  g_autofree gchar *id = NULL;
-  IdeConfigurationManager *config_manager;
-  IdeConfiguration *config;
-  IdeConfigurationPrivate *config_priv;
-  guint sequence;
-
-  g_assert (data != NULL);
-  g_assert (IDE_IS_CONTEXT (data[0]));
-
-  context = data[0];
-  id = data[1];
-  sequence = GPOINTER_TO_UINT (data[2]);
-
-  config_manager = ide_context_get_configuration_manager (context);
-  config = ide_configuration_manager_get_configuration (config_manager, id);
-  config_priv = ide_configuration_get_instance_private (config);
-
-  if (config != NULL)
-    {
-      if (sequence == config_priv->sequence)
-        ide_configuration_set_dirty (config, FALSE);
-    }
-
-  return G_SOURCE_REMOVE;
-}
-
 void
 ide_configuration_set_dirty (IdeConfiguration *self,
                              gboolean          dirty)
@@ -1134,21 +1080,6 @@ ide_configuration_set_dirty (IdeConfiguration *self,
       IDE_TRACE_MSG ("configuration set dirty with sequence %u", priv->sequence);
       ide_configuration_emit_changed (self);
     }
-  else if (priv->is_snapshot)
-    {
-      gpointer *data;
-
-      /*
-       * If we are marking this not-dirty, and it is a snapshot (which means it
-       * was copied for a build process), then we want to propagate the dirty
-       * bit back to the primary configuration.
-       */
-      data = g_new0 (gpointer, 3);
-      data[0] = g_object_ref (ide_object_get_context (IDE_OBJECT (self)));
-      data[1] = g_strdup (priv->id);
-      data[2] = GUINT_TO_POINTER (priv->sequence);
-      g_timeout_add (0, propagate_dirty_bit, data);
-    }
 
   IDE_EXIT;
 }
@@ -1264,83 +1195,6 @@ ide_configuration_set_post_install_commands (IdeConfiguration    *self,
     }
 }
 
-/**
- * ide_configuration_snapshot:
- *
- * Makes a snapshot of the configuration that can be used by build processes
- * to build the project without synchronizing with other threads.
- *
- * Returns: (transfer full): A newly allocated #IdeConfiguration.
- */
-IdeConfiguration *
-ide_configuration_snapshot (IdeConfiguration *self)
-{
-  IdeConfigurationPrivate *priv = ide_configuration_get_instance_private (self);
-  IdeConfigurationPrivate *copy_priv;
-  IdeConfiguration *copy;
-  IdeContext *context;
-  const gchar *key;
-  const GValue *value;
-  GHashTableIter iter;
-
-  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
-
-  context = ide_object_get_context (IDE_OBJECT (self));
-
-  copy = g_object_new (IDE_TYPE_CONFIGURATION,
-                       "config-opts", priv->config_opts,
-                       "context", context,
-                       "device-id", priv->device_id,
-                       "display-name", priv->display_name,
-                       "id", priv->id,
-                       "parallelism", priv->parallelism,
-                       "prefix", priv->prefix,
-                       "runtime-id", priv->runtime_id,
-                       NULL);
-
-  copy_priv = ide_configuration_get_instance_private (copy);
-  copy_priv->environment = ide_environment_copy (priv->environment);
-
-  g_hash_table_iter_init (&iter, priv->internal);
-  while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
-    g_hash_table_insert (copy_priv->internal, g_strdup (key), _value_copy (value));
-
-  copy_priv->dirty = priv->dirty;
-  copy_priv->is_snapshot = TRUE;
-  copy_priv->sequence = priv->sequence;
-
-  return copy;
-}
-
-/**
- * ide_configuration_duplicate:
- * @self: An #IdeConfiguration
- *
- * Copies the configuration into a new configuration.
- *
- * Returns: (transfer full): An #IdeConfiguration.
- */
-IdeConfiguration *
-ide_configuration_duplicate (IdeConfiguration *self)
-{
-  IdeConfigurationPrivate *priv = ide_configuration_get_instance_private (self);
-  IdeConfigurationPrivate *copy_priv;
-  static gint next_counter = 2;
-  IdeConfiguration *copy;
-
-  copy = ide_configuration_snapshot (self);
-  copy_priv = ide_configuration_get_instance_private (copy);
-
-  g_free (copy_priv->id);
-  g_free (copy_priv->display_name);
-
-  copy_priv->id = g_strdup_printf ("%s %d", priv->id, next_counter++);
-  copy_priv->display_name = g_strdup_printf ("%s Copy", priv->display_name);
-  copy_priv->is_snapshot = FALSE;
-
-  return copy;
-}
-
 /**
  * ide_configuration_get_sequence:
  * @self: An #IdeConfiguration
diff --git a/src/libide/config/ide-configuration.h b/src/libide/config/ide-configuration.h
index 70844ea3b..62e2fa65f 100644
--- a/src/libide/config/ide-configuration.h
+++ b/src/libide/config/ide-configuration.h
@@ -74,11 +74,6 @@ struct _IdeConfigurationClass
   gpointer _reserved16;
 };
 
-IDE_AVAILABLE_IN_ALL
-IdeConfiguration     *ide_configuration_new                       (IdeContext            *context,
-                                                                   const gchar           *id,
-                                                                   const gchar           *device_id,
-                                                                   const gchar           *runtime_id);
 IDE_AVAILABLE_IN_3_28
 const gchar          *ide_configuration_get_append_path           (IdeConfiguration      *self);
 IDE_AVAILABLE_IN_3_28
@@ -173,8 +168,6 @@ IDE_AVAILABLE_IN_ALL
 void                  ide_configuration_set_environment           (IdeConfiguration      *self,
                                                                    IdeEnvironment        *environment);
 IDE_AVAILABLE_IN_ALL
-IdeConfiguration     *ide_configuration_duplicate                 (IdeConfiguration      *self);
-IDE_AVAILABLE_IN_ALL
 IdeConfiguration     *ide_configuration_snapshot                  (IdeConfiguration      *self);
 IDE_AVAILABLE_IN_ALL
 guint                 ide_configuration_get_sequence              (IdeConfiguration      *self);
diff --git a/src/plugins/flatpak/gbp-flatpak-configuration-provider.c 
b/src/plugins/flatpak/gbp-flatpak-configuration-provider.c
index 06174abfb..b23ea5493 100644
--- a/src/plugins/flatpak/gbp-flatpak-configuration-provider.c
+++ b/src/plugins/flatpak/gbp-flatpak-configuration-provider.c
@@ -25,19 +25,14 @@
 #include "gbp-flatpak-configuration.h"
 #include "gbp-flatpak-configuration-provider.h"
 
-#define WRITEBACK_TIMEOUT_SECS 2
 #define DISCOVERY_MAX_DEPTH 3
 
 struct _GbpFlatpakConfigurationProvider
 {
-  IdeObject                parent_instance;
-
-  IdeConfigurationManager *manager;
-  GPtrArray               *configurations;
-  GPtrArray               *manifest_monitors;
-
-  guint                    writeback_handler;
-  guint                    change_count;
+  IdeObject  parent_instance;
+  GMutex     mutex;
+  GPtrArray *configs;
+  GPtrArray *manifest_monitors;
 };
 
 static void configuration_provider_iface_init (IdeConfigurationProviderInterface *iface);
@@ -64,13 +59,11 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
   g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  self->change_count = 0;
-
-  if (self->configurations == NULL)
-    IDE_EXIT;
+  g_mutex_lock (&self->mutex);
 
-  for (guint i = 0; i < self->configurations->len; i++)
+  for (guint i = 0; i < self->configs->len; i++)
     {
+      GbpFlatpakConfiguration *configuration = g_ptr_array_index (self->configs, i);
       g_autoptr(GFileInputStream) file_stream = NULL;
       g_autoptr(GDataInputStream) data_stream = NULL;
       g_autoptr(GRegex) runtime_regex = NULL;
@@ -108,24 +101,19 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
       guint opts_per_line = 0;
       guint nested_curly_braces;
 
-      GbpFlatpakConfiguration *configuration = (GbpFlatpakConfiguration *)g_ptr_array_index 
(self->configurations, i);
-
-      manifest = gbp_flatpak_configuration_get_manifest (configuration);
-      if (manifest == NULL)
+      if (!(manifest = gbp_flatpak_configuration_get_manifest (configuration)))
         continue;
 
-      primary_module = gbp_flatpak_configuration_get_primary_module (configuration);
-      if (primary_module == NULL)
+      if (!(primary_module = gbp_flatpak_configuration_get_primary_module (configuration)))
         {
           g_warning ("Flatpak manifest configuration has no primary module set");
           continue;
         }
 
-      file_stream = g_file_read (manifest, NULL, &error);
-      if (file_stream == NULL)
+      if (!(file_stream = g_file_read (manifest, NULL, &error)))
         {
           g_task_return_error (task, g_steal_pointer (&error));
-          IDE_EXIT;
+          IDE_GOTO (unlock);
         }
 
       data_stream = g_data_input_stream_new (G_INPUT_STREAM (file_stream));
@@ -138,6 +126,7 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
       primary_module_regex = g_regex_new (primary_module_regex_str, 0, 0, NULL);
 
       new_runtime_id = ide_configuration_get_runtime_id (IDE_CONFIGURATION (configuration));
+
       if (g_str_has_prefix (new_runtime_id, "flatpak:"))
         {
           new_runtime_parts = g_strsplit (new_runtime_id + 8, "/", 3);
@@ -173,13 +162,14 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
       nested_curly_braces = 0;
       for (;;)
         {
-          gchar *line;
+          g_autofree gchar *line = NULL;
 
           line = g_data_input_stream_read_line_utf8 (data_stream, NULL, NULL, &error);
+
           if (error != NULL)
             {
               g_task_return_error (task, g_steal_pointer (&error));
-              IDE_EXIT;
+              IDE_GOTO (unlock);
             }
 
           if (line == NULL)
@@ -276,7 +266,6 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
                     }
 
                   /* Discard the line because it will be replaced with new info */
-                  g_free (line);
                   continue;
                 }
               else
@@ -284,7 +273,6 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
                   if (nested_curly_braces > 0)
                     {
                       nested_curly_braces--;
-                      g_free (line);
                       continue;
                     }
                   else
@@ -439,7 +427,6 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
                               opts_per_line = (opts_per_line > 0) ? opts_per_line : 1;
                             }
 
-                          g_free (line);
                           continue;
                         }
                     }
@@ -447,7 +434,6 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
                     {
                       in_config_opts_array = TRUE;
                       config_opt_indent = g_strsplit (line, "\"", 0)[0];
-                      g_free (line);
                       continue;
                     }
 
@@ -456,7 +442,7 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
                   if (error != NULL)
                     {
                       g_task_return_error (task, g_steal_pointer (&error));
-                      IDE_EXIT;
+                      IDE_GOTO (unlock);
                     }
                   if (g_str_has_prefix (next_line, primary_module_right_curly_brace))
                     right_curly_brace_line = next_line;
@@ -511,7 +497,6 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
                     }
 
                   /* Discard the line that was just replaced with the new config-opts array */
-                  g_free (line);
 
                   /* If we're on the last line of the module, add the curly brace now */
                   if (right_curly_brace_line != NULL)
@@ -555,10 +540,15 @@ gbp_flatpak_configuration_provider_save_worker (GTask        *task,
                                     &error))
         {
           g_task_return_error (task, g_steal_pointer (&error));
-          IDE_EXIT;
+          IDE_GOTO (unlock);
         }
     }
 
+  g_task_return_boolean (task, TRUE);
+
+unlock:
+  g_mutex_unlock (&self->mutex);
+
   IDE_EXIT;
 }
 
@@ -577,11 +567,9 @@ gbp_flatpak_configuration_provider_save_async (IdeConfigurationProvider *provide
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   task = g_task_new (self, cancellable, callback, user_data);
-
-  if (self->change_count == 0)
-    g_task_return_boolean (task, TRUE);
-  else
-    g_task_run_in_thread (task, gbp_flatpak_configuration_provider_save_worker);
+  g_task_set_source_tag (task, gbp_flatpak_configuration_provider_save_async);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+  g_task_run_in_thread (task, gbp_flatpak_configuration_provider_save_worker);
 
   IDE_EXIT;
 }
@@ -599,49 +587,6 @@ gbp_flatpak_configuration_provider_save_finish (IdeConfigurationProvider  *provi
   return g_task_propagate_boolean (G_TASK (result), error);
 }
 
-static gboolean
-gbp_flatpak_configuration_provider_do_writeback (gpointer data)
-{
-  GbpFlatpakConfigurationProvider *self = data;
-
-  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
-
-  self->writeback_handler = 0;
-
-  gbp_flatpak_configuration_provider_save_async (IDE_CONFIGURATION_PROVIDER (self), NULL, NULL, NULL);
-
-  return G_SOURCE_REMOVE;
-}
-
-static void
-gbp_flatpak_configuration_provider_queue_writeback (GbpFlatpakConfigurationProvider *self)
-{
-  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
-
-  IDE_ENTRY;
-
-  if (self->writeback_handler != 0)
-    g_source_remove (self->writeback_handler);
-
-  self->writeback_handler = g_timeout_add_seconds (WRITEBACK_TIMEOUT_SECS,
-                                                   gbp_flatpak_configuration_provider_do_writeback,
-                                                   self);
-
-  IDE_EXIT;
-}
-
-static void
-gbp_flatpak_configuration_provider_config_changed (GbpFlatpakConfigurationProvider *self,
-                                                   IdeConfiguration                *configuration)
-{
-  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION (configuration));
-
-  self->change_count++;
-
-  gbp_flatpak_configuration_provider_queue_writeback (self);
-}
-
 static gboolean
 contains_id (GPtrArray   *ar,
              const gchar *id)
@@ -694,12 +639,12 @@ gbp_flatpak_configuration_provider_manifest_changed (GbpFlatpakConfigurationProv
                                                      GFileMonitorEvent                event,
                                                      GFileMonitor                    *file_monitor)
 {
-  GbpFlatpakConfiguration *relevant_config = NULL;
+  g_autoptr(GbpFlatpakConfiguration) relevant_config = NULL;
   IdeContext *context;
-  GFile *new_config_file;
   g_autofree gchar *filename = NULL;
   g_autofree gchar *path = NULL;
   g_autofree gchar *id = NULL;
+  GFile *new_config_file;
 
   IDE_ENTRY;
 
@@ -707,19 +652,21 @@ gbp_flatpak_configuration_provider_manifest_changed (GbpFlatpakConfigurationProv
   g_assert (G_IS_FILE (file));
   g_assert (G_IS_FILE_MONITOR (file_monitor));
 
-  context = ide_object_get_context (IDE_OBJECT (self->manager));
+  context = ide_object_get_context (IDE_OBJECT (self));
 
-  if (self->configurations != NULL)
+  g_mutex_lock (&self->mutex);
+
+  if (self->configs != NULL)
     {
-      for (guint i = 0; i < self->configurations->len; i++)
+      for (guint i = 0; i < self->configs->len; i++)
         {
-          GbpFlatpakConfiguration *configuration = g_ptr_array_index (self->configurations, i);
+          GbpFlatpakConfiguration *configuration = g_ptr_array_index (self->configs, i);
           GFile *config_manifest;
           config_manifest = gbp_flatpak_configuration_get_manifest (configuration);
           if (g_file_equal (file, config_manifest) ||
               (event == G_FILE_MONITOR_EVENT_RENAMED && g_file_equal (other_file, config_manifest)))
             {
-              relevant_config = configuration;
+              relevant_config = g_object_ref (configuration);
               break;
             }
         }
@@ -728,22 +675,23 @@ gbp_flatpak_configuration_provider_manifest_changed (GbpFlatpakConfigurationProv
   if (relevant_config == NULL &&
       event != G_FILE_MONITOR_EVENT_CREATED &&
       event != G_FILE_MONITOR_EVENT_MOVED_IN)
-    IDE_EXIT;
+    IDE_GOTO (unlock);
 
   new_config_file = file;
   switch (event)
     {
     case G_FILE_MONITOR_EVENT_DELETED:
     case G_FILE_MONITOR_EVENT_MOVED_OUT:
-      ide_configuration_manager_remove (self->manager, IDE_CONFIGURATION (relevant_config));
-      g_ptr_array_remove_fast (self->configurations, relevant_config);
+      ide_configuration_provider_emit_removed (IDE_CONFIGURATION_PROVIDER (self),
+                                               IDE_CONFIGURATION (relevant_config));
+      g_ptr_array_remove_fast (self->configs, relevant_config);
       break;
 
     case G_FILE_MONITOR_EVENT_RENAMED:
       filename = g_file_get_basename (other_file);
       /* The "rename" is just a temporary file created by an editor */
       if (g_str_has_suffix (filename, "~"))
-        IDE_EXIT;
+        IDE_GOTO (unlock);
       else
         g_clear_pointer (&filename, g_free);
       new_config_file = other_file;
@@ -757,7 +705,7 @@ gbp_flatpak_configuration_provider_manifest_changed (GbpFlatpakConfigurationProv
       filename = g_file_get_basename (new_config_file);
       id = get_manifest_id (path, filename);
 
-      if (!contains_id (self->configurations, id))
+      if (!contains_id (self->configs, id))
         {
           g_autoptr(GbpFlatpakConfiguration) new_config = NULL;
 
@@ -767,12 +715,6 @@ gbp_flatpak_configuration_provider_manifest_changed (GbpFlatpakConfigurationProv
               g_autoptr(GFileMonitor) manifest_monitor = NULL;
               g_autoptr(GError) local_error = NULL;
 
-              g_signal_connect_object (new_config,
-                                       "changed",
-                                       G_CALLBACK (gbp_flatpak_configuration_provider_config_changed),
-                                       self,
-                                       G_CONNECT_SWAPPED);
-
               manifest_monitor = g_file_monitor_file (new_config_file, G_FILE_MONITOR_WATCH_MOVES, NULL, 
&local_error);
               if (manifest_monitor == NULL)
                 g_warning ("Error encountered trying to monitor flatpak manifest %s: %s", path, 
local_error->message);
@@ -788,13 +730,14 @@ gbp_flatpak_configuration_provider_manifest_changed (GbpFlatpakConfigurationProv
 
               if (relevant_config != NULL)
                 {
-                  ide_configuration_manager_remove (self->manager, IDE_CONFIGURATION (relevant_config));
-                  g_ptr_array_remove_fast (self->configurations, relevant_config);
+                  ide_configuration_provider_emit_removed (IDE_CONFIGURATION_PROVIDER (self),
+                                                           IDE_CONFIGURATION (relevant_config));
+                  g_ptr_array_remove_fast (self->configs, relevant_config);
                 }
               g_ptr_array_remove_fast (self->manifest_monitors, file_monitor);
-              ide_configuration_manager_add (self->manager, IDE_CONFIGURATION (new_config));
-              ide_configuration_manager_set_current (self->manager, IDE_CONFIGURATION (new_config));
-              g_ptr_array_add (self->configurations, g_steal_pointer (&new_config));
+              g_ptr_array_add (self->configs, g_object_ref (new_config));
+              ide_configuration_provider_emit_added (IDE_CONFIGURATION_PROVIDER (self),
+                                                     IDE_CONFIGURATION (new_config));
             }
         }
       break;
@@ -808,6 +751,9 @@ gbp_flatpak_configuration_provider_manifest_changed (GbpFlatpakConfigurationProv
       break;
     }
 
+unlock:
+  g_mutex_unlock (&self->mutex);
+
   IDE_EXIT;
 }
 
@@ -821,8 +767,8 @@ gbp_flatpak_configuration_provider_find_manifests (GbpFlatpakConfigurationProvid
 {
   g_autoptr(GFileEnumerator) enumerator = NULL;
   g_autoptr(GPtrArray) child_dirs = NULL;
-  GFileInfo *file_info = NULL;
   IdeContext *context;
+  gpointer infoptr;
 
   g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
   g_assert (G_IS_FILE (directory));
@@ -830,19 +776,21 @@ gbp_flatpak_configuration_provider_find_manifests (GbpFlatpakConfigurationProvid
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
   g_assert (depth < DISCOVERY_MAX_DEPTH);
 
-  context = ide_object_get_context (IDE_OBJECT (self->manager));
+  context = ide_object_get_context (IDE_OBJECT (self));
+  g_assert (IDE_IS_CONTEXT (context));
 
   enumerator = g_file_enumerate_children (directory,
                                           G_FILE_ATTRIBUTE_STANDARD_NAME,
                                           G_FILE_QUERY_INFO_NONE,
                                           cancellable,
                                           error);
-  if (!enumerator)
+
+  if (enumerator == NULL)
     return;
 
-  while ((file_info = g_file_enumerator_next_file (enumerator, cancellable, NULL)))
+  while ((infoptr = g_file_enumerator_next_file (enumerator, cancellable, NULL)))
     {
-      GFileType file_type;
+      g_autoptr(GFileInfo) file_info = infoptr;
       g_autofree gchar *filename = NULL;
       g_autofree gchar *path = NULL;
       g_autofree gchar *id = NULL;
@@ -851,6 +799,7 @@ gbp_flatpak_configuration_provider_find_manifests (GbpFlatpakConfigurationProvid
       g_autoptr(GbpFlatpakConfiguration) possible_config = NULL;
       g_autoptr(GFileMonitor) manifest_monitor = NULL;
       g_autoptr(GError) local_error = NULL;
+      GFileType file_type;
 
       file_type = g_file_info_get_file_type (file_info);
       filename = g_strdup (g_file_info_get_name (file_info));
@@ -892,12 +841,6 @@ gbp_flatpak_configuration_provider_find_manifests (GbpFlatpakConfigurationProvid
       if (!gbp_flatpak_configuration_load_from_file (possible_config, file))
         continue;
 
-      g_signal_connect_object (possible_config,
-                               "changed",
-                               G_CALLBACK (gbp_flatpak_configuration_provider_config_changed),
-                               self,
-                               G_CONNECT_SWAPPED);
-
       manifest_monitor = g_file_monitor_file (file, G_FILE_MONITOR_WATCH_MOVES, NULL, &local_error);
       if (manifest_monitor == NULL)
         g_warning ("Error encountered trying to monitor flatpak manifest %s: %s", path, 
local_error->message);
@@ -926,27 +869,28 @@ gbp_flatpak_configuration_provider_find_manifests (GbpFlatpakConfigurationProvid
           gbp_flatpak_configuration_provider_find_manifests (self, file, configs, depth + 1, cancellable, 
error);
         }
     }
-
-  return;
 }
 
 static void
 gbp_flatpak_configuration_provider_load_manifests (GbpFlatpakConfigurationProvider  *self,
-                                                   GPtrArray                        *configurations,
+                                                   GPtrArray                        *configs,
                                                    GCancellable                     *cancellable,
                                                    GError                          **error)
 {
   g_autoptr(GPtrArray) ar = NULL;
   g_autoptr(GFileInfo) file_info = NULL;
+  g_autoptr(GFile) project_dir = NULL;
   IdeContext *context;
   GFile *project_file;
-  g_autoptr(GFile) project_dir = NULL;
 
   g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+  g_assert (configs != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  context = ide_object_get_context (IDE_OBJECT (self->manager));
-  project_file = ide_context_get_project_file (context);
+  context = ide_object_get_context (IDE_OBJECT (self));
+  g_assert (IDE_IS_CONTEXT (context));
 
+  project_file = ide_context_get_project_file (context);
   g_assert (G_IS_FILE (project_file));
 
   file_info = g_file_query_info (project_file,
@@ -954,7 +898,6 @@ gbp_flatpak_configuration_provider_load_manifests (GbpFlatpakConfigurationProvid
                                  G_FILE_QUERY_INFO_NONE,
                                  cancellable,
                                  error);
-
   if (file_info == NULL)
     return;
 
@@ -963,18 +906,11 @@ gbp_flatpak_configuration_provider_load_manifests (GbpFlatpakConfigurationProvid
   else
     project_dir = g_file_get_parent (project_file);
 
-  gbp_flatpak_configuration_provider_find_manifests (self,
-                                                     project_dir,
-                                                     configurations,
-                                                     0,
-                                                     cancellable,
-                                                     error);
-  if (error != NULL)
-    return;
+  g_mutex_lock (&self->mutex);
+  gbp_flatpak_configuration_provider_find_manifests (self, project_dir, configs, 0, cancellable, error);
+  g_mutex_unlock (&self->mutex);
 
-  IDE_TRACE_MSG ("Found %u flatpak manifests", configurations->len);
-
-  return;
+  g_debug ("Found %u flatpak manifests", configs->len);
 }
 
 static void
@@ -984,147 +920,145 @@ gbp_flatpak_configuration_provider_load_worker (GTask        *task,
                                                 GCancellable *cancellable)
 {
   GbpFlatpakConfigurationProvider *self = source_object;
-  g_autoptr(GPtrArray) ret = NULL;
-  g_autoptr(GFile) file = NULL;
-  g_autofree gchar *path = NULL;
-  GError *error = NULL;
+  g_autoptr(GError) error = NULL;
+  GPtrArray *configs = task_data;
 
   IDE_ENTRY;
 
   g_assert (G_IS_TASK (task));
   g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (self->manager));
+  g_assert (configs != NULL);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  ret = g_ptr_array_new_with_free_func (g_object_unref);
+  gbp_flatpak_configuration_provider_load_manifests (self, configs, cancellable, &error);
 
-  /* Load flatpak manifests in the repo */
-  gbp_flatpak_configuration_provider_load_manifests (self, ret, cancellable, &error);
   if (error != NULL)
-    {
-      g_warning ("%s", error->message);
-      g_clear_error (&error);
-    }
-
-  g_task_return_pointer (task, g_steal_pointer (&ret), (GDestroyNotify)g_ptr_array_unref);
-
-  IDE_EXIT;
-}
-
-static void
-gbp_flatpak_configuration_provider_load_cb (GObject      *object,
-                                            GAsyncResult *result,
-                                            gpointer      user_data)
-{
-  GbpFlatpakConfigurationProvider *self = (GbpFlatpakConfigurationProvider *)object;
-  g_autoptr(GError) error = NULL;
-  GPtrArray *ret;
-  guint i;
-  g_autoptr(GTask) task = user_data;
-
-  IDE_ENTRY;
-
-  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
-  g_assert (G_IS_TASK (result));
-  g_assert (G_IS_TASK (task));
-
-  if (!(ret = g_task_propagate_pointer (G_TASK (result), &error)))
-    {
-      g_warning ("%s", error->message);
-      g_task_return_error (task, g_steal_pointer (&error));
-      IDE_EXIT;
-    }
-
-  for (i = 0; i < ret->len; i++)
-    {
-      IdeConfiguration *configuration = g_ptr_array_index (ret, i);
-
-      ide_configuration_manager_add (self->manager, configuration);
-      ide_configuration_manager_set_current (self->manager, configuration);
-    }
-
-  self->configurations = ret;
-
-  g_task_return_boolean (task, TRUE);
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
 
   IDE_EXIT;
 }
 
 static void
 gbp_flatpak_configuration_provider_load_async (IdeConfigurationProvider *provider,
-                                               IdeConfigurationManager  *manager,
                                                GCancellable             *cancellable,
                                                GAsyncReadyCallback       callback,
                                                gpointer                  user_data)
 {
-  GbpFlatpakConfigurationProvider *self = (GbpFlatpakConfigurationProvider *)provider;
-  g_autoptr(GTask) parent_task = NULL;
   g_autoptr(GTask) task = NULL;
 
   IDE_ENTRY;
 
-  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (provider));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  dzl_set_weak_pointer (&self->manager, manager);
-
-  self->manifest_monitors = g_ptr_array_new_with_free_func (g_object_unref);
-
-  parent_task = g_task_new (self, cancellable, callback, user_data);
-  task = g_task_new (self, cancellable, gbp_flatpak_configuration_provider_load_cb, g_steal_pointer 
(&parent_task));
+  task = g_task_new (provider, cancellable, callback, user_data);
+  g_task_set_source_tag (task, gbp_flatpak_configuration_provider_load_async);
+  g_task_set_priority (task, G_PRIORITY_LOW);
+  g_task_set_task_data (task,
+                        g_ptr_array_new_with_free_func (g_object_unref),
+                        (GDestroyNotify)g_ptr_array_unref);
   g_task_run_in_thread (task, gbp_flatpak_configuration_provider_load_worker);
 
   IDE_EXIT;
 }
 
-gboolean
+static gboolean
 gbp_flatpak_configuration_provider_load_finish (IdeConfigurationProvider  *provider,
                                                 GAsyncResult              *result,
                                                 GError                   **error)
 {
   GbpFlatpakConfigurationProvider *self = (GbpFlatpakConfigurationProvider *)provider;
 
-  g_return_val_if_fail (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self), FALSE);
-  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+  g_assert (G_IS_TASK (result));
+  g_assert (g_task_is_valid (G_TASK (result), provider));
 
-  return g_task_propagate_boolean (G_TASK (result), error);
+  if (g_task_propagate_boolean (G_TASK (result), error))
+    {
+      GPtrArray *configs = g_task_get_task_data (G_TASK (result));
+
+      g_assert (configs != NULL);
+
+      for (guint i = 0; i < configs->len; i++)
+        {
+          IdeConfiguration *config = g_ptr_array_index (configs, i);
+          g_assert (IDE_IS_CONFIGURATION (config));
+          ide_configuration_provider_emit_added (provider, config);
+        }
+
+      if (configs->len > 0)
+        {
+          IdeConfiguration *config = g_ptr_array_index (configs, 0);
+          IdeContext *context = ide_object_get_context (IDE_OBJECT (self));
+          IdeConfigurationManager *manager = ide_context_get_configuration_manager (context);
+
+          g_assert (IDE_IS_CONFIGURATION (config));
+
+          /* TODO: We should have a GSetting for this, in config-manager */
+          ide_configuration_manager_set_current (manager, config);
+        }
+
+      return TRUE;
+    }
+
+  return FALSE;
 }
 
 static void
-gbp_flatpak_configuration_provider_unload (IdeConfigurationProvider *provider,
-                                           IdeConfigurationManager  *manager)
+gbp_flatpak_configuration_provider_unload (IdeConfigurationProvider *provider)
 {
   GbpFlatpakConfigurationProvider *self = (GbpFlatpakConfigurationProvider *)provider;
 
   IDE_ENTRY;
 
   g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
-  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
 
-  dzl_clear_source (&self->writeback_handler);
+  g_mutex_lock (&self->mutex);
 
-  if (self->configurations != NULL)
+  if (self->configs != NULL)
     {
-      for (guint i = 0; i < self->configurations->len; i++)
+      for (guint i = self->configs->len; i > 0; i--)
         {
-          IdeConfiguration *configuration = g_ptr_array_index (self->configurations, i);
+          g_autoptr(IdeConfiguration) config = NULL;
+
+          config = g_object_ref (g_ptr_array_index (self->configs, i - 1));
+          g_ptr_array_remove_index (self->configs, i);
 
-          ide_configuration_manager_remove (manager, configuration);
+          g_mutex_unlock (&self->mutex);
+          ide_configuration_provider_emit_removed (provider, config);
+          g_mutex_lock (&self->mutex);
         }
     }
 
-  g_clear_pointer (&self->configurations, g_ptr_array_unref);
-
+  g_clear_pointer (&self->configs, g_ptr_array_unref);
   g_clear_pointer (&self->manifest_monitors, g_ptr_array_unref);
 
-  dzl_clear_weak_pointer (&self->manager);
+  g_mutex_unlock (&self->mutex);
 
   IDE_EXIT;
 }
 
+static void
+gbp_flatpak_configuration_provider_finalize (GObject *object)
+{
+  GbpFlatpakConfigurationProvider *self = (GbpFlatpakConfigurationProvider *)object;
+
+  g_mutex_clear (&self->mutex);
+
+  G_OBJECT_CLASS (gbp_flatpak_configuration_provider_parent_class)->finalize (object);
+}
+
 static void
 gbp_flatpak_configuration_provider_class_init (GbpFlatpakConfigurationProviderClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_flatpak_configuration_provider_finalize;
+
   /* This regex is based on https://wiki.gnome.org/HowDoI/ChooseApplicationID */
   filename_regex = g_regex_new ("^[[:alnum:]-_]+\\.[[:alnum:]-_]+(\\.[[:alnum:]-_]+)*\\.json$",
                                 G_REGEX_OPTIMIZE, 0, NULL);
@@ -1133,6 +1067,10 @@ gbp_flatpak_configuration_provider_class_init (GbpFlatpakConfigurationProviderCl
 static void
 gbp_flatpak_configuration_provider_init (GbpFlatpakConfigurationProvider *self)
 {
+  g_mutex_init (&self->mutex);
+
+  self->manifest_monitors = g_ptr_array_new_with_free_func (g_object_unref);
+  self->configs = g_ptr_array_new_with_free_func (g_object_unref);
 }
 
 static void



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