ide-buildconfig-configuration-provider.c 25.8 KB
Newer Older
1 2
/* ide-buildconfig-configuration-provider.c
 *
3
 * Copyright © 2016 Matthew Leeds <mleeds@redhat.com>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * 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-buildconfig-configuration-provider"

21 22
#include "config.h"

23
#include <dazzle.h>
24
#include <gio/gio.h>
25 26
#include <glib/gi18n.h>
#include <string.h>
27 28 29 30

#include "ide-context.h"
#include "ide-debug.h"

31 32
#include "buildconfig/ide-buildconfig-configuration.h"
#include "buildconfig/ide-buildconfig-configuration-provider.h"
33 34 35
#include "config/ide-configuration-manager.h"
#include "config/ide-configuration-provider.h"
#include "config/ide-configuration.h"
36 37
#include "buildsystem/ide-environment.h"
#include "vcs/ide-vcs.h"
38
#include "threading/ide-task.h"
39

40
#define DOT_BUILDCONFIG ".buildconfig"
41 42 43

struct _IdeBuildconfigConfigurationProvider
{
44
  IdeObject  parent_instance;
45

46 47 48 49 50 51 52 53 54 55 56 57
  /*
   * 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;
58

59 60 61 62 63 64 65 66 67 68
  /*
   * 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;
69 70
};

71 72 73 74 75 76 77
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;
78

79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
  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, "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;
231
  g_autoptr(IdeTask) task = NULL;
232 233 234 235 236 237 238 239 240 241
  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));

242 243 244
  task = ide_task_new (self, cancellable, callback, user_data);
  ide_task_set_source_tag (task, ide_buildconfig_configuration_provider_load_async);
  ide_task_set_priority (task, G_PRIORITY_LOW);
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294

  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,
                           "display-name", "Default",
                           "id", "default",
                           "runtime-id", "host",
                           NULL);
  ide_configuration_set_dirty (fallback, FALSE);
  ide_configuration_provider_emit_added (provider, fallback);

complete:
295
  ide_task_return_boolean (task, TRUE);
296 297 298 299 300 301 302 303
}

static gboolean
ide_buildconfig_configuration_provider_load_finish (IdeConfigurationProvider  *provider,
                                                    GAsyncResult              *result,
                                                    GError                   **error)
{
  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (provider));
304 305
  g_assert (IDE_IS_TASK (result));
  g_assert (ide_task_is_valid (IDE_TASK (result), provider));
306

307
  return ide_task_propagate_boolean (IDE_TASK (result), error);
308
}
309 310 311 312 313 314

static void
ide_buildconfig_configuration_provider_save_cb (GObject      *object,
                                                GAsyncResult *result,
                                                gpointer      user_data)
{
315
  GFile *file = (GFile *)object;
316
  g_autoptr(IdeTask) task = user_data;
317
  g_autoptr(GError) error = NULL;
318 319 320

  g_assert (G_IS_FILE (file));
  g_assert (G_IS_ASYNC_RESULT (result));
321
  g_assert (IDE_IS_TASK (task));
322 323

  if (!g_file_replace_contents_finish (file, result, NULL, &error))
324
    ide_task_return_error (task, g_steal_pointer (&error));
325
  else
326
    ide_task_return_boolean (task, TRUE);
327 328
}

329
static void
330 331 332 333 334 335 336
ide_buildconfig_configuration_provider_save_async (IdeConfigurationProvider *provider,
                                                   GCancellable             *cancellable,
                                                   GAsyncReadyCallback       callback,
                                                   gpointer                  user_data)
{
  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
  g_autoptr(GHashTable) group_names = NULL;
337
  g_autoptr(IdeTask) task = NULL;
338 339
  g_autoptr(GFile) file = NULL;
  g_autoptr(GBytes) bytes = NULL;
340
  g_autoptr(GError) error = NULL;
341 342 343 344
  g_auto(GStrv) groups = NULL;
  g_autofree gchar *path = NULL;
  g_autofree gchar *data = NULL;
  IdeConfigurationManager *manager;
345
  IdeContext *context;
346 347
  gboolean dirty = FALSE;
  gsize length = 0;
348 349 350

  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
351
  g_assert (self->key_file != NULL);
352

353 354 355
  task = ide_task_new (self, cancellable, callback, user_data);
  ide_task_set_source_tag (task, ide_buildconfig_configuration_provider_save_async);
  ide_task_set_priority (task, G_PRIORITY_LOW);
356 357 358 359 360 361 362 363 364

  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);
    }
365

366
  if (!dirty)
367
    {
368
      ide_task_return_boolean (task, TRUE);
369 370 371
      return;
    }

372 373 374 375
  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);
376 377

  /*
378 379
   * We keep the GKeyFile around from when we parsed .buildconfig, so that we
   * can try to preserve comments and such when writing back.
380
   *
381 382
   * 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.
383 384 385 386
   */

  group_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);

387
  for (guint i = 0; i < self->configs->len; i++)
