From a544ab672eb49bccb49c31b0ebdfec44a2e9fabd Mon Sep 17 00:00:00 2001
From: Fawzi Mohamed <fawzi.mohamed@digia.com>
Date: Wed, 7 May 2014 19:30:49 +0200
Subject: [PATCH] projectexplorer/qmake-makestep: parse output of xcodebuild

Xcodebuild (used by default for ios and optionally on mac) redirects all
compile errors to stdout.
This breaks the detection of errors.
Redirect stdout to stderr if within xcodebuild.

Change-Id: I60fd21a7b075fbfc29c99a10debf34e20487c4df
Reviewed-by: Eike Ziller <eike.ziller@digia.com>
Reviewed-by: Tobias Hunger <tobias.hunger@digia.com>
---
 src/plugins/projectexplorer/projectexplorer.h |   3 +
 .../projectexplorer/projectexplorer.pro       |   6 +-
 .../projectexplorer/projectexplorer.qbs       |   3 +-
 .../projectexplorer/xcodebuildparser.cpp      | 266 ++++++++++++++++++
 .../projectexplorer/xcodebuildparser.h        |  82 ++++++
 src/plugins/qmakeprojectmanager/makestep.cpp  |   3 +
 6 files changed, 360 insertions(+), 3 deletions(-)
 create mode 100644 src/plugins/projectexplorer/xcodebuildparser.cpp
 create mode 100644 src/plugins/projectexplorer/xcodebuildparser.h

diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h
index 0e82ff92815..ca544dab18b 100644
--- a/src/plugins/projectexplorer/projectexplorer.h
+++ b/src/plugins/projectexplorer/projectexplorer.h
@@ -256,6 +256,9 @@ private slots:
     void testGnuMakeParserTaskMangling_data();
     void testGnuMakeParserTaskMangling();
 
+    void testXcodebuildParserParsing_data();
+    void testXcodebuildParserParsing();
+
     void testMsvcOutputParsers_data();
     void testMsvcOutputParsers();
 
diff --git a/src/plugins/projectexplorer/projectexplorer.pro b/src/plugins/projectexplorer/projectexplorer.pro
index 94e6d80b501..a993fc79daf 100644
--- a/src/plugins/projectexplorer/projectexplorer.pro
+++ b/src/plugins/projectexplorer/projectexplorer.pro
@@ -143,7 +143,8 @@ HEADERS += projectexplorer.h \
     customparser.h \
     customparserconfigdialog.h \
     ipotentialkit.h \
-    selectablefilesmodel.h
+    selectablefilesmodel.h \
+    xcodebuildparser.h
 
 SOURCES += projectexplorer.cpp \
     abi.cpp \
@@ -273,7 +274,8 @@ SOURCES += projectexplorer.cpp \
     customparser.cpp \
     customparserconfigdialog.cpp \
     ipotentialkit.cpp \
-    selectablefilesmodel.cpp
+    selectablefilesmodel.cpp \
+    xcodebuildparser.cpp
 
 FORMS += processstep.ui \
     editorsettingspropertiespage.ui \
diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs
index 9ecac8b701f..884764ffc15 100644
--- a/src/plugins/projectexplorer/projectexplorer.qbs
+++ b/src/plugins/projectexplorer/projectexplorer.qbs
@@ -143,7 +143,8 @@ QtcPlugin {
             "toolchainmanager.cpp", "toolchainmanager.h",
             "toolchainoptionspage.cpp", "toolchainoptionspage.h",
             "unconfiguredprojectpanel.cpp", "unconfiguredprojectpanel.h",
-            "vcsannotatetaskhandler.cpp", "vcsannotatetaskhandler.h"
+            "vcsannotatetaskhandler.cpp", "vcsannotatetaskhandler.h",
+            "xcodebuildparser.cpp", "xcodebuildparser.h"
         ]
     }
 
