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 * "(?<filename>[a-zA-Z0-9\\-\\.\\/_]+):"
3132 * "(?<line>\\d+):"
3133 * "(?<column>\\d+): "
3134 * "(?<level>[\\w\\s]+): "
3135 * "(?<message>.*)"
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 > 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