Commit 7b60eb6b authored by Christian Hergert's avatar Christian Hergert

libide: add IdeRunManager and IdeRunner interface

IdeRunner allows us to spawn a program within the runtime. It needs to
allow some mutating of path, so building the argv is delayed until as
late as possible. (We may need to alter argv0 to gdb and similar in the
future).
parent 419ab68f
......@@ -42,6 +42,7 @@
#include "projects/ide-project-item.h"
#include "projects/ide-project.h"
#include "projects/ide-recent-projects.h"
#include "runner/ide-run-manager.h"
#include "runtimes/ide-runtime-manager.h"
#include "scripting/ide-script-manager.h"
#include "search/ide-search-engine.h"
......@@ -66,6 +67,7 @@ struct _IdeContext
IdeDeviceManager *device_manager;
IdeDoap *doap;
GtkRecentManager *recent_manager;
IdeRunManager *run_manager;
IdeRuntimeManager *runtime_manager;
IdeScriptManager *script_manager;
IdeSearchEngine *search_engine;
......@@ -842,6 +844,10 @@ ide_context_init (IdeContext *self)
"context", self,
NULL);
self->run_manager = g_object_new (IDE_TYPE_RUN_MANAGER,
"context", self,
NULL);
self->runtime_manager = g_object_new (IDE_TYPE_RUNTIME_MANAGER,
"context", self,
NULL);
......@@ -2171,3 +2177,21 @@ ide_context_warning (IdeContext *self,
g_logv ("Ide", G_LOG_LEVEL_WARNING, format, args);
va_end (args);
}
/**
* ide_context_get_run_manager:
*
* Gets the #IdeRunManager for the context. This manager object simplifies
* the process of running an #IdeBuildTarget from the build system. Primarily,
* it enforces that only a single target may be run at a time, since that is
* what the UI will expect.
*
* Returns: (transfer none): An #IdeRunManager.
*/
IdeRunManager *
ide_context_get_run_manager (IdeContext *self)
{
g_return_val_if_fail (IDE_IS_CONTEXT (self), NULL);
return self->run_manager;
}
......@@ -39,6 +39,7 @@ IdeConfigurationManager *ide_context_get_configuration_manager (IdeContext
IdeDeviceManager *ide_context_get_device_manager (IdeContext *self);
IdeProject *ide_context_get_project (IdeContext *self);
GtkRecentManager *ide_context_get_recent_manager (IdeContext *self);
IdeRunManager *ide_context_get_run_manager (IdeContext *self);
IdeRuntimeManager *ide_context_get_runtime_manager (IdeContext *self);
IdeScriptManager *ide_context_get_script_manager (IdeContext *self);
IdeSearchEngine *ide_context_get_search_engine (IdeContext *self);
......
......@@ -36,10 +36,10 @@ typedef struct _IdeBufferChangeMonitor IdeBufferChangeMonitor;
typedef struct _IdeBufferManager IdeBufferManager;
typedef struct _IdeBuilder IdeBuilder;
typedef struct _IdeBuildManager IdeBuildManager;
typedef struct _IdeBuildResult IdeBuildResult;
typedef struct _IdeBuildSystem IdeBuildSystem;
typedef struct _IdeBuildTarget IdeBuildTarget;
typedef struct _IdeConfiguration IdeConfiguration;
typedef struct _IdeConfigurationManager IdeConfigurationManager;
......
......@@ -85,6 +85,9 @@ G_BEGIN_DECLS
#include "projects/ide-project-miner.h"
#include "projects/ide-project.h"
#include "projects/ide-recent-projects.h"
#include "runner/ide-run-manager.h"
#include "runner/ide-runner.h"
#include "runner/ide-runner-addin.h"
#include "runtimes/ide-runtime-manager.h"
#include "runtimes/ide-runtime-provider.h"
#include "runtimes/ide-runtime.h"
......
# Running Projects
Running a project target in Builder needs to accomidate a few difficult
to plumb components. For example, Builder supports runtimes which might
be different than the current host (such as org.gnome.Platform 3.22).
Additionally, we might need to attach a debugger. The build system might
also need to perform an installation of the application bits into a
runtime so that the project can run as it's "installed state".
All of these complexities results in project execution being abstracted
into an “IdeRunner” object and series of “IdeRunnerAddin” extensions.
This allows the various plugins involved (build system, runtime, possible
debugger or profilers) to hook in to various stages and modify things as
necessary.
# IdeRunner
This is our core runner structure. It manages some basic process execution
stuff, but most things get tweaked and configured by the addins.
The process is launched by the IdeSubprocessLauncher created by the configured
runtime. However, addins can tweak things as necessary.
## IdeRunnerAddin
Plugins should implement this interface so that they can modify the runner
as necessary. This should be used for custom extensoin points that are always
needed, not to add integration points like debugger, profiler, etc.
## Debugger Integration
Debuggers may need to hook into the runtime pid namespace (and possibly mount
namespace to bring along tooling). This could also mean starting the process as
an inferior of something like `gdb`. In the flatpak scenario, this could be
flatpak reaper → gdb → inferior
Additionally, the debugger will need to be able to IPC with the gdb instance
inside the runtime.
While we don't have support for this yet, I think the design we can use is that
the `IdeRunner` can have API to bring in "sidecars" to the runtime which
contain the debugging tooling. For the local system, this would be a
passthrough. For something like flatpak, it would need to enter the namespace
and add a mountpoint for the tooling. (This is probably something that needs to
be implemented in bubblewrap to some degree).
When the debugger sets up the runner, it will need to be able to modify argv
similar to our IdeSubprocessLauncher. This will allow it to add gdb to the
prefix of the command.
Getting a runner will be somewhat consistent with:
BuildSystem = context.get_build_system()
ConfigManager = context.get_configuration_manager()
config = ConfigManager.get_current()
runtime = config.get_runtime()
target = BuildSystem.get_default_run_command(config)
runner = runtime.create_runner(config, target)
-> the build system, at this point, may have added a prehook via
the runner addin that will install the project before it can
be run.
And the debugger might do something like:
runner.prepend_argv('gdbserver')
runner.prepend_argv('--once')
runner.prepend_argv('--args')
When runner.run() is called, the implementation might choose to prepend
additional argv parameters. This allows gdb to simply prepend the command
line, but still have flatpak spawn the container process (bubblewrap).
## Profiling Integration
A profiler (such as sysprof) will need to know the process identifier of the
spawned process (and possibly children processes). This is somewhat similar
to debugging except that it isn't strictly necessary that the process stops
before reaching `main()`.
To synchronize on startup, sysprof might spawn a helper process that
communicates with the listener process, and then execs the child command.
So it might need to do something like:
runner.prepend_argv('sysprof-spawner')
/* ide-run-manager.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-run-manager"
#include <glib/gi18n.h>
#include "ide-context.h"
#include "ide-debug.h"
#include "buildsystem/ide-build-manager.h"
#include "buildsystem/ide-build-target.h"
#include "buildsystem/ide-configuration.h"
#include "buildsystem/ide-configuration-manager.h"
#include "runner/ide-run-manager.h"
#include "runner/ide-runner.h"
#include "runtimes/ide-runtime.h"
struct _IdeRunManager
{
IdeObject parent_instance;
GCancellable *cancellable;
GSimpleActionGroup *actions;
guint busy : 1;
};
G_DEFINE_TYPE (IdeRunManager, ide_run_manager, IDE_TYPE_OBJECT)
enum {
PROP_0,
PROP_BUSY,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static void
ide_run_manager_finalize (GObject *object)
{
IdeRunManager *self = (IdeRunManager *)object;
g_clear_object (&self->cancellable);
g_clear_object (&self->actions);
G_OBJECT_CLASS (ide_run_manager_parent_class)->finalize (object);
}
static void
ide_run_manager_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeRunManager *self = IDE_RUN_MANAGER (object);
switch (prop_id)
{
case PROP_BUSY:
g_value_set_boolean (value, ide_run_manager_get_busy (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_run_manager_class_init (IdeRunManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = ide_run_manager_finalize;
object_class->get_property = ide_run_manager_get_property;
properties [PROP_BUSY] =
g_param_spec_boolean ("busy",
"Busy",
"Busy",
FALSE,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
ide_run_manager_init (IdeRunManager *self)
{
}
gboolean
ide_run_manager_get_busy (IdeRunManager *self)
{
g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), FALSE);
return self->busy;
}
static gboolean
ide_run_manager_check_busy (IdeRunManager *self,
GError **error)
{
g_assert (IDE_IS_RUN_MANAGER (self));
g_assert (error != NULL);
if (ide_run_manager_get_busy (self))
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_BUSY,
"%s",
_("Cannot run target, another target is running"));
return TRUE;
}
return FALSE;
}
static void
ide_run_manager_run_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeRunner *runner = (IdeRunner *)object;
g_autoptr(GTask) task = user_data;
GError *error = NULL;
IDE_ENTRY;
g_assert (IDE_IS_RUNNER (runner));
g_assert (G_IS_TASK (task));
if (!ide_runner_run_finish (runner, result, &error))
{
g_task_return_error (task, error);
IDE_GOTO (failure);
}
g_task_return_boolean (task, TRUE);
failure:
IDE_EXIT;
}
static void
ide_run_manager_install_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeBuildManager *build_manager = (IdeBuildManager *)object;
g_autoptr(GTask) task = user_data;
g_autoptr(IdeRunner) runner = NULL;
IdeConfigurationManager *config_manager;
IdeConfiguration *config;
IdeBuildTarget *build_target;
IdeRunManager *self;
GCancellable *cancellable;
IdeContext *context;
IdeRuntime *runtime;
GError *error = NULL;
IDE_ENTRY;
g_assert (IDE_IS_BUILD_MANAGER (build_manager));
g_assert (G_IS_TASK (task));
if (!ide_build_manager_build_finish (build_manager, result, &error))
{
g_task_return_error (task, error);
IDE_GOTO (failure);
}
build_target = g_task_get_task_data (task);
self = g_task_get_source_object (task);
g_assert (IDE_IS_BUILD_TARGET (build_target));
g_assert (IDE_IS_RUN_MANAGER (self));
context = ide_object_get_context (IDE_OBJECT (self));
g_assert (IDE_IS_CONTEXT (context));
config_manager = ide_context_get_configuration_manager (context);
config = ide_configuration_manager_get_current (config_manager);
runtime = ide_configuration_get_runtime (config);
if (runtime == NULL)
{
g_task_return_new_error (task,
IDE_RUNTIME_ERROR,
IDE_RUNTIME_ERROR_NO_SUCH_RUNTIME,
"%s “%s”",
_("Failed to locate runtime"),
ide_configuration_get_runtime_id (config));
IDE_GOTO (failure);
}
runner = ide_runtime_create_runner (runtime, build_target);
cancellable = g_task_get_cancellable (task);
g_assert (IDE_IS_RUNNER (runner));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
ide_runner_run_async (runner,
cancellable,
ide_run_manager_run_cb,
g_steal_pointer (&task));
failure:
IDE_EXIT;
}
static void
ide_run_manager_task_completed (IdeRunManager *self,
GParamSpec *pspec,
GTask *task)
{
IDE_ENTRY;
g_assert (IDE_IS_RUN_MANAGER (self));
g_assert (pspec != NULL);
g_assert (G_IS_TASK (task));
self->busy = FALSE;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
IDE_EXIT;
}
void
ide_run_manager_run_async (IdeRunManager *self,
IdeBuildTarget *build_target,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_autoptr(GCancellable) local_cancellable = NULL;
IdeBuildManager *build_manager;
IdeContext *context;
GError *error = NULL;
IDE_ENTRY;
g_return_if_fail (IDE_IS_RUN_MANAGER (self));
g_return_if_fail (IDE_IS_BUILD_TARGET (build_target));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
if (cancellable == NULL)
cancellable = local_cancellable = g_cancellable_new ();
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_source_tag (task, ide_run_manager_run_async);
g_task_set_task_data (task, g_object_ref (build_target), g_object_unref);
if (ide_run_manager_check_busy (self, &error))
{
g_task_return_error (task, error);
IDE_GOTO (failure);
}
/*
* First we need to make sure the target is up to date and installed
* so that all the dependent resources are available.
*/
context = ide_object_get_context (IDE_OBJECT (self));
build_manager = ide_context_get_build_manager (context);
self->busy = TRUE;
g_set_object (&self->cancellable, cancellable);
g_signal_connect_object (task,
"notify::completed",
G_CALLBACK (ide_run_manager_task_completed),
self,
G_CONNECT_SWAPPED);
ide_build_manager_install_async (build_manager,
cancellable,
ide_run_manager_install_cb,
g_steal_pointer (&task));
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
failure:
IDE_EXIT;
}
gboolean
ide_run_manager_run_finish (IdeRunManager *self,
GAsyncResult *result,
GError **error)
{
gboolean ret;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), FALSE);
g_return_val_if_fail (G_IS_TASK (result), FALSE);
ret = g_task_propagate_boolean (G_TASK (result), error);
IDE_RETURN (ret);
}
static gboolean
do_cancel_in_timeout (gpointer user_data)
{
GCancellable *cancellable = user_data;
IDE_ENTRY;
g_assert (G_IS_CANCELLABLE (cancellable));
if (!g_cancellable_is_cancelled (cancellable))
g_cancellable_cancel (cancellable);
IDE_RETURN (G_SOURCE_REMOVE);
}
void
ide_run_manager_cancel (IdeRunManager *self)
{
IDE_ENTRY;
g_return_if_fail (IDE_IS_RUN_MANAGER (self));
if (self->cancellable != NULL)
g_timeout_add (0, do_cancel_in_timeout, g_object_ref (self->cancellable));
IDE_EXIT;
}
/* ide-run-manager.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_RUN_MANAGER_H
#define IDE_RUN_MANAGER_H
#include "ide-object.h"
G_BEGIN_DECLS
#define IDE_TYPE_RUN_MANAGER (ide_run_manager_get_type())
G_DECLARE_FINAL_TYPE (IdeRunManager, ide_run_manager, IDE, RUN_MANAGER, IdeObject)
void ide_run_manager_cancel (IdeRunManager *self);
gboolean ide_run_manager_get_busy (IdeRunManager *self);
void ide_run_manager_run_async (IdeRunManager *self,
IdeBuildTarget *build_target,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean ide_run_manager_run_finish (IdeRunManager *self,
GAsyncResult *result,
GError **error);
G_END_DECLS
#endif /* IDE_RUN_MANAGER_H */
/* ide-runner-addin.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-runner-addin"
#include "ide-runner-addin.h"
G_DEFINE_INTERFACE (IdeRunnerAddin, ide_runner_addin, G_TYPE_OBJECT)
static void
ide_runner_addin_real_load (IdeRunnerAddin *self,
IdeRunner *runner)
{
}
static void
ide_runner_addin_real_unload (IdeRunnerAddin *self,
IdeRunner *runner)
{
}
static void
dummy_async (IdeRunnerAddin *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_autoptr(GTask) task = NULL;
g_assert (IDE_IS_RUNNER_ADDIN (self));
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
if (callback == NULL)
return;
task = g_task_new (self, cancellable, callback, user_data);
g_task_return_boolean (task, TRUE);
}
static gboolean
dummy_finish (IdeRunnerAddin *self,
GAsyncResult *result,
GError **error)
{
g_assert (IDE_IS_RUNNER_ADDIN (self));
g_assert (G_IS_TASK (result));
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
ide_runner_addin_default_init (IdeRunnerAddinInterface *iface)
{
iface->load = ide_runner_addin_real_load;
iface->unload = ide_runner_addin_real_unload;
iface->prehook_async = dummy_async;
iface->prehook_finish = dummy_finish;
iface->posthook_async = dummy_async;
iface->posthook_finish = dummy_finish;
}
void
ide_runner_addin_load (IdeRunnerAddin *self,
IdeRunner *runner)
{
g_assert (IDE_IS_RUNNER_ADDIN (self));
g_assert (IDE_IS_RUNNER (runner));
IDE_RUNNER_ADDIN_GET_IFACE (self)->load (self, runner);
}
void
ide_runner_addin_unload (IdeRunnerAddin *self,
IdeRunner *runner)
{
g_assert (IDE_IS_RUNNER_ADDIN (self));
g_assert (IDE_IS_RUNNER (runner));
IDE_RUNNER_ADDIN_GET_IFACE (self)->unload (self, runner);
}
void
ide_runner_addin_prehook_async (IdeRunnerAddin *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (IDE_IS_RUNNER_ADDIN (self));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
IDE_RUNNER_ADDIN_GET_IFACE (self)->prehook_async (self, cancellable, callback, user_data);
}
gboolean
ide_runner_addin_prehook_finish (IdeRunnerAddin *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (IDE_IS_RUNNER_ADDIN (self), FALSE);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
return IDE_RUNNER_ADDIN_GET_IFACE (self)->prehook_finish (self, result, error);
}
void
ide_runner_addin_posthook_async (IdeRunnerAddin *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
g_return_if_fail (IDE_IS_RUNNER_ADDIN (self));
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
IDE_RUNNER_ADDIN_GET_IFACE (self)->posthook_async (self, cancellable, callback, user_data);
}
gboolean
ide_runner_addin_posthook_finish (IdeRunnerAddin *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (IDE_IS_RUNNER_ADDIN (self), FALSE);
g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
return IDE_RUNNER_ADDIN_GET_IFACE (self)->posthook_finish (self, result, error);
}
/* ide-runner-addin.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_RUNNER_ADDIN_H
#define IDE_RUNNER_ADDIN_H
#include <gio/gio.h>
#include "ide-types.h"
#include "ide-runner.h"
G_BEGIN_DECLS
#define IDE_TYPE_RUNNER_ADDIN (ide_runner_addin_get_type())
G_DECLARE_INTERFACE (IdeRunnerAddin, ide_runner_addin, IDE, RUNNER_ADDIN, GObject)
struct _IdeRunnerAddinInterface
{
GTypeInterface parent_interface;
void (*load) (IdeRunnerAddin *self,