[gnome-builder] configuration: add IdeConfiguration and IdeConfigurationManager



commit d17f108b95f71849613dcd24d09584ac478ded81
Author: Christian Hergert <chergert redhat com>
Date:   Sun Feb 14 20:40:45 2016 -0800

    configuration: add IdeConfiguration and IdeConfigurationManager
    
    IdeConfiguration wraps all the important information required to do a
    build. This means, runtime, device, environment variables, and possibly
    more as needed.

 libide/Makefile.am                 |    4 +
 libide/ide-configuration-manager.c |  747 ++++++++++++++++++++++++++++++++
 libide/ide-configuration-manager.h |   53 +++
 libide/ide-configuration.c         |  824 ++++++++++++++++++++++++++++++++++++
 libide/ide-configuration.h         |   79 ++++
 libide/ide-context.c               |  105 +++++
 libide/ide-context.h               |    1 +
 7 files changed, 1813 insertions(+), 0 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 7a89836..84dabe9 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -61,6 +61,10 @@ libide_1_0_la_public_sources = \
        ide-completion-provider.h \
        ide-completion-results.c \
        ide-completion-results.h \
+       ide-configuration.c \
+       ide-configuration.h \
+       ide-configuration-manager.c \
+       ide-configuration-manager.h \
        ide-context.c \
        ide-context.h \
        ide-debugger.c \
