1 /* ide-buildconfig-config-provider.c
2  *
3  * Copyright 2016 Matthew Leeds <mleeds@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-buildconfig-config-provider"
22 
23 #include "config.h"
24 
25 #include <dazzle.h>
26 #include <gio/gio.h>
27 #include <glib/gi18n.h>
28 #include <string.h>
29 
30 #include <libide-core.h>
31 #include <libide-foundry.h>
32 #include <libide-threading.h>
33 
34 #include "ide-buildconfig-config.h"
35 #include "ide-buildconfig-config-provider.h"
36 
37 #define DOT_BUILDCONFIG ".buildconfig"
38 
39 struct _IdeBuildconfigConfigProvider
40 {
41   IdeObject  parent_instance;
42 
43   /*
44    * A GPtrArray of IdeBuildconfigConfiguration that have been registered.
45    * We append/remove to/from this array in our default signal handler for
46    * the ::added and ::removed signals.
47    */
48   GPtrArray *configs;
49 
50   /*
51    * The GKeyFile that was parsed from disk. We keep this around so that
52    * we can persist the changes back without destroying comments.
53    */
54   GKeyFile *key_file;
55 
56   /*
57    * If we removed items from the keyfile, we need to know that so that
58    * we persist it back to disk. We only persist back to disk if this bit
59    * is set or if any of our registered configs are "dirty".
60    *
61    * We try hard to avoid writing .buildconfig files unless we know the
62    * user did something to change a config. Otherwise we would liter
63    * everyone's projects with .buildconfig files.
64    */
65   guint key_file_dirty : 1;
66 };
67 
68 static gchar *
69 gen_next_id (const gchar *id)
70 {
71   g_auto(GStrv) parts = g_strsplit (id, "-", 0);
72   guint len = g_strv_length (parts);
73   const gchar *end;
74   guint64 n64;
75 
76   if (len == 0)
77     goto add_suffix;
78 
79   end = parts[len - 1];
80 
81   n64 = g_ascii_strtoull (end, (gchar **)&end, 10);
82   if (n64 == 0 || n64 == G_MAXUINT64 || *end != 0)
83     goto add_suffix;
84 
85   g_free (g_steal_pointer (&parts[len -1]));
86   parts[len -1] = g_strdup_printf ("%"G_GUINT64_FORMAT, n64+1);
87   return g_strjoinv ("-", parts);
88 
89 add_suffix:
90   return g_strdup_printf ("%s-2", id);
91 }
92 
93 static gchar *
94 get_next_id (IdeConfigManager *manager,
95              const gchar             *id)
96 {
97   g_autoptr(GPtrArray) tries = NULL;
98 
99   g_assert (IDE_IS_CONFIG_MANAGER (manager));
100 
101   tries = g_ptr_array_new_with_free_func (g_free);
102 
103   while (ide_config_manager_get_config (manager, id))
104     {
105       g_autofree gchar *next = gen_next_id (id);
106       id = next;
107       g_ptr_array_add (tries, g_steal_pointer (&next));
108     }
109 
110   return g_strdup (id);
111 }
112 
113 static void
114 load_string (IdeConfig *config,
115              GKeyFile         *key_file,
116              const gchar      *group,
117              const gchar      *key,
118              const gchar      *property)
119 {
120   g_assert (IDE_IS_CONFIG (config));
121   g_assert (key_file != NULL);
122   g_assert (group != NULL);
123   g_assert (key != NULL);
124 
125   if (g_key_file_has_key (key_file, group, key, NULL))
126     {
127       g_auto(GValue) value = G_VALUE_INIT;
128 
129       g_value_init (&value, G_TYPE_STRING);
130       g_value_take_string (&value, g_key_file_get_string (key_file, group, key, NULL));
131       g_object_set_property (G_OBJECT (config), property, &value);
132     }
133 }
134 
135 static void
136 load_strv (IdeConfig *config,
137            GKeyFile         *key_file,
138            const gchar      *group,
139            const gchar      *key,
140            const gchar      *property)
141 {
142   g_assert (IDE_IS_CONFIG (config));
143   g_assert (key_file != NULL);
144   g_assert (group != NULL);
145   g_assert (key != NULL);
146 
147   if (g_key_file_has_key (key_file, group, key, NULL))
148     {
149       g_auto(GStrv) strv = NULL;
150       g_auto(GValue) value = G_VALUE_INIT;
151 
152       strv = g_key_file_get_string_list (key_file, group, key, NULL, NULL);
153       g_value_init (&value, G_TYPE_STRV);
154       g_value_take_boxed (&value, g_steal_pointer (&strv));
155       g_object_set_property (G_OBJECT (config), property, &value);
156     }
157 }
158 
159 static void
160 load_environ (IdeConfig *config,
161               GKeyFile         *key_file,
162               const gchar      *group)
163 {
164   IdeEnvironment *environment;
165   g_auto(GStrv) keys = NULL;
166   gsize len = 0;
167 
168   g_assert (IDE_IS_CONFIG (config));
169   g_assert (key_file != NULL);
170   g_assert (group != NULL);
171 
172   environment = ide_config_get_environment (config);
173   keys = g_key_file_get_keys (key_file, group, &len, NULL);
174 
175   for (gsize i = 0; i < len; i++)
176     {
177       g_autofree gchar *value = NULL;
178 
179       value = g_key_file_get_string (key_file, group, keys[i], NULL);
180       if (value != NULL)
181         ide_environment_setenv (environment, keys [i], value);
182     }
183 }
184 
185 static IdeConfig *
186 ide_buildconfig_config_provider_create (IdeBuildconfigConfigProvider *self,
187                                                const gchar                         *config_id)
188 {
189   g_autoptr(IdeConfig) config = NULL;
190   g_autofree gchar *env_group = NULL;
191 
192   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self));
193   g_assert (self->key_file != NULL);
194   g_assert (config_id != NULL);
195 
196   config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIG,
197                          "id", config_id,
198                          "parent", self,
199                          NULL);
200 
201   load_string (config, self->key_file, config_id, "config-opts", "config-opts");
202   load_string (config, self->key_file, config_id, "name", "display-name");
203   load_string (config, self->key_file, config_id, "run-opts", "run-opts");
204   load_string (config, self->key_file, config_id, "runtime", "runtime-id");
205   load_string (config, self->key_file, config_id, "toolchain", "toolchain-id");
206   load_string (config, self->key_file, config_id, "prefix", "prefix");
207   load_string (config, self->key_file, config_id, "app-id", "app-id");
208   load_strv (config, self->key_file, config_id, "prebuild", "prebuild");
209   load_strv (config, self->key_file, config_id, "postbuild", "postbuild");
210 
211   if (g_key_file_has_key (self->key_file, config_id, "builddir", NULL))
212     {
213       if (g_key_file_get_boolean (self->key_file, config_id, "builddir", NULL))
214         ide_config_set_locality (config, IDE_BUILD_LOCALITY_OUT_OF_TREE);
215       else
216         ide_config_set_locality (config, IDE_BUILD_LOCALITY_IN_TREE);
217     }
218 
219   env_group = g_strdup_printf ("%s.environment", config_id);
220   if (g_key_file_has_group (self->key_file, env_group))
221     load_environ (config, self->key_file, env_group);
222 
223   return g_steal_pointer (&config);
224 }
225 
226 static void
227 ide_buildconfig_config_provider_load_async (IdeConfigProvider *provider,
228                                                    GCancellable             *cancellable,
229                                                    GAsyncReadyCallback       callback,
230                                                    gpointer                  user_data)
231 {
232   IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider;
233   g_autoptr(IdeConfig) fallback = NULL;
234   g_autoptr(IdeTask) task = NULL;
235   g_autoptr(GError) error = NULL;
236   g_autofree gchar *path = NULL;
237   g_auto(GStrv) groups = NULL;
238   IdeContext *context;
239   gsize len;
240 
241   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self));
242   g_assert (self->key_file == NULL);
243   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
244 
245   task = ide_task_new (self, cancellable, callback, user_data);
246   ide_task_set_source_tag (task, ide_buildconfig_config_provider_load_async);
247   ide_task_set_priority (task, G_PRIORITY_LOW);
248 
249   self->key_file = g_key_file_new ();
250 
251   /*
252    * We could do this in a thread, but it's not really worth it. We want these
253    * configs loaded ASAP, and nothing can really progress until it's loaded
254    * anyway.
255    */
256 
257   context = ide_object_get_context (IDE_OBJECT (self));
258   path = ide_context_build_filename (context, DOT_BUILDCONFIG, NULL);
259   if (!g_file_test (path, G_FILE_TEST_IS_REGULAR))
260     goto add_default;
261 
262   if (!g_key_file_load_from_file (self->key_file, path, G_KEY_FILE_KEEP_COMMENTS, &error))
263     {
264       g_warning ("Failed to load .buildconfig: %s", error->message);
265       goto add_default;
266     }
267 
268   groups = g_key_file_get_groups (self->key_file, &len);
269 
270   for (gsize i = 0; i < len; i++)
271     {
272       g_autoptr(IdeConfig) config = NULL;
273       const gchar *group = groups[i];
274 
275       if (strchr (group, '.') != NULL)
276         continue;
277 
278       config = ide_buildconfig_config_provider_create (self, group);
279       ide_config_set_dirty (config, FALSE);
280       ide_config_provider_emit_added (provider, config);
281     }
282 
283   if (self->configs->len > 0)
284     goto complete;
285 
286 add_default:
287   /* "Default" is not translated because .buildconfig can be checked in */
288   fallback = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIG,
289                            "display-name", "Default",
290                            "id", "default",
291                            "parent", self,
292                            "runtime-id", "host",
293                            "toolchain-id", "default",
294                            NULL);
295   ide_config_set_dirty (fallback, FALSE);
296   ide_config_provider_emit_added (provider, fallback);
297 
298 complete:
299   ide_task_return_boolean (task, TRUE);
300 }
301 
302 static gboolean
303 ide_buildconfig_config_provider_load_finish (IdeConfigProvider  *provider,
304                                                     GAsyncResult              *result,
305                                                     GError                   **error)
306 {
307   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (provider));
308   g_assert (IDE_IS_TASK (result));
309   g_assert (ide_task_is_valid (IDE_TASK (result), provider));
310 
311   return ide_task_propagate_boolean (IDE_TASK (result), error);
312 }
313 
314 static void
315 ide_buildconfig_config_provider_save_cb (GObject      *object,
316                                                 GAsyncResult *result,
317                                                 gpointer      user_data)
318 {
319   GFile *file = (GFile *)object;
320   g_autoptr(IdeTask) task = user_data;
321   g_autoptr(GError) error = NULL;
322 
323   g_assert (G_IS_FILE (file));
324   g_assert (G_IS_ASYNC_RESULT (result));
325   g_assert (IDE_IS_TASK (task));
326 
327   if (!g_file_replace_contents_finish (file, result, NULL, &error))
328     ide_task_return_error (task, g_steal_pointer (&error));
329   else
330     ide_task_return_boolean (task, TRUE);
331 }
332 
333 static void
334 ide_buildconfig_config_provider_save_async (IdeConfigProvider   *provider,
335                                             GCancellable        *cancellable,
336                                             GAsyncReadyCallback  callback,
337                                             gpointer             user_data)
338 {
339   IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider;
340   g_autoptr(GHashTable) group_names = NULL;
341   g_autoptr(IdeTask) task = NULL;
342   g_autoptr(GFile) file = NULL;
343   g_autoptr(GBytes) bytes = NULL;
344   g_autoptr(GError) error = NULL;
345   g_auto(GStrv) groups = NULL;
346   g_autofree gchar *path = NULL;
347   g_autofree gchar *data = NULL;
348   IdeConfigManager *manager;
349   IdeContext *context;
350   gboolean dirty = FALSE;
351   gsize length = 0;
352 
353   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self));
354   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
355   g_assert (self->key_file != NULL);
356 
357   task = ide_task_new (self, cancellable, callback, user_data);
358   ide_task_set_source_tag (task, ide_buildconfig_config_provider_save_async);
359   ide_task_set_priority (task, G_PRIORITY_LOW);
360 
361   dirty = self->key_file_dirty;
362 
363   /* If no configs are dirty, short circuit to avoid writing any files to disk. */
364   for (guint i = 0; !dirty && i < self->configs->len; i++)
365     {
366       IdeConfig *config = g_ptr_array_index (self->configs, i);
367       dirty |= ide_config_get_dirty (config);
368     }
369 
370   if (!dirty)
371     {
372       ide_task_return_boolean (task, TRUE);
373       return;
374     }
375 
376   context = ide_object_get_context (IDE_OBJECT (self));
377   manager = ide_config_manager_from_context (context);
378   path = ide_context_build_filename (context, DOT_BUILDCONFIG, NULL);
379   file = g_file_new_for_path (path);
380 
381   /*
382    * We keep the GKeyFile around from when we parsed .buildconfig, so that we
383    * can try to preserve comments and such when writing back.
384    *
385    * This means that we need to fill in all our known configuration sections,
386    * and then remove any that were removed since we were parsed it last.
387    */
388 
389   group_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
390 
391   for (guint i = 0; i < self->configs->len; i++)
392     {
393       IdeConfig *config = g_ptr_array_index (self->configs, i);
394       g_autofree gchar *env_group = NULL;
395       const gchar *config_id;
396       IdeEnvironment *env;
397       guint n_items;
398 
399       if (!ide_config_get_dirty (config))
400         continue;
401 
402       config_id = ide_config_get_id (config);
403       env_group = g_strdup_printf ("%s.environment", config_id);
404 
405       /*
406        * Track our known group names, so we can remove missing names after
407        * we've updated the GKeyFile.
408        */
409       g_hash_table_insert (group_names, g_strdup (config_id), NULL);
410       g_hash_table_insert (group_names, g_strdup (env_group), NULL);
411 
412 #define PERSIST_STRING_KEY(key, getter) \
413       g_key_file_set_string (self->key_file, config_id, key, \
414                              ide_config_##getter (config) ?: "")
415 #define PERSIST_STRV_KEY(key, getter) G_STMT_START { \
416       const gchar * const *val = ide_buildconfig_config_##getter (IDE_BUILDCONFIG_CONFIG (config)); \
417       gsize vlen = val ? g_strv_length ((gchar **)val) : 0; \
418       g_key_file_set_string_list (self->key_file, config_id, key, val, vlen); \
419 } G_STMT_END
420 
421       PERSIST_STRING_KEY ("name", get_display_name);
422       PERSIST_STRING_KEY ("runtime", get_runtime_id);
423       PERSIST_STRING_KEY ("toolchain", get_toolchain_id);
424       PERSIST_STRING_KEY ("config-opts", get_config_opts);
425       PERSIST_STRING_KEY ("run-opts", get_run_opts);
426       PERSIST_STRING_KEY ("prefix", get_prefix);
427       PERSIST_STRING_KEY ("app-id", get_app_id);
428       PERSIST_STRV_KEY ("postbuild", get_postbuild);
429       PERSIST_STRV_KEY ("prebuild", get_prebuild);
430 
431 #undef PERSIST_STRING_KEY
432 #undef PERSIST_STRV_KEY
433 
434       if (ide_config_get_locality (config) == IDE_BUILD_LOCALITY_IN_TREE)
435         g_key_file_set_boolean (self->key_file, config_id, "builddir", FALSE);
436       else if (ide_config_get_locality (config) == IDE_BUILD_LOCALITY_OUT_OF_TREE)
437         g_key_file_set_boolean (self->key_file, config_id, "builddir", TRUE);
438       else
439         g_key_file_remove_key (self->key_file, config_id, "builddir", NULL);
440 
441       if (config == ide_config_manager_get_current (manager))
442         g_key_file_set_boolean (self->key_file, config_id, "default", TRUE);
443       else
444         g_key_file_remove_key (self->key_file, config_id, "default", NULL);
445 
446       env = ide_config_get_environment (config);
447 
448       /*
449        * Remove all environment keys that are no longer specified in the
450        * environment. This allows us to just do a single pass of additions
451        * from the environment below.
452        */
453       if (g_key_file_has_group (self->key_file, env_group))
454         {
455           g_auto(GStrv) keys = NULL;
456 
457           if (NULL != (keys = g_key_file_get_keys (self->key_file, env_group, NULL, NULL)))
458             {
459               for (guint j = 0; keys [j]; j++)
460                 {
461                   if (!ide_environment_getenv (env, keys [j]))
462                     g_key_file_remove_key (self->key_file, env_group, keys [j], NULL);
463                 }
464             }
465         }
466 
467       n_items = g_list_model_get_n_items (G_LIST_MODEL (env));
468 
469       for (guint j = 0; j < n_items; j++)
470         {
471           g_autoptr(IdeEnvironmentVariable) var = NULL;
472           const gchar *key;
473           const gchar *value;
474 
475           var = g_list_model_get_item (G_LIST_MODEL (env), j);
476           key = ide_environment_variable_get_key (var);
477           value = ide_environment_variable_get_value (var);
478 
479           if (!dzl_str_empty0 (key))
480             g_key_file_set_string (self->key_file, env_group, key, value ?: "");
481         }
482 
483       ide_config_set_dirty (config, FALSE);
484     }
485 
486   /* Now truncate any old groups in the keyfile. */
487   if (NULL != (groups = g_key_file_get_groups (self->key_file, NULL)))
488     {
489       for (guint i = 0; groups [i]; i++)
490         {
491           if (!g_hash_table_contains (group_names, groups [i]))
492             g_key_file_remove_group (self->key_file, groups [i], NULL);
493         }
494     }
495 
496   if (!(data = g_key_file_to_data (self->key_file, &length, &error)))
497     {
498       ide_task_return_error (task, g_steal_pointer (&error));
499       return;
500     }
501 
502   self->key_file_dirty = FALSE;
503 
504   if (length == 0)
505     {
506       /* Remove the file if it exists, since it would be empty */
507       g_file_delete (file, cancellable, NULL);
508       ide_task_return_boolean (task, TRUE);
509       return;
510     }
511 
512   bytes = g_bytes_new_take (g_steal_pointer (&data), length);
513 
514   g_file_replace_contents_bytes_async (file,
515                                        bytes,
516                                        NULL,
517                                        FALSE,
518                                        G_FILE_CREATE_NONE,
519                                        cancellable,
520                                        ide_buildconfig_config_provider_save_cb,
521                                        g_steal_pointer (&task));
522 }
523 
524 static gboolean
525 ide_buildconfig_config_provider_save_finish (IdeConfigProvider  *provider,
526                                              GAsyncResult       *result,
527                                              GError            **error)
528 {
529   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (provider));
530   g_assert (IDE_IS_TASK (result));
531   g_assert (ide_task_is_valid (IDE_TASK (result), provider));
532 
533   return ide_task_propagate_boolean (IDE_TASK (result), error);
534 }
535 
536 static void
537 ide_buildconfig_config_provider_delete (IdeConfigProvider *provider,
538                                         IdeConfig         *config)
539 {
540   IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider;
541   g_autoptr(IdeConfig) hold = NULL;
542   g_autofree gchar *env = NULL;
543   const gchar *config_id;
544   gboolean had_group;
545 
546   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self));
547   g_assert (IDE_IS_BUILDCONFIG_CONFIG (config));
548   g_assert (self->key_file != NULL);
549   g_assert (self->configs->len > 0);
550 
551   hold = g_object_ref (config);
552 
553   if (!g_ptr_array_remove (self->configs, hold))
554     {
555       g_critical ("No such configuration %s",
556                   ide_config_get_id (hold));
557       return;
558     }
559 
560   config_id = ide_config_get_id (config);
561   had_group = g_key_file_has_group (self->key_file, config_id);
562   env = g_strdup_printf ("%s.environment", config_id);
563   g_key_file_remove_group (self->key_file, config_id, NULL);
564   g_key_file_remove_group (self->key_file, env, NULL);
565 
566   self->key_file_dirty = had_group;
567 
568   /*
569    * If we removed our last buildconfig, synthesize a new one to replace it so
570    * that we never have no configurations available. We add it before we remove
571    * @config so that we never have zero configurations available.
572    *
573    * At some point in the future we might want a read only NULL configuration
574    * for fallback, and group configs by type or something.  But until we have
575    * designs for that, this will do.
576    */
577   if (self->configs->len == 0)
578     {
579       g_autoptr(IdeConfig) new_config = NULL;
580 
581       /* "Default" is not translated because .buildconfig can be checked in */
582       new_config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIG,
583                                  "display-name", "Default",
584                                  "id", "default",
585                                  "parent", self,
586                                  "runtime-id", "host",
587                                  "toolchain-id", "default",
588                                  NULL);
589 
590       /*
591        * Only persist this back if there was data in the keyfile
592        * before we were requested to delete the build-config.
593        */
594       ide_config_set_dirty (new_config, had_group);
595       ide_config_provider_emit_added (provider, new_config);
596     }
597 
598   ide_config_provider_emit_removed (provider, hold);
599 }
600 
601 static void
602 ide_buildconfig_config_provider_duplicate (IdeConfigProvider *provider,
603                                            IdeConfig         *config)
604 {
605   IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider;
606   g_autoptr(IdeConfig) new_config = NULL;
607   g_autofree GParamSpec **pspecs = NULL;
608   g_autofree gchar *new_config_id = NULL;
609   g_autofree gchar *new_name = NULL;
610   IdeConfigManager *manager;
611   IdeEnvironment *env;
612   const gchar *config_id;
613   const gchar *name;
614   IdeContext *context;
615   guint n_pspecs = 0;
616 
617   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self));
618   g_assert (IDE_IS_CONFIG (config));
619   g_assert (IDE_IS_BUILDCONFIG_CONFIG (config));
620 
621   context = ide_object_get_context (IDE_OBJECT (self));
622   g_assert (IDE_IS_CONTEXT (context));
623 
624   manager = ide_config_manager_from_context (context);
625   g_assert (IDE_IS_CONFIG_MANAGER (manager));
626 
627   config_id = ide_config_get_id (config);
628   g_return_if_fail (config_id != NULL);
629 
630   new_config_id = get_next_id (manager, config_id);
631   g_return_if_fail (new_config_id != NULL);
632 
633   name = ide_config_get_display_name (config);
634   /* translators: %s is replaced with the name of the configuration */
635   new_name = g_strdup_printf (_("%s (Copy)"), name);
636 
637   env = ide_config_get_environment (config);
638 
639   new_config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIG,
640                              "id", new_config_id,
641                              "display-name", new_name,
642                              "parent", self,
643                              NULL);
644 
645   ide_environment_copy_into (env, ide_config_get_environment (new_config), TRUE);
646 
647   pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (new_config), &n_pspecs);
648 
649   for (guint i = 0; i < n_pspecs; i++)
650     {
651       GParamSpec *pspec = pspecs[i];
652 
653       if (g_str_equal (pspec->name, "id") ||
654           g_str_equal (pspec->name, "display-name") ||
655           g_type_is_a (pspec->value_type, G_TYPE_BOXED) ||
656           g_type_is_a (pspec->value_type, G_TYPE_OBJECT))
657         continue;
658 
659 
660       if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE &&
661           (pspec->flags & G_PARAM_CONSTRUCT_ONLY) == 0)
662         {
663           GValue value = G_VALUE_INIT;
664 
665           g_value_init (&value, pspec->value_type);
666           g_object_get_property (G_OBJECT (config), pspec->name, &value);
667           g_object_set_property (G_OBJECT (new_config), pspec->name, &value);
668         }
669     }
670 
671   ide_config_set_dirty (new_config, TRUE);
672   ide_config_provider_emit_added (provider, new_config);
673 }
674 
675 static void
676 ide_buildconfig_config_provider_unload (IdeConfigProvider *provider)
677 {
678   IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider;
679   g_autoptr(GPtrArray) configs = NULL;
680 
681   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self));
682   g_assert (self->configs != NULL);
683 
684   configs = g_steal_pointer (&self->configs);
685   self->configs = g_ptr_array_new_with_free_func (g_object_unref);
686 
687   for (guint i = 0; i < configs->len; i++)
688     {
689       IdeConfig *config = g_ptr_array_index (configs, i);
690       ide_config_provider_emit_removed (provider, config);
691     }
692 }
693 
694 static void
695 ide_buildconfig_config_provider_added (IdeConfigProvider *provider,
696                                        IdeConfig         *config)
697 {
698   IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider;
699 
700   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self));
701   g_assert (IDE_IS_CONFIG (config));
702   g_assert (self->configs != NULL);
703 
704   g_ptr_array_add (self->configs, g_object_ref (config));
705 }
706 
707 static void
708 ide_buildconfig_config_provider_removed (IdeConfigProvider *provider,
709                                          IdeConfig         *config)
710 {
711   IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider;
712 
713   g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self));
714   g_assert (IDE_IS_CONFIG (config));
715   g_assert (self->configs != NULL);
716 
717   /* It's possible we already removed it by now */
718   g_ptr_array_remove (self->configs, config);
719 
720   ide_object_destroy (IDE_OBJECT (config));
721 }
722 
723 static void
724 configuration_provider_iface_init (IdeConfigProviderInterface *iface)
725 {
726   iface->added = ide_buildconfig_config_provider_added;
727   iface->removed = ide_buildconfig_config_provider_removed;
728   iface->load_async = ide_buildconfig_config_provider_load_async;
729   iface->load_finish = ide_buildconfig_config_provider_load_finish;
730   iface->save_async = ide_buildconfig_config_provider_save_async;
731   iface->save_finish = ide_buildconfig_config_provider_save_finish;
732   iface->delete = ide_buildconfig_config_provider_delete;
733   iface->duplicate = ide_buildconfig_config_provider_duplicate;
734   iface->unload = ide_buildconfig_config_provider_unload;
735 }
736 
737 G_DEFINE_FINAL_TYPE_WITH_CODE (IdeBuildconfigConfigProvider,
738                          ide_buildconfig_config_provider,
739                          IDE_TYPE_OBJECT,
740                          G_IMPLEMENT_INTERFACE (IDE_TYPE_CONFIG_PROVIDER,
741                                                 configuration_provider_iface_init))
742 
743 static void
744 ide_buildconfig_config_provider_finalize (GObject *object)
745 {
746   IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)object;
747 
748   g_clear_pointer (&self->configs, g_ptr_array_unref);
749   g_clear_pointer (&self->key_file, g_key_file_free);
750 
751   G_OBJECT_CLASS (ide_buildconfig_config_provider_parent_class)->finalize (object);
752 }
753 
754 static void
755 ide_buildconfig_config_provider_class_init (IdeBuildconfigConfigProviderClass *klass)
756 {
757   GObjectClass *object_class = G_OBJECT_CLASS (klass);
758 
759   object_class->finalize = ide_buildconfig_config_provider_finalize;
760 }
761 
762 static void
763 ide_buildconfig_config_provider_init (IdeBuildconfigConfigProvider *self)
764 {
765   self->configs = g_ptr_array_new_with_free_func (g_object_unref);
766 }
767