1 /* ide-run-manager.c
2 *
3 * Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #define G_LOG_DOMAIN "ide-run-manager"
22
23 #include "config.h"
24
25 #include <glib/gi18n.h>
26 #include <libide-threading.h>
27 #include <libide-vcs.h>
28 #include <libpeas/peas.h>
29 #include <libpeas/peas-autocleanups.h>
30
31 #include "ide-private.h"
32
33 #include "ide-build-manager.h"
34 #include "ide-build-system.h"
35 #include "ide-build-target-provider.h"
36 #include "ide-build-target.h"
37 #include "ide-config-manager.h"
38 #include "ide-config.h"
39 #include "ide-device-manager.h"
40 #include "ide-foundry-compat.h"
41 #include "ide-run-manager-private.h"
42 #include "ide-run-manager.h"
43 #include "ide-runner.h"
44 #include "ide-runtime.h"
45
46 struct _IdeRunManager
47 {
48 IdeObject parent_instance;
49
50 GCancellable *cancellable;
51 IdeBuildTarget *build_target;
52 IdeNotification *notif;
53
54 const IdeRunHandlerInfo *handler;
55 GList *handlers;
56
57 /* Keep track of last change sequence from the file monitor
58 * so that we can maybe skip past install phase and make
59 * secondary execution time faster.
60 */
61 guint64 last_change_seq;
62 guint64 pending_last_change_seq;
63
64 guint busy : 1;
65 };
66
67 typedef struct
68 {
69 GList *providers;
70 GPtrArray *results;
71 guint active;
72 } DiscoverState;
73
74 static void initable_iface_init (GInitableIface *iface);
75 static void ide_run_manager_actions_run (IdeRunManager *self,
76 GVariant *param);
77 static void ide_run_manager_actions_run_with_handler (IdeRunManager *self,
78 GVariant *param);
79 static void ide_run_manager_actions_stop (IdeRunManager *self,
80 GVariant *param);
81
82 DZL_DEFINE_ACTION_GROUP (IdeRunManager, ide_run_manager, {
83 { "run", ide_run_manager_actions_run },
84 { "run-with-handler", ide_run_manager_actions_run_with_handler, "s" },
85 { "stop", ide_run_manager_actions_stop },
86 })
87
88 G_DEFINE_TYPE_EXTENDED (IdeRunManager, ide_run_manager, IDE_TYPE_OBJECT, G_TYPE_FLAG_FINAL,
89 G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
90 G_IMPLEMENT_INTERFACE (G_TYPE_ACTION_GROUP,
91 ide_run_manager_init_action_group))
92
93 enum {
94 PROP_0,
95 PROP_BUSY,
96 PROP_HANDLER,
97 PROP_BUILD_TARGET,
98 N_PROPS
99 };
100
101 enum {
102 RUN,
103 STOPPED,
104 N_SIGNALS
105 };
106
107 static GParamSpec *properties [N_PROPS];
108 static guint signals [N_SIGNALS];
109
110 static void
discover_state_free(gpointer data)111 discover_state_free (gpointer data)
112 {
113 DiscoverState *state = data;
114
115 g_assert (state->active == 0);
116
117 g_list_free_full (state->providers, g_object_unref);
118 g_clear_pointer (&state->results, g_ptr_array_unref);
119 g_slice_free (DiscoverState, state);
120 }
121
122 static void
ide_run_manager_real_run(IdeRunManager * self,IdeRunner * runner)123 ide_run_manager_real_run (IdeRunManager *self,
124 IdeRunner *runner)
125 {
126 g_assert (IDE_IS_RUN_MANAGER (self));
127 g_assert (IDE_IS_RUNNER (runner));
128
129 /*
130 * If the current handler has a callback specified (our default "run" handler
131 * does not), then we need to allow that handler to prepare the runner.
132 */
133 if (self->handler != NULL && self->handler->handler != NULL)
134 self->handler->handler (self, runner, self->handler->handler_data);
135 }
136
137 static void
ide_run_handler_info_free(gpointer data)138 ide_run_handler_info_free (gpointer data)
139 {
140 IdeRunHandlerInfo *info = data;
141
142 g_free (info->id);
143 g_free (info->title);
144 g_free (info->icon_name);
145 g_free (info->accel);
146
147 if (info->handler_data_destroy)
148 info->handler_data_destroy (info->handler_data);
149
150 g_slice_free (IdeRunHandlerInfo, info);
151 }
152
153 static void
ide_run_manager_dispose(GObject * object)154 ide_run_manager_dispose (GObject *object)
155 {
156 IdeRunManager *self = (IdeRunManager *)object;
157
158 self->handler = NULL;
159
160 g_clear_object (&self->cancellable);
161 ide_clear_and_destroy_object (&self->build_target);
162
163 g_list_free_full (self->handlers, ide_run_handler_info_free);
164 self->handlers = NULL;
165
166 G_OBJECT_CLASS (ide_run_manager_parent_class)->dispose (object);
167 }
168
169 static void
ide_run_manager_update_action_enabled(IdeRunManager * self)170 ide_run_manager_update_action_enabled (IdeRunManager *self)
171 {
172 IdeBuildManager *build_manager;
173 IdeContext *context;
174 gboolean can_build;
175
176 g_assert (IDE_IS_RUN_MANAGER (self));
177
178 context = ide_object_get_context (IDE_OBJECT (self));
179 build_manager = ide_build_manager_from_context (context);
180 can_build = ide_build_manager_get_can_build (build_manager);
181
182 ide_run_manager_set_action_enabled (self, "run",
183 self->busy == FALSE && can_build == TRUE);
184 ide_run_manager_set_action_enabled (self, "run-with-handler",
185 self->busy == FALSE && can_build == TRUE);
186 ide_run_manager_set_action_enabled (self, "stop", self->busy == TRUE);
187 }
188
189 static void
ide_run_manager_notify_can_build(IdeRunManager * self,GParamSpec * pspec,IdeBuildManager * build_manager)190 ide_run_manager_notify_can_build (IdeRunManager *self,
191 GParamSpec *pspec,
192 IdeBuildManager *build_manager)
193 {
194 IDE_ENTRY;
195
196 g_assert (IDE_IS_RUN_MANAGER (self));
197 g_assert (G_IS_PARAM_SPEC (pspec));
198 g_assert (IDE_IS_BUILD_MANAGER (build_manager));
199
200 ide_run_manager_update_action_enabled (self);
201
202 IDE_EXIT;
203 }
204
205 static gboolean
initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)206 initable_init (GInitable *initable,
207 GCancellable *cancellable,
208 GError **error)
209 {
210 IdeRunManager *self = (IdeRunManager *)initable;
211 IdeBuildManager *build_manager;
212 IdeContext *context;
213
214 IDE_ENTRY;
215
216 g_assert (IDE_IS_RUN_MANAGER (self));
217 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
218
219 context = ide_object_get_context (IDE_OBJECT (self));
220 build_manager = ide_build_manager_from_context (context);
221
222 g_signal_connect_object (build_manager,
223 "notify::can-build",
224 G_CALLBACK (ide_run_manager_notify_can_build),
225 self,
226 G_CONNECT_SWAPPED);
227
228 ide_run_manager_update_action_enabled (self);
229
230 IDE_RETURN (TRUE);
231 }
232
233 static void
initable_iface_init(GInitableIface * iface)234 initable_iface_init (GInitableIface *iface)
235 {
236 iface->init = initable_init;
237 }
238
239 static void
ide_run_manager_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)240 ide_run_manager_get_property (GObject *object,
241 guint prop_id,
242 GValue *value,
243 GParamSpec *pspec)
244 {
245 IdeRunManager *self = IDE_RUN_MANAGER (object);
246
247 switch (prop_id)
248 {
249 case PROP_BUSY:
250 g_value_set_boolean (value, ide_run_manager_get_busy (self));
251 break;
252
253 case PROP_HANDLER:
254 g_value_set_string (value, ide_run_manager_get_handler (self));
255 break;
256
257 case PROP_BUILD_TARGET:
258 g_value_set_object (value, ide_run_manager_get_build_target (self));
259 break;
260
261 default:
262 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
263 }
264 }
265
266 static void
ide_run_manager_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)267 ide_run_manager_set_property (GObject *object,
268 guint prop_id,
269 const GValue *value,
270 GParamSpec *pspec)
271 {
272 IdeRunManager *self = IDE_RUN_MANAGER (object);
273
274 switch (prop_id)
275 {
276 case PROP_BUILD_TARGET:
277 ide_run_manager_set_build_target (self, g_value_get_object (value));
278 break;
279
280 default:
281 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
282 }
283 }
284
285 static void
ide_run_manager_class_init(IdeRunManagerClass * klass)286 ide_run_manager_class_init (IdeRunManagerClass *klass)
287 {
288 GObjectClass *object_class = G_OBJECT_CLASS (klass);
289
290 object_class->dispose = ide_run_manager_dispose;
291 object_class->get_property = ide_run_manager_get_property;
292 object_class->set_property = ide_run_manager_set_property;
293
294 properties [PROP_BUSY] =
295 g_param_spec_boolean ("busy",
296 "Busy",
297 "Busy",
298 FALSE,
299 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
300
301 properties [PROP_HANDLER] =
302 g_param_spec_string ("handler",
303 "Handler",
304 "Handler",
305 "run",
306 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
307
308 properties [PROP_BUILD_TARGET] =
309 g_param_spec_object ("build-target",
310 "Build Target",
311 "The IdeBuildTarget that will be run",
312 IDE_TYPE_BUILD_TARGET,
313 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
314
315 g_object_class_install_properties (object_class, N_PROPS, properties);
316
317 /**
318 * IdeRunManager::run:
319 * @self: An #IdeRunManager
320 * @runner: An #IdeRunner
321 *
322 * This signal is emitted right before ide_runner_run_async() is called
323 * on an #IdeRunner. It can be used by plugins to tweak things right
324 * before the runner is executed.
325 *
326 * The current run handler (debugger, profiler, etc) is run as the default
327 * handler for this function. So connect with %G_SIGNAL_AFTER if you want
328 * to be nofied after the run handler has executed. It's unwise to change
329 * things that the run handler might expect. Generally if you want to
330 * change settings, do that before the run handler has exected.
331 *
332 * Since: 3.32
333 */
334 signals [RUN] =
335 g_signal_new_class_handler ("run",
336 G_TYPE_FROM_CLASS (klass),
337 G_SIGNAL_RUN_LAST,
338 G_CALLBACK (ide_run_manager_real_run),
339 NULL,
340 NULL,
341 NULL,
342 G_TYPE_NONE,
343 1,
344 IDE_TYPE_RUNNER);
345
346 /**
347 * IdeRunManager::stopped:
348 *
349 * This signal is emitted when the run manager has stopped the currently
350 * executing inferior.
351 *
352 * Since: 3.32
353 */
354 signals [STOPPED] =
355 g_signal_new ("stopped",
356 G_TYPE_FROM_CLASS (klass),
357 G_SIGNAL_RUN_LAST,
358 0,
359 NULL,
360 NULL,
361 NULL,
362 G_TYPE_NONE,
363 0);
364 }
365
366 gboolean
ide_run_manager_get_busy(IdeRunManager * self)367 ide_run_manager_get_busy (IdeRunManager *self)
368 {
369 g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), FALSE);
370
371 return self->busy;
372 }
373
374 static gboolean
ide_run_manager_check_busy(IdeRunManager * self,GError ** error)375 ide_run_manager_check_busy (IdeRunManager *self,
376 GError **error)
377 {
378 g_assert (IDE_IS_RUN_MANAGER (self));
379 g_assert (error != NULL);
380
381 if (ide_run_manager_get_busy (self))
382 {
383 g_set_error (error,
384 G_IO_ERROR,
385 G_IO_ERROR_BUSY,
386 "%s",
387 _("Cannot run target, another target is running"));
388 return TRUE;
389 }
390
391 return FALSE;
392 }
393
394 static void
ide_run_manager_run_cb(GObject * object,GAsyncResult * result,gpointer user_data)395 ide_run_manager_run_cb (GObject *object,
396 GAsyncResult *result,
397 gpointer user_data)
398 {
399 IdeRunner *runner = (IdeRunner *)object;
400 g_autoptr(IdeTask) task = user_data;
401 g_autoptr(GError) error = NULL;
402 IdeRunManager *self;
403
404 IDE_ENTRY;
405
406 g_assert (IDE_IS_RUNNER (runner));
407 g_assert (IDE_IS_TASK (task));
408
409 self = ide_task_get_source_object (task);
410
411 if (self->notif != NULL)
412 {
413 ide_notification_withdraw (self->notif);
414 g_clear_object (&self->notif);
415 }
416
417 if (!ide_runner_run_finish (runner, result, &error))
418 ide_task_return_error (task, g_steal_pointer (&error));
419 else
420 ide_task_return_boolean (task, TRUE);
421
422 g_signal_emit (self, signals [STOPPED], 0);
423
424 ide_object_destroy (IDE_OBJECT (runner));
425
426 IDE_EXIT;
427 }
428
429 static void
copy_builtin_envvars(IdeEnvironment * environment)430 copy_builtin_envvars (IdeEnvironment *environment)
431 {
432 static const gchar *copy_env[] = {
433 "AT_SPI_BUS_ADDRESS",
434 "COLORTERM",
435 "DBUS_SESSION_BUS_ADDRESS",
436 "DBUS_SYSTEM_BUS_ADDRESS",
437 "DESKTOP_SESSION",
438 "DISPLAY",
439 "LANG",
440 "SHELL",
441 "SSH_AUTH_SOCK",
442 "USER",
443 "WAYLAND_DISPLAY",
444 "XAUTHORITY",
445 "XDG_CURRENT_DESKTOP",
446 "XDG_MENU_PREFIX",
447 #if 0
448 /* Can't copy these as they could mess up Flatpak */
449 "XDG_DATA_DIRS",
450 "XDG_RUNTIME_DIR",
451 #endif
452 "XDG_SEAT",
453 "XDG_SESSION_DESKTOP",
454 "XDG_SESSION_ID",
455 "XDG_SESSION_TYPE",
456 "XDG_VTNR",
457 };
458 const gchar * const *host_environ = _ide_host_environ ();
459
460 for (guint i = 0; i < G_N_ELEMENTS (copy_env); i++)
461 {
462 const gchar *key = copy_env[i];
463 const gchar *val = g_environ_getenv ((gchar **)host_environ, key);
464
465 if (val != NULL && ide_environment_getenv (environment, key) == NULL)
466 ide_environment_setenv (environment, key, val);
467 }
468 }
469
470 static void
create_runner_cb(GObject * object,GAsyncResult * result,gpointer user_data)471 create_runner_cb (GObject *object,
472 GAsyncResult *result,
473 gpointer user_data)
474 {
475 IdeRunManager *self;
476 IdeDeviceManager *device_manager = (IDE_DEVICE_MANAGER (object));
477 g_autoptr(IdeTask) task = user_data;
478 g_autoptr(GError) error = NULL;
479 g_autofree gchar *name = NULL;
480 g_autofree gchar *title = NULL;
481 IdeBuildTarget *build_target;
482 IdeContext *context;
483 IdeConfigManager *config_manager;
484 IdeConfig *config;
485 IdeEnvironment *environment;
486 IdeRuntime *runtime;
487 g_autoptr(IdeRunner) runner = NULL;
488 GCancellable *cancellable;
489 const gchar *run_opts;
490
491 IDE_ENTRY;
492
493 g_assert (IDE_IS_DEVICE_MANAGER (device_manager));
494 g_assert (G_IS_ASYNC_RESULT (result));
495 g_assert (IDE_IS_TASK (task));
496
497 self = ide_task_get_source_object (task);
498 g_assert (IDE_IS_RUN_MANAGER (self));
499
500 runner = ide_device_manager_create_runner_finish (device_manager, result, &error);
501
502 if (error != NULL)
503 {
504 ide_task_return_error (task, g_steal_pointer (&error));
505 IDE_EXIT;
506 }
507
508 build_target = ide_task_get_task_data (task);
509 context = ide_object_get_context (IDE_OBJECT (self));
510
511 g_assert (IDE_IS_BUILD_TARGET (build_target));
512 g_assert (IDE_IS_CONTEXT (context));
513
514 config_manager = ide_config_manager_from_context (context);
515 config = ide_config_manager_get_current (config_manager);
516 runtime = ide_config_get_runtime (config);
517
518 if (runner == NULL)
519 {
520 if (runtime == NULL)
521 {
522 ide_task_return_new_error (task,
523 IDE_RUNTIME_ERROR,
524 IDE_RUNTIME_ERROR_NO_SUCH_RUNTIME,
525 "%s “%s”",
526 _("Failed to locate runtime"),
527 ide_config_get_runtime_id (config));
528 IDE_EXIT;
529 }
530
531 runner = ide_runtime_create_runner (runtime, build_target);
532 }
533
534 cancellable = ide_task_get_cancellable (task);
535
536 g_assert (IDE_IS_RUNNER (runner));
537 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
538
539 /* Add our run arguments if specified in the config. */
540 if (NULL != (run_opts = ide_config_get_run_opts (config)))
541 {
542 g_auto(GStrv) argv = NULL;
543 gint argc;
544
545 if (g_shell_parse_argv (run_opts, &argc, &argv, NULL))
546 {
547 for (gint i = 0; i < argc; i++)
548 ide_runner_append_argv (runner, argv[i]);
549 }
550 }
551
552 /* Add our runtime environment variables. */
553 environment = ide_runner_get_environment (runner);
554 copy_builtin_envvars (environment);
555 ide_environment_copy_into (ide_config_get_runtime_environment (config), environment, TRUE);
556
557 g_signal_emit (self, signals [RUN], 0, runner);
558
559 if (ide_runner_get_failed (runner))
560 {
561 ide_task_return_new_error (task,
562 IDE_RUNTIME_ERROR,
563 IDE_RUNTIME_ERROR_SPAWN_FAILED,
564 "Failed to execute the application");
565 IDE_EXIT;
566 }
567
568 if (self->notif != NULL)
569 {
570 ide_notification_withdraw (self->notif);
571 g_clear_object (&self->notif);
572 }
573
574 self->notif = ide_notification_new ();
575 name = ide_build_target_get_name (build_target);
576 /* translators: %s is replaced with the name of the users executable */
577 title = g_strdup_printf (_("Running %s…"), name);
578 ide_notification_set_title (self->notif, title);
579 ide_notification_attach (self->notif, IDE_OBJECT (self));
580
581 ide_runner_run_async (runner,
582 cancellable,
583 ide_run_manager_run_cb,
584 g_object_ref (task));
585
586 IDE_EXIT;
587 }
588
589 static void
deploy_cb(GObject * object,GAsyncResult * result,gpointer user_data)590 deploy_cb (GObject *object,
591 GAsyncResult *result,
592 gpointer user_data)
593 {
594 IdeDeviceManager *device_manager = (IdeDeviceManager *)object;
595 IdeBuildManager *build_manager;
596 IdeContext *context;
597 IdePipeline *pipeline;
598 IdeRunManager *self;
599 g_autoptr(IdeTask) task = user_data;
600 g_autoptr(GError) error = NULL;
601
602 IDE_ENTRY;
603
604 g_assert (IDE_IS_DEVICE_MANAGER (device_manager));
605 g_assert (IDE_IS_TASK (task));
606
607 if (!ide_device_manager_deploy_finish (device_manager, result, &error))
608 {
609 ide_task_return_error (task, g_steal_pointer (&error));
610 IDE_EXIT;
611 }
612
613 self = ide_task_get_source_object (task);
614 g_assert (IDE_IS_RUN_MANAGER (self));
615
616 context = ide_object_get_context (IDE_OBJECT (self));
617 g_assert (IDE_IS_CONTEXT (context));
618
619 build_manager = ide_build_manager_from_context (context);
620 pipeline = ide_build_manager_get_pipeline (build_manager);
621
622 ide_device_manager_create_runner_async (device_manager,
623 pipeline,
624 ide_task_get_cancellable (task),
625 create_runner_cb,
626 g_object_ref (task));
627
628 IDE_EXIT;
629 }
630
631 static void
do_run_async(IdeRunManager * self,IdeTask * task)632 do_run_async (IdeRunManager *self,
633 IdeTask *task)
634 {
635 IdeBuildManager *build_manager;
636 IdeContext *context;
637 IdeDeviceManager *device_manager;
638 IdePipeline *pipeline;
639 GCancellable *cancellable;
640
641 IDE_ENTRY;
642
643 g_assert (IDE_IS_RUN_MANAGER (self));
644 g_assert (IDE_IS_TASK (task));
645
646 context = ide_object_get_context (IDE_OBJECT (self));
647 g_assert (IDE_IS_CONTEXT (context));
648
649 build_manager = ide_build_manager_from_context (context);
650 pipeline = ide_build_manager_get_pipeline (build_manager);
651 device_manager = ide_device_manager_from_context (context);
652
653 cancellable = ide_task_get_cancellable (task);
654 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
655
656 ide_device_manager_deploy_async (device_manager,
657 pipeline,
658 cancellable,
659 deploy_cb,
660 g_object_ref (task));
661
662 IDE_EXIT;
663 }
664
665 static void
ide_run_manager_run_discover_cb(GObject * object,GAsyncResult * result,gpointer user_data)666 ide_run_manager_run_discover_cb (GObject *object,
667 GAsyncResult *result,
668 gpointer user_data)
669 {
670 IdeRunManager *self = (IdeRunManager *)object;
671 g_autoptr(IdeBuildTarget) build_target = NULL;
672 g_autoptr(IdeTask) task = user_data;
673 g_autoptr(GError) error = NULL;
674
675 IDE_ENTRY;
676
677 g_assert (IDE_IS_RUN_MANAGER (self));
678 g_assert (G_IS_ASYNC_RESULT (result));
679
680 build_target = ide_run_manager_discover_default_target_finish (self, result, &error);
681
682 if (build_target == NULL)
683 {
684 ide_task_return_error (task, g_steal_pointer (&error));
685 IDE_EXIT;
686 }
687
688 ide_run_manager_set_build_target (self, build_target);
689
690 ide_task_set_task_data (task, g_steal_pointer (&build_target), g_object_unref);
691
692 do_run_async (self, task);
693
694 IDE_EXIT;
695 }
696
697 static void
ide_run_manager_install_cb(GObject * object,GAsyncResult * result,gpointer user_data)698 ide_run_manager_install_cb (GObject *object,
699 GAsyncResult *result,
700 gpointer user_data)
701 {
702 IdeBuildManager *build_manager = (IdeBuildManager *)object;
703 g_autoptr(IdeTask) task = user_data;
704 g_autoptr(GError) error = NULL;
705 IdeRunManager *self;
706 IdeBuildTarget *build_target;
707 GCancellable *cancellable;
708
709 IDE_ENTRY;
710
711 g_assert (IDE_IS_BUILD_MANAGER (build_manager));
712 g_assert (IDE_IS_TASK (task));
713
714 self = ide_task_get_source_object (task);
715 g_assert (IDE_IS_RUN_MANAGER (self));
716
717 if (!ide_build_manager_build_finish (build_manager, result, &error))
718 {
719 /* We want to let the consumer know there was a build error
720 * (but don't need to pass the specific error code) so that
721 * they have an error code to check against.
722 */
723 ide_task_return_new_error (task,
724 IDE_RUNTIME_ERROR,
725 IDE_RUNTIME_ERROR_BUILD_FAILED,
726 /* translators: %s is replaced with the specific error reason */
727 _("The build target failed to build: %s"),
728 error->message);
729 IDE_EXIT;
730 }
731
732 self->last_change_seq = self->pending_last_change_seq;
733
734 build_target = ide_run_manager_get_build_target (self);
735
736 if (build_target == NULL)
737 {
738 cancellable = ide_task_get_cancellable (task);
739 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
740
741 ide_run_manager_discover_default_target_async (self,
742 cancellable,
743 ide_run_manager_run_discover_cb,
744 g_steal_pointer (&task));
745 IDE_EXIT;
746 }
747
748 ide_task_set_task_data (task, g_object_ref (build_target), g_object_unref);
749
750 do_run_async (self, task);
751
752 IDE_EXIT;
753 }
754
755 static void
ide_run_manager_task_completed(IdeRunManager * self,GParamSpec * pspec,IdeTask * task)756 ide_run_manager_task_completed (IdeRunManager *self,
757 GParamSpec *pspec,
758 IdeTask *task)
759 {
760 IDE_ENTRY;
761
762 g_assert (IDE_IS_RUN_MANAGER (self));
763 g_assert (pspec != NULL);
764 g_assert (IDE_IS_TASK (task));
765
766 self->busy = FALSE;
767 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
768
769 ide_run_manager_update_action_enabled (self);
770
771 IDE_EXIT;
772 }
773
774 static void
ide_run_manager_do_install_before_run(IdeRunManager * self,IdeTask * task)775 ide_run_manager_do_install_before_run (IdeRunManager *self,
776 IdeTask *task)
777 {
778 g_autoptr(IdeContext) context = NULL;
779 IdeBuildManager *build_manager;
780 IdeVcsMonitor *monitor;
781 guint64 sequence = 0;
782
783 IDE_ENTRY;
784
785 g_assert (IDE_IS_MAIN_THREAD ());
786 g_assert (IDE_IS_RUN_MANAGER (self));
787 g_assert (IDE_IS_TASK (task));
788
789 context = ide_object_ref_context (IDE_OBJECT (self));
790 build_manager = ide_build_manager_from_context (context);
791 monitor = ide_vcs_monitor_from_context (context);
792
793 /*
794 * First we need to make sure the target is up to date and installed
795 * so that all the dependent resources are available.
796 */
797
798 self->busy = TRUE;
799 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
800
801 g_signal_connect_object (task,
802 "notify::completed",
803 G_CALLBACK (ide_run_manager_task_completed),
804 self,
805 G_CONNECT_SWAPPED);
806
807 if (monitor != NULL)
808 sequence = ide_vcs_monitor_get_sequence (monitor);
809
810 /*
811 * If we detect that nothing has changed in the project directory since the
812 * last time we ran, we can avoid installing the project. This will not help
813 * in situations where external resources have changed outside of builders
814 * control, but users can simply force a Build in that case.
815 */
816 if (self->build_target != NULL && sequence == self->last_change_seq)
817 {
818 g_debug ("Skipping install phase as no files appear to have changed "
819 "(sequence %"G_GUINT64_FORMAT")", sequence);
820 ide_run_manager_update_action_enabled (self);
821 ide_task_set_task_data (task, g_object_ref (self->build_target), g_object_unref);
822 do_run_async (self, task);
823 IDE_EXIT;
824 }
825
826 self->pending_last_change_seq = sequence;
827
828 ide_build_manager_build_async (build_manager,
829 IDE_PIPELINE_PHASE_INSTALL,
830 NULL,
831 ide_task_get_cancellable (task),
832 ide_run_manager_install_cb,
833 g_object_ref (task));
834
835 ide_run_manager_update_action_enabled (self);
836
837 IDE_EXIT;
838 }
839
840 void
ide_run_manager_run_async(IdeRunManager * self,IdeBuildTarget * build_target,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)841 ide_run_manager_run_async (IdeRunManager *self,
842 IdeBuildTarget *build_target,
843 GCancellable *cancellable,
844 GAsyncReadyCallback callback,
845 gpointer user_data)
846 {
847 g_autoptr(IdeTask) task = NULL;
848 g_autoptr(GCancellable) local_cancellable = NULL;
849 g_autoptr(GError) error = NULL;
850
851 IDE_ENTRY;
852
853 g_return_if_fail (IDE_IS_MAIN_THREAD ());
854 g_return_if_fail (IDE_IS_RUN_MANAGER (self));
855 g_return_if_fail (!build_target || IDE_IS_BUILD_TARGET (build_target));
856 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
857 g_return_if_fail (!g_cancellable_is_cancelled (self->cancellable));
858
859 if (cancellable == NULL)
860 cancellable = local_cancellable = g_cancellable_new ();
861
862 dzl_cancellable_chain (cancellable, self->cancellable);
863
864 task = ide_task_new (self, cancellable, callback, user_data);
865 ide_task_set_source_tag (task, ide_run_manager_run_async);
866 ide_task_set_priority (task, G_PRIORITY_LOW);
867
868 if (ide_task_return_error_if_cancelled (task))
869 IDE_EXIT;
870
871 if (ide_run_manager_check_busy (self, &error))
872 {
873 ide_task_return_error (task, g_steal_pointer (&error));
874 IDE_EXIT;
875 }
876
877 if (build_target != NULL)
878 ide_run_manager_set_build_target (self, build_target);
879
880 ide_run_manager_do_install_before_run (self, task);
881
882 IDE_EXIT;
883 }
884
885 gboolean
ide_run_manager_run_finish(IdeRunManager * self,GAsyncResult * result,GError ** error)886 ide_run_manager_run_finish (IdeRunManager *self,
887 GAsyncResult *result,
888 GError **error)
889 {
890 gboolean ret;
891
892 IDE_ENTRY;
893
894 g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), FALSE);
895 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
896
897 ret = ide_task_propagate_boolean (IDE_TASK (result), error);
898
899 IDE_RETURN (ret);
900 }
901
902 static gboolean
do_cancel_in_timeout(gpointer user_data)903 do_cancel_in_timeout (gpointer user_data)
904 {
905 g_autoptr(GCancellable) cancellable = user_data;
906
907 IDE_ENTRY;
908
909 g_assert (G_IS_CANCELLABLE (cancellable));
910
911 if (!g_cancellable_is_cancelled (cancellable))
912 g_cancellable_cancel (cancellable);
913
914 IDE_RETURN (G_SOURCE_REMOVE);
915 }
916
917 void
ide_run_manager_cancel(IdeRunManager * self)918 ide_run_manager_cancel (IdeRunManager *self)
919 {
920 IDE_ENTRY;
921
922 g_return_if_fail (IDE_IS_RUN_MANAGER (self));
923
924 if (self->cancellable != NULL)
925 g_timeout_add (0, do_cancel_in_timeout, g_steal_pointer (&self->cancellable));
926 self->cancellable = g_cancellable_new ();
927
928 IDE_EXIT;
929 }
930
931 void
ide_run_manager_set_handler(IdeRunManager * self,const gchar * id)932 ide_run_manager_set_handler (IdeRunManager *self,
933 const gchar *id)
934 {
935 g_return_if_fail (IDE_IS_RUN_MANAGER (self));
936
937 self->handler = NULL;
938
939 for (GList *iter = self->handlers; iter; iter = iter->next)
940 {
941 const IdeRunHandlerInfo *info = iter->data;
942
943 if (g_strcmp0 (info->id, id) == 0)
944 {
945 self->handler = info;
946 IDE_TRACE_MSG ("run handler set to %s", info->title);
947 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HANDLER]);
948 break;
949 }
950 }
951 }
952
953 void
ide_run_manager_add_handler(IdeRunManager * self,const gchar * id,const gchar * title,const gchar * icon_name,const gchar * accel,IdeRunHandler run_handler,gpointer user_data,GDestroyNotify user_data_destroy)954 ide_run_manager_add_handler (IdeRunManager *self,
955 const gchar *id,
956 const gchar *title,
957 const gchar *icon_name,
958 const gchar *accel,
959 IdeRunHandler run_handler,
960 gpointer user_data,
961 GDestroyNotify user_data_destroy)
962 {
963 IdeRunHandlerInfo *info;
964 DzlShortcutManager *manager;
965 DzlShortcutTheme *theme;
966 g_autofree gchar *action_name = NULL;
967 GApplication *app;
968
969 g_return_if_fail (IDE_IS_RUN_MANAGER (self));
970 g_return_if_fail (id != NULL);
971 g_return_if_fail (title != NULL);
972
973 info = g_slice_new0 (IdeRunHandlerInfo);
974 info->id = g_strdup (id);
975 info->title = g_strdup (title);
976 info->icon_name = g_strdup (icon_name);
977 info->accel = g_strdup (accel);
978 info->handler = run_handler;
979 info->handler_data = user_data;
980 info->handler_data_destroy = user_data_destroy;
981
982 self->handlers = g_list_append (self->handlers, info);
983
984 app = g_application_get_default ();
985 manager = dzl_application_get_shortcut_manager (DZL_APPLICATION (app));
986 theme = g_object_ref (dzl_shortcut_manager_get_theme (manager));
987
988 action_name = g_strdup_printf ("run-manager.run-with-handler('%s')", id);
989
990 dzl_shortcut_manager_add_action (manager,
991 action_name,
992 N_("Workbench shortcuts"),
993 N_("Build and Run"),
994 g_dgettext (GETTEXT_PACKAGE, title),
995 NULL);
996
997 dzl_shortcut_theme_set_accel_for_action (theme,
998 action_name,
999 accel,
1000 DZL_SHORTCUT_PHASE_GLOBAL | DZL_SHORTCUT_PHASE_CAPTURE);
1001
1002 if (self->handler == NULL)
1003 self->handler = info;
1004 }
1005
1006 void
ide_run_manager_remove_handler(IdeRunManager * self,const gchar * id)1007 ide_run_manager_remove_handler (IdeRunManager *self,
1008 const gchar *id)
1009 {
1010 g_return_if_fail (IDE_IS_RUN_MANAGER (self));
1011 g_return_if_fail (id != NULL);
1012
1013 for (GList *iter = self->handlers; iter; iter = iter->next)
1014 {
1015 IdeRunHandlerInfo *info = iter->data;
1016
1017 if (g_strcmp0 (info->id, id) == 0)
1018 {
1019 self->handlers = g_list_delete_link (self->handlers, iter);
1020
1021 if (self->handler == info && self->handlers != NULL)
1022 self->handler = self->handlers->data;
1023 else
1024 self->handler = NULL;
1025
1026 ide_run_handler_info_free (info);
1027
1028 break;
1029 }
1030 }
1031 }
1032
1033 /**
1034 * ide_run_manager_get_build_target:
1035 *
1036 * Gets the build target that will be executed by the run manager which
1037 * was either specified to ide_run_manager_run_async() or determined by
1038 * the build system.
1039 *
1040 * Returns: (transfer none): An #IdeBuildTarget or %NULL if no build target
1041 * has been set.
1042 *
1043 * Since: 3.32
1044 */
1045 IdeBuildTarget *
ide_run_manager_get_build_target(IdeRunManager * self)1046 ide_run_manager_get_build_target (IdeRunManager *self)
1047 {
1048 g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), NULL);
1049
1050 return self->build_target;
1051 }
1052
1053 void
ide_run_manager_set_build_target(IdeRunManager * self,IdeBuildTarget * build_target)1054 ide_run_manager_set_build_target (IdeRunManager *self,
1055 IdeBuildTarget *build_target)
1056 {
1057 g_return_if_fail (IDE_IS_RUN_MANAGER (self));
1058 g_return_if_fail (!build_target || IDE_IS_BUILD_TARGET (build_target));
1059
1060 if (build_target == self->build_target)
1061 return;
1062
1063 if (self->build_target)
1064 ide_clear_and_destroy_object (&self->build_target);
1065
1066 if (build_target)
1067 self->build_target = g_object_ref (build_target);
1068
1069 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUILD_TARGET]);
1070 }
1071
1072 static gint
compare_targets(gconstpointer a,gconstpointer b)1073 compare_targets (gconstpointer a,
1074 gconstpointer b)
1075 {
1076 const IdeBuildTarget * const *a_target = a;
1077 const IdeBuildTarget * const *b_target = b;
1078
1079 return ide_build_target_compare (*a_target, *b_target);
1080 }
1081
1082 static void
collect_extensions(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)1083 collect_extensions (PeasExtensionSet *set,
1084 PeasPluginInfo *plugin_info,
1085 PeasExtension *exten,
1086 gpointer user_data)
1087 {
1088 DiscoverState *state = user_data;
1089
1090 g_assert (state != NULL);
1091 g_assert (IDE_IS_BUILD_TARGET_PROVIDER (exten));
1092
1093 state->providers = g_list_append (state->providers, g_object_ref (exten));
1094 state->active++;
1095 }
1096
1097 static void
ide_run_manager_provider_get_targets_cb(GObject * object,GAsyncResult * result,gpointer user_data)1098 ide_run_manager_provider_get_targets_cb (GObject *object,
1099 GAsyncResult *result,
1100 gpointer user_data)
1101 {
1102 IdeBuildTargetProvider *provider = (IdeBuildTargetProvider *)object;
1103 g_autoptr(IdeBuildTarget) first = NULL;
1104 g_autoptr(IdeTask) task = user_data;
1105 g_autoptr(GPtrArray) ret = NULL;
1106 g_autoptr(GError) error = NULL;
1107 IdeRunManager *self;
1108 DiscoverState *state;
1109
1110 IDE_ENTRY;
1111
1112 g_assert (IDE_IS_BUILD_TARGET_PROVIDER (provider));
1113 g_assert (G_IS_ASYNC_RESULT (result));
1114 g_assert (IDE_IS_TASK (task));
1115
1116 self = ide_task_get_source_object (task);
1117 state = ide_task_get_task_data (task);
1118
1119 g_assert (IDE_IS_RUN_MANAGER (self));
1120 g_assert (state != NULL);
1121 g_assert (state->active > 0);
1122 g_assert (g_list_find (state->providers, provider) != NULL);
1123
1124 ret = ide_build_target_provider_get_targets_finish (provider, result, &error);
1125 IDE_PTR_ARRAY_SET_FREE_FUNC (ret, g_object_unref);
1126
1127 if (ret != NULL)
1128 {
1129 for (guint i = 0; i < ret->len; i++)
1130 {
1131 IdeBuildTarget *target = g_ptr_array_index (ret, i);
1132
1133 if (ide_object_is_root (IDE_OBJECT (target)))
1134 ide_object_append (IDE_OBJECT (self), IDE_OBJECT (target));
1135
1136 g_ptr_array_add (state->results, g_object_ref (target));
1137 }
1138 }
1139
1140 ide_object_destroy (IDE_OBJECT (provider));
1141
1142 state->active--;
1143
1144 if (state->active > 0)
1145 return;
1146
1147 if (state->results->len == 0)
1148 {
1149 if (error != NULL)
1150 ide_task_return_error (task, g_steal_pointer (&error));
1151 else
1152 ide_task_return_new_error (task,
1153 IDE_RUNTIME_ERROR,
1154 IDE_RUNTIME_ERROR_TARGET_NOT_FOUND,
1155 _("Failed to locate a build target"));
1156 IDE_EXIT;
1157 }
1158
1159 g_ptr_array_sort (state->results, compare_targets);
1160
1161 /* Steal the first item so that it is not destroyed */
1162 first = ide_ptr_array_steal_index (state->results,
1163 0,
1164 (GDestroyNotify)ide_object_unref_and_destroy);
1165 ide_task_return_pointer (task,
1166 IDE_OBJECT (g_steal_pointer (&first)),
1167 ide_object_unref_and_destroy);
1168
1169 IDE_EXIT;
1170 }
1171
1172 void
ide_run_manager_discover_default_target_async(IdeRunManager * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1173 ide_run_manager_discover_default_target_async (IdeRunManager *self,
1174 GCancellable *cancellable,
1175 GAsyncReadyCallback callback,
1176 gpointer user_data)
1177 {
1178 g_autoptr(PeasExtensionSet) set = NULL;
1179 g_autoptr(IdeTask) task = NULL;
1180 DiscoverState *state;
1181
1182 IDE_ENTRY;
1183
1184 g_return_if_fail (IDE_IS_RUN_MANAGER (self));
1185 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1186
1187 task = ide_task_new (self, cancellable, callback, user_data);
1188 ide_task_set_source_tag (task, ide_run_manager_discover_default_target_async);
1189 ide_task_set_priority (task, G_PRIORITY_LOW);
1190
1191 set = peas_extension_set_new (peas_engine_get_default (),
1192 IDE_TYPE_BUILD_TARGET_PROVIDER,
1193 NULL);
1194
1195 state = g_slice_new0 (DiscoverState);
1196 state->results = g_ptr_array_new_with_free_func ((GDestroyNotify)ide_object_unref_and_destroy);
1197 state->providers = NULL;
1198 state->active = 0;
1199
1200 peas_extension_set_foreach (set, collect_extensions, state);
1201
1202 for (const GList *iter = state->providers; iter; iter = iter->next)
1203 ide_object_append (IDE_OBJECT (self), IDE_OBJECT (iter->data));
1204
1205 ide_task_set_task_data (task, state, discover_state_free);
1206
1207 for (const GList *iter = state->providers; iter != NULL; iter = iter->next)
1208 {
1209 IdeBuildTargetProvider *provider = iter->data;
1210
1211 ide_build_target_provider_get_targets_async (provider,
1212 cancellable,
1213 ide_run_manager_provider_get_targets_cb,
1214 g_object_ref (task));
1215 }
1216
1217 if (state->active == 0)
1218 ide_task_return_new_error (task,
1219 IDE_RUNTIME_ERROR,
1220 IDE_RUNTIME_ERROR_TARGET_NOT_FOUND,
1221 _("Failed to locate a build target"));
1222
1223 IDE_EXIT;
1224 }
1225
1226 /**
1227 * ide_run_manager_discover_default_target_finish:
1228 *
1229 * Returns: (transfer full): An #IdeBuildTarget if successful; otherwise %NULL
1230 * and @error is set.
1231 *
1232 * Since: 3.32
1233 */
1234 IdeBuildTarget *
ide_run_manager_discover_default_target_finish(IdeRunManager * self,GAsyncResult * result,GError ** error)1235 ide_run_manager_discover_default_target_finish (IdeRunManager *self,
1236 GAsyncResult *result,
1237 GError **error)
1238 {
1239 IdeBuildTarget *ret;
1240
1241 IDE_ENTRY;
1242
1243 g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), NULL);
1244 g_return_val_if_fail (IDE_IS_TASK (result), NULL);
1245
1246 ret = ide_task_propagate_pointer (IDE_TASK (result), error);
1247
1248 IDE_RETURN (ret);
1249 }
1250
1251 const GList *
_ide_run_manager_get_handlers(IdeRunManager * self)1252 _ide_run_manager_get_handlers (IdeRunManager *self)
1253 {
1254 g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), NULL);
1255
1256 return self->handlers;
1257 }
1258
1259 const gchar *
ide_run_manager_get_handler(IdeRunManager * self)1260 ide_run_manager_get_handler (IdeRunManager *self)
1261 {
1262 g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), NULL);
1263
1264 if (self->handler != NULL)
1265 return self->handler->id;
1266
1267 return NULL;
1268 }
1269
1270 static void
ide_run_manager_run_action_cb(GObject * object,GAsyncResult * result,gpointer user_data)1271 ide_run_manager_run_action_cb (GObject *object,
1272 GAsyncResult *result,
1273 gpointer user_data)
1274 {
1275 IdeRunManager *self = (IdeRunManager *)object;
1276 IdeContext *context;
1277 g_autoptr(GError) error = NULL;
1278
1279 g_assert (IDE_IS_RUN_MANAGER (self));
1280 g_assert (G_IS_ASYNC_RESULT (result));
1281
1282 context = ide_object_get_context (IDE_OBJECT (self));
1283
1284 /* Propagate the error to the context */
1285 if (!ide_run_manager_run_finish (self, result, &error))
1286 ide_context_warning (context, "%s", error->message);
1287 }
1288
1289 static void
ide_run_manager_actions_run(IdeRunManager * self,GVariant * param)1290 ide_run_manager_actions_run (IdeRunManager *self,
1291 GVariant *param)
1292 {
1293 IDE_ENTRY;
1294
1295 g_assert (IDE_IS_RUN_MANAGER (self));
1296
1297 ide_run_manager_run_async (self,
1298 NULL,
1299 NULL,
1300 ide_run_manager_run_action_cb,
1301 NULL);
1302
1303 IDE_EXIT;
1304 }
1305
1306 static void
ide_run_manager_actions_run_with_handler(IdeRunManager * self,GVariant * param)1307 ide_run_manager_actions_run_with_handler (IdeRunManager *self,
1308 GVariant *param)
1309 {
1310 const gchar *handler = NULL;
1311 g_autoptr(GVariant) sunk = NULL;
1312
1313 IDE_ENTRY;
1314
1315 g_assert (IDE_IS_RUN_MANAGER (self));
1316
1317 if (param != NULL)
1318 {
1319 handler = g_variant_get_string (param, NULL);
1320 if (g_variant_is_floating (param))
1321 sunk = g_variant_ref_sink (param);
1322 }
1323
1324 /* Use specified handler, if provided */
1325 if (!ide_str_empty0 (handler))
1326 ide_run_manager_set_handler (self, handler);
1327
1328 ide_run_manager_run_async (self,
1329 NULL,
1330 NULL,
1331 ide_run_manager_run_action_cb,
1332 NULL);
1333
1334 IDE_EXIT;
1335 }
1336
1337 static void
ide_run_manager_actions_stop(IdeRunManager * self,GVariant * param)1338 ide_run_manager_actions_stop (IdeRunManager *self,
1339 GVariant *param)
1340 {
1341 IDE_ENTRY;
1342
1343 g_assert (IDE_IS_RUN_MANAGER (self));
1344
1345 ide_run_manager_cancel (self);
1346
1347 IDE_EXIT;
1348 }
1349
1350 static void
ide_run_manager_init(IdeRunManager * self)1351 ide_run_manager_init (IdeRunManager *self)
1352 {
1353 self->cancellable = g_cancellable_new ();
1354
1355 ide_run_manager_add_handler (self,
1356 "run",
1357 _("Run"),
1358 "builder-run-start-symbolic",
1359 "<primary>F5",
1360 NULL,
1361 NULL,
1362 NULL);
1363 }
1364
1365 void
_ide_run_manager_drop_caches(IdeRunManager * self)1366 _ide_run_manager_drop_caches (IdeRunManager *self)
1367 {
1368 g_return_if_fail (IDE_IS_RUN_MANAGER (self));
1369
1370 self->last_change_seq = 0;
1371 }
1372