diff --git a/libide/ide-configuration-manager.c b/libide/ide-configuration-manager.c
new file mode 100644
index 0000000..0ecb2fd
--- /dev/null
+++ b/libide/ide-configuration-manager.c
@@ -0,0 +1,747 @@
+/* ide-configuration-manager.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-configuration-manager"
+
+#include <glib/gi18n.h>
+
+#include "ide-configuration.h"
+#include "ide-configuration-manager.h"
+#include "ide-context.h"
+#include "ide-debug.h"
+#include "ide-environment.h"
+#include "ide-macros.h"
+#include "ide-vcs.h"
+
+#define DOT_BUILD_CONFIG ".buildconfig"
+#define WRITEBACK_TIMEOUT_SECS 2
+
+struct _IdeConfigurationManager
+{
+  GObject           parent_instance;
+
+  GPtrArray        *configurations;
+  IdeConfiguration *current;
+  GKeyFile         *key_file;
+
+  gulong            writeback_handler;
+  guint             change_count;
+};
+
+static void async_initable_iface_init (GAsyncInitableIface *iface);
+static void list_model_iface_init     (GListModelInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeConfigurationManager, ide_configuration_manager, IDE_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)
+                        G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init))
+
+enum {
+  PROP_0,
+  PROP_CURRENT,
+  PROP_CURRENT_DISPLAY_NAME,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+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))
+    {
+      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);
+    }
+}
+
+static void
+load_environ (IdeConfiguration *configuration,
+              GKeyFile         *key_file,
+              const gchar      *group)
+{
+  IdeEnvironment *environment;
+  g_auto(GStrv) keys = 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);
+
+  if (keys != NULL)
+    {
+      guint i;
+
+      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);
+        }
+    }
+}
+
+static gboolean
+ide_configuration_manager_load (IdeConfigurationManager  *self,
+                                GKeyFile                 *key_file,
+                                const gchar              *group,
+                                GError                  **error)
+{
+  g_autoptr(IdeConfiguration) configuration = NULL;
+  g_autofree gchar *env_group = NULL;
+  IdeContext *context;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_assert (key_file != NULL);
+  g_assert (group != NULL);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  configuration = g_object_new (IDE_TYPE_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, "runtime", "runtime-id");
+  load_string (configuration, key_file, group, "prefix", "prefix");
+
+  env_group = g_strdup_printf ("%s.environment", group);
+
+  if (g_key_file_has_group (key_file, env_group))
+    load_environ (configuration, key_file, env_group);
+
+  ide_configuration_set_dirty (configuration, FALSE);
+
+  ide_configuration_manager_add (self, configuration);
+
+  if (g_key_file_get_boolean (key_file, group, "default", NULL))
+    ide_configuration_manager_set_current (self, configuration);
+
+  return TRUE;
+}
+
+static gboolean
+ide_configuration_manager_restore (IdeConfigurationManager  *self,
+                                   GFile                    *file,
+                                   GCancellable             *cancellable,
+                                   GError                  **error)
+{
+  g_autofree gchar *contents = NULL;
+  g_auto(GStrv) groups = NULL;
+  gsize length = 0;
+  guint i;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (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);
+
+  if (!g_key_file_load_from_data (self->key_file,
+                                  contents,
+                                  length,
+                                  G_KEY_FILE_KEEP_COMMENTS,
+                                  error))
+    IDE_RETURN (FALSE);
+
+  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 (!ide_configuration_manager_load (self, self->key_file, groups [i], error))
+        IDE_RETURN (FALSE);
+    }
+
+  IDE_RETURN (TRUE);
+}
+
+static void
+ide_configuration_manager_save_cb (GObject      *object,
+                                   GAsyncResult *result,
+                                   gpointer      user_data)
+{
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+  GFile *file = (GFile *)object;
+
+  g_assert (G_IS_FILE (file));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!g_file_replace_contents_finish (file, result, NULL, &error))
+    g_task_return_error (task, error);
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+ide_configuration_manager_save_async (IdeConfigurationManager *self,
+                                      GCancellable            *cancellable,
+                                      GAsyncReadyCallback      callback,
+                                      gpointer                 user_data)
+{
+  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;
+  gchar *data;
+  gsize length;
+  IdeContext *context;
+  IdeVcs *vcs;
+  GFile *workdir;
+  GError *error = NULL;
+  guint i;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  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);
+      return;
+    }
+
+  self->change_count = 0;
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  vcs = ide_context_get_vcs (context);
+  workdir = ide_vcs_get_working_directory (vcs);
+  file = g_file_get_child (workdir, DOT_BUILD_CONFIG);
+
+  /*
+   * NOTE:
+   *
+   * 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.
+   */
+
+  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 (i = 0; i < self->configurations->len; i++)
+    {
+      IdeConfiguration *configuration = g_ptr_array_index (self->configurations, i);
+      IdeEnvironment *environment;
+      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);
+
+      /*
+       * 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);
+
+#define PERSIST_STRING_KEY(key, getter) \
+      g_key_file_set_string (self->key_file, group, key, \
+                             ide_configuration_##getter (configuration))
+      PERSIST_STRING_KEY ("name", get_display_name);
+      PERSIST_STRING_KEY ("device", get_device_id);
+      PERSIST_STRING_KEY ("runtime", get_runtime_id);
+      PERSIST_STRING_KEY ("config-opts", get_config_opts);
+      PERSIST_STRING_KEY ("prefix", get_prefix);
+#undef PERSIST_STRING_KEY
+
+      if (configuration == self->current)
+        g_key_file_set_boolean (self->key_file, group, "default", TRUE);
+      else
+        g_key_file_remove_key (self->key_file, group, "default", NULL);
+
+      environment = ide_configuration_get_environment (configuration);
+
+      /*
+       * 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))
+        {
+          g_auto(GStrv) keys = NULL;
+
+          if (NULL != (keys = g_key_file_get_keys (self->key_file, group_environ, NULL, NULL)))
+            {
+              for (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);
+                }
+            }
+        }
+
+      n_items = g_list_model_get_n_items (G_LIST_MODEL (environment));
+
+      for (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);
+          key = ide_environment_variable_get_key (var);
+          value = ide_environment_variable_get_value (var);
+
+          if (!ide_str_empty0 (key))
+            g_key_file_set_string (self->key_file, group_environ, key, value ?: "");
+        }
+    }
+
+  /*
+   * Now truncate any old groups in the keyfile.
+   */
+  if (NULL != (groups = g_key_file_get_groups (self->key_file, NULL)))
+    {
+      for (i = 0; groups [i]; i++)
+        {
+          if (!g_hash_table_contains (group_names, groups [i]))
+            g_key_file_remove_group (self->key_file, groups [i], NULL);
+        }
+    }
+
+  if (NULL == (data = g_key_file_to_data (self->key_file, &length, &error)))
+    {
+      g_task_return_error (task, error);
+      IDE_EXIT;
+    }
+
+  bytes = g_bytes_new_take (data, length);
+
+  g_file_replace_contents_bytes_async (file,
+                                       bytes,
+                                       NULL,
+                                       FALSE,
+                                       G_FILE_CREATE_NONE,
+                                       cancellable,
+                                       ide_configuration_manager_save_cb,
+                                       g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+gboolean
+ide_configuration_manager_save_finish (IdeConfigurationManager  *self,
+                                       GAsyncResult             *result,
+                                       GError                  **error)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+ide_configuration_manager_do_writeback (gpointer data)
+{
+  IdeConfigurationManager *self = data;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+
+  self->writeback_handler = 0;
+
+  ide_configuration_manager_save_async (self, NULL, NULL, NULL);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ide_configuration_manager_queue_writeback (IdeConfigurationManager *self)
+{
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+
+  if (self->writeback_handler != 0)
+    g_source_remove (self->writeback_handler);
+
+  self->writeback_handler = g_timeout_add_seconds (WRITEBACK_TIMEOUT_SECS,
+                                                   ide_configuration_manager_do_writeback,
+                                                   self);
+}
+
+static void
+ide_configuration_manager_add_default (IdeConfigurationManager *self)
+{
+  g_autoptr(IdeConfiguration) config = NULL;
+  IdeContext *context;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  config = ide_configuration_new (context, "default", "local", "host");
+  ide_configuration_set_display_name (config, _("Default Configuration"));
+  ide_configuration_manager_add (self, config);
+
+  if (self->configurations->len == 1)
+    ide_configuration_manager_set_current (self, config);
+}
+
+/**
+ * ide_configuration_manager_get_configuration:
+ * @self: An #IdeConfigurationManager
+ * @id: The string identifier of the configuration
+ *
+ * Gets the #IdeConfiguration by id. See ide_configuration_get_id().
+ *
+ * Returns: (transfer none) (nullable): An #IdeConfiguration or %NULL if
+ *   the configuration could not be found.
+ */
+IdeConfiguration *
+ide_configuration_manager_get_configuration (IdeConfigurationManager *self,
+                                             const gchar             *id)
+{
+  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++)
+    {
+      IdeConfiguration *configuration = g_ptr_array_index (self->configurations, i);
+
+      if (g_strcmp0 (id, ide_configuration_get_id (configuration)) == 0)
+        return configuration;
+    }
+
+  return NULL;
+}
+
+static void
+ide_configuration_manager_finalize (GObject *object)
+{
+  IdeConfigurationManager *self = (IdeConfigurationManager *)object;
+
+  ide_clear_source (&self->writeback_handler);
+  g_clear_pointer (&self->configurations, g_ptr_array_unref);
+  g_clear_pointer (&self->key_file, g_key_file_free);
+  g_clear_object (&self->current);
+
+  G_OBJECT_CLASS (ide_configuration_manager_parent_class)->finalize (object);
+}
+
+static void
+ide_configuration_manager_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+  IdeConfigurationManager *self = IDE_CONFIGURATION_MANAGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CURRENT:
+      g_value_set_object (value, ide_configuration_manager_get_current (self));
+      break;
+
+    case PROP_CURRENT_DISPLAY_NAME:
+      {
+        IdeConfiguration *current = ide_configuration_manager_get_current (self);
+        g_value_set_string (value, ide_configuration_get_display_name (current));
+        break;
+      }
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+ide_configuration_manager_set_property (GObject      *object,
+                                        guint         prop_id,
+                                        const GValue *value,
+                                        GParamSpec   *pspec)
+{
+  IdeConfigurationManager *self = IDE_CONFIGURATION_MANAGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CURRENT:
+      ide_configuration_manager_set_current (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+    }
+}
+
+static void
+ide_configuration_manager_class_init (IdeConfigurationManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  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;
+
+  properties [PROP_CURRENT] =
+    g_param_spec_object ("current",
+                         "Current",
+                         "The current configuration for the context",
+                         IDE_TYPE_CONFIGURATION,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_CURRENT_DISPLAY_NAME] =
+    g_param_spec_string ("current-display-name",
+                         "Current Display Name",
+                         "The display name of the current configuration",
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_configuration_manager_init (IdeConfigurationManager *self)
+{
+  self->configurations = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+static GType
+ide_configuration_manager_get_item_type (GListModel *model)
+{
+  return IDE_TYPE_CONFIGURATION;
+}
+
+static guint
+ide_configuration_manager_get_n_items (GListModel *model)
+{
+  IdeConfigurationManager *self = (IdeConfigurationManager *)model;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+
+  return self->configurations->len;
+}
+
+static gpointer
+ide_configuration_manager_get_item (GListModel *model,
+                                    guint       position)
+{
+  IdeConfigurationManager *self = (IdeConfigurationManager *)model;
+
+  g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), NULL);
+  g_return_val_if_fail (position < self->configurations->len, NULL);
+
+  return g_object_ref (g_ptr_array_index (self->configurations, position));
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_item_type = ide_configuration_manager_get_item_type;
+  iface->get_n_items = ide_configuration_manager_get_n_items;
+  iface->get_item = ide_configuration_manager_get_item;
+}
+
+static void
+ide_configuration_manager_init_worker (GTask        *task,
+                                       gpointer      source_object,
+                                       gpointer      task_data,
+                                       GCancellable *cancellable)
+{
+  IdeConfigurationManager *self = source_object;
+  g_autoptr(GFile) settings_file = NULL;
+  IdeContext *context;
+  GError *error = NULL;
+  IdeVcs *vcs;
+  GFile *workdir;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  vcs = ide_context_get_vcs (context);
+  workdir = ide_vcs_get_working_directory (vcs);
+  settings_file = g_file_get_child (workdir, DOT_BUILD_CONFIG);
+
+  if (!g_file_query_exists (settings_file, cancellable) ||
+      !ide_configuration_manager_restore (self, settings_file, cancellable, &error))
+    ide_configuration_manager_add_default (self);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_configuration_manager_init_async (GAsyncInitable      *initable,
+                                      gint                 priority,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data)
+{
+  IdeConfigurationManager *self = (IdeConfigurationManager *)initable;
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (G_IS_ASYNC_INITABLE (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_run_in_thread (task, ide_configuration_manager_init_worker);
+}
+
+static gboolean
+ide_configuration_manager_init_finish (GAsyncInitable  *initable,
+                                       GAsyncResult    *result,
+                                       GError         **error)
+{
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (initable));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *iface)
+{
+  iface->init_async = ide_configuration_manager_init_async;
+  iface->init_finish = ide_configuration_manager_init_finish;
+}
+
+void
+ide_configuration_manager_set_current (IdeConfigurationManager *self,
+                                       IdeConfiguration        *current)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_return_if_fail (!current || IDE_IS_CONFIGURATION (current));
+
+  if (g_set_object (&self->current, current))
+    {
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CURRENT]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CURRENT_DISPLAY_NAME]);
+    }
+}
+
+/**
+ * ide_configuration_manager_get_current:
+ * @self: An #IdeConfigurationManager
+ *
+ * Gets the current configuration to use for building.
+ *
+ * Many systems allow you to pass a configuration in instead of relying on the
+ * default configuration. This sets the default configuration that various
+ * background items might use, such as tags builders which need to discover
+ * settings.
+ *
+ * Returns: (transfer none): An #IdeConfiguration
+ */
+IdeConfiguration *
+ide_configuration_manager_get_current (IdeConfigurationManager *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION_MANAGER (self), NULL);
+
+  if ((self->current == NULL) && (self->configurations->len > 0))
+    return g_ptr_array_index (self->configurations, 0);
+
+  return self->current;
+}
+
+static void
+ide_configuration_manager_changed (IdeConfigurationManager *self,
+                                   GParamSpec              *pspec,
+                                   IdeConfiguration        *configuration)
+{
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_assert (IDE_IS_CONFIGURATION (configuration));
+
+  self->change_count++;
+
+  ide_configuration_manager_queue_writeback (self);
+}
+
+void
+ide_configuration_manager_add (IdeConfigurationManager *self,
+                               IdeConfiguration        *configuration)
+{
+  guint position;
+
+  g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_return_if_fail (IDE_IS_CONFIGURATION (configuration));
+
+  g_signal_connect_object (configuration,
+                           "changed",
+                           G_CALLBACK (ide_configuration_manager_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  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);
+}
+
+void
+ide_configuration_manager_remove (IdeConfigurationManager *self,
+                                  IdeConfiguration        *configuration)
+{
+  guint i;
+
+  g_return_if_fail (IDE_IS_CONFIGURATION_MANAGER (self));
+  g_return_if_fail (IDE_IS_CONFIGURATION (configuration));
+
+  for (i = 0; i < self->configurations->len; i++)
+    {
+      IdeConfiguration *item = g_ptr_array_index (self->configurations, i);
+
+      if (item == configuration)
+        {
+          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);
+          break;
+        }
+    }
+}
diff --git a/libide/ide-configuration-manager.h b/libide/ide-configuration-manager.h
new file mode 100644
index 0000000..efe8dbc
--- /dev/null
+++ b/libide/ide-configuration-manager.h
@@ -0,0 +1,53 @@
+/* ide-configuration-manager.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_CONFIGURATION_MANAGER_H
+#define IDE_CONFIGURATION_MANAGER_H
+
+#include <gio/gio.h>
+
+#include "ide-object.h"
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CONFIGURATION_MANAGER (ide_configuration_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeConfigurationManager, ide_configuration_manager, IDE, CONFIGURATION_MANAGER, 
IdeObject)
+
+IdeConfiguration *ide_configuration_manager_get_current       (IdeConfigurationManager  *self);
+void              ide_configuration_manager_set_current       (IdeConfigurationManager  *self,
+                                                               IdeConfiguration         *configuration);
+IdeConfiguration *ide_configuration_manager_get_configuration (IdeConfigurationManager  *self,
+                                                               const gchar              *id);
+void              ide_configuration_manager_add               (IdeConfigurationManager  *self,
+                                                               IdeConfiguration         *configuration);
+void              ide_configuration_manager_remove            (IdeConfigurationManager  *self,
+                                                               IdeConfiguration         *configuration);
+void              ide_configuration_manager_save_async        (IdeConfigurationManager  *self,
+                                                               GCancellable             *cancellable,
+                                                               GAsyncReadyCallback       callback,
+                                                               gpointer                  user_data);
+gboolean          ide_configuration_manager_save_finish       (IdeConfigurationManager  *self,
+                                                               GAsyncResult             *result,
+                                                               GError                  **error);
+
+
+G_END_DECLS
+
+#endif /* IDE_CONFIGURATION_MANAGER_H */
diff --git a/libide/ide-configuration.c b/libide/ide-configuration.c
new file mode 100644
index 0000000..e6d9fcf
--- /dev/null
+++ b/libide/ide-configuration.c
@@ -0,0 +1,824 @@
+/* ide-configuration.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-configuration"
+
+#include <string.h>
+
+#include "ide-configuration.h"
+#include "ide-context.h"
+#include "ide-device.h"
+#include "ide-device-manager.h"
+#include "ide-environment.h"
+#include "ide-runtime.h"
+#include "ide-runtime-manager.h"
+
+struct _IdeConfiguration
+{
+  IdeObject       parent_instance;
+
+  gchar          *config_opts;
+  gchar          *device_id;
+  gchar          *display_name;
+  gchar          *id;
+  gchar          *prefix;
+  gchar          *runtime_id;
+
+  IdeEnvironment *environment;
+
+  gint            parallelism;
+
+  guint           dirty : 1;
+  guint           debug : 1;
+};
+
+G_DEFINE_TYPE (IdeConfiguration, ide_configuration, IDE_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_CONFIG_OPTS,
+  PROP_DEBUG,
+  PROP_DEVICE,
+  PROP_DEVICE_ID,
+  PROP_DIRTY,
+  PROP_DISPLAY_NAME,
+  PROP_ENVIRON,
+  PROP_ID,
+  PROP_PARALLELISM,
+  PROP_PREFIX,
+  PROP_RUNTIME,
+  PROP_RUNTIME_ID,
+  N_PROPS
+};
+
+enum {
+  CHANGED,
+  LAST_SIGNAL
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [LAST_SIGNAL];
+
+static void
+ide_configuration_set_id (IdeConfiguration *self,
+                          const gchar      *id)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (id != NULL);
+
+  if (g_strcmp0 (id, self->id) != 0)
+    {
+      g_free (self->id);
+      self->id = g_strdup (id);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ID]);
+    }
+}
+
+static void
+ide_configuration_device_manager_items_changed (IdeConfiguration *self,
+                                                guint             position,
+                                                guint             added,
+                                                guint             removed,
+                                                IdeDeviceManager *device_manager)
+{
+  IdeDevice *device;
+
+  g_assert (IDE_IS_CONFIGURATION (self));
+  g_assert (IDE_IS_DEVICE_MANAGER (device_manager));
+
+  device = ide_device_manager_get_device (device_manager, self->device_id);
+
+  if (device != NULL)
+    ide_device_prepare_configuration (device, self);
+}
+
+static void
+ide_configuration_runtime_manager_items_changed (IdeConfiguration  *self,
+                                                 guint              position,
+                                                 guint              added,
+                                                 guint              removed,
+                                                 IdeRuntimeManager *runtime_manager)
+{
+  IdeRuntime *runtime;
+
+  g_assert (IDE_IS_CONFIGURATION (self));
+  g_assert (IDE_IS_RUNTIME_MANAGER (runtime_manager));
+
+  runtime = ide_runtime_manager_get_runtime (runtime_manager, self->runtime_id);
+
+  if (runtime != NULL)
+    ide_runtime_prepare_configuration (runtime, self);
+}
+
+static void
+ide_configuration_environment_changed (IdeConfiguration *self,
+                                       guint             position,
+                                       guint             added,
+                                       guint             removed,
+                                       IdeEnvironment   *environment)
+{
+  g_assert (IDE_IS_CONFIGURATION (self));
+  g_assert (IDE_IS_ENVIRONMENT (environment));
+
+  ide_configuration_set_dirty (self, TRUE);
+}
+
+static void
+ide_configuration_constructed (GObject *object)
+{
+  IdeConfiguration *self = (IdeConfiguration *)object;
+  IdeContext *context;
+  IdeDeviceManager *device_manager;
+  IdeRuntimeManager *runtime_manager;
+
+  G_OBJECT_CLASS (ide_configuration_parent_class)->constructed (object);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  device_manager = ide_context_get_device_manager (context);
+  runtime_manager = ide_context_get_runtime_manager (context);
+
+  g_signal_connect_object (device_manager,
+                           "items-changed",
+                           G_CALLBACK (ide_configuration_device_manager_items_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (runtime_manager,
+                           "items-changed",
+                           G_CALLBACK (ide_configuration_runtime_manager_items_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_configuration_device_manager_items_changed (self, 0, 0, 0, device_manager);
+  ide_configuration_runtime_manager_items_changed (self, 0, 0, 0, runtime_manager);
+}
+
+static void
+ide_configuration_finalize (GObject *object)
+{
+  IdeConfiguration *self = (IdeConfiguration *)object;
+
+  g_clear_object (&self->environment);
+
+  g_clear_pointer (&self->config_opts, g_free);
+  g_clear_pointer (&self->device_id, g_free);
+  g_clear_pointer (&self->display_name, g_free);
+  g_clear_pointer (&self->id, g_free);
+  g_clear_pointer (&self->prefix, g_free);
+  g_clear_pointer (&self->runtime_id, g_free);
+
+  G_OBJECT_CLASS (ide_configuration_parent_class)->finalize (object);
+}
+
+static void
+ide_configuration_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeConfiguration *self = IDE_CONFIGURATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONFIG_OPTS:
+      g_value_set_string (value, ide_configuration_get_config_opts (self));
+      break;
+
+    case PROP_DEBUG:
+      g_value_set_boolean (value, ide_configuration_get_debug (self));
+      break;
+
+    case PROP_DEVICE:
+      g_value_set_object (value, ide_configuration_get_device (self));
+      break;
+
+    case PROP_DIRTY:
+      g_value_set_boolean (value, ide_configuration_get_dirty (self));
+      break;
+
+    case PROP_DISPLAY_NAME:
+      g_value_set_string (value, ide_configuration_get_display_name (self));
+      break;
+
+    case PROP_ENVIRON:
+      g_value_set_boxed (value, ide_configuration_get_environ (self));
+      break;
+
+    case PROP_ID:
+      g_value_set_string (value, ide_configuration_get_id (self));
+      break;
+
+    case PROP_PARALLELISM:
+      g_value_set_int (value, ide_configuration_get_parallelism (self));
+      break;
+
+    case PROP_PREFIX:
+      g_value_set_string (value, ide_configuration_get_prefix (self));
+      break;
+
+    case PROP_RUNTIME:
+      g_value_set_object (value, ide_configuration_get_runtime (self));
+      break;
+
+    case PROP_RUNTIME_ID:
+      g_value_set_object (value, ide_configuration_get_runtime (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_configuration_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  IdeConfiguration *self = IDE_CONFIGURATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONFIG_OPTS:
+      ide_configuration_set_config_opts (self, g_value_get_string (value));
+      break;
+
+    case PROP_DEBUG:
+      ide_configuration_set_debug (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_DEVICE:
+      ide_configuration_set_device (self, g_value_get_object (value));
+      break;
+
+    case PROP_DEVICE_ID:
+      ide_configuration_set_device_id (self, g_value_get_string (value));
+      break;
+
+    case PROP_DIRTY:
+      ide_configuration_set_dirty (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_DISPLAY_NAME:
+      ide_configuration_set_display_name (self, g_value_get_string (value));
+      break;
+
+    case PROP_ID:
+      ide_configuration_set_id (self, g_value_get_string (value));
+      break;
+
+    case PROP_PREFIX:
+      ide_configuration_set_prefix (self, g_value_get_string (value));
+      break;
+
+    case PROP_PARALLELISM:
+      ide_configuration_set_parallelism (self, g_value_get_int (value));
+      break;
+
+    case PROP_RUNTIME:
+      ide_configuration_set_runtime (self, g_value_get_object (value));
+      break;
+
+    case PROP_RUNTIME_ID:
+      ide_configuration_set_runtime_id (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_configuration_class_init (IdeConfigurationClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_configuration_constructed;
+  object_class->finalize = ide_configuration_finalize;
+  object_class->get_property = ide_configuration_get_property;
+  object_class->set_property = ide_configuration_set_property;
+
+  properties [PROP_CONFIG_OPTS] =
+    g_param_spec_string ("config-opts",
+                         "Config Options",
+                         "Parameters to bootstrap the project",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DEBUG] =
+    g_param_spec_boolean ("debug",
+                          "Debug",
+                          "Debug",
+                          TRUE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "Device",
+                         IDE_TYPE_DEVICE,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DEVICE_ID] =
+    g_param_spec_string ("device-id",
+                         "Device Id",
+                         "The identifier of the device",
+                         "local",
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DIRTY] =
+    g_param_spec_boolean ("dirty",
+                          "Dirty",
+                          "If the configuration has been changed.",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DISPLAY_NAME] =
+    g_param_spec_string ("display-name",
+                         "Display Name",
+                         "Display Name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ENVIRON] =
+    g_param_spec_boxed ("environ",
+                        "Environ",
+                        "Environ",
+                        G_TYPE_STRV,
+                        (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id",
+                         "Id",
+                         "Id",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PARALLELISM] =
+    g_param_spec_int ("parallelism",
+                      "Parallelism",
+                      "Parallelism",
+                      -1,
+                      G_MAXINT,
+                      -1,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_PREFIX] =
+    g_param_spec_string ("prefix",
+                         "Prefix",
+                         "Prefix",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_RUNTIME] =
+    g_param_spec_object ("runtime",
+                         "Runtime",
+                         "Runtime",
+                         IDE_TYPE_RUNTIME,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_RUNTIME_ID] =
+    g_param_spec_string ("runtime-id",
+                         "Runtime Id",
+                         "The identifier of the runtime",
+                         "host",
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [CHANGED] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+}
+
+static void
+ide_configuration_init (IdeConfiguration *self)
+{
+  self->device_id = g_strdup ("local");
+  self->runtime_id = g_strdup ("host");
+  self->debug = TRUE;
+  self->environment = ide_environment_new ();
+
+  g_signal_connect_object (self->environment,
+                           "items-changed",
+                           G_CALLBACK (ide_configuration_environment_changed),
+                           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)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->device_id;
+}
+
+void
+ide_configuration_set_device_id (IdeConfiguration *self,
+                                 const gchar      *device_id)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (device_id != NULL);
+
+  if (g_strcmp0 (device_id, self->device_id) != 0)
+    {
+      IdeContext *context;
+      IdeDeviceManager *device_manager;
+
+      g_free (self->device_id);
+      self->device_id = g_strdup (device_id);
+
+      ide_configuration_set_dirty (self, TRUE);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE_ID]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEVICE]);
+
+      context = ide_object_get_context (IDE_OBJECT (self));
+      device_manager = ide_context_get_device_manager (context);
+      ide_configuration_device_manager_items_changed (self, 0, 0, 0, device_manager);
+    }
+}
+
+/**
+ * ide_configuration_get_device:
+ * @self: An #IdeConfiguration
+ *
+ * Gets the device for the configuration.
+ *
+ * Returns: (transfer none) (nullable): An #IdeDevice.
+ */
+IdeDevice *
+ide_configuration_get_device (IdeConfiguration *self)
+{
+  IdeDeviceManager *device_manager;
+  IdeContext *context;
+
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  device_manager = ide_context_get_device_manager (context);
+
+  return ide_device_manager_get_device (device_manager, self->device_id);
+}
+
+void
+ide_configuration_set_device (IdeConfiguration *self,
+                              IdeDevice        *device)
+{
+  const gchar *device_id = "local";
+
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (!device || IDE_IS_DEVICE (device));
+
+  if (device != NULL)
+    device_id = ide_device_get_id (device);
+
+  ide_configuration_set_device_id (self, device_id);
+}
+
+const gchar *
+ide_configuration_get_runtime_id (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->runtime_id;
+}
+
+void
+ide_configuration_set_runtime_id (IdeConfiguration *self,
+                                  const gchar      *runtime_id)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (runtime_id != NULL);
+
+  if (g_strcmp0 (runtime_id, self->runtime_id) != 0)
+    {
+      IdeRuntimeManager *runtime_manager;
+      IdeContext *context;
+
+      g_free (self->runtime_id);
+      self->runtime_id = g_strdup (runtime_id);
+
+      ide_configuration_set_dirty (self, TRUE);
+
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUNTIME_ID]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUNTIME]);
+
+      context = ide_object_get_context (IDE_OBJECT (self));
+      runtime_manager = ide_context_get_runtime_manager (context);
+      ide_configuration_runtime_manager_items_changed (self, 0, 0, 0, runtime_manager);
+    }
+}
+
+/**
+ * ide_configuration_get_runtime:
+ * @self: An #IdeConfiguration
+ *
+ * Gets the runtime for the configuration.
+ *
+ * Returns: (transfer none) (nullable): An #IdeRuntime
+ */
+IdeRuntime *
+ide_configuration_get_runtime (IdeConfiguration *self)
+{
+  IdeRuntimeManager *runtime_manager;
+  IdeContext *context;
+
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  runtime_manager = ide_context_get_runtime_manager (context);
+
+  return ide_runtime_manager_get_runtime (runtime_manager, self->runtime_id);
+}
+
+void
+ide_configuration_set_runtime (IdeConfiguration *self,
+                               IdeRuntime       *runtime)
+{
+  const gchar *runtime_id = "host";
+
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (!runtime || IDE_IS_RUNTIME (runtime));
+
+  if (runtime != NULL)
+    runtime_id = ide_runtime_get_id (runtime);
+
+  ide_configuration_set_runtime_id (self, runtime_id);
+}
+
+/**
+ * ide_configuration_get_environ:
+ * @self: An #IdeConfiguration
+ *
+ * Gets the environment to use when spawning processes.
+ *
+ * Returns: (transfer full): An array of key=value environment variables.
+ */
+gchar **
+ide_configuration_get_environ (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return ide_environment_get_environ (self->environment);
+}
+
+const gchar *
+ide_configuration_getenv (IdeConfiguration *self,
+                          const gchar      *key)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  return ide_environment_getenv (self->environment, key);
+}
+
+void
+ide_configuration_setenv (IdeConfiguration *self,
+                          const gchar      *key,
+                          const gchar      *value)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (key != NULL);
+
+  ide_environment_setenv (self->environment, key, value);
+}
+
+const gchar *
+ide_configuration_get_id (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->id;
+}
+
+const gchar *
+ide_configuration_get_prefix (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->prefix;
+}
+
+void
+ide_configuration_set_prefix (IdeConfiguration *self,
+                              const gchar      *prefix)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+  if (g_strcmp0 (prefix, self->prefix) != 0)
+    {
+      g_free (self->prefix);
+      self->prefix = g_strdup (prefix);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PREFIX]);
+      ide_configuration_set_dirty (self, TRUE);
+    }
+}
+
+gint
+ide_configuration_get_parallelism (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), -1);
+
+  return self->parallelism;
+}
+
+void
+ide_configuration_set_parallelism (IdeConfiguration *self,
+                                   gint              parallelism)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+  g_return_if_fail (parallelism >= -1);
+
+  if (parallelism != self->parallelism)
+    {
+      self->parallelism = parallelism;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PARALLELISM]);
+    }
+}
+
+gboolean
+ide_configuration_get_debug (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), FALSE);
+
+  return self->debug;
+}
+
+void
+ide_configuration_set_debug (IdeConfiguration *self,
+                             gboolean          debug)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+  debug = !!debug;
+
+  if (debug != self->debug)
+    {
+      self->debug = debug;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DEBUG]);
+      ide_configuration_set_dirty (self, TRUE);
+    }
+}
+
+const gchar *
+ide_configuration_get_display_name (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->display_name;
+}
+
+void
+ide_configuration_set_display_name (IdeConfiguration *self,
+                                    const gchar      *display_name)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+  if (g_strcmp0 (display_name, self->display_name) != 0)
+    {
+      g_free (self->display_name);
+      self->display_name = g_strdup (display_name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY_NAME]);
+    }
+}
+
+gboolean
+ide_configuration_get_dirty (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), FALSE);
+
+  return self->dirty;
+}
+
+void
+ide_configuration_set_dirty (IdeConfiguration *self,
+                             gboolean          dirty)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+  dirty = !!dirty;
+
+  if (dirty != self->dirty)
+    {
+      self->dirty = dirty;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIRTY]);
+    }
+
+  /*
+   * Always emit the changed signal so that the configuration manager
+   * can queue a writeback of the configuration.
+   */
+  g_signal_emit (self, signals [CHANGED], 0);
+}
+
+/**
+ * ide_configuration_get_environment:
+ *
+ * Returns: (transfer none): An #IdeEnvironment.
+ */
+IdeEnvironment *
+ide_configuration_get_environment (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->environment;
+}
+
+const gchar *
+ide_configuration_get_config_opts (IdeConfiguration *self)
+{
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  return self->config_opts;
+}
+
+void
+ide_configuration_set_config_opts (IdeConfiguration *self,
+                                   const gchar      *config_opts)
+{
+  g_return_if_fail (IDE_IS_CONFIGURATION (self));
+
+  if (g_strcmp0 (config_opts, self->config_opts) != 0)
+    {
+      g_free (self->config_opts);
+      self->config_opts = g_strdup (config_opts);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CONFIG_OPTS]);
+      ide_configuration_set_dirty (self, TRUE);
+    }
+}
+
+/**
+ * ide_configuration_duplicate:
+ * @self: An #IdeConfiguration
+ *
+ * Copies the configuration into a new configuration.
+ *
+ * Returns: (transfer full): An #IdeConfiguration.
+ */
+IdeConfiguration *
+ide_configuration_duplicate (IdeConfiguration *self)
+{
+  static gint next_counter = 2;
+  IdeConfiguration *copy;
+  IdeContext *context;
+  g_autofree gchar *id = NULL;
+  g_autofree gchar *name = NULL;
+
+  g_return_val_if_fail (IDE_IS_CONFIGURATION (self), NULL);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  id = g_strdup_printf ("%s %d", self->id, next_counter++);
+  name = g_strdup_printf ("%s Copy", self->display_name);
+
+  copy = g_object_new (IDE_TYPE_CONFIGURATION,
+                       "config-opts", self->config_opts,
+                       "context", context,
+                       "device-id", self->device_id,
+                       "display-name", name,
+                       "id", id,
+                       "prefix", self->prefix,
+                       "runtime-id", self->runtime_id,
+                       NULL);
+
+  copy->environment = ide_environment_copy (self->environment);
+
+  return copy;
+}
diff --git a/libide/ide-configuration.h b/libide/ide-configuration.h
new file mode 100644
index 0000000..e4d0596
--- /dev/null
+++ b/libide/ide-configuration.h
@@ -0,0 +1,79 @@
+/* ide-configuration.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_CONFIGURATION_H
+#define IDE_CONFIGURATION_H
+
+#include <gio/gio.h>
+
+#include "ide-object.h"
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CONFIGURATION (ide_configuration_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeConfiguration, ide_configuration, IDE, CONFIGURATION, IdeObject)
+
+IdeConfiguration    *ide_configuration_new              (IdeContext        *context,
+                                                         const gchar       *id,
+                                                         const gchar       *device_id,
+                                                         const gchar       *runtime_id);
+const gchar         *ide_configuration_get_id           (IdeConfiguration  *self);
+const gchar         *ide_configuration_get_runtime_id   (IdeConfiguration  *self);
+void                 ide_configuration_set_runtime_id   (IdeConfiguration  *self,
+                                                         const gchar       *runtime_id);
+const gchar         *ide_configuration_get_device_id    (IdeConfiguration  *self);
+void                 ide_configuration_set_device_id    (IdeConfiguration  *self,
+                                                         const gchar       *device_id);
+IdeDevice           *ide_configuration_get_device       (IdeConfiguration  *self);
+void                 ide_configuration_set_device       (IdeConfiguration  *self,
+                                                         IdeDevice         *device);
+gboolean             ide_configuration_get_dirty        (IdeConfiguration  *self);
+void                 ide_configuration_set_dirty        (IdeConfiguration  *self,
+                                                         gboolean           dirty);
+const gchar         *ide_configuration_get_display_name (IdeConfiguration  *self);
+void                 ide_configuration_set_display_name (IdeConfiguration  *self,
+                                                         const gchar       *display_name);
+IdeRuntime          *ide_configuration_get_runtime      (IdeConfiguration  *self);
+void                 ide_configuration_set_runtime      (IdeConfiguration  *self,
+                                                         IdeRuntime        *runtime);
+gchar              **ide_configuration_get_environ      (IdeConfiguration  *self);
+const gchar         *ide_configuration_getenv           (IdeConfiguration  *self,
+                                                         const gchar       *key);
+void                 ide_configuration_setenv           (IdeConfiguration  *self,
+                                                         const gchar       *key,
+                                                         const gchar       *value);
+gboolean             ide_configuration_get_debug        (IdeConfiguration  *self);
+void                 ide_configuration_set_debug        (IdeConfiguration  *self,
+                                                         gboolean           debug);
+const gchar         *ide_configuration_get_prefix       (IdeConfiguration  *self);
+void                 ide_configuration_set_prefix       (IdeConfiguration  *self,
+                                                         const gchar       *prefix);
+const gchar         *ide_configuration_get_config_opts  (IdeConfiguration  *self);
+void                 ide_configuration_set_config_opts  (IdeConfiguration  *self,
+                                                         const gchar       *config_opts);
+gint                 ide_configuration_get_parallelism  (IdeConfiguration  *self);
+void                 ide_configuration_set_parallelism  (IdeConfiguration  *self,
+                                                         gint               parallelism);
+IdeEnvironment      *ide_configuration_get_environment  (IdeConfiguration  *self);
+IdeConfiguration    *ide_configuration_duplicate        (IdeConfiguration  *self);
+
+G_END_DECLS
+
+#endif /* IDE_CONFIGURATION_H */
diff --git a/libide/ide-context.c b/libide/ide-context.c
index 5196892..b6c5edd 100644
--- a/libide/ide-context.c
+++ b/libide/ide-context.c
@@ -27,6 +27,7 @@
 #include "ide-buffer-manager.h"
 #include "ide-buffer.h"
 #include "ide-build-system.h"
