[gnome-builder] plugins/meson: port to GTK 4



commit 4693f7dc83715c7bb021f5cdc51c8def220d02d5
Author: Christian Hergert <chergert redhat com>
Date:   Mon Jul 11 23:06:30 2022 -0700

    plugins/meson: port to GTK 4
    
     - Remove libdazzle usage
     - Remove unnecessary Since: docs
     - Implement introspection support
     - Use run commands to access introspection
     - Use pipeline addin to register introspection stage
     - Cleanup meson discovery
     - Remove test provider

 src/plugins/meson/gbp-meson-build-system.c         | 101 ++-
 src/plugins/meson/gbp-meson-build-system.h         |   8 +-
 .../meson/gbp-meson-build-target-provider.c        |  14 +-
 src/plugins/meson/gbp-meson-introspection.c        | 682 +++++++++++++++++++++
 src/plugins/meson/gbp-meson-introspection.h        |  40 ++
 src/plugins/meson/gbp-meson-pipeline-addin.c       | 347 +++++------
 src/plugins/meson/gbp-meson-pipeline-addin.h       |   4 +
 src/plugins/meson/gbp-meson-run-command-provider.c | 171 ++++++
 ...provider.h => gbp-meson-run-command-provider.h} |   8 +-
 src/plugins/meson/gbp-meson-test-provider.c        | 655 --------------------
 src/plugins/meson/gbp-meson-test.c                 | 198 ------
 src/plugins/meson/gbp-meson-test.h                 |  36 --
 .../gbp-meson-toolchain-edition-preferences-row.c  |   2 -
 src/plugins/meson/gbp-meson-toolchain.c            |   2 -
 src/plugins/meson/meson-plugin.c                   |  25 +-
 src/plugins/meson/meson.build                      |  17 +-
 src/plugins/meson/meson.plugin                     |   5 +-
 17 files changed, 1206 insertions(+), 1109 deletions(-)
---
diff --git a/src/plugins/meson/gbp-meson-build-system.c b/src/plugins/meson/gbp-meson-build-system.c
index ee7dfd18e..df26e691b 100644
--- a/src/plugins/meson/gbp-meson-build-system.c
+++ b/src/plugins/meson/gbp-meson-build-system.c
@@ -20,8 +20,11 @@
 
 #define G_LOG_DOMAIN "gbp-meson-build-system"
 
+#include "config.h"
+
 #include <glib/gi18n.h>
 #include <json-glib/json-glib.h>
+#include <string.h>
 
 #include "gbp-meson-build-system.h"
 #include "gbp-meson-build-target.h"
@@ -29,20 +32,20 @@
 
 struct _GbpMesonBuildSystem
 {
-  IdeObject           parent_instance;
-  GFile              *project_file;
-  IdeCompileCommands *compile_commands;
-  GFileMonitor       *monitor;
-  gchar              *project_version;
-  gchar             **languages;
+  IdeObject            parent_instance;
+  GFile               *project_file;
+  IdeCompileCommands  *compile_commands;
+  GFileMonitor        *monitor;
+  char                *project_version;
+  char               **languages;
 };
 
 static void async_initable_iface_init (GAsyncInitableIface     *iface);
 static void build_system_iface_init   (IdeBuildSystemInterface *iface);
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpMesonBuildSystem, gbp_meson_build_system, IDE_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
-                         G_IMPLEMENT_INTERFACE (IDE_TYPE_BUILD_SYSTEM, build_system_iface_init))
+                               G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_BUILD_SYSTEM, build_system_iface_init))
 
 enum {
   PROP_0,
@@ -983,3 +986,85 @@ gbp_meson_build_system_get_languages (GbpMesonBuildSystem *self)
 
   return (const gchar * const *)self->languages;
 }
+
+char *
+gbp_meson_build_system_get_project_dir (GbpMesonBuildSystem *self)
+{
+  g_autoptr(GFile) workdir = NULL;
+  g_autofree char *base = NULL;
+  IdeContext *context;
+
+  g_return_val_if_fail (GBP_IS_MESON_BUILD_SYSTEM (self), NULL);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  workdir = ide_context_ref_workdir (context);
+
+  if (self->project_file == NULL)
+    return g_strdup (g_file_peek_path (workdir));
+
+  base = g_file_get_basename (self->project_file);
+
+  if (strcasecmp (base, "meson.build") == 0)
+    {
+      g_autoptr(GFile) parent = g_file_get_parent (self->project_file);
+      return g_file_get_path (parent);
+    }
+
+  return g_file_get_path (self->project_file);
+}
+
+char *
+gbp_meson_build_system_locate_meson (GbpMesonBuildSystem *self,
+                                     IdePipeline         *pipeline)
+{
+  IdeConfig *config;
+
+  g_return_val_if_fail (!self || GBP_IS_MESON_BUILD_SYSTEM (self), NULL);
+  g_return_val_if_fail (!pipeline || IDE_IS_PIPELINE (pipeline), NULL);
+
+  if ((config = ide_pipeline_get_config (pipeline)))
+    {
+      const char *envvar = ide_config_getenv (config, "MESON");
+
+      if (envvar != NULL)
+        return g_strdup (envvar);
+    }
+
+  return g_strdup ("meson");
+}
+
+char *
+gbp_meson_build_system_locate_ninja (GbpMesonBuildSystem *self,
+                                     IdePipeline         *pipeline)
+{
+  IdeConfig *config = NULL;
+
+  g_return_val_if_fail (!self || GBP_IS_MESON_BUILD_SYSTEM (self), NULL);
+  g_return_val_if_fail (!pipeline || IDE_IS_PIPELINE (pipeline), NULL);
+
+  if (pipeline != NULL && config == NULL)
+    config = ide_pipeline_get_config (pipeline);
+
+  /* First check NINJA=path override in IdeConfig */
+  if (config != NULL)
+    {
+      const char *envvar = ide_config_getenv (config, "NINJA");
+
+      if (envvar != NULL)
+        return g_strdup (envvar);
+    }
+
+  if (pipeline != NULL)
+    {
+      static const char *known_aliases[] = { "ninja", "ninja-build" };
+
+      for (guint i = 0; i < G_N_ELEMENTS (known_aliases); i++)
+        {
+          if (ide_pipeline_contains_program_in_path (pipeline, known_aliases[i], NULL))
+            return g_strdup (known_aliases[i]);
+        }
+    }
+
+  /* Fallback to "ninja" and hope for the best */
+  return g_strdup ("ninja");
+}
diff --git a/src/plugins/meson/gbp-meson-build-system.h b/src/plugins/meson/gbp-meson-build-system.h
index 5cb726ff3..bd0f57ece 100644
--- a/src/plugins/meson/gbp-meson-build-system.h
+++ b/src/plugins/meson/gbp-meson-build-system.h
@@ -27,6 +27,12 @@ G_BEGIN_DECLS
 #define GBP_TYPE_MESON_BUILD_SYSTEM (gbp_meson_build_system_get_type())
 
 G_DECLARE_FINAL_TYPE (GbpMesonBuildSystem, gbp_meson_build_system, GBP, MESON_BUILD_SYSTEM, IdeObject)
-const gchar * const * gbp_meson_build_system_get_languages (GbpMesonBuildSystem *self);
+
+const gchar * const *gbp_meson_build_system_get_languages   (GbpMesonBuildSystem *self);
+char                *gbp_meson_build_system_get_project_dir (GbpMesonBuildSystem *self);
+char                *gbp_meson_build_system_locate_meson    (GbpMesonBuildSystem *self,
+                                                             IdePipeline         *pipeline);
+char                *gbp_meson_build_system_locate_ninja    (GbpMesonBuildSystem *self,
+                                                             IdePipeline         *pipeline);
 
 G_END_DECLS
diff --git a/src/plugins/meson/gbp-meson-build-target-provider.c 
b/src/plugins/meson/gbp-meson-build-target-provider.c
index a78e050b3..f81c626da 100644
--- a/src/plugins/meson/gbp-meson-build-target-provider.c
+++ b/src/plugins/meson/gbp-meson-build-target-provider.c
@@ -55,7 +55,7 @@ create_launcher (IdeContext  *context,
       return NULL;
     }
 