388
    {
389 390 391 392
      IdeConfiguration *config = g_ptr_array_index (self->configs, i);
      g_autofree gchar *env_group = NULL;
      const gchar *config_id;
      IdeEnvironment *env;
393 394
      guint n_items;

395 396 397 398 399 400
      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);
401 402 403 404 405

      /*
       * Track our known group names, so we can remove missing names after
       * we've updated the GKeyFile.
       */
406 407
      g_hash_table_insert (group_names, g_strdup (config_id), NULL);
      g_hash_table_insert (group_names, g_strdup (env_group), NULL);
408 409

#define PERSIST_STRING_KEY(key, getter) \
410 411 412 413 414 415 416 417
      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

418 419 420
      PERSIST_STRING_KEY ("name", get_display_name);
      PERSIST_STRING_KEY ("runtime", get_runtime_id);
      PERSIST_STRING_KEY ("config-opts", get_config_opts);
421
      PERSIST_STRING_KEY ("run-opts", get_run_opts);
422 423
      PERSIST_STRING_KEY ("prefix", get_prefix);
      PERSIST_STRING_KEY ("app-id", get_app_id);
424 425 426
      PERSIST_STRV_KEY ("postbuild", get_postbuild);
      PERSIST_STRV_KEY ("prebuild", get_prebuild);

427
#undef PERSIST_STRING_KEY
428
#undef PERSIST_STRV_KEY
429

430 431
      if (config == ide_configuration_manager_get_current (manager))
        g_key_file_set_boolean (self->key_file, config_id, "default", TRUE);
432
      else
433
        g_key_file_remove_key (self->key_file, config_id, "default", NULL);
434

435
      env = ide_configuration_get_environment (config);
436 437 438 439 440 441

      /*
       * 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.
       */
442
      if (g_key_file_has_group (self->key_file, env_group))
443 444 445
        {
          g_auto(GStrv) keys = NULL;

446
          if (NULL != (keys = g_key_file_get_keys (self->key_file, env_group, NULL, NULL)))
447
            {
448
              for (guint j = 0; keys [j]; j++)
449
                {
450 451
                  if (!ide_environment_getenv (env, keys [j]))
                    g_key_file_remove_key (self->key_file, env_group, keys [j], NULL);
452 453 454 455
                }
            }
        }

456
      n_items = g_list_model_get_n_items (G_LIST_MODEL (env));
457

458
      for (guint j = 0; j < n_items; j++)
459 460 461 462 463
        {
          g_autoptr(IdeEnvironmentVariable) var = NULL;
          const gchar *key;
          const gchar *value;

464
          var = g_list_model_get_item (G_LIST_MODEL (env), j);
465 466 467
          key = ide_environment_variable_get_key (var);
          value = ide_environment_variable_get_value (var);

468
          if (!dzl_str_empty0 (key))
469
            g_key_file_set_string (self->key_file, env_group, key, value ?: "");
470
        }
471 472

      ide_configuration_set_dirty (config, FALSE);
473 474
    }

475
  /* Now truncate any old groups in the keyfile. */
476 477
  if (NULL != (groups = g_key_file_get_groups (self->key_file, NULL)))
    {
478
      for (guint i = 0; groups [i]; i++)
479 480 481 482 483 484
        {
          if (!g_hash_table_contains (group_names, groups [i]))
            g_key_file_remove_group (self->key_file, groups [i], NULL);
        }
    }

485
  if (!(data = g_key_file_to_data (self->key_file, &length, &error)))
486
    {
487
      ide_task_return_error (task, g_steal_pointer (&error));
488
      return;
489 490
    }

491 492 493 494 495 496
  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);
497
      ide_task_return_boolean (task, TRUE);
498 499 500 501
      return;
    }

  bytes = g_bytes_new_take (g_steal_pointer (&data), length);
502 503 504 505 506 507 508 509

  g_file_replace_contents_bytes_async (file,
                                       bytes,
                                       NULL,
                                       FALSE,
                                       G_FILE_CREATE_NONE,
                                       cancellable,
                                       ide_buildconfig_configuration_provider_save_cb,
510
                                       g_steal_pointer (&task));
511 512
}

513
static gboolean
514 515 516 517
ide_buildconfig_configuration_provider_save_finish (IdeConfigurationProvider  *provider,
                                                    GAsyncResult              *result,
                                                    GError                   **error)
{
518
  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (provider));
519 520
  g_assert (IDE_IS_TASK (result));
  g_assert (ide_task_is_valid (IDE_TASK (result), provider));
521

522
  return ide_task_propagate_boolean (IDE_TASK (result), error);
523 524 525
}

static void
526 527
ide_buildconfig_configuration_provider_delete (IdeConfigurationProvider *provider,
                                               IdeConfiguration         *config)
528
{
529 530 531 532 533
  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
  g_autoptr(IdeConfiguration) hold = NULL;
  g_autofree gchar *env = NULL;
  const gchar *config_id;
  gboolean had_group;
534 535

  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
536 537 538
  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION (config));
  g_assert (self->key_file != NULL);
  g_assert (self->configs->len > 0);
539

540
  hold = g_object_ref (config);
