1 /* ide-runner.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-runner"
22
23 #include "config.h"
24
25 #include <dazzle.h>
26 #include <errno.h>
27 #include <glib/gi18n.h>
28 #include <libide-io.h>
29 #include <libide-threading.h>
30 #include <libpeas/peas.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33
34 #include "ide-build-target.h"
35 #include "ide-config-manager.h"
36 #include "ide-config.h"
37 #include "ide-foundry-compat.h"
38 #include "ide-runner-addin.h"
39 #include "ide-runner.h"
40 #include "ide-runtime.h"
41
42 typedef struct
43 {
44 PeasExtensionSet *addins;
45 IdeEnvironment *env;
46 IdeBuildTarget *build_target;
47
48 GArray *fd_mapping;
49
50 gchar *cwd;
51
52 IdeSubprocess *subprocess;
53
54 GQueue argv;
55
56 GSubprocessFlags flags;
57
58 VtePty *pty;
59 gint child_fd;
60
61 guint clear_env : 1;
62 guint failed : 1;
63 guint run_on_host : 1;
64 guint disable_pty : 1;
65 } IdeRunnerPrivate;
66
67 typedef struct
68 {
69 GSList *prehook_queue;
70 GSList *posthook_queue;
71 } IdeRunnerRunState;
72
73 typedef struct
74 {
75 gint source_fd;
76 gint dest_fd;
77 } FdMapping;
78
79 enum {
80 PROP_0,
81 PROP_ARGV,
82 PROP_BUILD_TARGET,
83 PROP_CLEAR_ENV,
84 PROP_CWD,
85 PROP_DISABLE_PTY,
86 PROP_ENV,
87 PROP_FAILED,
88 PROP_RUN_ON_HOST,
89 N_PROPS
90 };
91
92 enum {
93 EXITED,
94 SPAWNED,
95 N_SIGNALS
96 };
97
98 static void ide_runner_tick_posthook (IdeTask *task);
99 static void ide_runner_tick_prehook (IdeTask *task);
100 static void ide_runner_tick_run (IdeTask *task);
101
G_DEFINE_TYPE_WITH_PRIVATE(IdeRunner,ide_runner,IDE_TYPE_OBJECT)102 G_DEFINE_TYPE_WITH_PRIVATE (IdeRunner, ide_runner, IDE_TYPE_OBJECT)
103
104 static GParamSpec *properties [N_PROPS];
105 static guint signals [N_SIGNALS];
106
107 static IdeRunnerAddin *
108 pop_runner_addin (GSList **list)
109 {
110 IdeRunnerAddin *ret;
111
112 g_assert (list != NULL);
113 g_assert (*list != NULL);
114
115 ret = (*list)->data;
116
117 *list = g_slist_delete_link (*list, *list);
118
119 return ret;
120 }
121
122 static void
ide_runner_run_state_free(gpointer data)123 ide_runner_run_state_free (gpointer data)
124 {
125 IdeRunnerRunState *state = data;
126
127 g_slist_foreach (state->prehook_queue, (GFunc)g_object_unref, NULL);
128 g_slist_free (state->prehook_queue);
129
130 g_slist_foreach (state->posthook_queue, (GFunc)g_object_unref, NULL);
131 g_slist_free (state->posthook_queue);
132
133 g_slice_free (IdeRunnerRunState, state);
134 }
135
136 static void
ide_runner_run_wait_cb(GObject * object,GAsyncResult * result,gpointer user_data)137 ide_runner_run_wait_cb (GObject *object,
138 GAsyncResult *result,
139 gpointer user_data)
140 {
141 IdeSubprocess *subprocess = (IdeSubprocess *)object;
142 IdeRunnerPrivate *priv;
143 g_autoptr(IdeTask) task = user_data;
144 g_autoptr(GError) error = NULL;
145 IdeRunner *self;
146
147 IDE_ENTRY;
148
149 g_assert (IDE_IS_SUBPROCESS (subprocess));
150 g_assert (G_IS_ASYNC_RESULT (result));
151 g_assert (IDE_IS_TASK (task));
152
153 self = ide_task_get_source_object (task);
154 priv = ide_runner_get_instance_private (self);
155
156 g_assert (IDE_IS_RUNNER (self));
157
158 g_clear_object (&priv->subprocess);
159
160 g_signal_emit (self, signals [EXITED], 0);
161
162 if (!ide_subprocess_wait_finish (subprocess, result, &error))
163 {
164 ide_task_return_error (task, g_steal_pointer (&error));
165 IDE_EXIT;
166 }
167
168 if (ide_subprocess_get_if_exited (subprocess))
169 {
170 gint exit_code;
171
172 exit_code = ide_subprocess_get_exit_status (subprocess);
173
174 if (exit_code == EXIT_SUCCESS)
175 {
176 ide_task_return_boolean (task, TRUE);
177 IDE_EXIT;
178 }
179 }
180
181 ide_task_return_new_error (task,
182 G_IO_ERROR,
183 G_IO_ERROR_FAILED,
184 "%s",
185 _("Process quit unexpectedly"));
186
187 IDE_EXIT;
188 }
189
190 static IdeSubprocessLauncher *
ide_runner_real_create_launcher(IdeRunner * self)191 ide_runner_real_create_launcher (IdeRunner *self)
192 {
193 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
194 IdeConfigManager *config_manager;
195 IdeSubprocessLauncher *ret;
196 IdeConfig *config;
197 IdeContext *context;
198 IdeRuntime *runtime;
199
200 g_assert (IDE_IS_RUNNER (self));
201
202 context = ide_object_get_context (IDE_OBJECT (self));
203 config_manager = ide_config_manager_from_context (context);
204 config = ide_config_manager_get_current (config_manager);
205 runtime = ide_config_get_runtime (config);
206
207 ret = ide_runtime_create_launcher (runtime, NULL);
208
209 if (ret != NULL && priv->cwd != NULL)
210 ide_subprocess_launcher_set_cwd (ret, priv->cwd);
211
212 return ret;
213 }
214
215 static void
ide_runner_real_run_async(IdeRunner * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)216 ide_runner_real_run_async (IdeRunner *self,
217 GCancellable *cancellable,
218 GAsyncReadyCallback callback,
219 gpointer user_data)
220 {
221 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
222 g_autoptr(IdeTask) task = NULL;
223 g_autoptr(IdeSubprocessLauncher) launcher = NULL;
224 g_autoptr(IdeSubprocess) subprocess = NULL;
225 IdeConfigManager *config_manager;
226 IdeConfig *config;
227 const gchar *identifier;
228 IdeContext *context;
229 IdeRuntime *runtime;
230 g_autoptr(GError) error = NULL;
231
232 IDE_ENTRY;
233
234 g_assert (IDE_IS_RUNNER (self));
235 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
236
237 task = ide_task_new (self, cancellable, callback, user_data);
238 ide_task_set_source_tag (task, ide_runner_real_run_async);
239
240 context = ide_object_get_context (IDE_OBJECT (self));
241 config_manager = ide_config_manager_from_context (context);
242 config = ide_config_manager_get_current (config_manager);
243 runtime = ide_config_get_runtime (config);
244
245 if (runtime != NULL)
246 launcher = IDE_RUNNER_GET_CLASS (self)->create_launcher (self);
247
248 if (launcher == NULL)
249 launcher = ide_subprocess_launcher_new (0);
250
251 ide_subprocess_launcher_set_flags (launcher, priv->flags);
252
253 /*
254 * If we have a tty_fd set, then we want to override our stdin,
255 * stdout, and stderr fds with our TTY.
256 */
257 if (!priv->disable_pty && (priv->child_fd != -1 || priv->pty != NULL))
258 {
259 if (priv->child_fd == -1)
260 {
261 gint master_fd;
262 gint tty_fd;
263
264 g_assert (priv->pty != NULL);
265
266 master_fd = vte_pty_get_fd (priv->pty);
267
268 errno = 0;
269 if (-1 == (tty_fd = ide_pty_intercept_create_slave (master_fd, TRUE)))
270 g_critical ("Failed to create TTY device: %s", g_strerror (errno));
271
272 g_assert (tty_fd == -1 || isatty (tty_fd));
273
274 ide_runner_take_tty_fd (self, tty_fd);
275 }
276
277 if (!(priv->flags & G_SUBPROCESS_FLAGS_STDIN_PIPE))
278 ide_subprocess_launcher_take_stdin_fd (launcher, dup (priv->child_fd));
279
280 if (!(priv->flags & (G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDOUT_SILENCE)))
281 ide_subprocess_launcher_take_stdout_fd (launcher, dup (priv->child_fd));
282
283 if (!(priv->flags & (G_SUBPROCESS_FLAGS_STDERR_PIPE | G_SUBPROCESS_FLAGS_STDERR_SILENCE)))
284 ide_subprocess_launcher_take_stderr_fd (launcher, dup (priv->child_fd));
285 }
286
287 /*
288 * Now map in any additionally requested FDs.
289 */
290 if (priv->fd_mapping != NULL)
291 {
292 g_autoptr(GArray) ar = g_steal_pointer (&priv->fd_mapping);
293
294 for (guint i = 0; i < ar->len; i++)
295 {
296 FdMapping *map = &g_array_index (ar, FdMapping, i);
297
298 ide_subprocess_launcher_take_fd (launcher, map->source_fd, map->dest_fd);
299 }
300 }
301
302 /*
303 * We want the runners to run on the host so that we aren't captive to
304 * our containing system (flatpak, jhbuild, etc).
305 */
306 ide_subprocess_launcher_set_run_on_host (launcher, priv->run_on_host);
307
308 /*
309 * We don't want the environment cleared because we need access to
310 * things like DISPLAY, WAYLAND_DISPLAY, and DBUS_SESSION_BUS_ADDRESS.
311 */
312 ide_subprocess_launcher_set_clear_env (launcher, priv->clear_env);
313
314 /*
315 * Overlay the environment provided.
316 */
317 ide_subprocess_launcher_overlay_environment (launcher, priv->env);
318
319 /*
320 * Push all of our configured arguments in order.
321 */
322 for (const GList *iter = priv->argv.head; iter != NULL; iter = iter->next)
323 ide_subprocess_launcher_push_argv (launcher, iter->data);
324
325 /* Give the runner a final chance to mutate the launcher */
326 if (IDE_RUNNER_GET_CLASS (self)->fixup_launcher)
327 IDE_RUNNER_GET_CLASS (self)->fixup_launcher (self, launcher);
328
329 subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, &error);
330
331 g_assert (subprocess == NULL || IDE_IS_SUBPROCESS (subprocess));
332
333 if (subprocess == NULL)
334 {
335 ide_task_return_error (task, g_steal_pointer (&error));
336 IDE_GOTO (failure);
337 }
338
339 priv->subprocess = g_object_ref (subprocess);
340
341 identifier = ide_subprocess_get_identifier (subprocess);
342 g_signal_emit (self, signals [SPAWNED], 0, identifier);
343
344 ide_subprocess_wait_async (subprocess,
345 cancellable,
346 ide_runner_run_wait_cb,
347 g_steal_pointer (&task));
348
349 failure:
350 IDE_EXIT;
351 }
352
353 static gboolean
ide_runner_real_run_finish(IdeRunner * self,GAsyncResult * result,GError ** error)354 ide_runner_real_run_finish (IdeRunner *self,
355 GAsyncResult *result,
356 GError **error)
357 {
358 g_assert (IDE_IS_RUNNER (self));
359 g_assert (IDE_IS_TASK (result));
360 g_assert (ide_task_is_valid (IDE_TASK (result), self));
361 g_assert (ide_task_get_source_tag (IDE_TASK (result)) == ide_runner_real_run_async);
362
363 return ide_task_propagate_boolean (IDE_TASK (result), error);
364 }
365
366 static GOutputStream *
ide_runner_real_get_stdin(IdeRunner * self)367 ide_runner_real_get_stdin (IdeRunner *self)
368 {
369 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
370
371 if (priv->subprocess)
372 return g_object_ref (ide_subprocess_get_stdin_pipe (priv->subprocess));
373 return NULL;
374 }
375
376 static GInputStream *
ide_runner_real_get_stdout(IdeRunner * self)377 ide_runner_real_get_stdout (IdeRunner *self)
378 {
379 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
380
381 if (priv->subprocess)
382 return g_object_ref (ide_subprocess_get_stdout_pipe (priv->subprocess));
383 return NULL;
384 }
385
386 static GInputStream *
ide_runner_real_get_stderr(IdeRunner * self)387 ide_runner_real_get_stderr (IdeRunner *self)
388 {
389 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
390
391 if (priv->subprocess)
392 return g_object_ref (ide_subprocess_get_stderr_pipe (priv->subprocess));
393 return NULL;
394 }
395
396 static void
ide_runner_real_force_quit(IdeRunner * self)397 ide_runner_real_force_quit (IdeRunner *self)
398 {
399 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
400
401 IDE_ENTRY;
402
403 g_assert (IDE_IS_RUNNER (self));
404
405 if (priv->subprocess != NULL)
406 ide_subprocess_force_exit (priv->subprocess);
407
408 IDE_EXIT;
409 }
410
411 static void
ide_runner_extension_added(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)412 ide_runner_extension_added (PeasExtensionSet *set,
413 PeasPluginInfo *plugin_info,
414 PeasExtension *exten,
415 gpointer user_data)
416 {
417 IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
418 IdeRunner *self = user_data;
419
420 g_assert (PEAS_IS_EXTENSION_SET (set));
421 g_assert (plugin_info != NULL);
422 g_assert (IDE_IS_RUNNER_ADDIN (addin));
423 g_assert (IDE_IS_RUNNER (self));
424
425 ide_runner_addin_load (addin, self);
426 }
427
428 static void
ide_runner_extension_removed(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)429 ide_runner_extension_removed (PeasExtensionSet *set,
430 PeasPluginInfo *plugin_info,
431 PeasExtension *exten,
432 gpointer user_data)
433 {
434 IdeRunnerAddin *addin = (IdeRunnerAddin *)exten;
435 IdeRunner *self = user_data;
436
437 g_assert (PEAS_IS_EXTENSION_SET (set));
438 g_assert (plugin_info != NULL);
439 g_assert (IDE_IS_RUNNER_ADDIN (addin));
440 g_assert (IDE_IS_RUNNER (self));
441
442 ide_runner_addin_unload (addin, self);
443 }
444
445 static void
ide_runner_constructed(GObject * object)446 ide_runner_constructed (GObject *object)
447 {
448 IdeRunner *self = (IdeRunner *)object;
449 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
450
451 G_OBJECT_CLASS (ide_runner_parent_class)->constructed (object);
452
453 priv->addins = peas_extension_set_new (peas_engine_get_default (),
454 IDE_TYPE_RUNNER_ADDIN,
455 NULL);
456
457 g_signal_connect (priv->addins,
458 "extension-added",
459 G_CALLBACK (ide_runner_extension_added),
460 self);
461
462 g_signal_connect (priv->addins,
463 "extension-removed",
464 G_CALLBACK (ide_runner_extension_removed),
465 self);
466
467 peas_extension_set_foreach (priv->addins,
468 ide_runner_extension_added,
469 self);
470 }
471
472 static void
ide_runner_finalize(GObject * object)473 ide_runner_finalize (GObject *object)
474 {
475 IdeRunner *self = (IdeRunner *)object;
476 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
477
478 g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
479 g_queue_clear (&priv->argv);
480 g_clear_object (&priv->env);
481 g_clear_object (&priv->subprocess);
482 g_clear_object (&priv->build_target);
483
484 if (priv->child_fd != -1)
485 {
486 close (priv->child_fd);
487 priv->child_fd = -1;
488 }
489
490 if (priv->fd_mapping != NULL)
491 {
492 for (guint i = 0; i < priv->fd_mapping->len; i++)
493 {
494 FdMapping *map = &g_array_index (priv->fd_mapping, FdMapping, i);
495
496 if (map->source_fd != -1)
497 {
498 close (map->source_fd);
499 map->source_fd = -1;
500 }
501 }
502 }
503
504 g_clear_pointer (&priv->fd_mapping, g_array_unref);
505 g_clear_object (&priv->pty);
506
507 G_OBJECT_CLASS (ide_runner_parent_class)->finalize (object);
508 }
509
510 static void
ide_runner_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)511 ide_runner_get_property (GObject *object,
512 guint prop_id,
513 GValue *value,
514 GParamSpec *pspec)
515 {
516 IdeRunner *self = IDE_RUNNER (object);
517
518 switch (prop_id)
519 {
520 case PROP_ARGV:
521 g_value_take_boxed (value, ide_runner_get_argv (self));
522 break;
523
524 case PROP_CLEAR_ENV:
525 g_value_set_boolean (value, ide_runner_get_clear_env (self));
526 break;
527
528 case PROP_CWD:
529 g_value_set_string (value, ide_runner_get_cwd (self));
530 break;
531
532 case PROP_DISABLE_PTY:
533 g_value_set_boolean (value, ide_runner_get_disable_pty (self));
534 break;
535
536 case PROP_ENV:
537 g_value_set_object (value, ide_runner_get_environment (self));
538 break;
539
540 case PROP_FAILED:
541 g_value_set_boolean (value, ide_runner_get_failed (self));
542 break;
543
544 case PROP_RUN_ON_HOST:
545 g_value_set_boolean (value, ide_runner_get_run_on_host (self));
546 break;
547
548 case PROP_BUILD_TARGET:
549 g_value_set_object (value, ide_runner_get_build_target (self));
550 break;
551
552 default:
553 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
554 }
555 }
556
557 static void
ide_runner_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)558 ide_runner_set_property (GObject *object,
559 guint prop_id,
560 const GValue *value,
561 GParamSpec *pspec)
562 {
563 IdeRunner *self = IDE_RUNNER (object);
564
565 switch (prop_id)
566 {
567 case PROP_ARGV:
568 ide_runner_set_argv (self, g_value_get_boxed (value));
569 break;
570
571 case PROP_CLEAR_ENV:
572 ide_runner_set_clear_env (self, g_value_get_boolean (value));
573 break;
574
575 case PROP_CWD:
576 ide_runner_set_cwd (self, g_value_get_string (value));
577 break;
578
579 case PROP_DISABLE_PTY:
580 ide_runner_set_disable_pty (self, g_value_get_boolean (value));
581 break;
582
583 case PROP_FAILED:
584 ide_runner_set_failed (self, g_value_get_boolean (value));
585 break;
586
587 case PROP_RUN_ON_HOST:
588 ide_runner_set_run_on_host (self, g_value_get_boolean (value));
589 break;
590
591 case PROP_BUILD_TARGET:
592 ide_runner_set_build_target (self, g_value_get_object (value));
593 break;
594
595 default:
596 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
597 }
598 }
599
600 static void
ide_runner_class_init(IdeRunnerClass * klass)601 ide_runner_class_init (IdeRunnerClass *klass)
602 {
603 GObjectClass *object_class = G_OBJECT_CLASS (klass);
604
605 object_class->constructed = ide_runner_constructed;
606 object_class->finalize = ide_runner_finalize;
607 object_class->get_property = ide_runner_get_property;
608 object_class->set_property = ide_runner_set_property;
609
610 klass->run_async = ide_runner_real_run_async;
611 klass->run_finish = ide_runner_real_run_finish;
612 klass->create_launcher = ide_runner_real_create_launcher;
613 klass->get_stdin = ide_runner_real_get_stdin;
614 klass->get_stdout = ide_runner_real_get_stdout;
615 klass->get_stderr = ide_runner_real_get_stderr;
616 klass->force_quit = ide_runner_real_force_quit;
617
618 properties [PROP_ARGV] =
619 g_param_spec_boxed ("argv",
620 "Argv",
621 "The argument list for the command",
622 G_TYPE_STRV,
623 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
624
625 properties [PROP_CLEAR_ENV] =
626 g_param_spec_boolean ("clear-env",
627 "Clear Env",
628 "If the environment should be cleared before applying overrides",
629 FALSE,
630 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
631
632 properties [PROP_CWD] =
633 g_param_spec_string ("cwd",
634 "Current Working Directory",
635 "The directory to use as the working directory for the process",
636 NULL,
637 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
638
639 properties [PROP_DISABLE_PTY] =
640 g_param_spec_boolean ("disable-pty",
641 "Disable PTY",
642 "If the pty should be disabled from use",
643 FALSE,
644 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
645
646 properties [PROP_ENV] =
647 g_param_spec_object ("environment",
648 "Environment",
649 "The environment variables for the command",
650 IDE_TYPE_ENVIRONMENT,
651 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
652
653 /**
654 * IdeRunner:failed:
655 *
656 * If the runner has "failed". This should be set if a plugin can determine
657 * that the runner cannot be executed due to an external issue. One such
658 * example might be a debugger plugin that cannot locate a suitable debugger
659 * to run the program.
660 *
661 * Since: 3.32
662 */
663 properties [PROP_FAILED] =
664 g_param_spec_boolean ("failed",
665 "Failed",
666 "If the runner has failed",
667 FALSE,
668 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
669
670 /**
671 * IdeRunner:run-on-host:
672 *
673 * The "run-on-host" property indicates the program should be run on the
674 * host machine rather than inside the application sandbox.
675 *
676 * Since: 3.32
677 */
678 properties [PROP_RUN_ON_HOST] =
679 g_param_spec_boolean ("run-on-host",
680 "Run on Host",
681 "Run on Host",
682 FALSE,
683 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
684
685 /**
686 * IdeRunner:build-target:
687 *
688 * The %IdeBuildTarget from which this %IdeRunner was constructed.
689 *
690 * This is useful to retrieve various properties related to the program
691 * that will be launched, such as what programming language it uses,
692 * or whether it's a graphical application, a command line tool or a test
693 * program.
694 *
695 * Since: 3.32
696 */
697 properties [PROP_BUILD_TARGET] =
698 g_param_spec_object ("build-target",
699 "Build Target",
700 "Build Target",
701 IDE_TYPE_BUILD_TARGET,
702 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
703
704 g_object_class_install_properties (object_class, N_PROPS, properties);
705
706 signals [EXITED] =
707 g_signal_new ("exited",
708 G_TYPE_FROM_CLASS (klass),
709 G_SIGNAL_RUN_LAST,
710 0,
711 NULL,
712 NULL,
713 NULL,
714 G_TYPE_NONE,
715 0);
716
717 signals [SPAWNED] =
718 g_signal_new ("spawned",
719 G_TYPE_FROM_CLASS (klass),
720 G_SIGNAL_RUN_LAST,
721 0, NULL, NULL,
722 g_cclosure_marshal_VOID__STRING,
723 G_TYPE_NONE, 1, G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
724 }
725
726 static void
ide_runner_init(IdeRunner * self)727 ide_runner_init (IdeRunner *self)
728 {
729 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
730
731 g_queue_init (&priv->argv);
732
733 priv->env = ide_environment_new ();
734 priv->child_fd = -1;
735 priv->flags = 0;
736 }
737
738 /**
739 * ide_runner_get_stdin:
740 *
741 * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
742 *
743 * Since: 3.32
744 */
745 GOutputStream *
ide_runner_get_stdin(IdeRunner * self)746 ide_runner_get_stdin (IdeRunner *self)
747 {
748 g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
749
750 return IDE_RUNNER_GET_CLASS (self)->get_stdin (self);
751 }
752
753 /**
754 * ide_runner_get_stdout:
755 *
756 * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
757 *
758 * Since: 3.32
759 */
760 GInputStream *
ide_runner_get_stdout(IdeRunner * self)761 ide_runner_get_stdout (IdeRunner *self)
762 {
763 g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
764
765 return IDE_RUNNER_GET_CLASS (self)->get_stdout (self);
766 }
767
768 /**
769 * ide_runner_get_stderr:
770 *
771 * Returns: (nullable) (transfer full): An #GOutputStream or %NULL.
772 *
773 * Since: 3.32
774 */
775 GInputStream *
ide_runner_get_stderr(IdeRunner * self)776 ide_runner_get_stderr (IdeRunner *self)
777 {
778 g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
779
780 return IDE_RUNNER_GET_CLASS (self)->get_stderr (self);
781 }
782
783 void
ide_runner_force_quit(IdeRunner * self)784 ide_runner_force_quit (IdeRunner *self)
785 {
786 IDE_ENTRY;
787
788 g_return_if_fail (IDE_IS_RUNNER (self));
789
790 if (IDE_RUNNER_GET_CLASS (self)->force_quit)
791 IDE_RUNNER_GET_CLASS (self)->force_quit (self);
792
793 IDE_EXIT;
794 }
795
796 void
ide_runner_set_argv(IdeRunner * self,const gchar * const * argv)797 ide_runner_set_argv (IdeRunner *self,
798 const gchar * const *argv)
799 {
800 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
801 guint i;
802
803 g_return_if_fail (IDE_IS_RUNNER (self));
804
805 g_queue_foreach (&priv->argv, (GFunc)g_free, NULL);
806 g_queue_clear (&priv->argv);
807
808 if (argv != NULL)
809 {
810 for (i = 0; argv [i]; i++)
811 g_queue_push_tail (&priv->argv, g_strdup (argv [i]));
812 }
813
814 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
815 }
816
817 /**
818 * ide_runner_get_environment:
819 *
820 * Returns: (transfer none): The #IdeEnvironment the process launched uses.
821 *
822 * Since: 3.32
823 */
824 IdeEnvironment *
ide_runner_get_environment(IdeRunner * self)825 ide_runner_get_environment (IdeRunner *self)
826 {
827 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
828
829 g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
830
831 return priv->env;
832 }
833
834 /**
835 * ide_runner_get_argv:
836 *
837 * Gets the argument list as a newly allocated string array.
838 *
839 * Returns: (transfer full): A newly allocated string array that should
840 * be freed with g_strfreev().
841 *
842 * Since: 3.32
843 */
844 gchar **
ide_runner_get_argv(IdeRunner * self)845 ide_runner_get_argv (IdeRunner *self)
846 {
847 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
848 GPtrArray *ar;
849 GList *iter;
850
851 g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
852
853 ar = g_ptr_array_new ();
854
855 for (iter = priv->argv.head; iter != NULL; iter = iter->next)
856 {
857 const gchar *param = iter->data;
858
859 g_ptr_array_add (ar, g_strdup (param));
860 }
861
862 g_ptr_array_add (ar, NULL);
863
864 return (gchar **)g_ptr_array_free (ar, FALSE);
865 }
866
867 static void
ide_runner_collect_addins_cb(PeasExtensionSet * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)868 ide_runner_collect_addins_cb (PeasExtensionSet *set,
869 PeasPluginInfo *plugin_info,
870 PeasExtension *exten,
871 gpointer user_data)
872 {
873 GSList **list = user_data;
874
875 *list = g_slist_prepend (*list, exten);
876 }
877
878 static void
ide_runner_collect_addins(IdeRunner * self,GSList ** list)879 ide_runner_collect_addins (IdeRunner *self,
880 GSList **list)
881 {
882 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
883
884 g_assert (IDE_IS_RUNNER (self));
885 g_assert (list != NULL);
886
887 peas_extension_set_foreach (priv->addins,
888 ide_runner_collect_addins_cb,
889 list);
890 }
891
892 static void
ide_runner_posthook_cb(GObject * object,GAsyncResult * result,gpointer user_data)893 ide_runner_posthook_cb (GObject *object,
894 GAsyncResult *result,
895 gpointer user_data)
896 {
897 IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
898 g_autoptr(IdeTask) task = user_data;
899 g_autoptr(GError) error = NULL;
900
901 g_assert (IDE_IS_RUNNER_ADDIN (addin));
902 g_assert (G_IS_ASYNC_RESULT (result));
903
904 if (!ide_runner_addin_posthook_finish (addin, result, &error))
905 ide_task_return_error (task, g_steal_pointer (&error));
906 else
907 ide_runner_tick_posthook (task);
908 }
909
910 static void
ide_runner_tick_posthook(IdeTask * task)911 ide_runner_tick_posthook (IdeTask *task)
912 {
913 IdeRunnerRunState *state;
914
915 g_assert (IDE_IS_TASK (task));
916
917 state = ide_task_get_task_data (task);
918
919 if (state->posthook_queue != NULL)
920 {
921 g_autoptr(IdeRunnerAddin) addin = NULL;
922
923 addin = pop_runner_addin (&state->posthook_queue);
924 ide_runner_addin_posthook_async (addin,
925 ide_task_get_cancellable (task),
926 ide_runner_posthook_cb,
927 g_object_ref (task));
928 return;
929 }
930
931 ide_task_return_boolean (task, TRUE);
932 }
933
934 static void
ide_runner_run_cb(GObject * object,GAsyncResult * result,gpointer user_data)935 ide_runner_run_cb (GObject *object,
936 GAsyncResult *result,
937 gpointer user_data)
938 {
939 IdeRunner *self = (IdeRunner *)object;
940 g_autoptr(IdeTask) task = user_data;
941 g_autoptr(GError) error = NULL;
942
943 IDE_ENTRY;
944
945 g_assert (IDE_IS_RUNNER (self));
946 g_assert (G_IS_ASYNC_RESULT (result));
947 g_assert (IDE_IS_TASK (task));
948
949 if (!IDE_RUNNER_GET_CLASS (self)->run_finish (self, result, &error))
950 ide_task_return_error (task, g_steal_pointer (&error));
951 else
952 ide_runner_tick_posthook (task);
953
954 IDE_EXIT;
955 }
956
957 static void
ide_runner_tick_run(IdeTask * task)958 ide_runner_tick_run (IdeTask *task)
959 {
960 IdeRunner *self;
961
962 IDE_ENTRY;
963
964 g_assert (IDE_IS_TASK (task));
965 self = ide_task_get_source_object (task);
966 g_assert (IDE_IS_RUNNER (self));
967
968 IDE_RUNNER_GET_CLASS (self)->run_async (self,
969 ide_task_get_cancellable (task),
970 ide_runner_run_cb,
971 g_object_ref (task));
972
973 IDE_EXIT;
974 }
975
976 static void
ide_runner_prehook_cb(GObject * object,GAsyncResult * result,gpointer user_data)977 ide_runner_prehook_cb (GObject *object,
978 GAsyncResult *result,
979 gpointer user_data)
980 {
981 IdeRunnerAddin *addin = (IdeRunnerAddin *)object;
982 g_autoptr(IdeTask) task = user_data;
983 g_autoptr(GError) error = NULL;
984
985 IDE_ENTRY;
986
987 g_assert (IDE_IS_RUNNER_ADDIN (addin));
988 g_assert (G_IS_ASYNC_RESULT (result));
989 g_assert (IDE_IS_TASK (task));
990
991 if (!ide_runner_addin_prehook_finish (addin, result, &error))
992 ide_task_return_error (task, g_steal_pointer (&error));
993 else
994 ide_runner_tick_prehook (task);
995
996 IDE_EXIT;
997 }
998
999 static void
ide_runner_tick_prehook(IdeTask * task)1000 ide_runner_tick_prehook (IdeTask *task)
1001 {
1002 IdeRunnerRunState *state;
1003
1004 IDE_ENTRY;
1005
1006 g_assert (IDE_IS_TASK (task));
1007
1008 state = ide_task_get_task_data (task);
1009
1010 if (state->prehook_queue != NULL)
1011 {
1012 g_autoptr(IdeRunnerAddin) addin = NULL;
1013
1014 addin = pop_runner_addin (&state->prehook_queue);
1015 ide_runner_addin_prehook_async (addin,
1016 ide_task_get_cancellable (task),
1017 ide_runner_prehook_cb,
1018 g_object_ref (task));
1019 IDE_EXIT;
1020 }
1021
1022 ide_runner_tick_run (task);
1023
1024 IDE_EXIT;
1025 }
1026
1027 void
ide_runner_run_async(IdeRunner * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1028 ide_runner_run_async (IdeRunner *self,
1029 GCancellable *cancellable,
1030 GAsyncReadyCallback callback,
1031 gpointer user_data)
1032 {
1033 g_autoptr(IdeTask) task = NULL;
1034 IdeRunnerRunState *state;
1035
1036 IDE_ENTRY;
1037
1038 g_return_if_fail (IDE_IS_RUNNER (self));
1039 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1040
1041 task = ide_task_new (self, cancellable, callback, user_data);
1042 ide_task_set_source_tag (task, ide_runner_run_async);
1043 ide_task_set_check_cancellable (task, FALSE);
1044 ide_task_set_priority (task, G_PRIORITY_LOW);
1045
1046 /*
1047 * We need to run the prehook functions for each addin first before we
1048 * can call our IdeRunnerClass.run vfunc. Since these are async, we
1049 * have to bring some state along with us.
1050 */
1051 state = g_slice_new0 (IdeRunnerRunState);
1052 ide_runner_collect_addins (self, &state->prehook_queue);
1053 ide_runner_collect_addins (self, &state->posthook_queue);
1054 ide_task_set_task_data (task, state, ide_runner_run_state_free);
1055
1056 ide_runner_tick_prehook (task);
1057
1058 IDE_EXIT;
1059 }
1060
1061 gboolean
ide_runner_run_finish(IdeRunner * self,GAsyncResult * result,GError ** error)1062 ide_runner_run_finish (IdeRunner *self,
1063 GAsyncResult *result,
1064 GError **error)
1065 {
1066 gboolean ret;
1067
1068 IDE_ENTRY;
1069
1070 g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
1071 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
1072
1073 ret = ide_task_propagate_boolean (IDE_TASK (result), error);
1074
1075 IDE_RETURN (ret);
1076 }
1077
1078 void
ide_runner_append_argv(IdeRunner * self,const gchar * param)1079 ide_runner_append_argv (IdeRunner *self,
1080 const gchar *param)
1081 {
1082 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1083
1084 g_return_if_fail (IDE_IS_RUNNER (self));
1085 g_return_if_fail (param != NULL);
1086
1087 g_queue_push_tail (&priv->argv, g_strdup (param));
1088 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
1089 }
1090
1091 void
ide_runner_prepend_argv(IdeRunner * self,const gchar * param)1092 ide_runner_prepend_argv (IdeRunner *self,
1093 const gchar *param)
1094 {
1095 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1096
1097 g_return_if_fail (IDE_IS_RUNNER (self));
1098 g_return_if_fail (param != NULL);
1099
1100 g_queue_push_head (&priv->argv, g_strdup (param));
1101 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ARGV]);
1102 }
1103
1104 IdeRunner *
ide_runner_new(IdeContext * context)1105 ide_runner_new (IdeContext *context)
1106 {
1107 g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
1108
1109 return g_object_new (IDE_TYPE_RUNNER,
1110 NULL);
1111 }
1112
1113 gboolean
ide_runner_get_run_on_host(IdeRunner * self)1114 ide_runner_get_run_on_host (IdeRunner *self)
1115 {
1116 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1117
1118 g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
1119
1120 return priv->run_on_host;
1121 }
1122
1123 void
ide_runner_set_run_on_host(IdeRunner * self,gboolean run_on_host)1124 ide_runner_set_run_on_host (IdeRunner *self,
1125 gboolean run_on_host)
1126 {
1127 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1128
1129 run_on_host = !!run_on_host;
1130
1131 if (run_on_host != priv->run_on_host)
1132 {
1133 priv->run_on_host = run_on_host;
1134 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RUN_ON_HOST]);
1135 }
1136 }
1137
1138 GSubprocessFlags
ide_runner_get_flags(IdeRunner * self)1139 ide_runner_get_flags (IdeRunner *self)
1140 {
1141 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1142
1143 g_return_val_if_fail (IDE_IS_RUNNER (self), 0);
1144
1145 return priv->flags;
1146 }
1147
1148 void
ide_runner_set_flags(IdeRunner * self,GSubprocessFlags flags)1149 ide_runner_set_flags (IdeRunner *self,
1150 GSubprocessFlags flags)
1151 {
1152 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1153
1154 g_return_if_fail (IDE_IS_RUNNER (self));
1155
1156 priv->flags = flags;
1157 }
1158
1159 gboolean
ide_runner_get_clear_env(IdeRunner * self)1160 ide_runner_get_clear_env (IdeRunner *self)
1161 {
1162 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1163
1164 g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
1165
1166 return priv->clear_env;
1167 }
1168
1169 void
ide_runner_set_clear_env(IdeRunner * self,gboolean clear_env)1170 ide_runner_set_clear_env (IdeRunner *self,
1171 gboolean clear_env)
1172 {
1173 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1174
1175 g_return_if_fail (IDE_IS_RUNNER (self));
1176
1177 clear_env = !!clear_env;
1178
1179 if (clear_env != priv->clear_env)
1180 {
1181 priv->clear_env = clear_env;
1182 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLEAR_ENV]);
1183 }
1184 }
1185
1186 /**
1187 * ide_runner_set_pty:
1188 * @self: a #IdeRunner
1189 * @pty: (nullable): a #VtePty or %NULL
1190 *
1191 * Sets the #VtePty to use for the runner.
1192 *
1193 * This is equivalent to calling ide_runner_set_tty() with the
1194 * result of vte_pty_get_fd().
1195 *
1196 * Since: 3.32
1197 */
1198 void
ide_runner_set_pty(IdeRunner * self,VtePty * pty)1199 ide_runner_set_pty (IdeRunner *self,
1200 VtePty *pty)
1201 {
1202 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1203
1204 g_return_if_fail (IDE_IS_RUNNER (self));
1205 g_return_if_fail (!pty || VTE_IS_PTY (pty));
1206
1207 g_set_object (&priv->pty, pty);
1208 }
1209
1210 /**
1211 * ide_runner_get_pty:
1212 * @self: a #IdeRunner
1213 *
1214 * Gets the #VtePty that was assigned.
1215 *
1216 * Returns: (nullable) (transfer none): a #VtePty or %NULL
1217 *
1218 * Since: 3.34
1219 */
1220 VtePty *
ide_runner_get_pty(IdeRunner * self)1221 ide_runner_get_pty (IdeRunner *self)
1222 {
1223 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1224
1225 g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
1226
1227 return priv->pty;
1228 }
1229
1230 static gint
sort_fd_mapping(gconstpointer a,gconstpointer b)1231 sort_fd_mapping (gconstpointer a,
1232 gconstpointer b)
1233 {
1234 const FdMapping *map_a = a;
1235 const FdMapping *map_b = b;
1236
1237 return map_a->dest_fd - map_b->dest_fd;
1238 }
1239
1240 /**
1241 * ide_runner_take_fd:
1242 * @self: An #IdeRunner
1243 * @source_fd: the fd to map, this will be closed by #IdeRunner
1244 * @dest_fd: the target FD in the spawned process, or -1 for next available
1245 *
1246 * This will ensure that @source_fd is mapped into the new process as @dest_fd.
1247 * If @dest_fd is -1, then the next fd will be used and that value will be
1248 * returned. Note that this is not a valid fd in the calling process, only
1249 * within the destination process.
1250 *
1251 * Returns: @dest_fd or the FD or the next available dest_fd.
1252 *
1253 * Since: 3.32
1254 */
1255 gint
ide_runner_take_fd(IdeRunner * self,gint source_fd,gint dest_fd)1256 ide_runner_take_fd (IdeRunner *self,
1257 gint source_fd,
1258 gint dest_fd)
1259 {
1260 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1261 FdMapping map = { -1, -1 };
1262
1263 g_return_val_if_fail (IDE_IS_RUNNER (self), -1);
1264 g_return_val_if_fail (source_fd > -1, -1);
1265
1266 if (priv->fd_mapping == NULL)
1267 priv->fd_mapping = g_array_new (FALSE, FALSE, sizeof (FdMapping));
1268
1269 /*
1270 * Quick and dirty hack to take the next FD, won't deal with people mapping
1271 * to 1024 well, but we can fix that when we come across it.
1272 */
1273 if (dest_fd < 0)
1274 {
1275 gint max_fd = 2;
1276
1277 for (guint i = 0; i < priv->fd_mapping->len; i++)
1278 {
1279 FdMapping *entry = &g_array_index (priv->fd_mapping, FdMapping, i);
1280
1281 if (entry->dest_fd > max_fd)
1282 max_fd = entry->dest_fd;
1283 }
1284
1285 dest_fd = max_fd + 1;
1286 }
1287
1288 map.source_fd = source_fd;
1289 map.dest_fd = dest_fd;
1290
1291 g_array_append_val (priv->fd_mapping, map);
1292 g_array_sort (priv->fd_mapping, sort_fd_mapping);
1293
1294 return dest_fd;
1295 }
1296
1297 /**
1298 * ide_runner_get_runtime:
1299 * @self: An #IdeRuntime
1300 *
1301 * This function will get the #IdeRuntime that will be used to execute the
1302 * application. Consumers may want to use this to determine if a particular
1303 * program is available (such as gdb, perf, strace, etc).
1304 *
1305 * Returns: (nullable) (transfer full): An #IdeRuntime or %NULL.
1306 *
1307 * Since: 3.32
1308 */
1309 IdeRuntime *
ide_runner_get_runtime(IdeRunner * self)1310 ide_runner_get_runtime (IdeRunner *self)
1311 {
1312 IdeConfigManager *config_manager;
1313 IdeConfig *config;
1314 IdeContext *context;
1315 IdeRuntime *runtime;
1316
1317 g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
1318
1319 if (IDE_RUNNER_GET_CLASS (self)->get_runtime)
1320 return IDE_RUNNER_GET_CLASS (self)->get_runtime (self);
1321
1322 context = ide_object_get_context (IDE_OBJECT (self));
1323 config_manager = ide_config_manager_from_context (context);
1324 config = ide_config_manager_get_current (config_manager);
1325 runtime = ide_config_get_runtime (config);
1326
1327 return runtime != NULL ? g_object_ref (runtime) : NULL;
1328 }
1329
1330 gboolean
ide_runner_get_failed(IdeRunner * self)1331 ide_runner_get_failed (IdeRunner *self)
1332 {
1333 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1334
1335 g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
1336
1337 return priv->failed;
1338 }
1339
1340 void
ide_runner_set_failed(IdeRunner * self,gboolean failed)1341 ide_runner_set_failed (IdeRunner *self,
1342 gboolean failed)
1343 {
1344 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1345
1346 IDE_ENTRY;
1347
1348 g_return_if_fail (IDE_IS_RUNNER (self));
1349
1350 failed = !!failed;
1351
1352 if (failed != priv->failed)
1353 {
1354 priv->failed = failed;
1355 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FAILED]);
1356 }
1357
1358 IDE_EXIT;
1359 }
1360
1361 /**
1362 * ide_runner_get_cwd:
1363 * @self: a #IdeRunner
1364 *
1365 * Returns: (nullable): The current working directory, or %NULL.
1366 *
1367 * Since: 3.32
1368 */
1369 const gchar *
ide_runner_get_cwd(IdeRunner * self)1370 ide_runner_get_cwd (IdeRunner *self)
1371 {
1372 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1373
1374 g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
1375
1376 return priv->cwd;
1377 }
1378
1379 /**
1380 * ide_runner_set_cwd:
1381 * @self: a #IdeRunner
1382 * @cwd: (nullable): The working directory or %NULL
1383 *
1384 * Sets the directory to use when spawning the runner.
1385 *
1386 * Since: 3.32
1387 */
1388 void
ide_runner_set_cwd(IdeRunner * self,const gchar * cwd)1389 ide_runner_set_cwd (IdeRunner *self,
1390 const gchar *cwd)
1391 {
1392 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1393
1394 g_return_if_fail (IDE_IS_RUNNER (self));
1395
1396 if (!ide_str_equal0 (priv->cwd, cwd))
1397 {
1398 g_free (priv->cwd);
1399 priv->cwd = g_strdup (cwd);
1400 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CWD]);
1401 }
1402 }
1403
1404 /**
1405 * ide_runner_push_args:
1406 * @self: a #IdeRunner
1407 * @args: (array zero-terminated=1) (element-type utf8) (nullable): the arguments
1408 *
1409 * Helper to call ide_runner_append_argv() for every argument
1410 * contained in @args.
1411 *
1412 * Since: 3.32
1413 */
1414 void
ide_runner_push_args(IdeRunner * self,const gchar * const * args)1415 ide_runner_push_args (IdeRunner *self,
1416 const gchar * const *args)
1417 {
1418 g_return_if_fail (IDE_IS_RUNNER (self));
1419
1420 if (args == NULL)
1421 return;
1422
1423 for (guint i = 0; args[i] != NULL; i++)
1424 ide_runner_append_argv (self, args[i]);
1425 }
1426
1427 /**
1428 * ide_runner_get_build_target:
1429 * @self: a #IdeRunner
1430 *
1431 * Returns: (nullable) (transfer none): The %IdeBuildTarget associated with this %IdeRunner, or %NULL.
1432 * See #IdeRunner:build-target for details.
1433 *
1434 * Since: 3.32
1435 */
1436 IdeBuildTarget *
ide_runner_get_build_target(IdeRunner * self)1437 ide_runner_get_build_target (IdeRunner *self)
1438 {
1439 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1440
1441 g_return_val_if_fail (IDE_IS_RUNNER (self), NULL);
1442
1443 return priv->build_target;
1444 }
1445
1446 /**
1447 * ide_runner_set_build_target:
1448 * @self: a #IdeRunner
1449 * @build_target: (nullable): The build target, or %NULL
1450 *
1451 * Sets the build target associated with this runner.
1452 *
1453 * Since: 3.32
1454 */
1455 void
ide_runner_set_build_target(IdeRunner * self,IdeBuildTarget * build_target)1456 ide_runner_set_build_target (IdeRunner *self,
1457 IdeBuildTarget *build_target)
1458 {
1459 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1460
1461 g_return_if_fail (IDE_IS_RUNNER (self));
1462
1463 if (g_set_object (&priv->build_target, build_target))
1464 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUILD_TARGET]);
1465 }
1466
1467 gboolean
ide_runner_get_disable_pty(IdeRunner * self)1468 ide_runner_get_disable_pty (IdeRunner *self)
1469 {
1470 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1471
1472 g_return_val_if_fail (IDE_IS_RUNNER (self), FALSE);
1473
1474 return priv->disable_pty;
1475 }
1476
1477 void
ide_runner_set_disable_pty(IdeRunner * self,gboolean disable_pty)1478 ide_runner_set_disable_pty (IdeRunner *self,
1479 gboolean disable_pty)
1480 {
1481 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1482
1483 g_return_if_fail (IDE_IS_RUNNER (self));
1484
1485 disable_pty = !!disable_pty;
1486
1487 if (disable_pty != priv->disable_pty)
1488 {
1489 priv->disable_pty = disable_pty;
1490 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISABLE_PTY]);
1491 }
1492 }
1493
1494 void
ide_runner_take_tty_fd(IdeRunner * self,gint tty_fd)1495 ide_runner_take_tty_fd (IdeRunner *self,
1496 gint tty_fd)
1497 {
1498 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1499
1500 g_return_if_fail (IDE_IS_RUNNER (self));
1501 g_return_if_fail (tty_fd > -1);
1502
1503 if (priv->child_fd != -1)
1504 close (priv->child_fd);
1505 priv->child_fd = tty_fd;
1506 }
1507
1508 gint
ide_runner_get_max_fd(IdeRunner * self)1509 ide_runner_get_max_fd (IdeRunner *self)
1510 {
1511 IdeRunnerPrivate *priv = ide_runner_get_instance_private (self);
1512 gint max_fd = 2;
1513
1514 g_return_val_if_fail (IDE_IS_RUNNER (self), 2);
1515
1516 if (priv->fd_mapping != NULL)
1517 {
1518 for (guint i = 0; i < priv->fd_mapping->len; i++)
1519 {
1520 const FdMapping *map = &g_array_index (priv->fd_mapping, FdMapping, i);
1521
1522 if (map->dest_fd > max_fd)
1523 max_fd = map->dest_fd;
1524 }
1525 }
1526
1527 return max_fd;
1528 }
1529