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