[gnome-builder/wip/mwleeds/ide-config-provider: 13/14] flatpak: writeback manifest changes to disk
- From: Matthew Leeds <mwleeds src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/mwleeds/ide-config-provider: 13/14] flatpak: writeback manifest changes to disk
- Date: Mon, 30 Jan 2017 20:57:50 +0000 (UTC)
commit c8be3a7d418c520f02e53274cb3f0473788db3cf
Author: Matthew Leeds <mleeds redhat com>
Date: Tue Jan 24 17:28:57 2017 -0600
flatpak: writeback manifest changes to disk
When the user changes build preferences in the Builder UI, those changes
should propagate back to the manifest file on the disk. This commit
accomplishes that in a somewhat hack-ish way (by reading the file
line-by-line and making changes where necessary) so that formatting,
comments, etc. are preserved that would be lost if JsonBuilder and
JsonGenerator were used instead.
.../flatpak/gbp-flatpak-configuration-provider.c | 514 +++++++++++++++++++-
1 files changed, 513 insertions(+), 1 deletions(-)
---
diff --git a/plugins/flatpak/gbp-flatpak-configuration-provider.c
b/plugins/flatpak/gbp-flatpak-configuration-provider.c
index 00edc4a..9152d1b 100644
--- a/plugins/flatpak/gbp-flatpak-configuration-provider.c
+++ b/plugins/flatpak/gbp-flatpak-configuration-provider.c
@@ -30,12 +30,17 @@
#include "gbp-flatpak-configuration-provider.h"
#include "gbp-flatpak-configuration.h"
+#define WRITEBACK_TIMEOUT_SECS 2
+
struct _GbpFlatpakConfigurationProvider
{
GObject parent_instance;
IdeConfigurationManager *manager;
GCancellable *cancellable;
GPtrArray *configurations;
+
+ gulong writeback_handler;
+ guint change_count;
};
typedef struct
@@ -58,6 +63,503 @@ G_DEFINE_TYPE_EXTENDED (GbpFlatpakConfigurationProvider, gbp_flatpak_configurati
static void gbp_flatpak_configuration_provider_load (IdeConfigurationProvider *provider,
IdeConfigurationManager *manager);
static void gbp_flatpak_configuration_provider_unload (IdeConfigurationProvider *provider,
IdeConfigurationManager *manager);
+static void
+gbp_flatpak_configuration_provider_save_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GbpFlatpakConfigurationProvider *self = source_object;
+ GError *error = NULL;
+
+ IDE_ENTRY;
+
+ g_assert (G_IS_TASK (task));
+ g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ self->change_count = 0;
+
+ if (self->configurations == NULL)
+ IDE_EXIT;
+
+ for (guint i = 0; i < self->configurations->len; i++)
+ {
+ g_autoptr(GFileInputStream) file_stream = NULL;
+ g_autoptr(GDataInputStream) data_stream = NULL;
+ g_autoptr(GRegex) runtime_regex = NULL;
+ g_autoptr(GRegex) build_options_regex = NULL;
+ g_autoptr(GRegex) config_opts_regex = NULL;
+ g_autoptr(GRegex) primary_module_regex = NULL;
+ g_autoptr(GPtrArray) new_lines = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+ g_auto(GStrv) new_config_opts = NULL;
+ g_auto(GStrv) new_runtime_parts = NULL;
+ g_auto(GStrv) new_environ = NULL;
+ g_autofree gchar *primary_module_regex_str = NULL;
+ g_autofree gchar *primary_module_right_curly_brace = NULL;
+ g_autofree gchar *right_curly_brace_line = NULL;
+ g_autofree gchar *primary_module_indent = NULL;
+ g_autofree gchar *build_options_indent = NULL;
+ g_autofree gchar *config_opt_indent = NULL;
+ g_autofree gchar *array_prefix = NULL;
+ g_autofree gchar *new_config_opts_string = NULL;
+ const gchar *primary_module;
+ const gchar *new_runtime_id;
+ const gchar *config_prefix;
+ const gchar *new_prefix;
+ gchar *json_string;
+ gchar *new_runtime_name;
+ GFile *manifest;
+ gboolean in_config_opts_array;
+ gboolean in_primary_module;
+ gboolean in_build_options;
+ gboolean config_opts_replaced;
+ gboolean build_options_replaced;
+ guint opts_per_line;
+ guint nested_curly_braces;
+
+ GbpFlatpakConfiguration *configuration = (GbpFlatpakConfiguration *)g_ptr_array_index
(self->configurations, i);
+
+ manifest = gbp_flatpak_configuration_get_manifest (configuration);
+ if (manifest == NULL)
+ continue;
+
+ primary_module = gbp_flatpak_configuration_get_primary_module (configuration);
+ if (primary_module == NULL)
+ {
+ g_warning ("Flatpak manifest configuration has no primary module set");
+ continue;
+ }
+
+ file_stream = g_file_read (manifest, NULL, &error);
+ if (file_stream == NULL)
+ {
+ g_task_return_error (task, error);
+ IDE_EXIT;
+ }
+
+ data_stream = g_data_input_stream_new (G_INPUT_STREAM (file_stream));
+
+ runtime_regex = g_regex_new ("^\\s*\"runtime\"\\s*:\\s*\"(?<id>.+)\",$", 0, 0, NULL);
+ build_options_regex = g_regex_new ("^\\s*\"build-options\"\\s*:\\s*{$", 0, 0, NULL);
+ config_opts_regex = g_regex_new ("^(\\s*\"config-opts\"\\s*:\\s*\\[\\s*).+$", 0, 0, NULL);
+ primary_module_regex_str = g_strdup_printf ("^(\\s*)\"name\"\\s*:\\s*\"%s\",$", primary_module);
+ primary_module_regex = g_regex_new (primary_module_regex_str, 0, 0, NULL);
+
+ new_runtime_id = ide_configuration_get_runtime_id (IDE_CONFIGURATION (configuration));
+ if (g_str_has_prefix (new_runtime_id, "flatpak:"))
+ {
+ new_runtime_parts = g_strsplit (new_runtime_id + 8, "/", 3);
+ if (new_runtime_parts[0] != NULL)
+ new_runtime_name = new_runtime_parts[0];
+ }
+
+ new_config_opts_string = g_strdup (ide_configuration_get_config_opts (IDE_CONFIGURATION
(configuration)));
+ if (!ide_str_empty0 (new_config_opts_string))
+ {
+ new_config_opts = g_strsplit (g_strstrip (new_config_opts_string), " ", 0);
+ }
+
+ new_environ = ide_configuration_get_environ (IDE_CONFIGURATION (configuration));
+
+ config_prefix = ide_configuration_get_prefix (IDE_CONFIGURATION (configuration));
+ new_prefix = (g_strcmp0 (config_prefix, "/app") != 0) ? config_prefix : "";
+
+ /**
+ * XXX: The following code, which parses parts of the manifest file and edits
+ * it to match the options chosen by the user in Builder's interface, assumes
+ * that the JSON is "pretty" (meaning it has lots of whitespace and newlines),
+ * which is not technically a requirement for JSON but a de facto standard used
+ * by developers.
+ */
+ new_lines = g_ptr_array_new_with_free_func (g_free);
+ in_config_opts_array = FALSE;
+ in_primary_module = FALSE;
+ in_build_options = FALSE;
+ config_opts_replaced = FALSE;
+ build_options_replaced = FALSE;
+ nested_curly_braces = 0;
+ for (;;)
+ {
+ gchar *line;
+
+ line = g_data_input_stream_read_line_utf8 (data_stream, NULL, NULL, &error);
+ if (error != NULL)
+ {
+ g_task_return_error (task, error);
+ IDE_EXIT;
+ }
+ if (line == NULL)
+ break;
+
+ if (!in_primary_module)
+ {
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_regex_match (primary_module_regex, line, 0, &match_info);
+ if (g_match_info_matches (match_info))
+ {
+ gchar *previous_line;
+ g_auto(GStrv) previous_line_parts = NULL;
+
+ in_primary_module = TRUE;
+ primary_module_indent = g_match_info_fetch (match_info, 1);
+
+ /* Replace '}' with '{' in the last line to get the right indentation */
+ previous_line = (gchar *)g_ptr_array_index (new_lines, new_lines->len - 1);
+ previous_line_parts = g_strsplit (previous_line, "{", 0);
+ primary_module_right_curly_brace = g_strjoinv ("}", previous_line_parts);
+ }
+ }
+
+ /* Replace the runtime with the user-chosen one */
+ if (!ide_str_empty0 (new_runtime_name))
+ {
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_regex_match (runtime_regex, line, 0, &match_info);
+ if (g_match_info_matches (match_info))
+ {
+ gchar *old_runtime_ptr;
+ gchar *new_line;
+ g_autofree gchar *id = NULL;
+ id = g_match_info_fetch_named (match_info, "id");
+ old_runtime_ptr = g_strstr_len (line, -1, id);
+ *old_runtime_ptr = '\0';
+ new_line = g_strdup_printf ("%s%s\",", line, new_runtime_name);
+ g_free (line);
+ line = new_line;
+ }
+ }
+
+ /* Update the environment variables */
+ if (!in_build_options && !build_options_replaced)
+ {
+ g_autoptr(GMatchInfo) match_info = NULL;
+ g_regex_match (build_options_regex, line, 0, &match_info);
+ if (g_match_info_matches (match_info))
+ {
+ in_build_options = TRUE;
+ }
+ }
+ else if (in_build_options)
+ {
+ if (g_strstr_len (line, -1, "{") != NULL)
+ nested_curly_braces++;
+ if (g_strstr_len (line, -1, "}") == NULL)
+ {
+ if (build_options_indent == NULL)
+ {
+ g_autoptr(GRegex) build_options_internal_regex = NULL;
+ g_autoptr(GMatchInfo) match_info = NULL;
+ build_options_internal_regex = g_regex_new ("^(\\s*)\".+\"\\s*:.*$", 0, 0, NULL);
+ g_regex_match (build_options_internal_regex, line, 0, &match_info);
+ if (g_match_info_matches (match_info))
+ {
+ build_options_indent = g_match_info_fetch (match_info, 1);
+ }
+ }
+ g_free (line);
+ continue;
+ }
+ else
+ {
+ if (nested_curly_braces > 0)
+ {
+ nested_curly_braces--;
+ g_free (line);
+ continue;
+ }
+ else
+ {
+ /* We're at the closing curly brace for build-options */
+ guint num_env;
+ num_env = g_strv_length (new_environ);
+ if (num_env > 0)
+ {
+ g_autofree gchar *cflags_line = NULL;
+ g_autofree gchar *cxxflags_line = NULL;
+ g_autoptr(GPtrArray) env_lines = NULL;
+ if (build_options_indent == NULL)
+ build_options_indent = g_strdup (" ");
+ for (guint j = 0; new_environ[j]; j++)
+ {
+ g_auto(GStrv) line_parts = NULL;
+ line_parts = g_strsplit (new_environ[j], "=", 2);
+ if (g_strcmp0 (line_parts[0], "CFLAGS") == 0)
+ cflags_line = g_strdup_printf ("%s\"cflags\": \"%s\"",
+ build_options_indent,
+ line_parts[1]);
+ else if (g_strcmp0 (line_parts[0], "CXXFLAGS") == 0)
+ cxxflags_line = g_strdup_printf ("%s\"cxxflags\": \"%s\"",
+ build_options_indent,
+ line_parts[1]);
+ else
+ {
+ if (env_lines == NULL)
+ {
+ env_lines = g_ptr_array_new_with_free_func (g_free);
+ g_ptr_array_add (env_lines, g_strdup_printf ("%s\"env\": {",
build_options_indent));
+ }
+ g_ptr_array_add (env_lines, g_strdup_printf ("%s \"%s\": \"%s\"",
+ build_options_indent,
+ line_parts[0],
+ line_parts[1]));
+ }
+ }
+ if (cflags_line != NULL)
+ {
+ gchar *line_ending;
+ line_ending = (!ide_str_empty0 (new_prefix) || cxxflags_line != NULL ||
env_lines != NULL) ? "," : "";
+ g_ptr_array_add (new_lines, g_strdup_printf ("%s%s", cflags_line,
line_ending));
+ }
+ if (cxxflags_line != NULL)
+ {
+ gchar *line_ending;
+ line_ending = (!ide_str_empty0 (new_prefix) || env_lines != NULL) ? "," : "";
+ g_ptr_array_add (new_lines, g_strdup_printf ("%s%s", cxxflags_line,
line_ending));
+ }
+ if (!ide_str_empty0 (new_prefix))
+ {
+ gchar *line_ending;
+ line_ending = (env_lines != NULL) ? "," : "";
+ g_ptr_array_add (new_lines, g_strdup_printf ("%s\"prefix\": \"%s\"%s",
+ build_options_indent,
+ new_prefix,
+ line_ending));
+ }
+ if (env_lines != NULL)
+ {
+ g_ptr_array_add (env_lines, g_strdup_printf ("%s}", build_options_indent));
+ for (guint j = 0; j < env_lines->len; j++)
+ {
+ gchar *env_line;
+ gchar *line_ending;
+ line_ending = (j > 0 && j < env_lines->len - 2) ? "," : "";
+ env_line = (gchar *)g_ptr_array_index (env_lines, j);
+ g_ptr_array_add (new_lines, g_strdup_printf ("%s%s", env_line,
line_ending));
+ }
+ }
+ }
+ in_build_options = FALSE;
+ build_options_replaced = TRUE;
+ }
+ }
+ }
+
+ if (in_primary_module)
+ {
+ g_autoptr(GMatchInfo) match_info = NULL;
+
+ /* Check if we're at the end of the module and haven't seen a config-opts property */
+ if (g_str_has_prefix (line, primary_module_right_curly_brace))
+ {
+ in_primary_module = FALSE;
+ if (!config_opts_replaced && new_config_opts != NULL)
+ {
+ gchar *previous_line;
+
+ previous_line = (gchar *)g_ptr_array_remove_index (new_lines, new_lines->len - 1);
+ g_ptr_array_add (new_lines, g_strdup_printf ("%s,", previous_line));
+ right_curly_brace_line = line;
+ line = g_strdup_printf ("%s\"config-opts\": []", primary_module_indent);
+ }
+ }
+
+ /* Update the list of configure options, or omit it entirely */
+ g_regex_match (config_opts_regex, line, 0, &match_info);
+ if (g_match_info_matches (match_info) || in_config_opts_array)
+ {
+ gchar *right_bracket;
+
+ right_bracket = g_strstr_len (line, -1, "]");
+ if (g_match_info_matches (match_info))
+ {
+ array_prefix = g_match_info_fetch (match_info, 1);
+ if (right_bracket != NULL)
+ {
+ /*
+ * Ensure that all options will be on one line,
+ * even if there are more than before
+ */
+ if (new_config_opts == NULL)
+ opts_per_line = 1;
+ else
+ opts_per_line = g_strv_length (new_config_opts);
+ }
+ else
+ {
+ in_config_opts_array = TRUE;
+ if (new_config_opts == NULL)
+ opts_per_line = 1;
+ else
+ opts_per_line = (g_strv_length (g_strsplit (line, "\"", 0)) - 3) / 2;
+ g_free (line);
+ continue;
+ }
+ }
+ if (right_bracket == NULL)
+ {
+ in_config_opts_array = TRUE;
+ config_opt_indent = g_strsplit (line, "\"", 0)[0];
+ g_free (line);
+ continue;
+ }
+
+ /* At this point it's either a single line or we're on the last line */
+ in_config_opts_array = FALSE;
+ config_opts_replaced = TRUE;
+ if (new_config_opts == NULL)
+ {
+ g_free (line);
+ line = g_strdup_printf ("%s],", array_prefix);
+ }
+ else
+ {
+ gchar *array_suffix;
+ array_suffix = *(right_bracket - 1) == ' ' ? " ]" : "]";
+ if (config_opt_indent == NULL)
+ {
+ g_auto(GStrv) line_parts = NULL;
+ line_parts = g_strsplit (line, "\"", 0);
+ config_opt_indent = g_strdup (line_parts[0]);
+ }
+ for (guint j = 0; g_strv_length (new_config_opts) > j; j += opts_per_line)
+ {
+ g_autoptr(GPtrArray) config_opts_subset = NULL;
+ g_autofree gchar *opts_this_line = NULL;
+ gchar *prefix;
+ gchar *suffix;
+ gchar *new_line;
+
+ prefix = (j == 0) ? array_prefix : config_opt_indent;
+ suffix = (g_strv_length (new_config_opts) <= j + opts_per_line) ? array_suffix :
"";
+ config_opts_subset = g_ptr_array_new ();
+ for (guint k = 0; k < opts_per_line && new_config_opts[j+k]; ++k)
+ g_ptr_array_add (config_opts_subset, new_config_opts[j+k]);
+ g_ptr_array_add (config_opts_subset, NULL);
+ opts_this_line = g_strjoinv ("\", \"", (gchar **)config_opts_subset->pdata);
+
+ new_line = g_strdup_printf ("%s\"%s\"%s,", prefix, opts_this_line, suffix);
+ g_ptr_array_add (new_lines, new_line);
+ }
+ g_free (line);
+ continue;
+ }
+ }
+ }
+
+ g_ptr_array_add (new_lines, line);
+ if (right_curly_brace_line != NULL)
+ {
+ g_ptr_array_add (new_lines, right_curly_brace_line);
+ right_curly_brace_line = NULL;
+ }
+ }
+
+ /* Ensure there's a newline at the end of the file */
+ g_ptr_array_add (new_lines, g_strdup (""));
+ g_ptr_array_add (new_lines, NULL);
+ json_string = g_strjoinv ("\n", (gchar **)new_lines->pdata);
+ bytes = g_bytes_new_take (json_string, strlen (json_string));
+
+ if (!g_file_replace_contents (manifest,
+ g_bytes_get_data (bytes, NULL),
+ g_bytes_get_size (bytes),
+ NULL,
+ FALSE,
+ G_FILE_CREATE_NONE,
+ NULL,
+ cancellable,
+ &error))
+ {
+ g_task_return_error (task, error);
+ IDE_EXIT;
+ }
+ }
+
+ IDE_EXIT;
+}
+
+void
+gbp_flatpak_configuration_provider_save_async (IdeConfigurationProvider *provider,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GbpFlatpakConfigurationProvider *self = (GbpFlatpakConfigurationProvider *)provider;
+ g_autoptr(GTask) task = NULL;
+
+ IDE_ENTRY;
+
+ g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (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);
+ else
+ g_task_run_in_thread (task, gbp_flatpak_configuration_provider_save_worker);
+
+ IDE_EXIT;
+}
+
+gboolean
+gbp_flatpak_configuration_provider_save_finish (IdeConfigurationProvider *provider,
+ GAsyncResult *result,
+ GError **error)
+{
+ GbpFlatpakConfigurationProvider *self = (GbpFlatpakConfigurationProvider *)provider;
+
+ g_return_val_if_fail (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+gbp_flatpak_configuration_provider_do_writeback (gpointer data)
+{
+ GbpFlatpakConfigurationProvider *self = data;
+
+ g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+
+ self->writeback_handler = 0;
+
+ gbp_flatpak_configuration_provider_save_async (IDE_CONFIGURATION_PROVIDER (self), NULL, NULL, NULL);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+gbp_flatpak_configuration_provider_queue_writeback (GbpFlatpakConfigurationProvider *self)
+{
+ g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+
+ IDE_ENTRY;
+
+ if (self->writeback_handler != 0)
+ g_source_remove (self->writeback_handler);
+
+ self->writeback_handler = g_timeout_add_seconds (WRITEBACK_TIMEOUT_SECS,
+ gbp_flatpak_configuration_provider_do_writeback,
+ self);
+
+ IDE_EXIT;
+}
+
+static void
+gbp_flatpak_configuration_provider_changed (GbpFlatpakConfigurationProvider *self,
+ IdeConfiguration *configuration)
+{
+ g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
+ g_assert (IDE_IS_CONFIGURATION (configuration));
+
+ self->change_count++;
+
+ gbp_flatpak_configuration_provider_queue_writeback (self);
+}
+
static gboolean
contains_id (GPtrArray *ar,
const gchar *id)
@@ -461,6 +963,12 @@ gbp_flatpak_configuration_provider_load_manifests (GbpFlatpakConfigurationProvid
if (manifest->config_opts != NULL)
ide_configuration_set_config_opts (IDE_CONFIGURATION (configuration), manifest->config_opts);
+ g_signal_connect_object (configuration,
+ "changed",
+ G_CALLBACK (gbp_flatpak_configuration_provider_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
g_ptr_array_add (configurations, configuration);
}
@@ -566,9 +1074,11 @@ gbp_flatpak_configuration_provider_unload (IdeConfigurationProvider *provider,
g_assert (GBP_IS_FLATPAK_CONFIGURATION_PROVIDER (self));
g_assert (IDE_IS_CONFIGURATION_MANAGER (manager));
+ ide_clear_source (&self->writeback_handler);
+
if (self->configurations != NULL)
{
- for (guint i= 0; i < self->configurations->len; i++)
+ for (guint i = 0; i < self->configurations->len; i++)
{
IdeConfiguration *configuration = g_ptr_array_index (self->configurations, i);
@@ -602,4 +1112,6 @@ configuration_provider_iface_init (IdeConfigurationProviderInterface *iface)
{
iface->load = gbp_flatpak_configuration_provider_load;
iface->unload = gbp_flatpak_configuration_provider_unload;
+ iface->save_async = gbp_flatpak_configuration_provider_save_async;
+ iface->save_finish = gbp_flatpak_configuration_provider_save_finish;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]