1 /* ide-pipeline-stage.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-stage"
22
23 #include "config.h"
24
25 #include <libide-threading.h>
26 #include <string.h>
27
28 #include "ide-pipeline.h"
29 #include "ide-pipeline-stage.h"
30 #include "ide-pipeline-stage-private.h"
31
32 typedef struct
33 {
34 gchar *name;
35 IdeBuildLogObserver observer;
36 gpointer observer_data;
37 GDestroyNotify observer_data_destroy;
38 IdeTask *queued_build;
39 gchar *stdout_path;
40 GOutputStream *stdout_stream;
41 gint n_pause;
42 IdePipelinePhase phase;
43 guint completed : 1;
44 guint disabled : 1;
45 guint transient : 1;
46 guint check_stdout : 1;
47 guint active : 1;
48 } IdePipelineStagePrivate;
49
50 G_DEFINE_TYPE_WITH_PRIVATE (IdePipelineStage, ide_pipeline_stage, IDE_TYPE_OBJECT)
51
52 enum {
53 PROP_0,
54 PROP_ACTIVE,
55 PROP_CHECK_STDOUT,
56 PROP_COMPLETED,
57 PROP_DISABLED,
58 PROP_NAME,
59 PROP_STDOUT_PATH,
60 PROP_TRANSIENT,
61 N_PROPS
62 };
63
64 enum {
65 CHAIN,
66 QUERY,
67 REAP,
68 N_SIGNALS
69 };
70
71 static GParamSpec *properties [N_PROPS];
72 static guint signals [N_SIGNALS];
73
74 typedef struct
75 {
76 IdePipelineStage *self;
77 GOutputStream *stream;
78 IdeBuildLogStream stream_type;
79 } Tail;
80
81 static Tail *
tail_new(IdePipelineStage * self,GOutputStream * stream,IdeBuildLogStream stream_type)82 tail_new (IdePipelineStage *self,
83 GOutputStream *stream,
84 IdeBuildLogStream stream_type)
85 {
86 Tail *tail;
87
88 g_assert (IDE_IS_PIPELINE_STAGE (self));
89 g_assert (!stream || G_IS_OUTPUT_STREAM (stream));
90 g_assert (stream_type == IDE_BUILD_LOG_STDOUT || stream_type == IDE_BUILD_LOG_STDERR);
91
92 tail = g_slice_new0 (Tail);
93 tail->self = g_object_ref (self);
94 tail->stream = stream ? g_object_ref (stream) : NULL;
95 tail->stream_type = stream_type;
96
97 return tail;
98 }
99
100 static void
tail_free(Tail * tail)101 tail_free (Tail *tail)
102 {
103 IDE_ENTRY;
104
105 g_clear_object (&tail->self);
106 g_clear_object (&tail->stream);
107 g_slice_free (Tail, tail);
108
109 IDE_EXIT;
110 }
111
112 static void
ide_pipeline_stage_clear_observer(IdePipelineStage * self)113 ide_pipeline_stage_clear_observer (IdePipelineStage *self)
114 {
115 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
116 GDestroyNotify notify = priv->observer_data_destroy;
117 gpointer data = priv->observer_data;
118
119 priv->observer_data_destroy = NULL;
120 priv->observer_data = NULL;
121 priv->observer = NULL;
122
123 if (notify != NULL)
124 notify (data);
125 }
126
127 static gboolean
ide_pipeline_stage_real_build(IdePipelineStage * self,IdePipeline * pipeline,GCancellable * cancellable,GError ** error)128 ide_pipeline_stage_real_build (IdePipelineStage *self,
129 IdePipeline *pipeline,
130 GCancellable *cancellable,
131 GError **error)
132 {
133 g_assert (IDE_IS_PIPELINE_STAGE (self));
134 g_assert (IDE_IS_PIPELINE (pipeline));
135 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
136
137 return TRUE;
138 }
139
140 static void
ide_pipeline_stage_real_build_worker(IdeTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)141 ide_pipeline_stage_real_build_worker (IdeTask *task,
142 gpointer source_object,
143 gpointer task_data,
144 GCancellable *cancellable)
145 {
146 IdePipelineStage *self = source_object;
147 IdePipeline *pipeline = task_data;
148 g_autoptr(GError) error = NULL;
149
150 g_assert (IDE_IS_TASK (task));
151 g_assert (IDE_IS_PIPELINE_STAGE (self));
152 g_assert (IDE_IS_PIPELINE (pipeline));
153
154 if (IDE_PIPELINE_STAGE_GET_CLASS (self)->build (self, pipeline, cancellable, &error))
155 ide_task_return_boolean (task, TRUE);
156 else
157 ide_task_return_error (task, g_steal_pointer (&error));
158 }
159
160 static void
ide_pipeline_stage_real_build_async(IdePipelineStage * self,IdePipeline * pipeline,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)161 ide_pipeline_stage_real_build_async (IdePipelineStage *self,
162 IdePipeline *pipeline,
163 GCancellable *cancellable,
164 GAsyncReadyCallback callback,
165 gpointer user_data)
166 {
167 g_autoptr(IdeTask) task = NULL;
168
169 g_assert (IDE_IS_PIPELINE_STAGE (self));
170 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
171 g_assert (IDE_IS_PIPELINE (pipeline));
172
173 task = ide_task_new (self, cancellable, callback, user_data);
174 ide_task_set_source_tag (task, ide_pipeline_stage_real_build_async);
175 ide_task_set_task_data (task, g_object_ref (pipeline), g_object_unref);
176 ide_task_run_in_thread (task, ide_pipeline_stage_real_build_worker);
177 }
178
179 static gboolean
ide_pipeline_stage_real_build_finish(IdePipelineStage * self,GAsyncResult * result,GError ** error)180 ide_pipeline_stage_real_build_finish (IdePipelineStage *self,
181 GAsyncResult *result,
182 GError **error)
183 {
184 g_assert (IDE_IS_PIPELINE_STAGE (self));
185 g_assert (IDE_IS_TASK (result));
186
187 return ide_task_propagate_boolean (IDE_TASK (result), error);
188 }
189
190 const gchar *
ide_pipeline_stage_get_name(IdePipelineStage * self)191 ide_pipeline_stage_get_name (IdePipelineStage *self)
192 {
193 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
194
195 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), NULL);
196
197 return priv->name;
198 }
199
200 void
ide_pipeline_stage_set_name(IdePipelineStage * self,const gchar * name)201 ide_pipeline_stage_set_name (IdePipelineStage *self,
202 const gchar *name)
203 {
204 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
205
206 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
207
208 if (g_strcmp0 (name, priv->name) != 0)
209 {
210 g_free (priv->name);
211 priv->name = g_strdup (name);
212 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NAME]);
213 }
214 }
215
216 static void
ide_pipeline_stage_real_clean_async(IdePipelineStage * self,IdePipeline * pipeline,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)217 ide_pipeline_stage_real_clean_async (IdePipelineStage *self,
218 IdePipeline *pipeline,
219 GCancellable *cancellable,
220 GAsyncReadyCallback callback,
221 gpointer user_data)
222 {
223 g_autoptr(IdeTask) task = NULL;
224
225 g_assert (IDE_IS_PIPELINE_STAGE (self));
226 g_assert (IDE_IS_PIPELINE (pipeline));
227 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
228
229 task = ide_task_new (self, cancellable, callback, user_data);
230 ide_task_set_source_tag (task, ide_pipeline_stage_real_clean_async);
231
232 ide_pipeline_stage_set_completed (self, FALSE);
233
234 ide_task_return_boolean (task, TRUE);
235 }
236
237 static gboolean
ide_pipeline_stage_real_clean_finish(IdePipelineStage * self,GAsyncResult * result,GError ** error)238 ide_pipeline_stage_real_clean_finish (IdePipelineStage *self,
239 GAsyncResult *result,
240 GError **error)
241 {
242 return ide_task_propagate_boolean (IDE_TASK (result), error);
243 }
244
245 static gboolean
ide_pipeline_stage_real_chain(IdePipelineStage * self,IdePipelineStage * next)246 ide_pipeline_stage_real_chain (IdePipelineStage *self,
247 IdePipelineStage *next)
248 {
249 return FALSE;
250 }
251
252 static void
ide_pipeline_stage_finalize(GObject * object)253 ide_pipeline_stage_finalize (GObject *object)
254 {
255 IdePipelineStage *self = (IdePipelineStage *)object;
256 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
257
258 ide_pipeline_stage_clear_observer (self);
259
260 g_clear_pointer (&priv->name, g_free);
261 g_clear_pointer (&priv->stdout_path, g_free);
262 g_clear_object (&priv->queued_build);
263 g_clear_object (&priv->stdout_stream);
264
265 G_OBJECT_CLASS (ide_pipeline_stage_parent_class)->finalize (object);
266 }
267
268 static void
ide_pipeline_stage_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)269 ide_pipeline_stage_get_property (GObject *object,
270 guint prop_id,
271 GValue *value,
272 GParamSpec *pspec)
273 {
274 IdePipelineStage *self = IDE_PIPELINE_STAGE (object);
275
276 switch (prop_id)
277 {
278 case PROP_ACTIVE:
279 g_value_set_boolean (value, ide_pipeline_stage_get_active (self));
280 break;
281
282 case PROP_CHECK_STDOUT:
283 g_value_set_boolean (value, ide_pipeline_stage_get_check_stdout (self));
284 break;
285
286 case PROP_COMPLETED:
287 g_value_set_boolean (value, ide_pipeline_stage_get_completed (self));
288 break;
289
290 case PROP_DISABLED:
291 g_value_set_boolean (value, ide_pipeline_stage_get_disabled (self));
292 break;
293
294 case PROP_NAME:
295 g_value_set_string (value, ide_pipeline_stage_get_name (self));
296 break;
297
298 case PROP_STDOUT_PATH:
299 g_value_set_string (value, ide_pipeline_stage_get_stdout_path (self));
300 break;
301
302 case PROP_TRANSIENT:
303 g_value_set_boolean (value, ide_pipeline_stage_get_transient (self));
304 break;
305
306 default:
307 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
308 }
309 }
310
311 static void
ide_pipeline_stage_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)312 ide_pipeline_stage_set_property (GObject *object,
313 guint prop_id,
314 const GValue *value,
315 GParamSpec *pspec)
316 {
317 IdePipelineStage *self = IDE_PIPELINE_STAGE (object);
318
319 switch (prop_id)
320 {
321 case PROP_ACTIVE:
322 ide_pipeline_stage_set_active (self, g_value_get_boolean (value));
323 break;
324
325 case PROP_CHECK_STDOUT:
326 ide_pipeline_stage_set_check_stdout (self, g_value_get_boolean (value));
327 break;
328
329 case PROP_COMPLETED:
330 ide_pipeline_stage_set_completed (self, g_value_get_boolean (value));
331 break;
332
333 case PROP_DISABLED:
334 ide_pipeline_stage_set_disabled (self, g_value_get_boolean (value));
335 break;
336
337 case PROP_NAME:
338 ide_pipeline_stage_set_name (self, g_value_get_string (value));
339 break;
340
341 case PROP_STDOUT_PATH:
342 ide_pipeline_stage_set_stdout_path (self, g_value_get_string (value));
343 break;
344
345 case PROP_TRANSIENT:
346 ide_pipeline_stage_set_transient (self, g_value_get_boolean (value));
347 break;
348
349 default:
350 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
351 }
352 }
353
354 static void
ide_pipeline_stage_class_init(IdePipelineStageClass * klass)355 ide_pipeline_stage_class_init (IdePipelineStageClass *klass)
356 {
357 GObjectClass *object_class = G_OBJECT_CLASS (klass);
358
359 object_class->finalize = ide_pipeline_stage_finalize;
360 object_class->get_property = ide_pipeline_stage_get_property;
361 object_class->set_property = ide_pipeline_stage_set_property;
362
363 klass->build = ide_pipeline_stage_real_build;
364 klass->build_async = ide_pipeline_stage_real_build_async;
365 klass->build_finish = ide_pipeline_stage_real_build_finish;
366 klass->clean_async = ide_pipeline_stage_real_clean_async;
367 klass->clean_finish = ide_pipeline_stage_real_clean_finish;
368 klass->chain = ide_pipeline_stage_real_chain;
369
370 /**
371 * IdePipelineStage:active:
372 *
373 * This property is set to %TRUE when the build stage is actively
374 * running or cleaning.
375 *
376 * Since: 3.32
377 */
378 properties [PROP_ACTIVE] =
379 g_param_spec_boolean ("active",
380 "Active",
381 "If the stage is actively running",
382 FALSE,
383 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
384
385 /**
386 * IdePipelineStage:check-stdout:
387 *
388 * Most build systems will preserve stderr for the processes they call, such
389 * as gcc, clang, and others. However, if your build system redirects all
390 * output to stdout, you may need to set this property to %TRUE to ensure
391 * that Builder will extract errors from stdout.
392 *
393 * One such example is Ninja.
394 *
395 * Since: 3.32
396 */
397 properties [PROP_CHECK_STDOUT] =
398 g_param_spec_boolean ("check-stdout",
399 "Check STDOUT",
400 "If STDOUT should be checked for errors using error regexes",
401 FALSE,
402 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
403
404 /**
405 * IdePipelineStage:completed:
406 *
407 * The "completed" property is set to %TRUE after the pipeline has
408 * completed processing the stage. When the pipeline invalidates
409 * phases, completed may be reset to %FALSE.
410 *
411 * Since: 3.32
412 */
413 properties [PROP_COMPLETED] =
414 g_param_spec_boolean ("completed",
415 "Completed",
416 "If the stage has been completed",
417 FALSE,
418 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
419
420 /**
421 * IdePipelineStage:disabled:
422 *
423 * If the build stage is disabled. This allows you to have a stage that is
424 * attached but will not be activated during execution.
425 *
426 * You may enable it later and then re-build the pipeline.
427 *
428 * If the stage is both transient and disabled, it will not be removed during
429 * the transient cleanup phase.
430 *
431 * Since: 3.32
432 */
433 properties [PROP_DISABLED] =
434 g_param_spec_boolean ("disabled",
435 "Disabled",
436 "If the stage has been disabled",
437 FALSE,
438 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
439
440 /**
441 * IdePipelineStage:name:
442 *
443 * The name of the build stage. This is only used by UI to view
444 * the build pipeline.
445 *
446 * Since: 3.32
447 */
448 properties [PROP_NAME] =
449 g_param_spec_string ("name",
450 "Name",
451 "The user visible name of the stage",
452 NULL,
453 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
454
455 /**
456 * IdePipelineStage:stdout-path:
457 *
458 * The "stdout-path" property allows a build stage to redirect its log
459 * messages to a stdout file. Instead of passing stdout along to the
460 * build pipeline, they will be redirected to this file.
461 *
462 * For safety reasons, the contents are first redirected to a temporary
463 * file and will be redirected to the stdout-path location after the
464 * build stage has completed executing.
465 *
466 * Since: 3.32
467 */
468 properties [PROP_STDOUT_PATH] =
469 g_param_spec_string ("stdout-path",
470 "Stdout Path",
471 "Redirect standard output to this path",
472 NULL,
473 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
474
475 /**
476 * IdePipelineStage:transient:
477 *
478 * If the build stage is transient.
479 *
480 * A transient build stage is removed after the completion of
481 * ide_pipeline_build_async(). This can be a convenient
482 * way to add a temporary item to a build pipeline that should
483 * be immediately discarded.
484 *
485 * Since: 3.32
486 */
487 properties [PROP_TRANSIENT] =
488 g_param_spec_boolean ("transient",
489 "Transient",
490 "If the stage should be removed after execution",
491 FALSE,
492 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
493
494 g_object_class_install_properties (object_class, N_PROPS, properties);
495
496 /**
497 * IdePipelineStage:chain:
498 *
499 * We might want to be able to "chain" multiple stages into a single stage
500 * so that we can avoid duplicate work. For example, if we have a "make"
501 * stage immediately follwed by a "make install" stage, it does not make
502 * sense to perform them both individually.
503 *
504 * Returns: %TRUE if @next's work was chained into @self for the next
505 * execution of the pipeline.
506 *
507 * Since: 3.32
508 */
509 signals [CHAIN] =
510 g_signal_new ("chain",
511 G_TYPE_FROM_CLASS (klass),
512 G_SIGNAL_RUN_LAST,
513 G_STRUCT_OFFSET (IdePipelineStageClass, chain),
514 g_signal_accumulator_true_handled,
515 NULL,
516 NULL,
517 G_TYPE_BOOLEAN, 1, IDE_TYPE_PIPELINE_STAGE);
518
519 /**
520 * IdePipelineStage::query:
521 * @self: An #IdePipelineStage
522 * @pipeline: An #IdePipeline
523 * @targets: (element-type IdeBuildTarget) (nullable): an array
524 * of #IdeBuildTarget or %NULL
525 * @cancellable: (nullable): a #GCancellable or %NULL
526 *
527 * The #IdePipelineStage::query signal is emitted to request that the
528 * build stage update its completed stage from any external resources.
529 *
530 * This can be useful if you want to use an existing build stage instances
531 * and use a signal to pause forward progress until an external system
532 * has been checked.
533 *
534 * The targets that the user would like to ensure are built are provided
535 * as @targets. Some #IdePipelineStage may use this to reduce the amount
536 * of work they perform
537 *
538 * For example, in a signal handler, you may call ide_pipeline_stage_pause()
539 * and perform an external operation. Forward progress of the stage will
540 * be paused until a matching number of ide_pipeline_stage_unpause() calls
541 * have been made.
542 *
543 * Since: 3.32
544 */
545 signals [QUERY] =
546 g_signal_new ("query",
547 G_TYPE_FROM_CLASS (klass),
548 G_SIGNAL_RUN_LAST,
549 G_STRUCT_OFFSET (IdePipelineStageClass, query),
550 NULL, NULL, NULL,
551 G_TYPE_NONE,
552 3,
553 IDE_TYPE_PIPELINE,
554 G_TYPE_PTR_ARRAY,
555 G_TYPE_CANCELLABLE);
556
557 /**
558 * IdePipelineStage::reap:
559 * @self: An #IdePipelineStage
560 * @reaper: An #DzlDirectoryReaper
561 *
562 * This signal is emitted when a request to rebuild the project has
563 * occurred. This allows build stages to ensure that certain files are
564 * removed from the system. For example, an autotools build stage might
565 * request that "configure" is removed so that autogen.sh will be Executed
566 * as part of the next build.
567 *
568 * Since: 3.32
569 */
570 signals [REAP] =
571 g_signal_new ("reap",
572 G_TYPE_FROM_CLASS (klass),
573 G_SIGNAL_RUN_LAST,
574 G_STRUCT_OFFSET (IdePipelineStageClass, reap),
575 NULL, NULL, NULL,
576 G_TYPE_NONE, 1, DZL_TYPE_DIRECTORY_REAPER);
577 }
578
579 static void
ide_pipeline_stage_init(IdePipelineStage * self)580 ide_pipeline_stage_init (IdePipelineStage *self)
581 {
582 }
583
584 void
ide_pipeline_stage_build_async(IdePipelineStage * self,IdePipeline * pipeline,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)585 ide_pipeline_stage_build_async (IdePipelineStage *self,
586 IdePipeline *pipeline,
587 GCancellable *cancellable,
588 GAsyncReadyCallback callback,
589 gpointer user_data)
590 {
591 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
592
593 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
594 g_return_if_fail (IDE_IS_PIPELINE (pipeline));
595 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
596
597 if G_UNLIKELY (priv->stdout_path != NULL)
598 {
599 g_autoptr(GFileOutputStream) stream = NULL;
600 g_autoptr(GFile) file = NULL;
601 g_autoptr(GError) error = NULL;
602
603 file = g_file_new_for_path (priv->stdout_path);
604 stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, cancellable, &error);
605
606 if (stream == NULL)
607 {
608 g_task_report_error (self, callback, user_data,
609 ide_pipeline_stage_build_async,
610 g_steal_pointer (&error));
611 return;
612 }
613
614 g_clear_object (&priv->stdout_stream);
615
616 priv->stdout_stream = G_OUTPUT_STREAM (g_steal_pointer (&stream));
617 }
618
619 IDE_PIPELINE_STAGE_GET_CLASS (self)->build_async (self, pipeline, cancellable, callback, user_data);
620 }
621
622 gboolean
ide_pipeline_stage_build_finish(IdePipelineStage * self,GAsyncResult * result,GError ** error)623 ide_pipeline_stage_build_finish (IdePipelineStage *self,
624 GAsyncResult *result,
625 GError **error)
626 {
627 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
628
629 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
630 g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
631
632 /*
633 * If for some reason build_finish() is not called (likely due to use of
634 * the build stage without a pipeline, so sort of a programming error) then
635 * we won't clean up the stdout stream. But it gets cleaned up in finalize
636 * anyway, so its safe (if only delayed rename()).
637 *
638 * We can just unref the stream, and the close will happen silently. We need
639 * to do this as some async reads to be proxied to the stream may occur after
640 * the build_finish() completes.
641 *
642 * The Tail structure has it's own reference to stdout_stream.
643 */
644 g_clear_object (&priv->stdout_stream);
645
646 return IDE_PIPELINE_STAGE_GET_CLASS (self)->build_finish (self, result, error);
647 }
648
649 /**
650 * ide_pipeline_stage_set_log_observer:
651 * @self: An #IdePipelineStage
652 * @observer: (scope async): The observer for the log entries
653 * @observer_data: data for @observer
654 * @observer_data_destroy: destroy callback for @observer_data
655 *
656 * Sets the log observer to handle calls to the various stage logging
657 * functions. This will be set by the pipeline to mux logs from all
658 * stages into a unified build log.
659 *
660 * Plugins that need to handle logging from a build stage should set
661 * an observer on the pipeline so that log distribution may be fanned
662 * out to all observers.
663 *
664 * Since: 3.32
665 */
666 void
ide_pipeline_stage_set_log_observer(IdePipelineStage * self,IdeBuildLogObserver observer,gpointer observer_data,GDestroyNotify observer_data_destroy)667 ide_pipeline_stage_set_log_observer (IdePipelineStage *self,
668 IdeBuildLogObserver observer,
669 gpointer observer_data,
670 GDestroyNotify observer_data_destroy)
671 {
672 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
673
674 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
675
676 ide_pipeline_stage_clear_observer (self);
677
678 priv->observer = observer;
679 priv->observer_data = observer_data;
680 priv->observer_data_destroy = observer_data_destroy;
681 }
682
683 static void
ide_pipeline_stage_log_internal(IdePipelineStage * self,IdeBuildLogStream stream_type,GOutputStream * stream,const gchar * message,gssize message_len)684 ide_pipeline_stage_log_internal (IdePipelineStage *self,
685 IdeBuildLogStream stream_type,
686 GOutputStream *stream,
687 const gchar *message,
688 gssize message_len)
689 {
690 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
691
692 /*
693 * If we are logging to a file instead of the build pipeline, handle that
694 * specially now and then exit without calling the observer.
695 */
696 if (stream != NULL)
697 {
698 gsize count;
699
700 if G_UNLIKELY (message_len < 0)
701 message_len = strlen (message);
702
703 g_output_stream_write_all (stream, message, message_len, &count, NULL, NULL);
704 g_output_stream_write_all (stream, "\n", 1, &count, NULL, NULL);
705
706 return;
707 }
708
709 if G_LIKELY (priv->observer != NULL)
710 priv->observer (stream_type, message, message_len, priv->observer_data);
711 }
712
713 void
ide_pipeline_stage_log(IdePipelineStage * self,IdeBuildLogStream stream_type,const gchar * message,gssize message_len)714 ide_pipeline_stage_log (IdePipelineStage *self,
715 IdeBuildLogStream stream_type,
716 const gchar *message,
717 gssize message_len)
718 {
719 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
720
721 if (stream_type == IDE_BUILD_LOG_STDOUT)
722 ide_pipeline_stage_log_internal (self, stream_type, priv->stdout_stream, message, message_len);
723 else
724 ide_pipeline_stage_log_internal (self, stream_type, NULL, message, message_len);
725 }
726
727 gboolean
ide_pipeline_stage_get_completed(IdePipelineStage * self)728 ide_pipeline_stage_get_completed (IdePipelineStage *self)
729 {
730 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
731
732 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
733
734 return priv->completed;
735 }
736
737 void
ide_pipeline_stage_set_completed(IdePipelineStage * self,gboolean completed)738 ide_pipeline_stage_set_completed (IdePipelineStage *self,
739 gboolean completed)
740 {
741 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
742
743 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
744
745 completed = !!completed;
746
747 if (completed != priv->completed)
748 {
749 priv->completed = completed;
750 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COMPLETED]);
751 }
752 }
753
754 void
ide_pipeline_stage_set_transient(IdePipelineStage * self,gboolean transient)755 ide_pipeline_stage_set_transient (IdePipelineStage *self,
756 gboolean transient)
757 {
758 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
759
760 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
761
762 transient = !!transient;
763
764 if (priv->transient != transient)
765 {
766 priv->transient = transient;
767 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TRANSIENT]);
768 }
769 }
770
771 gboolean
ide_pipeline_stage_get_transient(IdePipelineStage * self)772 ide_pipeline_stage_get_transient (IdePipelineStage *self)
773 {
774 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
775
776 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
777
778 return priv->transient;
779 }
780
781 static void
ide_pipeline_stage_observe_stream_cb(GObject * object,GAsyncResult * result,gpointer user_data)782 ide_pipeline_stage_observe_stream_cb (GObject *object,
783 GAsyncResult *result,
784 gpointer user_data)
785 {
786 GDataInputStream *stream = (GDataInputStream *)object;
787 g_autofree gchar *line = NULL;
788 g_autoptr(GError) error = NULL;
789 Tail *tail = user_data;
790 gsize n_read = 0;
791
792 g_assert (G_IS_DATA_INPUT_STREAM (stream));
793 g_assert (G_IS_ASYNC_RESULT (result));
794 g_assert (tail != NULL);
795
796 line = g_data_input_stream_read_line_finish_utf8 (stream, result, &n_read, &error);
797
798 if (error == NULL)
799 {
800 if (line == NULL)
801 goto cleanup;
802
803 ide_pipeline_stage_log_internal (tail->self, tail->stream_type, tail->stream, line, (gssize)n_read);
804
805 if G_UNLIKELY (g_input_stream_is_closed (G_INPUT_STREAM (stream)))
806 goto cleanup;
807
808 g_data_input_stream_read_line_async (stream,
809 G_PRIORITY_DEFAULT,
810 NULL,
811 ide_pipeline_stage_observe_stream_cb,
812 tail);
813
814 return;
815 }
816
817 g_debug ("%s", error->message);
818
819 cleanup:
820 tail_free (tail);
821 }
822
823
824 static void
ide_pipeline_stage_observe_stream(IdePipelineStage * self,IdeBuildLogStream stream_type,GInputStream * stream)825 ide_pipeline_stage_observe_stream (IdePipelineStage *self,
826 IdeBuildLogStream stream_type,
827 GInputStream *stream)
828 {
829 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
830 g_autoptr(GDataInputStream) data_stream = NULL;
831 Tail *tail;
832
833 g_assert (IDE_IS_PIPELINE_STAGE (self));
834 g_assert (stream_type == IDE_BUILD_LOG_STDOUT || stream_type == IDE_BUILD_LOG_STDERR);
835 g_assert (G_IS_INPUT_STREAM (stream));
836
837 if (G_IS_DATA_INPUT_STREAM (stream))
838 data_stream = g_object_ref (G_DATA_INPUT_STREAM (stream));
839 else
840 data_stream = g_data_input_stream_new (stream);
841
842 IDE_TRACE_MSG ("Logging subprocess stream of type %s as %s",
843 G_OBJECT_TYPE_NAME (data_stream),
844 stream_type == IDE_BUILD_LOG_STDOUT ? "stdout" : "stderr");
845
846 if (stream_type == IDE_BUILD_LOG_STDOUT)
847 tail = tail_new (self, priv->stdout_stream, stream_type);
848 else
849 tail = tail_new (self, NULL, stream_type);
850
851 g_data_input_stream_read_line_async (data_stream,
852 G_PRIORITY_DEFAULT,
853 NULL,
854 ide_pipeline_stage_observe_stream_cb,
855 tail);
856 }
857
858 /**
859 * ide_pipeline_stage_log_subprocess:
860 * @self: An #IdePipelineStage
861 * @subprocess: An #IdeSubprocess
862 *
863 * This function will begin logging @subprocess by reading from the
864 * stdout and stderr streams of the subprocess. You must have created
865 * the subprocess with %G_SUBPROCESS_FLAGS_STDERR_PIPE and
866 * %G_SUBPROCESS_FLAGS_STDOUT_PIPE so that the streams may be read.
867 *
868 * Since: 3.32
869 */
870 void
ide_pipeline_stage_log_subprocess(IdePipelineStage * self,IdeSubprocess * subprocess)871 ide_pipeline_stage_log_subprocess (IdePipelineStage *self,
872 IdeSubprocess *subprocess)
873 {
874 GInputStream *stdout_stream;
875 GInputStream *stderr_stream;
876
877 IDE_ENTRY;
878
879 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
880 g_return_if_fail (IDE_IS_SUBPROCESS (subprocess));
881
882 stderr_stream = ide_subprocess_get_stderr_pipe (subprocess);
883 stdout_stream = ide_subprocess_get_stdout_pipe (subprocess);
884
885 if (stderr_stream != NULL)
886 ide_pipeline_stage_observe_stream (self, IDE_BUILD_LOG_STDERR, stderr_stream);
887
888 if (stdout_stream != NULL)
889 ide_pipeline_stage_observe_stream (self, IDE_BUILD_LOG_STDOUT, stdout_stream);
890
891 IDE_EXIT;
892 }
893
894 void
ide_pipeline_stage_pause(IdePipelineStage * self)895 ide_pipeline_stage_pause (IdePipelineStage *self)
896 {
897 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
898
899 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
900
901 g_atomic_int_inc (&priv->n_pause);
902 }
903
904 static void
ide_pipeline_stage_unpause_build_cb(GObject * object,GAsyncResult * result,gpointer user_data)905 ide_pipeline_stage_unpause_build_cb (GObject *object,
906 GAsyncResult *result,
907 gpointer user_data)
908 {
909 IdePipelineStage *self = (IdePipelineStage *)object;
910 g_autoptr(IdeTask) task = user_data;
911 g_autoptr(GError) error = NULL;
912
913 g_assert (IDE_IS_PIPELINE_STAGE (self));
914 g_assert (IDE_IS_TASK (task));
915
916 if (!ide_pipeline_stage_build_finish (self, result, &error))
917 ide_task_return_error (task, g_steal_pointer (&error));
918 else
919 ide_task_return_boolean (task, TRUE);
920 }
921
922 void
ide_pipeline_stage_unpause(IdePipelineStage * self)923 ide_pipeline_stage_unpause (IdePipelineStage *self)
924 {
925 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
926
927 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
928 g_return_if_fail (priv->n_pause > 0);
929
930 if (g_atomic_int_dec_and_test (&priv->n_pause) && priv->queued_build != NULL)
931 {
932 g_autoptr(IdeTask) task = g_steal_pointer (&priv->queued_build);
933 GCancellable *cancellable = ide_task_get_cancellable (task);
934 IdePipeline *pipeline = ide_task_get_task_data (task);
935
936 g_assert (IDE_IS_TASK (task));
937 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
938 g_assert (IDE_IS_PIPELINE (pipeline));
939
940 if (priv->completed)
941 {
942 ide_task_return_boolean (task, TRUE);
943 return;
944 }
945
946 ide_pipeline_stage_build_async (self,
947 pipeline,
948 cancellable,
949 ide_pipeline_stage_unpause_build_cb,
950 g_steal_pointer (&task));
951 }
952 }
953
954 /**
955 * _ide_pipeline_stage_build_with_query_async: (skip)
956 *
957 * This function is used to build the build stage after emitting the
958 * query signal. If the stage is paused after the query, build will
959 * be delayed until the correct number of ide_pipeline_stage_unpause() calls
960 * have occurred.
961 *
962 * Since: 3.32
963 */
964 void
_ide_pipeline_stage_build_with_query_async(IdePipelineStage * self,IdePipeline * pipeline,GPtrArray * targets,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)965 _ide_pipeline_stage_build_with_query_async (IdePipelineStage *self,
966 IdePipeline *pipeline,
967 GPtrArray *targets,
968 GCancellable *cancellable,
969 GAsyncReadyCallback callback,
970 gpointer user_data)
971 {
972 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
973 g_autoptr(GPtrArray) local_targets = NULL;
974 g_autoptr(IdeTask) task = NULL;
975
976 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
977 g_return_if_fail (IDE_IS_PIPELINE (pipeline));
978 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
979
980 task = ide_task_new (self, cancellable, callback, user_data);
981 ide_task_set_source_tag (task, _ide_pipeline_stage_build_with_query_async);
982 ide_task_set_task_data (task, g_object_ref (pipeline), g_object_unref);
983
984 if (targets == NULL)
985 targets = local_targets = g_ptr_array_new_with_free_func (g_object_unref);
986
987 if (priv->queued_build != NULL)
988 {
989 ide_task_return_new_error (task,
990 G_IO_ERROR,
991 G_IO_ERROR_PENDING,
992 "A build is already in progress");
993 return;
994 }
995
996 priv->queued_build = g_steal_pointer (&task);
997
998 /*
999 * Pause the pipeline around our query call so that any call to
1000 * pause/unpause does not cause the stage to make progress. This allows
1001 * us to share the code-path to make progress on the build stage.
1002 */
1003 ide_pipeline_stage_pause (self);
1004 g_signal_emit (self, signals [QUERY], 0, pipeline, targets, cancellable);
1005 ide_pipeline_stage_unpause (self);
1006 }
1007
1008 gboolean
_ide_pipeline_stage_build_with_query_finish(IdePipelineStage * self,GAsyncResult * result,GError ** error)1009 _ide_pipeline_stage_build_with_query_finish (IdePipelineStage *self,
1010 GAsyncResult *result,
1011 GError **error)
1012 {
1013 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
1014 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
1015
1016 return ide_task_propagate_boolean (IDE_TASK (result), error);
1017 }
1018
1019 void
ide_pipeline_stage_set_stdout_path(IdePipelineStage * self,const gchar * stdout_path)1020 ide_pipeline_stage_set_stdout_path (IdePipelineStage *self,
1021 const gchar *stdout_path)
1022 {
1023 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1024
1025 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
1026
1027 if (g_strcmp0 (stdout_path, priv->stdout_path) != 0)
1028 {
1029 g_free (priv->stdout_path);
1030 priv->stdout_path = g_strdup (stdout_path);
1031 g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STDOUT_PATH]);
1032 }
1033 }
1034
1035 const gchar *
ide_pipeline_stage_get_stdout_path(IdePipelineStage * self)1036 ide_pipeline_stage_get_stdout_path (IdePipelineStage *self)
1037 {
1038 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1039
1040 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), NULL);
1041
1042 return priv->stdout_path;
1043 }
1044
1045 gboolean
_ide_pipeline_stage_has_query(IdePipelineStage * self)1046 _ide_pipeline_stage_has_query (IdePipelineStage *self)
1047 {
1048 IDE_ENTRY;
1049
1050 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
1051
1052 if (g_signal_has_handler_pending (self, signals [QUERY], 0, FALSE))
1053 IDE_RETURN (TRUE);
1054
1055 if (IDE_PIPELINE_STAGE_GET_CLASS (self)->query)
1056 IDE_RETURN (TRUE);
1057
1058 IDE_RETURN (FALSE);
1059 }
1060
1061 void
ide_pipeline_stage_clean_async(IdePipelineStage * self,IdePipeline * pipeline,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1062 ide_pipeline_stage_clean_async (IdePipelineStage *self,
1063 IdePipeline *pipeline,
1064 GCancellable *cancellable,
1065 GAsyncReadyCallback callback,
1066 gpointer user_data)
1067 {
1068 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
1069 g_return_if_fail (IDE_IS_PIPELINE (pipeline));
1070 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1071
1072 IDE_PIPELINE_STAGE_GET_CLASS (self)->clean_async (self, pipeline, cancellable, callback, user_data);
1073 }
1074
1075 gboolean
ide_pipeline_stage_clean_finish(IdePipelineStage * self,GAsyncResult * result,GError ** error)1076 ide_pipeline_stage_clean_finish (IdePipelineStage *self,
1077 GAsyncResult *result,
1078 GError **error)
1079 {
1080 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
1081 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
1082
1083 return IDE_PIPELINE_STAGE_GET_CLASS (self)->clean_finish (self, result, error);
1084 }
1085
1086 void
ide_pipeline_stage_emit_reap(IdePipelineStage * self,DzlDirectoryReaper * reaper)1087 ide_pipeline_stage_emit_reap (IdePipelineStage *self,
1088 DzlDirectoryReaper *reaper)
1089 {
1090 IDE_ENTRY;
1091
1092 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
1093 g_return_if_fail (DZL_IS_DIRECTORY_REAPER (reaper));
1094
1095 g_signal_emit (self, signals [REAP], 0, reaper);
1096
1097 IDE_EXIT;
1098 }
1099
1100 gboolean
ide_pipeline_stage_chain(IdePipelineStage * self,IdePipelineStage * next)1101 ide_pipeline_stage_chain (IdePipelineStage *self,
1102 IdePipelineStage *next)
1103 {
1104 gboolean ret = FALSE;
1105
1106 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
1107 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (next), FALSE);
1108
1109 if (ide_pipeline_stage_get_disabled (next))
1110 return FALSE;
1111
1112 g_signal_emit (self, signals[CHAIN], 0, next, &ret);
1113
1114 return ret;
1115 }
1116
1117 gboolean
ide_pipeline_stage_get_disabled(IdePipelineStage * self)1118 ide_pipeline_stage_get_disabled (IdePipelineStage *self)
1119 {
1120 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1121
1122 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
1123
1124 return priv->disabled;
1125 }
1126
1127 void
ide_pipeline_stage_set_disabled(IdePipelineStage * self,gboolean disabled)1128 ide_pipeline_stage_set_disabled (IdePipelineStage *self,
1129 gboolean disabled)
1130 {
1131 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1132
1133 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
1134
1135 disabled = !!disabled;
1136
1137 if (priv->disabled != disabled)
1138 {
1139 priv->disabled = disabled;
1140 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DISABLED]);
1141 }
1142 }
1143
1144 gboolean
ide_pipeline_stage_get_check_stdout(IdePipelineStage * self)1145 ide_pipeline_stage_get_check_stdout (IdePipelineStage *self)
1146 {
1147 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1148
1149 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
1150
1151 return priv->check_stdout;
1152 }
1153
1154 void
ide_pipeline_stage_set_check_stdout(IdePipelineStage * self,gboolean check_stdout)1155 ide_pipeline_stage_set_check_stdout (IdePipelineStage *self,
1156 gboolean check_stdout)
1157 {
1158 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1159
1160 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
1161
1162 check_stdout = !!check_stdout;
1163
1164 if (check_stdout != priv->check_stdout)
1165 {
1166 priv->check_stdout = check_stdout;
1167 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHECK_STDOUT]);
1168 }
1169 }
1170
1171 /**
1172 * ide_pipeline_stage_get_active:
1173 * @self: a #IdePipelineStage
1174 *
1175 * Gets the "active" property, which is set to %TRUE when the
1176 * build stage is actively executing or cleaning.
1177 *
1178 * Returns: %TRUE if the stage is actively executing or cleaning.
1179 *
1180 * Since: 3.32
1181 */
1182 gboolean
ide_pipeline_stage_get_active(IdePipelineStage * self)1183 ide_pipeline_stage_get_active (IdePipelineStage *self)
1184 {
1185 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1186
1187 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), FALSE);
1188
1189 return priv->active;
1190 }
1191
1192 void
ide_pipeline_stage_set_active(IdePipelineStage * self,gboolean active)1193 ide_pipeline_stage_set_active (IdePipelineStage *self,
1194 gboolean active)
1195 {
1196 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1197
1198 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
1199
1200 active = !!active;
1201
1202 if (priv->active != active)
1203 {
1204 priv->active = active;
1205 ide_object_notify_in_main (IDE_OBJECT (self), properties [PROP_ACTIVE]);
1206 }
1207 }
1208
1209 IdePipelinePhase
_ide_pipeline_stage_get_phase(IdePipelineStage * self)1210 _ide_pipeline_stage_get_phase (IdePipelineStage *self)
1211 {
1212 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1213
1214 g_return_val_if_fail (IDE_IS_PIPELINE_STAGE (self), 0);
1215
1216 return priv->phase;
1217 }
1218
1219 void
_ide_pipeline_stage_set_phase(IdePipelineStage * self,IdePipelinePhase phase)1220 _ide_pipeline_stage_set_phase (IdePipelineStage *self,
1221 IdePipelinePhase phase)
1222 {
1223 IdePipelineStagePrivate *priv = ide_pipeline_stage_get_instance_private (self);
1224
1225 g_return_if_fail (IDE_IS_PIPELINE_STAGE (self));
1226
1227 priv->phase = phase;
1228 }
1229