+#include "ide-configuration-manager.h"
 #include "ide-context.h"
 #include "ide-debug.h"
 #include "ide-device-manager.h"
@@ -57,6 +58,7 @@ struct _IdeContext
   IdeBackForwardList       *back_forward_list;
   IdeBufferManager         *buffer_manager;
   IdeBuildSystem           *build_system;
+  IdeConfigurationManager  *configuration_manager;
   IdeDeviceManager         *device_manager;
   IdeDoap                  *doap;
   GtkRecentManager         *recent_manager;
@@ -91,6 +93,7 @@ enum {
   PROP_BACK_FORWARD_LIST,
   PROP_BUFFER_MANAGER,
   PROP_BUILD_SYSTEM,
+  PROP_CONFIGURATION_MANAGER,
   PROP_DEVICE_MANAGER,
   PROP_PROJECT_FILE,
   PROP_PROJECT,
@@ -531,6 +534,7 @@ ide_context_finalize (GObject *object)
   g_clear_pointer (&self->recent_projects_path, g_free);
 
   g_clear_object (&self->build_system);
+  g_clear_object (&self->configuration_manager);
   g_clear_object (&self->device_manager);
   g_clear_object (&self->doap);
   g_clear_object (&self->project);
@@ -570,6 +574,10 @@ ide_context_get_property (GObject    *object,
       g_value_set_object (value, ide_context_get_build_system (self));
       break;
 
+    case PROP_CONFIGURATION_MANAGER:
+      g_value_set_object (value, ide_context_get_configuration_manager (self));
+      break;
+
     case PROP_DEVICE_MANAGER:
       g_value_set_object (value, ide_context_get_device_manager (self));
       break;
@@ -665,6 +673,13 @@ ide_context_class_init (IdeContextClass *klass)
                          IDE_TYPE_BUILD_SYSTEM,
                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
+  properties [PROP_CONFIGURATION_MANAGER] =
+    g_param_spec_object ("configuration-manager",
+                         "Configuration Manager",
+                         "The configuration manager for the context",
+                         IDE_TYPE_CONFIGURATION_MANAGER,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
   properties [PROP_DEVICE_MANAGER] =
     g_param_spec_object ("device-manager",
                          "Device Manager",
@@ -782,6 +797,10 @@ ide_context_init (IdeContext *self)
                                        "context", self,
                                        NULL);
 
+  self->configuration_manager = g_object_new (IDE_TYPE_CONFIGURATION_MANAGER,
+                                              "context", self,
+                                              NULL);
+
   self->project = g_object_new (IDE_TYPE_PROJECT,
                                 "context", self,
                                 NULL);
@@ -1338,6 +1357,44 @@ ide_context_init_search_engine (gpointer             source_object,
 }
 
 static void
+ide_context_init_configuration_manager_cb (GObject      *object,
+                                           GAsyncResult *result,
+                                           gpointer      user_data)
+{
+  GAsyncInitable *initable = (GAsyncInitable *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (G_IS_ASYNC_INITABLE (initable));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  if (!g_async_initable_init_finish (initable, result, &error))
+    g_task_return_error (task, error);
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_context_init_configuration_manager (gpointer             source_object,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             user_data)
+{
+  IdeContext *self = source_object;
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_CONTEXT (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_async_initable_init_async (G_ASYNC_INITABLE (self->configuration_manager),
+                               G_PRIORITY_DEFAULT,
+                               cancellable,
+                               ide_context_init_configuration_manager_cb,
+                               g_object_ref (task));
+}
+
+static void
 ide_context_init_loaded (gpointer             source_object,
                          GCancellable        *cancellable,
                          GAsyncReadyCallback  callback,
@@ -1381,6 +1438,7 @@ ide_context_init_async (GAsyncInitable      *initable,
                         ide_context_init_unsaved_files,
                         ide_context_init_add_recent,
                         ide_context_init_search_engine,
+                        ide_context_init_configuration_manager,
                         ide_context_init_loaded,
                         NULL);
 }
@@ -1501,6 +1559,50 @@ ide_context_unload_buffer_manager (gpointer             source_object,
 }
 
 static void
+ide_context_unload__configuration_manager_save_cb (GObject      *object,
+                                                   GAsyncResult *result,
+                                                   gpointer      user_data)
+{
+  IdeConfigurationManager *manager = (IdeConfigurationManager *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
+  g_assert (G_IS_TASK (task));
+
+  /* unfortunate if this happens, but not much we can do */
+  if (!ide_configuration_manager_save_finish (manager, result, &error))
+    g_warning ("%s", error->message);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_context_unload_configuration_manager (gpointer             source_object,
+                                          GCancellable        *cancellable,
+                                          GAsyncReadyCallback  callback,
+                                          gpointer             user_data)
+{
+  IdeContext *self = source_object;
+  g_autoptr(GTask) task = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CONTEXT (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+  g_assert (IDE_IS_CONFIGURATION_MANAGER (self->configuration_manager));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  ide_configuration_manager_save_async (self->configuration_manager,
+                                        cancellable,
+                                        ide_context_unload__configuration_manager_save_cb,
+                                        g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+static void
 ide_context_unload__back_forward_list_save_cb (GObject      *object,
                                                GAsyncResult *result,
                                                gpointer      user_data)
@@ -1618,10 +1720,13 @@ ide_context_do_unload_locked (IdeContext *self)
   task = self->delayed_unload_task;
   self->delayed_unload_task = NULL;
 
+  g_clear_object (&self->device_manager);
+
   ide_async_helper_run (self,
                         g_task_get_cancellable (task),
                         ide_context_unload_cb,
                         g_object_ref (task),
+                        ide_context_unload_configuration_manager,
                         ide_context_unload_back_forward_list,
                         ide_context_unload_buffer_manager,
                         ide_context_unload_unsaved_files,
diff --git a/libide/ide-context.h b/libide/ide-context.h
index ef249ee..0742e48 100644
--- a/libide/ide-context.h
+++ b/libide/ide-context.h
@@ -74,6 +74,7 @@ void                      ide_context_hold                  (IdeContext
 void                      ide_context_hold_for_object       (IdeContext           *self,
                                                              gpointer              instance);
 void                      ide_context_release               (IdeContext           *self);
+IdeConfigurationManager  *ide_context_get_configuration_manager (IdeContext           *self);
 
 G_END_DECLS
 


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