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