1 /* ide-pipeline.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-pipeline"
22 
23 #include "config.h"
24 
25 #include <glib/gi18n.h>
26 #include <dazzle.h>
27 #include <libide-plugins.h>
28 #include <libpeas/peas.h>
29 #include <string.h>
30 #include <vte/vte.h>
31 
32 #include <libide-core.h>
33 #include <libide-code.h>
34 #include <libide-io.h>
35 #include <libide-plugins.h>
36 #include <libide-projects.h>
37 #include <libide-threading.h>
38 
39 #include "ide-build-log-private.h"
40 #include "ide-build-log.h"
41 #include "ide-pipeline-addin.h"
42 #include "ide-pipeline.h"
43 #include "ide-build-private.h"
44 #include "ide-pipeline-stage-launcher.h"
45 #include "ide-pipeline-stage-private.h"
46 #include "ide-pipeline-stage.h"
47 #include "ide-build-system.h"
48 #include "ide-device-info.h"
49 #include "ide-device.h"
50 #include "ide-foundry-compat.h"
51 #include "ide-foundry-enums.h"
52 #include "ide-run-manager-private.h"
53 #include "ide-runtime.h"
54 #include "ide-toolchain-manager.h"
55 #include "ide-toolchain.h"
56 #include "ide-triplet.h"
57 
58 DZL_DEFINE_COUNTER (Instances, "Pipeline", "N Pipelines", "Number of Pipeline instances")
59 G_DEFINE_QUARK (ide_build_error, ide_build_error)
60 
61 /**
62  * SECTION:idebuildpipeline
63  * @title: IdePipeline
64  * @short_description: Pluggable build pipeline
65  * @include: ide.h
66  *
67  * The #IdePipeline is responsible for managing the build process
68  * for Builder. It consists of multiple build "phases" (see #IdePipelinePhase
69  * for the individual phases). An #IdePipelineStage can be attached with
70  * a priority to each phase and is the primary mechanism that plugins
71  * use to perform their operations in the proper ordering.
72  *
73  * For example, the flatpak plugin provides its download stage as part of the
74  * %IDE_PIPELINE_PHASE_DOWNLOAD phase. The autotools plugin provides stages to
75  * phases such as %IDE_PIPELINE_PHASE_AUTOGEN, %IDE_PIPELINE_PHASE_CONFIGURE,
76  * %IDE_PIPELINE_PHASE_BUILD, and %IDE_PIPELINE_PHASE_INSTALL.
77  *
78  * If you want ensure a particular phase is performed as part of a build,
79  * then fall ide_pipeline_request_phase() with the phase you are
80  * interested in seeing complete successfully.
81  *
82  * If your plugin has discovered that something has changed that invalidates a
83  * given phase, use ide_pipeline_invalidate_phase() to ensure that the
84  * phase is re-buildd the next time a requested phase of higher precidence
85  * is requested.
86  *
87  * It can be useful to perform operations before or after a given stage (but
88  * still be buildd as part of that stage) so the %IDE_PIPELINE_PHASE_BEFORE and
89  * %IDE_PIPELINE_PHASE_AFTER flags may be xor'd with the requested phase.  If more
90  * precise ordering is required, you may use the priority parameter to order
91  * the operation with regards to other stages in that phase.
92  *
93  * Transient stages may be added to the pipeline and they will be removed after
94  * the ide_pipeline_build_async() operation has completed successfully
95  * or has failed. You can mark a stage as trandient with
96  * ide_pipeline_stage_set_transient(). This may be useful to perform operations
97  * such as an "export tarball" stage which should only run once as determined
98  * by the user requesting a "make dist" style operation.
99  *
100  * Since: 3.32
101  */
102 
103 typedef struct
104 {
105   guint          id;
106   IdePipelinePhase  phase;
107   gint           priority;
108   IdePipelineStage *stage;
109 } PipelineEntry;
110 
111 typedef struct
112 {
113   IdePipeline *self;
114   GPtrArray        *addins;
115 } IdleLoadState;
116 
117 typedef struct
118 {
119   guint   id;
120   GRegex *regex;
121 } ErrorFormat;
122 
123 struct _IdePipeline
124 {
125   IdeObject parent_instance;
126 
127   /*
128    * A cancellable we can use to chain to all incoming requests so that
129    * all tasks may be cancelled at once when _ide_pipeline_cancel()
130    * is called.
131    */
132   GCancellable *cancellable;
133 
134   /*
135    * These are our extensions to the BuildPipeline. Plugins insert
136    * them and they might go about adding stages to the pipeline,
137    * add error formats, or just monitor logs.
138    */
139   IdeExtensionSetAdapter *addins;
140 
141   /*
142    * This is the configuration for the build. It is a snapshot of
143    * the real configuration so that we do not need to synchronize
144    * with the UI process for accesses.
145    */
146   IdeConfig *config;
147 
148   /*
149    * The device we are building for. This allows components to setup
150    * cross-compiling if necessary based on the architecture and system of
151    * the device in question. It also allows for determining a deployment
152    * strategy to get the compiled bits onto the device.
153    */
154   IdeDevice *device;
155   IdeDeviceInfo *device_info;
156 
157   /*
158    * The cached triplet for the device we're compiling for. This allows
159    * plugins to avoid some classes of work when building for the same
160    * system that Builder is running upon.
161    */
162   IdeTriplet *host_triplet;
163 
164   /*
165    * The runtime we're using to build. This may be different than what
166    * is specified in the IdeConfig, as the @device could alter
167    * what architecture we're building for (and/or cross-compiling).
168    */
169   IdeRuntime *runtime;
170 
171   /*
172    * The toolchain we're using to build. This may be different than what
173    * is specified in the IdeConfig, as the @device could alter
174    * what architecture we're building for (and/or cross-compiling).
175    */
176   IdeToolchain *toolchain;
177 
178   /*
179    * The IdeBuildLog is a private implementation that we use to
180    * log things from addins via observer callbacks.
181    */
182   IdeBuildLog *log;
183 
184   /*
185    * These are our builddir/srcdir paths. Useful for building paths
186    * by addins. We try to create a new builddir that will be unique
187    * based on hashing of the configuration.
188    */
189   gchar *builddir;
190   gchar *srcdir;
191 
192   /*
193    * This is an array of PipelineEntry, which contain information we
194    * need about the stage and an identifier that addins can use to
195    * remove their inserted stages.
196    */
197   GArray *pipeline;
198 
199   /*
200    * This contains the GBinding objects used to keep the "completed"
201    * property of chained stages updated.
202    */
203   GPtrArray *chained_bindings;
204 
205   /*
206    * This are used for ErrorFormat registration so that we have a
207    * single place to extract "GCC-style" warnings and errors. Other
208    * languages can also register these so they show up in the build
209    * errors panel.
210    */
211   GArray *errfmts;
212   gchar  *errfmt_current_dir;
213   gchar  *errfmt_top_dir;
214   guint   errfmt_seqnum;
215 
216   /*
217    * The VtePty is used to connect to a VteTerminal. It's basically
218    * just a wrapper around a PTY master. We then add a IdePtyIntercept
219    * to proxy PTY data while allowing us to tap into the content being
220    * transmitted. We can use that to run regexes against and perform
221    * additional error extraction. Finally, pty_slave is the PTY device
222    * we created that will get attached to stdin/stdout/stderr in our
223    * spawned subprocesses. It is a slave to the PTY master owned by
224    * the IdePtyIntercept.
225    */
226   VtePty          *pty;
227   IdePtyIntercept  intercept;
228   IdePtyFd         pty_slave;
229 
230   /*
231    * If the terminal interpreting our Pty has received a terminal
232    * title update, it might set this message which we can use for
233    * better build messages.
234    */
235   gchar *message;
236 
237   /*
238    * No reference to the current stage. It is only available during
239    * the asynchronous execution of the stage.
240    */
241   IdePipelineStage *current_stage;
242 
243   /*
244    * The index of our current PipelineEntry. This should start at -1
245    * to indicate that no stage is currently active.
246    */
247   gint position;
248 
249   /*
250    * This is the requested mask to be built. It should be reset after
251    * performing a build so that a followup build_async() would be
252    * innocuous.
253    */
254   IdePipelinePhase requested_mask;
255 
256   /*
257    * We queue incoming tasks in case we need for a finish task to
258    * complete before our task can continue. The items in the queue
259    * are DelayedTask structs with a IdeTask and the type id so we
260    * can progress the task upon completion of the previous task.
261    */
262   GQueue task_queue;
263 
264   /*
265    * We use this sequence number to give PipelineEntry instances a
266    * unique identifier. The addins can use this to remove their
267    * inserted build stages.
268    */
269   guint seqnum;
270 
271   /* We use a GSource to load addins in an idle callback so that
272    * we don't block the main loop for too long. When disposing the
273    * pipeline, we need to kill that operation too (since it may
274    * lose access to IdeContext in the process).
275    */
276   guint idle_addins_load_source;
277 
278   /*
279    * If we failed to build, this should be set.
280    */
281   guint failed : 1;
282 
283   /*
284    * If we are within a build, this should be set.
285    */
286   guint busy : 1;
287 
288   /*
289    * If we are in the middle of a clean operation.
290    */
291   guint in_clean : 1;
292 
293   /*
294    * Precalculation if we need to look for errors on stdout. We can't rely
295    * on @current_stage for this, because log entries might come in
296    * asynchronously and after the processes/stage has completed.
297    */
298   guint errors_on_stdout : 1;
299 
300   /*
301    * This is set to TRUE if the pipeline has failed initialization. That means
302    * that all future operations will fail (but we can keep the object alive to
303    * ensure that the manager has a valid object instance for the pipeline).
304    */
305   guint broken : 1;
306 
307   /*
308    * This is set to TRUE when we attempt to load plugins (after the config
309    * has been marked as ready).
310    */
311   guint loaded : 1;
312 };
313 
314 typedef enum
315 {
316   TASK_BUILD   = 1,
317   TASK_CLEAN   = 2,
318   TASK_REBUILD = 3,
319 } TaskType;
320 
321 typedef struct
322 {
323   /*
324    * Our operation type. This will indicate one of the TaskType enum
325    * which corellate to the various async functions of the pipeline.
326    */
327   TaskType type;
328 
329   /*
330    * This is an unowned pointer to the task. Since the Operation structure is
331    * the task data, we cannot reference as that would create a cycle. Instead,
332    * we just rely on this becoming invalid during the task cleanup.
333    */
334   IdeTask *task;
335 
336   /*
337    * The phase that should be met for the given pipeline operation.
338    */
339   IdePipelinePhase phase;
340 
341   union {
342     struct {
343       GPtrArray *stages;
344     } clean;
345     struct {
346       GPtrArray *targets;
347     } build;
348     struct {
349       GPtrArray *targets;
350     } rebuild;
351   };
352 } TaskData;
353 
354 static void ide_pipeline_queue_flush  (IdePipeline         *self);
355 static void ide_pipeline_tick_build   (IdePipeline         *self,
356                                        IdeTask             *task);
357 static void ide_pipeline_tick_clean   (IdePipeline         *self,
358                                        IdeTask             *task);
359 static void ide_pipeline_tick_rebuild (IdePipeline         *self,
360                                        IdeTask             *task);
361 static void initable_iface_init       (GInitableIface      *iface);
362 static void list_model_iface_init     (GListModelInterface *iface);
363 
364 G_DEFINE_FINAL_TYPE_WITH_CODE (IdePipeline, ide_pipeline, IDE_TYPE_OBJECT,
365                          G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init)
366                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
367 
368 enum {
369   PROP_0,
370   PROP_BUSY,
371   PROP_CONFIG,
372   PROP_DEVICE,
373   PROP_MESSAGE,
374   PROP_PHASE,
375   PROP_PTY,
376   N_PROPS
377 };
378 
379 enum {
380   DIAGNOSTIC,
381   STARTED,
382   FINISHED,
383   LOADED,
384   LAUNCHER_CREATED,
385   N_SIGNALS
386 };
387 
388 static GParamSpec *properties [N_PROPS];
389 static guint signals [N_SIGNALS];
390 static const gchar *task_type_names[] = {
391   NULL,
392   "build",
393   "clean",
394   "rebuild",
395 };
396 
397 static void
drop_caches(IdePipeline * self)398 drop_caches (IdePipeline *self)
399 {
400   g_autoptr(IdeContext) context = NULL;
401 
402   g_assert (IDE_IS_PIPELINE (self));
403 
404   /* We need to notify the run manager that it should drop any cached
405    * information about the install state. This would normally be done
406    * with a signal, but to simplify backporting, we can just call private
407    * API between the two modules.
408    */
409   if ((context = ide_object_ref_context (IDE_OBJECT (self))))
410     {
411       IdeRunManager *run_manager = ide_run_manager_from_context (context);
412       _ide_run_manager_drop_caches (run_manager);
413     }
414 }
415 
416 static void
chained_binding_clear(gpointer data)417 chained_binding_clear (gpointer data)
418 {
419   GBinding *binding = data;
420 
421   g_binding_unbind (binding);
422   g_object_unref (binding);
423 }
424 
425 static void
idle_load_state_free(gpointer data)426 idle_load_state_free (gpointer data)
427 {
428   IdleLoadState *state = data;
429 
430   g_clear_pointer (&state->addins, g_ptr_array_unref);
431   g_clear_object (&state->self);
432   g_slice_free (IdleLoadState, state);
433 }
434 
435 static void
task_data_free(gpointer data)436 task_data_free (gpointer data)
437 {
438   TaskData *td = data;
439 
440   if (td != NULL)
441     {
442       if (td->type == TASK_CLEAN)
443         g_clear_pointer (&td->clean.stages, g_ptr_array_unref);
444       if (td->type == TASK_BUILD)
445         g_clear_pointer (&td->build.targets, g_ptr_array_unref);
446       if (td->type == TASK_REBUILD)
447         g_clear_pointer (&td->rebuild.targets, g_ptr_array_unref);
448       td->type = 0;
449       td->task = NULL;
450       g_slice_free (TaskData, td);
451     }
452 }
453 
454 static TaskData *
task_data_new(IdeTask * task,TaskType type)455 task_data_new (IdeTask  *task,
456                TaskType  type)
457 {
458   TaskData *td;
459 
460   g_assert (IDE_IS_TASK (task));
461   g_assert (type > 0);
462   g_assert (type <= TASK_REBUILD);
463 
464   td = g_slice_new0 (TaskData);
465   td->type = type;
466   td->task = task;
467 
468   return td;
469 }
470 
471 static void
clear_error_format(gpointer data)472 clear_error_format (gpointer data)
473 {
474   ErrorFormat *errfmt = data;
475 
476   errfmt->id = 0;
477   g_clear_pointer (&errfmt->regex, g_regex_unref);
478 }
479 
480 static inline const gchar *
build_phase_nick(IdePipelinePhase phase)481 build_phase_nick (IdePipelinePhase phase)
482 {
483   GFlagsClass *klass = g_type_class_peek (IDE_TYPE_PIPELINE_PHASE);
484   GFlagsValue *value;
485 
486   g_assert (klass != NULL);
487 
488   phase &= IDE_PIPELINE_PHASE_MASK;
489   value = g_flags_get_first_value (klass, phase);
490 
491   if (value != NULL)
492     return value->value_nick;
493 
494   return "unknown";
495 }
496 
497 static IdeDiagnosticSeverity
parse_severity(const gchar * str)498 parse_severity (const gchar *str)
499 {
500   g_autofree gchar *lower = NULL;
501 
502   if (str == NULL)
503     return IDE_DIAGNOSTIC_WARNING;
504 
505   lower = g_utf8_strdown (str, -1);
506 
507   if (strstr (lower, "fatal") != NULL)
508     return IDE_DIAGNOSTIC_FATAL;
509 
510   if (strstr (lower, "error") != NULL)
511     return IDE_DIAGNOSTIC_ERROR;
512 
513   if (strstr (lower, "warning") != NULL)
514     return IDE_DIAGNOSTIC_WARNING;
515 
516   if (strstr (lower, "ignored") != NULL)
517     return IDE_DIAGNOSTIC_IGNORED;
518 
519   if (strstr (lower, "unused") != NULL)
520     return IDE_DIAGNOSTIC_UNUSED;
521 
522   if (strstr (lower, "deprecated") != NULL)
523     return IDE_DIAGNOSTIC_DEPRECATED;
524 
525   if (strstr (lower, "note") != NULL)
526     return IDE_DIAGNOSTIC_NOTE;
527 
528   return IDE_DIAGNOSTIC_WARNING;
529 }
530 
531 static IdeDiagnostic *
create_diagnostic(IdePipeline * self,GMatchInfo * match_info)532 create_diagnostic (IdePipeline *self,
533                    GMatchInfo  *match_info)
534 {
535   g_autofree gchar *filename = NULL;
536   g_autofree gchar *line = NULL;
537   g_autofree gchar *column = NULL;
538   g_autofree gchar *message = NULL;
539   g_autofree gchar *level = NULL;
540   g_autoptr(GFile) file = NULL;
541   g_autoptr(IdeLocation) location = NULL;
542   IdeContext *context;
543   struct {
544     gint64 line;
545     gint64 column;
546     IdeDiagnosticSeverity severity;
547   } parsed = { 0 };
548 
549   g_assert (IDE_IS_PIPELINE (self));
550   g_assert (match_info != NULL);
551 
552   message = g_match_info_fetch_named (match_info, "message");
553 
554   /* XXX: This is a hack to ignore a common but unuseful error message.
555    *      This really belongs somewhere else, but it's easier to do the
556    *      check here for now. We need proper callback for ErrorRegex in
557    *      the future so they can ignore it.
558    */
559   if (message == NULL || strncmp (message, "#warning _FORTIFY_SOURCE requires compiling with optimization", 61) == 0)
560     return NULL;
561 
562   filename = g_match_info_fetch_named (match_info, "filename");
563   line = g_match_info_fetch_named (match_info, "line");
564   column = g_match_info_fetch_named (match_info, "column");
565   level = g_match_info_fetch_named (match_info, "level");
566 
567   if (line != NULL)
568     {
569       parsed.line = g_ascii_strtoll (line, NULL, 10);
570       if (parsed.line < 1 || parsed.line > G_MAXINT32)
571         return NULL;
572       parsed.line--;
573     }
574 
575   if (column != NULL)
576     {
577       parsed.column = g_ascii_strtoll (column, NULL, 10);
578       if (parsed.column < 1 || parsed.column > G_MAXINT32)
579         return NULL;
580       parsed.column--;
581     }
582 
583   parsed.severity = parse_severity (level);
584 
585   /* Expand local user only, if we get a home-relative path */
586   if (filename != NULL && strncmp (filename, "~/", 2) == 0)
587     {
588       gchar *expanded = ide_path_expand (filename);
589       g_free (filename);
590       filename = expanded;
591     }
592 
593   if (!g_path_is_absolute (filename))
594     {
595       gchar *path;
596 
597       if (self->errfmt_current_dir != NULL)
598         {
599           const gchar *basedir = self->errfmt_current_dir;
600 
601           if (g_str_has_prefix (basedir, self->errfmt_top_dir))
602             {
603               basedir += strlen (self->errfmt_top_dir);
604               if (*basedir == G_DIR_SEPARATOR)
605                 basedir++;
606             }
607 
608           path = g_build_filename (basedir, filename, NULL);
609           g_free (filename);
610           filename = path;
611         }
612       else
613         {
614           path = g_build_filename (self->builddir, filename, NULL);
615           g_free (filename);
616           filename = path;
617         }
618     }
619 
620   context = ide_object_get_context (IDE_OBJECT (self));
621 
622   if (!g_path_is_absolute (filename))
623     {
624       g_autoptr(GFile) child = NULL;
625       g_autoptr(GFile) workdir = NULL;
626       gchar *path;
627 
628       workdir = ide_context_ref_workdir (context);
629       child = g_file_get_child (workdir, filename);
630       path = g_file_get_path (child);
631 
632       g_free (filename);
633       filename = path;
634     }
635 
636   file = ide_context_build_file (context, filename);
637   location = ide_location_new (file, parsed.line, parsed.column);
638 
639   return ide_diagnostic_new (parsed.severity, message, location);
640 }
641 
642 static gboolean
extract_directory_change(IdePipeline * self,const guint8 * data,gsize len)643 extract_directory_change (IdePipeline  *self,
644                           const guint8 *data,
645                           gsize         len)
646 {
647   g_autofree gchar *dir = NULL;
648   const guint8 *begin;
649 
650   g_assert (IDE_IS_PIPELINE (self));
651 
652   if (len == 0)
653     return FALSE;
654 
655 #define ENTERING_DIRECTORY_BEGIN "Entering directory '"
656 #define ENTERING_DIRECTORY_END   "'"
657 
658   begin = memmem (data, len, ENTERING_DIRECTORY_BEGIN, strlen (ENTERING_DIRECTORY_BEGIN));
659   if (begin == NULL)
660     return FALSE;
661 
662   begin += strlen (ENTERING_DIRECTORY_BEGIN);
663 
664   if (data[len - 1] != '\'')
665     return FALSE;
666 
667   len = &data[len - 1] - begin;
668   dir = g_strndup ((gchar *)begin, len);
669 
670   if (g_utf8_validate (dir, len, NULL))
671     {
672       g_free (self->errfmt_current_dir);
673 
674       if (len == 0)
675         self->errfmt_current_dir = g_strdup (self->errfmt_top_dir);
676       else
677         self->errfmt_current_dir = g_strndup (dir, len);
678 
679       if (self->errfmt_top_dir == NULL)
680         self->errfmt_top_dir = g_strdup (self->errfmt_current_dir);
681 
682       return TRUE;
683     }
684 
685 #undef ENTERING_DIRECTORY_BEGIN
686 #undef ENTERING_DIRECTORY_END
687 
688   return FALSE;
689 }
690 
691 static void
extract_diagnostics(IdePipeline * self,const guint8 * data,gsize len)692 extract_diagnostics (IdePipeline  *self,
693                      const guint8 *data,
694                      gsize         len)
695 {
696   g_autofree guint8 *unescaped = NULL;
697   IdeLineReader reader;
698   gchar *line;
699   gsize line_len;
700 
701   g_assert (IDE_IS_PIPELINE (self));
702   g_assert (data != NULL);
703 
704   if (len == 0 || self->errfmts->len == 0)
705     return;
706 
707   /* If we have any color escape sequences, remove them */
708   if G_UNLIKELY (memchr (data, '\033', len) || memmem (data, len, "\\e", 2))
709     {
710       gsize out_len = 0;
711 
712       unescaped = _ide_build_utils_filter_color_codes (data, len, &out_len);
713       if (out_len == 0)
714         return;
715 
716       data = unescaped;
717       len = out_len;
718     }
719 
720   ide_line_reader_init (&reader, (gchar *)data, len);
721 
722   while (NULL != (line = ide_line_reader_next (&reader, &line_len)))
723     {
724       if (extract_directory_change (self, (const guint8 *)line, line_len))
725         continue;
726 
727       for (guint i = 0; i < self->errfmts->len; i++)
728         {
729           const ErrorFormat *errfmt = &g_array_index (self->errfmts, ErrorFormat, i);
730           g_autoptr(GMatchInfo) match_info = NULL;
731 
732           if (g_regex_match_full (errfmt->regex, line, line_len, 0, 0, &match_info, NULL))
733             {
734               g_autoptr(IdeDiagnostic) diagnostic = create_diagnostic (self, match_info);
735 
736               if (diagnostic != NULL)
737                 {
738                   ide_pipeline_emit_diagnostic (self, diagnostic);
739                   break;
740                 }
741             }
742         }
743     }
744 }
745 
746 static void
ide_pipeline_log_observer(IdeBuildLogStream stream,const gchar * message,gssize message_len,gpointer user_data)747 ide_pipeline_log_observer (IdeBuildLogStream  stream,
748                            const gchar       *message,
749                            gssize             message_len,
750                            gpointer           user_data)
751 {
752   IdePipeline *self = user_data;
753 
754   g_assert (stream == IDE_BUILD_LOG_STDOUT || stream == IDE_BUILD_LOG_STDERR);
755   g_assert (IDE_IS_PIPELINE (self));
756   g_assert (message != NULL);
757 
758   if (message_len < 0)
759     message_len = strlen (message);
760 
761   if (self->log != NULL)
762     ide_build_log_observer (stream, message, message_len, self->log);
763 
764   extract_diagnostics (self, (const guint8 *)message, message_len);
765 }
766 
767 static void
ide_pipeline_intercept_pty_master_cb(const IdePtyIntercept * intercept,const IdePtyInterceptSide * side,const guint8 * data,gsize len,gpointer user_data)768 ide_pipeline_intercept_pty_master_cb (const IdePtyIntercept     *intercept,
769                                       const IdePtyInterceptSide *side,
770                                       const guint8              *data,
771                                       gsize                      len,
772                                       gpointer                   user_data)
773 {
774   IdePipeline *self = user_data;
775 
776   g_assert (intercept != NULL);
777   g_assert (side != NULL);
778   g_assert (data != NULL);
779   g_assert (len > 0);
780   g_assert (IDE_IS_PIPELINE (self));
781 
782   extract_diagnostics (self, data, len);
783 }
784 
785 static void
ide_pipeline_release_transients(IdePipeline * self)786 ide_pipeline_release_transients (IdePipeline *self)
787 {
788   IDE_ENTRY;
789 
790   g_assert (IDE_IS_PIPELINE (self));
791   g_assert (self->pipeline != NULL);
792 
793   for (guint i = self->pipeline->len; i > 0; i--)
794     {
795       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i - 1);
796 
797       g_assert (IDE_IS_PIPELINE_STAGE (entry->stage));
798 
799       if (ide_pipeline_stage_get_transient (entry->stage))
800         {
801           IDE_TRACE_MSG ("Releasing transient stage %s at index %u",
802                          G_OBJECT_TYPE_NAME (entry->stage),
803                          i - 1);
804           g_array_remove_index (self->pipeline, i - 1);
805           g_list_model_items_changed (G_LIST_MODEL (self), i - 1, 1, 0);
806         }
807     }
808 
809   IDE_EXIT;
810 }
811 
812 static gboolean
ide_pipeline_check_ready(IdePipeline * self,IdeTask * task)813 ide_pipeline_check_ready (IdePipeline *self,
814                           IdeTask     *task)
815 {
816   g_assert (IDE_IS_PIPELINE (self));
817   g_assert (IDE_IS_TASK (task));
818 
819   if (self->broken)
820     {
821       ide_task_return_new_error (task,
822                                  IDE_BUILD_ERROR,
823                                  IDE_BUILD_ERROR_BROKEN,
824                                  _("The build pipeline is in a failed state"));
825       return FALSE;
826     }
827 
828   if (self->loaded == FALSE)
829     {
830       /* configuration:ready is FALSE */
831       ide_task_return_new_error (task,
832                                  IDE_BUILD_ERROR,
833                                  IDE_BUILD_ERROR_NOT_LOADED,
834                                  _("The build configuration has errors"));
835       return FALSE;
836     }
837 
838   return TRUE;
839 }
840 
841 /**
842  * ide_pipeline_get_phase:
843  *
844  * Gets the current phase that is executing. This is only useful during
845  * execution of the pipeline.
846  *
847  * Since: 3.32
848  */
849 IdePipelinePhase
ide_pipeline_get_phase(IdePipeline * self)850 ide_pipeline_get_phase (IdePipeline *self)
851 {
852   g_return_val_if_fail (IDE_IS_PIPELINE (self), 0);
853 
854   if (self->position < 0)
855     return IDE_PIPELINE_PHASE_NONE;
856   else if (self->failed)
857     return IDE_PIPELINE_PHASE_FAILED;
858   else if ((guint)self->position < self->pipeline->len)
859     return g_array_index (self->pipeline, PipelineEntry, self->position).phase & IDE_PIPELINE_PHASE_MASK;
860   else
861     return IDE_PIPELINE_PHASE_FINISHED;
862 }
863 
864 /**
865  * ide_pipeline_get_config:
866  *
867  * Gets the #IdeConfig to use for the pipeline.
868  *
869  * Returns: (transfer none): An #IdeConfig
870  *
871  * Since: 3.32
872  */
873 IdeConfig *
ide_pipeline_get_config(IdePipeline * self)874 ide_pipeline_get_config (IdePipeline *self)
875 {
876   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
877 
878   return self->config;
879 }
880 
881 static void
clear_pipeline_entry(gpointer data)882 clear_pipeline_entry (gpointer data)
883 {
884   PipelineEntry *entry = data;
885 
886   if (entry->stage != NULL)
887     {
888       ide_pipeline_stage_set_log_observer (entry->stage, NULL, NULL, NULL);
889       g_clear_object (&entry->stage);
890     }
891 }
892 
893 static gint
pipeline_entry_compare(gconstpointer a,gconstpointer b)894 pipeline_entry_compare (gconstpointer a,
895                         gconstpointer b)
896 {
897   const PipelineEntry *entry_a = a;
898   const PipelineEntry *entry_b = b;
899   gint ret;
900 
901   ret = (gint)(entry_a->phase & IDE_PIPELINE_PHASE_MASK)
902       - (gint)(entry_b->phase & IDE_PIPELINE_PHASE_MASK);
903 
904   if (ret == 0)
905     {
906       gint whence_a = (entry_a->phase & IDE_PIPELINE_PHASE_WHENCE_MASK);
907       gint whence_b = (entry_b->phase & IDE_PIPELINE_PHASE_WHENCE_MASK);
908 
909       if (whence_a != whence_b)
910         {
911           if (whence_a == IDE_PIPELINE_PHASE_BEFORE)
912             return -1;
913 
914           if (whence_b == IDE_PIPELINE_PHASE_BEFORE)
915             return 1;
916 
917           if (whence_a == 0)
918             return -1;
919 
920           if (whence_b == 0)
921             return 1;
922 
923           g_assert_not_reached ();
924         }
925     }
926 
927   if (ret == 0)
928     ret = entry_a->priority - entry_b->priority;
929 
930   return ret;
931 }
932 
933 static void
ide_pipeline_real_started(IdePipeline * self)934 ide_pipeline_real_started (IdePipeline *self)
935 {
936   IDE_ENTRY;
937 
938   g_assert (IDE_IS_PIPELINE (self));
939 
940   self->errors_on_stdout = FALSE;
941 
942   for (guint i = 0; i < self->pipeline->len; i++)
943     {
944       PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
945 
946       if (ide_pipeline_stage_get_check_stdout (entry->stage))
947         {
948           self->errors_on_stdout = TRUE;
949           break;
950         }
951     }
952 
953   IDE_EXIT;
954 }
955 
956 static void
ide_pipeline_real_finished(IdePipeline * self,gboolean failed)957 ide_pipeline_real_finished (IdePipeline *self,
958                             gboolean     failed)
959 {
960   IDE_ENTRY;
961 
962   g_assert (IDE_IS_PIPELINE (self));
963 
964   IDE_EXIT;
965 }
966 
967 static void
ide_pipeline_extension_prepare(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)968 ide_pipeline_extension_prepare (IdeExtensionSetAdapter *set,
969                                 PeasPluginInfo         *plugin_info,
970                                 PeasExtension          *exten,
971                                 gpointer                user_data)
972 {
973   IdePipeline *self = user_data;
974   IdePipelineAddin *addin = (IdePipelineAddin *)exten;
975 
976   IDE_ENTRY;
977 
978   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
979   g_assert (plugin_info != NULL);
980   g_assert (IDE_IS_PIPELINE_ADDIN (addin));
981   g_assert (IDE_IS_PIPELINE (self));
982 
983   ide_pipeline_addin_prepare (addin, self);
984 
985   IDE_EXIT;
986 }
987 
988 static void
ide_pipeline_extension_added(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)989 ide_pipeline_extension_added (IdeExtensionSetAdapter *set,
990                               PeasPluginInfo         *plugin_info,
991                               PeasExtension          *exten,
992                               gpointer                user_data)
993 {
994   IdePipeline *self = user_data;
995   IdePipelineAddin *addin = (IdePipelineAddin *)exten;
996 
997   IDE_ENTRY;
998 
999   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
1000   g_assert (plugin_info != NULL);
1001   g_assert (IDE_IS_PIPELINE_ADDIN (addin));
1002   g_assert (IDE_IS_PIPELINE (self));
1003 
1004   /* Mark that we loaded this addin, so we don't unload it if it
1005    * was never loaded (during async loading).
1006    */
1007   g_object_set_data (G_OBJECT (addin), "HAS_LOADED", GINT_TO_POINTER (1));
1008 
1009   ide_pipeline_addin_load (addin, self);
1010 
1011   IDE_EXIT;
1012 }
1013 
1014 static void
ide_pipeline_extension_removed(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)1015 ide_pipeline_extension_removed (IdeExtensionSetAdapter *set,
1016                                 PeasPluginInfo         *plugin_info,
1017                                 PeasExtension          *exten,
1018                                 gpointer                user_data)
1019 {
1020   IdePipeline *self = user_data;
1021   IdePipelineAddin *addin = (IdePipelineAddin *)exten;
1022 
1023   IDE_ENTRY;
1024 
1025   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
1026   g_assert (plugin_info != NULL);
1027   g_assert (IDE_IS_PIPELINE_ADDIN (addin));
1028   g_assert (IDE_IS_PIPELINE (self));
1029 
1030   if (g_object_get_data (G_OBJECT (addin), "HAS_LOADED"))
1031     ide_pipeline_addin_unload (addin, self);
1032 
1033   IDE_EXIT;
1034 }
1035 
1036 static void
build_command_query_cb(IdePipelineStage * stage,IdePipeline * pipeline,GPtrArray * targets,GCancellable * cancellable,gpointer user_data)1037 build_command_query_cb (IdePipelineStage *stage,
1038                         IdePipeline      *pipeline,
1039                         GPtrArray        *targets,
1040                         GCancellable     *cancellable,
1041                         gpointer          user_data)
1042 {
1043   IDE_ENTRY;
1044 
1045   g_assert (IDE_IS_PIPELINE_STAGE_LAUNCHER (stage));
1046   g_assert (IDE_IS_PIPELINE (pipeline));
1047   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
1048   g_assert (user_data == NULL);
1049 
1050   ide_pipeline_stage_set_completed (stage, FALSE);
1051 
1052   IDE_EXIT;
1053 }
1054 
1055 static void
register_build_commands_stage(IdePipeline * self,IdeContext * context)1056 register_build_commands_stage (IdePipeline *self,
1057                                IdeContext  *context)
1058 {
1059   g_autoptr(GError) error = NULL;
1060   const gchar * const *build_commands;
1061   g_autofree gchar *rundir_path = NULL;
1062   GFile *rundir;
1063 
1064   g_assert (IDE_IS_PIPELINE (self));
1065   g_assert (IDE_IS_CONTEXT (context));
1066   g_assert (IDE_IS_CONFIG (self->config));
1067 
1068   if (!(build_commands = ide_config_get_build_commands (self->config)))
1069     return;
1070 
1071   if ((rundir = ide_config_get_build_commands_dir (self->config)))
1072     rundir_path = g_file_get_path (rundir);
1073 
1074   for (guint i = 0; build_commands[i]; i++)
1075     {
1076       g_autoptr(IdeSubprocessLauncher) launcher = NULL;
1077       g_autoptr(IdePipelineStage) stage = NULL;
1078 
1079       if (!(launcher = ide_pipeline_create_launcher (self, &error)))
1080         {
1081           g_warning ("%s", error->message);
1082           return;
1083         }
1084 
1085       /* Request deprecation warnings from the GLib stack by default */
1086       ide_subprocess_launcher_setenv (launcher, "G_ENABLE_DIAGNOSTIC", "1", FALSE);
1087 
1088       ide_subprocess_launcher_push_argv (launcher, "/bin/sh");
1089       ide_subprocess_launcher_push_argv (launcher, "-c");
1090       ide_subprocess_launcher_push_argv (launcher, build_commands[i]);
1091 
1092       if (rundir_path != NULL)
1093         ide_subprocess_launcher_set_cwd (launcher, rundir_path);
1094 
1095       stage = g_object_new (IDE_TYPE_PIPELINE_STAGE_LAUNCHER,
1096                             "launcher", launcher,
1097                             NULL);
1098       g_signal_connect (stage,
1099                         "query",
1100                         G_CALLBACK (build_command_query_cb),
1101                         NULL);
1102       ide_pipeline_attach (self,
1103                            IDE_PIPELINE_PHASE_BUILD | IDE_PIPELINE_PHASE_AFTER,
1104                            i,
1105                            stage);
1106     }
1107 }
1108 
1109 static void
register_post_install_commands_stage(IdePipeline * self,IdeContext * context)1110 register_post_install_commands_stage (IdePipeline *self,
1111                                       IdeContext  *context)
1112 {
1113   g_autoptr(GError) error = NULL;
1114   const gchar * const *post_install_commands;
1115 
1116   g_assert (IDE_IS_PIPELINE (self));
1117   g_assert (IDE_IS_CONTEXT (context));
1118   g_assert (IDE_IS_CONFIG (self->config));
1119 
1120   if (!(post_install_commands = ide_config_get_post_install_commands (self->config)))
1121     return;
1122 
1123   for (guint i = 0; post_install_commands[i]; i++)
1124     {
1125       g_autoptr(IdeSubprocessLauncher) launcher = NULL;
1126       g_autoptr(IdePipelineStage) stage = NULL;
1127 
1128       if (!(launcher = ide_pipeline_create_launcher (self, &error)))
1129         {
1130           ide_object_warning (self, "%s", error->message);
1131           return;
1132         }
1133 
1134       ide_subprocess_launcher_push_argv (launcher, "/bin/sh");
1135       ide_subprocess_launcher_push_argv (launcher, "-c");
1136       ide_subprocess_launcher_push_argv (launcher, post_install_commands[i]);
1137 
1138       stage = g_object_new (IDE_TYPE_PIPELINE_STAGE_LAUNCHER,
1139                             "launcher", launcher,
1140                             NULL);
1141       g_signal_connect (stage,
1142                         "query",
1143                         G_CALLBACK (build_command_query_cb),
1144                         NULL);
1145       ide_pipeline_attach (self,
1146                            IDE_PIPELINE_PHASE_INSTALL | IDE_PIPELINE_PHASE_AFTER,
1147                            i,
1148                            stage);
1149     }
1150 }
1151 
1152 static void
collect_pipeline_addins(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)1153 collect_pipeline_addins (IdeExtensionSetAdapter *set,
1154                          PeasPluginInfo         *plugin_info,
1155                          PeasExtension          *exten,
1156                          gpointer                user_data)
1157 {
1158   GPtrArray *addins = user_data;
1159 
1160   g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
1161   g_assert (plugin_info != NULL);
1162   g_assert (IDE_IS_PIPELINE_ADDIN (exten));
1163   g_assert (addins != NULL);
1164 
1165   g_ptr_array_add (addins, g_object_ref (exten));
1166 }
1167 
1168 static gboolean
ide_pipeline_load_cb(IdleLoadState * state)1169 ide_pipeline_load_cb (IdleLoadState *state)
1170 {
1171   g_assert (state != NULL);
1172   g_assert (IDE_IS_PIPELINE (state->self));
1173   g_assert (state->addins != NULL);
1174 
1175   /*
1176    * We only load a single addin per idle callback so that we can return to
1177    * the main loop and potentially start the next frame at a higher priority
1178    * than the addin loading.
1179    */
1180 
1181   if (state->addins->len > 0)
1182     {
1183       IdePipelineAddin *addin = g_ptr_array_index (state->addins, state->addins->len - 1);
1184       gint64 begin, end;
1185 
1186       /* Keep in sync with ide_pipeline_extension_added() */
1187       g_object_set_data (G_OBJECT (addin), "HAS_LOADED", GINT_TO_POINTER (1));
1188 
1189       begin = g_get_monotonic_time ();
1190       ide_pipeline_addin_load (addin, state->self);
1191       end = g_get_monotonic_time ();
1192 
1193       g_debug ("%s loaded in %lf seconds",
1194                G_OBJECT_TYPE_NAME (addin),
1195                (end - begin) / (gdouble)G_USEC_PER_SEC);
1196 
1197       g_ptr_array_remove_index (state->addins, state->addins->len - 1);
1198 
1199       if (state->addins->len > 0)
1200         return G_SOURCE_CONTINUE;
1201     }
1202 
1203   state->self->loaded = TRUE;
1204   state->self->idle_addins_load_source = 0;
1205 
1206   IDE_TRACE_MSG ("Pipeline ready");
1207 
1208   g_signal_emit (state->self, signals [LOADED], 0);
1209 
1210   return G_SOURCE_REMOVE;
1211 }
1212 
1213 /**
1214  * ide_pipeline_load:
1215  *
1216  * This manages the loading of addins which will register their necessary build
1217  * stages.  We do this separately from ::constructed so that we can
1218  * enable/disable the pipeline as the IdeConfig:ready property changes.
1219  * This could happen when the device or runtime is added/removed while the
1220  * application is running.
1221  *
1222  * Since: 3.32
1223  */
1224 static void
ide_pipeline_load(IdePipeline * self)1225 ide_pipeline_load (IdePipeline *self)
1226 {
1227   g_autoptr(GPtrArray) addins = NULL;
1228   IdleLoadState *state;
1229   IdeContext *context;
1230 
1231   IDE_ENTRY;
1232 
1233   g_assert (IDE_IS_PIPELINE (self));
1234   g_assert (self->addins == NULL);
1235 
1236   /* We might have already disposed if our pipeline got discarded */
1237   if (!(context = ide_object_get_context (IDE_OBJECT (self))))
1238     return;
1239 
1240   register_build_commands_stage (self, context);
1241   register_post_install_commands_stage (self, context);
1242 
1243   self->addins = ide_extension_set_adapter_new (IDE_OBJECT (self),
1244                                                 peas_engine_get_default (),
1245                                                 IDE_TYPE_PIPELINE_ADDIN,
1246                                                 NULL, NULL);
1247 
1248   g_signal_connect (self->addins,
1249                     "extension-added",
1250                     G_CALLBACK (ide_pipeline_extension_prepare),
1251                     self);
1252 
1253   ide_extension_set_adapter_foreach (self->addins,
1254                                      ide_pipeline_extension_prepare,
1255                                      self);
1256 
1257   g_signal_connect_after (self->addins,
1258                           "extension-added",
1259                           G_CALLBACK (ide_pipeline_extension_added),
1260                           self);
1261 
1262   g_signal_connect (self->addins,
1263                     "extension-removed",
1264                     G_CALLBACK (ide_pipeline_extension_removed),
1265                     self);
1266 
1267   /* Collect our addins so we can incrementally load them in an
1268    * idle callback to reduce chances of stalling the main loop.
1269    */
1270   addins = g_ptr_array_new_with_free_func (g_object_unref);
1271   ide_extension_set_adapter_foreach (self->addins,
1272                                      collect_pipeline_addins,
1273                                      addins);
1274 
1275   state = g_slice_new0 (IdleLoadState);
1276   state->self = g_object_ref (self);
1277   state->addins = g_steal_pointer (&addins);
1278 
1279   self->idle_addins_load_source =
1280     g_idle_add_full (G_PRIORITY_LOW,
1281                      (GSourceFunc) ide_pipeline_load_cb,
1282                      state,
1283                      idle_load_state_free);
1284 
1285   IDE_EXIT;
1286 }
1287 
1288 static void
ide_pipeline_load_get_info_cb(GObject * object,GAsyncResult * result,gpointer user_data)1289 ide_pipeline_load_get_info_cb (GObject      *object,
1290                                GAsyncResult *result,
1291                                gpointer      user_data)
1292 {
1293   IdeDevice *device = (IdeDevice *)object;
1294   g_autoptr(IdePipeline) self = user_data;
1295   g_autoptr(IdeDeviceInfo) info = NULL;
1296   g_autoptr(GError) error = NULL;
1297 
1298   IDE_ENTRY;
1299 
1300   g_assert (IDE_IS_DEVICE (device));
1301   g_assert (G_IS_ASYNC_RESULT (result));
1302   g_assert (IDE_IS_PIPELINE (self));
1303 
1304   if (!(info = ide_device_get_info_finish (device, result, &error)))
1305     {
1306       g_warning ("Failed to get device information: %s", error->message);
1307       IDE_EXIT;
1308     }
1309 
1310   if (g_cancellable_is_cancelled (self->cancellable))
1311     IDE_EXIT;
1312 
1313   _ide_pipeline_check_toolchain (self, info);
1314 
1315   ide_pipeline_load (self);
1316 }
1317 
1318 static void
ide_pipeline_begin_load(IdePipeline * self)1319 ide_pipeline_begin_load (IdePipeline *self)
1320 {
1321   IDE_ENTRY;
1322 
1323   g_assert (IDE_IS_PIPELINE (self));
1324   g_assert (IDE_IS_DEVICE (self->device));
1325 
1326   /*
1327    * The first thing we need to do is get some information from the
1328    * configured device. We want to know the arch/kernel/system triplet
1329    * for the device as some pipeline addins may need that. We can also
1330    * use that to ensure that we load the proper runtime and toolchain
1331    * for the device.
1332    *
1333    * We have to load this information asynchronously, as the device might
1334    * be remote (and we need to connect to it to get the information).
1335    */
1336 
1337   ide_device_get_info_async (self->device,
1338                              self->cancellable,
1339                              ide_pipeline_load_get_info_cb,
1340                              g_object_ref (self));
1341 
1342   IDE_EXIT;
1343 }
1344 
1345 /**
1346  * ide_pipeline_unload:
1347  * @self: an #IdePipeline
1348  *
1349  * This clears things up that were initialized in ide_pipeline_load().
1350  * This function is safe to run even if load has not been called. We will not
1351  * clean things up if the pipeline is currently executing (we can wait until
1352  * its finished or dispose/finalize to cleanup up further.
1353  *
1354  * Since: 3.32
1355  */
1356 static void
ide_pipeline_unload(IdePipeline * self)1357 ide_pipeline_unload (IdePipeline *self)
1358 {
1359   IDE_ENTRY;
1360 
1361   g_assert (IDE_IS_PIPELINE (self));
1362 
1363   ide_clear_and_destroy_object (&self->addins);
1364 
1365   IDE_EXIT;
1366 }
1367 
1368 static void
ide_pipeline_notify_ready(IdePipeline * self,GParamSpec * pspec,IdeConfig * configuration)1369 ide_pipeline_notify_ready (IdePipeline *self,
1370                            GParamSpec  *pspec,
1371                            IdeConfig   *configuration)
1372 {
1373   IDE_ENTRY;
1374 
1375   g_assert (IDE_IS_PIPELINE (self));
1376   g_assert (IDE_IS_CONFIG (configuration));
1377 
1378   /*
1379    * If we're being realistic, we can only really setup the build pipeline one
1380    * time, once the configuration is ready. So cancel all tracking after that
1381    * so that and just rely on the build manager to create a new pipeline when
1382    * the active configuration changes.
1383    */
1384 
1385   if (ide_config_get_ready (configuration))
1386     {
1387       g_signal_handlers_disconnect_by_func (configuration,
1388                                             G_CALLBACK (ide_pipeline_notify_ready),
1389                                             self);
1390       ide_pipeline_begin_load (self);
1391     }
1392   else
1393     g_debug ("Configuration not yet ready, delaying pipeline setup");
1394 
1395   IDE_EXIT;
1396 }
1397 
1398 static void
ide_pipeline_finalize(GObject * object)1399 ide_pipeline_finalize (GObject *object)
1400 {
1401   IdePipeline *self = (IdePipeline *)object;
1402 
1403   IDE_ENTRY;
1404 
1405   g_assert (self->task_queue.length == 0);
1406   g_queue_clear (&self->task_queue);
1407 
1408   g_clear_object (&self->cancellable);
1409   g_clear_object (&self->log);
1410   g_clear_object (&self->device);
1411   g_clear_object (&self->device_info);
1412   g_clear_object (&self->runtime);
1413   g_clear_object (&self->toolchain);
1414   g_clear_object (&self->config);
1415   g_clear_pointer (&self->pipeline, g_array_unref);
1416   g_clear_pointer (&self->srcdir, g_free);
1417   g_clear_pointer (&self->builddir, g_free);
1418   g_clear_pointer (&self->errfmts, g_array_unref);
1419   g_clear_pointer (&self->errfmt_top_dir, g_free);
1420   g_clear_pointer (&self->errfmt_current_dir, g_free);
1421   g_clear_pointer (&self->chained_bindings, g_ptr_array_unref);
1422   g_clear_pointer (&self->host_triplet, ide_triplet_unref);
1423 
1424   G_OBJECT_CLASS (ide_pipeline_parent_class)->finalize (object);
1425 
1426   DZL_COUNTER_DEC (Instances);
1427 
1428   IDE_EXIT;
1429 }
1430 
1431 static void
ide_pipeline_destroy(IdeObject * object)1432 ide_pipeline_destroy (IdeObject *object)
1433 {
1434   IdePipeline *self = IDE_PIPELINE (object);
1435   g_auto(IdePtyFd) fd = IDE_PTY_FD_INVALID;
1436 
1437   IDE_ENTRY;
1438 
1439   g_clear_handle_id (&self->idle_addins_load_source, g_source_remove);
1440 
1441   _ide_pipeline_cancel (self);
1442 
1443   ide_pipeline_unload (self);
1444 
1445   g_clear_pointer (&self->message, g_free);
1446 
1447   g_clear_object (&self->pty);
1448   fd = pty_fd_steal (&self->pty_slave);
1449 
1450   if (IDE_IS_PTY_INTERCEPT (&self->intercept))
1451     ide_pty_intercept_clear (&self->intercept);
1452 
1453   IDE_OBJECT_CLASS (ide_pipeline_parent_class)->destroy (object);
1454 
1455   IDE_EXIT;
1456 }
1457 
1458 static gboolean
ide_pipeline_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)1459 ide_pipeline_initable_init (GInitable     *initable,
1460                             GCancellable  *cancellable,
1461                             GError       **error)
1462 {
1463   IdePipeline *self = (IdePipeline *)initable;
1464   IdePtyFd master_fd;
1465 
1466   IDE_ENTRY;
1467 
1468   g_assert (IDE_IS_PIPELINE (self));
1469   g_assert (IDE_IS_CONFIG (self->config));
1470   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
1471 
1472   g_debug ("initializing build pipeline with device %s",
1473            G_OBJECT_TYPE_NAME (self->device));
1474 
1475   if (self->runtime == NULL)
1476     {
1477       g_set_error_literal (error,
1478                            G_IO_ERROR,
1479                            G_IO_ERROR_FAILED,
1480                            "No runtime assigned to build pipeline");
1481       IDE_RETURN (FALSE);
1482     }
1483 
1484   /*
1485    * Create a PTY for subprocess launchers. PTY initialization does not
1486    * support cancellation, so do not pass @cancellable along to it.
1487    */
1488   self->pty = vte_pty_new_sync (VTE_PTY_DEFAULT, NULL, error);
1489   if (self->pty == NULL)
1490     IDE_RETURN (FALSE);
1491 
1492   vte_pty_set_utf8 (self->pty, TRUE, NULL);
1493 
1494   master_fd = vte_pty_get_fd (self->pty);
1495 
1496   if (!ide_pty_intercept_init (&self->intercept, master_fd, NULL))
1497     {
1498       g_set_error_literal (error,
1499                            G_IO_ERROR,
1500                            G_IO_ERROR_FAILED,
1501                            "Failed to initialize PTY intercept");
1502       IDE_RETURN (FALSE);
1503     }
1504 
1505   ide_pty_intercept_set_callback (&self->intercept,
1506                               &self->intercept.master,
1507                               ide_pipeline_intercept_pty_master_cb,
1508                               self);
1509 
1510   g_signal_connect_object (self->config,
1511                            "notify::ready",
1512                            G_CALLBACK (ide_pipeline_notify_ready),
1513                            self,
1514                            G_CONNECT_SWAPPED);
1515 
1516   ide_pipeline_notify_ready (self, NULL, self->config);
1517 
1518   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PTY]);
1519 
1520   IDE_RETURN (TRUE);
1521 }
1522 
1523 static void
initable_iface_init(GInitableIface * iface)1524 initable_iface_init (GInitableIface *iface)
1525 {
1526   iface->init = ide_pipeline_initable_init;
1527 }
1528 
1529 static void
ide_pipeline_parent_set(IdeObject * object,IdeObject * parent)1530 ide_pipeline_parent_set (IdeObject *object,
1531                          IdeObject *parent)
1532 {
1533   IdePipeline *self = IDE_PIPELINE (object);
1534   IdeToolchainManager *toolchain_manager;
1535   g_autoptr(IdeContext) context = NULL;
1536   g_autoptr(GFile) workdir = NULL;
1537 
1538   IDE_ENTRY;
1539 
1540   g_assert (IDE_IS_PIPELINE (self));
1541   g_assert (!parent || IDE_IS_OBJECT (parent));
1542   g_assert (IDE_IS_CONFIG (self->config));
1543 
1544   if (parent == NULL)
1545     return;
1546 
1547   context = IDE_CONTEXT (ide_object_ref_root (IDE_OBJECT (self)));
1548   workdir = ide_context_ref_workdir (context);
1549 
1550   self->srcdir = g_file_get_path (workdir);
1551 
1552   toolchain_manager = ide_toolchain_manager_from_context (context);
1553   self->toolchain = ide_toolchain_manager_get_toolchain (toolchain_manager, "default");
1554 
1555   IDE_EXIT;
1556 }
1557 
1558 static void
ide_pipeline_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1559 ide_pipeline_get_property (GObject    *object,
1560                            guint       prop_id,
1561                            GValue     *value,
1562                            GParamSpec *pspec)
1563 {
1564   IdePipeline *self = IDE_PIPELINE (object);
1565 
1566   switch (prop_id)
1567     {
1568     case PROP_BUSY:
1569       g_value_set_boolean (value, self->busy);
1570       break;
1571 
1572     case PROP_CONFIG:
1573       g_value_set_object (value, ide_pipeline_get_config (self));
1574       break;
1575 
1576     case PROP_MESSAGE:
1577       g_value_set_string (value, ide_pipeline_get_message (self));
1578       break;
1579 
1580     case PROP_PHASE:
1581       g_value_set_flags (value, ide_pipeline_get_phase (self));
1582       break;
1583 
1584     case PROP_PTY:
1585       g_value_set_object (value, ide_pipeline_get_pty (self));
1586       break;
1587 
1588     default:
1589       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1590     }
1591 }
1592 
1593 static void
ide_pipeline_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1594 ide_pipeline_set_property (GObject      *object,
1595                            guint         prop_id,
1596                            const GValue *value,
1597                            GParamSpec   *pspec)
1598 {
1599   IdePipeline *self = IDE_PIPELINE (object);
1600 
1601   switch (prop_id)
1602     {
1603     case PROP_CONFIG:
1604       self->config = g_value_dup_object (value);
1605       break;
1606 
1607     case PROP_DEVICE:
1608       self->device = g_value_dup_object (value);
1609       break;
1610 
1611     default:
1612       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1613     }
1614 }
1615 
1616 static void
ide_pipeline_class_init(IdePipelineClass * klass)1617 ide_pipeline_class_init (IdePipelineClass *klass)
1618 {
1619   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1620   IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
1621 
1622   object_class->finalize = ide_pipeline_finalize;
1623   object_class->get_property = ide_pipeline_get_property;
1624   object_class->set_property = ide_pipeline_set_property;
1625 
1626   i_object_class->destroy = ide_pipeline_destroy;
1627   i_object_class->parent_set = ide_pipeline_parent_set;
1628 
1629   /**
1630    * IdePipeline:busy:
1631    *
1632    * Gets the "busy" property. If %TRUE, the pipeline is busy executing.
1633    *
1634    * Since: 3.32
1635    */
1636   properties [PROP_BUSY] =
1637     g_param_spec_boolean ("busy",
1638                           "Busy",
1639                           "If the pipeline is busy",
1640                           FALSE,
1641                           (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1642 
1643   /**
1644    * IdePipeline:configuration:
1645    *
1646    * The configuration to use for the build pipeline.
1647    *
1648    * Since: 3.32
1649    */
1650   properties [PROP_CONFIG] =
1651     g_param_spec_object ("config",
1652                          "Configuration",
1653                          "Configuration",
1654                          IDE_TYPE_CONFIG,
1655                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1656 
1657   /**
1658    * IdePipeline:device:
1659    *
1660    * The "device" property is the device we are compiling for.
1661    *
1662    * Since: 3.32
1663    */
1664   properties [PROP_DEVICE] =
1665     g_param_spec_object ("device",
1666                          "Device",
1667                          "The device we are building for",
1668                          IDE_TYPE_DEVICE,
1669                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1670 
1671   /**
1672    * IdePipeline:message:
1673    *
1674    * The "message" property is descriptive text about what the the
1675    * pipeline is doing or it's readiness status.
1676    *
1677    * Since: 3.32
1678    */
1679   properties [PROP_MESSAGE] =
1680     g_param_spec_string ("message",
1681                          "Message",
1682                          "The message for the build phase",
1683                          NULL,
1684                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1685 
1686   /**
1687    * IdePipeline:phase:
1688    *
1689    * The current build phase during execution of the pipeline.
1690    *
1691    * Since: 3.32
1692    */
1693   properties [PROP_PHASE] =
1694     g_param_spec_flags ("phase",
1695                         "Phase",
1696                         "The phase that is being buildd",
1697                         IDE_TYPE_PIPELINE_PHASE,
1698                         IDE_PIPELINE_PHASE_NONE,
1699                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1700 
1701   /**
1702    * IdePipeline:pty:
1703    *
1704    * The "pty" property is the #VtePty that is used by build stages that
1705    * build subprocesses with a pseudo terminal.
1706    *
1707    * Since: 3.32
1708    */
1709   properties [PROP_PTY] =
1710     g_param_spec_object ("pty",
1711                          "PTY",
1712                          "The PTY used by the pipeline",
1713                          VTE_TYPE_PTY,
1714                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1715 
1716   g_object_class_install_properties (object_class, N_PROPS, properties);
1717 
1718   /**
1719    * IdePipeline::diagnostic:
1720    * @self: An #IdePipeline
1721    * @diagnostic: The newly created diagnostic
1722    *
1723    * This signal is emitted when a plugin has detected a diagnostic while
1724    * building the pipeline.
1725    *
1726    * Since: 3.32
1727    */
1728   signals [DIAGNOSTIC] =
1729     g_signal_new_class_handler ("diagnostic",
1730                                 G_TYPE_FROM_CLASS (klass),
1731                                 G_SIGNAL_RUN_LAST,
1732                                 NULL, NULL, NULL, NULL,
1733                                 G_TYPE_NONE, 1, IDE_TYPE_DIAGNOSTIC);
1734 
1735   /**
1736    * IdePipeline::started:
1737    * @self: An #IdePipeline
1738    * @phase: the #IdePipelinePhase for which we are advancing
1739    *
1740    * This signal is emitted when the pipeline has started executing in
1741    * response to ide_pipeline_build_async() being called.
1742    *
1743    * Since: 3.32
1744    */
1745   signals [STARTED] =
1746     g_signal_new_class_handler ("started",
1747                                 G_TYPE_FROM_CLASS (klass),
1748                                 G_SIGNAL_RUN_LAST,
1749                                 G_CALLBACK (ide_pipeline_real_started),
1750                                 NULL, NULL, NULL,
1751                                 G_TYPE_NONE, 1, IDE_TYPE_PIPELINE_PHASE);
1752 
1753   /**
1754    * IdePipeline::finished:
1755    * @self: An #IdePipeline
1756    * @failed: If the build was a failure
1757    *
1758    * This signal is emitted when the build process has finished executing.
1759    * If the build failed to complete all requested stages, then @failed will
1760    * be set to %TRUE, otherwise %FALSE.
1761    *
1762    * Since: 3.32
1763    */
1764   signals [FINISHED] =
1765     g_signal_new_class_handler ("finished",
1766                                 G_TYPE_FROM_CLASS (klass),
1767                                 G_SIGNAL_RUN_LAST,
1768                                 G_CALLBACK (ide_pipeline_real_finished),
1769                                 NULL, NULL, NULL,
1770                                 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
1771 
1772   /**
1773    * IdePipeline::loaded:
1774    *
1775    * The "loaded" signal is emitted after the pipeline has finished
1776    * loading addins.
1777    *
1778    * Since: 3.32
1779    */
1780   signals [LOADED] =
1781     g_signal_new_class_handler ("loaded",
1782                                 G_TYPE_FROM_CLASS (klass),
1783                                 G_SIGNAL_RUN_LAST,
1784                                 NULL, NULL, NULL, NULL,
1785                                 G_TYPE_NONE, 0);
1786 
1787   /**
1788    * IdePipeline::launcher-created:
1789    * @self: an #IdePipeline
1790    * @launcher: an #IdeSubprocessLauncher
1791    *
1792    * The "launcher-created" signal is emitted when a new
1793    * #IdeSubprocessLauncher is created by the pipeline. This may be useful
1794    * to plugins that wan to modify the launcher in a consistent way for all
1795    * pipeline consumers.
1796    *
1797    * Since: 3.34
1798    */
1799   signals [LAUNCHER_CREATED] =
1800     g_signal_new_class_handler ("launcher-created",
1801                                 G_TYPE_FROM_CLASS (klass),
1802                                 G_SIGNAL_RUN_LAST,
1803                                 NULL, NULL, NULL,
1804                                 g_cclosure_marshal_VOID__OBJECT,
1805                                 G_TYPE_NONE, 1, IDE_TYPE_SUBPROCESS_LAUNCHER);
1806   g_signal_set_va_marshaller (signals [LAUNCHER_CREATED],
1807                               G_TYPE_FROM_CLASS (klass),
1808                               g_cclosure_marshal_VOID__OBJECTv);
1809 }
1810 
1811 static void
ide_pipeline_init(IdePipeline * self)1812 ide_pipeline_init (IdePipeline *self)
1813 {
1814   DZL_COUNTER_INC (Instances);
1815 
1816   self->cancellable = g_cancellable_new ();
1817 
1818   self->position = -1;
1819   self->pty_slave = -1;
1820 
1821   self->pipeline = g_array_new (FALSE, FALSE, sizeof (PipelineEntry));
1822   g_array_set_clear_func (self->pipeline, clear_pipeline_entry);
1823 
1824   self->errfmts = g_array_new (FALSE, FALSE, sizeof (ErrorFormat));
1825   g_array_set_clear_func (self->errfmts, clear_error_format);
1826 
1827   self->chained_bindings = g_ptr_array_new_with_free_func ((GDestroyNotify)chained_binding_clear);
1828 
1829   self->log = ide_build_log_new ();
1830 }
1831 
1832 static void
ide_pipeline_stage_build_cb(GObject * object,GAsyncResult * result,gpointer user_data)1833 ide_pipeline_stage_build_cb (GObject      *object,
1834                                      GAsyncResult *result,
1835                                      gpointer      user_data)
1836 {
1837   IdePipelineStage *stage = (IdePipelineStage *)object;
1838   IdePipeline *self;
1839   g_autoptr(IdeTask) task = user_data;
1840   g_autoptr(GError) error = NULL;
1841 
1842   IDE_ENTRY;
1843 
1844   g_assert (IDE_IS_PIPELINE_STAGE (stage));
1845   g_assert (G_IS_ASYNC_RESULT (result));
1846   g_assert (IDE_IS_TASK (task));
1847 
1848   self = ide_task_get_source_object (task);
1849   g_assert (IDE_IS_PIPELINE (self));
1850 
1851   if (!_ide_pipeline_stage_build_with_query_finish (stage, result, &error))
1852     {
1853       g_debug ("stage of type %s failed: %s",
1854                G_OBJECT_TYPE_NAME (stage),
1855                error->message);
1856       self->failed = TRUE;
1857       ide_task_return_error (task, g_steal_pointer (&error));
1858     }
1859 
1860   ide_pipeline_stage_set_completed (stage, !self->failed);
1861 
1862   g_clear_pointer (&self->chained_bindings, g_ptr_array_unref);
1863   self->chained_bindings = g_ptr_array_new_with_free_func (g_object_unref);
1864 
1865   if (self->failed == FALSE)
1866     ide_pipeline_tick_build (self, task);
1867 
1868   IDE_EXIT;
1869 }
1870 
1871 static void
ide_pipeline_try_chain(IdePipeline * self,IdePipelineStage * stage,guint position)1872 ide_pipeline_try_chain (IdePipeline      *self,
1873                         IdePipelineStage *stage,
1874                         guint             position)
1875 {
1876   g_assert (IDE_IS_PIPELINE (self));
1877   g_assert (IDE_IS_PIPELINE_STAGE (stage));
1878 
1879   for (; position < self->pipeline->len; position++)
1880     {
1881       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, position);
1882       gboolean chained;
1883       GBinding *chained_binding;
1884 
1885       /*
1886        * Ignore all future stages if they were not requested by the current
1887        * pipeline execution.
1888        */
1889       if (((entry->phase & IDE_PIPELINE_PHASE_MASK) & self->requested_mask) == 0)
1890         return;
1891 
1892       /* Skip past the stage if it is disabled. */
1893       if (ide_pipeline_stage_get_disabled (entry->stage))
1894         continue;
1895 
1896       chained = ide_pipeline_stage_chain (stage, entry->stage);
1897 
1898       IDE_TRACE_MSG ("Checking if %s chains to stage[%d] (%s) = %s",
1899                      G_OBJECT_TYPE_NAME (stage),
1900                      position,
1901                      G_OBJECT_TYPE_NAME (entry->stage),
1902                      chained ? "yes" : "no");
1903 
1904       if (!chained)
1905         return;
1906 
1907       chained_binding = g_object_bind_property (stage, "completed", entry->stage, "completed", 0);
1908       g_ptr_array_add (self->chained_bindings, g_object_ref (chained_binding));
1909 
1910       self->position = position;
1911     }
1912 }
1913 
1914 static void
complete_queued_before_phase(IdePipeline * self,IdePipelinePhase phase)1915 complete_queued_before_phase (IdePipeline      *self,
1916                               IdePipelinePhase  phase)
1917 {
1918   g_assert (IDE_IS_PIPELINE (self));
1919 
1920   phase = phase & IDE_PIPELINE_PHASE_MASK;
1921 
1922   for (GList *iter = self->task_queue.head; iter; iter = iter->next)
1923     {
1924       IdeTask *task;
1925       TaskData *task_data;
1926 
1927     again:
1928       task = iter->data;
1929       task_data = ide_task_get_task_data (task);
1930 
1931       g_assert (IDE_IS_TASK (task));
1932       g_assert (task_data->task == task);
1933 
1934       /*
1935        * If this task has a phase that is less-than the phase given
1936        * to us, we can complete the task immediately.
1937        */
1938       if (task_data->phase < phase)
1939         {
1940           GList *to_remove = iter;
1941 
1942           iter = iter->next;
1943           g_queue_delete_link (&self->task_queue, to_remove);
1944           ide_task_return_boolean (task, TRUE);
1945           g_object_unref (task);
1946 
1947           if (iter == NULL)
1948             break;
1949 
1950           goto again;
1951         }
1952     }
1953 }
1954 
1955 static void
ide_pipeline_tick_build(IdePipeline * self,IdeTask * task)1956 ide_pipeline_tick_build (IdePipeline *self,
1957                          IdeTask     *task)
1958 {
1959   GCancellable *cancellable;
1960   TaskData *td;
1961 
1962   IDE_ENTRY;
1963 
1964   g_assert (IDE_IS_PIPELINE (self));
1965   g_assert (IDE_IS_TASK (task));
1966 
1967   self->current_stage = NULL;
1968 
1969   td = ide_task_get_task_data (task);
1970   cancellable = ide_task_get_cancellable (task);
1971 
1972   g_assert (td != NULL);
1973   g_assert (td->type == TASK_BUILD || td->type == TASK_REBUILD);
1974   g_assert (td->task == task);
1975   g_assert (td->phase != IDE_PIPELINE_PHASE_NONE);
1976   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
1977 
1978   /* Clear any message from the previous stage */
1979   _ide_pipeline_set_message (self, NULL);
1980 
1981   /* Clear cached directory enter/leave tracking */
1982   g_clear_pointer (&self->errfmt_current_dir, g_free);
1983   g_clear_pointer (&self->errfmt_top_dir, g_free);
1984 
1985   /* Short circuit now if the task was cancelled */
1986   if (ide_task_return_error_if_cancelled (task))
1987     IDE_EXIT;
1988 
1989   /* If we can skip walking the pipeline, go ahead and do so now. */
1990   if (!ide_pipeline_request_phase (self, td->phase))
1991     {
1992       ide_task_return_boolean (task, TRUE);
1993       IDE_EXIT;
1994     }
1995 
1996   /*
1997    * Walk forward to the next stage requiring execution and asynchronously
1998    * build it. The stage may also need to perform an async ::query signal
1999    * delaying pipeline execution. _ide_pipeline_stage_build_with_query_async()
2000    * will handle all of that for us, in cause they call ide_pipeline_stage_pause()
2001    * during the ::query callback.
2002    */
2003   for (self->position++; (guint)self->position < self->pipeline->len; self->position++)
2004     {
2005       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, self->position);
2006 
2007       g_assert (entry->stage != NULL);
2008       g_assert (IDE_IS_PIPELINE_STAGE (entry->stage));
2009 
2010       /* Complete any tasks that are waiting for this to complete */
2011       complete_queued_before_phase (self, entry->phase);
2012 
2013       /* Ignore the stage if it is disabled */
2014       if (ide_pipeline_stage_get_disabled (entry->stage))
2015         continue;
2016 
2017       if ((entry->phase & IDE_PIPELINE_PHASE_MASK) & self->requested_mask)
2018         {
2019           GPtrArray *targets = NULL;
2020 
2021           self->current_stage = entry->stage;
2022 
2023           if (td->type == TASK_BUILD)
2024             targets = td->build.targets;
2025           else if (td->type == TASK_REBUILD)
2026             targets = td->rebuild.targets;
2027 
2028           /*
2029            * We might be able to chain upcoming stages to this stage and avoid
2030            * duplicate work. This will also advance self->position based on
2031            * how many stages were chained.
2032            */
2033           ide_pipeline_try_chain (self, entry->stage, self->position + 1);
2034 
2035           _ide_pipeline_stage_build_with_query_async (entry->stage,
2036                                                      self,
2037                                                      targets,
2038                                                      cancellable,
2039                                                      ide_pipeline_stage_build_cb,
2040                                                      g_object_ref (task));
2041 
2042           g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
2043           g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PHASE]);
2044 
2045           IDE_EXIT;
2046         }
2047     }
2048 
2049   ide_task_return_boolean (task, TRUE);
2050 
2051   IDE_EXIT;
2052 }
2053 
2054 static void
ide_pipeline_task_notify_completed(IdePipeline * self,GParamSpec * pspec,IdeTask * task)2055 ide_pipeline_task_notify_completed (IdePipeline *self,
2056                                     GParamSpec  *pspec,
2057                                     IdeTask     *task)
2058 {
2059   IDE_ENTRY;
2060 
2061   g_assert (IDE_IS_PIPELINE (self));
2062   g_assert (IDE_IS_TASK (task));
2063 
2064   IDE_TRACE_MSG ("Clearing busy bit for pipeline");
2065 
2066   self->current_stage = NULL;
2067   self->busy = FALSE;
2068   self->requested_mask = 0;
2069   self->in_clean = FALSE;
2070 
2071   g_clear_pointer (&self->message, g_free);
2072   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
2073 
2074   /*
2075    * XXX: How do we ensure transients are built with the part of the
2076    *      pipeline we care about? We might just need to ensure that :busy is
2077    *      FALSE before adding transients.
2078    */
2079   ide_pipeline_release_transients (self);
2080 
2081   g_signal_emit (self, signals [FINISHED], 0, self->failed);
2082 
2083   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
2084   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PHASE]);
2085 
2086   /*
2087    * We might have a delayed addin unloading that needs to occur after the
2088    * build operation completes. If the configuration is no longer valid,
2089    * go ahead and unload the pipeline.
2090    */
2091   if (!ide_config_get_ready (self->config))
2092     ide_pipeline_unload (self);
2093   else
2094     ide_pipeline_queue_flush (self);
2095 
2096   IDE_EXIT;
2097 }
2098 
2099 /**
2100  * ide_pipeline_build_targets_async:
2101  * @self: A @IdePipeline
2102  * @phase: the requested build phase
2103  * @targets: (nullable) (element-type IdeBuildTarget): an optional array of
2104  *   #IdeBuildTarget for the pipeline to build.
2105  * @cancellable: (nullable): a #GCancellable or %NULL
2106  * @callback: a callback to build upon completion
2107  * @user_data: data for @callback
2108  *
2109  * Asynchronously starts the build pipeline.
2110  *
2111  * The @phase parameter should contain the #IdePipelinePhase that is
2112  * necessary to complete. If you simply want to trigger a generic
2113  * build, you probably want %IDE_PIPELINE_PHASE_BUILD. If you only
2114  * need to configure the project (and necessarily the dependencies
2115  * up to that phase) you might want %IDE_PIPELINE_PHASE_CONFIGURE.
2116  *
2117  * You may not specify %IDE_PIPELINE_PHASE_AFTER or
2118  * %IDE_PIPELINE_PHASE_BEFORE flags as those must always be processed
2119  * with the underlying phase they are attached to.
2120  *
2121  * Upon completion, @callback will be buildd and should call
2122  * ide_pipeline_build_finish() to get the status of the
2123  * operation.
2124  *
2125  * Since: 3.32
2126  */
2127 void
ide_pipeline_build_targets_async(IdePipeline * self,IdePipelinePhase phase,GPtrArray * targets,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2128 ide_pipeline_build_targets_async (IdePipeline         *self,
2129                                   IdePipelinePhase     phase,
2130                                   GPtrArray           *targets,
2131                                   GCancellable        *cancellable,
2132                                   GAsyncReadyCallback  callback,
2133                                   gpointer             user_data)
2134 {
2135   g_autoptr(IdeTask) task = NULL;
2136   TaskData *task_data;
2137 
2138   IDE_ENTRY;
2139 
2140   g_return_if_fail (IDE_IS_PIPELINE (self));
2141   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
2142 
2143   cancellable = dzl_cancellable_chain (cancellable, self->cancellable);
2144 
2145   task = ide_task_new (self, cancellable, callback, user_data);
2146   ide_task_set_source_tag (task, ide_pipeline_build_targets_async);
2147   ide_task_set_priority (task, G_PRIORITY_LOW);
2148 
2149   if (!ide_pipeline_check_ready (self, task))
2150     return;
2151 
2152   /*
2153    * If the requested phase has already been met (by a previous build
2154    * or by an active build who has already surpassed this build phase,
2155    * we can return a result immediately.
2156    *
2157    * Only short circuit if we're running a build, otherwise we need to
2158    * touch each entry and ::query() to see if it needs execution.
2159    */
2160 
2161   if (self->busy && !self->in_clean)
2162     {
2163       if (self->position >= self->pipeline->len)
2164         {
2165           goto short_circuit;
2166         }
2167       else if (self->position >= 0)
2168         {
2169           const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, self->position);
2170 
2171           /* This phase is past the requested phase, we can complete the
2172            * task immediately.
2173            */
2174           if (entry->phase > phase)
2175             goto short_circuit;
2176         }
2177     }
2178 
2179   task_data = task_data_new (task, TASK_BUILD);
2180   task_data->phase = 1 << g_bit_nth_msf (phase, -1);
2181   task_data->build.targets = _g_ptr_array_copy_objects (targets);
2182   ide_task_set_task_data (task, task_data, task_data_free);
2183 
2184   g_queue_push_tail (&self->task_queue, g_steal_pointer (&task));
2185 
2186   ide_pipeline_queue_flush (self);
2187 
2188   IDE_EXIT;
2189 
2190 short_circuit:
2191   ide_task_return_boolean (task, TRUE);
2192   IDE_EXIT;
2193 }
2194 
2195 /**
2196  * ide_pipeline_build_targets_finish:
2197  * @self: An #IdePipeline
2198  * @result: a #GAsyncResult provided to callback
2199  * @error: A location for a #GError, or %NULL
2200  *
2201  * This function completes the asynchronous request to build
2202  * up to a particular phase and targets of the build pipeline.
2203  *
2204  * Returns: %TRUE if the build stages were buildd successfully
2205  *   up to the requested build phase provided to
2206  *   ide_pipeline_build_targets_async().
2207  *
2208  * Since: 3.32
2209  */
2210 gboolean
ide_pipeline_build_targets_finish(IdePipeline * self,GAsyncResult * result,GError ** error)2211 ide_pipeline_build_targets_finish (IdePipeline   *self,
2212                                    GAsyncResult  *result,
2213                                    GError       **error)
2214 {
2215   gboolean ret;
2216 
2217   IDE_ENTRY;
2218 
2219   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
2220   g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
2221 
2222   ret = ide_task_propagate_boolean (IDE_TASK (result), error);
2223 
2224   IDE_RETURN (ret);
2225 }
2226 
2227 /**
2228  * ide_pipeline_build_async:
2229  * @self: A @IdePipeline
2230  * @phase: the requested build phase
2231  * @cancellable: (nullable): a #GCancellable or %NULL
2232  * @callback: a callback to build upon completion
2233  * @user_data: data for @callback
2234  *
2235  * Asynchronously starts the build pipeline.
2236  *
2237  * The @phase parameter should contain the #IdePipelinePhase that is
2238  * necessary to complete. If you simply want to trigger a generic
2239  * build, you probably want %IDE_PIPELINE_PHASE_BUILD. If you only
2240  * need to configure the project (and necessarily the dependencies
2241  * up to that phase) you might want %IDE_PIPELINE_PHASE_CONFIGURE.
2242  *
2243  * You may not specify %IDE_PIPELINE_PHASE_AFTER or
2244  * %IDE_PIPELINE_PHASE_BEFORE flags as those must always be processed
2245  * with the underlying phase they are attached to.
2246  *
2247  * Upon completion, @callback will be buildd and should call
2248  * ide_pipeline_build_finish() to get the status of the
2249  * operation.
2250  *
2251  * Since: 3.32
2252  */
2253 void
ide_pipeline_build_async(IdePipeline * self,IdePipelinePhase phase,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2254 ide_pipeline_build_async (IdePipeline         *self,
2255                           IdePipelinePhase        phase,
2256                           GCancellable        *cancellable,
2257                           GAsyncReadyCallback  callback,
2258                           gpointer             user_data)
2259 {
2260   ide_pipeline_build_targets_async (self, phase, NULL, cancellable, callback, user_data);
2261 }
2262 
2263 /**
2264  * ide_pipeline_build_finish:
2265  * @self: An #IdePipeline
2266  * @result: a #GAsyncResult provided to callback
2267  * @error: A location for a #GError, or %NULL
2268  *
2269  * This function completes the asynchronous request to build
2270  * up to a particular phase of the build pipeline.
2271  *
2272  * Returns: %TRUE if the build stages were buildd successfully
2273  *   up to the requested build phase provided to
2274  *   ide_pipeline_build_async().
2275  *
2276  * Since: 3.32
2277  */
2278 gboolean
ide_pipeline_build_finish(IdePipeline * self,GAsyncResult * result,GError ** error)2279 ide_pipeline_build_finish (IdePipeline   *self,
2280                            GAsyncResult  *result,
2281                            GError       **error)
2282 {
2283   gboolean ret;
2284 
2285   IDE_ENTRY;
2286 
2287   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
2288   g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
2289 
2290   ret = ide_task_propagate_boolean (IDE_TASK (result), error);
2291 
2292   IDE_RETURN (ret);
2293 }
2294 
2295 static gboolean
ide_pipeline_do_flush(gpointer data)2296 ide_pipeline_do_flush (gpointer data)
2297 {
2298   IdePipeline *self = data;
2299   g_autoptr(IdeTask) task = NULL;
2300   g_autoptr(GFile) builddir = NULL;
2301   g_autoptr(GError) error = NULL;
2302   TaskData *task_data;
2303 
2304   IDE_ENTRY;
2305 
2306   g_assert (IDE_IS_PIPELINE (self));
2307 
2308   /*
2309    * If the busy bit is set, there is nothing to do right now.
2310    */
2311   if (self->busy)
2312     {
2313       IDE_TRACE_MSG ("pipeline already busy, defering flush");
2314       IDE_RETURN (G_SOURCE_REMOVE);
2315     }
2316 
2317   /* Ensure our builddir is created, or else we will fail all pending tasks. */
2318   builddir = g_file_new_for_path (self->builddir);
2319   if (!g_file_make_directory_with_parents (builddir, NULL, &error) &&
2320       !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
2321     {
2322       IdeTask *failed_task;
2323 
2324       while (NULL != (failed_task = g_queue_pop_head (&self->task_queue)))
2325         {
2326           ide_task_return_error (failed_task, g_error_copy (error));
2327           g_object_unref (failed_task);
2328         }
2329 
2330       IDE_RETURN (G_SOURCE_REMOVE);
2331     }
2332 
2333   /*
2334    * Pop the next task off the queue from the head (we push to the
2335    * tail and we want FIFO semantics).
2336    */
2337   task = g_queue_pop_head (&self->task_queue);
2338 
2339   if (task == NULL)
2340     {
2341       IDE_TRACE_MSG ("No tasks to process");
2342       IDE_RETURN (G_SOURCE_REMOVE);
2343     }
2344 
2345   g_assert (IDE_IS_TASK (task));
2346   g_assert (self->busy == FALSE);
2347 
2348   /*
2349    * Now prepare the task so that when it completes we can make
2350    * forward progress again.
2351    */
2352   g_signal_connect_object (task,
2353                            "notify::completed",
2354                            G_CALLBACK (ide_pipeline_task_notify_completed),
2355                            self,
2356                            G_CONNECT_SWAPPED);
2357 
2358   /* We need access to the task data to determine how to process the task. */
2359   task_data = ide_task_get_task_data (task);
2360 
2361   g_assert (task_data != NULL);
2362   g_assert (task_data->type > 0);
2363   g_assert (task_data->type <= TASK_REBUILD);
2364   g_assert (IDE_IS_TASK (task_data->task));
2365 
2366   /*
2367    * If this build request could cause us to spin while we are continually
2368    * failing to reach the CONFIGURE stage, protect ourselves as early as we
2369    * can. We'll defer to a rebuild request to cause the full thing to build.
2370    */
2371   if (self->failed &&
2372       task_data->type == TASK_BUILD &&
2373       task_data->phase <= IDE_PIPELINE_PHASE_CONFIGURE)
2374     {
2375       ide_task_return_new_error (task,
2376                                  IDE_BUILD_ERROR,
2377                                  IDE_BUILD_ERROR_NEEDS_REBUILD,
2378                                  "The build pipeline is in a failed state and requires a rebuild");
2379       IDE_RETURN (G_SOURCE_REMOVE);
2380     }
2381 
2382   /*
2383    * Now mark the pipeline as busy to protect ourself from anything recursively
2384    * calling into the pipeline.
2385    */
2386   self->busy = TRUE;
2387   self->failed = FALSE;
2388   self->position = -1;
2389   self->in_clean = (task_data->type == TASK_CLEAN);
2390 
2391   /* Clear any lingering message */
2392   g_clear_pointer (&self->message, g_free);
2393 
2394   /*
2395    * The following logs some helpful information about the build to our
2396    * debug log. This is useful to allow users to debug some problems
2397    * with our assistance (using gnome-builder -vvv).
2398    */
2399   {
2400     g_autoptr(GString) str = g_string_new (NULL);
2401     GFlagsClass *klass;
2402     IdePipelinePhase phase = self->requested_mask;
2403 
2404     klass = g_type_class_peek (IDE_TYPE_PIPELINE_PHASE);
2405 
2406     for (guint i = 0; i < klass->n_values; i++)
2407       {
2408         const GFlagsValue *value = &klass->values[i];
2409 
2410         if (phase & value->value)
2411           {
2412             if (str->len > 0)
2413               g_string_append (str, ", ");
2414             g_string_append (str, value->value_nick);
2415           }
2416       }
2417 
2418     g_debug ("Executing pipeline %s stages %s with %u pipeline entries",
2419              task_type_names[task_data->type],
2420              str->str,
2421              self->pipeline->len);
2422 
2423     for (guint i = 0; i < self->pipeline->len; i++)
2424       {
2425         const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
2426 
2427         g_debug (" pipeline[%u]: %12s: %s [%s]",
2428                  i,
2429                  build_phase_nick (entry->phase),
2430                  G_OBJECT_TYPE_NAME (entry->stage),
2431                  ide_pipeline_stage_get_completed (entry->stage) ? "completed" : "pending");
2432       }
2433   }
2434 
2435   /* Notify any observers that a build (of some sort) is about to start. */
2436   g_signal_emit (self, signals [STARTED], 0, task_data->phase);
2437 
2438   switch (task_data->type)
2439     {
2440     case TASK_BUILD:
2441       ide_pipeline_tick_build (self, task);
2442       break;
2443 
2444     case TASK_CLEAN:
2445       ide_pipeline_tick_clean (self, task);
2446       break;
2447 
2448     case TASK_REBUILD:
2449       ide_pipeline_tick_rebuild (self, task);
2450       break;
2451 
2452     default:
2453       g_assert_not_reached ();
2454     }
2455 
2456   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
2457   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
2458 
2459   IDE_RETURN (G_SOURCE_REMOVE);
2460 }
2461 
2462 static void
ide_pipeline_queue_flush(IdePipeline * self)2463 ide_pipeline_queue_flush (IdePipeline *self)
2464 {
2465   IDE_ENTRY;
2466 
2467   g_assert (IDE_IS_PIPELINE (self));
2468 
2469   gdk_threads_add_idle_full (G_PRIORITY_LOW,
2470                              ide_pipeline_do_flush,
2471                              g_object_ref (self),
2472                              g_object_unref);
2473 
2474   IDE_EXIT;
2475 }
2476 
2477 /**
2478  * ide_pipeline_attach:
2479  * @self: an #IdePipeline
2480  * @phase: An #IdePipelinePhase
2481  * @priority: an optional priority for sorting within the phase
2482  * @stage: An #IdePipelineStage
2483  *
2484  * Insert @stage into the pipeline as part of the phase denoted by @phase.
2485  *
2486  * If priority is non-zero, it will be used to sort the stage among other
2487  * stages that are part of the same phase.
2488  *
2489  * Returns: A stage_id that may be passed to ide_pipeline_detach().
2490  *
2491  * Since: 3.32
2492  */
2493 guint
ide_pipeline_attach(IdePipeline * self,IdePipelinePhase phase,gint priority,IdePipelineStage * stage)2494 ide_pipeline_attach (IdePipeline      *self,
2495                      IdePipelinePhase  phase,
2496                      gint              priority,
2497                      IdePipelineStage *stage)
2498 {
2499   GFlagsClass *klass, *unref_class = NULL;
2500   guint ret = 0;
2501 
2502   IDE_ENTRY;
2503 
2504   g_return_val_if_fail (IDE_IS_PIPELINE (self), 0);
2505   g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (stage), 0);
2506   g_return_val_if_fail ((phase & IDE_PIPELINE_PHASE_MASK) != IDE_PIPELINE_PHASE_NONE, 0);
2507   g_return_val_if_fail ((phase & IDE_PIPELINE_PHASE_WHENCE_MASK) == 0 ||
2508                         (phase & IDE_PIPELINE_PHASE_WHENCE_MASK) == IDE_PIPELINE_PHASE_BEFORE ||
2509                         (phase & IDE_PIPELINE_PHASE_WHENCE_MASK) == IDE_PIPELINE_PHASE_AFTER, 0);
2510 
2511   if (!(klass = g_type_class_peek (IDE_TYPE_PIPELINE_PHASE)))
2512     klass = unref_class = g_type_class_ref (IDE_TYPE_PIPELINE_PHASE);
2513 
2514   for (guint i = 0; i < klass->n_values; i++)
2515     {
2516       const GFlagsValue *value = &klass->values[i];
2517 
2518       if ((phase & IDE_PIPELINE_PHASE_MASK) == value->value)
2519         {
2520           PipelineEntry entry = { 0 };
2521 
2522           _ide_pipeline_stage_set_phase (stage, phase);
2523 
2524           IDE_TRACE_MSG ("Adding stage to pipeline with phase %s and priority %d",
2525                          value->value_nick, priority);
2526 
2527           entry.id = ++self->seqnum;
2528           entry.phase = phase;
2529           entry.priority = priority;
2530           entry.stage = g_object_ref (stage);
2531 
2532           g_array_append_val (self->pipeline, entry);
2533           g_array_sort (self->pipeline, pipeline_entry_compare);
2534 
2535           ret = entry.id;
2536 
2537           ide_pipeline_stage_set_log_observer (stage,
2538                                             ide_pipeline_log_observer,
2539                                             self,
2540                                             NULL);
2541 
2542           /*
2543            * We need to emit items-changed for the newly added entry, but we relied
2544            * on insertion sort above to get our final position. So now we need to
2545            * scan the pipeline for where we ended up, and then emit items-changed for
2546            * the new stage.
2547            */
2548           for (guint j = 0; j < self->pipeline->len; j++)
2549             {
2550               const PipelineEntry *ele = &g_array_index (self->pipeline, PipelineEntry, j);
2551 
2552               if (ele->id == entry.id)
2553                 {
2554                   g_list_model_items_changed (G_LIST_MODEL (self), j, 0, 1);
2555                   break;
2556                 }
2557             }
2558 
2559           ide_object_append (IDE_OBJECT (self), IDE_OBJECT (stage));
2560 
2561           IDE_GOTO (cleanup);
2562         }
2563     }
2564 
2565   g_warning ("No such pipeline phase %02x", phase);
2566 
2567 cleanup:
2568   if (unref_class != NULL)
2569     g_type_class_unref (unref_class);
2570 
2571   IDE_RETURN (ret);
2572 }
2573 
2574 /**
2575  * ide_pipeline_attach_launcher:
2576  * @self: an #IdePipeline
2577  * @phase: An #IdePipelinePhase
2578  * @priority: an optional priority for sorting within the phase
2579  * @launcher: An #IdeSubprocessLauncher
2580  *
2581  * This creates a new stage that will spawn a process using @launcher and log
2582  * the output of stdin/stdout.
2583  *
2584  * It is a programmer error to modify @launcher after passing it to this
2585  * function.
2586  *
2587  * Returns: A stage_id that may be passed to ide_pipeline_remove().
2588  *
2589  * Since: 3.32
2590  */
2591 guint
ide_pipeline_attach_launcher(IdePipeline * self,IdePipelinePhase phase,gint priority,IdeSubprocessLauncher * launcher)2592 ide_pipeline_attach_launcher (IdePipeline           *self,
2593                               IdePipelinePhase       phase,
2594                               gint                   priority,
2595                               IdeSubprocessLauncher *launcher)
2596 {
2597   g_autoptr(IdePipelineStage) stage = NULL;
2598   IdeContext *context;
2599 
2600   g_return_val_if_fail (IDE_IS_PIPELINE (self), 0);
2601   g_return_val_if_fail ((phase & IDE_PIPELINE_PHASE_MASK) != IDE_PIPELINE_PHASE_NONE, 0);
2602   g_return_val_if_fail ((phase & IDE_PIPELINE_PHASE_WHENCE_MASK) == 0 ||
2603                         (phase & IDE_PIPELINE_PHASE_WHENCE_MASK) == IDE_PIPELINE_PHASE_BEFORE ||
2604                         (phase & IDE_PIPELINE_PHASE_WHENCE_MASK) == IDE_PIPELINE_PHASE_AFTER, 0);
2605 
2606   context = ide_object_get_context (IDE_OBJECT (self));
2607   stage = ide_pipeline_stage_launcher_new (context, launcher);
2608 
2609   return ide_pipeline_attach (self, phase, priority, stage);
2610 }
2611 
2612 /**
2613  * ide_pipeline_request_phase:
2614  * @self: An #IdePipeline
2615  * @phase: An #IdePipelinePhase
2616  *
2617  * Requests that the next execution of the pipeline will build up to @phase
2618  * including all stages that were previously invalidated.
2619  *
2620  * Returns: %TRUE if a stage is known to require execution.
2621  *
2622  * Since: 3.32
2623  */
2624 gboolean
ide_pipeline_request_phase(IdePipeline * self,IdePipelinePhase phase)2625 ide_pipeline_request_phase (IdePipeline      *self,
2626                             IdePipelinePhase  phase)
2627 {
2628   GFlagsClass *klass, *unref_class = NULL;
2629   gboolean ret = FALSE;
2630 
2631   IDE_ENTRY;
2632 
2633   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
2634   g_return_val_if_fail ((phase & IDE_PIPELINE_PHASE_MASK) != IDE_PIPELINE_PHASE_NONE, FALSE);
2635 
2636   /*
2637    * You can only request basic phases. That does not include modifiers
2638    * like BEFORE, AFTER, FAILED, FINISHED.
2639    */
2640   phase &= IDE_PIPELINE_PHASE_MASK;
2641 
2642   if (!(klass = g_type_class_peek (IDE_TYPE_PIPELINE_PHASE)))
2643     klass = unref_class = g_type_class_ref (IDE_TYPE_PIPELINE_PHASE);
2644 
2645   for (guint i = 0; i < klass->n_values; i++)
2646     {
2647       const GFlagsValue *value = &klass->values[i];
2648 
2649       if ((guint)phase == value->value)
2650         {
2651           IDE_TRACE_MSG ("requesting pipeline phase %s", value->value_nick);
2652           /*
2653            * Each flag is a power of two, so we can simply subtract one
2654            * to get a mask of all the previous phases.
2655            */
2656           self->requested_mask |= phase | (phase - 1);
2657           IDE_GOTO (cleanup);
2658         }
2659     }
2660 
2661   g_warning ("No such phase %02x", (guint)phase);
2662 
2663 cleanup:
2664 
2665   /*
2666    * If we have a stage in one of the requested phases, then we can let the
2667    * caller know that they need to run build_async() to be up to date. This
2668    * is useful for situations where you might want to avoid calling
2669    * build_async() altogether. Additionally, we want to know if there are
2670    * any connections to the "query" which could cause the completed state
2671    * to be invalidated.
2672    */
2673   for (guint i = 0; i < self->pipeline->len; i++)
2674     {
2675       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
2676 
2677       if (!(entry->phase & self->requested_mask))
2678         continue;
2679 
2680       if (!ide_pipeline_stage_get_completed (entry->stage) ||
2681           _ide_pipeline_stage_has_query (entry->stage))
2682         {
2683           ret = TRUE;
2684           break;
2685         }
2686     }
2687 
2688   if (unref_class != NULL)
2689     g_type_class_unref (unref_class);
2690 
2691   IDE_RETURN (ret);
2692 }
2693 
2694 /**
2695  * ide_pipeline_get_builddir:
2696  * @self: An #IdePipeline
2697  *
2698  * Gets the "builddir" to be used for the build process. This is generally
2699  * the location that build systems will use for out-of-tree builds.
2700  *
2701  * Returns: the path of the build directory
2702  *
2703  * Since: 3.32
2704  */
2705 const gchar *
ide_pipeline_get_builddir(IdePipeline * self)2706 ide_pipeline_get_builddir (IdePipeline *self)
2707 {
2708   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
2709 
2710   return self->builddir;
2711 }
2712 
2713 /**
2714  * ide_pipeline_get_srcdir:
2715  * @self: An #IdePipeline
2716  *
2717  * Gets the "srcdir" of the project. This is equivalent to the
2718  * IdeVcs:working-directory property as a string.
2719  *
2720  * Returns: the path of the source directory
2721  *
2722  * Since: 3.32
2723  */
2724 const gchar *
ide_pipeline_get_srcdir(IdePipeline * self)2725 ide_pipeline_get_srcdir (IdePipeline *self)
2726 {
2727   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
2728 
2729   return self->srcdir;
2730 }
2731 
2732 static gchar *
ide_pipeline_build_path_va_list(const gchar * prefix,const gchar * first_part,va_list args)2733 ide_pipeline_build_path_va_list (const gchar *prefix,
2734                                  const gchar *first_part,
2735                                  va_list      args)
2736 {
2737   g_autoptr(GPtrArray) ar = NULL;
2738 
2739   g_assert (prefix != NULL);
2740   g_assert (first_part != NULL);
2741 
2742   ar = g_ptr_array_new ();
2743   g_ptr_array_add (ar, (gchar *)prefix);
2744   do
2745     g_ptr_array_add (ar, (gchar *)first_part);
2746   while (NULL != (first_part = va_arg (args, const gchar *)));
2747   g_ptr_array_add (ar, NULL);
2748 
2749   return g_build_filenamev ((gchar **)ar->pdata);
2750 }
2751 
2752 /**
2753  * ide_pipeline_build_srcdir_path:
2754  *
2755  * This is a convenience function to create a new path that starts with
2756  * the source directory of the project.
2757  *
2758  * This is functionally equivalent to calling g_build_filename() with the
2759  * working directory of the source tree.
2760  *
2761  * Returns: (transfer full): A newly allocated string.
2762  *
2763  * Since: 3.32
2764  */
2765 gchar *
ide_pipeline_build_srcdir_path(IdePipeline * self,const gchar * first_part,...)2766 ide_pipeline_build_srcdir_path (IdePipeline *self,
2767                                 const gchar *first_part,
2768                                 ...)
2769 {
2770   gchar *ret;
2771   va_list args;
2772 
2773   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
2774   g_return_val_if_fail (self->srcdir != NULL, NULL);
2775   g_return_val_if_fail (first_part != NULL, NULL);
2776 
2777   va_start (args, first_part);
2778   ret = ide_pipeline_build_path_va_list (self->srcdir, first_part, args);
2779   va_end (args);
2780 
2781   return ret;
2782 }
2783 
2784 /**
2785  * ide_pipeline_build_builddir_path:
2786  *
2787  * This is a convenience function to create a new path that starts with
2788  * the build directory for this build configuration.
2789  *
2790  * This is functionally equivalent to calling g_build_filename() with the
2791  * result of ide_pipeline_get_builddir() as the first parameter.
2792  *
2793  * Returns: (transfer full): A newly allocated string.
2794  *
2795  * Since: 3.32
2796  */
2797 gchar *
ide_pipeline_build_builddir_path(IdePipeline * self,const gchar * first_part,...)2798 ide_pipeline_build_builddir_path (IdePipeline *self,
2799                                   const gchar *first_part,
2800                                   ...)
2801 {
2802   gchar *ret;
2803   va_list args;
2804 
2805   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
2806   g_return_val_if_fail (self->builddir != NULL, NULL);
2807   g_return_val_if_fail (first_part != NULL, NULL);
2808 
2809   va_start (args, first_part);
2810   ret = ide_pipeline_build_path_va_list (self->builddir, first_part, args);
2811   va_end (args);
2812 
2813   return ret;
2814 }
2815 
2816 /**
2817  * ide_pipeline_detach:
2818  * @self: An #IdePipeline
2819  * @stage_id: An identifier returned from adding a stage
2820  *
2821  * This removes the stage matching @stage_id. You are returned a @stage_id when
2822  * inserting a stage with functions such as ide_pipeline_attach()
2823  * or ide_pipeline_attach_launcher().
2824  *
2825  * Plugins should use this function to remove their stages when the plugin
2826  * is unloading.
2827  *
2828  * Since: 3.32
2829  */
2830 void
ide_pipeline_detach(IdePipeline * self,guint stage_id)2831 ide_pipeline_detach (IdePipeline *self,
2832                      guint        stage_id)
2833 {
2834   g_return_if_fail (IDE_IS_PIPELINE (self));
2835   g_return_if_fail (self->pipeline != NULL);
2836   g_return_if_fail (stage_id != 0);
2837 
2838   for (guint i = 0; i < self->pipeline->len; i++)
2839     {
2840       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
2841 
2842       if (entry->id == stage_id)
2843         {
2844           ide_object_destroy (IDE_OBJECT (entry->stage));
2845           g_array_remove_index (self->pipeline, i);
2846           g_list_model_items_changed (G_LIST_MODEL (self), i, 1, 0);
2847           break;
2848         }
2849     }
2850 }
2851 
2852 /**
2853  * ide_pipeline_invalidate_phase:
2854  * @self: An #IdePipeline
2855  * @phases: The phases to invalidate
2856  *
2857  * Invalidates the phases matching @phases flags.
2858  *
2859  * If the requested phases include the phases invalidated here, the next
2860  * execution of the pipeline will build thse phases.
2861  *
2862  * This should be used by plugins to ensure a particular phase is re-buildd
2863  * upon discovering its state is no longer valid. Such an example might be
2864  * invalidating the %IDE_PIPELINE_PHASE_AUTOGEN phase when the an autotools
2865  * projects autogen.sh file has been changed.
2866  *
2867  * Since: 3.32
2868  */
2869 void
ide_pipeline_invalidate_phase(IdePipeline * self,IdePipelinePhase phases)2870 ide_pipeline_invalidate_phase (IdePipeline *self,
2871                                      IdePipelinePhase     phases)
2872 {
2873   g_return_if_fail (IDE_IS_PIPELINE (self));
2874 
2875   for (guint i = 0; i < self->pipeline->len; i++)
2876     {
2877       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
2878 
2879       if ((entry->phase & IDE_PIPELINE_PHASE_MASK) & phases)
2880         ide_pipeline_stage_set_completed (entry->stage, FALSE);
2881     }
2882 }
2883 
2884 /**
2885  * ide_pipeline_get_stage_by_id:
2886  * @self: An #IdePipeline
2887  * @stage_id: the identfier of the stage
2888  *
2889  * Gets the stage matching the identifier @stage_id as returned from
2890  * ide_pipeline_attach().
2891  *
2892  * Returns: (transfer none) (nullable): An #IdePipelineStage or %NULL if the
2893  *   stage could not be found.
2894  *
2895  * Since: 3.32
2896  */
2897 IdePipelineStage *
ide_pipeline_get_stage_by_id(IdePipeline * self,guint stage_id)2898 ide_pipeline_get_stage_by_id (IdePipeline *self,
2899                               guint        stage_id)
2900 {
2901   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
2902 
2903   for (guint i = 0; i < self->pipeline->len; i++)
2904     {
2905       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
2906 
2907       if (entry->id == stage_id)
2908         return entry->stage;
2909     }
2910 
2911   return NULL;
2912 }
2913 
2914 /**
2915  * ide_pipeline_get_runtime:
2916  * @self: An #IdePipeline
2917  *
2918  * A convenience function to get the runtime for a build pipeline.
2919  *
2920  * Returns: (transfer none) (nullable): An #IdeRuntime or %NULL
2921  *
2922  * Since: 3.32
2923  */
2924 IdeRuntime *
ide_pipeline_get_runtime(IdePipeline * self)2925 ide_pipeline_get_runtime (IdePipeline *self)
2926 {
2927   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
2928 
2929   return self->runtime;
2930 }
2931 
2932 /**
2933  * ide_pipeline_get_toolchain:
2934  * @self: An #IdePipeline
2935  *
2936  * A convenience function to get the toolchain for a build pipeline.
2937  *
2938  * Returns: (transfer none): An #IdeToolchain
2939  *
2940  * Since: 3.32
2941  */
2942 IdeToolchain *
ide_pipeline_get_toolchain(IdePipeline * self)2943 ide_pipeline_get_toolchain (IdePipeline *self)
2944 {
2945   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
2946 
2947   return self->toolchain;
2948 }
2949 
2950 /**
2951  * ide_pipeline_create_launcher:
2952  * @self: An #IdePipeline
2953  *
2954  * This is a convenience function to create a new #IdeSubprocessLauncher
2955  * using the configuration and runtime associated with the pipeline.
2956  *
2957  * Returns: (transfer full): An #IdeSubprocessLauncher.
2958  *
2959  * Since: 3.32
2960  */
2961 IdeSubprocessLauncher *
ide_pipeline_create_launcher(IdePipeline * self,GError ** error)2962 ide_pipeline_create_launcher (IdePipeline  *self,
2963                               GError      **error)
2964 {
2965   g_autoptr(IdeSubprocessLauncher) ret = NULL;
2966   IdeRuntime *runtime;
2967 
2968   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2969   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
2970 
2971   runtime = ide_config_get_runtime (self->config);
2972 
2973   if (runtime == NULL)
2974     {
2975       g_set_error (error,
2976                    G_IO_ERROR,
2977                    G_IO_ERROR_FAILED,
2978                    "The runtime %s is missing",
2979                    ide_config_get_runtime_id (self->config));
2980       return NULL;
2981     }
2982 
2983   ret = ide_runtime_create_launcher (runtime, error);
2984 
2985   if (ret != NULL)
2986     {
2987       IdeEnvironment *env = ide_config_get_environment (self->config);
2988 
2989       ide_subprocess_launcher_set_clear_env (ret, TRUE);
2990       ide_subprocess_launcher_overlay_environment (ret, env);
2991       /* Always ignore V=1 from configurations */
2992       ide_subprocess_launcher_setenv (ret, "V", "0", TRUE);
2993       ide_subprocess_launcher_set_cwd (ret, ide_pipeline_get_builddir (self));
2994       ide_subprocess_launcher_set_flags (ret,
2995                                          (G_SUBPROCESS_FLAGS_STDERR_PIPE |
2996                                           G_SUBPROCESS_FLAGS_STDOUT_PIPE));
2997       ide_config_apply_path (self->config, ret);
2998 
2999       g_signal_emit (self, signals [LAUNCHER_CREATED], 0, ret);
3000     }
3001 
3002   return g_steal_pointer (&ret);
3003 }
3004 
3005 /**
3006  * ide_pipeline_attach_pty:
3007  * @self: an #IdePipeline
3008  * @launcher: an #IdeSubprocessLauncher
3009  *
3010  * Attaches a PTY to stdin/stdout/stderr of the #IdeSubprocessLauncher.
3011  * This is useful if the application can take advantage of a PTY for
3012  * features like colors and other escape sequences.
3013  *
3014  * Since: 3.32
3015  */
3016 void
ide_pipeline_attach_pty(IdePipeline * self,IdeSubprocessLauncher * launcher)3017 ide_pipeline_attach_pty (IdePipeline      *self,
3018                                IdeSubprocessLauncher *launcher)
3019 {
3020   GSubprocessFlags flags;
3021 
3022   g_return_if_fail (IDE_IS_PIPELINE (self));
3023   g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (launcher));
3024 
3025   if (self->pty_slave == -1)
3026     {
3027       IdePtyFd master_fd = ide_pty_intercept_get_fd (&self->intercept);
3028       self->pty_slave = ide_pty_intercept_create_slave (master_fd, TRUE);
3029     }
3030 
3031   if (self->pty_slave == -1)
3032     {
3033       ide_object_warning (self, _("Pseudo terminal creation failed. Terminal features will be limited."));
3034       return;
3035     }
3036 
3037   /* Turn off built in pipes if set */
3038   flags = ide_subprocess_launcher_get_flags (launcher);
3039   flags &= ~(G_SUBPROCESS_FLAGS_STDERR_PIPE |
3040              G_SUBPROCESS_FLAGS_STDOUT_PIPE |
3041              G_SUBPROCESS_FLAGS_STDIN_PIPE);
3042   ide_subprocess_launcher_set_flags (launcher, flags);
3043 
3044   /* Assign slave device */
3045   ide_subprocess_launcher_take_stdin_fd (launcher, dup (self->pty_slave));
3046   ide_subprocess_launcher_take_stdout_fd (launcher, dup (self->pty_slave));
3047   ide_subprocess_launcher_take_stderr_fd (launcher, dup (self->pty_slave));
3048 
3049   /* Ensure a terminal type is set */
3050   ide_subprocess_launcher_setenv (launcher, "TERM", "xterm-256color", FALSE);
3051 }
3052 
3053 /**
3054  * ide_pipeline_get_pty:
3055  * @self: a #IdePipeline
3056  *
3057  * Gets the #VtePty for the pipeline, if set.
3058  *
3059  * This will not be set until the pipeline has been initialized. That is not
3060  * guaranteed to happen at object creation time.
3061  *
3062  * Returns: (transfer none) (nullable): a #VtePty or %NULL
3063  *
3064  * Since: 3.32
3065  */
3066 VtePty *
ide_pipeline_get_pty(IdePipeline * self)3067 ide_pipeline_get_pty (IdePipeline *self)
3068 {
3069   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
3070 
3071   return self->pty;
3072 }
3073 
3074 guint
ide_pipeline_add_log_observer(IdePipeline * self,IdeBuildLogObserver observer,gpointer observer_data,GDestroyNotify observer_data_destroy)3075 ide_pipeline_add_log_observer (IdePipeline         *self,
3076                                IdeBuildLogObserver  observer,
3077                                gpointer             observer_data,
3078                                GDestroyNotify       observer_data_destroy)
3079 {
3080   g_return_val_if_fail (IDE_IS_PIPELINE (self), 0);
3081   g_return_val_if_fail (observer != NULL, 0);
3082 
3083   return ide_build_log_add_observer (self->log, observer, observer_data, observer_data_destroy);
3084 
3085 }
3086 
3087 gboolean
ide_pipeline_remove_log_observer(IdePipeline * self,guint observer_id)3088 ide_pipeline_remove_log_observer (IdePipeline *self,
3089                                   guint        observer_id)
3090 {
3091   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
3092   g_return_val_if_fail (observer_id > 0, FALSE);
3093 
3094   return ide_build_log_remove_observer (self->log, observer_id);
3095 }
3096 
3097 void
ide_pipeline_emit_diagnostic(IdePipeline * self,IdeDiagnostic * diagnostic)3098 ide_pipeline_emit_diagnostic (IdePipeline   *self,
3099                               IdeDiagnostic *diagnostic)
3100 {
3101   g_return_if_fail (IDE_IS_PIPELINE (self));
3102   g_return_if_fail (diagnostic != NULL);
3103   g_return_if_fail (IDE_IS_MAIN_THREAD ());
3104 
3105   g_signal_emit (self, signals[DIAGNOSTIC], 0, diagnostic);
3106 }
3107 
3108 /**
3109  * ide_pipeline_add_error_format:
3110  * @self: an #IdePipeline
3111  * @regex: A regex to be compiled
3112  *
3113  * This can be used to add a regex that will extract errors from
3114  * standard output. This is similar to the "errorformat" feature
3115  * of vim to extract warnings from standard output.
3116  *
3117  * The regex should used named capture groups to pass information
3118  * to the extraction process.
3119  *
3120  * Supported group names are:
3121  *
3122  *  • filename (a string path)
3123  *  • line (an integer)
3124  *  • column (an integer)
3125  *  • level (a string)
3126  *  • message (a string)
3127  *
3128  * For example, to extract warnings from GCC you might do something
3129  * like the following:
3130  *
3131  *   "(?&lt;filename&gt;[a-zA-Z0-9\\-\\.\\/_]+):"
3132  *   "(?&lt;line&gt;\\d+):"
3133  *   "(?&lt;column&gt;\\d+): "
3134  *   "(?&lt;level&gt;[\\w\\s]+): "
3135  *   "(?&lt;message&gt;.*)"
3136  *
3137  * To remove the regex, use the ide_pipeline_remove_error_format()
3138  * function with the resulting format id returned from this function.
3139  *
3140  * The resulting format id will be &gt; 0 if successful.
3141  *
3142  * Returns: an error format id that may be passed to
3143  *   ide_pipeline_remove_error_format().
3144  *
3145  * Since: 3.32
3146  */
3147 guint
ide_pipeline_add_error_format(IdePipeline * self,const gchar * regex,GRegexCompileFlags flags)3148 ide_pipeline_add_error_format (IdePipeline        *self,
3149                                const gchar        *regex,
3150                                GRegexCompileFlags  flags)
3151 {
3152   ErrorFormat errfmt = { 0 };
3153   g_autoptr(GError) error = NULL;
3154 
3155   g_return_val_if_fail (IDE_IS_PIPELINE (self), 0);
3156 
3157   errfmt.regex = g_regex_new (regex, G_REGEX_OPTIMIZE | flags, 0, &error);
3158 
3159   if (errfmt.regex == NULL)
3160     {
3161       g_warning ("%s", error->message);
3162       return 0;
3163     }
3164 
3165   errfmt.id = ++self->errfmt_seqnum;
3166 
3167   g_array_append_val (self->errfmts, errfmt);
3168 
3169   return errfmt.id;
3170 }
3171 
3172 /**
3173  * ide_pipeline_remove_error_format:
3174  * @self: An #IdePipeline
3175  * @error_format_id: an identifier for the error format.
3176  *
3177  * Removes an error format that was registered with
3178  * ide_pipeline_add_error_format().
3179  *
3180  * Returns: %TRUE if the error format was removed.
3181  *
3182  * Since: 3.32
3183  */
3184 gboolean
ide_pipeline_remove_error_format(IdePipeline * self,guint error_format_id)3185 ide_pipeline_remove_error_format (IdePipeline *self,
3186                                   guint        error_format_id)
3187 {
3188   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
3189   g_return_val_if_fail (error_format_id > 0, FALSE);
3190 
3191   for (guint i = 0; i < self->errfmts->len; i++)
3192     {
3193       const ErrorFormat *errfmt = &g_array_index (self->errfmts, ErrorFormat, i);
3194 
3195       if (errfmt->id == error_format_id)
3196         {
3197           g_array_remove_index (self->errfmts, i);
3198           return TRUE;
3199         }
3200     }
3201 
3202   return FALSE;
3203 }
3204 
3205 gboolean
ide_pipeline_get_busy(IdePipeline * self)3206 ide_pipeline_get_busy (IdePipeline *self)
3207 {
3208   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
3209 
3210   return self->busy;
3211 }
3212 
3213 /**
3214  * ide_pipeline_get_message:
3215  * @self: An #IdePipeline
3216  *
3217  * Gets the current message for the build pipeline. This can be
3218  * shown to users in UI elements to signify progress in the
3219  * build.
3220  *
3221  * Returns: (nullable) (transfer full): A string representing the
3222  *   current stage of the build, or %NULL.
3223  *
3224  * Since: 3.32
3225  */
3226 gchar *
ide_pipeline_get_message(IdePipeline * self)3227 ide_pipeline_get_message (IdePipeline *self)
3228 {
3229   IdePipelinePhase phase;
3230   const gchar *ret = NULL;
3231 
3232   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
3233 
3234   /* Use any message the Pty has given us while building. */
3235   if (self->busy && self->message != NULL)
3236     return g_strdup (self->message);
3237 
3238   if (self->in_clean)
3239     return g_strdup (_("Cleaning…"));
3240 
3241   /* Not active, use simple messaging */
3242   if (self->failed)
3243     return g_strdup (_("Failed"));
3244   else if (!self->busy)
3245     return g_strdup (_("Ready"));
3246 
3247   if (self->current_stage != NULL)
3248     {
3249       const gchar *name = ide_pipeline_stage_get_name (self->current_stage);
3250 
3251       if (!ide_str_empty0 (name))
3252         return g_strdup (name);
3253     }
3254 
3255   phase = ide_pipeline_get_phase (self);
3256 
3257   switch (phase)
3258     {
3259     case IDE_PIPELINE_PHASE_DOWNLOADS:
3260       ret = _("Downloading…");
3261       break;
3262 
3263     case IDE_PIPELINE_PHASE_DEPENDENCIES:
3264       ret = _("Building dependencies…");
3265       break;
3266 
3267     case IDE_PIPELINE_PHASE_AUTOGEN:
3268       ret = _("Bootstrapping…");
3269       break;
3270 
3271     case IDE_PIPELINE_PHASE_CONFIGURE:
3272       ret = _("Configuring…");
3273       break;
3274 
3275     case IDE_PIPELINE_PHASE_BUILD:
3276       ret = _("Building…");
3277       break;
3278 
3279     case IDE_PIPELINE_PHASE_INSTALL:
3280       ret = _("Installing…");
3281       break;
3282 
3283     case IDE_PIPELINE_PHASE_COMMIT:
3284       ret = _("Committing…");
3285       break;
3286 
3287     case IDE_PIPELINE_PHASE_EXPORT:
3288       ret = _("Exporting…");
3289       break;
3290 
3291     case IDE_PIPELINE_PHASE_FINAL:
3292       ret = _("Success");
3293       break;
3294 
3295     case IDE_PIPELINE_PHASE_FINISHED:
3296       ret = _("Success");
3297       break;
3298 
3299     case IDE_PIPELINE_PHASE_FAILED:
3300       ret = _("Failed");
3301       break;
3302 
3303     case IDE_PIPELINE_PHASE_PREPARE:
3304       ret = _("Preparing…");
3305       break;
3306 
3307     case IDE_PIPELINE_PHASE_NONE:
3308       ret = _("Ready");
3309       break;
3310 
3311     case IDE_PIPELINE_PHASE_AFTER:
3312     case IDE_PIPELINE_PHASE_BEFORE:
3313     default:
3314       g_assert_not_reached ();
3315     }
3316 
3317   return g_strdup (ret);
3318 }
3319 
3320 /**
3321  * ide_pipeline_foreach_stage:
3322  * @self: An #IdePipeline
3323  * @stage_callback: (scope call): A callback for each #IdePipelineStage
3324  * @user_data: user data for @stage_callback
3325  *
3326  * This function will call @stage_callback for every #IdePipelineStage registered
3327  * in the pipeline.
3328  *
3329  * Since: 3.32
3330  */
3331 void
ide_pipeline_foreach_stage(IdePipeline * self,GFunc stage_callback,gpointer user_data)3332 ide_pipeline_foreach_stage (IdePipeline *self,
3333                             GFunc        stage_callback,
3334                             gpointer     user_data)
3335 {
3336   g_return_if_fail (IDE_IS_PIPELINE (self));
3337   g_return_if_fail (stage_callback != NULL);
3338 
3339   for (guint i = 0; i < self->pipeline->len; i++)
3340     {
3341       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
3342 
3343       stage_callback (entry->stage, user_data);
3344     }
3345 }
3346 
3347 static void
ide_pipeline_clean_cb(GObject * object,GAsyncResult * result,gpointer user_data)3348 ide_pipeline_clean_cb (GObject      *object,
3349                        GAsyncResult *result,
3350                        gpointer      user_data)
3351 {
3352   IdePipelineStage *stage = (IdePipelineStage *)object;
3353   g_autoptr(IdeTask) task = user_data;
3354   g_autoptr(GError) error = NULL;
3355   IdePipeline *self;
3356   GPtrArray *stages;
3357   TaskData *td;
3358 
3359   IDE_ENTRY;
3360 
3361   g_assert (IDE_IS_PIPELINE_STAGE (stage));
3362   g_assert (IDE_IS_TASK (task));
3363 
3364   self = ide_task_get_source_object (task);
3365   td = ide_task_get_task_data (task);
3366 
3367   g_assert (IDE_IS_PIPELINE (self));
3368   g_assert (td != NULL);
3369   g_assert (td->type == TASK_CLEAN);
3370   g_assert (td->task == task);
3371   g_assert (td->clean.stages != NULL);
3372 
3373   stages = td->clean.stages;
3374 
3375   g_assert (stages != NULL);
3376   g_assert (stages->len > 0);
3377   g_assert (g_ptr_array_index (stages, stages->len - 1) == stage);
3378 
3379   if (!ide_pipeline_stage_clean_finish (stage, result, &error))
3380     {
3381       ide_task_return_error (task, g_steal_pointer (&error));
3382       IDE_EXIT;
3383     }
3384 
3385   g_ptr_array_remove_index (stages, stages->len - 1);
3386 
3387   ide_pipeline_tick_clean (self, task);
3388 
3389   IDE_EXIT;
3390 }
3391 
3392 static void
ide_pipeline_tick_clean(IdePipeline * self,IdeTask * task)3393 ide_pipeline_tick_clean (IdePipeline *self,
3394                          IdeTask     *task)
3395 {
3396   GCancellable *cancellable;
3397   GPtrArray *stages;
3398   TaskData *td;
3399 
3400   IDE_ENTRY;
3401 
3402   g_assert (IDE_IS_PIPELINE (self));
3403   g_assert (IDE_IS_TASK (task));
3404 
3405   td = ide_task_get_task_data (task);
3406   cancellable = ide_task_get_cancellable (task);
3407 
3408   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
3409   g_assert (td != NULL);
3410   g_assert (td->type == TASK_CLEAN);
3411   g_assert (td->task == task);
3412   g_assert (td->clean.stages != NULL);
3413 
3414   stages = td->clean.stages;
3415 
3416   if (stages->len != 0)
3417     {
3418       IdePipelineStage *stage = g_ptr_array_index (stages, stages->len - 1);
3419 
3420       self->current_stage = stage;
3421 
3422       ide_pipeline_stage_clean_async (stage,
3423                                    self,
3424                                    cancellable,
3425                                    ide_pipeline_clean_cb,
3426                                    g_object_ref (task));
3427 
3428       IDE_GOTO (notify);
3429     }
3430 
3431   ide_task_return_boolean (task, TRUE);
3432 
3433 notify:
3434   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
3435   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PHASE]);
3436 
3437   IDE_EXIT;
3438 }
3439 
3440 void
ide_pipeline_clean_async(IdePipeline * self,IdePipelinePhase phase,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3441 ide_pipeline_clean_async (IdePipeline         *self,
3442                           IdePipelinePhase     phase,
3443                           GCancellable        *cancellable,
3444                           GAsyncReadyCallback  callback,
3445                           gpointer             user_data)
3446 {
3447   g_autoptr(IdeTask) task = NULL;
3448   g_autoptr(GCancellable) local_cancellable = NULL;
3449   g_autoptr(GPtrArray) stages = NULL;
3450   IdePipelinePhase min_phase = IDE_PIPELINE_PHASE_FINAL;
3451   IdePipelinePhase phase_mask;
3452   GFlagsClass *phase_class;
3453   TaskData *td;
3454 
3455   IDE_ENTRY;
3456 
3457   g_assert (IDE_IS_PIPELINE (self));
3458   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
3459 
3460   drop_caches (self);
3461 
3462   if (cancellable == NULL)
3463     cancellable = local_cancellable = g_cancellable_new ();
3464 
3465   task = ide_task_new (self, cancellable, callback, user_data);
3466   ide_task_set_priority (task, G_PRIORITY_LOW);
3467   ide_task_set_source_tag (task, ide_pipeline_clean_async);
3468 
3469   if (!ide_pipeline_check_ready (self, task))
3470     return;
3471 
3472   dzl_cancellable_chain (cancellable, self->cancellable);
3473 
3474   td = task_data_new (task, TASK_CLEAN);
3475   td->phase = phase;
3476   ide_task_set_task_data (task, td, task_data_free);
3477 
3478   /*
3479    * To clean the project, we go through each stage and call it's clean async
3480    * vfunc pairs if they have been set. Afterwards, we ensure their
3481    * IdePipelineStage:completed bit is cleared so they will run as part of the
3482    * next build operation.
3483    *
3484    * Also, when performing a clean we walk backwards from the last stage to the
3485    * present so that they can rely on things being semi-up-to-date from their
3486    * point of view.
3487    *
3488    * To simplify the case of walking through the affected stages, we create a
3489    * copy of the affected stages up front. We store them in the opposite order
3490    * they need to be ran so that we only have to pop the last item after
3491    * completing each stage. Otherwise we would additionally need a position
3492    * variable.
3493    *
3494    * To calculate the phases that are affected, we subtract 1 from the min
3495    * phase that was given to us. We then twos-compliment that and use it as our
3496    * mask (so only our min and higher stages are cleaned).
3497    */
3498 
3499   phase_class = g_type_class_peek (IDE_TYPE_PIPELINE_PHASE);
3500 
3501   for (guint i = 0; i < phase_class->n_values; i++)
3502     {
3503       const GFlagsValue *value = &phase_class->values [i];
3504 
3505       if (value->value & phase)
3506         {
3507           if (value->value < (guint)min_phase)
3508             min_phase = value->value;
3509         }
3510     }
3511 
3512   phase_mask = ~(min_phase - 1);
3513 
3514   stages = g_ptr_array_new_with_free_func (g_object_unref);
3515 
3516   for (guint i = 0; i < self->pipeline->len; i++)
3517     {
3518       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
3519 
3520       if ((entry->phase & IDE_PIPELINE_PHASE_MASK) & phase_mask)
3521         g_ptr_array_add (stages, g_object_ref (entry->stage));
3522     }
3523 
3524   /*
3525    * Short-circuit if we don't have any stages to clean.
3526    */
3527   if (stages->len == 0)
3528     {
3529       ide_task_return_boolean (task, TRUE);
3530       IDE_EXIT;
3531     }
3532 
3533   td->clean.stages = g_steal_pointer (&stages);
3534 
3535   g_queue_push_tail (&self->task_queue, g_steal_pointer (&task));
3536 
3537   ide_pipeline_queue_flush (self);
3538 
3539   IDE_EXIT;
3540 }
3541 
3542 gboolean
ide_pipeline_clean_finish(IdePipeline * self,GAsyncResult * result,GError ** error)3543 ide_pipeline_clean_finish (IdePipeline   *self,
3544                            GAsyncResult  *result,
3545                            GError       **error)
3546 {
3547   gboolean ret;
3548 
3549   IDE_ENTRY;
3550 
3551   g_assert (IDE_IS_PIPELINE (self));
3552   g_assert (IDE_IS_TASK (result));
3553 
3554   ret = ide_task_propagate_boolean (IDE_TASK (result), error);
3555 
3556   IDE_RETURN (ret);
3557 }
3558 
3559 static gboolean
can_remove_builddir(IdePipeline * self)3560 can_remove_builddir (IdePipeline *self)
3561 {
3562   g_autofree gchar *_build = NULL;
3563   g_autoptr(GFile) builddir = NULL;
3564   g_autoptr(GFile) cache = NULL;
3565   IdeContext *context;
3566 
3567   g_assert (IDE_IS_PIPELINE (self));
3568 
3569   /*
3570    * Only remove builddir if it is in ~/.cache/ or our XDG data dirs
3571    * equivalent. We don't want to accidentally remove data that might
3572    * be important to the user.
3573    *
3574    * However, if the build dir is our special case "_build" inside the
3575    * project directory, we'll allow that too.
3576    */
3577 
3578   cache = g_file_new_for_path (g_get_user_cache_dir ());
3579   builddir = g_file_new_for_path (self->builddir);
3580   if (g_file_has_prefix (builddir, cache))
3581     return TRUE;
3582 
3583   /* If this is _build in the project tree, we will allow that too
3584    * since we create those sometimes.
3585    */
3586   context = ide_object_get_context (IDE_OBJECT (self));
3587   _build = ide_context_build_filename (context, "_build", NULL);
3588   if (g_str_equal (_build, self->builddir) &&
3589       g_file_test (_build, G_FILE_TEST_IS_DIR) &&
3590       !g_file_test (_build, G_FILE_TEST_IS_SYMLINK))
3591     return TRUE;
3592 
3593   g_debug ("%s is not in a cache directory, will not delete it", self->builddir);
3594 
3595   return FALSE;
3596 }
3597 
3598 static void
ide_pipeline_reaper_cb(GObject * object,GAsyncResult * result,gpointer user_data)3599 ide_pipeline_reaper_cb (GObject      *object,
3600                         GAsyncResult *result,
3601                         gpointer      user_data)
3602 {
3603   DzlDirectoryReaper *reaper = (DzlDirectoryReaper *)object;
3604   IdePipeline *self;
3605   g_autoptr(IdeTask) task = user_data;
3606   g_autoptr(GError) error = NULL;
3607   TaskData *td;
3608 
3609   IDE_ENTRY;
3610 
3611   g_assert (DZL_IS_DIRECTORY_REAPER (reaper));
3612   g_assert (G_IS_ASYNC_RESULT (result));
3613   g_assert (IDE_IS_TASK (task));
3614 
3615   td = ide_task_get_task_data (task);
3616 
3617   g_assert (td != NULL);
3618   g_assert (td->task == task);
3619   g_assert (td->type == TASK_REBUILD);
3620 
3621   self = ide_task_get_source_object (task);
3622   g_assert (IDE_IS_PIPELINE (self));
3623 
3624   /* Make sure our reaper completed or else we bail */
3625   if (!dzl_directory_reaper_execute_finish (reaper, result, &error))
3626     {
3627       ide_task_return_error (task, g_steal_pointer (&error));
3628       IDE_EXIT;
3629     }
3630 
3631   if (td->phase == IDE_PIPELINE_PHASE_NONE)
3632     {
3633       ide_task_return_boolean (task, TRUE);
3634       IDE_EXIT;
3635     }
3636 
3637   /* Perform a build using the same task and skipping the build queue. */
3638   ide_pipeline_tick_build (self, task);
3639 
3640   IDE_EXIT;
3641 }
3642 
3643 static void
ide_pipeline_tick_rebuild(IdePipeline * self,IdeTask * task)3644 ide_pipeline_tick_rebuild (IdePipeline *self,
3645                                  IdeTask          *task)
3646 {
3647   g_autoptr(DzlDirectoryReaper) reaper = NULL;
3648   GCancellable *cancellable;
3649 
3650   IDE_ENTRY;
3651 
3652   g_assert (IDE_IS_PIPELINE (self));
3653   g_assert (IDE_IS_TASK (task));
3654 
3655 #ifndef G_DISABLE_ASSERT
3656   {
3657     TaskData *td = ide_task_get_task_data (task);
3658 
3659     g_assert (td != NULL);
3660     g_assert (td->type == TASK_REBUILD);
3661     g_assert (td->task == task);
3662   }
3663 #endif
3664 
3665   reaper = dzl_directory_reaper_new ();
3666 
3667   /*
3668    * Check if we can remove the builddir. We don't want to do this if it is the
3669    * same as the srcdir (in-tree builds).
3670    */
3671   if (can_remove_builddir (self))
3672     {
3673       g_autoptr(GFile) builddir = g_file_new_for_path (self->builddir);
3674 
3675       dzl_directory_reaper_add_directory (reaper, builddir, 0);
3676     }
3677 
3678   /*
3679    * Now let the build stages add any files they might want to reap as part of
3680    * the rebuild process.
3681    */
3682   for (guint i = 0; i < self->pipeline->len; i++)
3683     {
3684       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
3685 
3686       ide_pipeline_stage_emit_reap (entry->stage, reaper);
3687       ide_pipeline_stage_set_completed (entry->stage, FALSE);
3688     }
3689 
3690   cancellable = ide_task_get_cancellable (task);
3691   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
3692 
3693   /* Now build the reaper to clean up the build files. */
3694   dzl_directory_reaper_execute_async (reaper,
3695                                       cancellable,
3696                                       ide_pipeline_reaper_cb,
3697                                       g_object_ref (task));
3698 
3699   IDE_EXIT;
3700 }
3701 
3702 /**
3703  * ide_pipeline_rebuild_async:
3704  * @self: A @IdePipeline
3705  * @phase: the requested build phase
3706  * @targets: (element-type IdeBuildTarget) (nullable): an array of
3707  *   #IdeBuildTarget or %NULL
3708  * @cancellable: (nullable): a #GCancellable or %NULL
3709  * @callback: a callback to build upon completion
3710  * @user_data: data for @callback
3711  *
3712  * Asynchronously starts the build pipeline after cleaning any
3713  * existing build artifacts.
3714  *
3715  * Since: 3.32
3716  */
3717 void
ide_pipeline_rebuild_async(IdePipeline * self,IdePipelinePhase phase,GPtrArray * targets,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3718 ide_pipeline_rebuild_async (IdePipeline         *self,
3719                             IdePipelinePhase     phase,
3720                             GPtrArray           *targets,
3721                             GCancellable        *cancellable,
3722                             GAsyncReadyCallback  callback,
3723                             gpointer             user_data)
3724 {
3725   g_autoptr(IdeTask) task = NULL;
3726   TaskData *td;
3727 
3728   IDE_ENTRY;
3729 
3730   g_return_if_fail (IDE_IS_PIPELINE (self));
3731   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
3732   g_return_if_fail ((phase & ~IDE_PIPELINE_PHASE_MASK) == 0);
3733 
3734   drop_caches (self);
3735 
3736   cancellable = dzl_cancellable_chain (cancellable, self->cancellable);
3737 
3738   task = ide_task_new (self, cancellable, callback, user_data);
3739   ide_task_set_priority (task, G_PRIORITY_LOW);
3740   ide_task_set_source_tag (task, ide_pipeline_rebuild_async);
3741 
3742   if (!ide_pipeline_check_ready (self, task))
3743     return;
3744 
3745   td = task_data_new (task, TASK_REBUILD);
3746   td->phase = phase;
3747   td->rebuild.targets = _g_ptr_array_copy_objects (targets);
3748   ide_task_set_task_data (task, td, task_data_free);
3749 
3750   g_queue_push_tail (&self->task_queue, g_steal_pointer (&task));
3751 
3752   ide_pipeline_queue_flush (self);
3753 
3754   IDE_EXIT;
3755 }
3756 
3757 gboolean
ide_pipeline_rebuild_finish(IdePipeline * self,GAsyncResult * result,GError ** error)3758 ide_pipeline_rebuild_finish (IdePipeline  *self,
3759                                    GAsyncResult      *result,
3760                                    GError           **error)
3761 {
3762   gboolean ret;
3763 
3764   IDE_ENTRY;
3765 
3766   g_assert (IDE_IS_PIPELINE (self));
3767   g_assert (IDE_IS_TASK (result));
3768 
3769   ret = ide_task_propagate_boolean (IDE_TASK (result), error);
3770 
3771   IDE_RETURN (ret);
3772 }
3773 
3774 /**
3775  * ide_pipeline_get_can_export:
3776  * @self: a #IdePipeline
3777  *
3778  * This function is useful to discover if there are any pipeline addins
3779  * which implement the export phase. UI or GAction implementations may
3780  * want to use this value to set the enabled state of the action or
3781  * sensitivity of a button.
3782  *
3783  * Returns: %TRUE if there are export pipeline stages.
3784  *
3785  * Since: 3.32
3786  */
3787 gboolean
ide_pipeline_get_can_export(IdePipeline * self)3788 ide_pipeline_get_can_export (IdePipeline *self)
3789 {
3790   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
3791 
3792   if (self->broken)
3793     return FALSE;
3794 
3795   for (guint i = 0; i < self->pipeline->len; i++)
3796     {
3797       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
3798 
3799       if ((entry->phase & IDE_PIPELINE_PHASE_EXPORT) != 0)
3800         return TRUE;
3801     }
3802 
3803   return FALSE;
3804 }
3805 
3806 void
_ide_pipeline_set_message(IdePipeline * self,const gchar * message)3807 _ide_pipeline_set_message (IdePipeline *self,
3808                                  const gchar      *message)
3809 {
3810   g_return_if_fail (IDE_IS_PIPELINE (self));
3811 
3812   if (message != NULL)
3813     {
3814       /*
3815        * Special case to deal with messages coming from systems we
3816        * know prefix the build tooling information to the message.
3817        * It's easier to just do this here rather than provide some
3818        * sort of API for plugins to do this for us.
3819        */
3820       if (g_str_has_prefix (message, "flatpak-builder: "))
3821         message += strlen ("flatpak-builder: ");
3822       else if (g_str_has_prefix (message, "jhbuild:"))
3823         message += strlen ("jhbuild:");
3824     }
3825 
3826   if (!ide_str_equal0 (message, self->message))
3827     {
3828       g_free (self->message);
3829       self->message = g_strdup (message);
3830       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
3831     }
3832 }
3833 
3834 void
_ide_pipeline_cancel(IdePipeline * self)3835 _ide_pipeline_cancel (IdePipeline *self)
3836 {
3837   g_autoptr(GCancellable) cancellable = NULL;
3838 
3839   g_return_if_fail (IDE_IS_PIPELINE (self));
3840 
3841   cancellable = g_steal_pointer (&self->cancellable);
3842   self->cancellable = g_cancellable_new ();
3843   g_cancellable_cancel (cancellable);
3844 }
3845 
3846 /**
3847  * ide_pipeline_has_configured:
3848  * @self: a #IdePipeline
3849  *
3850  * Checks to see if the pipeline has advanced far enough to ensure that
3851  * the configure stage has been reached.
3852  *
3853  * Returns: %TRUE if %IDE_PIPELINE_PHASE_CONFIGURE has been reached.
3854  *
3855  * Since: 3.32
3856  */
3857 gboolean
ide_pipeline_has_configured(IdePipeline * self)3858 ide_pipeline_has_configured (IdePipeline *self)
3859 {
3860   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
3861 
3862   if (self->broken)
3863     return FALSE;
3864 
3865   /*
3866    * We need to walk from beginning towards end (instead of
3867    * taking a cleaner approach that would be to walk from the
3868    * end forward) because it's possible for some items to be
3869    * marked completed before they've ever been run.
3870    *
3871    * So just walk forward and we have configured if we hit
3872    * any phase that is CONFIGURE and has completed, or no
3873    * configure phases were found.
3874    */
3875 
3876   for (guint i = 0; i < self->pipeline->len; i++)
3877     {
3878       const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
3879 
3880       if ((entry->phase & IDE_PIPELINE_PHASE_MASK) < IDE_PIPELINE_PHASE_CONFIGURE)
3881         continue;
3882 
3883       if (entry->phase & IDE_PIPELINE_PHASE_CONFIGURE)
3884         {
3885           /*
3886            * This is a configure phase, ensure that it has been
3887            * completed, or we have not really configured.
3888            */
3889           if (!ide_pipeline_stage_get_completed (entry->stage))
3890             return FALSE;
3891 
3892           /*
3893            * Check the next pipeline entry to ensure that it too
3894            * has been configured.
3895            */
3896           continue;
3897         }
3898 
3899       /*
3900        * We've advanced past CONFIGURE, so anything at this point
3901        * can be considered configured.
3902        */
3903 
3904       return TRUE;
3905     }
3906 
3907   /*
3908    * Technically we could have a build system that only supports
3909    * up to configure. But I don't really care about that case. If
3910    * that ever happens, we need an additional check here that the
3911    * last pipeline entry completed.
3912    */
3913 
3914   return FALSE;
3915 }
3916 
3917 void
_ide_pipeline_mark_broken(IdePipeline * self)3918 _ide_pipeline_mark_broken (IdePipeline *self)
3919 {
3920   g_return_if_fail (IDE_IS_PIPELINE (self));
3921 
3922   self->broken = TRUE;
3923 }
3924 
3925 static GType
ide_pipeline_get_item_type(GListModel * model)3926 ide_pipeline_get_item_type (GListModel *model)
3927 {
3928   return IDE_TYPE_PIPELINE_STAGE;
3929 }
3930 
3931 static guint
ide_pipeline_get_n_items(GListModel * model)3932 ide_pipeline_get_n_items (GListModel *model)
3933 {
3934   IdePipeline *self = (IdePipeline *)model;
3935 
3936   g_assert (IDE_IS_PIPELINE (self));
3937 
3938   return self->pipeline != NULL ? self->pipeline->len : 0;
3939 }
3940 
3941 static gpointer
ide_pipeline_get_item(GListModel * model,guint position)3942 ide_pipeline_get_item (GListModel *model,
3943                              guint       position)
3944 {
3945   IdePipeline *self = (IdePipeline *)model;
3946   const PipelineEntry *entry;
3947 
3948   g_assert (IDE_IS_PIPELINE (self));
3949   g_assert (self->pipeline != NULL);
3950   g_assert (position < self->pipeline->len);
3951 
3952   entry = &g_array_index (self->pipeline, PipelineEntry, position);
3953 
3954   return g_object_ref (entry->stage);
3955 }
3956 
3957 static void
list_model_iface_init(GListModelInterface * iface)3958 list_model_iface_init (GListModelInterface *iface)
3959 {
3960   iface->get_item = ide_pipeline_get_item;
3961   iface->get_item_type = ide_pipeline_get_item_type;
3962   iface->get_n_items = ide_pipeline_get_n_items;
3963 }
3964 
3965 /**
3966  * ide_pipeline_get_requested_phase:
3967  * @self: a #IdePipeline
3968  *
3969  * Gets the phase that has been requested. This can be useful when you want to
3970  * get an idea of where the build pipeline will attempt to advance.
3971  *
3972  * Returns: an #IdePipelinePhase
3973  *
3974  * Since: 3.32
3975  */
3976 IdePipelinePhase
ide_pipeline_get_requested_phase(IdePipeline * self)3977 ide_pipeline_get_requested_phase (IdePipeline *self)
3978 {
3979   IdePipelinePhase requested;
3980   gint msb;
3981 
3982   g_return_val_if_fail (IDE_IS_PIPELINE (self), 0);
3983 
3984   requested = self->requested_mask & IDE_PIPELINE_PHASE_MASK;
3985 
3986   /* We want to return a value that is not a mask of all phases
3987    * that will be run, but just the most signficant phase. This
3988    * is represented by the most-signficant-bit after our phase
3989    * mask has been applied.
3990    */
3991 
3992   msb = g_bit_nth_msf (requested, -1);
3993 
3994   if (msb == -1)
3995     return IDE_PIPELINE_PHASE_NONE;
3996 
3997   return (IdePipelinePhase)(1 << msb);
3998 }
3999 
4000 void
_ide_pipeline_set_pty_size(IdePipeline * self,guint rows,guint columns)4001 _ide_pipeline_set_pty_size (IdePipeline *self,
4002                                   guint             rows,
4003                                   guint             columns)
4004 {
4005   g_return_if_fail (IDE_IS_PIPELINE (self));
4006 
4007   if (self->pty_slave != IDE_PTY_FD_INVALID)
4008     ide_pty_intercept_set_size (&self->intercept, rows, columns);
4009 }
4010 
4011 void
_ide_pipeline_set_runtime(IdePipeline * self,IdeRuntime * runtime)4012 _ide_pipeline_set_runtime (IdePipeline *self,
4013                                  IdeRuntime       *runtime)
4014 {
4015   g_return_if_fail (IDE_IS_PIPELINE (self));
4016   g_return_if_fail (!runtime || IDE_IS_RUNTIME (runtime));
4017 
4018   if (g_set_object (&self->runtime, runtime))
4019     {
4020       IdeBuildSystem *build_system;
4021       IdeContext *context;
4022 
4023       context = ide_object_get_context (IDE_OBJECT (self));
4024       build_system = ide_build_system_from_context (context);
4025 
4026       g_clear_pointer (&self->builddir, g_free);
4027       self->builddir = ide_build_system_get_builddir (build_system, self);
4028     }
4029 }
4030 
4031 void
_ide_pipeline_set_toolchain(IdePipeline * self,IdeToolchain * toolchain)4032 _ide_pipeline_set_toolchain (IdePipeline *self,
4033                                    IdeToolchain     *toolchain)
4034 {
4035   g_return_if_fail (IDE_IS_PIPELINE (self));
4036   g_return_if_fail (!toolchain || IDE_IS_TOOLCHAIN (toolchain));
4037 
4038   ide_object_lock (IDE_OBJECT (self));
4039   if (g_set_object (&self->toolchain, toolchain))
4040     ide_config_set_toolchain (self->config, toolchain);
4041   ide_object_unlock (IDE_OBJECT (self));
4042 }
4043 
4044 /**
4045  * ide_pipeline_ref_toolchain:
4046  * @self: a #IdePipeline
4047  *
4048  * Thread-safe variant of ide_pipeline_get_toolchain().
4049  *
4050  * Returns: (transfer full) (nullable): an #IdeToolchain or %NULL
4051  *
4052  * Since: 3.32
4053  */
4054 IdeToolchain *
ide_pipeline_ref_toolchain(IdePipeline * self)4055 ide_pipeline_ref_toolchain (IdePipeline *self)
4056 {
4057   IdeToolchain *ret = NULL;
4058 
4059   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
4060 
4061   ide_object_lock (IDE_OBJECT (self));
4062   g_set_object (&ret, self->toolchain);
4063   ide_object_unlock (IDE_OBJECT (self));
4064 
4065   return g_steal_pointer (&ret);
4066 }
4067 
4068 void
_ide_pipeline_check_toolchain(IdePipeline * self,IdeDeviceInfo * info)4069 _ide_pipeline_check_toolchain (IdePipeline   *self,
4070                                IdeDeviceInfo *info)
4071 {
4072   g_autoptr(IdeToolchain) toolchain = NULL;
4073   g_autoptr(IdeTriplet) toolchain_triplet = NULL;
4074   g_autoptr(IdeContext) context = NULL;
4075   IdeRuntime *runtime;
4076   IdeTriplet *device_triplet;
4077   IdeToolchainManager *manager;
4078 
4079   IDE_ENTRY;
4080 
4081   g_return_if_fail (IDE_IS_PIPELINE (self));
4082   g_return_if_fail (IDE_IS_DEVICE_INFO (info));
4083 
4084   if (ide_object_in_destruction (IDE_OBJECT (self)))
4085     IDE_EXIT;
4086 
4087   g_set_object (&self->device_info, info);
4088 
4089   context = ide_object_ref_context (IDE_OBJECT (self));
4090   g_return_if_fail (IDE_IS_CONTEXT (context));
4091 
4092   manager = ide_toolchain_manager_from_context (context);
4093   g_return_if_fail (IDE_IS_TOOLCHAIN_MANAGER (manager));
4094 
4095   toolchain = ide_config_get_toolchain (self->config);
4096   runtime = ide_config_get_runtime (self->config);
4097   device_triplet = ide_device_info_get_host_triplet (info);
4098   toolchain_triplet = ide_toolchain_get_host_triplet (toolchain);
4099 
4100   if (self->host_triplet != device_triplet)
4101     {
4102       g_clear_pointer (&self->host_triplet, ide_triplet_unref);
4103       self->host_triplet = ide_triplet_ref (device_triplet);
4104     }
4105 
4106   /* Don't try to initialize too early */
4107   if (ide_toolchain_manager_is_loaded (manager))
4108     IDE_EXIT;
4109 
4110   /* TODO: fallback to most compatible toolchain instead of default */
4111 
4112   if (toolchain == NULL ||
4113       g_strcmp0 (ide_triplet_get_arch (device_triplet),
4114                  ide_triplet_get_arch (toolchain_triplet)) != 0 ||
4115       !ide_runtime_supports_toolchain (runtime, toolchain))
4116     {
4117       g_autoptr(IdeToolchain) default_toolchain = NULL;
4118 
4119       default_toolchain = ide_toolchain_manager_get_toolchain (manager, "default");
4120       _ide_pipeline_set_toolchain (self, default_toolchain);
4121     }
4122 
4123   IDE_EXIT;
4124 }
4125 
4126 /**
4127  * ide_pipeline_get_device:
4128  * @self: a #IdePipeline
4129  *
4130  * Gets the device that the pipeline is building for.
4131  *
4132  * Returns: (transfer none): an #IdeDevice.
4133  *
4134  * Since: 3.32
4135  */
4136 IdeDevice *
ide_pipeline_get_device(IdePipeline * self)4137 ide_pipeline_get_device (IdePipeline *self)
4138 {
4139   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
4140 
4141   return self->device;
4142 }
4143 
4144 /**
4145  * ide_pipeline_get_device_info:
4146  * @self: a #IdePipeline
4147  *
4148  * Gets the device info for the current device.
4149  *
4150  * Returns: (nullable) (transfer none): an #IdeDeviceInfo or %NULL
4151  *
4152  * Since: 3.32
4153  */
4154 IdeDeviceInfo *
ide_pipeline_get_device_info(IdePipeline * self)4155 ide_pipeline_get_device_info (IdePipeline *self)
4156 {
4157   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
4158 
4159   return self->device_info;
4160 }
4161 
4162 /**
4163  * ide_pipeline_is_ready:
4164  * @self: a #IdePipeline
4165  *
4166  * Checks to see if the pipeline has been loaded. Loading may be delayed
4167  * due to various initialization routines that need to complete.
4168  *
4169  * Returns: %TRUE if the pipeline has loaded, otherwise %FALSE
4170  *
4171  * Since: 3.32
4172  */
4173 gboolean
ide_pipeline_is_ready(IdePipeline * self)4174 ide_pipeline_is_ready (IdePipeline *self)
4175 {
4176   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
4177 
4178   return self->loaded;
4179 }
4180 
4181 /**
4182  * ide_pipeline_get_host_triplet:
4183  * @self: a #IdePipeline
4184  *
4185  * Gets the "host" triplet which specifies where the build results will run.
4186  *
4187  * This is a convenience wrapper around getting the triplet from the device
4188  * set for the build pipeline.
4189  *
4190  * Returns: (transfer none): an #IdeTriplet
4191  *
4192  * Since: 3.32
4193  */
4194 IdeTriplet *
ide_pipeline_get_host_triplet(IdePipeline * self)4195 ide_pipeline_get_host_triplet (IdePipeline *self)
4196 {
4197   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
4198 
4199   return self->host_triplet;
4200 }
4201 
4202 /**
4203  * ide_pipeline_is_native:
4204  * @self: a #IdePipeline
4205  *
4206  * This is a helper to check if the triplet that we are compiling
4207  * for matches the host system. That allows some plugins to do less
4208  * work by avoiding some cross-compiling work.
4209  *
4210  * Returns: %FALSE if we're possibly cross-compiling, otherwise %TRUE
4211  *
4212  * Since: 3.32
4213  */
4214 gboolean
ide_pipeline_is_native(IdePipeline * self)4215 ide_pipeline_is_native (IdePipeline *self)
4216 {
4217   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
4218 
4219   if (self->host_triplet != NULL)
4220     return ide_triplet_is_system (self->host_triplet);
4221 
4222   return FALSE;
4223 }
4224 
4225 gchar *
ide_pipeline_get_arch(IdePipeline * self)4226 ide_pipeline_get_arch (IdePipeline *self)
4227 {
4228   IdeRuntime *runtime;
4229 
4230   g_return_val_if_fail (IDE_IS_PIPELINE (self), NULL);
4231 
4232   if (self->device_info != NULL)
4233     {
4234       IdeTriplet *triplet;
4235 
4236       if ((triplet = ide_device_info_get_host_triplet (self->device_info)))
4237         return g_strdup (ide_triplet_get_arch (triplet));
4238     }
4239 
4240   if ((runtime = ide_pipeline_get_runtime (self)))
4241     return ide_runtime_get_arch (runtime);
4242 
4243   return NULL;
4244 }
4245 
4246 /**
4247  * ide_pipeline_contains_program_in_path:
4248  * @self: a #IdePipeline
4249  * @name: the name of a binary
4250  *
4251  * Looks through the runtime and SDK extensions for binaries matching
4252  * @name that may be executed.
4253  *
4254  * Returns: %TRUE if @name was found; otherwise %FALSE
4255  *
4256  * Since: 3.34
4257  */
4258 gboolean
ide_pipeline_contains_program_in_path(IdePipeline * self,const gchar * name,GCancellable * cancellable)4259 ide_pipeline_contains_program_in_path (IdePipeline  *self,
4260                                        const gchar  *name,
4261                                        GCancellable *cancellable)
4262 {
4263   g_return_val_if_fail (IDE_IS_PIPELINE (self), FALSE);
4264   g_return_val_if_fail (name != NULL, FALSE);
4265   g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
4266 
4267   if (self->runtime != NULL)
4268     {
4269       if (ide_runtime_contains_program_in_path (self->runtime, name, cancellable))
4270         return TRUE;
4271     }
4272 
4273   if (self->config != NULL)
4274     {
4275       g_autoptr(GPtrArray) ar = NULL;
4276 
4277       if (g_cancellable_is_cancelled (cancellable))
4278         return FALSE;
4279 
4280       ar = ide_config_get_extensions (self->config);
4281       IDE_PTR_ARRAY_SET_FREE_FUNC (ar, g_object_unref);
4282 
4283       for (guint i = 0; i < ar->len; i++)
4284         {
4285           IdeRuntime *runtime = g_ptr_array_index (ar, i);
4286 
4287           g_assert (IDE_IS_RUNTIME (runtime));
4288 
4289           if (ide_runtime_contains_program_in_path (runtime, name, cancellable))
4290             return TRUE;
4291         }
4292     }
4293 
4294   return FALSE;
4295 }
4296 
4297 /**
4298  * ide_pipeline_addin_find_by_module_name:
4299  * @pipeline: an #IdePipeline
4300  * @module_name: the name of the addin module
4301  *
4302  * Finds the addin (if any) matching the plugin's @module_name.
4303  *
4304  * Returns: (transfer none) (nullable): an #IdePipelineAddin or %NULL
4305  *
4306  * Since: 3.40
4307  */
4308 IdePipelineAddin *
ide_pipeline_addin_find_by_module_name(IdePipeline * pipeline,const gchar * module_name)4309 ide_pipeline_addin_find_by_module_name (IdePipeline *pipeline,
4310                                         const gchar *module_name)
4311 {
4312   PeasPluginInfo *plugin_info;
4313   PeasExtension *ret = NULL;
4314   PeasEngine *engine;
4315 
4316   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
4317   g_return_val_if_fail (IDE_IS_PIPELINE (pipeline), NULL);
4318   g_return_val_if_fail (module_name != NULL, NULL);
4319 
4320   if (pipeline->addins == NULL)
4321     return NULL;
4322 
4323   engine = peas_engine_get_default ();
4324 
4325   if ((plugin_info = peas_engine_get_plugin_info (engine, module_name)))
4326     ret = ide_extension_set_adapter_get_extension (pipeline->addins, plugin_info);
4327 
4328   return IDE_PIPELINE_ADDIN (ret);
4329 }
4330