[gnome-builder] pipeline: intercept PTY communication to extract error regex
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] pipeline: intercept PTY communication to extract error regex
- Date: Tue, 23 Jan 2018 04:58:06 +0000 (UTC)
commit d0c677ecb7844f2dec4da3fdeb0e02b40cd4b2c7
Author: Christian Hergert <chergert redhat com>
Date: Mon Jan 22 20:57:05 2018 -0800
pipeline: intercept PTY communication to extract error regex
This uses the new pty_intercept_t to track PTY communication and extract
error regexes in the style we previously extracted from the build log.
But since this is a tap into the PTY traffic, we still maintain our support
for VtePty attached to a terminal.
src/libide/buildsystem/ide-build-pipeline.c | 206 +++++++++++++++++++---------
1 file changed, 141 insertions(+), 65 deletions(-)
---
diff --git a/src/libide/buildsystem/ide-build-pipeline.c b/src/libide/buildsystem/ide-build-pipeline.c
index f68ba18e7..5aa4920ef 100644
--- a/src/libide/buildsystem/ide-build-pipeline.c
+++ b/src/libide/buildsystem/ide-build-pipeline.c
@@ -47,6 +47,8 @@
#include "projects/ide-project.h"
#include "runtimes/ide-runtime.h"
#include "terminal/ide-terminal-util.h"
+#include "util/ide-line-reader.h"
+#include "util/ptyintercept.h"
#include "vcs/ide-vcs.h"
DZL_DEFINE_COUNTER (Instances, "Pipeline", "N Pipelines", "Number of Pipeline instances")
@@ -169,12 +171,18 @@ struct _IdeBuildPipeline
guint errfmt_seqnum;
/*
- * Our PTY master for use by launchers in the build pipeline.
- * We create a slave FD for the launchers (and dup() them when
- * passing it to the child) that are created by pipeline addins.
+ * The VtePty is used to connect to a VteTerminal. It's basically
+ * just a wrapper around a PTY master. We then add a pty_intercept_t
+ * to proxy PTY data while allowing us to tap into the content being
+ * transmitted. We can use that to run regexes against and perform
+ * additional error extraction. Finally, pty_slave is the PTY device
+ * we created that will get attached to stdin/stdout/stderr in our
+ * spawned subprocesses. It is a slave to the PTY master owned by
+ * the pty_intercept_t.
*/
- VtePty *pty;
- int pty_slave;
+ VtePty *pty;
+ pty_intercept_t intercept;
+ pty_fd_t pty_slave;
/*
* If the terminal interpreting our Pty has received a terminal
@@ -525,94 +533,144 @@ create_diagnostic (IdeBuildPipeline *self,
return ide_diagnostic_new (parsed.severity, message, location);
}
-static void
-ide_build_pipeline_log_observer (IdeBuildLogStream stream,
- const gchar *message,
- gssize message_len,
- gpointer user_data)
+static gboolean
+extract_directory_change (IdeBuildPipeline *self,
+ const guint8 *data,
+ gsize len)
{
- IdeBuildPipeline *self = user_data;
- g_autofree gchar *filtered_message = NULL;
- const gchar *enterdir;
+ g_autofree gchar *dir = NULL;
+ const guint8 *begin;
- g_assert (stream == IDE_BUILD_LOG_STDOUT || stream == IDE_BUILD_LOG_STDERR);
g_assert (IDE_IS_BUILD_PIPELINE (self));
- g_assert (message != NULL);
+
+ if (len == 0)
+ return FALSE;
#define ENTERING_DIRECTORY_BEGIN "Entering directory '"
#define ENTERING_DIRECTORY_END "'"
- if (message_len < 0)
- message_len = strlen (message);
+ begin = memmem (data, len, ENTERING_DIRECTORY_BEGIN, strlen (ENTERING_DIRECTORY_BEGIN));
+ if (begin == NULL)
+ return FALSE;
- if (self->log != NULL)
- ide_build_log_observer (stream, message, message_len, self->log);
+ begin += strlen (ENTERING_DIRECTORY_BEGIN);
+
+ if (data[len - 1] != '\'')
+ return FALSE;
- filtered_message = ide_build_utils_color_codes_filtering (message);
+ len = &data[len - 1] - begin;
+ dir = g_memdup (begin, len);
- if (stream == IDE_BUILD_LOG_STDOUT)
+ if (g_utf8_validate (dir, len, NULL))
{
- /*
- * This expects LANG=C, which is defined in the autotools Builder.
- * Not the most ideal decoupling of logic, but we don't have a whole
- * lot to work with here.
- */
- if (NULL != (enterdir = strstr (filtered_message, ENTERING_DIRECTORY_BEGIN)) &&
- g_str_has_suffix (enterdir, ENTERING_DIRECTORY_END))
- {
- gssize len;
+ g_free (self->errfmt_current_dir);
- enterdir += DZL_LITERAL_LENGTH (ENTERING_DIRECTORY_BEGIN);
+ if (len == 0)
+ self->errfmt_current_dir = g_strdup (self->errfmt_top_dir);
+ else
+ self->errfmt_current_dir = g_strndup (dir, len);
- /* Translate to relative paths for out-of-tree builds */
- if (g_str_has_prefix (enterdir, self->builddir))
- {
- enterdir += strlen (self->builddir);
- if (*enterdir == G_DIR_SEPARATOR)
- enterdir++;
- }
+ return TRUE;
+ }
- len = strlen (enterdir) - DZL_LITERAL_LENGTH (ENTERING_DIRECTORY_END);
+#undef ENTERING_DIRECTORY_BEGIN
+#undef ENTERING_DIRECTORY_END
- if (len > 0)
- {
- g_free (self->errfmt_current_dir);
- self->errfmt_current_dir = g_strndup (enterdir, len);
- if (self->errfmt_top_dir == NULL)
- self->errfmt_top_dir = g_strndup (enterdir, len);
- }
+ return FALSE;
+}
- return;
- }
+static void
+extract_diagnostics (IdeBuildPipeline *self,
+ const guint8 *data,
+ gsize len)
+{
+ g_autofree guint8 *unescaped = NULL;
+ IdeLineReader reader;
+ gchar *line;
+ gsize line_len;
+
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+ g_assert (data != NULL);
+
+ if (len == 0 || self->errfmts->len == 0)
+ return;
+
+ /* If we have any color escape sequences, remove them */
+ if G_UNLIKELY (memchr (data, '\033', len) || memmem (data, len, "\\e", 2))
+ {
+ gsize out_len = 0;
+
+ unescaped = ide_build_utils_filter_color_codes (data, len, &out_len);
+ if (out_len == 0)
+ return;
+
+ data = unescaped;
+ len = out_len;
}
- /*
- * Unfortunately, some build engines such as Ninja refuse to pass errors on
- * stderr like the tooling they abstract. So we must parse stdout in addition
- * to stderr to extract errors.
- */
- if (stream == IDE_BUILD_LOG_STDERR || self->errors_on_stdout)
+ ide_line_reader_init (&reader, (gchar *)data, len);
+
+ while (NULL != (line = ide_line_reader_next (&reader, &line_len)))
{
+ if (extract_directory_change (self, (const guint8 *)line, line_len))
+ continue;
+
for (guint i = 0; i < self->errfmts->len; i++)
{
const ErrorFormat *errfmt = &g_array_index (self->errfmts, ErrorFormat, i);
g_autoptr(GMatchInfo) match_info = NULL;
- if (g_regex_match (errfmt->regex, filtered_message, 0, &match_info))
+ if (g_regex_match_full (errfmt->regex, line, line_len, 0, 0, &match_info, NULL))
{
g_autoptr(IdeDiagnostic) diagnostic = create_diagnostic (self, match_info);
if (diagnostic != NULL)
{
ide_build_pipeline_emit_diagnostic (self, diagnostic);
- return;
+ break;
}
}
}
}
+}
-#undef ENTERING_DIRECTORY_BEGIN
-#undef ENTERING_DIRECTORY_END
+static void
+ide_build_pipeline_log_observer (IdeBuildLogStream stream,
+ const gchar *message,
+ gssize message_len,
+ gpointer user_data)
+{
+ IdeBuildPipeline *self = user_data;
+
+ g_assert (stream == IDE_BUILD_LOG_STDOUT || stream == IDE_BUILD_LOG_STDERR);
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+ g_assert (message != NULL);
+
+ if (message_len < 0)
+ message_len = strlen (message);
+
+ if (self->log != NULL)
+ ide_build_log_observer (stream, message, message_len, self->log);
+
+ extract_diagnostics (self, (const guint8 *)message, message_len);
+}
+
+static void
+ide_build_pipeline_intercept_pty_master_cb (const pty_intercept_t *intercept,
+ const pty_intercept_side_t *side,
+ const guint8 *data,
+ gsize len,
+ gpointer user_data)
+{
+ IdeBuildPipeline *self = user_data;
+
+ g_assert (intercept != NULL);
+ g_assert (side != NULL);
+ g_assert (data != NULL);
+ g_assert (len > 0);
+ g_assert (IDE_IS_BUILD_PIPELINE (self));
+
+ extract_diagnostics (self, data, len);
}
static void
@@ -1036,19 +1094,17 @@ static void
ide_build_pipeline_dispose (GObject *object)
{
IdeBuildPipeline *self = IDE_BUILD_PIPELINE (object);
+ g_auto(pty_fd_t) fd = PTY_FD_INVALID;
IDE_ENTRY;
ide_build_pipeline_unload (self);
g_clear_pointer (&self->message, g_free);
- g_clear_object (&self->pty);
- if (self->pty_slave != -1)
- {
- close (self->pty_slave);
- self->pty_slave = -1;
- }
+ g_clear_object (&self->pty);
+ pty_intercept_clear (&self->intercept);
+ fd = pty_fd_steal (&self->pty_slave);
G_OBJECT_CLASS (ide_build_pipeline_parent_class)->dispose (object);
@@ -1061,6 +1117,7 @@ ide_build_pipeline_initable_init (GInitable *initable,
GError **error)
{
IdeBuildPipeline *self = (IdeBuildPipeline *)initable;
+ pty_fd_t master_fd;
IDE_ENTRY;
@@ -1076,6 +1133,22 @@ ide_build_pipeline_initable_init (GInitable *initable,
if (self->pty == NULL)
IDE_RETURN (FALSE);
+ master_fd = vte_pty_get_fd (self->pty);
+
+ if (!pty_intercept_init (&self->intercept, master_fd, NULL))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Failed to initialize PTY intercept");
+ IDE_RETURN (FALSE);
+ }
+
+ pty_intercept_set_callback (&self->intercept,
+ &self->intercept.master,
+ ide_build_pipeline_intercept_pty_master_cb,
+ self);
+
g_signal_connect_object (self->configuration,
"notify::ready",
G_CALLBACK (ide_build_pipeline_notify_ready),
@@ -2431,7 +2504,10 @@ ide_build_pipeline_attach_pty (IdeBuildPipeline *self,
g_return_if_fail (IDE_IS_SUBPROCESS_LAUNCHER (launcher));
if (self->pty_slave == -1)
- self->pty_slave = ide_vte_pty_create_slave (self->pty);
+ {
+ pty_fd_t master_fd = pty_intercept_get_fd (&self->intercept);
+ self->pty_slave = pty_intercept_create_slave (master_fd);
+ }
if (self->pty_slave == -1)
{
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]