-  if ((ret = ide_runtime_create_launcher (runtime, error)))
+  if ((ret = ide_pipeline_create_launcher (pipeline, error)))
     {
       ide_subprocess_launcher_set_flags (ret, G_SUBPROCESS_FLAGS_STDOUT_PIPE | 
G_SUBPROCESS_FLAGS_STDERR_SILENCE);
       ide_subprocess_launcher_set_cwd (ret, ide_pipeline_get_builddir (pipeline));
@@ -314,19 +314,17 @@ gbp_meson_build_target_provider_communicate_cb (GObject      *object,
       return;
     }
 
-  launcher = create_launcher (context, &error);
+  context = ide_object_get_context (IDE_OBJECT (self));
+  build_manager = ide_build_manager_from_context (context);
+  pipeline = ide_build_manager_get_pipeline (build_manager);
+  cancellable = ide_task_get_cancellable (task);
 
-  if (launcher == NULL)
+  if (!(launcher = ide_pipeline_create_launcher (pipeline, &error)))
     {
       ide_task_return_error (task, g_steal_pointer (&error));
       return;
     }
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-  build_manager = ide_build_manager_from_context (context);
-  pipeline = ide_build_manager_get_pipeline (build_manager);
-  cancellable = ide_task_get_cancellable (task);
-
   ide_subprocess_launcher_push_argv (launcher, "meson");
   ide_subprocess_launcher_push_argv (launcher, "introspect");
   ide_subprocess_launcher_push_argv (launcher, "--installed");
diff --git a/src/plugins/meson/gbp-meson-introspection.c b/src/plugins/meson/gbp-meson-introspection.c
new file mode 100644
index 000000000..59038844d
--- /dev/null
+++ b/src/plugins/meson/gbp-meson-introspection.c
@@ -0,0 +1,682 @@
+/* gbp-meson-introspection.c
+ *
+ * Copyright 2022 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-meson-introspection"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <json-glib/json-glib.h>
+
+#include <libide-core.h>
+#include <libide-foundry.h>
+#include <libide-threading.h>
+
+#include "gbp-meson-build-system.h"
+#include "gbp-meson-introspection.h"
+
+struct _GbpMesonIntrospection
+{
+  IdePipelineStage parent_instance;
+
+  IdePipeline *pipeline;
+
+  char *etag;
+
+  GListStore *run_commands;
+
+  char *descriptive_name;
+  char *subproject_dir;
+  char *version;
+
+  guint loaded : 1;
+  guint has_built_once : 1;
+};
+
+G_DEFINE_FINAL_TYPE (GbpMesonIntrospection, gbp_meson_introspection, IDE_TYPE_PIPELINE_STAGE)
+
+static gboolean
+get_string_member (JsonObject  *object,
+                   const char  *member,
+                   char       **location)
+{
+  JsonNode *node;
+
+  g_assert (object != NULL);
+  g_assert (member != NULL);
+  g_assert (location != NULL);
+
+  g_clear_pointer (location, g_free);
+
+  if (json_object_has_member (object, member) &&
+      (node = json_object_get_member (object, member)) &&
+      JSON_NODE_HOLDS_VALUE (node))
+    {
+      *location = g_strdup (json_node_get_string (node));
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+get_strv_member (JsonObject   *object,
+                 const char   *member,
+                 char       ***location)
+{
+  JsonNode *node;
+  JsonArray *ar;
+
+  g_assert (object != NULL);
+  g_assert (member != NULL);
+  g_assert (location != NULL);
+
+  g_clear_pointer (location, g_strfreev);
+
+  if (json_object_has_member (object, member) &&
+      (node = json_object_get_member (object, member)) &&
+      JSON_NODE_HOLDS_ARRAY (node) &&
+      (ar = json_node_get_array (node)))
+    {
+      GPtrArray *strv = g_ptr_array_new ();
+      guint n_items = json_array_get_length (ar);
+
+      for (guint i = 0; i < n_items; i++)
+        {
+          JsonNode *ele = json_array_get_element (ar, i);
+          const char *str;
+
+          if (JSON_NODE_HOLDS_VALUE (ele) &&
+              (str = json_node_get_string (ele)))
+            g_ptr_array_add (strv, g_strdup (str));
+        }
+
+      g_ptr_array_add (strv, NULL);
+
+      *location = (char **)(gpointer)g_ptr_array_free (strv, FALSE);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+get_environ_member (JsonObject   *object,
+                    const char   *member,
+                    char       ***location)
+{
+  JsonNode *node;
+  JsonObject *envobj;
+
+  g_assert (object != NULL);
+  g_assert (member != NULL);
+  g_assert (location != NULL);
+  g_assert (*location == NULL);
+
+  if (json_object_has_member (object, member) &&
+      (node = json_object_get_member (object, member)) &&
+      JSON_NODE_HOLDS_OBJECT (node) &&
+      (envobj = json_node_get_object (node)))
+    {
+      JsonObjectIter iter;
+      const char *key;
+      JsonNode *value_node;
+
+      json_object_iter_init (&iter, envobj);
+      while (json_object_iter_next (&iter, &key, &value_node))
+        {
+          const char *value;
+
+          if (!JSON_NODE_HOLDS_VALUE (value_node) ||
+              !(value = json_node_get_string (value_node)))
+            continue;
+
+          *location = g_environ_setenv (*location, key, value, TRUE);
+        }
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+gbp_meson_introspection_load_buildoptions (GbpMesonIntrospection *self,
+                                           JsonArray             *buildoptions)
+{
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (buildoptions != NULL);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_introspection_load_projectinfo (GbpMesonIntrospection *self,
+                                          JsonObject            *projectinfo)
+{
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (projectinfo != NULL);
+
+  get_string_member (projectinfo, "version", &self->version);
+  get_string_member (projectinfo, "descriptive_name", &self->descriptive_name);
+  get_string_member (projectinfo, "subproject_dir", &self->subproject_dir);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_introspection_load_test (GbpMesonIntrospection *self,
+                                   JsonObject            *test)
+{
+  g_autoptr(IdeRunCommand) run_command = NULL;
+  g_auto(GStrv) cmd = NULL;
+  g_auto(GStrv) env = NULL;
+  g_auto(GStrv) suite = NULL;
+  g_autofree char *name = NULL;
+  g_autofree char *workdir = NULL;
+  g_autofree char *id = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (test != NULL);
+
+  get_strv_member (test, "cmd", &cmd);
+  get_strv_member (test, "suite", &suite);
+  get_environ_member (test, "env", &env);
+  get_string_member (test, "name", &name);
+  get_string_member (test, "workdir", &workdir);
+
+  id = g_strdup_printf ("meson:%s", name);
+
+  run_command = ide_run_command_new ();
+  ide_run_command_set_id (run_command, id);
+  ide_run_command_set_kind (run_command, IDE_RUN_COMMAND_KIND_TEST);
+  ide_run_command_set_display_name (run_command, name);
+  ide_run_command_set_environ (run_command, (const char * const *)env);
+  ide_run_command_set_argv (run_command, (const char * const *)cmd);
+  ide_run_command_set_cwd (run_command, workdir);
+
+  g_list_store_append (self->run_commands, run_command);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_introspection_load_tests (GbpMesonIntrospection *self,
+                                    JsonArray             *tests)
+{
+  guint n_items;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (tests != NULL);
+
+  n_items = json_array_get_length (tests);
+
+  for (guint i = 0; i < n_items; i++)
+    {
+      JsonNode *node = json_array_get_element (tests, i);
+      JsonObject *obj;
+
+      if (node != NULL &&
+          JSON_NODE_HOLDS_OBJECT (node) &&
+          (obj = json_node_get_object (node)))
+        gbp_meson_introspection_load_test (self, obj);
+    }
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_introspection_load_benchmarks (GbpMesonIntrospection *self,
+                                         JsonArray             *benchmarks)
+{
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (benchmarks != NULL);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_introspection_load_targets (GbpMesonIntrospection *self,
+                                      JsonArray             *targets)
+{
+  guint length;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (targets != NULL);
+
+  length = json_array_get_length (targets);
+
+  for (guint i = 0; i < length; i++)
+    {
+      JsonNode *node = json_array_get_element (targets, i);
+      g_autofree char *id = NULL;
+      g_autofree char *name = NULL;
+      g_autofree char *type = NULL;
+      JsonObject *obj;
+
+      if (!JSON_NODE_HOLDS_OBJECT (node) || !(obj = json_node_get_object (node)))
+        continue;
+
+      get_string_member (obj, "id", &id);
+      get_string_member (obj, "name", &name);
+      get_string_member (obj, "type", &type);
+
+      if (ide_str_equal0 (type, "executable"))
+        {
+          g_auto(GStrv) filename = NULL;
+
+          get_strv_member (obj, "filename", &filename);
+
+          if (filename != NULL && filename[0] != NULL)
+            {
+              g_autoptr(IdeRunCommand) run_command = ide_run_command_new ();
+
+              ide_run_command_set_kind (run_command, IDE_RUN_COMMAND_KIND_UTILITY);
+              ide_run_command_set_id (run_command, id);
+              ide_run_command_set_display_name (run_command, name);
+              ide_run_command_set_argv (run_command, IDE_STRV_INIT (filename[0]));
+
+              g_list_store_append (self->run_commands, run_command);
+            }
+        }
+    }
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_introspection_load_installed (GbpMesonIntrospection *self,
+                                        JsonObject            *installed)
+{
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (installed != NULL);
+
+  IDE_EXIT;
+}
+
+static char *
+get_current_etag (IdePipeline *pipeline)
+{
+  g_autofree char *build_dot_ninja = NULL;
+  g_autoptr(GFileInfo) info = NULL;
+  g_autoptr(GFile) file = NULL;
+
+  g_assert (IDE_IS_PIPELINE (pipeline));
+
+  build_dot_ninja = ide_pipeline_build_builddir_path (pipeline, "build.ninja", NULL);
+  file = g_file_new_for_path (build_dot_ninja);
+  info = g_file_query_info (file,
+                            G_FILE_ATTRIBUTE_ETAG_VALUE,
+                            G_FILE_QUERY_INFO_NONE,
+                            NULL, NULL);
+
+  if (info == NULL)
+    return NULL;
+
+  return g_strdup (g_file_info_get_etag (info));
+}
+
+static void
+gbp_meson_introspection_query (IdePipelineStage *stage,
+                               IdePipeline      *pipeline,
+                               GPtrArray        *targets,
+                               GCancellable     *cancellable)
+{
+  GbpMesonIntrospection *self = (GbpMesonIntrospection *)stage;
+  g_autofree char *etag = NULL;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  etag = get_current_etag (pipeline);
+
+  ide_pipeline_stage_set_completed (stage,
+                                    ide_str_equal0 (etag, self->etag));
+}
+
+static void
+gbp_meson_introspection_load_json (GbpMesonIntrospection *self,
+                                   JsonObject            *root)
+{
+  JsonNode *member;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (root != NULL);
+
+  if (json_object_has_member (root, "buildoptions") &&
+      (member = json_object_get_member (root, "buildoptions")) &&
+      JSON_NODE_HOLDS_ARRAY (member))
+    gbp_meson_introspection_load_buildoptions (self, json_node_get_array (member));
+
+  if (json_object_has_member (root, "projectinfo") &&
+      (member = json_object_get_member (root, "projectinfo")) &&
+      JSON_NODE_HOLDS_OBJECT (member))
+    gbp_meson_introspection_load_projectinfo (self, json_node_get_object (member));
+
+  if (json_object_has_member (root, "tests") &&
+      (member = json_object_get_member (root, "tests")) &&
+      JSON_NODE_HOLDS_ARRAY (member))
+    gbp_meson_introspection_load_tests (self, json_node_get_array (member));
+
+  if (json_object_has_member (root, "benchmarks") &&
+      (member = json_object_get_member (root, "benchmarks")) &&
+      JSON_NODE_HOLDS_ARRAY (member))
+    gbp_meson_introspection_load_benchmarks (self, json_node_get_array (member));
+
+  if (json_object_has_member (root, "installed") &&
+      (member = json_object_get_member (root, "installed")) &&
+      JSON_NODE_HOLDS_OBJECT (member))
+    gbp_meson_introspection_load_installed (self, json_node_get_object (member));
+
+  if (json_object_has_member (root, "targets") &&
+      (member = json_object_get_member (root, "targets")) &&
+      JSON_NODE_HOLDS_ARRAY (member))
+    gbp_meson_introspection_load_targets (self, json_node_get_array (member));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_introspection_load_stream_cb (GObject      *object,
+                                        GAsyncResult *result,
+                                        gpointer      user_data)
+{
+  JsonParser *parser = (JsonParser *)object;
+  GbpMesonIntrospection *self;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  const char *etag;
+  JsonObject *obj;
+  JsonNode *root;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (JSON_IS_PARSER (parser));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!json_parser_load_from_stream_finish (parser, result, &error))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  self = ide_task_get_source_object (task);
+  etag = ide_task_get_task_data (task);
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (etag != NULL);
+
+  /* Clear all of our previously loaded state */
+  ide_set_string (&self->etag, etag);
+  g_list_store_remove_all (self->run_commands);
+
+  if ((root = json_parser_get_root (parser)) &&
+      JSON_NODE_HOLDS_OBJECT (root) &&
+      (obj = json_node_get_object (root)))
+    gbp_meson_introspection_load_json (self, obj);
+
+  ide_task_return_boolean (task, TRUE);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_introspection_build_async (IdePipelineStage    *stage,
+                                     IdePipeline         *pipeline,
+                                     GCancellable        *cancellable,
+                                     GAsyncReadyCallback  callback,
+                                     gpointer             user_data)
+{
+  GbpMesonIntrospection *self = (GbpMesonIntrospection *)stage;
+  g_autoptr(IdeRunContext) run_context = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
+  g_autoptr(JsonParser) parser = NULL;
+  g_autoptr(GIOStream) io_stream = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree char *meson = NULL;
+  IdeBuildSystem *build_system;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  self->has_built_once = TRUE;
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_meson_introspection_build_async);
+  ide_task_set_task_data (task, get_current_etag (pipeline), g_free);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  build_system = ide_build_system_from_context (context);
+  meson = gbp_meson_build_system_locate_meson (GBP_MESON_BUILD_SYSTEM (build_system), pipeline);
+
+  g_assert (IDE_IS_CONTEXT (context));
+  g_assert (GBP_IS_MESON_BUILD_SYSTEM (build_system));
+  g_assert (meson != NULL);
+
+  run_context = ide_run_context_new ();
+  ide_pipeline_prepare_run_context (pipeline, run_context);
+  ide_run_context_append_args (run_context, IDE_STRV_INIT (meson, "introspect", "--all", 
"--force-object-output"));
+
+  /* Create a stream to communicate with the subprocess and then spawn it */
+  if (!(io_stream = ide_run_context_create_stdio_stream (run_context, &error)) ||
+      !(subprocess = ide_run_context_spawn (run_context, &error)))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  /* Keep stream alive for duration of operation */
+  g_object_set_data_full (G_OBJECT (task),
+                          "IO_STREAM",
+                          g_object_ref (io_stream),
+                          g_object_unref);
+
+  /* Start parsing our input stream */
+  parser = json_parser_new ();
+  json_parser_load_from_stream_async (parser,
+                                      g_io_stream_get_input_stream (io_stream),
+                                      cancellable,
+                                      gbp_meson_introspection_load_stream_cb,
+                                      g_steal_pointer (&task));
+
+  /* Make sure something watches the child */
+  ide_subprocess_wait_async (subprocess, NULL, NULL, NULL);
+
+  IDE_EXIT;
+}
+
+static gboolean
+gbp_meson_introspection_build_finish (IdePipelineStage  *stage,
+                                      GAsyncResult      *result,
+                                      GError           **error)
+{
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (stage));
+  g_assert (IDE_IS_TASK (result));
+
+  ret = ide_task_propagate_boolean (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static void
+gbp_meson_introspection_dispose (GObject *object)
+{
+  GbpMesonIntrospection *self = (GbpMesonIntrospection *)object;
+
+  g_clear_object (&self->run_commands);
+
+  g_clear_pointer (&self->descriptive_name, g_free);
+  g_clear_pointer (&self->subproject_dir, g_free);
+  g_clear_pointer (&self->version, g_free);
+  g_clear_pointer (&self->etag, g_free);
+
+  g_clear_weak_pointer (&self->pipeline);
+
+  G_OBJECT_CLASS (gbp_meson_introspection_parent_class)->dispose (object);
+}
+
+static void
+gbp_meson_introspection_class_init (GbpMesonIntrospectionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdePipelineStageClass *pipeline_stage_class = IDE_PIPELINE_STAGE_CLASS (klass);
+
+  object_class->dispose = gbp_meson_introspection_dispose;
+
+  pipeline_stage_class->query = gbp_meson_introspection_query;
+  pipeline_stage_class->build_async = gbp_meson_introspection_build_async;
+  pipeline_stage_class->build_finish = gbp_meson_introspection_build_finish;
+}
+
+static void
+gbp_meson_introspection_init (GbpMesonIntrospection *self)
+{
+  self->run_commands = g_list_store_new (IDE_TYPE_RUN_COMMAND);
+
+  ide_pipeline_stage_set_name (IDE_PIPELINE_STAGE (self),
+                               _("Load Meson Introspection"));
+}
+
+GbpMesonIntrospection *
+gbp_meson_introspection_new (IdePipeline *pipeline)
+{
+  GbpMesonIntrospection *self;
+
+  g_return_val_if_fail (IDE_IS_PIPELINE (pipeline), NULL);
+
+  self = g_object_new (GBP_TYPE_MESON_INTROSPECTION, NULL);
+  g_set_weak_pointer (&self->pipeline, pipeline);
+
+  return self;
+}
+
+static void
+gbp_meson_introspection_list_run_commands_cb (GObject      *object,
+                                              GAsyncResult *result,
+                                              gpointer      user_data)
+{
+  g_autoptr(IdeTask) task = user_data;
+  GbpMesonIntrospection *self;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_PIPELINE (object) || IDE_IS_PIPELINE_STAGE (object));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  self = ide_task_get_source_object (task);
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+
+  ide_task_return_pointer (task, g_object_ref (self->run_commands), g_object_unref);
+
+  IDE_EXIT;
+}
+
+void
+gbp_meson_introspection_list_run_commands_async (GbpMesonIntrospection *self,
+                                                 GCancellable          *cancellable,
+                                                 GAsyncReadyCallback    callback,
+                                                 gpointer               user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (GBP_IS_MESON_INTROSPECTION (self));
+  g_return_if_fail (IDE_IS_PIPELINE (self->pipeline));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_meson_introspection_list_run_commands_async);
+
+  if (!self->has_built_once)
+    {
+      g_autofree char *build_dot_ninja = ide_pipeline_build_builddir_path (self->pipeline, "build.ninja", 
NULL);
+
+      /* If there is a build.ninja then assume we can skip running through
+       * the pipeline and just introspection immediately.
+       */
+      if (g_file_test (build_dot_ninja, G_FILE_TEST_EXISTS))
+        ide_pipeline_stage_build_async (IDE_PIPELINE_STAGE (self),
+                                        self->pipeline,
+                                        cancellable,
+                                        gbp_meson_introspection_list_run_commands_cb,
+                                        g_steal_pointer (&task));
+      else
+        ide_pipeline_build_async (self->pipeline,
+                                  IDE_PIPELINE_PHASE_CONFIGURE,
+                                  cancellable,
+                                  gbp_meson_introspection_list_run_commands_cb,
+                                  g_steal_pointer (&task));
+
+      IDE_EXIT;
+    }
+
+  ide_task_return_pointer (task, g_object_ref (self->run_commands), g_object_unref);
+
+  IDE_EXIT;
+}
+
+GListModel *
+gbp_meson_introspection_list_run_commands_finish (GbpMesonIntrospection  *self,
+                                                  GAsyncResult           *result,
+                                                  GError                **error)
+{
+  GListModel *ret;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (self));
+  g_assert (IDE_IS_TASK (result));
+
+  ret = ide_task_propagate_pointer (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
diff --git a/src/plugins/meson/gbp-meson-introspection.h b/src/plugins/meson/gbp-meson-introspection.h
new file mode 100644
index 000000000..ed83a4c2e
--- /dev/null
+++ b/src/plugins/meson/gbp-meson-introspection.h
@@ -0,0 +1,40 @@
+/* gbp-meson-introspection.h
+ *
+ * Copyright 2022 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-foundry.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_MESON_INTROSPECTION (gbp_meson_introspection_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpMesonIntrospection, gbp_meson_introspection, GBP, MESON_INTROSPECTION, 
IdePipelineStage)
+
+GbpMesonIntrospection *gbp_meson_introspection_new                      (IdePipeline            *pipeline);
+void                   gbp_meson_introspection_list_run_commands_async  (GbpMesonIntrospection  *self,
+                                                                         GCancellable           *cancelalble,
+                                                                         GAsyncReadyCallback     callback,
+                                                                         gpointer                user_data);
+GListModel            *gbp_meson_introspection_list_run_commands_finish (GbpMesonIntrospection  *self,
+                                                                         GAsyncResult           *result,
+                                                                         GError                **error);
+
+G_END_DECLS
diff --git a/src/plugins/meson/gbp-meson-pipeline-addin.c b/src/plugins/meson/gbp-meson-pipeline-addin.c
index b7f3f15fa..df1594f2e 100644
--- a/src/plugins/meson/gbp-meson-pipeline-addin.c
+++ b/src/plugins/meson/gbp-meson-pipeline-addin.c
@@ -29,11 +29,13 @@
 #include "gbp-meson-build-stage-cross-file.h"
 #include "gbp-meson-build-system.h"
 #include "gbp-meson-build-target.h"
+#include "gbp-meson-introspection.h"
 #include "gbp-meson-pipeline-addin.h"
 
 struct _GbpMesonPipelineAddin
 {
-  IdeObject parent_instance;
+  IdeObject              parent_instance;
+  GbpMesonIntrospection *introspection;
 };
 
 static const gchar *ninja_names[] = { "ninja", "ninja-build", NULL };
@@ -77,12 +79,11 @@ on_build_stage_query (IdePipelineStage *stage,
 
           if (GBP_IS_MESON_BUILD_TARGET (target))
             {
-              const gchar *filename;
+              const char *builddir = ide_pipeline_get_builddir (pipeline);
+              const char *filename = gbp_meson_build_target_get_filename (GBP_MESON_BUILD_TARGET (target));
 
-              filename = gbp_meson_build_target_get_filename (GBP_MESON_BUILD_TARGET (target));
-
-              if (filename != NULL)
-                ide_subprocess_launcher_push_argv (launcher, filename);
+              if (filename != NULL && g_str_has_prefix (filename, builddir))
+                ide_subprocess_launcher_push_argv (launcher, g_path_skip_root (filename + strlen 
(builddir)));
             }
         }
     }
@@ -102,214 +103,186 @@ on_install_stage_query (IdePipelineStage *stage,
   ide_pipeline_stage_set_completed (stage, FALSE);
 }
 
-static void
-gbp_meson_pipeline_addin_load (IdePipelineAddin *addin,
-                               IdePipeline      *pipeline)
+G_GNUC_NULL_TERMINATED
+static IdeRunContext *
+create_run_context (GbpMesonPipelineAddin *self,
+                    IdePipeline           *pipeline,
+                    const char            *argv,
+                    ...)
+{
+  IdeRunContext *run_context;
+  va_list args;
+
+  g_assert (GBP_IS_MESON_PIPELINE_ADDIN (self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
+
+  run_context = ide_run_context_new ();
+  ide_pipeline_prepare_run_context (pipeline, run_context);
+
+  va_start (args, argv);
+  while (argv != NULL)
+    {
+      ide_run_context_append_argv (run_context, argv);
+      argv = va_arg (args, const char *);
+    }
+  va_end (args);
+
+  return run_context;
+}
+
+static IdePipelineStage *
+attach_run_context (GbpMesonPipelineAddin *self,
+                    IdePipeline           *pipeline,
+                    IdeRunContext         *build_context,
+                    IdeRunContext         *clean_context,
+                    const char            *title,
+                    IdePipelinePhase       phase)
 {
-  GbpMesonPipelineAddin *self = (GbpMesonPipelineAddin *)addin;
-  g_autoptr(IdeSubprocessLauncher) config_launcher = NULL;
   g_autoptr(IdeSubprocessLauncher) build_launcher = NULL;
   g_autoptr(IdeSubprocessLauncher) clean_launcher = NULL;
-  g_autoptr(IdeSubprocessLauncher) install_launcher = NULL;
-  g_autoptr(IdePipelineStage) build_stage = NULL;
-  g_autoptr(IdePipelineStage) config_stage = NULL;
-  g_autoptr(IdePipelineStage) install_stage = NULL;
+  g_autoptr(IdePipelineStage) stage = NULL;
   g_autoptr(GError) error = NULL;
-  g_autofree gchar *build_ninja = NULL;
-  g_autofree gchar *crossbuild_file = NULL;
-  g_autofree gchar *meson_build = NULL;
-  g_autofree gchar *alt_meson_build = NULL;
-  IdeBuildSystem *build_system;
-  IdeConfig *config;
   IdeContext *context;
-  IdeRuntime *runtime;
-  IdeToolchain *toolchain;
-  IdeWorkbench *workbench;
-  IdeProjectInfo *project_info;
-  g_autoptr(GFile) project_dir = NULL;
-  g_autoptr(GFile) alt_meson_build_file = NULL;
-  const gchar *config_opts;
-  const gchar *ninja = NULL;
-  const gchar *prefix;
-  const gchar *srcdir;
-  const gchar *meson;
-  GFile *project_file;
   guint id;
-  gint parallel;
-
-  IDE_ENTRY;
 
   g_assert (GBP_IS_MESON_PIPELINE_ADDIN (self));
   g_assert (IDE_IS_PIPELINE (pipeline));
+  g_assert (!build_context || IDE_IS_RUN_CONTEXT (build_context));
+  g_assert (!clean_context || IDE_IS_RUN_CONTEXT (clean_context));
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-
-  build_system = ide_build_system_from_context (context);
-  if (!GBP_IS_MESON_BUILD_SYSTEM (build_system))
-    IDE_GOTO (failure);
-
-  config = ide_pipeline_get_config (pipeline);
-  runtime = ide_pipeline_get_runtime (pipeline);
-  toolchain = ide_pipeline_get_toolchain (pipeline);
-  srcdir = ide_pipeline_get_srcdir (pipeline);
-  workbench = ide_workbench_from_context (context);
-  project_info = ide_workbench_get_project_info (workbench);
-  project_file = ide_project_info_get_file (project_info);
+  context = ide_object_get_context (IDE_OBJECT (pipeline));
+  stage = ide_pipeline_stage_launcher_new (context, NULL);
 
-  if (project_file != NULL)
+  if (build_context != NULL)
     {
-      GFileType file_type = g_file_query_file_type (project_file, 0, NULL);
-
-      if (file_type == G_FILE_TYPE_DIRECTORY)
-        project_dir = g_object_ref (project_file);
-      else
-        project_dir = g_file_get_parent (project_file);
-
-      alt_meson_build_file = g_file_get_child (project_dir, "meson.build");
-      alt_meson_build = g_file_get_path (alt_meson_build_file);
+      if (!(build_launcher = ide_run_context_end (build_context, &error)))
+        {
+          g_critical ("Failed to create launcher from run context: %s",
+                      error->message);
+          return NULL;
+        }
     }
 
-  g_assert (IDE_IS_CONFIG (config));
-  g_assert (IDE_IS_RUNTIME (runtime));
-  g_assert (srcdir != NULL);
-
-  /* If the srcdir does not contain the meson.build, perhaps the project's
-   * "Project File" directory does (and that could be in a sub-directory).
-   */
-  meson_build = g_build_filename (srcdir, "meson.build", NULL);
-  if (!g_file_test (meson_build, G_FILE_TEST_EXISTS) &&
-      alt_meson_build != NULL &&
-      project_dir != NULL &&
-      g_file_test (alt_meson_build, G_FILE_TEST_EXISTS))
-    srcdir = g_file_get_path (project_dir);
-
-  if (NULL == (meson = ide_config_getenv (config, "MESON")))
-    meson = "meson";
-
-  /* Warn about not finding Meson, but continue setting up */
-  if (!ide_runtime_contains_program_in_path (runtime, meson, NULL))
-    ide_context_warning (context,
-                         _("A Meson-based project is loaded but meson could not be found."));
-
-  /* Requires NULL check so we can use g_strv_contains() elsewhere */
-  for (guint i = 0; ninja_names[i]; i++)
+  if (clean_context != NULL)
     {
-      if (ide_runtime_contains_program_in_path (runtime, ninja_names[i], NULL))
+      if (!(clean_launcher = ide_run_context_end (clean_context, &error)))
         {
-          ninja = ninja_names[i];
-          break;
+          g_critical ("Failed to create launcher from run context: %s",
+                      error->message);
+          return NULL;
         }
     }
 
-  if (ninja == NULL)
-    ninja = ide_config_getenv (config, "NINJA");
+  g_object_set (stage,
+                "launcher", build_launcher,
+                "clean-launcher", clean_launcher,
+                "name", title,
+                NULL);
 
-  /* Warn about not finding ninja, but continue setting up */
-  if (ninja == NULL)
-    {
-      ide_context_warning (context,
-                           _("A Meson-based project is loaded but Ninja could not be found."));
-      ninja = "ninja";
-    }
+  id = ide_pipeline_attach (pipeline, phase, 0, stage);
+  ide_pipeline_addin_track (IDE_PIPELINE_ADDIN (self), id);
 
-  /* Create all our launchers up front */
-  if (NULL == (config_launcher = ide_pipeline_create_launcher (pipeline, &error)) ||
-      NULL == (build_launcher = ide_pipeline_create_launcher (pipeline, &error)) ||
-      NULL == (clean_launcher = ide_pipeline_create_launcher (pipeline, &error)) ||
-      NULL == (install_launcher = ide_pipeline_create_launcher (pipeline, &error)))
-    IDE_GOTO (failure);
+  /* We return a borrowed instance */
+  return stage;
+}
 
