[gnome-builder] libide-foundry: add IdeRunContext
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] libide-foundry: add IdeRunContext
- Date: Tue, 12 Jul 2022 06:39:11 +0000 (UTC)
commit bf307488364ded64b8d0debea2cf3c1cda4b1075
Author: Christian Hergert <chergert redhat com>
Date: Mon Jul 11 20:48:49 2022 -0700
libide-foundry: add IdeRunContext
This is a layered approach to creating subprocesses that allows each layer
to post-process the previous layer.
See https://blogs.gnome.org/chergert/2022/06/25/builder-gtk-4-porting-part-vii/
for more information.
src/libide/foundry/ide-foundry-types.h | 1 +
src/libide/foundry/ide-run-context.c | 1299 ++++++++++++++++++++++++++++++++
src/libide/foundry/ide-run-context.h | 145 ++++
src/libide/foundry/meson.build | 2 +
4 files changed, 1447 insertions(+)
---
diff --git a/src/libide/foundry/ide-foundry-types.h b/src/libide/foundry/ide-foundry-types.h
index 26732be96..6e1fe1b94 100644
--- a/src/libide/foundry/ide-foundry-types.h
+++ b/src/libide/foundry/ide-foundry-types.h
@@ -51,6 +51,7 @@ typedef struct _IdePipelineStage IdePipelineStage;
typedef struct _IdePipelineStageLauncher IdePipelineStageLauncher;
typedef struct _IdePipelineStageMkdirs IdePipelineStageMkdirs;
typedef struct _IdePipelineStageTransfer IdePipelineStageTransfer;
+typedef struct _IdeRunContext IdeRunContext;
typedef struct _IdeRunButton IdeRunButton;
typedef struct _IdeRunManager IdeRunManager;
typedef struct _IdeRunner IdeRunner;
diff --git a/src/libide/foundry/ide-run-context.c b/src/libide/foundry/ide-run-context.c
new file mode 100644
index 000000000..2f5210c3d
--- /dev/null
+++ b/src/libide/foundry/ide-run-context.c
@@ -0,0 +1,1299 @@
+/* ide-run-context.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-run-context"
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libide-io.h>
+
+#include "ide-private.h"
+
+#include "ide-run-context.h"
+
+typedef struct
+{
+ GList qlink;
+ char *cwd;
+ GArray *argv;
+ GArray *env;
+ IdeUnixFDMap *unix_fd_map;
+ IdeRunContextHandler handler;
+ gpointer handler_data;
+ GDestroyNotify handler_data_destroy;
+} IdeRunContextLayer;
+
+struct _IdeRunContext
+{
+ GObject parent_instance;
+ GQueue layers;
+ IdeRunContextLayer root;
+ guint ended : 1;
+};
+
+G_DEFINE_FINAL_TYPE (IdeRunContext, ide_run_context, G_TYPE_OBJECT)
+
+IdeRunContext *
+ide_run_context_new (void)
+{
+ return g_object_new (IDE_TYPE_RUN_CONTEXT, NULL);
+}
+
+/**
+ * ide_run_context_add_minimal_environment:
+ * @self: a #IdeRunContext
+ *
+ * Adds a minimal set of environment variables.
+ *
+ * This is useful to get access to things like the display or other
+ * expected variables.
+ */
+void
+ide_run_context_add_minimal_environment (IdeRunContext *self)
+{
+ const gchar * const *host_environ = _ide_host_environ ();
+ static const char *copy_env[] = {
+ "AT_SPI_BUS_ADDRESS",
+ "COLORTERM",
+ "DBUS_SESSION_BUS_ADDRESS",
+ "DBUS_SYSTEM_BUS_ADDRESS",
+ "DESKTOP_SESSION",
+ "DISPLAY",
+ "LANG",
+ "SHELL",
+ "SSH_AUTH_SOCK",
+ "USER",
+ "WAYLAND_DISPLAY",
+ "XAUTHORITY",
+ "XDG_CURRENT_DESKTOP",
+ "XDG_MENU_PREFIX",
+ "XDG_SEAT",
+ "XDG_SESSION_DESKTOP",
+ "XDG_SESSION_ID",
+ "XDG_SESSION_TYPE",
+ "XDG_VTNR",
+ };
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ for (guint i = 0; i < G_N_ELEMENTS (copy_env); i++)
+ {
+ const char *key = copy_env[i];
+ const char *val = g_environ_getenv ((char **)host_environ, key);
+
+ if (val != NULL)
+ ide_run_context_setenv (self, key, val);
+ }
+
+ IDE_EXIT;
+}
+
+static void
+ide_run_context_layer_clear (IdeRunContextLayer *layer)
+{
+ g_assert (layer != NULL);
+ g_assert (layer->qlink.data == layer);
+ g_assert (layer->qlink.prev == NULL);
+ g_assert (layer->qlink.next == NULL);
+
+ if (layer->handler_data_destroy)
+ g_clear_pointer (&layer->handler_data, layer->handler_data_destroy);
+
+ g_clear_pointer (&layer->cwd, g_free);
+ g_clear_pointer (&layer->argv, g_array_unref);
+ g_clear_pointer (&layer->env, g_array_unref);
+ g_clear_object (&layer->unix_fd_map);
+}
+
+static void
+ide_run_context_layer_free (IdeRunContextLayer *layer)
+{
+ ide_run_context_layer_clear (layer);
+
+ g_slice_free (IdeRunContextLayer, layer);
+}
+
+static void
+strptr_free (gpointer data)
+{
+ char **strptr = data;
+ g_clear_pointer (strptr, g_free);
+}
+
+static void
+ide_run_context_layer_init (IdeRunContextLayer *layer)
+{
+ g_assert (layer != NULL);
+
+ layer->qlink.data = layer;
+ layer->argv = g_array_new (TRUE, TRUE, sizeof (char *));
+ layer->env = g_array_new (TRUE, TRUE, sizeof (char *));
+ layer->unix_fd_map = ide_unix_fd_map_new ();
+
+ g_array_set_clear_func (layer->argv, strptr_free);
+ g_array_set_clear_func (layer->env, strptr_free);
+}
+
+static IdeRunContextLayer *
+ide_run_context_current_layer (IdeRunContext *self)
+{
+ g_assert (IDE_IS_RUN_CONTEXT (self));
+ g_assert (self->layers.length > 0);
+
+ return self->layers.head->data;
+}
+
+static void
+ide_run_context_dispose (GObject *object)
+{
+ IdeRunContext *self = (IdeRunContext *)object;
+ IdeRunContextLayer *layer;
+
+ while ((layer = g_queue_peek_head (&self->layers)))
+ {
+ g_queue_unlink (&self->layers, &layer->qlink);
+ if (layer != &self->root)
+ ide_run_context_layer_free (layer);
+ }
+
+ ide_run_context_layer_clear (&self->root);
+
+ G_OBJECT_CLASS (ide_run_context_parent_class)->dispose (object);
+}
+
+static void
+ide_run_context_class_init (IdeRunContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = ide_run_context_dispose;
+}
+
+static void
+ide_run_context_init (IdeRunContext *self)
+{
+ ide_run_context_layer_init (&self->root);
+
+ g_queue_push_head_link (&self->layers, &self->root.qlink);
+}
+
+void
+ide_run_context_push (IdeRunContext *self,
+ IdeRunContextHandler handler,
+ gpointer handler_data,
+ GDestroyNotify handler_data_destroy)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ layer = g_slice_new0 (IdeRunContextLayer);
+
+ ide_run_context_layer_init (layer);
+
+ layer->handler = handler;
+ layer->handler_data = handler_data;
+ layer->handler_data_destroy = handler_data_destroy;
+
+ g_queue_push_head_link (&self->layers, &layer->qlink);
+}
+
+static gboolean
+ide_run_context_host_handler (IdeRunContext *self,
+ const char * const *argv,
+ const char * const *env,
+ const char *cwd,
+ IdeUnixFDMap *unix_fd_map,
+ gpointer user_data,
+ GError **error)
+{
+ guint length;
+
+ g_assert (IDE_IS_RUN_CONTEXT (self));
+ g_assert (argv != NULL);
+ g_assert (env != NULL);
+ g_assert (IDE_IS_UNIX_FD_MAP (unix_fd_map));
+ g_assert (ide_is_flatpak ());
+
+ ide_run_context_append_argv (self, "flatpak-spawn");
+ ide_run_context_append_argv (self, "--host");
+ ide_run_context_append_argv (self, "--clear-env");
+ ide_run_context_append_argv (self, "--watch-bus");
+
+ if (env != NULL)
+ {
+ for (guint i = 0; env[i]; i++)
+ ide_run_context_append_formatted (self, "--env=%s", env[i]);
+ }
+
+ if (cwd != NULL)
+ ide_run_context_append_formatted (self, "--directory=%s", cwd);
+
+ if ((length = ide_unix_fd_map_get_length (unix_fd_map)))
+ {
+ if (!ide_run_context_merge_unix_fd_map (self, unix_fd_map, error))
+ return FALSE;
+
+ for (guint i = 0; i < length; i++)
+ {
+ int source_fd;
+ int dest_fd;
+
+ source_fd = ide_unix_fd_map_peek (unix_fd_map, i, &dest_fd);
+
+ if (source_fd != -1 && dest_fd != -1)
+ ide_run_context_append_formatted (self, "--forward-fd=%d", dest_fd);
+ }
+ }
+
+ /* Now append the arguments */
+ ide_run_context_append_args (self, argv);
+
+ return TRUE;
+}
+
+/**
+ * ide_run_context_push_host:
+ * @self: a #IdeRunContext
+ *
+ * Pushes handler to transform command to run on host.
+ *
+ * If necessary, a layer is pushed to ensure the command is run on the
+ * host instead of the application container.
+ *
+ * If Builder is running on the host already, this function does nothing.
+ */
+void
+ide_run_context_push_host (IdeRunContext *self)
+{
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ if (ide_is_flatpak ())
+ ide_run_context_push (self,
+ ide_run_context_host_handler,
+ NULL,
+ NULL);
+}
+
+static gboolean
+ide_run_context_shell_handler (IdeRunContext *self,
+ const char * const *argv,
+ const char * const *env,
+ const char *cwd,
+ IdeUnixFDMap *unix_fd_map,
+ gpointer user_data,
+ GError **error)
+{
+ g_autoptr(GString) str = NULL;
+ gboolean login = !!GPOINTER_TO_INT (user_data);
+
+ g_assert (IDE_IS_RUN_CONTEXT (self));
+ g_assert (argv != NULL);
+ g_assert (env != NULL);
+ g_assert (IDE_IS_UNIX_FD_MAP (unix_fd_map));
+
+ if (!ide_run_context_merge_unix_fd_map (self, unix_fd_map, error))
+ return FALSE;
+
+ if (cwd != NULL)
+ ide_run_context_set_cwd (self, cwd);
+
+ ide_run_context_append_argv (self, "/bin/sh");
+ if (login)
+ ide_run_context_append_argv (self, "--login");
+ ide_run_context_append_argv (self, "-c");
+
+ str = g_string_new (NULL);
+
+ if (env[0] != NULL)
+ {
+ g_string_append (str, "env");
+
+ for (guint i = 0; env[i]; i++)
+ {
+ g_autofree char *quoted = g_shell_quote (env[i]);
+
+ g_string_append_c (str, ' ');
+ g_string_append (str, quoted);
+ }
+
+ g_string_append_c (str, ' ');
+ }
+
+ for (guint i = 0; argv[i]; i++)
+ {
+ g_autofree char *quoted = g_shell_quote (argv[i]);
+
+ if (i > 0)
+ g_string_append_c (str, ' ');
+ g_string_append (str, quoted);
+ }
+
+ ide_run_context_append_argv (self, str->str);
+
+ return TRUE;
+}
+
+/**
+ * ide_run_context_push_shell:
+ * @self: a #IdeRunContext
+ * @login: if a login shell should be used
+ *
+ * Pushes a shell which can run the upper layer command with -c.
+ */
+void
+ide_run_context_push_shell (IdeRunContext *self,
+ gboolean login)
+{
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ ide_run_context_push (self,
+ ide_run_context_shell_handler,
+ GINT_TO_POINTER (!!login),
+ NULL);
+}
+
+static gboolean
+ide_run_context_error_handler (IdeRunContext *self,
+ const char * const *argv,
+ const char * const *env,
+ const char *cwd,
+ IdeUnixFDMap *unix_fd_map,
+ gpointer user_data,
+ GError **error)
+{
+ const GError *local_error = user_data;
+
+ g_assert (IDE_IS_RUN_CONTEXT (self));
+ g_assert (IDE_IS_UNIX_FD_MAP (unix_fd_map));
+ g_assert (local_error != NULL);
+
+ if (error != NULL)
+ *error = g_error_copy (local_error);
+
+ return FALSE;
+}
+
+/**
+ * ide_run_context_push_error:
+ * @self: a #IdeRunContext
+ * @error: (transfer full) (in): a #GError
+ *
+ * Pushes a new layer that will always fail with @error.
+ *
+ * This is useful if you have an error when attempting to build
+ * a run command, but need it to deliver the error when attempting
+ * to create a subprocess launcher.
+ */
+void
+ide_run_context_push_error (IdeRunContext *self,
+ GError *error)
+{
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+ g_return_if_fail (error != NULL);
+
+ ide_run_context_push (self,
+ ide_run_context_error_handler,
+ error,
+ (GDestroyNotify)g_error_free);
+}
+
+static gboolean
+next_variable (const char *str,
+ guint *cursor,
+ guint *begin)
+{
+ for (guint i = *cursor; str[i]; i++)
+ {
+ /* Skip past escaped $ */
+ if (str[i] == '\\' && str[i+1] == '$')
+ {
+ i++;
+ continue;
+ }
+
+ if (str[i] == '$')
+ {
+ *begin = i;
+ *cursor = i;
+
+ for (guint j = i+1; str[j]; j++)
+ {
+ if (!g_ascii_isalnum (str[j]) && str[j] != '_')
+ {
+ *cursor = j;
+ break;
+ }
+ }
+
+ if (*cursor > ((*begin) + 1))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static char *
+wordexp_with_environ (const char *input,
+ const char * const *environ)
+{
+ g_autoptr(GString) str = NULL;
+ guint cursor = 0;
+ guint begin;
+
+ g_assert (input != NULL);
+ g_assert (environ != NULL);
+
+ str = g_string_new (input);
+
+ while (next_variable (str->str, &cursor, &begin))
+ {
+ g_autofree char *key = NULL;
+ guint key_len = cursor - begin;
+ const char *value;
+ guint value_len;
+
+ g_assert (str->str[begin] == '$');
+
+ key = g_strndup (str->str + begin, key_len);
+ value = g_environ_getenv ((char **)environ, key+1);
+ value_len = value ? strlen (value) : 0;
+
+ if (value != NULL)
+ {
+ g_string_erase (str, begin, key_len);
+ g_string_insert_len (str, begin, value, value_len);
+
+ if (value_len > key_len)
+ cursor += (value_len - key_len);
+ else if (value_len < key_len)
+ cursor -= (key_len - value_len);
+ }
+ }
+
+ return g_string_free (g_steal_pointer (&str), FALSE);
+}
+
+static gboolean
+ide_run_context_expansion_handler (IdeRunContext *self,
+ const char * const *argv,
+ const char * const *env,
+ const char *cwd,
+ IdeUnixFDMap *unix_fd_map,
+ gpointer user_data,
+ GError **error)
+{
+ const char * const *environ = user_data;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_RUN_CONTEXT (self));
+ g_assert (argv != NULL);
+ g_assert (environ != NULL);
+ g_assert (IDE_IS_UNIX_FD_MAP (unix_fd_map));
+
+ if (!ide_run_context_merge_unix_fd_map (self, unix_fd_map, error))
+ IDE_RETURN (FALSE);
+
+ if (cwd != NULL)
+ {
+ g_autofree char *newcwd = wordexp_with_environ (cwd, environ);
+ g_autofree char *expanded = ide_path_expand (newcwd);
+
+ ide_run_context_set_cwd (self, expanded);
+ }
+
+ if (env != NULL)
+ {
+ g_autoptr(GArray) newenv = g_array_new (TRUE, TRUE, sizeof (char *));
+
+ for (guint i = 0; env[i]; i++)
+ {
+ char *expanded = wordexp_with_environ (env[i], environ);
+ g_array_append_val (newenv, expanded);
+ }
+
+ ide_run_context_add_environ (self, (const char * const *)(gpointer)newenv->data);
+ }
+
+ if (argv != NULL)
+ {
+ g_autoptr(GArray) newargv = g_array_new (TRUE, TRUE, sizeof (char *));
+
+ for (guint i = 0; argv[i]; i++)
+ {
+ char *expanded = wordexp_with_environ (argv[i], environ);
+ g_array_append_val (newargv, expanded);
+ }
+
+ ide_run_context_append_args (self, (const char * const *)(gpointer)newargv->data);
+ }
+
+ IDE_RETURN (TRUE);
+}
+
+/**
+ * ide_run_context_push_expansion:
+ * @self: a #IdeRunContext
+ *
+ * Pushes a layer to expand known environment variables.
+ *
+ * The command argv and cwd will have `$FOO` style environment
+ * variables expanded that are known. This can be useful to allow
+ * things like `$BUILDDIR` be expanded at this layer.
+ */
+void
+ide_run_context_push_expansion (IdeRunContext *self,
+ const char * const *environ)
+{
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ if (environ != NULL)
+ ide_run_context_push (self,
+ ide_run_context_expansion_handler,
+ g_strdupv ((char **)environ),
+ (GDestroyNotify)g_strfreev);
+}
+
+const char * const *
+ide_run_context_get_argv (IdeRunContext *self)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_val_if_fail (IDE_IS_RUN_CONTEXT (self), NULL);
+
+ layer = ide_run_context_current_layer (self);
+
+ return (const char * const *)(gpointer)layer->argv->data;
+}
+
+void
+ide_run_context_set_argv (IdeRunContext *self,
+ const char * const *argv)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ layer = ide_run_context_current_layer (self);
+
+ g_array_set_size (layer->argv, 0);
+
+ if (argv != NULL)
+ {
+ char **copy = g_strdupv ((char **)argv);
+ g_array_append_vals (layer->argv, copy, g_strv_length (copy));
+ g_free (copy);
+ }
+}
+
+const char * const *
+ide_run_context_get_environ (IdeRunContext *self)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_val_if_fail (IDE_IS_RUN_CONTEXT (self), NULL);
+
+ layer = ide_run_context_current_layer (self);
+
+ return (const char * const *)(gpointer)layer->env->data;
+}
+
+void
+ide_run_context_set_environ (IdeRunContext *self,
+ const char * const *environ)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ layer = ide_run_context_current_layer (self);
+
+ g_array_set_size (layer->env, 0);
+
+ if (environ != NULL && environ[0] != NULL)
+ {
+ char **copy = g_strdupv ((char **)environ);
+ g_array_append_vals (layer->env, copy, g_strv_length (copy));
+ g_free (copy);
+ }
+}
+
+void
+ide_run_context_add_environ (IdeRunContext *self,
+ const char * const *environ)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ if (environ == NULL || environ[0] == NULL)
+ return;
+
+ layer = ide_run_context_current_layer (self);
+
+ for (guint i = 0; environ[i]; i++)
+ {
+ const char *pair = environ[i];
+ const char *eq = strchr (pair, '=');
+ char **dest = NULL;
+ gsize keylen;
+
+ if (eq == NULL)
+ continue;
+
+ keylen = eq - pair;
+
+ for (guint j = 0; j < layer->env->len; j++)
+ {
+ const char *ele = g_array_index (layer->env, const char *, j);
+
+ if (strncmp (pair, ele, keylen) == 0 && ele[keylen] == '=')
+ {
+ dest = &g_array_index (layer->env, char *, j);
+ break;
+ }
+ }
+
+ if (dest == NULL)
+ {
+ g_array_set_size (layer->env, layer->env->len + 1);
+ dest = &g_array_index (layer->env, char *, layer->env->len - 1);
+ }
+
+ g_clear_pointer (dest, g_free);
+ *dest = g_strdup (pair);
+ }
+}
+
+const char *
+ide_run_context_get_cwd (IdeRunContext *self)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_val_if_fail (IDE_IS_RUN_CONTEXT (self), NULL);
+
+ layer = ide_run_context_current_layer (self);
+
+ return layer->cwd;
+}
+
+void
+ide_run_context_set_cwd (IdeRunContext *self,
+ const char *cwd)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ layer = ide_run_context_current_layer (self);
+
+ if (g_strcmp0 (cwd, layer->cwd) != 0)
+ {
+ g_free (layer->cwd);
+ layer->cwd = g_strdup (cwd);
+ }
+}
+
+void
+ide_run_context_prepend_argv (IdeRunContext *self,
+ const char *arg)
+{
+ IdeRunContextLayer *layer;
+ char *copy;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+ g_return_if_fail (arg != NULL);
+
+ layer = ide_run_context_current_layer (self);
+
+ copy = g_strdup (arg);
+ g_array_insert_val (layer->argv, 0, copy);
+}
+
+void
+ide_run_context_prepend_args (IdeRunContext *self,
+ const char * const *args)
+{
+ IdeRunContextLayer *layer;
+ char **copy;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ if (args == NULL || args[0] == NULL)
+ return;
+
+ layer = ide_run_context_current_layer (self);
+
+ copy = g_strdupv ((char **)args);
+ g_array_insert_vals (layer->argv, 0, copy, g_strv_length (copy));
+ g_free (copy);
+}
+
+void
+ide_run_context_append_argv (IdeRunContext *self,
+ const char *arg)
+{
+ IdeRunContextLayer *layer;
+ char *copy;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+ g_return_if_fail (arg != NULL);
+
+ layer = ide_run_context_current_layer (self);
+
+ copy = g_strdup (arg);
+ g_array_append_val (layer->argv, copy);
+}
+
+void
+ide_run_context_append_formatted (IdeRunContext *self,
+ const char *format,
+ ...)
+{
+ g_autofree char *arg = NULL;
+ va_list args;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+ g_return_if_fail (format != NULL);
+
+ va_start (args, format);
+ arg = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ ide_run_context_append_argv (self, arg);
+}
+
+void
+ide_run_context_append_args (IdeRunContext *self,
+ const char * const *args)
+{
+ IdeRunContextLayer *layer;
+ char **copy;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ if (args == NULL || args[0] == NULL)
+ return;
+
+ layer = ide_run_context_current_layer (self);
+
+ copy = g_strdupv ((char **)args);
+ g_array_append_vals (layer->argv, copy, g_strv_length (copy));
+ g_free (copy);
+}
+
+gboolean
+ide_run_context_append_args_parsed (IdeRunContext *self,
+ const char *args,
+ GError **error)
+{
+ IdeRunContextLayer *layer;
+ char **argv = NULL;
+ int argc;
+
+ g_return_val_if_fail (IDE_IS_RUN_CONTEXT (self), FALSE);
+ g_return_val_if_fail (args != NULL, FALSE);
+
+ layer = ide_run_context_current_layer (self);
+
+ if (!g_shell_parse_argv (args, &argc, &argv, error))
+ return FALSE;
+
+ g_array_append_vals (layer->argv, argv, argc);
+ g_free (argv);
+
+ return TRUE;
+}
+
+void
+ide_run_context_take_fd (IdeRunContext *self,
+ int source_fd,
+ int dest_fd)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+ g_return_if_fail (source_fd > -1);
+ g_return_if_fail (dest_fd > -1);
+
+ layer = ide_run_context_current_layer (self);
+
+ ide_unix_fd_map_take (layer->unix_fd_map, source_fd, dest_fd);
+}
+
+const char *
+ide_run_context_getenv (IdeRunContext *self,
+ const char *key)
+{
+ IdeRunContextLayer *layer;
+ gsize keylen;
+
+ g_return_val_if_fail (IDE_IS_RUN_CONTEXT (self), NULL);
+ g_return_val_if_fail (key != NULL, NULL);
+
+ layer = ide_run_context_current_layer (self);
+
+ keylen = strlen (key);
+
+ for (guint i = 0; i < layer->env->len; i++)
+ {
+ const char *envvar = g_array_index (layer->env, const char *, i);
+
+ if (strncmp (key, envvar, keylen) == 0 && envvar[keylen] == '=')
+ return &envvar[keylen+1];
+ }
+
+ return NULL;
+}
+
+void
+ide_run_context_setenv (IdeRunContext *self,
+ const char *key,
+ const char *value)
+{
+ IdeRunContextLayer *layer;
+ char *element;
+ gsize keylen;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+ g_return_if_fail (key != NULL);
+
+ if (value == NULL)
+ {
+ ide_run_context_unsetenv (self, key);
+ return;
+ }
+
+ layer = ide_run_context_current_layer (self);
+
+ keylen = strlen (key);
+ element = g_strconcat (key, "=", value, NULL);
+
+ g_array_append_val (layer->env, element);
+
+ for (guint i = 0; i < layer->env->len-1; i++)
+ {
+ const char *envvar = g_array_index (layer->env, const char *, i);
+
+ if (strncmp (key, envvar, keylen) == 0 && envvar[keylen] == '=')
+ {
+ g_array_remove_index_fast (layer->env, i);
+ break;
+ }
+ }
+}
+
+void
+ide_run_context_unsetenv (IdeRunContext *self,
+ const char *key)
+{
+ IdeRunContextLayer *layer;
+ gsize len;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+ g_return_if_fail (key != NULL);
+
+ layer = ide_run_context_current_layer (self);
+
+ len = strlen (key);
+
+ for (guint i = 0; i < layer->env->len; i++)
+ {
+ const char *envvar = g_array_index (layer->env, const char *, i);
+
+ if (strncmp (key, envvar, len) == 0 && envvar[len] == '=')
+ {
+ g_array_remove_index_fast (layer->env, i);
+ return;
+ }
+ }
+}
+
+void
+ide_run_context_environ_to_argv (IdeRunContext *self)
+{
+ IdeRunContextLayer *layer;
+ const char **copy;
+
+ g_assert (IDE_IS_RUN_CONTEXT (self));
+
+ layer = ide_run_context_current_layer (self);
+
+ if (layer->env->len == 0)
+ return;
+
+ copy = (const char **)g_new0 (char *, layer->env->len + 2);
+ copy[0] = "env";
+ for (guint i = 0; i < layer->env->len; i++)
+ copy[1+i] = g_array_index (layer->env, const char *, i);
+ ide_run_context_prepend_args (self, (const char * const *)copy);
+ g_free (copy);
+
+ g_array_set_size (layer->env, 0);
+}
+
+static gboolean
+ide_run_context_default_handler (IdeRunContext *self,
+ const char * const *argv,
+ const char * const *env,
+ const char *cwd,
+ IdeUnixFDMap *unix_fd_map,
+ gpointer user_data,
+ GError **error)
+{
+ IdeRunContextLayer *layer;
+
+ g_assert (IDE_IS_RUN_CONTEXT (self));
+ g_assert (argv != NULL);
+ g_assert (env != NULL);
+ g_assert (IDE_IS_UNIX_FD_MAP (unix_fd_map));
+
+ layer = ide_run_context_current_layer (self);
+
+ if (cwd != NULL)
+ {
+ /* If the working directories do not match, we can't satisfy this and
+ * need to error out.
+ */
+ if (layer->cwd != NULL && !ide_str_equal (cwd, layer->cwd))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ "Cannot resolve differently requested cwd: %s and %s",
+ cwd, layer->cwd);
+ return FALSE;
+ }
+
+ ide_run_context_set_cwd (self, cwd);
+ }
+
+ /* Merge all the FDs unless there are collisions */
+ if (!ide_unix_fd_map_steal_from (layer->unix_fd_map, unix_fd_map, error))
+ return FALSE;
+
+ if (env[0] != NULL)
+ {
+ if (argv[0] == NULL)
+ {
+ ide_run_context_add_environ (self, env);
+ }
+ else
+ {
+ ide_run_context_append_argv (self, "env");
+ ide_run_context_append_args (self, env);
+ }
+ }
+
+ if (argv[0] != NULL)
+ ide_run_context_append_args (self, argv);
+
+ return TRUE;
+}
+
+static gboolean
+ide_run_context_callback_layer (IdeRunContext *self,
+ IdeRunContextLayer *layer,
+ GError **error)
+{
+ IdeRunContextHandler handler;
+ gpointer handler_data;
+ gboolean ret;
+
+ g_assert (IDE_IS_RUN_CONTEXT (self));
+ g_assert (layer != NULL);
+ g_assert (layer != &self->root);
+
+ handler = layer->handler ? layer->handler : ide_run_context_default_handler;
+ handler_data = layer->handler ? layer->handler_data : NULL;
+
+ ret = handler (self,
+ (const char * const *)(gpointer)layer->argv->data,
+ (const char * const *)(gpointer)layer->env->data,
+ layer->cwd,
+ layer->unix_fd_map,
+ handler_data,
+ error);
+
+ ide_run_context_layer_free (layer);
+
+ return ret;
+}
+
+/**
+ * ide_run_context_end:
+ * @self: a #IdeRunContext
+ *
+ * Returns: (transfer full): an #IdeSubprocessLauncher if successful; otherwise
+ * %NULL and @error is set.
+ */
+IdeSubprocessLauncher *
+ide_run_context_end (IdeRunContext *self,
+ GError **error)
+{
+ g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+ guint length;
+
+ g_return_val_if_fail (IDE_IS_RUN_CONTEXT (self), NULL);
+ g_return_val_if_fail (self->ended == FALSE, NULL);
+
+#if 0
+ {
+ guint j = 0;
+ for (const GList *iter = self->layers.head;
+ iter != NULL;
+ iter = iter->next)
+ {
+ IdeRunContextLayer *layer = iter->data;
+
+ g_print ("[%d]: CWD: %s\n", j++, layer->cwd);
+ g_print (" N FDS: %u\n", ide_unix_fd_map_get_length (layer->unix_fd_map));
+ g_print (" Environment: ");
+ for (guint i = 0; i < layer->env->len; i++)
+ g_print ("%s ", g_array_index (layer->env, char *, i));
+ g_print ("\n");
+ g_print (" Arguments: ");
+ for (guint i = 0; i < layer->argv->len; i++)
+ g_print ("%s ", g_array_index (layer->argv, char *, i));
+ g_print ("\n");
+
+ }
+ }
+#endif
+
+ self->ended = TRUE;
+
+ while (self->layers.length > 1)
+ {
+ IdeRunContextLayer *layer = ide_run_context_current_layer (self);
+
+ g_queue_unlink (&self->layers, &layer->qlink);
+
+ if (!ide_run_context_callback_layer (self, layer, error))
+ return FALSE;
+ }
+
+ launcher = ide_subprocess_launcher_new (0);
+
+ ide_subprocess_launcher_set_argv (launcher, ide_run_context_get_argv (self));
+ ide_subprocess_launcher_set_environ (launcher, ide_run_context_get_environ (self));
+ ide_subprocess_launcher_set_cwd (launcher, ide_run_context_get_cwd (self));
+
+ length = ide_unix_fd_map_get_length (self->root.unix_fd_map);
+
+ for (guint i = 0; i < length; i++)
+ {
+ int source_fd;
+ int dest_fd;
+
+ source_fd = ide_unix_fd_map_steal (self->root.unix_fd_map, i, &dest_fd);
+
+ if (source_fd != -1 && dest_fd != -1)
+ {
+ if (dest_fd == STDIN_FILENO)
+ ide_subprocess_launcher_take_stdin_fd (launcher, source_fd);
+ else if (dest_fd == STDOUT_FILENO)
+ ide_subprocess_launcher_take_stdout_fd (launcher, source_fd);
+ else if (dest_fd == STDERR_FILENO)
+ ide_subprocess_launcher_take_stderr_fd (launcher, source_fd);
+ else
+ ide_subprocess_launcher_take_fd (launcher, source_fd, dest_fd);
+ }
+ }
+
+ return g_steal_pointer (&launcher);
+}
+
+/**
+ * ide_run_context_spawn:
+ * @self: a #IdeRunContext
+ *
+ * Spwans the run command.
+ *
+ * If there is a failure to build the command into a subprocess launcher,
+ * then %NULL is returned and @error is set.
+ *
+ * If the subprocess fails to launch, then %NULL is returned and @error is set.
+ *
+ * Returns: (transfer full): an #IdeSubprocess if successful; otherwise %NULL
+ * and @error is set.
+ */
+IdeSubprocess *
+ide_run_context_spawn (IdeRunContext *self,
+ GError **error)
+{
+ g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+ g_autoptr(IdeSubprocess) ret = NULL;
+
+ IDE_ENTRY;
+
+ g_return_val_if_fail (IDE_IS_RUN_CONTEXT (self), NULL);
+
+ if (!(launcher = ide_run_context_end (self, error)))
+ IDE_RETURN (NULL);
+
+ if (!(ret = ide_subprocess_launcher_spawn (launcher, NULL, error)))
+ IDE_RETURN (NULL);
+
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (ret), NULL);
+
+ IDE_RETURN (g_steal_pointer (&ret));
+}
+
+/**
+ * ide_run_context_merge_unix_fd_map:
+ * @self: a #IdeRunContext
+ * @unix_fd_map: a #IdeUnixFDMap
+ * @error: a #GError, or %NULL
+ *
+ * Merges the #IdeUnixFDMap into the current layer.
+ *
+ * If there are collisions in destination FDs, then that may cause an
+ * error and %FALSE is returned.
+ *
+ * @unix_fd_map will have the FDs stolen using ide_unix_fd_map_steal_from()
+ * which means that if successful, @unix_fd_map will not have any open
+ * file-descriptors after calling this function.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+ide_run_context_merge_unix_fd_map (IdeRunContext *self,
+ IdeUnixFDMap *unix_fd_map,
+ GError **error)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_val_if_fail (IDE_IS_RUN_CONTEXT (self), FALSE);
+ g_return_val_if_fail (IDE_IS_UNIX_FD_MAP (unix_fd_map), FALSE);
+
+ layer = ide_run_context_current_layer (self);
+
+ return ide_unix_fd_map_steal_from (layer->unix_fd_map, unix_fd_map, error);
+}
+
+/**
+ * ide_run_context_set_pty_fd:
+ * @self: an #IdeRunContext
+ * @consumer_fd: the FD of the PTY consumer
+ *
+ * Sets up a PTY for the run context that will communicate with the
+ * consumer. The consumer is the generally the widget that is rendering
+ * the PTY contents and the producer is the FD that is connected to the
+ * subprocess.
+ */
+void
+ide_run_context_set_pty_fd (IdeRunContext *self,
+ int consumer_fd)
+{
+ int stdin_fd = -1;
+ int stdout_fd = -1;
+ int stderr_fd = -1;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+
+ if (consumer_fd < 0)
+ return;
+
+ if (-1 == (stdin_fd = ide_pty_intercept_create_producer (consumer_fd, TRUE)))
+ {
+ int errsv = errno;
+ g_critical ("Failed to create PTY device: %s", g_strerror (errsv));
+ return;
+ }
+
+ if (-1 == (stdout_fd = dup (stdin_fd)))
+ {
+ int errsv = errno;
+ g_critical ("Failed to dup stdout FD: %s", g_strerror (errsv));
+ }
+
+ if (-1 == (stderr_fd = dup (stdin_fd)))
+ {
+ int errsv = errno;
+ g_critical ("Failed to dup stderr FD: %s", g_strerror (errsv));
+ }
+
+ g_assert (stdin_fd > -1);
+ g_assert (stdout_fd > -1);
+ g_assert (stderr_fd > -1);
+
+ ide_run_context_take_fd (self, stdin_fd, STDIN_FILENO);
+ ide_run_context_take_fd (self, stdout_fd, STDOUT_FILENO);
+ ide_run_context_take_fd (self, stderr_fd, STDERR_FILENO);
+}
+
+/**
+ * ide_run_context_set_pty:
+ * @self: a #IdeRunContext
+ *
+ * Sets the PTY for a run context.
+ */
+void
+ide_run_context_set_pty (IdeRunContext *self,
+ VtePty *pty)
+{
+ int consumer_fd;
+
+ g_return_if_fail (IDE_IS_RUN_CONTEXT (self));
+ g_return_if_fail (VTE_IS_PTY (pty));
+
+ consumer_fd = vte_pty_get_fd (pty);
+
+ if (consumer_fd != -1)
+ ide_run_context_set_pty_fd (self, consumer_fd);
+}
+
+/**
+ * ide_run_context_create_stdio_stream:
+ * @self: a #IdeRunContext
+ * @error: a location for a #GError
+ *
+ * Creates a stream to communicate with the subprocess using stdin/stdout.
+ *
+ * The stream is created using UNIX pipes which are attached to the
+ * stdin/stdout of the child process.
+ *
+ * Returns: (transfer full): a #GIOStream if successful; otherwise
+ * %NULL and @error is set.
+ */
+GIOStream *
+ide_run_context_create_stdio_stream (IdeRunContext *self,
+ GError **error)
+{
+ IdeRunContextLayer *layer;
+
+ g_return_val_if_fail (IDE_IS_RUN_CONTEXT (self), NULL);
+
+ layer = ide_run_context_current_layer (self);
+
+ return ide_unix_fd_map_create_stream (layer->unix_fd_map,
+ STDIN_FILENO,
+ STDOUT_FILENO,
+ error);
+}
diff --git a/src/libide/foundry/ide-run-context.h b/src/libide/foundry/ide-run-context.h
new file mode 100644
index 000000000..f14e920ef
--- /dev/null
+++ b/src/libide/foundry/ide-run-context.h
@@ -0,0 +1,145 @@
+/* ide-run-context.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_FOUNDRY_INSIDE) && !defined (IDE_FOUNDRY_COMPILATION)
+# error "Only <libide-foundry.h> can be included directly."
+#endif
+
+#include <vte/vte.h>
+
+#include <libide-threading.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUN_CONTEXT (ide_run_context_get_type())
+
+IDE_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (IdeRunContext, ide_run_context, IDE, RUN_CONTEXT, GObject)
+
+/**
+ * IdeRunContextHandler:
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error must be set.
+ */
+typedef gboolean (*IdeRunContextHandler) (IdeRunContext *run_context,
+ const char * const *argv,
+ const char * const *env,
+ const char *cwd,
+ IdeUnixFDMap *unix_fd_map,
+ gpointer user_data,
+ GError **error);
+
+IDE_AVAILABLE_IN_ALL
+IdeRunContext *ide_run_context_new (void);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_push (IdeRunContext *self,
+ IdeRunContextHandler handler,
+ gpointer handler_data,
+ GDestroyNotify handler_data_destroy);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_push_error (IdeRunContext *self,
+ GError *error);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_push_host (IdeRunContext *self);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_push_shell (IdeRunContext *self,
+ gboolean login);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_push_expansion (IdeRunContext *self,
+ const char * const *environ);
+IDE_AVAILABLE_IN_ALL
+const char * const *ide_run_context_get_argv (IdeRunContext *self);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_set_argv (IdeRunContext *self,
+ const char * const *argv);
+IDE_AVAILABLE_IN_ALL
+const char * const *ide_run_context_get_environ (IdeRunContext *self);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_set_environ (IdeRunContext *self,
+ const char * const *environ);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_add_environ (IdeRunContext *self,
+ const char * const *environ);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_add_minimal_environment (IdeRunContext *self);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_environ_to_argv (IdeRunContext *self);
+IDE_AVAILABLE_IN_ALL
+const char *ide_run_context_get_cwd (IdeRunContext *self);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_set_cwd (IdeRunContext *self,
+ const char *cwd);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_set_pty_fd (IdeRunContext *self,
+ int consumer_fd);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_set_pty (IdeRunContext *self,
+ VtePty *pty);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_take_fd (IdeRunContext *self,
+ int source_fd,
+ int dest_fd);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_run_context_merge_unix_fd_map (IdeRunContext *self,
+ IdeUnixFDMap *unix_fd_map,
+ GError **error);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_prepend_argv (IdeRunContext *self,
+ const char *arg);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_prepend_args (IdeRunContext *self,
+ const char * const *args);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_append_argv (IdeRunContext *self,
+ const char *arg);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_append_args (IdeRunContext *self,
+ const char * const *args);
+IDE_AVAILABLE_IN_ALL
+gboolean ide_run_context_append_args_parsed (IdeRunContext *self,
+ const char *args,
+ GError **error);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_append_formatted (IdeRunContext *self,
+ const char *format,
+ ...) G_GNUC_PRINTF (2, 3);
+IDE_AVAILABLE_IN_ALL
+const char *ide_run_context_getenv (IdeRunContext *self,
+ const char *key);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_setenv (IdeRunContext *self,
+ const char *key,
+ const char *value);
+IDE_AVAILABLE_IN_ALL
+void ide_run_context_unsetenv (IdeRunContext *self,
+ const char *key);
+IDE_AVAILABLE_IN_ALL
+GIOStream *ide_run_context_create_stdio_stream (IdeRunContext *self,
+ GError **error);
+IDE_AVAILABLE_IN_ALL
+IdeSubprocessLauncher *ide_run_context_end (IdeRunContext *self,
+ GError **error);
+IDE_AVAILABLE_IN_ALL
+IdeSubprocess *ide_run_context_spawn (IdeRunContext *self,
+ GError **error);
+
+G_END_DECLS
diff --git a/src/libide/foundry/meson.build b/src/libide/foundry/meson.build
index 3173ff9df..6b96e17f5 100644
--- a/src/libide/foundry/meson.build
+++ b/src/libide/foundry/meson.build
@@ -40,6 +40,7 @@ libide_foundry_public_headers = [
'ide-pipeline-stage-transfer.h',
'ide-pipeline-stage.h',
'ide-pipeline.h',
+ 'ide-run-context.h',
'ide-run-manager.h',
'ide-runner-addin.h',
'ide-runner.h',
@@ -114,6 +115,7 @@ libide_foundry_public_sources = [
'ide-pipeline-stage-transfer.c',
'ide-pipeline-stage.c',
'ide-pipeline.c',
+ 'ide-run-context.c',
'ide-run-manager.c',
'ide-runner-addin.c',
'ide-runner.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]