541

542
  if (!g_ptr_array_remove (self->configs, hold))
543
    {
544 545 546
      g_critical ("No such configuration %s",
                  ide_configuration_get_id (hold));
      return;
547 548
    }

549 550 551 552 553
  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);
554

555
  self->key_file_dirty = had_group;
556

557 558 559 560 561 562 563 564 565 566
  /*
   * 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)
567
    {
568 569 570 571 572 573 574 575 576 577
      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,
                                 "display-name", "Default",
                                 "id", "default",
                                 "runtime-id", "host",
                                 NULL);
578

579 580 581 582 583 584
      /*
       * 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);
585
    }
586 587

  ide_configuration_provider_emit_removed (provider, hold);
588 589
}

590 591 592
static void
ide_buildconfig_configuration_provider_duplicate (IdeConfigurationProvider *provider,
                                                  IdeConfiguration         *config)
593
{
594 595 596 597 598 599 600 601
  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;
602
  IdeContext *context;
603
  guint n_pspecs = 0;
604 605

  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
606 607
  g_assert (IDE_IS_CONFIGURATION (config));
  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION (config));
608

609 610
  context = ide_object_get_context (IDE_OBJECT (self));
  g_assert (IDE_IS_CONTEXT (context));
611

612 613
  manager = ide_context_get_configuration_manager (context);
  g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
614

615 616
  config_id = ide_configuration_get_id (config);
  g_return_if_fail (config_id != NULL);
617

618 619
  new_config_id = get_next_id (manager, config_id);
  g_return_if_fail (new_config_id != NULL);
620

621 622 623
  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);
624

625 626 627 628 629
  new_config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIGURATION,
                             "id", new_config_id,
                             "context", context,
                             "display-name", new_name,
                             NULL);
630

631
  pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (new_config), &n_pspecs);
632

633 634 635
  for (guint i = 0; i < n_pspecs; i++)
    {
      GParamSpec *pspec = pspecs[i];
636

637 638 639 640 641 642
      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;
643 644


645 646 647 648
      if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
          (pspec->flags & G_PARAM_CONSTRUCT_ONLY) == 0)
        {
          GValue value = G_VALUE_INIT;
649

650 651 652 653
          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);
        }
654 655
    }

656 657
  ide_configuration_set_dirty (new_config, TRUE);
  ide_configuration_provider_emit_added (provider, new_config);
658 659 660
}

static void
661
ide_buildconfig_configuration_provider_unload (IdeConfigurationProvider *provider)
662
{
663
  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
664
  g_autoptr(GPtrArray) configs = NULL;
665 666

  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
667
  g_assert (self->configs != NULL);
668

669 670
  configs = g_steal_pointer (&self->configs);
  self->configs = g_ptr_array_new_with_free_func (g_object_unref);
671

672
  for (guint i = 0; i < configs->len; i++)
673
    {
674 675
      IdeConfiguration *config = g_ptr_array_index (configs, i);
      ide_configuration_provider_emit_removed (provider, config);
676 677 678 679
    }
}

static void
680 681
ide_buildconfig_configuration_provider_added (IdeConfigurationProvider *provider,
                                              IdeConfiguration         *config)
682
{
683
  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;
684 685

  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
686 687
  g_assert (IDE_IS_CONFIGURATION (config));
  g_assert (self->configs != NULL);
688

689
  g_ptr_array_add (self->configs, g_object_ref (config));
690 691 692
}

static void
693 694
ide_buildconfig_configuration_provider_removed (IdeConfigurationProvider *provider,
                                                IdeConfiguration         *config)
695 696 697 698
{
  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)provider;

  g_assert (IDE_IS_BUILDCONFIG_CONFIGURATION_PROVIDER (self));
699 700
  g_assert (IDE_IS_CONFIGURATION (config));
  g_assert (self->configs != NULL);
701

702 703
  /* It's possible we already removed it by now */
  g_ptr_array_remove (self->configs, config);
704 705
}

706
static void
707
configuration_provider_iface_init (IdeConfigurationProviderInterface *iface)
708
{
709 710 711 712 713 714 715 716 717
  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;
718 719
}

720 721 722 723 724 725 726 727
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)
728
{
729
  IdeBuildconfigConfigurationProvider *self = (IdeBuildconfigConfigurationProvider *)object;
730

731 732
  g_clear_pointer (&self->configs, g_ptr_array_unref);
  g_clear_pointer (&self->key_file, g_key_file_free);
733

734
  G_OBJECT_CLASS (ide_buildconfig_configuration_provider_parent_class)->finalize (object);
735 736
}

737 738 739
static void
ide_buildconfig_configuration_provider_class_init (IdeBuildconfigConfigurationProviderClass *klass)
{
740
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
741

742
  object_class->finalize = ide_buildconfig_configuration_provider_finalize;
743 744 745
}

static void
746
ide_buildconfig_configuration_provider_init (IdeBuildconfigConfigurationProvider *self)
747
{
748
  self->configs = g_ptr_array_new_with_free_func (g_object_unref);
749
}