-  prefix = ide_config_get_prefix (config);
+static void
+gbp_meson_pipeline_addin_load (IdePipelineAddin *addin,
+                               IdePipeline      *pipeline)
+{
+  GbpMesonPipelineAddin *self = (GbpMesonPipelineAddin *)addin;
+  g_autoptr(IdeRunContext) build_context = NULL;
+  g_autoptr(IdeRunContext) clean_context = NULL;
+  g_autoptr(IdeRunContext) config_context = NULL;
+  g_autoptr(IdeRunContext) install_context = NULL;
+  IdePipelineStage *stage;
+  g_autofree char *build_dot_ninja = NULL;
+  g_autofree char *crossbuild_file = NULL;
+  g_autofree char *meson = NULL;
+  g_autofree char *ninja = NULL;
+  IdeBuildSystem *build_system;
+  IdeToolchain *toolchain;
+  IdeContext *context;
+  const char *config_opts;
+  const char *prefix;
+  const char *srcdir;
+  IdeConfig *config;
+  guint id;
+  int parallel;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_MESON_PIPELINE_ADDIN (self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
+
+  config = ide_pipeline_get_config (pipeline);
+  context = ide_object_get_context (IDE_OBJECT (pipeline));
+  build_system = ide_build_system_from_context (context);
+
+  if (!GBP_IS_MESON_BUILD_SYSTEM (build_system))
+    IDE_EXIT;
+
+  srcdir = ide_pipeline_get_srcdir (pipeline);
   config_opts = ide_config_get_config_opts (config);
+  prefix = ide_config_get_prefix (config);
+  build_dot_ninja = ide_pipeline_build_builddir_path (pipeline, "build.ninja", NULL);
   parallel = ide_config_get_parallelism (config);
+  toolchain = ide_pipeline_get_toolchain (pipeline);
+
+  /* Discover program locations for meson/ninja */
+  meson = gbp_meson_build_system_locate_meson (GBP_MESON_BUILD_SYSTEM (build_system), pipeline);
+  ninja = gbp_meson_build_system_locate_ninja (GBP_MESON_BUILD_SYSTEM (build_system), pipeline);
 
   /* Create the toolchain file if required */
   if (GBP_IS_MESON_TOOLCHAIN (toolchain))
-    crossbuild_file = g_strdup (gbp_meson_toolchain_get_file_path (GBP_MESON_TOOLCHAIN (toolchain)));
+    {
+      crossbuild_file = g_strdup (gbp_meson_toolchain_get_file_path (GBP_MESON_TOOLCHAIN (toolchain)));
+    }
   else if (g_strcmp0 (ide_toolchain_get_id (toolchain), "default") != 0)
     {
-      GbpMesonBuildStageCrossFile *cross_file_stage;
-      cross_file_stage = gbp_meson_build_stage_cross_file_new (toolchain);
-      crossbuild_file = gbp_meson_build_stage_cross_file_get_path (cross_file_stage, pipeline);
-
+      g_autoptr(GbpMesonBuildStageCrossFile) cross_file_stage = gbp_meson_build_stage_cross_file_new 
(toolchain);
       id = ide_pipeline_attach (pipeline, IDE_PIPELINE_PHASE_PREPARE, 0, IDE_PIPELINE_STAGE 
(cross_file_stage));
+      crossbuild_file = gbp_meson_build_stage_cross_file_get_path (cross_file_stage, pipeline);
       ide_pipeline_addin_track (addin, id);
     }
 
-  /* Setup our meson configure stage. */
-
-  ide_subprocess_launcher_push_argv (config_launcher, meson);
-  ide_subprocess_launcher_push_argv (config_launcher, srcdir);
-  ide_subprocess_launcher_push_argv (config_launcher, ".");
-  ide_subprocess_launcher_push_argv (config_launcher, "--prefix");
-  ide_subprocess_launcher_push_argv (config_launcher, prefix);
+  /* Setup our configure stage */
+  config_context = create_run_context (self, pipeline, meson, srcdir, ".", "--prefix", prefix, NULL);
   if (crossbuild_file != NULL)
-    {
-      ide_subprocess_launcher_push_argv (config_launcher, "--cross-file");
-      ide_subprocess_launcher_push_argv (config_launcher, crossbuild_file);
-    }
-
+    ide_run_context_append_formatted (config_context, "--cross-file=%s", crossbuild_file);
   if (!ide_str_empty0 (config_opts))
-    {
-      g_auto(GStrv) argv = NULL;
-      gint argc;
-
-      if (!g_shell_parse_argv (config_opts, &argc, &argv, &error))
-        IDE_GOTO (failure);
-
-      ide_subprocess_launcher_push_args (config_launcher, (const gchar * const *)argv);
-    }
-
-  config_stage = ide_pipeline_stage_launcher_new (context, config_launcher);
-  ide_pipeline_stage_set_name (config_stage, _("Configuring project"));
-  build_ninja = ide_pipeline_build_builddir_path (pipeline, "build.ninja", NULL);
-  if (g_file_test (build_ninja, G_FILE_TEST_IS_REGULAR))
-    ide_pipeline_stage_set_completed (config_stage, TRUE);
-
-  id = ide_pipeline_attach (pipeline, IDE_PIPELINE_PHASE_CONFIGURE, 0, config_stage);
-  ide_pipeline_addin_track (addin, id);
-
-  /*
-   * Register the build launcher which will perform the incremental
-   * build of the project when the IDE_PIPELINE_PHASE_BUILD phase is
-   * requested of the pipeline.
-   */
-  ide_subprocess_launcher_push_argv (build_launcher, ninja);
-  ide_subprocess_launcher_push_argv (clean_launcher, ninja);
-
+    ide_run_context_append_args_parsed (config_context, config_opts, NULL);
+  stage = attach_run_context (self, pipeline, config_context, NULL,
+                              _("Configure project"), IDE_PIPELINE_PHASE_CONFIGURE);
+  if (g_file_test (build_dot_ninja, G_FILE_TEST_EXISTS))
+    ide_pipeline_stage_set_completed (stage, TRUE);
+
+  /* Setup our Build/Clean stage */
+  clean_context = create_run_context (self, pipeline, ninja, "clean", NULL);
+  build_context = create_run_context (self, pipeline, ninja, NULL);
   if (parallel > 0)
-    {
-      g_autofree gchar *j = g_strdup_printf ("-j%u", parallel);
-
-      ide_subprocess_launcher_push_argv (build_launcher, j);
-      ide_subprocess_launcher_push_argv (clean_launcher, j);
-    }
-
-  ide_subprocess_launcher_push_argv (clean_launcher, "clean");
-
-  build_stage = ide_pipeline_stage_launcher_new (context, build_launcher);
-  ide_pipeline_stage_launcher_set_clean_launcher (IDE_PIPELINE_STAGE_LAUNCHER (build_stage), clean_launcher);
-  ide_pipeline_stage_set_check_stdout (build_stage, TRUE);
-  ide_pipeline_stage_set_name (build_stage, _("Building project"));
-  g_signal_connect (build_stage, "query", G_CALLBACK (on_build_stage_query), NULL);
-
-  id = ide_pipeline_attach (pipeline, IDE_PIPELINE_PHASE_BUILD, 0, build_stage);
-  ide_pipeline_addin_track (addin, id);
-
-  /* Setup our install stage */
-  ide_subprocess_launcher_push_argv (install_launcher, ninja);
-  ide_subprocess_launcher_push_argv (install_launcher, "install");
-  install_stage = ide_pipeline_stage_launcher_new (context, install_launcher);
-  ide_pipeline_stage_set_name (install_stage, _("Installing project"));
-  g_signal_connect (install_stage, "query", G_CALLBACK (on_install_stage_query), NULL);
-  id = ide_pipeline_attach (pipeline, IDE_PIPELINE_PHASE_INSTALL, 0, install_stage);
+    ide_run_context_append_formatted (build_context, "-j%u", parallel);
+  stage = attach_run_context (self, pipeline, build_context, clean_context,
+                              _("Build project"), IDE_PIPELINE_PHASE_BUILD);
+  ide_pipeline_stage_set_check_stdout (stage, TRUE);
+  g_signal_connect (stage, "query", G_CALLBACK (on_build_stage_query), NULL);
+
+  /* Setup our Install stage */
+  install_context = create_run_context (self, pipeline, ninja, "install", NULL);
+  stage = attach_run_context (self, pipeline, install_context, NULL,
+                              _("Install project"), IDE_PIPELINE_PHASE_INSTALL);
+  g_signal_connect (stage, "query", G_CALLBACK (on_install_stage_query), NULL);
+
+  /* Setup our introspection stage */
+  self->introspection = gbp_meson_introspection_new (pipeline);
+  id = ide_pipeline_attach (pipeline,
+                            IDE_PIPELINE_PHASE_CONFIGURE | IDE_PIPELINE_PHASE_AFTER,
+                            0,
+                            IDE_PIPELINE_STAGE (self->introspection));
   ide_pipeline_addin_track (addin, id);
 
   IDE_EXIT;
-
-failure:
-  if (error != NULL)
-    g_warning ("Failed to setup meson build pipeline: %s", error->message);
 }
 
 static void
@@ -319,15 +292,35 @@ pipeline_addin_iface_init (IdePipelineAddinInterface *iface)
 }
 
 G_DEFINE_FINAL_TYPE_WITH_CODE (GbpMesonPipelineAddin, gbp_meson_pipeline_addin, IDE_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (IDE_TYPE_PIPELINE_ADDIN,
-                                                pipeline_addin_iface_init))
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_PIPELINE_ADDIN, pipeline_addin_iface_init))
+
+static void
+gbp_meson_pipeline_addin_dispose (GObject *object)
+{
+  GbpMesonPipelineAddin *self = (GbpMesonPipelineAddin *)object;
+
+  g_clear_object (&self->introspection);
+
+  G_OBJECT_CLASS (gbp_meson_pipeline_addin_parent_class)->dispose (object);
+}
 
 static void
 gbp_meson_pipeline_addin_class_init (GbpMesonPipelineAddinClass *klass)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gbp_meson_pipeline_addin_dispose;
 }
 
 static void
 gbp_meson_pipeline_addin_init (GbpMesonPipelineAddin *self)
 {
 }
