gem-graph-client/libide/foundry/ide-runner.c

1529 lines
39 KiB
C

/* ide-runner.c
*
* Copyright 2016-2019 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-runner"
#include "config.h"
#include <dazzle.h>
#include <errno.h>
#include <glib/gi18n.h>
#include <libide-io.h>
#include <libide-threading.h>
#include <libpeas/peas.h>
#include <stdlib.h>
#include <unistd.h>
#include "ide-build-target.h"
#include "ide-config-manager.h"
#include "ide-config.h"
#include "ide-foundry-compat.h"
#include "ide-runner-addin.h"
#include "ide-runner.h"
#include "ide-runtime.h"
typedef struct
{
PeasExtensionSet *addins;
IdeEnvironment *env;
IdeBuildTarget *build_target;
GArray *fd_mapping;
gchar *cwd;
IdeSubprocess *subprocess;
GQueue argv;
GSubprocessFlags flags;
VtePty *pty;
gint child_fd;
guint clear_env : 1;
guint failed : 1;
guint run_on_host : 1;
guint disable_pty : 1;
} IdeRunnerPrivate;
typedef struct
{
GSList *prehook_queue;
GSList *posthook_queue;
} IdeRunnerRunState;
typedef struct
{
gint source_fd;
gint dest_fd;
} FdMapping;
enum {
PROP_0,
PROP_ARGV,
PROP_BUILD_TARGET,
PROP_CLEAR_ENV,
PROP_CWD,
PROP_DISABLE_PTY,
PROP_ENV,
PROP_FAILED,
PROP_RUN_ON_HOST,
N_PROPS
};
enum {
EXITED,
SPAWNED,
N_SIGNALS
};
static void ide_runner_tick_posthook (IdeTask *task);
static void ide_runner_tick_prehook (IdeTask *task);
static void ide_runner_tick_run (IdeTask *task);
G_DEFINE_TYPE_WITH_PRIVATE (IdeRunner, ide_runner, IDE_TYPE_OBJECT)
static GParamSpec *properties [N_PROPS];
static guint signals [N_SIGNALS];
static IdeRunnerAddin *
pop_runner_addin (GSList **list)
{
IdeRunnerAddin *ret;
g_assert (list != NULL);
g_assert (*list != NULL);
ret = (*list)->data;
*list = g_slist_delete_link (*list, *list);
return ret;
}
static void
ide_runner_run_state_free (gpointer data)
{
IdeRunnerRunState *state = data;
g_slist_foreach (state->prehook_queue, (GFunc)g_object_unref, NULL);
g_slist_free (state->prehook_queue);
g_slist_foreach (state->posthook_queue, (GFunc)g_object_unref, NULL);
g_slist_free (state->posthook_queue);
g_slice_free (IdeRunnerRunState, state);
}
static void
ide_runner_run_wait_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeSubprocess *subprocess = (IdeSubprocess *)object;
IdeRunnerPrivate *priv;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
IdeRunner *self;
IDE_ENTRY;
g_assert (IDE_IS_SUBPROCESS (subprocess));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
self = ide_task_get_source_object (task);
priv = ide_runner_get_instance_private (self);
g_assert (IDE_IS_RUNNER (self));
g_clear_object (&priv->subprocess);
g_signal_emit (self, signals [EXITED], 0);
if (!ide_subprocess_wait_finish (subprocess, result, &error))
{
ide_task_return_error (task, g_steal_pointer (&error));
IDE_EXIT;
}
if (ide_subprocess_get_if_exited (subprocess))
{
gint exit_code;
exit_code = ide_subprocess_get_exit_status (subprocess);
if (exit_code == EXIT_SUCCESS)
{
ide_task_return_boolean (task, TRUE);
IDE_EXIT;
}
}
ide_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"%s",
_("Process quit unexpectedly"));
IDE_EXIT;
}
static IdeSubprocessLauncher *
ide_runner_real_create_launcher (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
IdeConfigManager *config_manager;
IdeSubprocessLauncher *ret;
IdeConfig *config;
IdeContext *context;
IdeRuntime *runtime;
g_assert (IDE_IS_RUNNER (self));
context = ide_object_get_context (IDE_OBJECT (self));
config_manager = ide_config_manager_from_context (context);
config = ide_config_manager_get_current (config_manager);
runtime = ide_config_get_runtime (config);
ret = ide_runtime_create_launcher (runtime, NULL);
if (ret != NULL && priv->cwd != NULL)
ide_subprocess_launcher_set_cwd (ret, priv->cwd);
return ret;
}
static void
ide_runner_real_run_async (IdeRunner *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_autoptr(IdeTask) task = NULL;
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
g_autoptr(IdeSubprocess) subprocess = NULL;
IdeConfigManager *config_manager;
IdeConfig *config;
const gchar *identifier;
IdeContext *context;
IdeRuntime *runtime;
g_autoptr(GError) error = NULL;
IDE_ENTRY;
g_assert (IDE_IS_RUNNER (self));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_runner_real_run_async);
context = ide_object_get_context (IDE_OBJECT (self));
config_manager = ide_config_manager_from_context (context);
config = ide_config_manager_get_current (config_manager);
runtime = ide_config_get_runtime (config);
if (runtime != NULL)
launcher = IDE_RUNNER_GET_CLASS (self)->create_launcher (self);
if (launcher == NULL)
launcher = ide_subprocess_launcher_new (0);
ide_subprocess_launcher_set_flags (launcher, priv->flags);
/*
* If we have a tty_fd set, then we want to override our stdin,
* stdout, and stderr fds with our TTY.
*/
if (!priv->disable_pty && (priv->child_fd != -1 || priv->pty != NULL))
{
if (priv->child_fd == -1)
{
gint master_fd;
gint tty_fd;
g_assert (priv->pty != NULL);
master_fd = vte_pty_get_fd (priv->pty);
errno = 0;
if (-1 == (tty_fd = ide_pty_intercept_create_slave (master_fd, TRUE)))
g_critical ("Failed to create TTY device: %s", g_strerror (errno));
g_assert (tty_fd == -1 || isatty (tty_fd));
ide_runner_take_tty_fd (self, tty_fd);
}
if (!(priv->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE))
ide_subprocess_launcher_take_stdin_fd (launcher, dup (priv->child_fd));
if (!(priv->flags & (G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDOUT_SILENCE)))
ide_subprocess_launcher_take_stdout_fd (launcher, dup (priv->child_fd));
if (!(priv->flags & (G_SUBPROCESS_FLAGS_STDERR_PIPE | G_SUBPROCESS_FLAGS_STDERR_SILENCE)))
ide_subprocess_launcher_take_stderr_fd (launcher, dup (priv->child_fd));
}
/*
* Now map in any additionally requested FDs.
*/
if (priv->fd_mapping != NULL)
{
g_autoptr(GArray) ar = g_steal_pointer (&priv->fd_mapping);
for (guint i = 0; i < ar->len; i++)
{
FdMapping *map = &g_array_index (ar, FdMapping, i);
ide_subprocess_launcher_take_fd (launcher, map->source_fd, map->dest_fd);
}
}
/*
* We want the runners to run on the host so that we aren't captive to
* our containing system (flatpak, jhbuild, etc).
*/
ide_subprocess_launcher_set_run_on_host (launcher, priv->run_on_host);
/*
* We don't want the environment cleared because we need access to
* things like DISPLAY, WAYLAND_DISPLAY, and DBUS_SESSION_BUS_ADDRESS.
*/
ide_subprocess_launcher_set_clear_env (launcher, priv->clear_env);
/*
* Overlay the environment provided.
*/
ide_subprocess_launcher_overlay_environment (launcher, priv->env);
/*
* Push all of our configured arguments in order.
*/
for (const GList *iter = priv->argv.head; iter != NULL; iter = iter->next)
ide_subprocess_launcher_push_argv (launcher, iter->data);
/* Give the runner a final chance to mutate the launcher */
if (IDE_RUNNER_GET_CLASS (self)->fixup_launcher)
IDE_RUNNER_GET_CLASS (self)->fixup_launcher (self, launcher);
subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, &error);
g_assert (subprocess == NULL || IDE_IS_SUBPROCESS (subprocess));
if (subprocess == NULL)
{
ide_task_return_error (task, g_steal_pointer (&error));
IDE_GOTO (failure);
}
priv->subprocess = g_object_ref (subprocess);
identifier = ide_subprocess_get_identifier (subprocess);
g_signal_emit (self, signals [SPAWNED], 0, identifier);
ide_subprocess_wait_async (subprocess,
cancellable,
ide_runner_run_wait_cb,
g_steal_pointer (&task));
failure:
IDE_EXIT;
}
static gboolean
ide_runner_real_run_finish (IdeRunner *self,
GAsyncResult *result,
GError **error)
{
g_assert (IDE_IS_RUNNER (self));
g_assert (IDE_IS_TASK (result));
g_assert (ide_task_is_valid (IDE_TASK (result), self));
g_assert (ide_task_get_source_tag (IDE_TASK (result)) == ide_runner_real_run_async);
return ide_task_propagate_boolean (IDE_TASK (result), error);
}
static GOutputStream *
ide_runner_real_get_stdin (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
if (priv->subprocess)
return g_object_ref (ide_subprocess_get_stdin_pipe (priv->subprocess));
return NULL;
}
static GInputStream *
ide_runner_real_get_stdout (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
if (priv->subprocess)
return g_object_ref (ide_subprocess_get_stdout_pipe (priv->subprocess));
return NULL;
}
static GInputStream *
ide_runner_real_get_stderr (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
if (priv->subprocess)
return g_object_ref (ide_subprocess_get_stderr_pipe (priv->subprocess));
return NULL;
}
static void
ide_runner_real_force_quit (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
IDE_ENTRY;
g_assert (IDE_IS_RUNNER (self));
if (priv->subprocess != NULL)
ide_subprocess_force_exit (priv->subprocess);
IDE_EXIT;
}
static void
ide_runner_extension_added (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
IdeRunner *self = user_data;
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (plugin_info != NULL);
g_assert (IDE_IS_RUNNER_ADDIN (addin));
g_assert (IDE_IS_RUNNER (self));
ide_runner_addin_load (addin, self);
}
static void
ide_runner_extension_removed (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
IdeRunner *self = user_data;
g_assert (PEAS_IS_EXTENSION_SET (set));
g_assert (plugin_info != NULL);
g_assert (IDE_IS_RUNNER_ADDIN (addin));
g_assert (IDE_IS_RUNNER (self));
ide_runner_addin_unload (addin, self);
}
static void
ide_runner_constructed (GObject *object)
{
IdeRunner *self = (IdeRunner *)object;
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
G_OBJECT_CLASS (ide_runner_parent_class)->constructed (object);
priv->addins = peas_extension_set_new (peas_engine_get_default (),
IDE_TYPE_RUNNER_ADDIN,
NULL);
g_signal_connect (priv->addins,
"extension-added",
G_CALLBACK (ide_runner_extension_added),
self);
g_signal_connect (priv->addins,
"extension-removed",
G_CALLBACK (ide_runner_extension_removed),
self);
peas_extension_set_foreach (priv->addins,
ide_runner_extension_added,
self);
}
static void
ide_runner_finalize (GObject *object)
{
IdeRunner *self = (IdeRunner *)object;
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
g_queue_clear (&priv->argv);
g_clear_object (&priv->env);
g_clear_object (&priv->subprocess);
g_clear_object (&priv->build_target);
if (priv->child_fd != -1)
{
close (priv->child_fd);
priv->child_fd = -1;
}
if (priv->fd_mapping != NULL)
{
for (guint i = 0; i < priv->fd_mapping->len; i++)
{
FdMapping *map = &g_array_index (priv->fd_mapping, FdMapping, i);
if (map->source_fd != -1)
{
close (map->source_fd);
map->source_fd = -1;
}
}
}
g_clear_pointer (&priv->fd_mapping, g_array_unref);
g_clear_object (&priv->pty);
G_OBJECT_CLASS (ide_runner_parent_class)->finalize (object);
}
static void
ide_runner_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeRunner *self = IDE_RUNNER (object);
switch (prop_id)
{
case PROP_ARGV:
g_value_take_boxed (value, ide_runner_get_argv (self));
break;
case PROP_CLEAR_ENV:
g_value_set_boolean (value, ide_runner_get_clear_env (self));
break;
case PROP_CWD:
g_value_set_string (value, ide_runner_get_cwd (self));
break;
case PROP_DISABLE_PTY:
g_value_set_boolean (value, ide_runner_get_disable_pty (self));
break;
case PROP_ENV:
g_value_set_object (value, ide_runner_get_environment (self));
break;
case PROP_FAILED:
g_value_set_boolean (value, ide_runner_get_failed (self));
break;
case PROP_RUN_ON_HOST:
g_value_set_boolean (value, ide_runner_get_run_on_host (self));
break;
case PROP_BUILD_TARGET:
g_value_set_object (value, ide_runner_get_build_target (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_runner_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdeRunner *self = IDE_RUNNER (object);
switch (prop_id)
{
case PROP_ARGV:
ide_runner_set_argv (self, g_value_get_boxed (value));
break;
case PROP_CLEAR_ENV:
ide_runner_set_clear_env (self, g_value_get_boolean (value));
break;
case PROP_CWD:
ide_runner_set_cwd (self, g_value_get_string (value));
break;
case PROP_DISABLE_PTY:
ide_runner_set_disable_pty (self, g_value_get_boolean (value));
break;
case PROP_FAILED:
ide_runner_set_failed (self, g_value_get_boolean (value));
break;
case PROP_RUN_ON_HOST:
ide_runner_set_run_on_host (self, g_value_get_boolean (value));
break;
case PROP_BUILD_TARGET:
ide_runner_set_build_target (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_runner_class_init (IdeRunnerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = ide_runner_constructed;
object_class->finalize = ide_runner_finalize;
object_class->get_property = ide_runner_get_property;
object_class->set_property = ide_runner_set_property;
klass->run_async = ide_runner_real_run_async;
klass->run_finish = ide_runner_real_run_finish;
klass->create_launcher = ide_runner_real_create_launcher;
klass->get_stdin = ide_runner_real_get_stdin;
klass->get_stdout = ide_runner_real_get_stdout;
klass->get_stderr = ide_runner_real_get_stderr;
klass->force_quit = ide_runner_real_force_quit;
properties [PROP_ARGV] =
g_param_spec_boxed ("argv",
"Argv",
"The argument list for the command",
G_TYPE_STRV,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_CLEAR_ENV] =
g_param_spec_boolean ("clear-env",
"Clear Env",
"If the environment should be cleared before applying overrides",
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_CWD] =
g_param_spec_string ("cwd",
"Current Working Directory",
"The directory to use as the working directory for the process",
NULL,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_DISABLE_PTY] =
g_param_spec_boolean ("disable-pty",
"Disable PTY",
"If the pty should be disabled from use",
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_ENV] =
g_param_spec_object ("environment",
"Environment",
"The environment variables for the command",
IDE_TYPE_ENVIRONMENT,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
/**
* IdeRunner:failed:
*
* If the runner has "failed". This should be set if a plugin can determine
* that the runner cannot be executed due to an external issue. One such
* example might be a debugger plugin that cannot locate a suitable debugger
* to run the program.
*
* Since: 3.32
*/
properties [PROP_FAILED] =
g_param_spec_boolean ("failed",
"Failed",
"If the runner has failed",
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* IdeRunner:run-on-host:
*
* The "run-on-host" property indicates the program should be run on the
* host machine rather than inside the application sandbox.
*
* Since: 3.32
*/
properties [PROP_RUN_ON_HOST] =
g_param_spec_boolean ("run-on-host",
"Run on Host",
"Run on Host",
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* IdeRunner:build-target:
*
* The %IdeBuildTarget from which this %IdeRunner was constructed.
*
* This is useful to retrieve various properties related to the program
* that will be launched, such as what programming language it uses,
* or whether it's a graphical application, a command line tool or a test
* program.
*
* Since: 3.32
*/
properties [PROP_BUILD_TARGET] =
g_param_spec_object ("build-target",
"Build Target",
"Build Target",
IDE_TYPE_BUILD_TARGET,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
signals [EXITED] =
g_signal_new ("exited",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
signals [SPAWNED] =
g_signal_new ("spawned",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, NULL, NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1, G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
}
static void
ide_runner_init (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_queue_init (&priv->argv);
priv->env = ide_environment_new ();
priv->child_fd = -1;
priv->flags = 0;
}
/**
* ide_runner_get_stdin:
*
* Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
*
* Since: 3.32
*/
GOutputStream *
ide_runner_get_stdin (IdeRunner *self)
{
g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
return IDE_RUNNER_GET_CLASS (self)->get_stdin (self);
}
/**
* ide_runner_get_stdout:
*
* Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
*
* Since: 3.32
*/
GInputStream *
ide_runner_get_stdout (IdeRunner *self)
{
g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
return IDE_RUNNER_GET_CLASS (self)->get_stdout (self);
}
/**
* ide_runner_get_stderr:
*
* Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
*
* Since: 3.32
*/
GInputStream *
ide_runner_get_stderr (IdeRunner *self)
{
g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
return IDE_RUNNER_GET_CLASS (self)->get_stderr (self);
}
void
ide_runner_force_quit (IdeRunner *self)
{
IDE_ENTRY;
g_return_if_fail (IDE_IS_RUNNER (self));
if (IDE_RUNNER_GET_CLASS (self)->force_quit)
IDE_RUNNER_GET_CLASS (self)->force_quit (self);
IDE_EXIT;
}
void
ide_runner_set_argv (IdeRunner *self,
const gchar * const *argv)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
guint i;
g_return_if_fail (IDE_IS_RUNNER (self));
g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
g_queue_clear (&priv->argv);
if (argv != NULL)
{
for (i = 0; argv [i]; i++)
g_queue_push_tail (&priv->argv, g_strdup (argv [i]));
}
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
}
/**
* ide_runner_get_environment:
*
* Returns: (transfer none): The #IdeEnvironment the process launched uses.
*
* Since: 3.32
*/
IdeEnvironment *
ide_runner_get_environment (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
return priv->env;
}
/**
* ide_runner_get_argv:
*
* Gets the argument list as a newly allocated string array.
*
* Returns: (transfer full): A newly allocated string array that should
* be freed with g_strfreev().
*
* Since: 3.32
*/
gchar **
ide_runner_get_argv (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
GPtrArray *ar;
GList *iter;
g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
ar = g_ptr_array_new ();
for (iter = priv->argv.head; iter != NULL; iter = iter->next)
{
const gchar *param = iter->data;
g_ptr_array_add (ar, g_strdup (param));
}
g_ptr_array_add (ar, NULL);
return (gchar **)g_ptr_array_free (ar, FALSE);
}
static void
ide_runner_collect_addins_cb (PeasExtensionSet *set,
PeasPluginInfo *plugin_info,
PeasExtension *exten,
gpointer user_data)
{
GSList **list = user_data;
*list = g_slist_prepend (*list, exten);
}
static void
ide_runner_collect_addins (IdeRunner *self,
GSList **list)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_assert (IDE_IS_RUNNER (self));
g_assert (list != NULL);
peas_extension_set_foreach (priv->addins,
ide_runner_collect_addins_cb,
list);
}
static void
ide_runner_posthook_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
g_assert (IDE_IS_RUNNER_ADDIN (addin));
g_assert (G_IS_ASYNC_RESULT (result));
if (!ide_runner_addin_posthook_finish (addin, result, &error))
ide_task_return_error (task, g_steal_pointer (&error));
else
ide_runner_tick_posthook (task);
}
static void
ide_runner_tick_posthook (IdeTask *task)
{
IdeRunnerRunState *state;
g_assert (IDE_IS_TASK (task));
state = ide_task_get_task_data (task);
if (state->posthook_queue != NULL)
{
g_autoptr(IdeRunnerAddin) addin = NULL;
addin = pop_runner_addin (&state->posthook_queue);
ide_runner_addin_posthook_async (addin,
ide_task_get_cancellable (task),
ide_runner_posthook_cb,
g_object_ref (task));
return;
}
ide_task_return_boolean (task, TRUE);
}
static void
ide_runner_run_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeRunner *self = (IdeRunner *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
IDE_ENTRY;
g_assert (IDE_IS_RUNNER (self));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
if (!IDE_RUNNER_GET_CLASS (self)->run_finish (self, result, &error))
ide_task_return_error (task, g_steal_pointer (&error));
else
ide_runner_tick_posthook (task);
IDE_EXIT;
}
static void
ide_runner_tick_run (IdeTask *task)
{
IdeRunner *self;
IDE_ENTRY;
g_assert (IDE_IS_TASK (task));
self = ide_task_get_source_object (task);
g_assert (IDE_IS_RUNNER (self));
IDE_RUNNER_GET_CLASS (self)->run_async (self,
ide_task_get_cancellable (task),
ide_runner_run_cb,
g_object_ref (task));
IDE_EXIT;
}
static void
ide_runner_prehook_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
g_autoptr(IdeTask) task = user_data;
g_autoptr(GError) error = NULL;
IDE_ENTRY;
g_assert (IDE_IS_RUNNER_ADDIN (addin));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
if (!ide_runner_addin_prehook_finish (addin, result, &error))
ide_task_return_error (task, g_steal_pointer (&error));
else
ide_runner_tick_prehook (task);
IDE_EXIT;
}
static void
ide_runner_tick_prehook (IdeTask *task)
{
IdeRunnerRunState *state;
IDE_ENTRY;
g_assert (IDE_IS_TASK (task));
state = ide_task_get_task_data (task);
if (state->prehook_queue != NULL)
{
g_autoptr(IdeRunnerAddin) addin = NULL;
addin = pop_runner_addin (&state->prehook_queue);
ide_runner_addin_prehook_async (addin,
ide_task_get_cancellable (task),
ide_runner_prehook_cb,
g_object_ref (task));
IDE_EXIT;
}
ide_runner_tick_run (task);
IDE_EXIT;
}
void
ide_runner_run_async (IdeRunner *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(IdeTask) task = NULL;
IdeRunnerRunState *state;
IDE_ENTRY;
g_return_if_fail (IDE_IS_RUNNER (self));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
task = ide_task_new (self, cancellable, callback, user_data);
ide_task_set_source_tag (task, ide_runner_run_async);
ide_task_set_check_cancellable (task, FALSE);
ide_task_set_priority (task, G_PRIORITY_LOW);
/*
* We need to run the prehook functions for each addin first before we
* can call our IdeRunnerClass.run vfunc. Since these are async, we
* have to bring some state along with us.
*/
state = g_slice_new0 (IdeRunnerRunState);
ide_runner_collect_addins (self, &state->prehook_queue);
ide_runner_collect_addins (self, &state->posthook_queue);
ide_task_set_task_data (task, state, ide_runner_run_state_free);
ide_runner_tick_prehook (task);
IDE_EXIT;
}
gboolean
ide_runner_run_finish (IdeRunner *self,
GAsyncResult *result,
GError **error)
{
gboolean ret;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
ret = ide_task_propagate_boolean (IDE_TASK (result), error);
IDE_RETURN (ret);
}
void
ide_runner_append_argv (IdeRunner *self,
const gchar *param)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_if_fail (IDE_IS_RUNNER (self));
g_return_if_fail (param != NULL);
g_queue_push_tail (&priv->argv, g_strdup (param));
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
}
void
ide_runner_prepend_argv (IdeRunner *self,
const gchar *param)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_if_fail (IDE_IS_RUNNER (self));
g_return_if_fail (param != NULL);
g_queue_push_head (&priv->argv, g_strdup (param));
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
}
IdeRunner *
ide_runner_new (IdeContext *context)
{
g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
return g_object_new (IDE_TYPE_RUNNER,
NULL);
}
gboolean
ide_runner_get_run_on_host (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
return priv->run_on_host;
}
void
ide_runner_set_run_on_host (IdeRunner *self,
gboolean run_on_host)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
run_on_host = !!run_on_host;
if (run_on_host != priv->run_on_host)
{
priv->run_on_host = run_on_host;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUN_ON_HOST]);
}
}
GSubprocessFlags
ide_runner_get_flags (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_val_if_fail (IDE_IS_RUNNER (self), 0);
return priv->flags;
}
void
ide_runner_set_flags (IdeRunner *self,
GSubprocessFlags flags)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_if_fail (IDE_IS_RUNNER (self));
priv->flags = flags;
}
gboolean
ide_runner_get_clear_env (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
return priv->clear_env;
}
void
ide_runner_set_clear_env (IdeRunner *self,
gboolean clear_env)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_if_fail (IDE_IS_RUNNER (self));
clear_env = !!clear_env;
if (clear_env != priv->clear_env)
{
priv->clear_env = clear_env;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLEAR_ENV]);
}
}
/**
* ide_runner_set_pty:
* @self: a #IdeRunner
* @pty: (nullable): a #VtePty or %NULL
*
* Sets the #VtePty to use for the runner.
*
* This is equivalent to calling ide_runner_set_tty() with the
* result of vte_pty_get_fd().
*
* Since: 3.32
*/
void
ide_runner_set_pty (IdeRunner *self,
VtePty *pty)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_if_fail (IDE_IS_RUNNER (self));
g_return_if_fail (!pty || VTE_IS_PTY (pty));
g_set_object (&priv->pty, pty);
}
/**
* ide_runner_get_pty:
* @self: a #IdeRunner
*
* Gets the #VtePty that was assigned.
*
* Returns: (nullable) (transfer none): a #VtePty or %NULL
*
* Since: 3.34
*/
VtePty *
ide_runner_get_pty (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
return priv->pty;
}
static gint
sort_fd_mapping (gconstpointer a,
gconstpointer b)
{
const FdMapping *map_a = a;
const FdMapping *map_b = b;
return map_a->dest_fd - map_b->dest_fd;
}
/**
* ide_runner_take_fd:
* @self: An #IdeRunner
* @source_fd: the fd to map, this will be closed by #IdeRunner
* @dest_fd: the target FD in the spawned process, or -1 for next available
*
* This will ensure that @source_fd is mapped into the new process as @dest_fd.
* If @dest_fd is -1, then the next fd will be used and that value will be
* returned. Note that this is not a valid fd in the calling process, only
* within the destination process.
*
* Returns: @dest_fd or the FD or the next available dest_fd.
*
* Since: 3.32
*/
gint
ide_runner_take_fd (IdeRunner *self,
gint source_fd,
gint dest_fd)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
FdMapping map = { -1, -1 };
g_return_val_if_fail (IDE_IS_RUNNER (self), -1);
g_return_val_if_fail (source_fd > -1, -1);
if (priv->fd_mapping == NULL)
priv->fd_mapping = g_array_new (FALSE, FALSE, sizeof (FdMapping));
/*
* Quick and dirty hack to take the next FD, won't deal with people mapping
* to 1024 well, but we can fix that when we come across it.
*/
if (dest_fd < 0)
{
gint max_fd = 2;
for (guint i = 0; i < priv->fd_mapping->len; i++)
{
FdMapping *entry = &g_array_index (priv->fd_mapping, FdMapping, i);
if (entry->dest_fd > max_fd)
max_fd = entry->dest_fd;
}
dest_fd = max_fd + 1;
}
map.source_fd = source_fd;
map.dest_fd = dest_fd;
g_array_append_val (priv->fd_mapping, map);
g_array_sort (priv->fd_mapping, sort_fd_mapping);
return dest_fd;
}
/**
* ide_runner_get_runtime:
* @self: An #IdeRuntime
*
* This function will get the #IdeRuntime that will be used to execute the
* application. Consumers may want to use this to determine if a particular
* program is available (such as gdb, perf, strace, etc).
*
* Returns: (nullable) (transfer full): An #IdeRuntime or %NULL.
*
* Since: 3.32
*/
IdeRuntime *
ide_runner_get_runtime (IdeRunner *self)
{
IdeConfigManager *config_manager;
IdeConfig *config;
IdeContext *context;
IdeRuntime *runtime;
g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
if (IDE_RUNNER_GET_CLASS (self)->get_runtime)
return IDE_RUNNER_GET_CLASS (self)->get_runtime (self);
context = ide_object_get_context (IDE_OBJECT (self));
config_manager = ide_config_manager_from_context (context);
config = ide_config_manager_get_current (config_manager);
runtime = ide_config_get_runtime (config);
return runtime != NULL ? g_object_ref (runtime) : NULL;
}
gboolean
ide_runner_get_failed (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
return priv->failed;
}
void
ide_runner_set_failed (IdeRunner *self,
gboolean failed)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
IDE_ENTRY;
g_return_if_fail (IDE_IS_RUNNER (self));
failed = !!failed;
if (failed != priv->failed)
{
priv->failed = failed;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FAILED]);
}
IDE_EXIT;
}
/**
* ide_runner_get_cwd:
* @self: a #IdeRunner
*
* Returns: (nullable): The current working directory, or %NULL.
*
* Since: 3.32
*/
const gchar *
ide_runner_get_cwd (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
return priv->cwd;
}
/**
* ide_runner_set_cwd:
* @self: a #IdeRunner
* @cwd: (nullable): The working directory or %NULL
*
* Sets the directory to use when spawning the runner.
*
* Since: 3.32
*/
void
ide_runner_set_cwd (IdeRunner *self,
const gchar *cwd)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_if_fail (IDE_IS_RUNNER (self));
if (!ide_str_equal0 (priv->cwd, cwd))
{
g_free (priv->cwd);
priv->cwd = g_strdup (cwd);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CWD]);
}
}
/**
* ide_runner_push_args:
* @self: a #IdeRunner
* @args: (array zero-terminated=1) (element-type utf8) (nullable): the arguments
*
* Helper to call ide_runner_append_argv() for every argument
* contained in @args.
*
* Since: 3.32
*/
void
ide_runner_push_args (IdeRunner *self,
const gchar * const *args)
{
g_return_if_fail (IDE_IS_RUNNER (self));
if (args == NULL)
return;
for (guint i = 0; args[i] != NULL; i++)
ide_runner_append_argv (self, args[i]);
}
/**
* ide_runner_get_build_target:
* @self: a #IdeRunner
*
* Returns: (nullable) (transfer none): The %IdeBuildTarget associated with this %IdeRunner, or %NULL.
* See #IdeRunner:build-target for details.
*
* Since: 3.32
*/
IdeBuildTarget *
ide_runner_get_build_target (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
return priv->build_target;
}
/**
* ide_runner_set_build_target:
* @self: a #IdeRunner
* @build_target: (nullable): The build target, or %NULL
*
* Sets the build target associated with this runner.
*
* Since: 3.32
*/
void
ide_runner_set_build_target (IdeRunner *self,
IdeBuildTarget *build_target)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_if_fail (IDE_IS_RUNNER (self));
if (g_set_object (&priv->build_target, build_target))
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUILD_TARGET]);
}
gboolean
ide_runner_get_disable_pty (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
return priv->disable_pty;
}
void
ide_runner_set_disable_pty (IdeRunner *self,
gboolean disable_pty)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_if_fail (IDE_IS_RUNNER (self));
disable_pty = !!disable_pty;
if (disable_pty != priv->disable_pty)
{
priv->disable_pty = disable_pty;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISABLE_PTY]);
}
}
void
ide_runner_take_tty_fd (IdeRunner *self,
gint tty_fd)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_return_if_fail (IDE_IS_RUNNER (self));
g_return_if_fail (tty_fd > -1);
if (priv->child_fd != -1)
close (priv->child_fd);
priv->child_fd = tty_fd;
}
gint
ide_runner_get_max_fd (IdeRunner *self)
{
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
gint max_fd = 2;
g_return_val_if_fail (IDE_IS_RUNNER (self), 2);
if (priv->fd_mapping != NULL)
{
for (guint i = 0; i < priv->fd_mapping->len; i++)
{
const FdMapping *map = &g_array_index (priv->fd_mapping, FdMapping, i);
if (map->dest_fd > max_fd)
max_fd = map->dest_fd;
}
}
return max_fd;
}