diff --git a/src/plugins/projectexplorer/xcodebuildparser.cpp b/src/plugins/projectexplorer/xcodebuildparser.cpp
new file mode 100644
index 00000000000..4d6e280a955
--- /dev/null
+++ b/src/plugins/projectexplorer/xcodebuildparser.cpp
@@ -0,0 +1,266 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+#include "xcodebuildparser.h"
+
+#include "projectexplorerconstants.h"
+#include "task.h"
+
+#include <utils/qtcassert.h>
+
+#include <QCoreApplication>
+
+namespace ProjectExplorer {
+
+static const char failureRe[] = "\\*\\* BUILD FAILED \\*\\*$";
+static const char successRe[] = "\\*\\* BUILD SUCCEEDED \\*\\*$";
+static const char buildRe[] = "=== BUILD (AGGREGATE )?TARGET (.*) OF PROJECT (.*) WITH .* ===$";
+
+XcodebuildParser::XcodebuildParser() :
+    m_fatalErrorCount(0),
+    m_xcodeBuildParserState(OutsideXcodebuild)
+{
+    setObjectName(QLatin1String("XcodeParser"));
+    m_failureRe.setPattern(QLatin1String(failureRe));
+    QTC_CHECK(m_failureRe.isValid());
+    m_successRe.setPattern(QLatin1String(successRe));
+    QTC_CHECK(m_successRe.isValid());
+    m_buildRe.setPattern(QLatin1String(buildRe));
+    QTC_CHECK(m_buildRe.isValid());
+}
+
+bool XcodebuildParser::hasFatalErrors() const
+{
+    return (m_fatalErrorCount > 0) || IOutputParser::hasFatalErrors();
+}
+
+void XcodebuildParser::stdOutput(const QString &line)
+{
+    const QString lne = rightTrimmed(line);
+    if (m_buildRe.indexIn(lne) > -1) {
+        m_xcodeBuildParserState = InXcodebuild;
+        m_lastTarget = m_buildRe.cap(2);
+        m_lastProject = m_buildRe.cap(3);
+        return;
+    }
+    if (m_xcodeBuildParserState == InXcodebuild || m_xcodeBuildParserState == UnknownXcodebuildState) {
+        if (m_successRe.indexIn(lne) > -1) {
+            m_xcodeBuildParserState = OutsideXcodebuild;
+            return;
+        }
+        IOutputParser::stdError(line);
+    } else {
+        IOutputParser::stdOutput(line);
+    }
+}
+
+void XcodebuildParser::stdError(const QString &line)
+{
+    const QString lne = rightTrimmed(line);
+    if (m_failureRe.indexIn(lne) > -1) {
+        ++m_fatalErrorCount;
+        m_xcodeBuildParserState = UnknownXcodebuildState;
+        // unfortunately the m_lastTarget, m_lastProject might not be in sync
+        Task task(Task::Error,
+                  QCoreApplication::translate("ProjectExplorer::XcodebuildParser",
+                                              "Xcodebuild failed."),
+                  Utils::FileName(), /* filename */
+                  -1, /* line */
+                  ProjectExplorer::Constants::TASK_CATEGORY_COMPILE);
+        taskAdded(task);
+        return;
+    }
+    if (m_xcodeBuildParserState == OutsideXcodebuild) { // also forward if UnknownXcodebuildState ?
+        IOutputParser::stdError(line);
+        return;
+    }
+}
+
+} // namespace ProjectExplorer
+
+// Unit tests:
+
+#ifdef WITH_TESTS
+#   include <QTest>
+
+#   include "outputparser_test.h"
+#   include "projectexplorer.h"
+
+#   include "metatypedeclarations.h"
+
+using namespace ProjectExplorer;
+
+Q_DECLARE_METATYPE(ProjectExplorer::XcodebuildParser::XcodebuildStatus)
+
+XcodebuildParserTester::XcodebuildParserTester(XcodebuildParser *p, QObject *parent) :
+    QObject(parent),
+    parser(p)
+{ }
+
+void ProjectExplorerPlugin::testXcodebuildParserParsing_data()
+{
+    QTest::addColumn<ProjectExplorer::XcodebuildParser::XcodebuildStatus>("initialStatus");
+    QTest::addColumn<QString>("input");
+    QTest::addColumn<OutputParserTester::Channel>("inputChannel");
+    QTest::addColumn<QString>("childStdOutLines");
+    QTest::addColumn<QString>("childStdErrLines");
+    QTest::addColumn<QList<Task> >("tasks");
+    QTest::addColumn<QString>("outputLines");
+    QTest::addColumn<ProjectExplorer::XcodebuildParser::XcodebuildStatus>("finalStatus");
+
+    QTest::newRow("outside pass-through stdout")
+            << XcodebuildParser::OutsideXcodebuild
+            << QString::fromLatin1("Sometext") << OutputParserTester::STDOUT
+            << QString::fromLatin1("Sometext\n") << QString()
+            << QList<Task>()
+            << QString()
+            << XcodebuildParser::OutsideXcodebuild;
+    QTest::newRow("outside pass-through stderr")
+            << XcodebuildParser::OutsideXcodebuild
+            << QString::fromLatin1("Sometext") << OutputParserTester::STDERR
+            << QString() << QString::fromLatin1("Sometext\n")
+            << QList<Task>()
+            << QString()
+            << XcodebuildParser::OutsideXcodebuild;
+    QTest::newRow("inside pass stdout to stderr")
+            << XcodebuildParser::InXcodebuild
+            << QString::fromLatin1("Sometext") << OutputParserTester::STDOUT
+            << QString() << QString::fromLatin1("Sometext\n")
+            << QList<Task>()
+            << QString()
+            << XcodebuildParser::InXcodebuild;
+    QTest::newRow("inside ignore stderr")
+            << XcodebuildParser::InXcodebuild
+            << QString::fromLatin1("Sometext") << OutputParserTester::STDERR
+            << QString() << QString()
+            << QList<Task>()
+            << QString()
+            << XcodebuildParser::InXcodebuild;
+    QTest::newRow("unknown pass stdout to stderr")
+            << XcodebuildParser::UnknownXcodebuildState
+            << QString::fromLatin1("Sometext") << OutputParserTester::STDOUT
+            << QString() << QString::fromLatin1("Sometext\n")
+            << QList<Task>()
+            << QString()
+            << XcodebuildParser::UnknownXcodebuildState;
+    QTest::newRow("unknown ignore stderr (change?)")
+            << XcodebuildParser::UnknownXcodebuildState
+            << QString::fromLatin1("Sometext") << OutputParserTester::STDERR
+            << QString() << QString()
+            << QList<Task>()
+            << QString()
+            << XcodebuildParser::UnknownXcodebuildState;
+    QTest::newRow("switch outside->in->outside")
+            << XcodebuildParser::OutsideXcodebuild
+            << QString::fromLatin1("outside\n"
+                                   "=== BUILD AGGREGATE TARGET Qt Preprocess OF PROJECT testQQ WITH THE DEFAULT CONFIGURATION (Debug) ===\n"
+                                   "in xcodebuild\n"
+                                   "=== BUILD TARGET testQQ OF PROJECT testQQ WITH THE DEFAULT CONFIGURATION (Debug) ===\n"
+                                   "in xcodebuild2\n"
+                                   "** BUILD SUCCEEDED **\n"
+                                   "outside2")
+            << OutputParserTester::STDOUT
+            << QString::fromLatin1("outside\noutside2\n") << QString::fromLatin1("in xcodebuild\nin xcodebuild2\n")
+            << QList<Task>()
+            << QString()
+            << XcodebuildParser::OutsideXcodebuild;
+    QTest::newRow("switch Unknown->in->outside")
+            << XcodebuildParser::UnknownXcodebuildState
+            << QString::fromLatin1("unknown\n"
+                                   "=== BUILD TARGET testQQ OF PROJECT testQQ WITH THE DEFAULT CONFIGURATION (Debug) ===\n"
+                                   "in xcodebuild\n"
+                                   "** BUILD SUCCEEDED **\n"
+                                   "outside")
+            << OutputParserTester::STDOUT
+            << QString::fromLatin1("outside\n") << QString::fromLatin1("unknown\nin xcodebuild\n")
+            << QList<Task>()
+            << QString()
+            << XcodebuildParser::OutsideXcodebuild;
+    QTest::newRow("switch in->unknown")
+            << XcodebuildParser::InXcodebuild
+            << QString::fromLatin1("insideErr\n"
+                                   "** BUILD FAILED **\n"
+                                   "unknownErr")
+            << OutputParserTester::STDERR
+            << QString() << QString()
+            << (QList<ProjectExplorer::Task>()
+                << ProjectExplorer::Task(
+                    Task::Error,
+                    QCoreApplication::translate("ProjectExplorer::XcodebuildParser",
+                                                "Xcodebuild failed."),
+                    Utils::FileName(), /* filename */
+                    -1, /* line */
+                    ProjectExplorer::Constants::TASK_CATEGORY_COMPILE))
+            << QString()
+            << XcodebuildParser::UnknownXcodebuildState;
+    QTest::newRow("switch out->unknown")
+            << XcodebuildParser::OutsideXcodebuild
+            << QString::fromLatin1("outErr\n"
+                                   "** BUILD FAILED **\n"
+                                   "unknownErr")
+            << OutputParserTester::STDERR
+            << QString() << QString::fromLatin1("outErr\n")
+            << (QList<ProjectExplorer::Task>()
+                << ProjectExplorer::Task(
+                    Task::Error,
+                    QCoreApplication::translate("ProjectExplorer::XcodebuildParser",
+                                                "Xcodebuild failed."),
+                    Utils::FileName(), /* filename */
+                    -1, /* line */
+                    ProjectExplorer::Constants::TASK_CATEGORY_COMPILE))
+            << QString()
+            << XcodebuildParser::UnknownXcodebuildState;
+}
+
+void ProjectExplorerPlugin::testXcodebuildParserParsing()
+{
+    OutputParserTester testbench;
+    XcodebuildParser *childParser = new XcodebuildParser;
+    XcodebuildParserTester *tester = new XcodebuildParserTester(childParser);
+
+    testbench.appendOutputParser(childParser);
+    QFETCH(ProjectExplorer::XcodebuildParser::XcodebuildStatus, initialStatus);
+    QFETCH(QString, input);
+    QFETCH(OutputParserTester::Channel, inputChannel);
+    QFETCH(QString, childStdOutLines);
+    QFETCH(QString, childStdErrLines);
+    QFETCH(QList<Task>, tasks);
+    QFETCH(QString, outputLines);
+    QFETCH(ProjectExplorer::XcodebuildParser::XcodebuildStatus, finalStatus);
+
+    childParser->m_xcodeBuildParserState = initialStatus;
+    testbench.testParsing(input, inputChannel,
+                          tasks, childStdOutLines, childStdErrLines,
+                          outputLines);
+    QCOMPARE(childParser->m_xcodeBuildParserState, finalStatus);
+
+    delete tester;
+}
+
+#endif
+
diff --git a/src/plugins/projectexplorer/xcodebuildparser.h b/src/plugins/projectexplorer/xcodebuildparser.h
new file mode 100644
index 00000000000..53da4736e79
--- /dev/null
+++ b/src/plugins/projectexplorer/xcodebuildparser.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights.  These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+#ifndef IOSXCODEPARSER_H
+#define IOSXCODEPARSER_H
+
+#include "projectexplorer_export.h"
+#include "ioutputparser.h"
+#include "devicesupport/idevice.h"
+
+#include <QRegExp>
+#include <QStringList>
+
+namespace ProjectExplorer {
+
+class PROJECTEXPLORER_EXPORT XcodebuildParser : public IOutputParser
+{
+    Q_OBJECT
+public:
+    enum XcodebuildStatus {
+        InXcodebuild,
+        OutsideXcodebuild,
+        UnknownXcodebuildState
+    };
+
+    XcodebuildParser();
+
+    void stdOutput(const QString &line);
+    void stdError(const QString &line);
+    bool hasFatalErrors() const;
+private:
+    int m_fatalErrorCount;
+    QRegExp m_failureRe;
+    QRegExp m_successRe;
+    QRegExp m_buildRe;
+    XcodebuildStatus m_xcodeBuildParserState;
+    QString m_lastTarget;
+    QString m_lastProject;
+#if defined WITH_TESTS
+    friend class ProjectExplorerPlugin;
+#endif
+};
+
+#if defined WITH_TESTS
+class XcodebuildParserTester : public QObject
+{
+    Q_OBJECT
+public:
+    explicit XcodebuildParserTester(XcodebuildParser *parser, QObject *parent = 0);
+
+    XcodebuildParser *parser;
+};
+#endif
+
+} // namespace ProjectExplorer
+
+#endif // IOSXCODEPARSER_H
diff --git a/src/plugins/qmakeprojectmanager/makestep.cpp b/src/plugins/qmakeprojectmanager/makestep.cpp
index d1b39bf2ed0..067fdf53e81 100644
--- a/src/plugins/qmakeprojectmanager/makestep.cpp
+++ b/src/plugins/qmakeprojectmanager/makestep.cpp
@@ -42,6 +42,7 @@
 #include <projectexplorer/gnumakeparser.h>
 #include <projectexplorer/projectexplorer.h>
 #include <projectexplorer/kitinformation.h>
+#include <projectexplorer/xcodebuildparser.h>
 #include <utils/qtcprocess.h>
 
 #include <QDir>
@@ -255,6 +256,8 @@ bool MakeStep::init()
     pp->resolveAll();
 
     setOutputParser(new ProjectExplorer::GnuMakeParser());
+    if (tc && tc->targetAbi().os() == Abi::MacOS)
+        appendOutputParser(new XcodebuildParser);
     IOutputParser *parser = target()->kit()->createOutputParser();
     if (parser)
         appendOutputParser(parser);
-- 
GitLab