+
+GbpMesonIntrospection *
+gbp_meson_pipeline_addin_get_introspection (GbpMesonPipelineAddin *self)
+{
+  g_return_val_if_fail (GBP_IS_MESON_PIPELINE_ADDIN (self), NULL);
+
+  return self->introspection;
+}
diff --git a/src/plugins/meson/gbp-meson-pipeline-addin.h b/src/plugins/meson/gbp-meson-pipeline-addin.h
index f2b24093e..cefda157d 100644
--- a/src/plugins/meson/gbp-meson-pipeline-addin.h
+++ b/src/plugins/meson/gbp-meson-pipeline-addin.h
@@ -22,10 +22,14 @@
 
 #include <libide-foundry.h>
 
+#include "gbp-meson-introspection.h"
+
 G_BEGIN_DECLS
 
 #define GBP_TYPE_MESON_PIPELINE_ADDIN (gbp_meson_pipeline_addin_get_type())
 
 G_DECLARE_FINAL_TYPE (GbpMesonPipelineAddin, gbp_meson_pipeline_addin, GBP, MESON_PIPELINE_ADDIN, IdeObject)
 
+GbpMesonIntrospection *gbp_meson_pipeline_addin_get_introspection (GbpMesonPipelineAddin *self);
+
 G_END_DECLS
