[gnome-builder/wip/chergert/flatpak-breakout] sandboxing: add subprocess abstraction
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/flatpak-breakout] sandboxing: add subprocess abstraction
- Date: Fri, 9 Sep 2016 19:32:05 +0000 (UTC)
commit bc904efde72b11aaab184848581eacd31c865c3f
Author: Christian Hergert <chergert redhat com>
Date: Fri Sep 9 12:31:32 2016 -0700
sandboxing: add subprocess abstraction
To support Building applications while running from inside the sandbox, we
need to allow breaking out of the sandbox for such tooling. For example,
we want to run the build tooling for flatpaks from the host system (since
we wont have sufficient privileges inside the sandbox).
This abstracts our use of GSubprocess so that we can have multiple
implementations. Sadly, since GSubprocess is not an interface, we cannot
simply create an alternate implementation.
There are two implementations of the abstraction. IdeSimpleSubprocess is
just a wrapper around GSubprocess (since our API is almost identical).
Additionally, we have IdeHostProcess which is a process that runs within
the host system, via the --allow=devel support in flatpak. Also required
--talk-name=org.freedesktop.Flatpak.
contrib/egg/egg-counter.h | 2 +-
libide/Makefile.am | 11 +-
libide/buildsystem/ide-build-command.c | 31 +-
libide/buildsystem/ide-build-manager.c | 1 +
libide/buildsystem/ide-build-result.c | 9 +-
libide/buildsystem/ide-build-result.h | 2 +-
libide/buildsystem/ide-configuration-manager.c | 4 +
libide/buildsystem/ide-configuration.c | 17 +-
libide/ide-types.h | 3 +
libide/ide.h | 3 +-
libide/runner/ide-run-manager.c | 38 +-
libide/runner/ide-runner.c | 90 +-
libide/runner/ide-runner.h | 43 +-
libide/runtimes/ide-runtime-manager.c | 11 +-
libide/runtimes/ide-runtime.h | 2 +-
.../subprocess/ide-breakout-subprocess-private.h | 36 +
libide/subprocess/ide-breakout-subprocess.c | 1431 ++++++++++++++++++++
libide/subprocess/ide-breakout-subprocess.h | 29 +
libide/subprocess/ide-simple-subprocess.c | 310 +++++
libide/subprocess/ide-simple-subprocess.h | 34 +
.../ide-subprocess-launcher.c | 269 ++++-
.../ide-subprocess-launcher.h | 39 +-
libide/subprocess/ide-subprocess.c | 328 +++++
libide/subprocess/ide-subprocess.h | 141 ++
plugins/autotools/ide-autotools-build-system.c | 14 +-
plugins/autotools/ide-autotools-build-task.c | 66 +-
plugins/autotools/ide-makecache.c | 4 +-
plugins/flatpak/gbp-flatpak-runtime.c | 42 +-
plugins/jhbuild/jhbuild_plugin.py | 16 +
29 files changed, 2864 insertions(+), 162 deletions(-)
---
diff --git a/contrib/egg/egg-counter.h b/contrib/egg/egg-counter.h
index 412d3b9..6a74fdd 100644
--- a/contrib/egg/egg-counter.h
+++ b/contrib/egg/egg-counter.h
@@ -55,7 +55,7 @@
* To increment the counter in a function of your choice (but within the
* same module), use EGG_COUNTER_ADD, EGG_COUNTER_INC, EGG_COUNTER_DEC.
*
- * EGG_COUNTER_ADD (Symbol);
+ * EGG_COUNTER_INC (Symbol);
*
*
* Architecture Support
diff --git a/libide/Makefile.am b/libide/Makefile.am
index dddce43..cc1ef10 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -124,6 +124,8 @@ libide_1_0_la_public_headers = \
sourceview/ide-source-style-scheme.h \
sourceview/ide-source-view-mode.h \
sourceview/ide-source-view.h \
+ subprocess/ide-subprocess.h \
+ subprocess/ide-subprocess-launcher.h \
symbols/ide-symbol-node.h \
symbols/ide-symbol-resolver.h \
symbols/ide-symbol-tree.h \
@@ -175,7 +177,6 @@ libide_1_0_la_public_headers = \
workbench/ide-workbench-addin.h \
workbench/ide-workbench-header-bar.h \
workbench/ide-workbench.h \
- workers/ide-subprocess-launcher.h \
workers/ide-worker.h \
$(NULL)
@@ -287,6 +288,8 @@ libide_1_0_la_public_sources = \
sourceview/ide-source-style-scheme.c \
sourceview/ide-source-view-mode.c \
sourceview/ide-source-view.c \
+ subprocess/ide-subprocess.c \
+ subprocess/ide-subprocess-launcher.c \
symbols/ide-symbol-node.c \
symbols/ide-symbol-resolver.c \
symbols/ide-symbol-tree.c \
@@ -326,7 +329,6 @@ libide_1_0_la_public_sources = \
workbench/ide-workbench-header-bar.c \
workbench/ide-workbench-open.c \
workbench/ide-workbench.c \
- workers/ide-subprocess-launcher.c \
workers/ide-worker.c \
$(NULL)
@@ -416,6 +418,11 @@ libide_1_0_la_SOURCES = \
sourceview/ide-text-iter.h \
sourceview/ide-text-util.c \
sourceview/ide-text-util.h \
+ subprocess/ide-breakout-subprocess.c \
+ subprocess/ide-breakout-subprocess.h \
+ subprocess/ide-breakout-subprocess-private.h \
+ subprocess/ide-simple-subprocess.c \
+ subprocess/ide-simple-subprocess.h \
theatrics/ide-box-theatric.c \
theatrics/ide-box-theatric.h \
theming/ide-css-provider.c \
diff --git a/libide/buildsystem/ide-build-command.c b/libide/buildsystem/ide-build-command.c
index 3aa69e3..300133c 100644
--- a/libide/buildsystem/ide-build-command.c
+++ b/libide/buildsystem/ide-build-command.c
@@ -24,7 +24,8 @@
#include "buildsystem/ide-build-result.h"
#include "buildsystem/ide-environment.h"
#include "runtimes/ide-runtime.h"
-#include "workers/ide-subprocess-launcher.h"
+#include "subprocess/ide-subprocess.h"
+#include "subprocess/ide-subprocess-launcher.h"
typedef struct
{
@@ -46,17 +47,17 @@ ide_build_command_wait_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
- GSubprocess *subprocess = (GSubprocess *)object;
+ IdeSubprocess *subprocess = (IdeSubprocess *)object;
g_autoptr(GTask) task = user_data;
GError *error = NULL;
IDE_ENTRY;
- g_assert (G_IS_SUBPROCESS (subprocess));
+ g_assert (IDE_IS_SUBPROCESS (subprocess));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (G_IS_TASK (task));
- if (!g_subprocess_wait_finish (subprocess, result, &error))
+ if (!ide_subprocess_wait_finish (subprocess, result, &error))
{
g_task_return_error (task, error);
IDE_EXIT;
@@ -121,7 +122,7 @@ ide_build_command_real_run (IdeBuildCommand *self,
{
IdeBuildCommandPrivate *priv = ide_build_command_get_instance_private (self);
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
- g_autoptr(GSubprocess) subprocess = NULL;
+ g_autoptr(IdeSubprocess) subprocess = NULL;
gboolean ret;
IDE_ENTRY;
@@ -147,7 +148,7 @@ ide_build_command_real_run (IdeBuildCommand *self,
ide_build_result_log_subprocess (build_result, subprocess);
- ret = g_subprocess_wait (subprocess, cancellable, error);
+ ret = ide_subprocess_wait (subprocess, cancellable, error);
IDE_RETURN (ret);
}
@@ -163,7 +164,7 @@ ide_build_command_real_run_async (IdeBuildCommand *self,
{
IdeBuildCommandPrivate *priv = ide_build_command_get_instance_private (self);
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
- g_autoptr(GSubprocess) subprocess = NULL;
+ g_autoptr(IdeSubprocess) subprocess = NULL;
g_autoptr(GTask) task = NULL;
GError *error = NULL;
@@ -201,10 +202,10 @@ ide_build_command_real_run_async (IdeBuildCommand *self,
ide_build_result_log_subprocess (build_result, subprocess);
- g_subprocess_wait_async (subprocess,
- cancellable,
- ide_build_command_wait_cb,
- g_steal_pointer (&task));
+ ide_subprocess_wait_async (subprocess,
+ cancellable,
+ ide_build_command_wait_cb,
+ g_steal_pointer (&task));
IDE_EXIT;
}
@@ -214,10 +215,16 @@ ide_build_command_real_run_finish (IdeBuildCommand *self,
GAsyncResult *result,
GError **error)
{
+ gboolean ret;
+
+ IDE_ENTRY;
+
g_assert (IDE_IS_BUILD_COMMAND (self));
g_assert (G_IS_TASK (result));
- return g_task_propagate_boolean (G_TASK (result), error);
+ ret = g_task_propagate_boolean (G_TASK (result), error);
+
+ IDE_RETURN (ret);
}
static IdeBuildCommand *
diff --git a/libide/buildsystem/ide-build-manager.c b/libide/buildsystem/ide-build-manager.c
index 4ce6c24..4f8e383 100644
--- a/libide/buildsystem/ide-build-manager.c
+++ b/libide/buildsystem/ide-build-manager.c
@@ -530,6 +530,7 @@ ide_build_manager_build_cb (GObject *object,
if (build_result == NULL)
{
+ IDE_TRACE_MSG ("%s", error->message);
g_task_return_error (task, error);
IDE_GOTO (failure);
}
diff --git a/libide/buildsystem/ide-build-result.c b/libide/buildsystem/ide-build-result.c
index 6e7045e..f37139e 100644
--- a/libide/buildsystem/ide-build-result.c
+++ b/libide/buildsystem/ide-build-result.c
@@ -29,6 +29,7 @@
#include "buildsystem/ide-build-result-addin.h"
#include "diagnostics/ide-source-location.h"
#include "files/ide-file.h"
+#include "subprocess/ide-subprocess.h"
#define POINTER_MARK(p) GSIZE_TO_POINTER(GPOINTER_TO_SIZE(p)|1)
#define POINTER_UNMARK(p) GSIZE_TO_POINTER(GPOINTER_TO_SIZE(p)&~(gsize)1)
@@ -351,27 +352,27 @@ ide_build_result_tail_into (IdeBuildResult *self,
void
ide_build_result_log_subprocess (IdeBuildResult *self,
- GSubprocess *subprocess)
+ IdeSubprocess *subprocess)
{
IdeBuildResultPrivate *priv = ide_build_result_get_instance_private (self);
GInputStream *stdout_stream;
GInputStream *stderr_stream;
g_return_if_fail (IDE_IS_BUILD_RESULT (self));
- g_return_if_fail (G_IS_SUBPROCESS (subprocess));
+ g_return_if_fail (IDE_IS_SUBPROCESS (subprocess));
/* ensure lazily created streams are available */
(void)ide_build_result_get_stderr_stream (self);
(void)ide_build_result_get_stdout_stream (self);
- stderr_stream = g_subprocess_get_stderr_pipe (subprocess);
+ stderr_stream = ide_subprocess_get_stderr_pipe (subprocess);
if (stderr_stream)
ide_build_result_tail_into (self,
IDE_BUILD_RESULT_LOG_STDERR,
stderr_stream,
priv->stderr_writer);
- stdout_stream = g_subprocess_get_stdout_pipe (subprocess);
+ stdout_stream = ide_subprocess_get_stdout_pipe (subprocess);
if (stdout_stream)
ide_build_result_tail_into (self,
IDE_BUILD_RESULT_LOG_STDOUT,
diff --git a/libide/buildsystem/ide-build-result.h b/libide/buildsystem/ide-build-result.h
index 26b802f..6df1e7e 100644
--- a/libide/buildsystem/ide-build-result.h
+++ b/libide/buildsystem/ide-build-result.h
@@ -60,7 +60,7 @@ struct _IdeBuildResultClass
GInputStream *ide_build_result_get_stdout_stream (IdeBuildResult *result);
GInputStream *ide_build_result_get_stderr_stream (IdeBuildResult *result);
void ide_build_result_log_subprocess (IdeBuildResult *result,
- GSubprocess *subprocess);
+ IdeSubprocess *subprocess);
GTimeSpan ide_build_result_get_running_time (IdeBuildResult *self);
gboolean ide_build_result_get_running (IdeBuildResult *self);
void ide_build_result_set_running (IdeBuildResult *self,
diff --git a/libide/buildsystem/ide-configuration-manager.c b/libide/buildsystem/ide-configuration-manager.c
index 0aaa4ab..bdcd9b4 100644
--- a/libide/buildsystem/ide-configuration-manager.c
+++ b/libide/buildsystem/ide-configuration-manager.c
@@ -457,12 +457,16 @@ ide_configuration_manager_queue_writeback (IdeConfigurationManager *self)
{
g_assert (IDE_IS_CONFIGURATION_MANAGER (self));
+ IDE_ENTRY;
+
if (self->writeback_handler != 0)
g_source_remove (self->writeback_handler);
self->writeback_handler = g_timeout_add_seconds (WRITEBACK_TIMEOUT_SECS,
ide_configuration_manager_do_writeback,
self);
+
+ IDE_EXIT;
}
static void
diff --git a/libide/buildsystem/ide-configuration.c b/libide/buildsystem/ide-configuration.c
index 60ea879..0302b01 100644
--- a/libide/buildsystem/ide-configuration.c
+++ b/libide/buildsystem/ide-configuration.c
@@ -21,6 +21,7 @@
#include <string.h>
#include "ide-context.h"
+#include "ide-debug.h"
#include "ide-internal.h"
#include "buildsystem/ide-build-command-queue.h"
@@ -82,6 +83,14 @@ static GParamSpec *properties [N_PROPS];
static guint signals [LAST_SIGNAL];
static void
+ide_configuration_emit_changed (IdeConfiguration *self)
+{
+ g_assert (IDE_IS_CONFIGURATION (self));
+
+ g_signal_emit (self, signals [CHANGED], 0);
+}
+
+static void
ide_configuration_set_id (IdeConfiguration *self,
const gchar *id)
{
@@ -139,10 +148,14 @@ ide_configuration_environment_changed (IdeConfiguration *self,
guint removed,
IdeEnvironment *environment)
{
+ IDE_ENTRY;
+
g_assert (IDE_IS_CONFIGURATION (self));
g_assert (IDE_IS_ENVIRONMENT (environment));
ide_configuration_set_dirty (self, TRUE);
+
+ IDE_EXIT;
}
static void
@@ -727,7 +740,7 @@ ide_configuration_set_display_name (IdeConfiguration *self,
g_free (self->display_name);
self->display_name = g_strdup (display_name);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISPLAY_NAME]);
- g_signal_emit (self, signals [CHANGED], 0);
+ ide_configuration_emit_changed (self);
}
}
@@ -758,7 +771,7 @@ ide_configuration_set_dirty (IdeConfiguration *self,
* can queue a writeback of the configuration.
*/
self->sequence++;
- g_signal_emit (self, signals [CHANGED], 0);
+ ide_configuration_emit_changed (self);
}
/**
diff --git a/libide/ide-types.h b/libide/ide-types.h
index c5afe50..b4aee57 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -122,6 +122,9 @@ typedef struct _IdeSourceSnippets IdeSourceSnippets;
typedef struct _IdeSourceSnippetsManager IdeSourceSnippetsManager;
+typedef struct _IdeSubprocess IdeSubprocess;
+typedef struct _IdeSubprocessLauncher IdeSubprocessLauncher;
+
typedef struct _IdeSymbol IdeSymbol;
typedef struct _IdeSymbolResolver IdeSymbolResolver;
diff --git a/libide/ide.h b/libide/ide.h
index 20258c0..1b93c8e 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -115,6 +115,8 @@ G_BEGIN_DECLS
#include "sourceview/ide-source-map.h"
#include "sourceview/ide-source-style-scheme.h"
#include "sourceview/ide-source-view.h"
+#include "subprocess/ide-subprocess.h"
+#include "subprocess/ide-subprocess-launcher.h"
#include "symbols/ide-symbol-resolver.h"
#include "symbols/ide-symbol.h"
#include "symbols/ide-tags-builder.h"
@@ -148,7 +150,6 @@ G_BEGIN_DECLS
#include "workbench/ide-workbench-addin.h"
#include "workbench/ide-workbench-header-bar.h"
#include "workbench/ide-workbench.h"
-#include "workers/ide-subprocess-launcher.h"
#undef IDE_INSIDE
diff --git a/libide/runner/ide-run-manager.c b/libide/runner/ide-run-manager.c
index 42c6ff9..451cced 100644
--- a/libide/runner/ide-run-manager.c
+++ b/libide/runner/ide-run-manager.c
@@ -830,6 +830,25 @@ finish:
}
static void
+ide_run_manager_run_action_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeRunManager *self = (IdeRunManager *)object;
+ IdeContext *context;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (IDE_IS_RUN_MANAGER (self));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+
+ /* Propagate the error to the context */
+ if (!ide_run_manager_run_finish (self, result, &error))
+ ide_context_warning (context, "%s", error->message);
+}
+
+static void
ide_run_manager_activate_action (GActionGroup *group,
const gchar *action_name,
GVariant *parameter)
@@ -840,23 +859,34 @@ ide_run_manager_activate_action (GActionGroup *group,
g_assert (IDE_IS_RUN_MANAGER (self));
g_assert (action_name != NULL);
- if (g_variant_is_floating (parameter))
+ if (parameter != NULL && g_variant_is_floating (parameter))
sunk = g_variant_ref_sink (parameter);
if (FALSE) {}
else if (g_strcmp0 (action_name, "run-with-handler") == 0)
{
- const gchar *handler = g_variant_get_string (parameter, NULL);
+ const gchar *handler = NULL;
+
+ if (parameter != NULL)
+ g_variant_get_string (parameter, NULL);
/* "" translates to current handler */
if (handler && *handler)
ide_run_manager_set_handler (self, handler);
- ide_run_manager_run_async (self, NULL, NULL, NULL, NULL);
+ ide_run_manager_run_async (self,
+ NULL,
+ NULL,
+ ide_run_manager_run_action_cb,
+ NULL);
}
else if (g_strcmp0 (action_name, "run") == 0)
{
- ide_run_manager_run_async (self, NULL, NULL, NULL, NULL);
+ ide_run_manager_run_async (self,
+ NULL,
+ NULL,
+ ide_run_manager_run_action_cb,
+ NULL);
}
else if (g_strcmp0 (action_name, "stop") == 0)
{
diff --git a/libide/runner/ide-runner.c b/libide/runner/ide-runner.c
index 18bf152..077479b 100644
--- a/libide/runner/ide-runner.c
+++ b/libide/runner/ide-runner.c
@@ -27,12 +27,15 @@
#include "runner/ide-runner.h"
#include "runner/ide-runner-addin.h"
-#include "workers/ide-subprocess-launcher.h"
+#include "subprocess/ide-subprocess.h"
+#include "subprocess/ide-subprocess-launcher.h"
typedef struct
{
PeasExtensionSet *addins;
GQueue argv;
+ GSubprocessFlags flags;
+ guint run_on_host : 1;
} IdeRunnerPrivate;
typedef struct
@@ -44,6 +47,7 @@ typedef struct
enum {
PROP_0,
PROP_ARGV,
+ PROP_RUN_ON_HOST,
N_PROPS
};
@@ -96,14 +100,16 @@ ide_runner_run_wait_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
- GSubprocess *subprocess = (GSubprocess *)object;
+ IdeSubprocess *subprocess = (IdeSubprocess *)object;
g_autoptr(GTask) task = user_data;
GError *error = NULL;
IdeRunner *self;
IDE_ENTRY;
- g_assert (G_IS_SUBPROCESS (subprocess));
+ g_assert (IDE_IS_SUBPROCESS (subprocess));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (G_IS_TASK (task));
self = g_task_get_source_object (task);
@@ -111,17 +117,17 @@ ide_runner_run_wait_cb (GObject *object,
g_signal_emit (self, signals [EXITED], 0);
- if (!g_subprocess_wait_finish (subprocess, result, &error))
+ if (!ide_subprocess_wait_finish (subprocess, result, &error))
{
g_task_return_error (task, error);
IDE_EXIT;
}
- if (g_subprocess_get_if_exited (subprocess))
+ if (ide_subprocess_get_if_exited (subprocess))
{
gint exit_code;
- exit_code = g_subprocess_get_exit_status (subprocess);
+ exit_code = ide_subprocess_get_exit_status (subprocess);
if (exit_code == EXIT_SUCCESS)
{
@@ -148,7 +154,7 @@ ide_runner_real_run_async (IdeRunner *self,
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_autoptr(GTask) task = NULL;
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
- g_autoptr(GSubprocess) subprocess = NULL;
+ g_autoptr(IdeSubprocess) subprocess = NULL;
const gchar *identifier;
GError *error = NULL;
@@ -160,7 +166,10 @@ ide_runner_real_run_async (IdeRunner *self,
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, ide_runner_real_run_async);
- launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+ launcher = ide_subprocess_launcher_new (priv->flags);
+
+ ide_subprocess_launcher_set_run_on_host (launcher, priv->run_on_host);
+ ide_subprocess_launcher_set_clear_env (launcher, FALSE);
for (GList *iter = priv->argv.head; iter != NULL; iter = iter->next)
ide_subprocess_launcher_push_argv (launcher, iter->data);
@@ -169,7 +178,7 @@ ide_runner_real_run_async (IdeRunner *self,
subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, &error);
- g_assert (subprocess == NULL || G_IS_SUBPROCESS (subprocess));
+ g_assert (subprocess == NULL || IDE_IS_SUBPROCESS (subprocess));
if (subprocess == NULL)
{
@@ -177,14 +186,14 @@ ide_runner_real_run_async (IdeRunner *self,
IDE_GOTO (failure);
}
- identifier = g_subprocess_get_identifier (subprocess);
+ identifier = ide_subprocess_get_identifier (subprocess);
g_signal_emit (self, signals [SPAWNED], 0, identifier);
- g_subprocess_wait_async (subprocess,
- cancellable,
- ide_runner_run_wait_cb,
- g_steal_pointer (&task));
+ ide_subprocess_wait_async (subprocess,
+ cancellable,
+ ide_runner_run_wait_cb,
+ g_steal_pointer (&task));
failure:
IDE_EXIT;
@@ -290,6 +299,10 @@ ide_runner_get_property (GObject *object,
g_value_take_boxed (value, ide_runner_get_argv (self));
break;
+ case PROP_RUN_ON_HOST:
+ g_value_set_boolean (value, ide_runner_get_run_on_host (self));
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -309,6 +322,10 @@ ide_runner_set_property (GObject *object,
ide_runner_set_argv (self, g_value_get_boxed (value));
break;
+ case PROP_RUN_ON_HOST:
+ ide_runner_set_run_on_host (self, g_value_get_boolean (value));
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -334,6 +351,13 @@ ide_runner_class_init (IdeRunnerClass *klass)
G_TYPE_STRV,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+ 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));
+
g_object_class_install_properties (object_class, N_PROPS, properties);
signals [EXITED] =
@@ -366,6 +390,8 @@ ide_runner_init (IdeRunner *self)
IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
g_queue_init (&priv->argv);
+
+ priv->flags = 0;
}
/**
@@ -693,3 +719,39 @@ ide_runner_new (IdeContext *context)
"context", context,
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]);
+ }
+}
+
+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;
+}
diff --git a/libide/runner/ide-runner.h b/libide/runner/ide-runner.h
index 39ac41f..ffb4058 100644
--- a/libide/runner/ide-runner.h
+++ b/libide/runner/ide-runner.h
@@ -46,25 +46,30 @@ struct _IdeRunnerClass
GError **error);
};
-IdeRunner *ide_runner_new (IdeContext *context);
-void ide_runner_force_quit (IdeRunner *self);
-void ide_runner_run_async (IdeRunner *self,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
-gboolean ide_runner_run_finish (IdeRunner *self,
- GAsyncResult *result,
- GError **error);
-void ide_runner_prepend_argv (IdeRunner *self,
- const gchar *param);
-void ide_runner_append_argv (IdeRunner *self,
- const gchar *param);
-gchar **ide_runner_get_argv (IdeRunner *self);
-void ide_runner_set_argv (IdeRunner *self,
- const gchar * const *argv);
-GInputStream *ide_runner_get_stdin (IdeRunner *self);
-GOutputStream *ide_runner_get_stdout (IdeRunner *self);
-GOutputStream *ide_runner_get_stderr (IdeRunner *self);
+IdeRunner *ide_runner_new (IdeContext *context);
+void ide_runner_force_quit (IdeRunner *self);
+void ide_runner_run_async (IdeRunner *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_runner_run_finish (IdeRunner *self,
+ GAsyncResult *result,
+ GError **error);
+void ide_runner_set_flags (IdeRunner *self,
+ GSubprocessFlags flags);
+void ide_runner_prepend_argv (IdeRunner *self,
+ const gchar *param);
+void ide_runner_append_argv (IdeRunner *self,
+ const gchar *param);
+gchar **ide_runner_get_argv (IdeRunner *self);
+void ide_runner_set_argv (IdeRunner *self,
+ const gchar * const *argv);
+GInputStream *ide_runner_get_stdin (IdeRunner *self);
+GOutputStream *ide_runner_get_stdout (IdeRunner *self);
+GOutputStream *ide_runner_get_stderr (IdeRunner *self);
+gboolean ide_runner_get_run_on_host (IdeRunner *self);
+void ide_runner_set_run_on_host (IdeRunner *self,
+ gboolean run_on_host);
G_END_DECLS
diff --git a/libide/runtimes/ide-runtime-manager.c b/libide/runtimes/ide-runtime-manager.c
index 4568bba..ef766e6 100644
--- a/libide/runtimes/ide-runtime-manager.c
+++ b/libide/runtimes/ide-runtime-manager.c
@@ -32,6 +32,7 @@ struct _IdeRuntimeManager
IdeObject parent_instance;
PeasExtensionSet *extensions;
GPtrArray *runtimes;
+ guint unloading : 1;
};
static void list_model_iface_init (GListModelInterface *iface);
@@ -109,6 +110,7 @@ _ide_runtime_manager_unload (IdeRuntimeManager *self)
{
g_return_if_fail (IDE_IS_RUNTIME_MANAGER (self));
+ self->unloading = TRUE;
g_clear_object (&self->extensions);
}
@@ -117,7 +119,7 @@ ide_runtime_manager_dispose (GObject *object)
{
IdeRuntimeManager *self = (IdeRuntimeManager *)object;
- g_clear_object (&self->extensions);
+ _ide_runtime_manager_unload (self);
g_clear_pointer (&self->runtimes, g_ptr_array_unref);
G_OBJECT_CLASS (ide_runtime_manager_parent_class)->dispose (object);
@@ -192,12 +194,13 @@ void
ide_runtime_manager_remove (IdeRuntimeManager *self,
IdeRuntime *runtime)
{
- guint i;
-
g_return_if_fail (IDE_IS_RUNTIME_MANAGER (self));
g_return_if_fail (IDE_IS_RUNTIME (runtime));
- for (i = 0; i < self->runtimes->len; i++)
+ if (self->unloading)
+ return;
+
+ for (guint i = 0; i < self->runtimes->len; i++)
{
IdeRuntime *item = g_ptr_array_index (self->runtimes, i);
diff --git a/libide/runtimes/ide-runtime.h b/libide/runtimes/ide-runtime.h
index cccdc3a..28f3931 100644
--- a/libide/runtimes/ide-runtime.h
+++ b/libide/runtimes/ide-runtime.h
@@ -25,7 +25,7 @@
#include "buildsystem/ide-build-target.h"
#include "runner/ide-runner.h"
-#include "workers/ide-subprocess-launcher.h"
+#include "subprocess/ide-subprocess-launcher.h"
G_BEGIN_DECLS
diff --git a/libide/subprocess/ide-breakout-subprocess-private.h
b/libide/subprocess/ide-breakout-subprocess-private.h
new file mode 100644
index 0000000..301e66b
--- /dev/null
+++ b/libide/subprocess/ide-breakout-subprocess-private.h
@@ -0,0 +1,36 @@
+/* ide-breakout-subprocess-private.h
+ *
+ * Copyright (C) 2016 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/>.
+ */
+
+#ifndef IDE_BREAKOUT_SUBPROCESS_PRIVATE_H
+#define IDE_BREAKOUT_SUBPROCESS_PRIVATE_H
+
+#include "subprocess/ide-breakout-subprocess.h"
+
+G_BEGIN_DECLS
+
+IdeSubprocess *_ide_breakout_subprocess_new (const gchar *cwd,
+ const gchar * const *argv,
+ const gchar * const *env,
+ GSubprocessFlags flags,
+ gboolean clear_flags,
+ GCancellable *cancellable,
+ GError **error) G_GNUC_INTERNAL;
+
+G_END_DECLS
+
+#endif /* IDE_BREAKOUT_SUBPROCESS_PRIVATE_H */
diff --git a/libide/subprocess/ide-breakout-subprocess.c b/libide/subprocess/ide-breakout-subprocess.c
new file mode 100644
index 0000000..55b6704
--- /dev/null
+++ b/libide/subprocess/ide-breakout-subprocess.c
@@ -0,0 +1,1431 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2012, 2013, 2016 Red Hat, Inc.
+ * Copyright © 2012, 2013 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: Colin Walters <walters verbum org>
+ * Ryan Lortie <desrt desrt ca>
+ * Alexander Larsson <alexl redhat com>
+ * Christian Hergert <chergert redhat com>
+ */
+
+#define G_LOG_DOMAIN "ide-breakout-subprocess"
+
+#include <egg-counter.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <gio/gunixfdlist.h>
+#include <glib-unix.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "ide-debug.h"
+#include "ide-macros.h"
+
+#include "subprocess/ide-breakout-subprocess.h"
+
+#ifndef FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV
+# define FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV (1 << 0)
+#endif
+
+EGG_DEFINE_COUNTER (instances, "Subprocess", "HostCommand Instances", "Number of IdeBreakoutSubprocess
instances")
+
+struct _IdeBreakoutSubprocess
+{
+ GObject parent_instance;
+
+ GDBusConnection *connection;
+
+ GPid client_pid;
+ gint status;
+
+ GSubprocessFlags flags;
+
+ gchar **argv;
+ gchar **env;
+ gchar *cwd;
+
+ gchar *identifier;
+
+ GOutputStream *stdin_pipe;
+ GInputStream *stdout_pipe;
+ GInputStream *stderr_pipe;
+
+ guint sigint_id;
+ guint sigterm_id;
+ guint exited_subscription;
+
+ /* GList of GTasks for wait_async() */
+ GList *waiting;
+
+ /* Mutex/Cond pair guards client_has_exited */
+ GMutex waiter_mutex;
+ GCond waiter_cond;
+
+ guint client_has_exited : 1;
+ guint clear_env : 1;
+};
+
+/* ide_subprocess_communicate implementation below:
+ *
+ * This is a tough problem. We have to watch 5 things at the same time:
+ *
+ * - writing to stdin made progress
+ * - reading from stdout made progress
+ * - reading from stderr made progress
+ * - process terminated
+ * - cancellable being cancelled by caller
+ *
+ * We use a GMainContext for all of these (either as async function
+ * calls or as a GSource (in the case of the cancellable). That way at
+ * least we don't have to worry about threading.
+ *
+ * For the sync case we use the usual trick of creating a private main
+ * context and iterating it until completion.
+ *
+ * It's very possible that the process will dump a lot of data to stdout
+ * just before it quits, so we can easily have data to read from stdout
+ * and see the process has terminated at the same time. We want to make
+ * sure that we read all of the data from the pipes first, though, so we
+ * do IO operations at a higher priority than the wait operation (which
+ * is at G_IO_PRIORITY_DEFAULT). Even in the case that we have to do
+ * multiple reads to get this data, the pipe() will always be polling
+ * as ready and with the async result for the read at a higher priority,
+ * the main context will not dispatch the completion for the wait().
+ *
+ * We keep our own private GCancellable. In the event that any of the
+ * above suffers from an error condition (including the user cancelling
+ * their cancellable) we immediately dispatch the GTask with the error
+ * result and fire our cancellable to cleanup any pending operations.
+ * In the case that the error is that the user's cancellable was fired,
+ * it's vaguely wasteful to report an error because GTask will handle
+ * this automatically, so we just return FALSE.
+ *
+ * We let each pending sub-operation take a ref on the GTask of the
+ * communicate operation. We have to be careful that we don't report
+ * the task completion more than once, though, so we keep a flag for
+ * that.
+ */
+typedef struct
+{
+ const gchar *stdin_data;
+ gsize stdin_length;
+ gsize stdin_offset;
+
+ gboolean add_nul;
+
+ GInputStream *stdin_buf;
+ GMemoryOutputStream *stdout_buf;
+ GMemoryOutputStream *stderr_buf;
+
+ GCancellable *cancellable;
+ GSource *cancellable_source;
+
+ guint outstanding_ops;
+ gboolean reported_error;
+} CommunicateState;
+
+enum {
+ PROP_0,
+ PROP_ARGV,
+ PROP_CWD,
+ PROP_ENV,
+ PROP_FLAGS,
+ N_PROPS
+};
+
+static void ide_breakout_subprocess_sync_setup (void);
+static void ide_breakout_subprocess_sync_complete (GAsyncResult **result);
+static void ide_breakout_subprocess_sync_done (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data);
+static CommunicateState *ide_breakout_subprocess_communicate_internal (IdeBreakoutSubprocess *subprocess,
+ gboolean add_nul,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+static GParamSpec *properties [N_PROPS];
+
+static const gchar *
+ide_breakout_subprocess_get_identifier (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ return self->identifier;
+}
+
+static GInputStream *
+ide_breakout_subprocess_get_stdout_pipe (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ return self->stdout_pipe;
+}
+
+static GInputStream *
+ide_breakout_subprocess_get_stderr_pipe (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ return self->stderr_pipe;
+}
+
+static GOutputStream *
+ide_breakout_subprocess_get_stdin_pipe (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ return self->stdin_pipe;
+}
+
+static gboolean
+ide_breakout_subprocess_wait (IdeSubprocess *subprocess,
+ GCancellable *cancellable,
+ GError **error)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ g_object_ref (self);
+
+ /* Completion will broadcast upon completion */
+ g_mutex_lock (&self->waiter_mutex);
+ if (!self->client_has_exited)
+ g_cond_wait (&self->waiter_cond, &self->waiter_mutex);
+ g_mutex_unlock (&self->waiter_mutex);
+
+ g_object_unref (self);
+
+ return self->client_has_exited;
+}
+
+static void
+ide_breakout_subprocess_wait_async (IdeSubprocess *subprocess,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+ g_autoptr(GTask) task = NULL;
+ g_autoptr(GMutexLocker) locker = NULL;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, ide_breakout_subprocess_wait_async);
+
+ locker = g_mutex_locker_new (&self->waiter_mutex);
+
+ if (self->client_has_exited)
+ {
+ g_task_return_boolean (task, TRUE);
+ return;
+ }
+
+ self->waiting = g_list_append (self->waiting, g_steal_pointer (&task));
+}
+
+static gboolean
+ide_breakout_subprocess_wait_finish (IdeSubprocess *subprocess,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (subprocess));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+void
+ide_subprocess_communicate_utf8_async (IdeSubprocess *subprocess,
+ const char *stdin_buf,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+ g_autoptr(GBytes) stdin_bytes = NULL;
+ size_t stdin_buf_len = 0;
+
+ g_return_if_fail (IDE_IS_BREAKOUT_SUBPROCESS (subprocess));
+ g_return_if_fail (stdin_buf == NULL || (self->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ if (stdin_buf != NULL)
+ stdin_buf_len = strlen (stdin_buf);
+ stdin_bytes = g_bytes_new (stdin_buf, stdin_buf_len);
+
+ ide_breakout_subprocess_communicate_internal (self, TRUE, stdin_bytes, cancellable, callback, user_data);
+}
+
+static gboolean
+communicate_result_validate_utf8 (const char *stream_name,
+ char **return_location,
+ GMemoryOutputStream *buffer,
+ GError **error)
+{
+ if (return_location == NULL)
+ return TRUE;
+
+ if (buffer)
+ {
+ const char *end;
+ *return_location = g_memory_output_stream_steal_data (buffer);
+ if (!g_utf8_validate (*return_location, -1, &end))
+ {
+ g_free (*return_location);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Invalid UTF-8 in child %s at offset %lu",
+ stream_name,
+ (unsigned long) (end - *return_location));
+ return FALSE;
+ }
+ }
+ else
+ *return_location = NULL;
+
+ return TRUE;
+}
+
+gboolean
+ide_subprocess_communicate_utf8_finish (IdeSubprocess *subprocess,
+ GAsyncResult *result,
+ char **stdout_buf,
+ char **stderr_buf,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ CommunicateState *state;
+
+ g_return_val_if_fail (IDE_IS_BREAKOUT_SUBPROCESS (subprocess), FALSE);
+ g_return_val_if_fail (g_task_is_valid (result, subprocess), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_object_ref (result);
+
+ state = g_task_get_task_data ((GTask*)result);
+ if (!g_task_propagate_boolean ((GTask*)result, error))
+ goto out;
+
+ if (!communicate_result_validate_utf8 ("stdout", stdout_buf, state->stdout_buf, error))
+ goto out;
+
+ if (!communicate_result_validate_utf8 ("stderr", stderr_buf, state->stderr_buf, error))
+ goto out;
+
+ ret = TRUE;
+
+ out:
+ g_object_unref (result);
+
+ return ret;
+}
+
+static gboolean
+ide_breakout_subprocess_communicate_utf8 (IdeSubprocess *subprocess,
+ const char *stdin_buf,
+ GCancellable *cancellable,
+ char **stdout_buf,
+ char **stderr_buf,
+ GError **error)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+ g_autoptr(GAsyncResult) result = NULL;
+ g_autoptr(GBytes) stdin_bytes = NULL;
+ size_t stdin_buf_len = 0;
+ gboolean success;
+
+ g_return_val_if_fail (IDE_IS_BREAKOUT_SUBPROCESS (subprocess), FALSE);
+ g_return_val_if_fail (stdin_buf == NULL || (self->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE), FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (stdin_buf != NULL)
+ stdin_buf_len = strlen (stdin_buf);
+ stdin_bytes = g_bytes_new (stdin_buf, stdin_buf_len);
+
+ ide_breakout_subprocess_sync_setup ();
+ ide_breakout_subprocess_communicate_internal (self,
+ TRUE,
+ stdin_bytes,
+ cancellable,
+ ide_breakout_subprocess_sync_done,
+ &result);
+ ide_breakout_subprocess_sync_complete (&result);
+ success = ide_subprocess_communicate_utf8_finish (subprocess, result, stdout_buf, stderr_buf, error);
+
+ return success;
+}
+
+static gboolean
+ide_breakout_subprocess_get_successful (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ return WIFEXITED (self->status) && WEXITSTATUS (self->status) == 0;
+}
+
+static gboolean
+ide_breakout_subprocess_get_if_exited (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ return WIFEXITED (self->status);
+}
+
+static gint
+ide_breakout_subprocess_get_exit_status (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (self->client_has_exited);
+
+ if (!WIFEXITED (self->status))
+ return 1;
+
+ return WEXITSTATUS (self->status);
+}
+
+static gboolean
+ide_breakout_subprocess_get_if_signaled (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (self->client_has_exited == TRUE);
+
+ return WIFSIGNALED (self->status);
+}
+
+static gint
+ide_breakout_subprocess_get_term_sig (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (self->client_has_exited == TRUE);
+
+ return WTERMSIG (self->status);
+}
+
+static gint
+ide_breakout_subprocess_get_status (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (self->client_has_exited == TRUE);
+
+ return self->status;
+}
+
+static void
+ide_breakout_subprocess_send_signal (IdeSubprocess *subprocess,
+ gint signal_num)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (G_IS_DBUS_CONNECTION (self->connection));
+
+ /* Signal delivery is not guaranteed, so we can drop this on the floor. */
+ if (self->client_has_exited)
+ return;
+
+ g_dbus_connection_call_sync (self->connection,
+ "org.freedesktop.Flatpak",
+ "/org/freedesktop/Flatpak/Development",
+ "org.freedesktop.Flatpak.Development",
+ "HostCommandSignal",
+ g_variant_new ("(uub)", self->client_pid, signal_num, TRUE),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ NULL, NULL);
+}
+
+static void
+ide_breakout_subprocess_force_exit (IdeSubprocess *subprocess)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ ide_breakout_subprocess_send_signal (subprocess, SIGKILL);
+}
+
+static void
+ide_breakout_subprocess_sync_setup (void)
+{
+ /* Leak ref until ide_breakout_subprocess_sync_complete() */
+ g_main_context_push_thread_default (g_main_context_new ());
+}
+
+static void
+ide_breakout_subprocess_sync_complete (GAsyncResult **result)
+{
+ GMainContext *context = g_main_context_get_thread_default ();
+
+ IDE_ENTRY;
+
+ g_assert (result != NULL);
+ g_assert (*result == NULL || G_IS_ASYNC_RESULT (*result));
+
+ while (*result == NULL)
+ g_main_context_iteration (context, TRUE);
+
+ g_main_context_pop_thread_default (context);
+
+ /* Unref pair for ide_breakout_subprocess_sync_setup() */
+ g_main_context_unref (context);
+
+ IDE_EXIT;
+}
+
+static void
+ide_breakout_subprocess_sync_done (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GAsyncResult **ret = user_data;
+
+ IDE_ENTRY;
+
+ g_assert (ret != NULL);
+ g_assert (*ret == NULL);
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ *ret = g_object_ref (result);
+
+ IDE_EXIT;
+}
+
+static void
+ide_subprocess_communicate_state_free (gpointer data)
+{
+ CommunicateState *state = data;
+
+ g_clear_object (&state->cancellable);
+ g_clear_object (&state->stdin_buf);
+ g_clear_object (&state->stdout_buf);
+ g_clear_object (&state->stderr_buf);
+
+ if (state->cancellable_source)
+ {
+ if (!g_source_is_destroyed (state->cancellable_source))
+ g_source_destroy (state->cancellable_source);
+ g_source_unref (state->cancellable_source);
+ }
+
+ g_slice_free (CommunicateState, state);
+}
+
+static gboolean
+ide_subprocess_communicate_cancelled (gpointer user_data)
+{
+ CommunicateState *state = user_data;
+
+ g_cancellable_cancel (state->cancellable);
+
+ return FALSE;
+}
+
+static void
+ide_subprocess_communicate_made_progress (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CommunicateState *state;
+ IdeBreakoutSubprocess *subprocess;
+ GError *error = NULL;
+ gpointer source;
+ GTask *task;
+
+ g_assert (source_object != NULL);
+
+ task = user_data;
+ subprocess = g_task_get_source_object (task);
+ state = g_task_get_task_data (task);
+ source = source_object;
+
+ state->outstanding_ops--;
+
+ if (source == subprocess->stdin_pipe ||
+ source == state->stdout_buf ||
+ source == state->stderr_buf)
+ {
+ if (g_output_stream_splice_finish ((GOutputStream*) source, result, &error) == -1)
+ goto out;
+
+ if (source == state->stdout_buf ||
+ source == state->stderr_buf)
+ {
+ /* This is a memory stream, so it can't be cancelled or return
+ * an error really.
+ */
+ if (state->add_nul)
+ {
+ gsize bytes_written;
+ if (!g_output_stream_write_all (source, "\0", 1, &bytes_written,
+ NULL, &error))
+ goto out;
+ }
+ if (!g_output_stream_close (source, NULL, &error))
+ goto out;
+ }
+ }
+ else if (source == subprocess)
+ {
+ (void) ide_subprocess_wait_finish (IDE_SUBPROCESS (subprocess), result, &error);
+ }
+ else
+ g_assert_not_reached ();
+
+ out:
+ if (error)
+ {
+ /* Only report the first error we see.
+ *
+ * We might be seeing an error as a result of the cancellation
+ * done when the process quits.
+ */
+ if (!state->reported_error)
+ {
+ state->reported_error = TRUE;
+ g_cancellable_cancel (state->cancellable);
+ g_task_return_error (task, error);
+ }
+ else
+ g_error_free (error);
+ }
+ else if (state->outstanding_ops == 0)
+ {
+ g_task_return_boolean (task, TRUE);
+ }
+
+ /* And drop the original ref */
+ g_object_unref (task);
+}
+
+static CommunicateState *
+ide_breakout_subprocess_communicate_internal (IdeBreakoutSubprocess *subprocess,
+ gboolean add_nul,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ CommunicateState *state;
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (subprocess));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (subprocess, cancellable, callback, user_data);
+ g_task_set_source_tag (task, ide_breakout_subprocess_communicate_internal);
+
+ state = g_slice_new0 (CommunicateState);
+ g_task_set_task_data (task, state, ide_subprocess_communicate_state_free);
+
+ state->cancellable = g_cancellable_new ();
+ state->add_nul = add_nul;
+
+ if (cancellable)
+ {
+ state->cancellable_source = g_cancellable_source_new (cancellable);
+ /* No ref held here, but we unref the source from state's free function */
+ g_source_set_callback (state->cancellable_source, ide_subprocess_communicate_cancelled, state, NULL);
+ g_source_attach (state->cancellable_source, g_main_context_get_thread_default ());
+ }
+
+ if (subprocess->stdin_pipe)
+ {
+ g_assert (stdin_buf != NULL);
+ state->stdin_buf = g_memory_input_stream_new_from_bytes (stdin_buf);
+ g_output_stream_splice_async (subprocess->stdin_pipe, (GInputStream*)state->stdin_buf,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ G_PRIORITY_DEFAULT, state->cancellable,
+ ide_subprocess_communicate_made_progress, g_object_ref (task));
+ state->outstanding_ops++;
+ }
+
+ if (subprocess->stdout_pipe)
+ {
+ state->stdout_buf = (GMemoryOutputStream*)g_memory_output_stream_new_resizable ();
+ g_output_stream_splice_async ((GOutputStream*)state->stdout_buf, subprocess->stdout_pipe,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
+ G_PRIORITY_DEFAULT, state->cancellable,
+ ide_subprocess_communicate_made_progress, g_object_ref (task));
+ state->outstanding_ops++;
+ }
+
+ if (subprocess->stderr_pipe)
+ {
+ state->stderr_buf = (GMemoryOutputStream*)g_memory_output_stream_new_resizable ();
+ g_output_stream_splice_async ((GOutputStream*)state->stderr_buf, subprocess->stderr_pipe,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
+ G_PRIORITY_DEFAULT, state->cancellable,
+ ide_subprocess_communicate_made_progress, g_object_ref (task));
+ state->outstanding_ops++;
+ }
+
+ ide_subprocess_wait_async (IDE_SUBPROCESS (subprocess), state->cancellable,
+ ide_subprocess_communicate_made_progress, g_object_ref (task));
+ state->outstanding_ops++;
+
+ return state;
+}
+
+static void
+ide_breakout_subprocess_communicate_async (IdeSubprocess *subprocess,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ ide_breakout_subprocess_communicate_internal (self, FALSE, stdin_buf, cancellable, callback, user_data);
+}
+
+static gboolean
+ide_breakout_subprocess_communicate_finish (IdeSubprocess *subprocess,
+ GAsyncResult *result,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+ CommunicateState *state;
+ GTask *task = (GTask *)result;
+ gboolean success;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (G_IS_TASK (task));
+
+ g_object_ref (task);
+
+ state = g_task_get_task_data (task);
+
+ g_assert (state != NULL);
+
+ success = g_task_propagate_boolean (task, error);
+
+ if (success)
+ {
+ if (stdout_buf)
+ *stdout_buf = g_memory_output_stream_steal_as_bytes (state->stdout_buf);
+ if (stderr_buf)
+ *stderr_buf = g_memory_output_stream_steal_as_bytes (state->stderr_buf);
+ }
+
+ g_object_unref (task);
+
+ return success;
+}
+
+static gboolean
+ide_breakout_subprocess_communicate (IdeSubprocess *subprocess,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)subprocess;
+ g_autoptr(GMainContext) main_context = g_main_context_new ();
+ g_autoptr(GAsyncResult) result = NULL;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ ide_breakout_subprocess_sync_setup ();
+ ide_breakout_subprocess_communicate_internal (self,
+ FALSE,
+ stdin_buf,
+ cancellable,
+ ide_breakout_subprocess_sync_done,
+ &result);
+ ide_breakout_subprocess_sync_complete (&result);
+
+ return ide_breakout_subprocess_communicate_finish (subprocess, result, stdout_buf, stderr_buf, error);
+}
+
+static void
+subprocess_iface_init (IdeSubprocessInterface *iface)
+{
+ iface->get_identifier = ide_breakout_subprocess_get_identifier;
+ iface->get_stdout_pipe = ide_breakout_subprocess_get_stdout_pipe;
+ iface->get_stderr_pipe = ide_breakout_subprocess_get_stderr_pipe;
+ iface->get_stdin_pipe = ide_breakout_subprocess_get_stdin_pipe;
+ iface->wait = ide_breakout_subprocess_wait;
+ iface->wait_async = ide_breakout_subprocess_wait_async;
+ iface->wait_finish = ide_breakout_subprocess_wait_finish;
+ iface->get_successful = ide_breakout_subprocess_get_successful;
+ iface->get_if_exited = ide_breakout_subprocess_get_if_exited;
+ iface->get_exit_status = ide_breakout_subprocess_get_exit_status;
+ iface->get_if_signaled = ide_breakout_subprocess_get_if_signaled;
+ iface->get_term_sig = ide_breakout_subprocess_get_term_sig;
+ iface->get_status = ide_breakout_subprocess_get_status;
+ iface->send_signal = ide_breakout_subprocess_send_signal;
+ iface->force_exit = ide_breakout_subprocess_force_exit;
+ iface->communicate = ide_breakout_subprocess_communicate;
+ iface->communicate_utf8 = ide_breakout_subprocess_communicate_utf8;
+ iface->communicate_async = ide_breakout_subprocess_communicate_async;
+ iface->communicate_finish = ide_breakout_subprocess_communicate_finish;
+}
+
+static gboolean
+sigterm_handler (gpointer user_data)
+{
+ IdeBreakoutSubprocess *self = user_data;
+ g_autoptr(GDBusConnection) bus = NULL;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ g_dbus_connection_call_sync (self->connection,
+ "org.freedesktop.Flatpak",
+ "/org/freedesktop/Flatpak/Development",
+ "org.freedesktop.Flatpak.Development",
+ "HostCommandSignal",
+ g_variant_new ("(uub)", self->client_pid, SIGTERM, TRUE),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ NULL, NULL);
+
+ kill (getpid (), SIGTERM);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+sigint_handler (gpointer user_data)
+{
+ IdeBreakoutSubprocess *self = user_data;
+ g_autoptr(GDBusConnection) bus = NULL;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ g_dbus_connection_call_sync (self->connection,
+ "org.freedesktop.Flatpak",
+ "/org/freedesktop/Flatpak/Development",
+ "org.freedesktop.Flatpak.Development",
+ "HostCommandSignal",
+ g_variant_new ("(uub)", self->client_pid, SIGINT, TRUE),
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ NULL, NULL);
+
+ kill (getpid (), SIGINT);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+maybe_create_input_stream (GInputStream **ret,
+ gint *fdptr)
+{
+ g_assert (ret != NULL);
+ g_assert (fdptr != NULL);
+
+ g_clear_object (ret);
+
+ if (*fdptr != -1)
+ {
+ *ret = g_unix_input_stream_new (*fdptr, TRUE);
+ *fdptr = -1;
+ }
+}
+
+static void
+maybe_create_output_stream (GOutputStream **ret,
+ gint *fdptr)
+{
+ g_assert (ret != NULL);
+ g_assert (fdptr != NULL);
+
+ g_clear_object (ret);
+
+ if (*fdptr != -1)
+ {
+ *ret = g_unix_output_stream_new (*fdptr, TRUE);
+ *fdptr = -1;
+ }
+}
+
+static inline void
+set_error_from_errno (GError **error)
+{
+ g_set_error (error,
+ G_IO_ERROR,
+ g_io_error_from_errno (errno),
+ "%s",
+ g_strerror (errno));
+}
+
+static void
+host_command_exited_cb (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+{
+ g_autoptr(IdeBreakoutSubprocess) finalize_protect = NULL;
+ IdeBreakoutSubprocess *self = user_data;
+ g_autoptr(GMutexLocker) locker = NULL;
+ GList *waiting;
+ guint32 client_pid = 0;
+ guint32 exit_status = 0;
+
+ IDE_ENTRY;
+
+ g_assert (G_IS_DBUS_CONNECTION (connection));
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ finalize_protect = g_object_ref (self);
+
+ if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(uu)")))
+ IDE_EXIT;
+
+ g_variant_get (parameters, "(uu)", &client_pid, &exit_status);
+ if (client_pid != self->client_pid)
+ IDE_EXIT;
+
+ locker = g_mutex_locker_new (&self->waiter_mutex);
+ self->client_has_exited = TRUE;
+
+ IDE_TRACE_MSG ("Host process %u exited with %u",
+ (guint)self->client_pid,
+ (guint)exit_status);
+
+ self->status = exit_status;
+
+ /* Clear process identifiers to prevent accidental use by API consumers
+ * after the process has exited.
+ */
+ self->client_pid = 0;
+ g_clear_pointer (&self->identifier, g_free);
+
+ /* We can release our dbus signal handler now */
+ if (self->exited_subscription != 0)
+ {
+ g_dbus_connection_signal_unsubscribe (self->connection, self->exited_subscription);
+ self->exited_subscription = 0;
+ }
+
+ /* Remove our sources used for signal propagation */
+ ide_clear_source (&self->sigint_id);
+ ide_clear_source (&self->sigterm_id);
+
+ /* Notify all of our waiters that their task has completed. */
+ waiting = self->waiting, self->waiting = NULL;
+ for (GList *iter = waiting; iter != NULL; iter = iter->next)
+ {
+ g_autoptr(GTask) task = iter->data;
+
+ g_task_return_boolean (task, TRUE);
+ }
+ g_list_free (waiting);
+
+ /* Notify synchronous waiters */
+ g_cond_broadcast (&self->waiter_cond);
+
+ IDE_EXIT;
+}
+
+static gboolean
+ide_breakout_subprocess_initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)initable;
+ g_autoptr(GVariantBuilder) fd_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{uh}"));
+ g_autoptr(GVariantBuilder) env_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{ss}"));
+ g_autoptr(GUnixFDList) fd_list = g_unix_fd_list_new ();
+ g_autoptr(GVariant) reply = NULL;
+ guint32 client_pid = 0;
+ gint stdout_pair[2] = { -1, -1 };
+ gint stderr_pair[2] = { -1, -1 };
+ gint stdin_pair[2] = { -1, -1 };
+ gint stdin_handle;
+ gint stdout_handle;
+ gint stderr_handle;
+ gboolean ret = FALSE;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ self->connection = g_bus_get_sync (G_BUS_TYPE_SESSION, cancellable, error);
+ if (self->connection == NULL)
+ IDE_RETURN (FALSE);
+
+
+ /*
+ * Handle STDIN for the process.
+ *
+ * Make sure we handle inherit STDIN, a new pipe (so that the application can
+ * get the stdin stream), or simply redirect to /dev/null.
+ */
+ if (self->flags & G_SUBPROCESS_FLAGS_STDIN_INHERIT)
+ {
+ stdin_pair[0] = STDIN_FILENO;
+ }
+ else if (self->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE)
+ {
+ if (pipe2 (stdin_pair, O_CLOEXEC) != 0)
+ {
+ set_error_from_errno (error);
+ IDE_GOTO (cleanup_fds);
+ }
+ }
+ else
+ {
+ stdin_pair[0] = open ("/dev/null", O_RDWR, 0);
+
+ if (stdin_pair[0] == -1)
+ IDE_GOTO (cleanup_fds);
+ }
+
+ g_assert (stdin_pair[0] != -1);
+
+ stdin_handle = g_unix_fd_list_append (fd_list, stdin_pair[0], error);
+ if (stdin_handle == -1)
+ IDE_GOTO (cleanup_fds);
+
+
+ /*
+ * Setup STDOUT for the process.
+ *
+ * Make sure we redirect STDOUT to our stdout, unless a pipe was requested
+ * for the application to read. However, if silence was requested, redirect
+ * to /dev/null.
+ */
+ if (self->flags & G_SUBPROCESS_FLAGS_STDOUT_SILENCE)
+ {
+ stdout_pair[1] = open ("/dev/null", O_RDWR, 0);
+
+ if (stdout_pair[1] == -1)
+ IDE_GOTO (cleanup_fds);
+ }
+ else if (self->flags & G_SUBPROCESS_FLAGS_STDOUT_PIPE)
+ {
+ if (pipe2 (stdout_pair, O_CLOEXEC) != 0)
+ {
+ set_error_from_errno (error);
+ IDE_GOTO (cleanup_fds);
+ }
+ }
+ else
+ {
+ stdout_pair[1] = STDOUT_FILENO;
+ }
+
+ g_assert (stdout_pair[1] != -1);
+
+ stdout_handle = g_unix_fd_list_append (fd_list, stdout_pair[1], error);
+ if (stdout_handle == -1)
+ IDE_GOTO (cleanup_fds);
+
+
+ /*
+ * Handle STDERR for the process.
+ *
+ * If silence is requested, we simply redirect to /dev/null. If the
+ * application requested to read from the subprocesses stderr, then we need
+ * to create a pipe. Otherwose, merge stderr into our own stderr.
+ */
+ if (self->flags & G_SUBPROCESS_FLAGS_STDERR_SILENCE)
+ {
+ stderr_pair[1] = open ("/dev/null", O_RDWR, 0);
+
+ if (stderr_pair[1] == -1)
+ IDE_GOTO (cleanup_fds);
+ }
+ else if (self->flags & G_SUBPROCESS_FLAGS_STDERR_PIPE)
+ {
+ if (pipe2 (stderr_pair, O_CLOEXEC) != 0)
+ {
+ set_error_from_errno (error);
+ IDE_GOTO (cleanup_fds);
+ }
+ }
+ else
+ {
+ stderr_pair[1] = STDERR_FILENO;
+ }
+
+ g_assert (stderr_pair[1] != -1);
+
+ stderr_handle = g_unix_fd_list_append (fd_list, stderr_pair[1], error);
+ if (stderr_handle == -1)
+ IDE_GOTO (cleanup_fds);
+
+
+ /*
+ * Build our FDs for the message.
+ */
+ g_variant_builder_add (fd_builder, "{uh}", 0, stdin_handle);
+ g_variant_builder_add (fd_builder, "{uh}", 1, stdout_handle);
+ g_variant_builder_add (fd_builder, "{uh}", 2, stderr_handle);
+
+
+ /*
+ * Build streams for our application to use.
+ */
+ maybe_create_output_stream (&self->stdin_pipe, &stdin_pair[1]);
+ maybe_create_input_stream (&self->stdout_pipe, &stdout_pair[0]);
+ maybe_create_input_stream (&self->stderr_pipe, &stderr_pair[0]);
+
+
+ /*
+ * Build our environment variables message.
+ */
+ if (self->env != NULL)
+ {
+ for (guint i = 0; self->env[i]; i++)
+ {
+ const gchar *pair = self->env[i];
+ const gchar *eq = strchr (pair, '=');
+ const gchar *val = eq ? eq + 1 : "";
+ g_autofree gchar *key = eq ? g_strndup (pair, eq - pair) : g_strdup (pair);
+
+ g_variant_builder_add (env_builder, "{ss}", key, val);
+ }
+ }
+
+
+ /*
+ * Register signal handlers for SIGTERM/SIGINT so that we can terminate
+ * the host process with us (which won't be guaranteed since its outside
+ * our cgroup, nor can we use a process group leader).
+ */
+ self->sigterm_id = g_unix_signal_add (SIGTERM, sigterm_handler, self);
+ self->sigint_id = g_unix_signal_add (SIGINT, sigint_handler, self);
+
+
+ /*
+ * Connect to the HostCommandExited signal so that we can make progress
+ * on all tasks waiting on ide_subprocess_wait() and it's async variants.
+ * We need to do this before spawning the process to avoid the race.
+ */
+ self->exited_subscription = g_dbus_connection_signal_subscribe (self->connection,
+ NULL,
+ "org.freedesktop.Flatpak.Development",
+ "HostCommandExited",
+ "/org/freedesktop/Flatpak/Development",
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ host_command_exited_cb,
+ self,
+ NULL);
+
+
+ /*
+ * Now call the HostCommand service to execute the process within the host
+ * system. We need to ensure our fd_list is sent across for redirecting
+ * various standard streams.
+ */
+ reply = g_dbus_connection_call_with_unix_fd_list_sync (self->connection,
+ "org.freedesktop.Flatpak",
+ "/org/freedesktop/Flatpak/Development",
+ "org.freedesktop.Flatpak.Development",
+ "HostCommand",
+ g_variant_new ("(^ay^aay@a{uh}@a{ss}u)",
+ self->cwd ?: g_get_home_dir (),
+ self->argv,
+ g_variant_builder_end
(g_steal_pointer (&fd_builder)),
+ g_variant_builder_end
(g_steal_pointer (&env_builder)),
+ self->clear_env ?
FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV : 0),
+ G_VARIANT_TYPE ("(u)"),
+ G_DBUS_CALL_FLAGS_NONE, -1,
+ fd_list, NULL,
+ NULL, error);
+ if (reply == NULL)
+ IDE_GOTO (cleanup_fds);
+
+ if (!g_variant_is_of_type (reply, G_VARIANT_TYPE ("(u)")))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "Received invalid reply for HostCommand: %s",
+ g_variant_get_type_string (reply));
+ IDE_RETURN (FALSE);
+ }
+
+ g_variant_get (reply, "(u)", &client_pid);
+
+ self->client_pid = (GPid)client_pid;
+ self->identifier = g_strdup_printf ("%u", client_pid);
+
+ IDE_TRACE_MSG ("HostCommand() spawned client_pid %u", (guint)client_pid);
+
+ ret = TRUE;
+
+cleanup_fds:
+
+#define maybe_close(fd) \
+ G_STMT_START { \
+ if (fd != -1 && fd != STDERR_FILENO && fd != STDOUT_FILENO && fd != STDIN_FILENO) \
+ close(fd); \
+ } G_STMT_END
+
+ /* Close lingering stdout fds */
+ maybe_close (stdout_pair[0]);
+ maybe_close (stdout_pair[1]);
+
+ /* Close lingering stderr fds */
+ maybe_close (stderr_pair[0]);
+ maybe_close (stderr_pair[1]);
+
+ /* Close lingering stdin fds */
+ maybe_close (stdin_pair[0]);
+ maybe_close (stdin_pair[1]);
+
+ IDE_RETURN (ret);
+}
+
+static void
+initiable_iface_init (GInitableIface *iface)
+{
+ iface->init = ide_breakout_subprocess_initable_init;
+}
+
+G_DEFINE_TYPE_EXTENDED (IdeBreakoutSubprocess, ide_breakout_subprocess, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initiable_iface_init)
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_SUBPROCESS, subprocess_iface_init))
+
+static void
+ide_breakout_subprocess_dispose (GObject *object)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)object;
+
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (self));
+
+ if (self->exited_subscription != 0)
+ {
+ if (self->connection != NULL && !g_dbus_connection_is_closed (self->connection))
+ g_dbus_connection_signal_unsubscribe (self->connection, self->exited_subscription);
+ self->exited_subscription = 0;
+ }
+
+ if (self->waiting != NULL)
+ g_warning ("improper disposal while async operations are active!");
+
+ ide_clear_source (&self->sigint_id);
+ ide_clear_source (&self->sigterm_id);
+
+ G_OBJECT_CLASS (ide_breakout_subprocess_parent_class)->dispose (object);
+}
+
+static void
+ide_breakout_subprocess_finalize (GObject *object)
+{
+ IdeBreakoutSubprocess *self = (IdeBreakoutSubprocess *)object;
+
+ IDE_ENTRY;
+
+ g_assert (self->waiting == NULL);
+ g_assert_cmpint (self->sigint_id, ==, 0);
+ g_assert_cmpint (self->sigterm_id, ==, 0);
+ g_assert_cmpint (self->exited_subscription, ==, 0);
+
+ g_clear_pointer (&self->identifier, g_free);
+ g_clear_pointer (&self->cwd, g_free);
+ g_clear_pointer (&self->argv, g_strfreev);
+ g_clear_pointer (&self->env, g_strfreev);
+
+ g_clear_object (&self->stdin_pipe);
+ g_clear_object (&self->stdout_pipe);
+ g_clear_object (&self->stderr_pipe);
+ g_clear_object (&self->connection);
+
+ g_mutex_clear (&self->waiter_mutex);
+ g_cond_clear (&self->waiter_cond);
+
+ G_OBJECT_CLASS (ide_breakout_subprocess_parent_class)->finalize (object);
+
+ EGG_COUNTER_DEC (instances);
+
+ IDE_EXIT;
+}
+
+static void
+ide_breakout_subprocess_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBreakoutSubprocess *self = IDE_BREAKOUT_SUBPROCESS (object);
+
+ switch (prop_id)
+ {
+ case PROP_CWD:
+ g_value_set_string (value, self->cwd);
+ break;
+
+ case PROP_ARGV:
+ g_value_set_boxed (value, self->argv);
+ break;
+
+ case PROP_ENV:
+ g_value_set_boxed (value, self->env);
+ break;
+
+ case PROP_FLAGS:
+ g_value_set_flags (value, self->flags);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_breakout_subprocess_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBreakoutSubprocess *self = IDE_BREAKOUT_SUBPROCESS (object);
+
+ switch (prop_id)
+ {
+ case PROP_CWD:
+ self->cwd = g_value_dup_string (value);
+ break;
+
+ case PROP_ARGV:
+ self->argv = g_value_dup_boxed (value);
+ break;
+
+ case PROP_ENV:
+ self->env = g_value_dup_boxed (value);
+ break;
+
+ case PROP_FLAGS:
+ self->flags = g_value_get_flags (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_breakout_subprocess_class_init (IdeBreakoutSubprocessClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = ide_breakout_subprocess_dispose;
+ object_class->finalize = ide_breakout_subprocess_finalize;
+ object_class->get_property = ide_breakout_subprocess_get_property;
+ object_class->set_property = ide_breakout_subprocess_set_property;
+
+ properties [PROP_CWD] =
+ g_param_spec_string ("cwd",
+ "Current Working Directory",
+ "The working directory for spawning the process",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ARGV] =
+ g_param_spec_boxed ("argv",
+ "Argv",
+ "The arguments for the process, including argv0",
+ G_TYPE_STRV,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ENV] =
+ g_param_spec_boxed ("env",
+ "Environment",
+ "The environment variables for the process",
+ G_TYPE_STRV,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_FLAGS] =
+ g_param_spec_flags ("flags",
+ "Flags",
+ "The subprocess flags to use when spawning",
+ G_TYPE_SUBPROCESS_FLAGS,
+ G_SUBPROCESS_FLAGS_NONE,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_breakout_subprocess_init (IdeBreakoutSubprocess *self)
+{
+ IDE_ENTRY;
+
+ EGG_COUNTER_INC (instances);
+
+ g_mutex_init (&self->waiter_mutex);
+ g_cond_init (&self->waiter_cond);
+
+ IDE_EXIT;
+}
+
+IdeSubprocess *
+_ide_breakout_subprocess_new (const gchar *cwd,
+ const gchar * const *argv,
+ const gchar * const *env,
+ GSubprocessFlags flags,
+ gboolean clear_env,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(IdeBreakoutSubprocess) ret = NULL;
+
+ g_return_val_if_fail (argv != NULL, NULL);
+ g_return_val_if_fail (argv[0] != NULL, NULL);
+
+ ret = g_object_new (IDE_TYPE_BREAKOUT_SUBPROCESS,
+ "cwd", cwd,
+ "argv", argv,
+ "env", env,
+ "flags", flags,
+ NULL);
+
+ ret->clear_env = clear_env;
+
+ if (!g_initable_init (G_INITABLE (ret), cancellable, error))
+ return NULL;
+
+ return g_steal_pointer (&ret);
+}
diff --git a/libide/subprocess/ide-breakout-subprocess.h b/libide/subprocess/ide-breakout-subprocess.h
new file mode 100644
index 0000000..3ce2daa
--- /dev/null
+++ b/libide/subprocess/ide-breakout-subprocess.h
@@ -0,0 +1,29 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright © 2012, 2013, 2016 Red Hat, Inc.
+ * Copyright © 2012, 2013 Canonical Limited
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ *
+ * Authors: Christian Hergert <chergert redhat com>
+ */
+
+#ifndef IDE_BREAKOUT_SUBPROCESS_H
+#define IDE_BREAKOUT_SUBPROCESS_H
+
+#include "subprocess/ide-subprocess.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BREAKOUT_SUBPROCESS (ide_breakout_subprocess_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeBreakoutSubprocess, ide_breakout_subprocess, IDE, BREAKOUT_SUBPROCESS, GObject)
+
+G_END_DECLS
+
+#endif /* IDE_BREAKOUT_SUBPROCESS_H */
diff --git a/libide/subprocess/ide-simple-subprocess.c b/libide/subprocess/ide-simple-subprocess.c
new file mode 100644
index 0000000..98bc99d
--- /dev/null
+++ b/libide/subprocess/ide-simple-subprocess.c
@@ -0,0 +1,310 @@
+/* ide-simple-subprocess.c
+ *
+ * Copyright (C) 2016 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-simple-subprocess"
+
+#include "ide-simple-subprocess.h"
+
+struct _IdeSimpleSubprocess
+{
+ GObject parent_instance;
+ GSubprocess *subprocess;
+};
+
+static void subprocess_iface_init (IdeSubprocessInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeSimpleSubprocess, ide_simple_subprocess, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_SUBPROCESS, subprocess_iface_init))
+
+static void
+ide_simple_subprocess_finalize (GObject *object)
+{
+ IdeSimpleSubprocess *self = (IdeSimpleSubprocess *)object;
+
+ g_clear_object (&self->subprocess);
+
+ G_OBJECT_CLASS (ide_simple_subprocess_parent_class)->finalize (object);
+}
+
+static void
+ide_simple_subprocess_class_init (IdeSimpleSubprocessClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_simple_subprocess_finalize;
+}
+
+static void
+ide_simple_subprocess_init (IdeSimpleSubprocess *self)
+{
+}
+
+#define WRAP_INTERFACE_METHOD(name, ...) \
+ g_subprocess_##name(IDE_SIMPLE_SUBPROCESS(subprocess)->subprocess, ## __VA_ARGS__)
+
+static const gchar *
+ide_simple_subprocess_get_identifier (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_identifier);
+}
+
+static GInputStream *
+ide_simple_subprocess_get_stdout_pipe (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_stdout_pipe);
+}
+
+static GInputStream *
+ide_simple_subprocess_get_stderr_pipe (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_stderr_pipe);
+}
+
+static GOutputStream *
+ide_simple_subprocess_get_stdin_pipe (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_stdin_pipe);
+}
+
+static gboolean
+ide_simple_subprocess_wait (IdeSubprocess *subprocess,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return WRAP_INTERFACE_METHOD (wait, cancellable, error);
+}
+
+static void
+ide_simple_subprocess_wait_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSubprocess *subprocess = (GSubprocess *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ if (!g_subprocess_wait_finish (subprocess, result, &error))
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_simple_subprocess_wait_async (IdeSubprocess *subprocess,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeSimpleSubprocess *self = (IdeSimpleSubprocess *)subprocess;
+ GTask *task = g_task_new (self, cancellable, callback, user_data);
+ g_subprocess_wait_async (self->subprocess, cancellable, ide_simple_subprocess_wait_cb, task);
+}
+
+static gboolean
+ide_simple_subprocess_wait_finish (IdeSubprocess *subprocess,
+ GAsyncResult *result,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static gboolean
+ide_simple_subprocess_get_successful (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_successful);
+}
+
+static gboolean
+ide_simple_subprocess_get_if_exited (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_if_exited);
+}
+
+static gint
+ide_simple_subprocess_get_exit_status (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_exit_status);
+}
+
+static gboolean
+ide_simple_subprocess_get_if_signaled (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_if_signaled);
+}
+
+static gint
+ide_simple_subprocess_get_term_sig (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_term_sig);
+}
+
+static gint
+ide_simple_subprocess_get_status (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (get_status);
+}
+
+static void
+ide_simple_subprocess_send_signal (IdeSubprocess *subprocess,
+ gint signal_num)
+{
+ return WRAP_INTERFACE_METHOD (send_signal, signal_num);
+}
+
+static void
+ide_simple_subprocess_force_exit (IdeSubprocess *subprocess)
+{
+ return WRAP_INTERFACE_METHOD (force_exit);
+}
+
+static gboolean
+ide_simple_subprocess_communicate (IdeSubprocess *subprocess,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error)
+{
+ return WRAP_INTERFACE_METHOD (communicate, stdin_buf, cancellable, stdout_buf, stderr_buf, error);
+}
+
+static gboolean
+ide_simple_subprocess_communicate_utf8 (IdeSubprocess *subprocess,
+ const gchar *stdin_buf,
+ GCancellable *cancellable,
+ gchar **stdout_buf,
+ gchar **stderr_buf,
+ GError **error)
+{
+ return WRAP_INTERFACE_METHOD (communicate_utf8, stdin_buf, cancellable, stdout_buf, stderr_buf, error);
+}
+
+static void
+free_object_pair (gpointer data)
+{
+ gpointer *pair = data;
+
+ g_clear_object (&pair[0]);
+ g_clear_object (&pair[1]);
+ g_free (pair);
+}
+
+static void
+ide_simple_subprocess_communicate_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSubprocess *subprocess = (GSubprocess *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GBytes) stdout_buf = NULL;
+ g_autoptr(GBytes) stderr_buf = NULL;
+ gpointer *data;
+
+ if (!g_subprocess_communicate_finish (subprocess, result, &stdout_buf, &stderr_buf, &error))
+ g_task_return_error (task, g_steal_pointer (&error));
+
+ data = g_new0 (gpointer, 2);
+ data[0] = g_steal_pointer (&stdout_buf);
+ data[1] = g_steal_pointer (&stderr_buf);
+
+ g_task_return_pointer (task, data, free_object_pair);
+}
+
+static void
+ide_simple_subprocess_communicate_async (IdeSubprocess *subprocess,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeSimpleSubprocess *self = (IdeSimpleSubprocess *)subprocess;
+ GTask *task = g_task_new (self, cancellable, callback, user_data);
+ g_subprocess_communicate_async (self->subprocess, stdin_buf, cancellable,
ide_simple_subprocess_communicate_cb, task);
+}
+
+static gboolean
+ide_simple_subprocess_communicate_finish (IdeSubprocess *subprocess,
+ GAsyncResult *result,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error)
+{
+ gpointer *pair;
+
+ pair = g_task_propagate_pointer (G_TASK (result), error);
+
+ if (pair != NULL)
+ {
+ if (stdout_buf != NULL)
+ *stdout_buf = g_steal_pointer (&pair[0]);
+
+ if (stderr_buf != NULL)
+ *stderr_buf = g_steal_pointer (&pair[1]);
+
+ free_object_pair (pair);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+subprocess_iface_init (IdeSubprocessInterface *iface)
+{
+ iface->get_identifier = ide_simple_subprocess_get_identifier;
+ iface->get_stdout_pipe = ide_simple_subprocess_get_stdout_pipe;
+ iface->get_stderr_pipe = ide_simple_subprocess_get_stderr_pipe;
+ iface->get_stdin_pipe = ide_simple_subprocess_get_stdin_pipe;
+ iface->wait = ide_simple_subprocess_wait;
+ iface->wait_async = ide_simple_subprocess_wait_async;
+ iface->wait_finish = ide_simple_subprocess_wait_finish;
+ iface->get_successful = ide_simple_subprocess_get_successful;
+ iface->get_if_exited = ide_simple_subprocess_get_if_exited;
+ iface->get_exit_status = ide_simple_subprocess_get_exit_status;
+ iface->get_if_signaled = ide_simple_subprocess_get_if_signaled;
+ iface->get_term_sig = ide_simple_subprocess_get_term_sig;
+ iface->get_status = ide_simple_subprocess_get_status;
+ iface->send_signal = ide_simple_subprocess_send_signal;
+ iface->force_exit = ide_simple_subprocess_force_exit;
+ iface->communicate = ide_simple_subprocess_communicate;
+ iface->communicate_utf8 = ide_simple_subprocess_communicate_utf8;
+ iface->communicate_async = ide_simple_subprocess_communicate_async;
+ iface->communicate_finish = ide_simple_subprocess_communicate_finish;
+}
+
+/**
+ * ide_simple_subprocess_new:
+ *
+ * Creates a new #IdeSimpleSubprocess wrapping the #GSubprocess.
+ *
+ * Returns: (transfer full): A new #IdeSubprocess
+ */
+IdeSubprocess *
+ide_simple_subprocess_new (GSubprocess *subprocess)
+{
+ IdeSimpleSubprocess *ret;
+
+ g_return_val_if_fail (G_IS_SUBPROCESS (subprocess), NULL);
+
+ ret = g_object_new (IDE_TYPE_SIMPLE_SUBPROCESS, NULL);
+ ret->subprocess = g_object_ref (subprocess);
+
+ return IDE_SUBPROCESS (ret);
+}
diff --git a/libide/subprocess/ide-simple-subprocess.h b/libide/subprocess/ide-simple-subprocess.h
new file mode 100644
index 0000000..dff3e3a
--- /dev/null
+++ b/libide/subprocess/ide-simple-subprocess.h
@@ -0,0 +1,34 @@
+/* ide-simple-subprocess.h
+ *
+ * Copyright (C) 2016 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/>.
+ */
+
+#ifndef IDE_SIMPLE_SUBPROCESS_H
+#define IDE_SIMPLE_SUBPROCESS_H
+
+#include "ide-subprocess.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SIMPLE_SUBPROCESS (ide_simple_subprocess_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeSimpleSubprocess, ide_simple_subprocess, IDE, SIMPLE_SUBPROCESS, GObject)
+
+IdeSubprocess *ide_simple_subprocess_new (GSubprocess *subprocess);
+
+G_END_DECLS
+
+#endif /* IDE_SIMPLE_SUBPROCESS_H */
diff --git a/libide/workers/ide-subprocess-launcher.c b/libide/subprocess/ide-subprocess-launcher.c
similarity index 66%
rename from libide/workers/ide-subprocess-launcher.c
rename to libide/subprocess/ide-subprocess-launcher.c
index 44e6563..a47454b 100644
--- a/libide/workers/ide-subprocess-launcher.c
+++ b/libide/subprocess/ide-subprocess-launcher.c
@@ -28,7 +28,10 @@
#include "buildsystem/ide-environment-variable.h"
#include "buildsystem/ide-environment.h"
-#include "workers/ide-subprocess-launcher.h"
+#include "subprocess/ide-breakout-subprocess.h"
+#include "subprocess/ide-breakout-subprocess-private.h"
+#include "subprocess/ide-simple-subprocess.h"
+#include "subprocess/ide-subprocess-launcher.h"
typedef struct
{
@@ -37,15 +40,20 @@ typedef struct
GPtrArray *argv;
gchar *cwd;
GPtrArray *environ;
+
+ guint run_on_host : 1;
+ guint clear_env : 1;
} IdeSubprocessLauncherPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (IdeSubprocessLauncher, ide_subprocess_launcher, G_TYPE_OBJECT)
enum {
PROP_0,
+ PROP_CLEAR_ENV,
PROP_CWD,
PROP_ENVIRON,
PROP_FLAGS,
+ PROP_RUN_ON_HOST,
N_PROPS
};
@@ -95,6 +103,20 @@ ide_subprocess_launcher_kill_process_group (GCancellable *cancellable,
#endif
}
+static void
+ide_subprocess_launcher_kill_host_process (GCancellable *cancellable,
+ IdeSubprocess *subprocess)
+{
+ g_assert (G_IS_CANCELLABLE (cancellable));
+ g_assert (IDE_IS_BREAKOUT_SUBPROCESS (subprocess));
+
+ g_signal_handlers_disconnect_by_func (cancellable,
+ G_CALLBACK (ide_subprocess_launcher_kill_host_process),
+ subprocess);
+
+ ide_subprocess_force_exit (subprocess);
+}
+
IdeSubprocessLauncher *
ide_subprocess_launcher_new (GSubprocessFlags flags)
{
@@ -103,6 +125,91 @@ ide_subprocess_launcher_new (GSubprocessFlags flags)
NULL);
}
+static gboolean
+should_use_breakout_process (IdeSubprocessLauncher *self)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+ static gsize initialized;
+ static gboolean is_contained;
+
+ g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+ if (g_once_init_enter (&initialized))
+ {
+ g_autofree gchar *flatpak_info_path = NULL;
+
+ flatpak_info_path = g_build_filename (g_get_user_runtime_dir (),
+ "flatpak-info",
+ NULL);
+
+ if (g_file_test (flatpak_info_path, G_FILE_TEST_EXISTS))
+ is_contained = TRUE;
+
+ g_once_init_leave (&initialized, TRUE);
+ }
+
+ if (g_getenv ("IDE_USE_BREAKOUT_SUBPROCESS") != NULL)
+ return TRUE;
+
+ if (!priv->run_on_host)
+ return FALSE;
+
+ return is_contained;
+}
+
+static void
+ide_subprocess_launcher_spawn_host_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ IdeSubprocessLauncher *self = source_object;
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+ g_autoptr(IdeSubprocess) process = NULL;
+ g_autoptr(GError) error = NULL;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+#ifdef IDE_ENABLE_TRACE
+ {
+ g_autofree gchar *str = NULL;
+ g_autofree gchar *env = NULL;
+ str = g_strjoinv (" ", (gchar **)priv->argv->pdata);
+ env = g_strjoinv (" ", (gchar **)priv->environ->pdata);
+ IDE_TRACE_MSG ("Launching '%s' with environment %s", str, env);
+ }
+#endif
+
+ process = _ide_breakout_subprocess_new (priv->cwd,
+ (const gchar * const *)priv->argv->pdata,
+ (const gchar * const *)priv->environ->pdata,
+ priv->flags,
+ priv->clear_env,
+ cancellable,
+ &error);
+
+ if (process == NULL)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ IDE_EXIT;
+ }
+
+ if (cancellable != NULL)
+ {
+ g_signal_connect_object (cancellable,
+ "cancelled",
+ G_CALLBACK (ide_subprocess_launcher_kill_host_process),
+ process,
+ 0);
+ }
+
+ g_task_return_pointer (task, g_steal_pointer (&process), g_object_unref);
+
+ IDE_EXIT;
+}
+
static void
ide_subprocess_launcher_spawn_worker (GTask *task,
gpointer source_object,
@@ -112,16 +219,19 @@ ide_subprocess_launcher_spawn_worker (GTask *task,
IdeSubprocessLauncher *self = source_object;
IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
g_autoptr(GSubprocessLauncher) launcher = NULL;
- GSubprocess *ret;
- GError *error = NULL;
+ g_autoptr(GSubprocess) real = NULL;
+ g_autoptr(IdeSubprocess) wrapped = NULL;
+ g_autoptr(GError) error = NULL;
g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
#ifdef IDE_ENABLE_TRACE
{
g_autofree gchar *str = NULL;
+ g_autofree gchar *env = NULL;
str = g_strjoinv (" ", (gchar **)priv->argv->pdata);
- IDE_TRACE_MSG ("Launching '%s'", str);
+ env = g_strjoinv (" ", (gchar **)priv->environ->pdata);
+ IDE_TRACE_MSG ("Launching '%s' with environment %s", str, env);
}
#endif
@@ -130,30 +240,31 @@ ide_subprocess_launcher_spawn_worker (GTask *task,
g_subprocess_launcher_set_cwd (launcher, priv->cwd);
if (priv->environ->len > 1)
g_subprocess_launcher_set_environ (launcher, (gchar **)priv->environ->pdata);
- ret = g_subprocess_launcher_spawnv (launcher,
- (const gchar * const *)priv->argv->pdata,
- &error);
+ real = g_subprocess_launcher_spawnv (launcher,
+ (const gchar * const *)priv->argv->pdata,
+ &error);
- if (ret == NULL)
+ if (real == NULL)
{
- g_task_return_error (task, error);
+ g_task_return_error (task, g_steal_pointer (&error));
return;
}
if (cancellable != NULL)
{
- g_signal_connect_data (cancellable,
- "cancelled",
- G_CALLBACK (ide_subprocess_launcher_kill_process_group),
- g_object_ref (ret),
- (GClosureNotify)g_object_unref,
- 0);
+ g_signal_connect_object (cancellable,
+ "cancelled",
+ G_CALLBACK (ide_subprocess_launcher_kill_process_group),
+ real,
+ 0);
}
- g_task_return_pointer (task, ret, g_object_unref);
+ wrapped = ide_simple_subprocess_new (real);
+
+ g_task_return_pointer (task, g_steal_pointer (&wrapped), g_object_unref);
}
-static GSubprocess *
+static IdeSubprocess *
ide_subprocess_launcher_real_spawn_sync (IdeSubprocessLauncher *self,
GCancellable *cancellable,
GError **error)
@@ -164,7 +275,12 @@ ide_subprocess_launcher_real_spawn_sync (IdeSubprocessLauncher *self,
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
task = g_task_new (self, cancellable, NULL, NULL);
- g_task_run_in_thread_sync (task, ide_subprocess_launcher_spawn_worker);
+ g_task_set_source_tag (task, ide_subprocess_launcher_real_spawn_sync);
+
+ if (should_use_breakout_process (self))
+ g_task_run_in_thread_sync (task, ide_subprocess_launcher_spawn_host_worker);
+ else
+ g_task_run_in_thread_sync (task, ide_subprocess_launcher_spawn_worker);
return g_task_propagate_pointer (task, error);
}
@@ -186,10 +302,15 @@ ide_subprocess_launcher_real_spawn_async (IdeSubprocessLauncher *self,
*/
task = g_task_new (self, cancellable, callback, user_data);
- g_task_run_in_thread (task, ide_subprocess_launcher_spawn_worker);
+ g_task_set_source_tag (task, ide_subprocess_launcher_real_spawn_async);
+
+ if (should_use_breakout_process (self))
+ g_task_run_in_thread (task, ide_subprocess_launcher_spawn_host_worker);
+ else
+ g_task_run_in_thread (task, ide_subprocess_launcher_spawn_worker);
}
-static GSubprocess *
+static IdeSubprocess *
ide_subprocess_launcher_real_spawn_finish (IdeSubprocessLauncher *self,
GAsyncResult *result,
GError **error)
@@ -223,6 +344,10 @@ ide_subprocess_launcher_get_property (GObject *object,
switch (prop_id)
{
+ case PROP_CLEAR_ENV:
+ g_value_set_boolean (value, ide_subprocess_launcher_get_clear_env (self));
+ break;
+
case PROP_CWD:
g_value_set_string (value, ide_subprocess_launcher_get_cwd (self));
break;
@@ -235,6 +360,10 @@ ide_subprocess_launcher_get_property (GObject *object,
g_value_set_boxed (value, ide_subprocess_launcher_get_environ (self));
break;
+ case PROP_RUN_ON_HOST:
+ g_value_set_boolean (value, ide_subprocess_launcher_get_run_on_host (self));
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -250,6 +379,10 @@ ide_subprocess_launcher_set_property (GObject *object,
switch (prop_id)
{
+ case PROP_CLEAR_ENV:
+ ide_subprocess_launcher_set_clear_env (self, g_value_get_boolean (value));
+ break;
+
case PROP_CWD:
ide_subprocess_launcher_set_cwd (self, g_value_get_string (value));
break;
@@ -262,6 +395,10 @@ ide_subprocess_launcher_set_property (GObject *object,
ide_subprocess_launcher_set_environ (self, g_value_get_boxed (value));
break;
+ case PROP_RUN_ON_HOST:
+ ide_subprocess_launcher_set_run_on_host (self, g_value_get_boolean (value));
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -280,6 +417,13 @@ ide_subprocess_launcher_class_init (IdeSubprocessLauncherClass *klass)
klass->spawn_async = ide_subprocess_launcher_real_spawn_async;
klass->spawn_finish = ide_subprocess_launcher_real_spawn_finish;
+ properties [PROP_CLEAR_ENV] =
+ g_param_spec_boolean ("clean-env",
+ "Clear Environment",
+ "If the environment should be cleared before setting environment variables.",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
properties [PROP_CWD] =
g_param_spec_string ("cwd",
"Current Working Directory",
@@ -302,6 +446,13 @@ ide_subprocess_launcher_class_init (IdeSubprocessLauncherClass *klass)
G_TYPE_STRV,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ 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));
+
g_object_class_install_properties (object_class, N_PROPS, properties);
}
@@ -447,9 +598,9 @@ ide_subprocess_launcher_spawn_async (IdeSubprocessLauncher *self,
*
* Complete a request to asynchronously spawn a process.
*
- * Returns: (transfer full): A #GSubprocess or %NULL upon error.
+ * Returns: (transfer full): A #IdeSubprocess or %NULL upon error.
*/
-GSubprocess *
+IdeSubprocess *
ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher *self,
GAsyncResult *result,
GError **error)
@@ -465,9 +616,9 @@ ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher *self,
*
* Synchronously spawn a process using the internal state.
*
- * Returns: (transfer full): A #GSubprocess or %NULL upon error.
+ * Returns: (transfer full): A #IdeSubprocess or %NULL upon error.
*/
-GSubprocess *
+IdeSubprocess *
ide_subprocess_launcher_spawn_sync (IdeSubprocessLauncher *self,
GCancellable *cancellable,
GError **error)
@@ -566,3 +717,73 @@ ide_subprocess_launcher_pop_argv (IdeSubprocessLauncher *self)
return ret;
}
+
+/**
+ * ide_subprocess_launcher_get_run_on_host:
+ *
+ * Gets if the process should be executed on the host system. This might be
+ * useful for situations where running in a contained environment is not
+ * sufficient to perform the given task.
+ *
+ * Currently, only flatpak is supported for breaking out of the containment
+ * zone and requires the application was built with --allow=devel.
+ *
+ * Returns: %TRUE if the process should be executed outside the containment zone.
+ */
+gboolean
+ide_subprocess_launcher_get_run_on_host (IdeSubprocessLauncher *self)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), FALSE);
+
+ return priv->run_on_host;
+}
+
+/**
+ * ide_subprocess_launcher_set_run_on_host:
+ *
+ * Sets the #IdeSubprocessLauncher:run-on-host property. See
+ * ide_subprocess_launcher_get_run_on_host() for more information.
+ */
+void
+ide_subprocess_launcher_set_run_on_host (IdeSubprocessLauncher *self,
+ gboolean run_on_host)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ run_on_host = !!run_on_host;
+
+ if (priv->run_on_host != run_on_host)
+ {
+ priv->run_on_host = run_on_host;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUN_ON_HOST]);
+ }
+}
+
+gboolean
+ide_subprocess_launcher_get_clear_env (IdeSubprocessLauncher *self)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self), FALSE);
+
+ return priv->clear_env;
+}
+
+void
+ide_subprocess_launcher_set_clear_env (IdeSubprocessLauncher *self,
+ gboolean clear_env)
+{
+ IdeSubprocessLauncherPrivate *priv = ide_subprocess_launcher_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (self));
+
+ clear_env = !!clear_env;
+
+ if (priv->clear_env != clear_env)
+ {
+ priv->clear_env = clear_env;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLEAR_ENV]);
+ }
+}
diff --git a/libide/workers/ide-subprocess-launcher.h b/libide/subprocess/ide-subprocess-launcher.h
similarity index 72%
rename from libide/workers/ide-subprocess-launcher.h
rename to libide/subprocess/ide-subprocess-launcher.h
index b72813a..4b2e6af 100644
--- a/libide/workers/ide-subprocess-launcher.h
+++ b/libide/subprocess/ide-subprocess-launcher.h
@@ -33,16 +33,25 @@ struct _IdeSubprocessLauncherClass
{
GObjectClass parent_class;
- GSubprocess *(*spawn_sync) (IdeSubprocessLauncher *self,
- GCancellable *cancellable,
- GError **error);
- void (*spawn_async) (IdeSubprocessLauncher *self,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data);
- GSubprocess *(*spawn_finish) (IdeSubprocessLauncher *self,
- GAsyncResult *result,
- GError **error);
+ IdeSubprocess *(*spawn_sync) (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GError **error);
+ void (*spawn_async) (IdeSubprocessLauncher *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ IdeSubprocess *(*spawn_finish) (IdeSubprocessLauncher *self,
+ GAsyncResult *result,
+ GError **error);
+
+ gpointer _reserved1;
+ gpointer _reserved2;
+ gpointer _reserved3;
+ gpointer _reserved4;
+ gpointer _reserved5;
+ gpointer _reserved6;
+ gpointer _reserved7;
+ gpointer _reserved8;
};
IdeSubprocessLauncher *ide_subprocess_launcher_new (GSubprocessFlags flags);
@@ -52,6 +61,12 @@ void ide_subprocess_launcher_set_cwd (IdeSubproces
GSubprocessFlags ide_subprocess_launcher_get_flags (IdeSubprocessLauncher *self);
void ide_subprocess_launcher_set_flags (IdeSubprocessLauncher *self,
GSubprocessFlags flags);
+gboolean ide_subprocess_launcher_get_run_on_host (IdeSubprocessLauncher *self);
+void ide_subprocess_launcher_set_run_on_host (IdeSubprocessLauncher *self,
+ gboolean run_on_host);
+gboolean ide_subprocess_launcher_get_clear_env (IdeSubprocessLauncher *self);
+void ide_subprocess_launcher_set_clear_env (IdeSubprocessLauncher *self,
+ gboolean clear_env);
const gchar * const *ide_subprocess_launcher_get_environ (IdeSubprocessLauncher *self);
void ide_subprocess_launcher_set_environ (IdeSubprocessLauncher *self,
const gchar * const *environ_);
@@ -66,14 +81,14 @@ void ide_subprocess_launcher_push_args (IdeSubproces
void ide_subprocess_launcher_push_argv (IdeSubprocessLauncher *self,
const gchar *argv);
gchar *ide_subprocess_launcher_pop_argv (IdeSubprocessLauncher *self)
G_GNUC_WARN_UNUSED_RESULT;
-GSubprocess *ide_subprocess_launcher_spawn_sync (IdeSubprocessLauncher *self,
+IdeSubprocess *ide_subprocess_launcher_spawn_sync (IdeSubprocessLauncher *self,
GCancellable *cancellable,
GError **error);
void ide_subprocess_launcher_spawn_async (IdeSubprocessLauncher *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
-GSubprocess *ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher *self,
+IdeSubprocess *ide_subprocess_launcher_spawn_finish (IdeSubprocessLauncher *self,
GAsyncResult *result,
GError **error);
diff --git a/libide/subprocess/ide-subprocess.c b/libide/subprocess/ide-subprocess.c
new file mode 100644
index 0000000..507c965
--- /dev/null
+++ b/libide/subprocess/ide-subprocess.c
@@ -0,0 +1,328 @@
+/* ide-subprocess.c
+ *
+ * Copyright (C) 2016 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/>.
+ */
+
+#define G_LOG_DOMAIN "ide-subprocess"
+
+#include "ide-subprocess.h"
+
+G_DEFINE_INTERFACE (IdeSubprocess, ide_subprocess, G_TYPE_OBJECT)
+
+static void
+ide_subprocess_default_init (IdeSubprocessInterface *iface)
+{
+}
+
+#define WRAP_INTERFACE_METHOD(self, name, default_return, ...) \
+ ((IDE_SUBPROCESS_GET_IFACE(self)->name != NULL) ? \
+ IDE_SUBPROCESS_GET_IFACE(self)->name (self, ##__VA_ARGS__) : \
+ default_return)
+
+const gchar *
+ide_subprocess_get_identifier (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
+
+ return WRAP_INTERFACE_METHOD (self, get_identifier, NULL);
+}
+
+/**
+ * ide_subprocess_get_stdout_pipe:
+ *
+ * Returns: (transfer none): A #GInputStream or %NULL.
+ */
+GInputStream *
+ide_subprocess_get_stdout_pipe (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
+
+ return WRAP_INTERFACE_METHOD (self, get_stdout_pipe, NULL);
+}
+
+/**
+ * ide_subprocess_get_stderr_pipe:
+ *
+ * Returns: (transfer none): A #GInputStream or %NULL.
+ */
+GInputStream *
+ide_subprocess_get_stderr_pipe (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
+
+ return WRAP_INTERFACE_METHOD (self, get_stderr_pipe, NULL);
+}
+
+/**
+ * ide_subprocess_get_stdin_pipe:
+ *
+ * Returns: (transfer none): A #GOutputStream or %NULL.
+ */
+GOutputStream *
+ide_subprocess_get_stdin_pipe (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), NULL);
+
+ return WRAP_INTERFACE_METHOD (self, get_stdin_pipe, NULL);
+}
+
+gboolean
+ide_subprocess_wait (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+ return WRAP_INTERFACE_METHOD (self, wait, FALSE, cancellable, error);
+}
+
+gboolean
+ide_subprocess_wait_check (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+ return ide_subprocess_wait (self, cancellable, error) &&
+ ide_subprocess_check_exit_status (self, error);
+}
+
+void
+ide_subprocess_wait_async (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_SUBPROCESS (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ WRAP_INTERFACE_METHOD (self, wait_async, NULL, cancellable, callback, user_data);
+}
+
+gboolean
+ide_subprocess_wait_finish (IdeSubprocess *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+ return WRAP_INTERFACE_METHOD (self, wait_finish, FALSE, result, error);
+}
+
+static void
+ide_subprocess_wait_check_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeSubprocess *self = (IdeSubprocess *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (IDE_IS_SUBPROCESS (self));
+ g_assert (G_IS_TASK (task));
+
+ if (!ide_subprocess_wait_finish (self, result, &error))
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ if (!ide_subprocess_check_exit_status (self, &error))
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ g_task_return_boolean (task, TRUE);
+}
+
+void
+ide_subprocess_wait_check_async (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (IDE_IS_SUBPROCESS (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, ide_subprocess_wait_check_async);
+
+ ide_subprocess_wait_async (self,
+ cancellable,
+ ide_subprocess_wait_check_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+ide_subprocess_wait_check_finish (IdeSubprocess *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+ g_return_val_if_fail (g_task_is_valid (G_TASK (result), self), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), FALSE);
+}
+
+gboolean
+ide_subprocess_get_successful (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+ return WRAP_INTERFACE_METHOD (self, get_successful, FALSE);
+}
+
+gboolean
+ide_subprocess_get_if_exited (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+ return WRAP_INTERFACE_METHOD (self, get_if_exited, FALSE);
+}
+
+gint
+ide_subprocess_get_exit_status (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), 0);
+
+ return WRAP_INTERFACE_METHOD (self, get_exit_status, 0);
+}
+
+gboolean
+ide_subprocess_get_if_signaled (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+ return WRAP_INTERFACE_METHOD (self, get_if_signaled, FALSE);
+}
+
+gint
+ide_subprocess_get_term_sig (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), 0);
+
+ return WRAP_INTERFACE_METHOD (self, get_term_sig, 0);
+}
+
+gint
+ide_subprocess_get_status (IdeSubprocess *self)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), 0);
+
+ return WRAP_INTERFACE_METHOD (self, get_status, 0);
+}
+
+void
+ide_subprocess_send_signal (IdeSubprocess *self,
+ gint signal_num)
+{
+ g_return_if_fail (IDE_IS_SUBPROCESS (self));
+
+ WRAP_INTERFACE_METHOD (self, send_signal, NULL, signal_num);
+}
+
+void
+ide_subprocess_force_exit (IdeSubprocess *self)
+{
+ g_return_if_fail (IDE_IS_SUBPROCESS (self));
+
+ WRAP_INTERFACE_METHOD (self, force_exit, NULL);
+}
+
+gboolean
+ide_subprocess_communicate (IdeSubprocess *self,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+ return WRAP_INTERFACE_METHOD (self, communicate, FALSE, stdin_buf, cancellable, stdout_buf, stderr_buf,
error);
+}
+
+/**
+ * ide_subprocess_communicate_utf8:
+ * @self: an #IdeSubprocess
+ * @stdin_buf: (nullable): input to deliver to the subprocesses stdin stream
+ * @cancellable: (nullable): an optional #GCancellable
+ * @stdout_buf: (out) (nullable): an optional location for the stdout contents
+ * @stderr_buf: (out) (nullable): an optional location for the stderr contents
+ *
+ * This process acts identical to g_subprocess_communicate_utf8().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+ide_subprocess_communicate_utf8 (IdeSubprocess *self,
+ const gchar *stdin_buf,
+ GCancellable *cancellable,
+ gchar **stdout_buf,
+ gchar **stderr_buf,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+ g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+ return WRAP_INTERFACE_METHOD (self, communicate_utf8, FALSE, stdin_buf, cancellable, stdout_buf,
stderr_buf, error);
+}
+
+void
+ide_subprocess_communicate_async (IdeSubprocess *self,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (IDE_IS_SUBPROCESS (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ WRAP_INTERFACE_METHOD (self, communicate_async, NULL, stdin_buf, cancellable, callback, user_data);
+}
+
+gboolean
+ide_subprocess_communicate_finish (IdeSubprocess *self,
+ GAsyncResult *result,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+ return WRAP_INTERFACE_METHOD (self, communicate_finish, FALSE, result, stdout_buf, stderr_buf, error);
+}
+
+gboolean
+ide_subprocess_check_exit_status (IdeSubprocess *self,
+ GError **error)
+{
+ gint exit_status;
+
+ g_return_val_if_fail (IDE_IS_SUBPROCESS (self), FALSE);
+
+ exit_status = ide_subprocess_get_exit_status (self);
+
+ return g_spawn_check_exit_status (exit_status, error);
+}
diff --git a/libide/subprocess/ide-subprocess.h b/libide/subprocess/ide-subprocess.h
new file mode 100644
index 0000000..557bc98
--- /dev/null
+++ b/libide/subprocess/ide-subprocess.h
@@ -0,0 +1,141 @@
+/* ide-subprocess.h
+ *
+ * Copyright (C) 2016 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/>.
+ */
+
+#ifndef IDE_SUBPROCESS_H
+#define IDE_SUBPROCESS_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SUBPROCESS (ide_subprocess_get_type())
+
+G_DECLARE_INTERFACE (IdeSubprocess, ide_subprocess, IDE, SUBPROCESS, GObject)
+
+struct _IdeSubprocessInterface
+{
+ GTypeInterface parent_interface;
+
+ const gchar *(*get_identifier) (IdeSubprocess *self);
+ GInputStream *(*get_stdout_pipe) (IdeSubprocess *self);
+ GInputStream *(*get_stderr_pipe) (IdeSubprocess *self);
+ GOutputStream *(*get_stdin_pipe) (IdeSubprocess *self);
+ gboolean (*wait) (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GError **error);
+ void (*wait_async) (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (*wait_finish) (IdeSubprocess *self,
+ GAsyncResult *result,
+ GError **error);
+ gboolean (*get_successful) (IdeSubprocess *self);
+ gboolean (*get_if_exited) (IdeSubprocess *self);
+ gint (*get_exit_status) (IdeSubprocess *self);
+ gboolean (*get_if_signaled) (IdeSubprocess *self);
+ gint (*get_term_sig) (IdeSubprocess *self);
+ gint (*get_status) (IdeSubprocess *self);
+ void (*send_signal) (IdeSubprocess *self,
+ gint signal_num);
+ void (*force_exit) (IdeSubprocess *self);
+ gboolean (*communicate) (IdeSubprocess *self,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error);
+ gboolean (*communicate_utf8) (IdeSubprocess *self,
+ const gchar *stdin_buf,
+ GCancellable *cancellable,
+ gchar **stdout_buf,
+ gchar **stderr_buf,
+ GError **error);
+ void (*communicate_async) (IdeSubprocess *self,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (*communicate_finish) (IdeSubprocess *self,
+ GAsyncResult *result,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error);
+};
+
+const gchar *ide_subprocess_get_identifier (IdeSubprocess *self);
+GInputStream *ide_subprocess_get_stdout_pipe (IdeSubprocess *self);
+GInputStream *ide_subprocess_get_stderr_pipe (IdeSubprocess *self);
+GOutputStream *ide_subprocess_get_stdin_pipe (IdeSubprocess *self);
+gboolean ide_subprocess_wait (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GError **error);
+gboolean ide_subprocess_wait_check (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GError **error);
+void ide_subprocess_wait_async (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_subprocess_wait_finish (IdeSubprocess *self,
+ GAsyncResult *result,
+ GError **error);
+void ide_subprocess_wait_check_async (IdeSubprocess *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_subprocess_wait_check_finish (IdeSubprocess *self,
+ GAsyncResult *result,
+ GError **error);
+gboolean ide_subprocess_check_exit_status (IdeSubprocess *self,
+ GError **error);
+gboolean ide_subprocess_get_successful (IdeSubprocess *self);
+gboolean ide_subprocess_get_if_exited (IdeSubprocess *self);
+gint ide_subprocess_get_exit_status (IdeSubprocess *self);
+gboolean ide_subprocess_get_if_signaled (IdeSubprocess *self);
+gint ide_subprocess_get_term_sig (IdeSubprocess *self);
+gint ide_subprocess_get_status (IdeSubprocess *self);
+void ide_subprocess_send_signal (IdeSubprocess *self,
+ gint signal_num);
+void ide_subprocess_force_exit (IdeSubprocess *self);
+gboolean ide_subprocess_communicate (IdeSubprocess *self,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error);
+gboolean ide_subprocess_communicate_utf8 (IdeSubprocess *self,
+ const gchar *stdin_buf,
+ GCancellable *cancellable,
+ gchar **stdout_buf,
+ gchar **stderr_buf,
+ GError **error);
+void ide_subprocess_communicate_async (IdeSubprocess *self,
+ GBytes *stdin_buf,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_subprocess_communicate_finish (IdeSubprocess *self,
+ GAsyncResult *result,
+ GBytes **stdout_buf,
+ GBytes **stderr_buf,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* IDE_SUBPROCESS_H */
diff --git a/plugins/autotools/ide-autotools-build-system.c b/plugins/autotools/ide-autotools-build-system.c
index 4f64866..7debea4 100644
--- a/plugins/autotools/ide-autotools-build-system.c
+++ b/plugins/autotools/ide-autotools-build-system.c
@@ -932,11 +932,11 @@ simple_make_command_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
- GSubprocess *subprocess = (GSubprocess *)object;
+ IdeSubprocess *subprocess = (IdeSubprocess *)object;
g_autoptr(GTask) task = user_data;
GError *error = NULL;
- if (!g_subprocess_wait_check_finish (subprocess, result, &error))
+ if (!ide_subprocess_wait_check_finish (subprocess, result, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
@@ -949,7 +949,7 @@ simple_make_command (GFile *directory,
IdeConfiguration *configuration)
{
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
- g_autoptr(GSubprocess) subprocess = NULL;
+ g_autoptr(IdeSubprocess) subprocess = NULL;
GCancellable *cancellable;
IdeRuntime *runtime;
GError *error = NULL;
@@ -1000,10 +1000,10 @@ simple_make_command (GFile *directory,
return;
}
- g_subprocess_wait_check_async (subprocess,
- g_task_get_cancellable (task),
- simple_make_command_cb,
- g_object_ref (task));
+ ide_subprocess_wait_check_async (subprocess,
+ cancellable,
+ simple_make_command_cb,
+ g_steal_pointer (&task));
}
static void
diff --git a/plugins/autotools/ide-autotools-build-task.c b/plugins/autotools/ide-autotools-build-task.c
index 8f43973..6a77cd9 100644
--- a/plugins/autotools/ide-autotools-build-task.c
+++ b/plugins/autotools/ide-autotools-build-task.c
@@ -16,6 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#define G_LOG_DOMAIN "ide-autotools-build-task"
+
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
@@ -75,30 +77,30 @@ enum {
LAST_PROP
};
-static GSubprocess *log_and_spawn (IdeAutotoolsBuildTask *self,
- IdeSubprocessLauncher *launcher,
- GCancellable *cancellable,
- GError **error,
- const gchar *argv0,
- ...) G_GNUC_NULL_TERMINATED;
-static gboolean step_mkdirs (GTask *task,
- IdeAutotoolsBuildTask *self,
- WorkerState *state,
- GCancellable *cancellable);
-static gboolean step_autogen (GTask *task,
- IdeAutotoolsBuildTask *self,
- WorkerState *state,
- GCancellable *cancellable);
-static gboolean step_configure (GTask *task,
- IdeAutotoolsBuildTask *self,
- WorkerState *state,
- GCancellable *cancellable);
-static gboolean step_make_all (GTask *task,
- IdeAutotoolsBuildTask *self,
- WorkerState *state,
- GCancellable *cancellable);
-static void apply_environment (IdeAutotoolsBuildTask *self,
- IdeSubprocessLauncher *launcher);
+static IdeSubprocess *log_and_spawn (IdeAutotoolsBuildTask *self,
+ IdeSubprocessLauncher *launcher,
+ GCancellable *cancellable,
+ GError **error,
+ const gchar *argv0,
+ ...) G_GNUC_NULL_TERMINATED;
+static gboolean step_mkdirs (GTask *task,
+ IdeAutotoolsBuildTask *self,
+ WorkerState *state,
+ GCancellable *cancellable);
+static gboolean step_autogen (GTask *task,
+ IdeAutotoolsBuildTask *self,
+ WorkerState *state,
+ GCancellable *cancellable);
+static gboolean step_configure (GTask *task,
+ IdeAutotoolsBuildTask *self,
+ WorkerState *state,
+ GCancellable *cancellable);
+static gboolean step_make_all (GTask *task,
+ IdeAutotoolsBuildTask *self,
+ WorkerState *state,
+ GCancellable *cancellable);
+static void apply_environment (IdeAutotoolsBuildTask *self,
+ IdeSubprocessLauncher *launcher);
static GParamSpec *properties [LAST_PROP];
static WorkStep workSteps [] = {
@@ -735,7 +737,7 @@ log_in_main (gpointer data)
return G_SOURCE_REMOVE;
}
-static GSubprocess *
+static IdeSubprocess *
log_and_spawn (IdeAutotoolsBuildTask *self,
IdeSubprocessLauncher *launcher,
GCancellable *cancellable,
@@ -743,7 +745,7 @@ log_and_spawn (IdeAutotoolsBuildTask *self,
const gchar *argv0,
...)
{
- GSubprocess *ret;
+ IdeSubprocess *ret;
struct {
IdeBuildResult *result;
gchar *message;
@@ -830,7 +832,7 @@ step_autogen (GTask *task,
g_autofree gchar *autogen_sh_path = NULL;
g_autofree gchar *configure_path = NULL;
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
- g_autoptr(GSubprocess) process = NULL;
+ g_autoptr(IdeSubprocess) process = NULL;
GError *error = NULL;
g_assert (G_IS_TASK (task));
@@ -889,7 +891,7 @@ step_autogen (GTask *task,
ide_build_result_log_subprocess (IDE_BUILD_RESULT (self), process);
- if (!g_subprocess_wait_check (process, cancellable, &error))
+ if (!ide_subprocess_wait_check (process, cancellable, &error))
{
g_task_return_error (task, error);
return FALSE;
@@ -915,7 +917,7 @@ step_configure (GTask *task,
GCancellable *cancellable)
{
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
- g_autoptr(GSubprocess) process = NULL;
+ g_autoptr(IdeSubprocess) process = NULL;
g_autofree gchar *makefile_path = NULL;
g_autofree gchar *config_log = NULL;
GError *error = NULL;
@@ -959,7 +961,7 @@ step_configure (GTask *task,
ide_build_result_log_subprocess (IDE_BUILD_RESULT (self), process);
- if (!g_subprocess_wait_check (process, cancellable, &error))
+ if (!ide_subprocess_wait_check (process, cancellable, &error))
{
g_task_return_error (task, error);
return FALSE;
@@ -981,7 +983,7 @@ step_make_all (GTask *task,
GCancellable *cancellable)
{
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
- g_autoptr(GSubprocess) process = NULL;
+ g_autoptr(IdeSubprocess) process = NULL;
const gchar * const *targets;
const gchar *make = NULL;
gchar *default_targets[] = { "all", NULL };
@@ -1047,7 +1049,7 @@ step_make_all (GTask *task,
ide_build_result_log_subprocess (IDE_BUILD_RESULT (self), process);
- if (!g_subprocess_wait_check (process, cancellable, &error))
+ if (!ide_subprocess_wait_check (process, cancellable, &error))
{
g_task_return_error (task, error);
return FALSE;
diff --git a/plugins/autotools/ide-makecache.c b/plugins/autotools/ide-makecache.c
index 14bd650..c4ecb82 100644
--- a/plugins/autotools/ide-makecache.c
+++ b/plugins/autotools/ide-makecache.c
@@ -1943,7 +1943,7 @@ ide_makecache_get_build_targets_worker (GTask *task,
for (guint j = 0; j < makedirs->len; j++)
{
- g_autoptr(GSubprocess) subprocess = NULL;
+ g_autoptr(IdeSubprocess) subprocess = NULL;
g_autoptr(GHashTable) amdirs = NULL;
g_autofree gchar *path = NULL;
GFile *makedir;
@@ -1970,7 +1970,7 @@ ide_makecache_get_build_targets_worker (GTask *task,
* Write our helper target that will include the Makefile and then print
* debug variables we care about.
*/
- if (!g_subprocess_communicate_utf8 (subprocess, PRINT_VARS, NULL, &stdout_buf, NULL, &error))
+ if (!ide_subprocess_communicate_utf8 (subprocess, PRINT_VARS, NULL, &stdout_buf, NULL, &error))
{
g_task_return_error (task, error);
IDE_GOTO (failure);
diff --git a/plugins/flatpak/gbp-flatpak-runtime.c b/plugins/flatpak/gbp-flatpak-runtime.c
index e8e35bc..c2d2211 100644
--- a/plugins/flatpak/gbp-flatpak-runtime.c
+++ b/plugins/flatpak/gbp-flatpak-runtime.c
@@ -61,11 +61,11 @@ get_build_directory (GbpFlatpakRuntime *self)
static gboolean
gbp_flatpak_runtime_contains_program_in_path (IdeRuntime *runtime,
- const gchar *program,
- GCancellable *cancellable)
+ const gchar *program,
+ GCancellable *cancellable)
{
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
- g_autoptr(GSubprocess) subprocess = NULL;
+ g_autoptr(IdeSubprocess) subprocess = NULL;
g_assert (IDE_IS_RUNTIME (runtime));
g_assert (program != NULL);
@@ -78,14 +78,14 @@ gbp_flatpak_runtime_contains_program_in_path (IdeRuntime *runtime,
subprocess = ide_subprocess_launcher_spawn_sync (launcher, cancellable, NULL);
- return (subprocess != NULL) && g_subprocess_wait_check (subprocess, cancellable, NULL);
+ return (subprocess != NULL) && ide_subprocess_wait_check (subprocess, cancellable, NULL);
}
static void
gbp_flatpak_runtime_prebuild_worker (GTask *task,
- gpointer source_object,
- gpointer task_data,
- GCancellable *cancellable)
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
{
GbpFlatpakRuntime *self = source_object;
g_autofree gchar *build_path = NULL;
@@ -153,9 +153,9 @@ gbp_flatpak_runtime_prebuild_worker (GTask *task,
static void
gbp_flatpak_runtime_prebuild_async (IdeRuntime *runtime,
- GCancellable *cancellable,
- GAsyncReadyCallback callback,
- gpointer user_data)
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
GbpFlatpakRuntime *self = (GbpFlatpakRuntime *)runtime;
g_autoptr(GTask) task = NULL;
@@ -169,8 +169,8 @@ gbp_flatpak_runtime_prebuild_async (IdeRuntime *runtime,
static gboolean
gbp_flatpak_runtime_prebuild_finish (IdeRuntime *runtime,
- GAsyncResult *result,
- GError **error)
+ GAsyncResult *result,
+ GError **error)
{
GbpFlatpakRuntime *self = (GbpFlatpakRuntime *)runtime;
@@ -182,7 +182,7 @@ gbp_flatpak_runtime_prebuild_finish (IdeRuntime *runtime,
static IdeSubprocessLauncher *
gbp_flatpak_runtime_create_launcher (IdeRuntime *runtime,
- GError **error)
+ GError **error)
{
IdeSubprocessLauncher *ret;
GbpFlatpakRuntime *self = (GbpFlatpakRuntime *)runtime;
@@ -198,6 +198,8 @@ gbp_flatpak_runtime_create_launcher (IdeRuntime *runtime,
ide_subprocess_launcher_push_argv (ret, "flatpak");
ide_subprocess_launcher_push_argv (ret, "build");
ide_subprocess_launcher_push_argv (ret, build_path);
+
+ ide_subprocess_launcher_set_run_on_host (ret, TRUE);
}
return ret;
@@ -205,7 +207,7 @@ gbp_flatpak_runtime_create_launcher (IdeRuntime *runtime,
static void
gbp_flatpak_runtime_prepare_configuration (IdeRuntime *runtime,
- IdeConfiguration *configuration)
+ IdeConfiguration *configuration)
{
g_assert (IDE_IS_RUNTIME (runtime));
g_assert (IDE_IS_CONFIGURATION (configuration));
@@ -215,9 +217,9 @@ gbp_flatpak_runtime_prepare_configuration (IdeRuntime *runtime,
static void
gbp_flatpak_runtime_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
{
GbpFlatpakRuntime *self = GBP_FLATPAK_RUNTIME(object);
@@ -242,9 +244,9 @@ gbp_flatpak_runtime_get_property (GObject *object,
static void
gbp_flatpak_runtime_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
{
GbpFlatpakRuntime *self = GBP_FLATPAK_RUNTIME(object);
diff --git a/plugins/jhbuild/jhbuild_plugin.py b/plugins/jhbuild/jhbuild_plugin.py
index be7ad45..47fa79d 100644
--- a/plugins/jhbuild/jhbuild_plugin.py
+++ b/plugins/jhbuild/jhbuild_plugin.py
@@ -43,11 +43,27 @@ class JhbuildRuntime(Ide.Runtime):
# Rely on search path
return 'jhbuild'
+ def do_create_runner(self, build_target):
+ try:
+ installdir = build_target.props.install_directory
+ name = build_target.props.name
+ binpath = installdir.get_child(name).get_path()
+
+ # The runner uses a launcher from self.create_launcher(), so
+ # most of our settings should already be applied.
+ runner = Ide.Runner.new(self.get_context())
+ runner.append_argv(binpath)
+
+ return runner
+ except Exception as ex:
+ print(ex)
+
def do_create_launcher(self):
try:
launcher = Ide.Runtime.do_create_launcher(self)
launcher.push_argv(self.get_jhbuild_path())
launcher.push_argv('run')
+ launcher.set_run_on_host(True)
return launcher
except GLib.Error:
return None
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]