[gnome-builder/wip/chergert/pipeline] wip: start on build pipeline/stages
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/pipeline] wip: start on build pipeline/stages
- Date: Fri, 16 Dec 2016 03:25:03 +0000 (UTC)
commit fed8ec1fd0939996c4d4d16fcb02f6ee1c485835
Author: Christian Hergert <chergert redhat com>
Date: Thu Dec 15 19:24:40 2016 -0800
wip: start on build pipeline/stages
libide/Makefile.am | 13 +
libide/buildsystem/ide-build-log-private.h | 44 +
libide/buildsystem/ide-build-log.c | 211 +++++
libide/buildsystem/ide-build-log.h | 39 +
libide/buildsystem/ide-build-pipeline-addin.c | 58 ++
libide/buildsystem/ide-build-pipeline-addin.h | 49 ++
libide/buildsystem/ide-build-pipeline.c | 826 ++++++++++++++++++++
libide/buildsystem/ide-build-pipeline.h | 87 ++
libide/buildsystem/ide-build-stage-launcher.c | 337 ++++++++
libide/buildsystem/ide-build-stage-launcher.h | 41 +
libide/buildsystem/ide-build-stage.c | 278 +++++++
libide/buildsystem/ide-build-stage.h | 85 ++
libide/ide-enums.c.in | 2 +
libide/ide-types.h | 4 +-
libide/ide.h | 4 +
plugins/autotools/Makefile.am | 2 +
plugins/autotools/autotools-plugin.c | 2 +
.../autotools/ide-autotools-build-pipeline-addin.c | 182 +++++
.../autotools/ide-autotools-build-pipeline-addin.h | 32 +
tests/Makefile.am | 6 +
tests/test-ide-build-pipeline.c | 115 +++
21 files changed, 2416 insertions(+), 1 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 987d890..07008c8 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -32,7 +32,12 @@ libide_1_0_la_public_headers = \
buffers/ide-unsaved-files.h \
buildsystem/ide-build-command.h \
buildsystem/ide-build-command-queue.h \
+ buildsystem/ide-build-log.h \
buildsystem/ide-build-manager.h \
+ buildsystem/ide-build-pipeline.h \
+ buildsystem/ide-build-pipeline-addin.h \
+ buildsystem/ide-build-stage.h \
+ buildsystem/ide-build-stage-launcher.h \
buildsystem/ide-build-result-addin.h \
buildsystem/ide-build-result.h \
buildsystem/ide-build-system.h \
@@ -208,6 +213,10 @@ libide_1_0_la_public_sources = \
buildsystem/ide-build-command.c \
buildsystem/ide-build-command-queue.c \
buildsystem/ide-build-manager.c \
+ buildsystem/ide-build-pipeline.c \
+ buildsystem/ide-build-pipeline-addin.c \
+ buildsystem/ide-build-stage.c \
+ buildsystem/ide-build-stage-launcher.c \
buildsystem/ide-build-result-addin.c \
buildsystem/ide-build-result.c \
buildsystem/ide-build-system.c \
@@ -374,6 +383,8 @@ libide_1_0_la_SOURCES = \
application/ide-application-private.h \
application/ide-application-tests.c \
application/ide-application-tests.h \
+ buildsystem/ide-build-log.c \
+ buildsystem/ide-build-log-private.h \
editor/ide-editor-frame-actions.c \
editor/ide-editor-frame-actions.h \
editor/ide-editor-frame-private.h \
@@ -612,6 +623,8 @@ endif
glib_enum_headers = \
buffers/ide-buffer.h \
+ buildsystem/ide-build-log.h \
+ buildsystem/ide-build-pipeline.h \
buildsystem/ide-build-result.h \
devices/ide-device.h \
diagnostics/ide-diagnostic.h \
diff --git a/libide/buildsystem/ide-build-log-private.h b/libide/buildsystem/ide-build-log-private.h
new file mode 100644
index 0000000..d0afbcd
--- /dev/null
+++ b/libide/buildsystem/ide-build-log-private.h
@@ -0,0 +1,44 @@
+/* ide-build-log-private.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BUILD_LOG_PRIVATE_H
+#define IDE_BUILD_LOG_PRIVATE_H
+
+#include <gio/gio.h>
+
+#include "ide-build-log.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BUILD_LOG (ide_build_log_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeBuildLog, ide_build_log, IDE, BUILD_LOG, GObject)
+
+IdeBuildLog *ide_build_log_new (void);
+void ide_build_log_observer (IdeBuildLogStream stream,
+ const gchar *message,
+ gssize message_len,
+ gpointer user_data);
+void ide_build_log_add (IdeBuildLog *self,
+ IdeBuildLogObserver observer,
+ gpointer observer_data,
+ GDestroyNotify observer_data_destroy);
+
+G_END_DECLS
+
+#endif /* IDE_BUILD_LOG_PRIVATE_H */
diff --git a/libide/buildsystem/ide-build-log.c b/libide/buildsystem/ide-build-log.c
new file mode 100644
index 0000000..8bc924e
--- /dev/null
+++ b/libide/buildsystem/ide-build-log.c
@@ -0,0 +1,211 @@
+/* ide-build-log.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-build-log"
+
+#include <string.h>
+
+#include "application/ide-application.h"
+#include "buildsystem/ide-build-log.h"
+#include "buildsystem/ide-build-log-private.h"
+
+#define POINTER_MARK(p) GSIZE_TO_POINTER(GPOINTER_TO_SIZE(p)|1)
+#define POINTER_UNMARK(p) GSIZE_TO_POINTER(GPOINTER_TO_SIZE(p)&~(gsize)1)
+#define POINTER_MARKED(p) (GPOINTER_TO_SIZE(p)&1)
+#define DISPATCH_MAX 20
+
+struct _IdeBuildLog
+{
+ GObject parent_instance;
+
+ GArray *observers;
+ GAsyncQueue *log_queue;
+ GSource *log_source;
+};
+
+typedef struct
+{
+ IdeBuildLogObserver callback;
+ gpointer data;
+ GDestroyNotify destroy;
+} Observer;
+
+G_DEFINE_TYPE (IdeBuildLog, ide_build_log, G_TYPE_OBJECT)
+
+static gboolean
+emit_log_from_main (gpointer user_data)
+{
+ IdeBuildLog *self = user_data;
+ g_autoptr(GPtrArray) ar = g_ptr_array_new ();
+ gpointer item;
+
+ g_assert (IDE_IS_BUILD_LOG (self));
+
+ /*
+ * Pull up to DISPATCH_MAX items from the log queue. We have an upper
+ * bound here so that we don't stall the main loop. Additionally, we
+ * update the ready-time when we run out of items while holding the
+ * async queue lock to synchronize with the caller for further wakeups.
+ */
+ g_async_queue_lock (self->log_queue);
+ for (guint i = 0; i < DISPATCH_MAX; i++)
+ {
+ if (NULL == (item = g_async_queue_try_pop_unlocked (self->log_queue)))
+ {
+ g_source_set_ready_time (self->log_source, -1);
+ break;
+ }
+ g_ptr_array_add (ar, item);
+ }
+ g_async_queue_unlock (self->log_queue);
+
+ for (guint i = 0; i < ar->len; i++)
+ {
+ IdeBuildLogStream stream = IDE_BUILD_LOG_STDOUT;
+ gchar *message;
+ gsize message_len;
+
+ item = g_ptr_array_index (ar, i);
+ message = POINTER_UNMARK (item);
+ message_len = strlen (message);
+
+ if (POINTER_MARKED (item))
+ stream = IDE_BUILD_LOG_STDERR;
+
+ for (guint j = 0; j < self->observers->len; j++)
+ {
+ const Observer *observer = &g_array_index (self->observers, Observer, j);
+
+ observer->callback (stream, message, message_len, observer->data);
+ }
+
+ g_free (message);
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+ide_build_log_finalize (GObject *object)
+{
+ IdeBuildLog *self = (IdeBuildLog *)object;
+
+ g_clear_pointer (&self->log_queue, g_async_queue_unref);
+ g_clear_pointer (&self->log_source, g_source_destroy);
+ g_clear_pointer (&self->observers, g_array_unref);
+
+ G_OBJECT_CLASS (ide_build_log_parent_class)->finalize (object);
+}
+
+static void
+ide_build_log_class_init (IdeBuildLogClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_build_log_finalize;
+}
+
+static void
+ide_build_log_init (IdeBuildLog *self)
+{
+ self->observers = g_array_new (FALSE, FALSE, sizeof (Observer));
+
+ self->log_queue = g_async_queue_new ();
+
+ self->log_source = g_timeout_source_new (G_MAXINT);
+ g_source_set_ready_time (self->log_source, -1);
+ g_source_set_name (self->log_source, "[ide] IdeBuildLog");
+ g_source_set_callback (self->log_source, emit_log_from_main, self, NULL);
+ g_source_attach (self->log_source, g_main_context_default ());
+}
+
+static void
+ide_build_log_via_main (IdeBuildLog *self,
+ IdeBuildLogStream stream,
+ const gchar *message,
+ gsize message_len)
+{
+ gchar *copied = g_strndup (message, message_len);
+
+ if G_UNLIKELY (stream == IDE_BUILD_LOG_STDERR)
+ copied = POINTER_MARK (copied);
+
+ /*
+ * Add the log entry to our queue to be dispatched in the main thread.
+ * However, we hold the async queue lock while updating the source ready
+ * time so we are synchronized with the main thread for setting the
+ * ready time. This is needed because the main thread may not dispatch
+ * all available items in a single dispatch (to avoid stalling the
+ * main loop).
+ */
+
+ g_async_queue_lock (self->log_queue);
+ g_async_queue_push_unlocked (self->log_queue, copied);
+ g_source_set_ready_time (self->log_source, 0);
+ g_async_queue_unlock (self->log_queue);
+}
+
+void
+ide_build_log_observer (IdeBuildLogStream stream,
+ const gchar *message,
+ gssize message_len,
+ gpointer user_data)
+{
+ IdeBuildLog *self = user_data;
+
+ if (message_len < 0)
+ message_len = strlen (message);
+
+ if G_LIKELY (IDE_IS_MAIN_THREAD ())
+ {
+ for (guint i = 0; i < self->observers->len; i++)
+ {
+ const Observer *observer = &g_array_index (self->observers, Observer, i);
+
+ observer->callback (stream, message, message_len, observer->data);
+ }
+ }
+ else
+ {
+ ide_build_log_via_main (self, stream, message, message_len);
+ }
+}
+
+void
+ide_build_log_add (IdeBuildLog *self,
+ IdeBuildLogObserver observer,
+ gpointer observer_data,
+ GDestroyNotify observer_data_destroy)
+{
+ Observer ele;
+
+ g_return_if_fail (IDE_IS_BUILD_LOG (self));
+ g_return_if_fail (observer != NULL);
+
+ ele.callback = observer;
+ ele.data = observer_data;
+ ele.destroy = observer_data_destroy;
+
+ g_array_append_val (self->observers, ele);
+}
+
+IdeBuildLog *
+ide_build_log_new (void)
+{
+ return g_object_new (IDE_TYPE_BUILD_LOG, NULL);
+}
diff --git a/libide/buildsystem/ide-build-log.h b/libide/buildsystem/ide-build-log.h
new file mode 100644
index 0000000..a6c807d
--- /dev/null
+++ b/libide/buildsystem/ide-build-log.h
@@ -0,0 +1,39 @@
+/* ide-build-log.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BUILD_LOG_H
+#define IDE_BUILD_LOG_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ IDE_BUILD_LOG_STDOUT,
+ IDE_BUILD_LOG_STDERR,
+} IdeBuildLogStream;
+
+typedef void (*IdeBuildLogObserver) (IdeBuildLogStream log_stream,
+ const gchar *message,
+ gssize message_len,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* IDE_BUILD_LOG_H */
diff --git a/libide/buildsystem/ide-build-pipeline-addin.c b/libide/buildsystem/ide-build-pipeline-addin.c
new file mode 100644
index 0000000..2531770
--- /dev/null
+++ b/libide/buildsystem/ide-build-pipeline-addin.c
@@ -0,0 +1,58 @@
+/* ide-build-pipeline-addin.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-build-pipeline-addin"
+
+#include "ide-context.h"
+
+#include "buildsystem/ide-build-pipeline-addin.h"
+
+G_DEFINE_INTERFACE (IdeBuildPipelineAddin, ide_build_pipeline_addin, G_TYPE_OBJECT)
+
+static void
+ide_build_pipeline_addin_default_init (IdeBuildPipelineAddinInterface *iface)
+{
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("context",
+ NULL,
+ NULL,
+ IDE_TYPE_CONTEXT,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS)));
+}
+
+void
+ide_build_pipeline_addin_load (IdeBuildPipelineAddin *self,
+ IdeBuildPipeline *pipeline)
+{
+ g_return_if_fail (IDE_IS_BUILD_PIPELINE_ADDIN (self));
+ g_return_if_fail (IDE_IS_BUILD_PIPELINE (pipeline));
+
+ if (IDE_BUILD_PIPELINE_ADDIN_GET_IFACE (self)->load)
+ IDE_BUILD_PIPELINE_ADDIN_GET_IFACE (self)->load (self, pipeline);
+}
+
+void
+ide_build_pipeline_addin_unload (IdeBuildPipelineAddin *self,
+ IdeBuildPipeline *pipeline)
+{
+ g_return_if_fail (IDE_IS_BUILD_PIPELINE_ADDIN (self));
+ g_return_if_fail (IDE_IS_BUILD_PIPELINE (pipeline));
+
+ if (IDE_BUILD_PIPELINE_ADDIN_GET_IFACE (self)->unload)
+ IDE_BUILD_PIPELINE_ADDIN_GET_IFACE (self)->unload (self, pipeline);
+}
diff --git a/libide/buildsystem/ide-build-pipeline-addin.h b/libide/buildsystem/ide-build-pipeline-addin.h
new file mode 100644
index 0000000..3b8f3df
--- /dev/null
+++ b/libide/buildsystem/ide-build-pipeline-addin.h
@@ -0,0 +1,49 @@
+/* ide-build-pipeline-addin.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BUILD_PIPELINE_ADDIN_H
+#define IDE_BUILD_PIPELINE_ADDIN_H
+
+#include <gio/gio.h>
+
+#include "ide-build-pipeline.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BUILD_PIPELINE_ADDIN (ide_build_pipeline_addin_get_type())
+
+G_DECLARE_INTERFACE (IdeBuildPipelineAddin, ide_build_pipeline_addin, IDE, BUILD_PIPELINE_ADDIN, GObject)
+
+struct _IdeBuildPipelineAddinInterface
+{
+ GTypeInterface type_interface;
+
+ void (*load) (IdeBuildPipelineAddin *self,
+ IdeBuildPipeline *pipeline);
+ void (*unload) (IdeBuildPipelineAddin *self,
+ IdeBuildPipeline *pipeline);
+};
+
+void ide_build_pipeline_addin_load (IdeBuildPipelineAddin *self,
+ IdeBuildPipeline *pipeline);
+void ide_build_pipeline_addin_unload (IdeBuildPipelineAddin *self,
+ IdeBuildPipeline *pipeline);
+
+G_END_DECLS
+
+#endif /* IDE_BUILD_PIPELINE_ADDIN_H */
diff --git a/libide/buildsystem/ide-build-pipeline.c b/libide/buildsystem/ide-build-pipeline.c
new file mode 100644
index 0000000..3f15f27
--- /dev/null
+++ b/libide/buildsystem/ide-build-pipeline.c
@@ -0,0 +1,826 @@
+/* ide-build-pipeline.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-build-pipeline"
+
+#include <libpeas/peas.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+#include "ide-enums.h"
+
+#include "buildsystem/ide-build-log.h"
+#include "buildsystem/ide-build-log-private.h"
+#include "buildsystem/ide-build-pipeline.h"
+#include "buildsystem/ide-build-pipeline-addin.h"
+#include "buildsystem/ide-build-stage-launcher.h"
+#include "projects/ide-project.h"
+#include "vcs/ide-vcs.h"
+
+/**
+ * SECTION:ide-build-pipeline
+ * @title: IdeBuildPipeline
+ * @short_description: Pluggable build pipeline
+ *
+ * The #IdeBuildPipeline is responsible for managing the build process
+ * for Builder. It consists of multiple build "phases" (see #IdeBuildPhase
+ * for the individual phases). An #IdeBuildStage can be attached with
+ * a priority to each phase and is the primary mechanism that plugins
+ * use to perform their operations in the proper ordering.
+ *
+ * For example, the flatpak plugin provides its download stage as part of the
+ * %IDE_BUILD_PHASE_DOWNLOAD phase. The autotools plugin provides stages to
+ * phases such as %IDE_BUILD_PHASE_AUTOGEN, %IDE_BUILD_PHASE_CONFIGURE,
+ * %IDE_BUILD_PHASE_BUILD, and %IDE_BUILD_PHASE_INSTALL.
+ *
+ * If you want ensure a particular phase is performed as part of a build,
+ * then fall ide_build_pipeline_request_phase() with the phase you are
+ * interested in seeing complete successfully.
+ *
+ * If your plugin has discovered that something has changed that invalidates a
+ * given phase, use ide_build_pipeline_invalidate_phase() to ensure that the
+ * phase is re-executed the next time a requested phase of higher precidence
+ * is requested.
+ *
+ * It can be useful to perform operations before or after a given stage (but
+ * still be executed as part of that stage) so the %IDE_BUILD_PHASE_BEFORE and
+ * %IDE_BUILD_PHASE_AFTER flags may be xor'd with the requested phase. If more
+ * precise ordering is required, you may use the priority parameter to order
+ * the operation with regards to other stages in that phase.
+ */
+
+typedef struct
+{
+ guint id;
+ IdeBuildPhase phase;
+ gint priority;
+ IdeBuildStage *stage;
+} PipelineEntry;
+
+struct _IdeBuildPipeline
+{
+ IdeObject parent_instance;
+ PeasExtensionSet *addins;
+ IdeConfiguration *configuration;
+ IdeBuildLog *log;
+ gchar *builddir;
+ gchar *srcdir;
+ GArray *pipeline;
+ gint position;
+ IdeBuildPhase requested_mask;
+ guint seqnum;
+ guint failed : 1;
+};
+
+static void ide_build_pipeline_tick (IdeBuildPipeline *self,
+ GTask *task);
+
+G_DEFINE_TYPE (IdeBuildPipeline, ide_build_pipeline, IDE_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_CONFIGURATION,
+ N_PROPS
+};
+
+enum {
+ STARTED,
+ FINISHED,
+ N_SIGNALS
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+IdeBuildPhase
+ide_build_pipeline_get_phase (IdeBuildPipeline *self)
+{
+ g_return_val_if_fail (IDE_IS_BUILD_PIPELINE (self), 0);
+
+ if (self->position < 0)
+ return IDE_BUILD_PHASE_NONE;
+ else if (self->position < self->pipeline->len)
+ return g_array_index (self->pipeline, PipelineEntry, self->position).phase;
+ else if (self->failed)
+ return IDE_BUILD_PHASE_FAILED;
+ else
+ return IDE_BUILD_PHASE_FINISHED;
+}
+
+/**
+ * ide_build_pipeline_get_configuration:
+ *
+ * Gets the #IdeConfiguration to use for the pipeline.
+ *
+ * Returns: (transfer none): An #IdeConfiguration
+ */
+IdeConfiguration *
+ide_build_pipeline_get_configuration (IdeBuildPipeline *self)
+{
+ g_return_val_if_fail (IDE_IS_BUILD_PIPELINE (self), NULL);
+
+ return self->configuration;
+}
+
+static void
+clear_pipeline_entry (gpointer data)
+{
+ PipelineEntry *entry = data;
+
+ g_clear_object (&entry->stage);
+}
+
+static gint
+pipeline_entry_compare (gconstpointer a,
+ gconstpointer b)
+{
+ const PipelineEntry *entry_a = a;
+ const PipelineEntry *entry_b = b;
+ gint ret;
+
+ ret = (gint)(entry_a->phase & IDE_BUILD_PHASE_MASK)
+ - (gint)(entry_b->phase & IDE_BUILD_PHASE_MASK);
+
+ if (ret == 0)
+ {
+ gint whence_a = (entry_a->phase & IDE_BUILD_PHASE_WHENCE_MASK);
+ gint whence_b = (entry_b->phase & IDE_BUILD_PHASE_WHENCE_MASK);
+
+ if (whence_a != whence_b)
+ {
+ if (whence_a == IDE_BUILD_PHASE_BEFORE)
+ return -1;
+
+ if (whence_b == IDE_BUILD_PHASE_BEFORE)
+ return 1;
+
+ if (whence_a == 0)
+ return -1;
+
+ if (whence_b == 0)
+ return 1;
+
+ g_assert_not_reached ();
+ }
+ }
+
+ if (ret == 0)
+ ret = entry_a->priority - entry_b->priority;
+
+ return ret;
+}
+
+static void
+ide_build_pipeline_set_configuration (IdeBuildPipeline *self,
+ IdeConfiguration *configuration)
+{
+ g_return_if_fail (IDE_IS_BUILD_PIPELINE (self));
+ g_return_if_fail (IDE_IS_CONFIGURATION (configuration));
+
+ g_set_object (&self->configuration, configuration);
+}
+
+static void
+ide_build_pipeline_real_started (IdeBuildPipeline *self)
+{
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+}
+
+static void
+ide_build_pipeline_real_finished (IdeBuildPipeline *self,
+ gboolean failed)
+{
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+
+ /*
+ * Now that the build is finished, we can aggressively drop our pipeline
+ * stages to help ensure that all references are dropped as soon as
+ * possible.
+ */
+
+ g_clear_object (&self->addins);
+}
+
+static void
+ide_build_pipeline_extension_added (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeBuildPipeline *self = user_data;
+ IdeBuildPipelineAddin *addin = (IdeBuildPipelineAddin *)exten;
+
+ IDE_ENTRY;
+
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_BUILD_PIPELINE_ADDIN (addin));
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+
+ ide_build_pipeline_addin_load (addin, self);
+
+ IDE_EXIT;
+}
+
+static void
+ide_build_pipeline_extension_removed (PeasExtensionSet *set,
+ PeasPluginInfo *plugin_info,
+ PeasExtension *exten,
+ gpointer user_data)
+{
+ IdeBuildPipeline *self = user_data;
+ IdeBuildPipelineAddin *addin = (IdeBuildPipelineAddin *)exten;
+
+ IDE_ENTRY;
+
+ g_assert (PEAS_IS_EXTENSION_SET (set));
+ g_assert (plugin_info != NULL);
+ g_assert (IDE_IS_BUILD_PIPELINE_ADDIN (addin));
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+
+ ide_build_pipeline_addin_unload (addin, self);
+
+ IDE_EXIT;
+}
+
+static void
+ide_build_pipeline_finalize (GObject *object)
+{
+ IdeBuildPipeline *self = (IdeBuildPipeline *)object;
+
+ g_clear_object (&self->log);
+ g_clear_object (&self->configuration);
+ g_clear_pointer (&self->pipeline, g_array_unref);
+ g_clear_pointer (&self->srcdir, g_free);
+ g_clear_pointer (&self->builddir, g_free);
+
+ G_OBJECT_CLASS (ide_build_pipeline_parent_class)->finalize (object);
+}
+
+static void
+ide_build_pipeline_dispose (GObject *object)
+{
+ IdeBuildPipeline *self = IDE_BUILD_PIPELINE (object);
+
+ g_clear_object (&self->addins);
+
+ G_OBJECT_CLASS (ide_build_pipeline_parent_class)->dispose (object);
+}
+
+static void
+ide_build_pipeline_constructed (GObject *object)
+{
+ IdeBuildPipeline *self = IDE_BUILD_PIPELINE (object);
+ g_autofree gchar *builddir = NULL;
+ const gchar *config_id;
+ const gchar *project_id;
+ IdeProject *project;
+ IdeContext *context;
+ IdeVcs *vcs;
+ GFile *workdir;
+
+ G_OBJECT_CLASS (ide_build_pipeline_parent_class)->constructed (object);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+
+ project = ide_context_get_project (context);
+ project_id = ide_project_get_id (project);
+
+ vcs = ide_context_get_vcs (context);
+ workdir = ide_vcs_get_working_directory (vcs);
+
+ config_id = ide_configuration_get_id (self->configuration);
+
+ self->srcdir = g_file_get_path (workdir);
+
+ self->builddir = g_build_filename (g_get_user_cache_dir (),
+ "gnome-builder",
+ "builds",
+ project_id,
+ config_id,
+ NULL);
+
+ self->addins = peas_extension_set_new (peas_engine_get_default (),
+ IDE_TYPE_BUILD_PIPELINE_ADDIN,
+ "context", context,
+ NULL);
+
+ g_signal_connect (self->addins,
+ "extension-added",
+ G_CALLBACK (ide_build_pipeline_extension_added),
+ self);
+
+ g_signal_connect (self->addins,
+ "extension-removed",
+ G_CALLBACK (ide_build_pipeline_extension_removed),
+ self);
+
+ peas_extension_set_foreach (self->addins,
+ ide_build_pipeline_extension_added,
+ self);
+}
+
+static void
+ide_build_pipeline_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBuildPipeline *self = IDE_BUILD_PIPELINE (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ g_value_set_object (value, ide_build_pipeline_get_configuration (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_build_pipeline_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBuildPipeline *self = IDE_BUILD_PIPELINE (object);
+
+ switch (prop_id)
+ {
+ case PROP_CONFIGURATION:
+ ide_build_pipeline_set_configuration (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_build_pipeline_class_init (IdeBuildPipelineClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = ide_build_pipeline_constructed;
+ object_class->dispose = ide_build_pipeline_dispose;
+ object_class->finalize = ide_build_pipeline_finalize;
+ object_class->get_property = ide_build_pipeline_get_property;
+ object_class->set_property = ide_build_pipeline_set_property;
+
+ /**
+ * IdeBuildPipeline:configuration:
+ *
+ * The configuration to use for the build pipeline.
+ */
+ properties [PROP_CONFIGURATION] =
+ g_param_spec_object ("configuration",
+ "Configuration",
+ "Configuration",
+ IDE_TYPE_CONFIGURATION,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ /**
+ * IdeBuildPipeline::started:
+ * @self: An @IdeBuildPipeline
+ *
+ * This signal is emitted when the pipeline has started executing in
+ * response to ide_build_pipeline_execute_async() being called.
+ */
+ signals [STARTED] =
+ g_signal_new_class_handler ("started",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (ide_build_pipeline_real_started),
+ NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ /**
+ * IdeBuildPipeline::finished:
+ * @self: An #IdeBuildPipeline
+ * @failed: If the build was a failure
+ *
+ * This signal is emitted when the build process has finished executing.
+ * If the build failed to complete all requested stages, then @failed will
+ * be set to %TRUE, otherwise %FALSE.
+ */
+ signals [FINISHED] =
+ g_signal_new_class_handler ("finished",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_CALLBACK (ide_build_pipeline_real_finished),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
+}
+
+static void
+ide_build_pipeline_init (IdeBuildPipeline *self)
+{
+ self->position = -1;
+
+ self->pipeline = g_array_new (FALSE, FALSE, sizeof (PipelineEntry));
+ g_array_set_clear_func (self->pipeline, clear_pipeline_entry);
+
+ self->log = ide_build_log_new ();
+}
+
+static void
+ide_build_pipeline_stage_execute_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeBuildStage *stage = (IdeBuildStage *)object;
+ IdeBuildPipeline *self;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (IDE_IS_BUILD_STAGE (stage));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (G_IS_TASK (task));
+
+ self = g_task_get_source_object (task);
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+
+ if (!ide_build_stage_execute_finish (stage, result, &error))
+ {
+ self->failed = TRUE;
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ ide_build_pipeline_tick (self, task);
+}
+
+static void
+ide_build_pipeline_tick (IdeBuildPipeline *self,
+ GTask *task)
+{
+ GCancellable *cancellable;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+ g_assert (G_IS_TASK (task));
+
+ cancellable = g_task_get_cancellable (task);
+
+ for (self->position++; self->position < self->pipeline->len; self->position++)
+ {
+ const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, self->position);
+
+ g_assert (entry->stage != NULL);
+ g_assert (IDE_IS_BUILD_STAGE (entry->stage));
+
+ if (entry->phase & self->requested_mask)
+ {
+ ide_build_stage_execute_async (entry->stage,
+ cancellable,
+ ide_build_pipeline_stage_execute_cb,
+ g_object_ref (task));
+ IDE_EXIT;
+ }
+ }
+
+ g_task_return_boolean (task, TRUE);
+
+ IDE_EXIT;
+}
+
+static void
+ide_build_pipeline_notify_completed (IdeBuildPipeline *self,
+ GParamSpec *pspec,
+ GTask *task)
+{
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+ g_assert (G_IS_TASK (task));
+
+ g_signal_emit (self, signals [FINISHED], 0, self->failed);
+
+ IDE_EXIT;
+}
+
+/**
+ * ide_build_pipeline_execute_async:
+ * @self: A @IdeBuildPipeline
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: a callback to execute upon completion
+ * @user_data: data for @callback
+ *
+ * Asynchronously starts the build pipeline.
+ *
+ * Any phase that has been invalidated up to the requested phase
+ * will be executed until a stage has failed.
+ *
+ * Upon completion, @callback will be executed and should call
+ * ide_build_pipeline_execute_finish() to get the status of the
+ * operation.
+ */
+void
+ide_build_pipeline_execute_async (IdeBuildPipeline *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+ g_autoptr(GFile) builddir = NULL;
+ g_autoptr(GError) error = NULL;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_BUILD_PIPELINE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ g_signal_emit (self, signals [STARTED], 0);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, ide_build_pipeline_execute_async);
+
+ g_signal_connect_object (task,
+ "notify::completed",
+ G_CALLBACK (ide_build_pipeline_notify_completed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ /*
+ * Before we make any progoress, ensure the build directory is created
+ * so that pipeline stages need not worry about it. We'll just do this
+ * synchronously because if we can't do directory creation fast, well
+ * then we are pretty much screwed anyway.
+ */
+
+ builddir = g_file_new_for_path (self->builddir);
+
+ if (!g_file_make_directory_with_parents (builddir, cancellable, &error) &&
+ !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ ide_build_pipeline_tick (self, task);
+
+ IDE_EXIT;
+}
+
+/**
+ * ide_build_pipeline_execute_finish:
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+ide_build_pipeline_execute_finish (IdeBuildPipeline *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ gboolean ret;
+
+ IDE_ENTRY;
+
+ g_return_val_if_fail (IDE_IS_BUILD_PIPELINE (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ ret = g_task_propagate_boolean (G_TASK (result), error);
+
+ IDE_RETURN (ret);
+}
+
+guint
+ide_build_pipeline_insert_stage (IdeBuildPipeline *self,
+ IdeBuildPhase phase,
+ gint priority,
+ IdeBuildStage *stage)
+{
+ GFlagsClass *klass;
+ guint ret = 0;
+
+ IDE_ENTRY;
+
+ g_return_val_if_fail (IDE_IS_BUILD_PIPELINE (self), 0);
+ g_return_val_if_fail (IDE_IS_BUILD_STAGE (stage), 0);
+ g_return_val_if_fail ((phase & IDE_BUILD_PHASE_MASK) != IDE_BUILD_PHASE_NONE, 0);
+ g_return_val_if_fail ((phase & IDE_BUILD_PHASE_WHENCE_MASK) == 0 ||
+ (phase & IDE_BUILD_PHASE_WHENCE_MASK) == IDE_BUILD_PHASE_BEFORE ||
+ (phase & IDE_BUILD_PHASE_WHENCE_MASK) == IDE_BUILD_PHASE_AFTER, 0);
+
+ if G_UNLIKELY (self->position != -1)
+ {
+ g_warning ("Cannot insert stage into pipeline after execution, ignoring");
+ IDE_RETURN (0);
+ }
+
+ klass = g_type_class_ref (IDE_TYPE_BUILD_PHASE);
+
+ for (guint i = 0; i < klass->n_values; i++)
+ {
+ const GFlagsValue *value = &klass->values[i];
+
+ if ((phase & IDE_BUILD_PHASE_MASK) == value->value)
+ {
+ PipelineEntry entry = { 0 };
+
+ IDE_TRACE_MSG ("Adding stage to pipeline with phase %s and priority %d",
+ value->value_nick, priority);
+
+ entry.id = ++self->seqnum;
+ entry.phase = phase;
+ entry.priority = priority;
+ entry.stage = g_object_ref (stage);
+
+ g_array_append_val (self->pipeline, entry);
+ g_array_sort (self->pipeline, pipeline_entry_compare);
+
+ ret = entry.id;
+
+ ide_build_stage_set_log_observer (stage,
+ ide_build_log_observer,
+ g_object_ref (self->log),
+ g_object_unref);
+
+ IDE_GOTO (cleanup);
+ }
+ }
+
+ g_warning ("No such pipeline phase %02x", phase);
+
+cleanup:
+ g_type_class_unref (klass);
+
+ IDE_RETURN (ret);
+}
+
+guint
+ide_build_pipeline_insert_stage_from_launcher (IdeBuildPipeline *self,
+ IdeBuildPhase phase,
+ gint priority,
+ IdeSubprocessLauncher *launcher)
+{
+ g_autoptr(IdeBuildStage) stage = NULL;
+ IdeContext *context;
+
+ g_return_val_if_fail (IDE_IS_BUILD_PIPELINE (self), 0);
+ g_return_val_if_fail ((phase & IDE_BUILD_PHASE_MASK) != IDE_BUILD_PHASE_NONE, 0);
+ g_return_val_if_fail ((phase & IDE_BUILD_PHASE_WHENCE_MASK) == 0 ||
+ (phase & IDE_BUILD_PHASE_WHENCE_MASK) == IDE_BUILD_PHASE_BEFORE ||
+ (phase & IDE_BUILD_PHASE_WHENCE_MASK) == IDE_BUILD_PHASE_AFTER, 0);
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ stage = ide_build_stage_launcher_new (context, launcher);
+
+ return ide_build_pipeline_insert_stage (self, phase, priority, stage);
+}
+
+void
+ide_build_pipeline_request_phase (IdeBuildPipeline *self,
+ IdeBuildPhase phase)
+{
+ GFlagsClass *klass;
+
+ IDE_ENTRY;
+
+ g_return_if_fail (IDE_IS_BUILD_PIPELINE (self));
+ g_return_if_fail ((phase & IDE_BUILD_PHASE_MASK) != IDE_BUILD_PHASE_NONE);
+
+ phase &= IDE_BUILD_PHASE_MASK;
+
+ if (self->position != -1)
+ {
+ g_warning ("Cannot request phase after execution has started");
+ IDE_EXIT;
+ }
+
+ klass = g_type_class_ref (IDE_TYPE_BUILD_PHASE);
+
+ for (guint i = 0; i < klass->n_values; i++)
+ {
+ const GFlagsValue *value = &klass->values[i];
+
+ if (phase == value->value)
+ {
+ IDE_TRACE_MSG ("requesting pipeline phase %s", value->value_nick);
+ /*
+ * Each flag is a power of two, so we can simply subtract one
+ * to get a mask of all the previous phases.
+ */
+ self->requested_mask |= phase | (phase - 1);
+ IDE_GOTO (cleanup);
+ }
+ }
+
+ g_warning ("No such phase %02x", (guint)phase);
+
+cleanup:
+ g_type_class_unref (klass);
+
+ IDE_EXIT;
+}
+
+const gchar *
+ide_build_pipeline_get_builddir (IdeBuildPipeline *self)
+{
+ g_return_val_if_fail (IDE_IS_BUILD_PIPELINE (self), NULL);
+
+ return self->builddir;
+}
+
+const gchar *
+ide_build_pipeline_get_srcdir (IdeBuildPipeline *self)
+{
+ g_return_val_if_fail (IDE_IS_BUILD_PIPELINE (self), NULL);
+
+ return self->srcdir;
+}
+
+static gchar *
+ide_build_pipeline_build_path_va_list (const gchar *prefix,
+ const gchar *first_part,
+ va_list args)
+{
+ g_autoptr(GPtrArray) ar = NULL;
+
+ g_assert (prefix != NULL);
+ g_assert (first_part != NULL);
+
+ ar = g_ptr_array_new ();
+ g_ptr_array_add (ar, (gchar *)prefix);
+ do
+ g_ptr_array_add (ar, (gchar *)first_part);
+ while (NULL != (first_part = va_arg (args, const gchar *)));
+ g_ptr_array_add (ar, NULL);
+
+ return g_build_filenamev ((gchar **)ar->pdata);
+}
+
+gchar *
+ide_build_pipeline_build_srcdir_path (IdeBuildPipeline *self,
+ const gchar *first_part,
+ ...)
+{
+ gchar *ret;
+ va_list args;
+
+ g_return_val_if_fail (IDE_IS_BUILD_PIPELINE (self), NULL);
+ g_return_val_if_fail (first_part != NULL, NULL);
+
+ va_start (args, first_part);
+ ret = ide_build_pipeline_build_path_va_list (self->srcdir, first_part, args);
+ va_end (args);
+
+ return ret;
+}
+
+gchar *
+ide_build_pipeline_build_builddir_path (IdeBuildPipeline *self,
+ const gchar *first_part,
+ ...)
+{
+ gchar *ret;
+ va_list args;
+
+ g_return_val_if_fail (IDE_IS_BUILD_PIPELINE (self), NULL);
+ g_return_val_if_fail (first_part != NULL, NULL);
+
+ va_start (args, first_part);
+ ret = ide_build_pipeline_build_path_va_list (self->builddir, first_part, args);
+ va_end (args);
+
+ return ret;
+}
+
+void
+ide_build_pipeline_remove (IdeBuildPipeline *self,
+ guint stage_id)
+{
+ g_return_if_fail (IDE_IS_BUILD_PIPELINE (self));
+ g_return_if_fail (self->pipeline != NULL);
+ g_return_if_fail (stage_id != 0);
+
+ for (guint i = 0; i < self->pipeline->len; i++)
+ {
+ const PipelineEntry *entry = &g_array_index (self->pipeline, PipelineEntry, i);
+
+ if (entry->id == stage_id)
+ {
+ g_array_remove_index (self->pipeline, i);
+ break;
+ }
+ }
+}
diff --git a/libide/buildsystem/ide-build-pipeline.h b/libide/buildsystem/ide-build-pipeline.h
new file mode 100644
index 0000000..8b339ef
--- /dev/null
+++ b/libide/buildsystem/ide-build-pipeline.h
@@ -0,0 +1,87 @@
+/* ide-build-pipeline.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BUILD_PIPELINE_H
+#define IDE_BUILD_PIPELINE_H
+
+#include <gio/gio.h>
+
+#include "ide-types.h"
+
+#include "buildsystem/ide-build-log.h"
+#include "buildsystem/ide-build-stage.h"
+#include "buildsystem/ide-configuration.h"
+#include "subprocess/ide-subprocess-launcher.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BUILD_PIPELINE (ide_build_pipeline_get_type())
+#define IDE_BUILD_PHASE_MASK (0xFFFFFF)
+#define IDE_BUILD_PHASE_WHENCE_MASK (IDE_BUILD_PHASE_BEFORE | IDE_BUILD_PHASE_AFTER)
+
+typedef enum
+{
+ IDE_BUILD_PHASE_NONE = 0,
+ IDE_BUILD_PHASE_PREPARE = 1 << 0,
+ IDE_BUILD_PHASE_DOWNLOADS = 1 << 1,
+ IDE_BUILD_PHASE_DEPENDENCIES = 1 << 2,
+ IDE_BUILD_PHASE_AUTOGEN = 1 << 3,
+ IDE_BUILD_PHASE_CONFIGURE = 1 << 4,
+ IDE_BUILD_PHASE_BUILD = 1 << 6,
+ IDE_BUILD_PHASE_INSTALL = 1 << 7,
+ IDE_BUILD_PHASE_EXPORT = 1 << 8,
+ IDE_BUILD_PHASE_BEFORE = 1 << 28,
+ IDE_BUILD_PHASE_AFTER = 1 << 29,
+ IDE_BUILD_PHASE_FINISHED = 1 << 30,
+ IDE_BUILD_PHASE_FAILED = 1 << 31,
+} IdeBuildPhase;
+
+G_DECLARE_FINAL_TYPE (IdeBuildPipeline, ide_build_pipeline, IDE, BUILD_PIPELINE, IdeObject)
+
+IdeConfiguration *ide_build_pipeline_get_configuration (IdeBuildPipeline *self);
+const gchar *ide_build_pipeline_get_builddir (IdeBuildPipeline *self);
+const gchar *ide_build_pipeline_get_srcdir (IdeBuildPipeline *self);
+gchar *ide_build_pipeline_build_srcdir_path (IdeBuildPipeline *self,
+ const gchar *first_part,
+ ...) G_GNUC_NULL_TERMINATED;
+gchar *ide_build_pipeline_build_builddir_path (IdeBuildPipeline *self,
+ const gchar *first_part,
+ ...) G_GNUC_NULL_TERMINATED;
+void ide_build_pipeline_request_phase (IdeBuildPipeline *self,
+ IdeBuildPhase phase);
+guint ide_build_pipeline_insert_stage (IdeBuildPipeline *self,
+ IdeBuildPhase phase,
+ gint priority,
+ IdeBuildStage *stage);
+guint ide_build_pipeline_insert_stage_from_launcher (IdeBuildPipeline *self,
+ IdeBuildPhase phase,
+ gint priority,
+ IdeSubprocessLauncher *launcher);
+void ide_build_pipeline_remove (IdeBuildPipeline *self,
+ guint stage_id);
+void ide_build_pipeline_execute_async (IdeBuildPipeline *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_build_pipeline_execute_finish (IdeBuildPipeline *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* IDE_BUILD_PIPELINE_H */
diff --git a/libide/buildsystem/ide-build-stage-launcher.c b/libide/buildsystem/ide-build-stage-launcher.c
new file mode 100644
index 0000000..20b7592
--- /dev/null
+++ b/libide/buildsystem/ide-build-stage-launcher.c
@@ -0,0 +1,337 @@
+/* ide-build-stage-launcher.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-build-stage-launcher"
+
+#include "ide-debug.h"
+
+#include "buildsystem/ide-build-stage-launcher.h"
+#include "subprocess/ide-subprocess.h"
+
+struct _IdeBuildStageLauncher
+{
+ IdeBuildStage parent_instance;
+ IdeSubprocessLauncher *launcher;
+};
+
+typedef struct
+{
+ IdeBuildStage *self;
+ IdeBuildLogStream stream_type;
+} Tail;
+
+enum {
+ PROP_0,
+ PROP_LAUNCHER,
+ N_PROPS
+};
+
+G_DEFINE_TYPE (IdeBuildStageLauncher, ide_build_stage_launcher, IDE_TYPE_BUILD_STAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static Tail *
+tail_new (IdeBuildStageLauncher *self,
+ IdeBuildLogStream stream_type)
+{
+ Tail *tail;
+
+ tail = g_slice_new0 (Tail);
+ tail->self = g_object_ref (self);
+ tail->stream_type = stream_type;
+
+ return tail;
+}
+
+static void
+tail_free (Tail *tail)
+{
+ g_clear_object (&tail->self);
+ g_slice_free (Tail, tail);
+}
+
+static void
+ide_build_stage_launcher_wait_check_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeSubprocess *subprocess = (IdeSubprocess *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_SUBPROCESS (subprocess));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ if (!ide_subprocess_wait_check_finish (subprocess, result, &error))
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_boolean (task, TRUE);
+
+ IDE_EXIT;
+}
+
+static void
+ide_build_stage_launcher_observe_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GDataInputStream *stream = (GDataInputStream *)object;
+ g_autofree gchar *line = NULL;
+ Tail *tail = user_data;
+ gsize n_read = 0;
+
+ line = g_data_input_stream_read_line_finish_utf8 (stream, result, &n_read, NULL);
+
+ if G_LIKELY (line != NULL && n_read > 0 && n_read < G_MAXSSIZE)
+ {
+ ide_build_stage_log (tail->self, tail->stream_type, line, (gssize)n_read);
+ g_data_input_stream_read_line_async (stream,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ ide_build_stage_launcher_observe_cb,
+ tail);
+ return;
+ }
+
+ tail_free (tail);
+}
+
+static void
+ide_build_stage_launcher_observe (IdeBuildStageLauncher *self,
+ IdeBuildLogStream stream_type,
+ GInputStream *stream)
+{
+ g_autoptr(GDataInputStream) data_stream = NULL;
+ Tail *tail;
+
+ g_assert (IDE_IS_BUILD_STAGE_LAUNCHER (self));
+ g_assert (stream_type == IDE_BUILD_LOG_STDOUT ||
+ stream_type == IDE_BUILD_LOG_STDERR);
+ g_assert (G_IS_INPUT_STREAM (stream));
+
+ if (G_IS_DATA_INPUT_STREAM (stream))
+ data_stream = g_object_ref (stream);
+ else
+ data_stream = g_data_input_stream_new (stream);
+
+ tail = tail_new (self, stream_type);
+
+ g_data_input_stream_read_line_async (data_stream,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ ide_build_stage_launcher_observe_cb,
+ tail);
+}
+
+static void
+ide_build_stage_launcher_execute_async (IdeBuildStage *stage,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ IdeBuildStageLauncher *self = (IdeBuildStageLauncher *)stage;
+ g_autoptr(GTask) task = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(IdeSubprocess) subprocess = NULL;
+ GInputStream *stdout_stream;
+ GInputStream *stderr_stream;
+ GSubprocessFlags flags;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_BUILD_STAGE_LAUNCHER (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, ide_build_stage_launcher_execute_async);
+
+ if (self->launcher == NULL)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_INVAL,
+ "Improperly configured %s",
+ G_OBJECT_TYPE_NAME (self));
+ IDE_EXIT;
+ }
+
+ flags = ide_subprocess_launcher_get_flags (self->launcher);
+
+ /* Disable flags we do not want set for build pipeline stuff */
+
+ if (flags & G_SUBPROCESS_FLAGS_STDERR_SILENCE)
+ flags &= ~G_SUBPROCESS_FLAGS_STDERR_SILENCE;
+
+ if (flags & G_SUBPROCESS_FLAGS_STDERR_MERGE)
+ flags &= ~G_SUBPROCESS_FLAGS_STDERR_MERGE;
+
+ if (flags & G_SUBPROCESS_FLAGS_STDIN_INHERIT)
+ flags &= ~G_SUBPROCESS_FLAGS_STDIN_INHERIT;
+
+ /* Ensure we have access to stdin/stdout streams */
+
+ flags |= G_SUBPROCESS_FLAGS_STDOUT_PIPE;
+ flags |= G_SUBPROCESS_FLAGS_STDERR_PIPE;
+
+ ide_subprocess_launcher_set_flags (self->launcher, flags);
+
+ /* Now launch the process */
+
+ subprocess = ide_subprocess_launcher_spawn (self->launcher, cancellable, &error);
+
+ if (subprocess == NULL)
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ IDE_EXIT;
+ }
+
+ /* Start our async reads from underlying process streams */
+
+ stderr_stream = ide_subprocess_get_stderr_pipe (subprocess);
+ stdout_stream = ide_subprocess_get_stdout_pipe (subprocess);
+
+ ide_build_stage_launcher_observe (self, IDE_BUILD_LOG_STDERR, stderr_stream);
+ ide_build_stage_launcher_observe (self, IDE_BUILD_LOG_STDOUT, stdout_stream);
+
+ /* Now wait for the process to exit */
+
+ ide_subprocess_wait_check_async (subprocess,
+ cancellable,
+ ide_build_stage_launcher_wait_check_cb,
+ g_steal_pointer (&task));
+
+ IDE_EXIT;
+}
+
+static gboolean
+ide_build_stage_launcher_execute_finish (IdeBuildStage *stage,
+ GAsyncResult *result,
+ GError **error)
+{
+ gboolean ret;
+
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_BUILD_STAGE_LAUNCHER (stage));
+ g_assert (G_IS_TASK (result));
+
+ ret = g_task_propagate_boolean (G_TASK (result), error);
+
+ IDE_RETURN (ret);
+}
+
+static void
+ide_build_stage_launcher_finalize (GObject *object)
+{
+ IdeBuildStageLauncher *self = (IdeBuildStageLauncher *)object;
+
+ g_clear_object (&self->launcher);
+
+ G_OBJECT_CLASS (ide_build_stage_launcher_parent_class)->finalize (object);
+}
+
+static void
+ide_build_stage_launcher_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBuildStageLauncher *self = (IdeBuildStageLauncher *)object;
+
+ switch (prop_id)
+ {
+ case PROP_LAUNCHER:
+ g_value_set_object (value, ide_build_stage_launcher_get_launcher (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_build_stage_launcher_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBuildStageLauncher *self = (IdeBuildStageLauncher *)object;
+
+ switch (prop_id)
+ {
+ case PROP_LAUNCHER:
+ self->launcher = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_build_stage_launcher_class_init (IdeBuildStageLauncherClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ IdeBuildStageClass *build_stage_class = IDE_BUILD_STAGE_CLASS (klass);
+
+ object_class->finalize = ide_build_stage_launcher_finalize;
+ object_class->get_property = ide_build_stage_launcher_get_property;
+ object_class->set_property = ide_build_stage_launcher_set_property;
+
+ build_stage_class->execute_async = ide_build_stage_launcher_execute_async;
+ build_stage_class->execute_finish = ide_build_stage_launcher_execute_finish;
+
+ properties [PROP_LAUNCHER] =
+ g_param_spec_object ("launcher",
+ "Launcher",
+ "The subprocess launcher to execute",
+ IDE_TYPE_SUBPROCESS_LAUNCHER,
+ (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_build_stage_launcher_init (IdeBuildStageLauncher *self)
+{
+}
+
+/**
+ * ide_build_stage_launcher_get_launcher:
+ *
+ * Returns: (transfer none): An #IdeSubprocessLauncher
+ */
+IdeSubprocessLauncher *
+ide_build_stage_launcher_get_launcher (IdeBuildStageLauncher *self)
+{
+ g_return_val_if_fail (IDE_IS_BUILD_STAGE_LAUNCHER (self), NULL);
+
+ return self->launcher;
+}
+
+IdeBuildStage *
+ide_build_stage_launcher_new (IdeContext *context,
+ IdeSubprocessLauncher *launcher)
+{
+ return g_object_new (IDE_TYPE_BUILD_STAGE_LAUNCHER,
+ "context", context,
+ "launcher", launcher,
+ NULL);
+}
diff --git a/libide/buildsystem/ide-build-stage-launcher.h b/libide/buildsystem/ide-build-stage-launcher.h
new file mode 100644
index 0000000..e3eb2e8
--- /dev/null
+++ b/libide/buildsystem/ide-build-stage-launcher.h
@@ -0,0 +1,41 @@
+/* ide-build-stage-launcher.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BUILD_STAGE_LAUNCHER_H
+#define IDE_BUILD_STAGE_LAUNCHER_H
+
+#include <gio/gio.h>
+
+#include "ide-context.h"
+
+#include "buildsystem/ide-build-stage.h"
+#include "subprocess/ide-subprocess-launcher.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BUILD_STAGE_LAUNCHER (ide_build_stage_launcher_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeBuildStageLauncher, ide_build_stage_launcher, IDE, BUILD_STAGE_LAUNCHER,
IdeBuildStage)
+
+IdeBuildStage *ide_build_stage_launcher_new (IdeContext *context,
+ IdeSubprocessLauncher *launcher);
+IdeSubprocessLauncher *ide_build_stage_launcher_get_launcher (IdeBuildStageLauncher *self);
+
+G_END_DECLS
+
+#endif /* IDE_BUILD_STAGE_LAUNCHER_H */
diff --git a/libide/buildsystem/ide-build-stage.c b/libide/buildsystem/ide-build-stage.c
new file mode 100644
index 0000000..7624357
--- /dev/null
+++ b/libide/buildsystem/ide-build-stage.c
@@ -0,0 +1,278 @@
+/* ide-build-stage.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-build-stage"
+
+#include "ide-build-stage.h"
+
+typedef struct
+{
+ gchar *name;
+ IdeBuildLogObserver observer;
+ gpointer observer_data;
+ GDestroyNotify observer_data_destroy;
+} IdeBuildStagePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeBuildStage, ide_build_stage, IDE_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_build_stage_clear_observer (IdeBuildStage *self)
+{
+ IdeBuildStagePrivate *priv = ide_build_stage_get_instance_private (self);
+ GDestroyNotify notify = priv->observer_data_destroy;
+ gpointer data = priv->observer_data;
+
+ priv->observer_data_destroy = NULL;
+ priv->observer_data = NULL;
+ priv->observer = NULL;
+
+ if (notify != NULL)
+ notify (data);
+}
+
+static gboolean
+ide_build_stage_real_execute (IdeBuildStage *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return TRUE;
+}
+
+static void
+ide_build_stage_real_execute_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_build_stage_real_execute_async (IdeBuildStage *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_assert (IDE_IS_BUILD_STAGE (self));
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, ide_build_stage_real_execute_async);
+ g_task_run_in_thread (task, ide_build_stage_real_execute_worker);
+}
+
+static gboolean
+ide_build_stage_real_execute_finish (IdeBuildStage *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_assert (IDE_IS_BUILD_STAGE (self));
+ g_assert (G_IS_TASK (result));
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+IdeBuildStage *
+ide_build_stage_new (void)
+{
+ return g_object_new (IDE_TYPE_BUILD_STAGE, NULL);
+}
+
+const gchar *
+ide_build_stage_get_name (IdeBuildStage *self)
+{
+ IdeBuildStagePrivate *priv = ide_build_stage_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_BUILD_STAGE (self), NULL);
+
+ return priv->name;
+}
+
+void
+ide_build_stage_set_name (IdeBuildStage *self,
+ const gchar *name)
+{
+ IdeBuildStagePrivate *priv = ide_build_stage_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_BUILD_STAGE (self));
+
+ if (g_strcmp0 (name, priv->name) != 0)
+ {
+ g_free (priv->name);
+ priv->name = g_strdup (name);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NAME]);
+ }
+}
+
+static void
+ide_build_stage_finalize (GObject *object)
+{
+ IdeBuildStage *self = (IdeBuildStage *)object;
+ IdeBuildStagePrivate *priv = ide_build_stage_get_instance_private (self);
+
+ ide_build_stage_clear_observer (self);
+
+ g_clear_pointer (&priv->name, g_free);
+
+ G_OBJECT_CLASS (ide_build_stage_parent_class)->finalize (object);
+}
+
+static void
+ide_build_stage_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBuildStage *self = IDE_BUILD_STAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, ide_build_stage_get_name (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_build_stage_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ IdeBuildStage *self = IDE_BUILD_STAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ ide_build_stage_set_name (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+ide_build_stage_class_init (IdeBuildStageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = ide_build_stage_finalize;
+ object_class->get_property = ide_build_stage_get_property;
+ object_class->set_property = ide_build_stage_set_property;
+
+ klass->execute = ide_build_stage_real_execute;
+ klass->execute_async = ide_build_stage_real_execute_async;
+ klass->execute_finish = ide_build_stage_real_execute_finish;
+
+ properties [PROP_NAME] =
+ g_param_spec_string ("name",
+ "Name",
+ "The user visible name of the stage",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_build_stage_init (IdeBuildStage *self)
+{
+}
+
+void
+ide_build_stage_execute_async (IdeBuildStage *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (IDE_IS_BUILD_STAGE (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ IDE_BUILD_STAGE_GET_CLASS (self)->execute_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_build_stage_execute_finish (IdeBuildStage *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (IDE_IS_BUILD_STAGE (self), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+ return IDE_BUILD_STAGE_GET_CLASS (self)->execute_finish (self, result, error);
+}
+
+/**
+ * ide_build_stage_set_log_observer:
+ * @self: An #IdeBuildStage
+ * @observer: (scope async): The observer for the log entries
+ * @observer_data: data for @observer
+ * @observer_data_destroy: destroy callback for @observer_data
+ *
+ * Sets the log observer to handle calls to the various stage logging
+ * functions. This will be set by the pipeline to mux logs from all
+ * stages into a unified build log.
+ *
+ * Plugins that need to handle logging from a build stage should set
+ * an observer on the pipeline so that log distribution may be fanned
+ * out to all observers.
+ */
+void
+ide_build_stage_set_log_observer (IdeBuildStage *self,
+ IdeBuildLogObserver observer,
+ gpointer observer_data,
+ GDestroyNotify observer_data_destroy)
+{
+ IdeBuildStagePrivate *priv = ide_build_stage_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_BUILD_STAGE (self));
+
+ ide_build_stage_clear_observer (self);
+
+ priv->observer = observer;
+ priv->observer_data = observer_data;
+ priv->observer_data_destroy = observer_data_destroy;
+}
+
+void
+ide_build_stage_log (IdeBuildStage *self,
+ IdeBuildLogStream stream,
+ const gchar *message,
+ gssize message_len)
+{
+ IdeBuildStagePrivate *priv = ide_build_stage_get_instance_private (self);
+
+ if G_LIKELY (priv->observer)
+ priv->observer (stream, message, message_len, priv->observer_data);
+}
diff --git a/libide/buildsystem/ide-build-stage.h b/libide/buildsystem/ide-build-stage.h
new file mode 100644
index 0000000..0af4e41
--- /dev/null
+++ b/libide/buildsystem/ide-build-stage.h
@@ -0,0 +1,85 @@
+/* ide-build-stage.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BUILD_STAGE_H
+#define IDE_BUILD_STAGE_H
+
+#include <gio/gio.h>
+
+#include "buildsystem/ide-build-log.h"
+#include "buildsystem/ide-configuration.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BUILD_STAGE (ide_build_stage_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeBuildStage, ide_build_stage, IDE, BUILD_STAGE, IdeObject)
+
+struct _IdeBuildStageClass
+{
+ IdeObjectClass parent_class;
+
+ gboolean (*execute) (IdeBuildStage *self,
+ GCancellable *cancellable,
+ GError **error);
+ void (*execute_async) (IdeBuildStage *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ gboolean (*execute_finish) (IdeBuildStage *self,
+ GAsyncResult *result,
+ GError **error);
+
+ gpointer _reserved1;
+ gpointer _reserved2;
+ gpointer _reserved3;
+ gpointer _reserved4;
+ gpointer _reserved5;
+ gpointer _reserved6;
+ gpointer _reserved7;
+ gpointer _reserved8;
+ gpointer _reserved9;
+ gpointer _reserved10;
+ gpointer _reserved11;
+ gpointer _reserved12;
+};
+
+IdeBuildStage *ide_build_stage_new (void);
+const gchar *ide_build_stage_get_name (IdeBuildStage *self);
+void ide_build_stage_set_name (IdeBuildStage *self,
+ const gchar *name);
+void ide_build_stage_log (IdeBuildStage *self,
+ IdeBuildLogStream stream,
+ const gchar *message,
+ gssize message_len);
+void ide_build_stage_set_log_observer (IdeBuildStage *self,
+ IdeBuildLogObserver observer,
+ gpointer observer_data,
+ GDestroyNotify observer_data_destroy);
+void ide_build_stage_execute_async (IdeBuildStage *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean ide_build_stage_execute_finish (IdeBuildStage *self,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* IDE_BUILD_STAGE_H */
+
diff --git a/libide/ide-enums.c.in b/libide/ide-enums.c.in
index 0f71244..f6aba69 100644
--- a/libide/ide-enums.c.in
+++ b/libide/ide-enums.c.in
@@ -5,6 +5,8 @@
#include "ide-enums.h"
#include "buffers/ide-buffer.h"
+#include "buildsystem/ide-build-log.h"
+#include "buildsystem/ide-build-pipeline.h"
#include "buildsystem/ide-build-result.h"
#include "devices/ide-device.h"
#include "diagnostics/ide-diagnostic.h"
diff --git a/libide/ide-types.h b/libide/ide-types.h
index 230ebaa..49a6316 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -35,11 +35,13 @@ typedef struct _IdeBufferChangeMonitor IdeBufferChangeMonitor;
typedef struct _IdeBufferManager IdeBufferManager;
-typedef struct _IdeBuilder IdeBuilder;
typedef struct _IdeBuildCommand IdeBuildCommand;
typedef struct _IdeBuildCommandQueue IdeBuildCommandQueue;
+typedef struct _IdeBuilder IdeBuilder;
typedef struct _IdeBuildManager IdeBuildManager;
+typedef struct _IdeBuildPipeline IdeBuildPipeline;
typedef struct _IdeBuildResult IdeBuildResult;
+typedef struct _IdeBuildStage IdeBuildStage;
typedef struct _IdeBuildSystem IdeBuildSystem;
typedef struct _IdeBuildTarget IdeBuildTarget;
diff --git a/libide/ide.h b/libide/ide.h
index 78aa113..415b24b 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -38,8 +38,12 @@ G_BEGIN_DECLS
#include "buildsystem/ide-build-command.h"
#include "buildsystem/ide-build-command-queue.h"
#include "buildsystem/ide-build-manager.h"
+#include "buildsystem/ide-build-pipeline.h"
+#include "buildsystem/ide-build-pipeline-addin.h"
#include "buildsystem/ide-build-result-addin.h"
#include "buildsystem/ide-build-result.h"
+#include "buildsystem/ide-build-stage.h"
+#include "buildsystem/ide-build-stage-launcher.h"
#include "buildsystem/ide-build-system.h"
#include "buildsystem/ide-build-target.h"
#include "buildsystem/ide-builder.h"
diff --git a/plugins/autotools/Makefile.am b/plugins/autotools/Makefile.am
index 09e4ebb..ce3570a 100644
--- a/plugins/autotools/Makefile.am
+++ b/plugins/autotools/Makefile.am
@@ -10,6 +10,8 @@ libautotools_plugin_la_SOURCES = \
ide-autotools-application-addin.h \
ide-autotools-builder.c \
ide-autotools-builder.h \
+ ide-autotools-build-pipeline-addin.c \
+ ide-autotools-build-pipeline-addin.h \
ide-autotools-build-system.c \
ide-autotools-build-system.h \
ide-autotools-build-target.c \
diff --git a/plugins/autotools/autotools-plugin.c b/plugins/autotools/autotools-plugin.c
index 9e15698..8490ebf 100644
--- a/plugins/autotools/autotools-plugin.c
+++ b/plugins/autotools/autotools-plugin.c
@@ -20,6 +20,7 @@
#include <ide.h>
#include "ide-autotools-application-addin.h"
+#include "ide-autotools-build-pipeline-addin.h"
#include "ide-autotools-build-system.h"
#include "ide-autotools-project-miner.h"
@@ -27,6 +28,7 @@ void
peas_register_types (PeasObjectModule *module)
{
peas_object_module_register_extension_type (module, IDE_TYPE_APPLICATION_ADDIN,
IDE_TYPE_AUTOTOOLS_APPLICATION_ADDIN);
+ peas_object_module_register_extension_type (module, IDE_TYPE_BUILD_PIPELINE_ADDIN,
IDE_TYPE_AUTOTOOLS_BUILD_PIPELINE_ADDIN);
peas_object_module_register_extension_type (module, IDE_TYPE_BUILD_SYSTEM,
IDE_TYPE_AUTOTOOLS_BUILD_SYSTEM);
peas_object_module_register_extension_type (module, IDE_TYPE_PROJECT_MINER,
IDE_TYPE_AUTOTOOLS_PROJECT_MINER);
}
diff --git a/plugins/autotools/ide-autotools-build-pipeline-addin.c
b/plugins/autotools/ide-autotools-build-pipeline-addin.c
new file mode 100644
index 0000000..276ec9e
--- /dev/null
+++ b/plugins/autotools/ide-autotools-build-pipeline-addin.c
@@ -0,0 +1,182 @@
+/* ide-autotools-build-pipeline-addin.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-autotools-build-pipeline-addin"
+
+#include "ide-autotools-build-pipeline-addin.h"
+#include "ide-autotools-build-system.h"
+
+struct _IdeAutotoolsBuildPipelineAddin
+{
+ IdeObject parent;
+ GArray *stage_ids;
+};
+
+static void
+add_launcher (IdeAutotoolsBuildPipelineAddin *self,
+ IdeBuildPipeline *pipeline,
+ IdeBuildPhase phase,
+ IdeSubprocessLauncher *launcher)
+{
+ guint stage_id;
+
+ g_assert (IDE_IS_AUTOTOOLS_BUILD_PIPELINE_ADDIN (self));
+ g_assert (IDE_IS_BUILD_PIPELINE (pipeline));
+ g_assert (IDE_IS_SUBPROCESS_LAUNCHER (launcher));
+
+ stage_id = ide_build_pipeline_insert_stage_from_launcher (pipeline, phase, 0, launcher);
+ g_array_append_val (self->stage_ids, stage_id);
+}
+
+static void
+ide_autotools_build_pipeline_addin_load (IdeBuildPipelineAddin *addin,
+ IdeBuildPipeline *pipeline)
+{
+ IdeAutotoolsBuildPipelineAddin *self = (IdeAutotoolsBuildPipelineAddin *)addin;
+ g_autoptr(IdeSubprocessLauncher) autogen_launcher = NULL;
+ g_autoptr(IdeSubprocessLauncher) configure_launcher = NULL;
+ g_autoptr(IdeSubprocessLauncher) make_launcher = NULL;
+ g_autoptr(IdeSubprocessLauncher) install_launcher = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *autogen_sh = NULL;
+ g_autofree gchar *configure = NULL;
+ g_autofree gchar *j = NULL;
+ IdeConfiguration *config;
+ IdeBuildSystem *build_system;
+ IdeContext *context;
+ IdeRuntime *runtime;
+ const gchar *make = "make";
+ const gchar *builddir;
+ const gchar *srcdir;
+ const gchar *config_opts;
+ gint parallel;
+
+ g_assert (IDE_IS_AUTOTOOLS_BUILD_PIPELINE_ADDIN (self));
+ g_assert (IDE_IS_BUILD_PIPELINE (pipeline));
+
+ self->stage_ids = g_array_new (FALSE, FALSE, sizeof (guint));
+
+ context = ide_object_get_context (IDE_OBJECT (addin));
+ build_system = ide_context_get_build_system (context);
+ config = ide_build_pipeline_get_configuration (pipeline);
+ runtime = ide_configuration_get_runtime (config);
+
+ if (!IDE_IS_AUTOTOOLS_BUILD_SYSTEM (build_system))
+ return;
+
+ if (NULL == (autogen_launcher = ide_runtime_create_launcher (runtime, &error)) ||
+ NULL == (configure_launcher = ide_runtime_create_launcher (runtime, &error)) ||
+ NULL == (make_launcher = ide_runtime_create_launcher (runtime, &error)) ||
+ NULL == (install_launcher = ide_runtime_create_launcher (runtime, &error)))
+ {
+ g_warning ("Failed to create autotools launcher: %s", error->message);
+ return;
+ }
+
+ srcdir = ide_build_pipeline_get_builddir (pipeline);
+ builddir = ide_build_pipeline_get_builddir (pipeline);
+ config_opts = ide_configuration_get_config_opts (config);
+ parallel = ide_configuration_get_parallelism (config);
+
+ if (parallel == -1)
+ j = g_strdup_printf ("-j%u", g_get_num_processors () + 1);
+ else if (parallel == 0)
+ j = g_strdup_printf ("-j%u", g_get_num_processors ());
+ else
+ j = g_strdup_printf ("-j%u", parallel);
+
+ autogen_sh = g_build_filename (srcdir, "autogen.sh", NULL);
+ ide_subprocess_launcher_push_argv (autogen_launcher, autogen_sh);
+ ide_subprocess_launcher_setenv (autogen_launcher, "NOCONFIGURE", "1", TRUE);
+ ide_subprocess_launcher_set_cwd (autogen_launcher, srcdir);
+
+ configure = g_build_filename (builddir, "configure", NULL);
+ ide_subprocess_launcher_push_argv (configure_launcher, configure);
+ ide_subprocess_launcher_set_cwd (configure_launcher, builddir);
+
+ if (config_opts != NULL)
+ {
+ g_auto(GStrv) argv = NULL;
+ gint argc;
+
+ if (g_shell_parse_argv (config_opts, &argc, &argv, NULL))
+ {
+ for (guint i = 0; i < argc; i++)
+ ide_subprocess_launcher_push_argv (configure_launcher, argv[i]);
+ }
+ }
+
+ if (ide_runtime_contains_program_in_path (runtime, "gmake", NULL))
+ make = "gmake";
+
+ ide_subprocess_launcher_push_argv (make_launcher, make);
+ ide_subprocess_launcher_push_argv (make_launcher, "all");
+ ide_subprocess_launcher_push_argv (make_launcher, j);
+ ide_subprocess_launcher_setenv (make_launcher, "V", "1", TRUE);
+ ide_subprocess_launcher_set_cwd (make_launcher, builddir);
+
+ ide_subprocess_launcher_push_argv (install_launcher, make);
+ ide_subprocess_launcher_push_argv (install_launcher, "install");
+ ide_subprocess_launcher_push_argv (install_launcher, j);
+ ide_subprocess_launcher_setenv (install_launcher, "V", "1", TRUE);
+ ide_subprocess_launcher_set_cwd (install_launcher, builddir);
+
+ add_launcher (self, pipeline, IDE_BUILD_PHASE_AUTOGEN, autogen_launcher);
+ add_launcher (self, pipeline, IDE_BUILD_PHASE_CONFIGURE, configure_launcher);
+ add_launcher (self, pipeline, IDE_BUILD_PHASE_BUILD, make_launcher);
+ add_launcher (self, pipeline, IDE_BUILD_PHASE_INSTALL, install_launcher);
+}
+
+static void
+ide_autotools_build_pipeline_addin_unload (IdeBuildPipelineAddin *addin,
+ IdeBuildPipeline *pipeline)
+{
+ IdeAutotoolsBuildPipelineAddin *self = (IdeAutotoolsBuildPipelineAddin *)addin;
+
+ g_assert (IDE_IS_AUTOTOOLS_BUILD_PIPELINE_ADDIN (self));
+ g_assert (IDE_IS_BUILD_PIPELINE (pipeline));
+
+ for (guint i = 0; i < self->stage_ids->len; i++)
+ {
+ guint stage_id = g_array_index (self->stage_ids, guint, i);
+
+ ide_build_pipeline_remove (pipeline, stage_id);
+ }
+
+ g_clear_pointer (&self->stage_ids, g_array_unref);
+}
+
+static void
+addin_iface_init (IdeBuildPipelineAddinInterface *iface)
+{
+ iface->load = ide_autotools_build_pipeline_addin_load;
+ iface->unload = ide_autotools_build_pipeline_addin_unload;
+}
+
+G_DEFINE_TYPE_WITH_CODE (IdeAutotoolsBuildPipelineAddin, ide_autotools_build_pipeline_addin, IDE_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_BUILD_PIPELINE_ADDIN, addin_iface_init))
+
+static void
+ide_autotools_build_pipeline_addin_class_init (IdeAutotoolsBuildPipelineAddinClass *klass)
+{
+}
+
+static void
+ide_autotools_build_pipeline_addin_init (IdeAutotoolsBuildPipelineAddin *self)
+{
+}
diff --git a/plugins/autotools/ide-autotools-build-pipeline-addin.h
b/plugins/autotools/ide-autotools-build-pipeline-addin.h
new file mode 100644
index 0000000..375271b
--- /dev/null
+++ b/plugins/autotools/ide-autotools-build-pipeline-addin.h
@@ -0,0 +1,32 @@
+/* ide-autotools-build-pipeline-addin.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_AUTOTOOLS_BUILD_PIPELINE_ADDIN_H
+#define IDE_AUTOTOOLS_BUILD_PIPELINE_ADDIN_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_AUTOTOOLS_BUILD_PIPELINE_ADDIN (ide_autotools_build_pipeline_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeAutotoolsBuildPipelineAddin, ide_autotools_build_pipeline_addin, IDE,
AUTOTOOLS_BUILD_PIPELINE_ADDIN, IdeObject)
+
+G_END_DECLS
+
+#endif /* IDE_AUTOTOOLS_BUILD_PIPELINE_ADDIN_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index bfe716f..f72fb7c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -123,6 +123,12 @@ test_ide_builder_CFLAGS = $(tests_cflags)
test_ide_builder_LDADD = $(tests_libs)
+TESTS += test-ide-build-pipeline
+test_ide_build_pipeline_SOURCES = test-ide-build-pipeline.c
+test_ide_build_pipeline_CFLAGS = $(tests_cflags)
+test_ide_build_pipeline_LDADD = $(tests_libs)
+
+
TESTS += test-ide-doap
test_ide_doap_SOURCES = test-ide-doap.c
test_ide_doap_CFLAGS = $(tests_cflags)
diff --git a/tests/test-ide-build-pipeline.c b/tests/test-ide-build-pipeline.c
new file mode 100644
index 0000000..4429913
--- /dev/null
+++ b/tests/test-ide-build-pipeline.c
@@ -0,0 +1,115 @@
+/* test-ide-build-pipeline.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ide.h>
+
+#include "application/ide-application-tests.h"
+
+static void
+execute_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ IdeBuildPipeline *pipeline = (IdeBuildPipeline *)object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GTask) task = user_data;
+ gint r;
+
+ g_assert (IDE_IS_BUILD_PIPELINE (pipeline));
+
+ r = ide_build_pipeline_execute_finish (pipeline, result, &error);
+ g_assert_no_error (error);
+ g_assert_cmpint (r, ==, TRUE);
+
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+context_loaded (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(IdeContext) context = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(IdeBuildPipeline) pipeline = NULL;
+ IdeConfigurationManager *config_manager;
+ IdeConfiguration *config;
+
+ context = ide_context_new_finish (result, &error);
+ g_assert_no_error (error);
+ g_assert (context != NULL);
+ g_assert (IDE_IS_CONTEXT (context));
+
+ config_manager = ide_context_get_configuration_manager (context);
+ g_assert (IDE_IS_CONFIGURATION_MANAGER (config_manager));
+
+ config = ide_configuration_manager_get_current (config_manager);
+ g_assert (IDE_IS_CONFIGURATION (config));
+
+ pipeline = g_object_new (IDE_TYPE_BUILD_PIPELINE,
+ "context", context,
+ "configuration", config,
+ NULL);
+
+ ide_build_pipeline_execute_async (pipeline,
+ NULL,
+ execute_cb,
+ g_steal_pointer (&task));
+}
+
+static void
+test_build_pipeline (GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GFile) project_file = NULL;
+ g_autofree gchar *path = NULL;
+ const gchar *srcdir = g_getenv ("G_TEST_SRCDIR");
+ g_autoptr(GTask) task = NULL;
+
+ task = g_task_new (NULL, cancellable, callback, user_data);
+
+ path = g_build_filename (srcdir, "data", "project1", NULL);
+ project_file = g_file_new_for_path (path);
+
+ ide_context_new_async (project_file,
+ cancellable,
+ context_loaded,
+ g_steal_pointer (&task));
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ IdeApplication *app;
+ gint ret;
+
+ g_test_init (&argc, &argv, NULL);
+
+ ide_log_init (TRUE, NULL);
+ ide_log_set_verbosity (4);
+
+ app = ide_application_new ();
+ ide_application_add_test (app, "/Ide/BuildPipeline/basic", test_build_pipeline, NULL);
+ ret = g_application_run (G_APPLICATION (app), argc, argv);
+ g_object_unref (app);
+
+ return ret;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]