diff --git a/src/plugins/meson/gbp-meson-run-command-provider.c 
b/src/plugins/meson/gbp-meson-run-command-provider.c
new file mode 100644
index 000000000..d166bcef2
--- /dev/null
+++ b/src/plugins/meson/gbp-meson-run-command-provider.c
@@ -0,0 +1,171 @@
+/* gbp-meson-run-command-provider.c
+ *
+ * Copyright 2022 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-meson-run-command-provider"
+
+#include <libide-threading.h>
+
+#include "gbp-meson-build-system.h"
+#include "gbp-meson-introspection.h"
+#include "gbp-meson-pipeline-addin.h"
+#include "gbp-meson-run-command-provider.h"
+
+struct _GbpMesonRunCommandProvider
+{
+  IdeObject parent_instance;
+};
+
+static void
+gbp_meson_run_command_provider_list_run_commands_cb (GObject      *object,
+                                                     GAsyncResult *result,
+                                                     gpointer      user_data)
+{
+  GbpMesonIntrospection *introspection = (GbpMesonIntrospection *)object;
+  g_autoptr(GListModel) run_commands = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_MESON_INTROSPECTION (introspection));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!(run_commands = gbp_meson_introspection_list_run_commands_finish (introspection, result, &error)))
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_task_return_pointer (task, g_steal_pointer (&run_commands), g_object_unref);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_run_command_provider_list_commands_async (IdeRunCommandProvider *provider,
+                                                    GCancellable          *cancellable,
+                                                    GAsyncReadyCallback    callback,
+                                                    gpointer               user_data)
+{
+  GbpMesonRunCommandProvider *self = (GbpMesonRunCommandProvider *)provider;
+  g_autoptr(GListModel) run_commands = NULL;
+  GbpMesonIntrospection *introspection;
+  g_autoptr(IdeTask) task = NULL;
+  IdePipelineAddin *addin;
+  IdeBuildManager *build_manager;
+  IdeBuildSystem *build_system;
+  IdePipeline *pipeline;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_RUN_COMMAND_PROVIDER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_meson_run_command_provider_list_commands_async);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  build_system = ide_build_system_from_context (context);
+  build_manager = ide_build_manager_from_context (context);
+  pipeline = ide_build_manager_get_pipeline (build_manager);
+
+  if (!GBP_IS_MESON_BUILD_SYSTEM (build_system) ||
+      pipeline == NULL ||
+      !(addin = ide_pipeline_addin_find_by_module_name (pipeline, "meson")) ||
+      !(introspection = gbp_meson_pipeline_addin_get_introspection (GBP_MESON_PIPELINE_ADDIN (addin))))
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_NOT_SUPPORTED,
+                                 "Cannot list run commands without a meson-based pipeline");
+      IDE_EXIT;
+    }
+
+  g_assert (GBP_IS_MESON_INTROSPECTION (introspection));
+
+  gbp_meson_introspection_list_run_commands_async (introspection,
+                                                   cancellable,
+                                                   gbp_meson_run_command_provider_list_run_commands_cb,
+                                                   g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+static GListModel *
+gbp_meson_run_command_provider_list_commands_finish (IdeRunCommandProvider  *provider,
+                                                     GAsyncResult           *result,
+                                                     GError                **error)
+{
+  GListModel *ret;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_RUN_COMMAND_PROVIDER (provider));
+  g_assert (IDE_IS_TASK (result));
+
+  ret = ide_task_propagate_pointer (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static void
+run_command_provider_iface (IdeRunCommandProviderInterface *iface)
+{
+  iface->list_commands_async = gbp_meson_run_command_provider_list_commands_async;
+  iface->list_commands_finish = gbp_meson_run_command_provider_list_commands_finish;
+}
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (GbpMesonRunCommandProvider, gbp_meson_run_command_provider, IDE_TYPE_OBJECT,
+                               G_IMPLEMENT_INTERFACE (IDE_TYPE_RUN_COMMAND_PROVIDER, 
run_command_provider_iface))
+
+static void
+gbp_meson_run_command_provider_parent_set (IdeObject *object,
+                                           IdeObject *parent)
+{
+  GbpMesonRunCommandProvider *self = (GbpMesonRunCommandProvider *)object;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (GBP_IS_MESON_RUN_COMMAND_PROVIDER (self));
+  g_assert (!parent || IDE_IS_OBJECT (parent));
+
+  if (parent == NULL)
+    IDE_EXIT;
+
+  ide_run_command_provider_invalidates_at_phase (IDE_RUN_COMMAND_PROVIDER (self),
+                                                 IDE_PIPELINE_PHASE_CONFIGURE);
+
+  IDE_EXIT;
+}
+
+static void
+gbp_meson_run_command_provider_class_init (GbpMesonRunCommandProviderClass *klass)
+{
+  IdeObjectClass *ide_object_class = IDE_OBJECT_CLASS (klass);
+
+  ide_object_class->parent_set = gbp_meson_run_command_provider_parent_set;
+}
+
+static void
+gbp_meson_run_command_provider_init (GbpMesonRunCommandProvider *self)
+{
+}
diff --git a/src/plugins/meson/gbp-meson-test-provider.h b/src/plugins/meson/gbp-meson-run-command-provider.h
similarity index 71%
rename from src/plugins/meson/gbp-meson-test-provider.h
rename to src/plugins/meson/gbp-meson-run-command-provider.h
index 2ed6efd51..2b2bff778 100644
--- a/src/plugins/meson/gbp-meson-test-provider.h
+++ b/src/plugins/meson/gbp-meson-run-command-provider.h
@@ -1,6 +1,6 @@
-/* gbp-meson-test-provider.h
+/* gbp-meson-run-command-provider.h
  *
- * Copyright 2017-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2022 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
@@ -24,8 +24,8 @@
 
 G_BEGIN_DECLS
 
-#define GBP_TYPE_MESON_TEST_PROVIDER (gbp_meson_test_provider_get_type())
+#define GBP_TYPE_MESON_RUN_COMMAND_PROVIDER (gbp_meson_run_command_provider_get_type())
 
-G_DECLARE_FINAL_TYPE (GbpMesonTestProvider, gbp_meson_test_provider, GBP, MESON_TEST_PROVIDER, 
IdeTestProvider)
+G_DECLARE_FINAL_TYPE (GbpMesonRunCommandProvider, gbp_meson_run_command_provider, GBP, 
MESON_RUN_COMMAND_PROVIDER, IdeObject)
 
 G_END_DECLS
diff --git a/src/plugins/meson/gbp-meson-toolchain-edition-preferences-row.c 
b/src/plugins/meson/gbp-meson-toolchain-edition-preferences-row.c
index e93e67fda..b1ff91388 100644
--- a/src/plugins/meson/gbp-meson-toolchain-edition-preferences-row.c
+++ b/src/plugins/meson/gbp-meson-toolchain-edition-preferences-row.c
@@ -367,8 +367,6 @@ gbp_meson_toolchain_edition_preferences_row_finalize (GObject *object)
  * @self: a #GbpMesonToolchainEditionPreferencesRow
  *
  * Requests the configuration popover the be shown over the widget.
- *
- * Since: 3.32
  */
 void
 gbp_meson_toolchain_edition_preferences_row_show_popup (GbpMesonToolchainEditionPreferencesRow *self)
