[gnome-builder/wip/chergert/pipeline-merge: 53/64] flatpak: Writeback manifest changes to disk



commit 7b61bac2229e7838c665effdd6ef83ac985fc5f0
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.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=777959

 .../flatpak/gbp-flatpak-configuration-provider.c   |  523 +++++++++++++++++++-
 1 files changed, 522 insertions(+), 1 deletions(-)
---
diff --git a/plugins/flatpak/gbp-flatpak-configuration-provider.c 
b/plugins/flatpak/gbp-flatpak-configuration-provider.c
index 00edc4a..d1dc7a6 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,512 @@ 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;
+
+          /* Check if we've reached the primary module's section */
+          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 build-options object */
+          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);
+                        }
+                    }
+                  /* Discard the line because it will be replaced with new info */
+                  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 || !ide_str_empty0 (new_prefix))
+                        {
+                          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
+                            {
+                              g_auto(GStrv) line_parts = NULL;
+                              line_parts = g_strsplit (line, "\"", 0);
+                              opts_per_line = (g_strv_length (line_parts) - 3) / 2;
+                              opts_per_line = (opts_per_line > 0) ? opts_per_line : 1;
+                            }
+                          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);
+                        }
+                      /* Discard the line that was just replaced with the new config-opts array */
+                      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);
+
+      /* Write the updated lines to the disk */
+      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 +972,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 +1083,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 +1121,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]