diff --git a/src/plugins/meson/gbp-meson-toolchain.c b/src/plugins/meson/gbp-meson-toolchain.c
index 9a52fe280..0720fea7f 100644
--- a/src/plugins/meson/gbp-meson-toolchain.c
+++ b/src/plugins/meson/gbp-meson-toolchain.c
@@ -119,8 +119,6 @@ gbp_meson_toolchain_load (GbpMesonToolchain  *self,
  * Gets the path to the Meson cross-file
  *
  * Returns: (transfer none): the path to the Meson cross-file.
- *
- * Since: 3.32
  */
 const gchar *
 gbp_meson_toolchain_get_file_path (GbpMesonToolchain  *self)
diff --git a/src/plugins/meson/meson-plugin.c b/src/plugins/meson/meson-plugin.c
index 9724ec5e3..166a5c04a 100644
--- a/src/plugins/meson/meson-plugin.c
+++ b/src/plugins/meson/meson-plugin.c
@@ -18,7 +18,12 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
+#define G_LOG_DOMAIN "meson-plugin"
+
+#include "config.h"
+
 #include <libpeas/peas.h>
+
 #include <libide-foundry.h>
 #include <libide-gui.h>
 
@@ -27,9 +32,12 @@
 #include "gbp-meson-build-target-provider.h"
 #include "gbp-meson-config-view-addin.h"
 #include "gbp-meson-pipeline-addin.h"
-#include "gbp-meson-test-provider.h"
+#include "gbp-meson-run-command-provider.h"
 #include "gbp-meson-toolchain-provider.h"
+
+#if 0
 #include "gbp-meson-toolchain-edition-preferences-addin.h"
+#endif
 
 _IDE_EXTERN void
 _gbp_meson_register_types (PeasObjectModule *module)
@@ -37,11 +45,6 @@ _gbp_meson_register_types (PeasObjectModule *module)
   /* For in-tree builds of meson projects */
   ide_g_file_add_ignored_pattern ("_build");
 
-#if 0
-  peas_object_module_register_extension_type (module,
-                                              IDE_TYPE_CONFIG_VIEW_ADDIN,
-                                              GBP_TYPE_MESON_CONFIG_VIEW_ADDIN);
-#endif
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_PIPELINE_ADDIN,
                                               GBP_TYPE_MESON_PIPELINE_ADDIN);
@@ -55,12 +58,18 @@ _gbp_meson_register_types (PeasObjectModule *module)
                                               IDE_TYPE_BUILD_TARGET_PROVIDER,
                                               GBP_TYPE_MESON_BUILD_TARGET_PROVIDER);
   peas_object_module_register_extension_type (module,
-                                              IDE_TYPE_TEST_PROVIDER,
-                                              GBP_TYPE_MESON_TEST_PROVIDER);
+                                              IDE_TYPE_RUN_COMMAND_PROVIDER,
+                                              GBP_TYPE_MESON_RUN_COMMAND_PROVIDER);
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_TOOLCHAIN_PROVIDER,
                                               GBP_TYPE_MESON_TOOLCHAIN_PROVIDER);
+
+#if 0
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_PREFERENCES_ADDIN,
                                               GBP_TYPE_MESON_TOOLCHAIN_EDITION_PREFERENCES_ADDIN);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_CONFIG_VIEW_ADDIN,
+                                              GBP_TYPE_MESON_CONFIG_VIEW_ADDIN);
+#endif
 }
diff --git a/src/plugins/meson/meson.build b/src/plugins/meson/meson.build
index 38923ed21..b3df6d1fe 100644
--- a/src/plugins/meson/meson.build
+++ b/src/plugins/meson/meson.build
@@ -6,17 +6,18 @@ plugins_sources += files([
   'gbp-meson-build-system.c',
   'gbp-meson-build-target-provider.c',
   'gbp-meson-build-target.c',
-  'gbp-meson-config-view-addin.c',
+  'gbp-meson-introspection.c',
   'gbp-meson-pipeline-addin.c',
-  'gbp-meson-test-provider.c',
-  'gbp-meson-test.c',
-  'gbp-meson-tool-row.c',
-  'gbp-meson-toolchain-edition-preferences-addin.c',
-  'gbp-meson-toolchain-edition-preferences-row.c',
+  'gbp-meson-run-command-provider.c',
   'gbp-meson-toolchain-provider.c',
   'gbp-meson-toolchain.c',
   'gbp-meson-utils.c',
   'meson-plugin.c',
+
+  # 'gbp-meson-config-view-addin.c',
+  # 'gbp-meson-toolchain-edition-preferences-addin.c',
+  # 'gbp-meson-toolchain-edition-preferences-row.c',
+  # 'gbp-meson-tool-row.c',
 ])
 
 plugin_meson_resources = gnome.compile_resources(
@@ -27,14 +28,14 @@ plugin_meson_resources = gnome.compile_resources(
 
 plugins_sources += plugin_meson_resources
 
-test_sources = files([
+test_meson_sources = files([
   'test-meson-build-system.c',
   'gbp-meson-build-system.c',
   'gbp-meson-toolchain.c',
   'gbp-meson-utils.c',
 ])
 
-test_meson = executable('test-meson', test_sources,
+test_meson = executable('test-meson', test_meson_sources,
         c_args: test_cflags,
   dependencies: [ libide_foundry_dep, libide_terminal_dep ],
 )
diff --git a/src/plugins/meson/meson.plugin b/src/plugins/meson/meson.plugin
index 8d9ab2349..963141de8 100644
--- a/src/plugins/meson/meson.plugin
+++ b/src/plugins/meson/meson.plugin
@@ -4,8 +4,9 @@ Builtin=true
 Copyright=Copyright © 2016 Patrick Griffis
 Description=Provides integration with the Meson build system
 Embedded=_gbp_meson_register_types
-Hidden=true
 Module=meson
 Name=Meson
-X-Project-File-Filter-Name=Meson Project (meson.build)
+X-Category=buildsystems
+X-Project-File-Filter-Name=Meson (meson.build)
 X-Project-File-Filter-Pattern=meson.build
+X-Preferences-Kind=application;project;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]