From 58dac308b91814c79d3a05051a0656d5236faee2 Mon Sep 17 00:00:00 2001
From: Tobias Hunger <tobias.hunger@nokia.com>
Date: Wed, 18 May 2011 18:38:58 +0200
Subject: [PATCH] Clang: Add parser for clang output

Change-Id: Ic5bb4742e07009172d64acb53c07659c3f4d640a
---
 src/plugins/projectexplorer/clangparser.cpp   | 252 ++++++++++++++++++
 src/plugins/projectexplorer/clangparser.h     |  65 +++++
 src/plugins/projectexplorer/gcctoolchain.cpp  |  15 +-
 src/plugins/projectexplorer/gcctoolchain.h    |   2 +
 .../projectexplorer/projectexplorer.pro       |   2 +
 5 files changed, 331 insertions(+), 5 deletions(-)
 create mode 100644 src/plugins/projectexplorer/clangparser.cpp
 create mode 100644 src/plugins/projectexplorer/clangparser.h

diff --git a/src/plugins/projectexplorer/clangparser.cpp b/src/plugins/projectexplorer/clangparser.cpp
new file mode 100644
index 00000000000..d0386c388ff
--- /dev/null
+++ b/src/plugins/projectexplorer/clangparser.cpp
@@ -0,0 +1,252 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at info@qt.nokia.com.
+**
+**************************************************************************/
+
+#include "clangparser.h"
+#include "ldparser.h"
+#include "taskwindow.h"
+#include "projectexplorerconstants.h"
+
+#include <QtCore/QDir>
+
+using namespace ProjectExplorer;
+
+namespace {
+    // opt. drive letter + filename: (2 brackets)
+    const char * const FILE_PATTERN = "(<command line>|([A-Za-z]:)?[^:]+\\.[^:]+)";
+}
+
+ClangParser::ClangParser() :
+    m_commandRegExp(QLatin1String("^clang(\\+\\+)?: (warning|error|note): (.*)$")),
+    m_inLineRegExp(QLatin1String("^In (.*) included from (.*):(\\d+):$")),
+    m_messageRegExp(QLatin1String("^") + QLatin1String(FILE_PATTERN) + QLatin1String("(:(\\d+):\\d+|\\((\\d+)\\) ): +(error|warning|note): (.*)$"))
+{
+    setObjectName(QLatin1String("ClangParser"));
+
+    appendOutputParser(new LdParser);
+}
+
+ClangParser::~ClangParser()
+{
+    if (!m_currentTask.isNull())
+        emit addTask(m_currentTask);
+}
+
+void ClangParser::stdError(const QString &line)
+{
+    const QString lne = line.left(line.count() - 1);
+    if (m_commandRegExp.indexIn(lne) > -1) {
+        m_codeSnippet.clear();
+        newTask(Task::Error,
+                m_commandRegExp.cap(3),
+                QString(), /* filename */
+                -1, /* line */
+                Constants::TASK_CATEGORY_COMPILE);
+        if (m_commandRegExp.cap(2) == QLatin1String("warning"))
+            m_currentTask.type = Task::Warning;
+        else if (m_commandRegExp.cap(2) == QLatin1String("note"))
+            m_currentTask.type = Task::Unknown;
+        return;
+    }
+
+    if (m_inLineRegExp.indexIn(lne) > -1) {
+        m_codeSnippet.clear();
+        newTask(Task::Unknown,
+                lne.trimmed(),
+                m_inLineRegExp.cap(2), /* filename */
+                m_inLineRegExp.cap(3).toInt(), /* line */
+                Constants::TASK_CATEGORY_COMPILE);
+        return;
+    }
+
+    if (m_messageRegExp.indexIn(lne) > -1) {
+        m_codeSnippet.clear();
+        bool ok = false;
+        int lineNo = m_messageRegExp.cap(4).toInt(&ok);
+        if (!ok)
+            lineNo = m_messageRegExp.cap(5).toInt(&ok);
+        newTask(Task::Error,
+                m_messageRegExp.cap(7),
+                m_messageRegExp.cap(1), /* filename */
+                lineNo,
+                Constants::TASK_CATEGORY_COMPILE);
+        if (m_messageRegExp.cap(6) == "warning")
+            m_currentTask.type = Task::Warning;
+        else if (m_messageRegExp.cap(6) == "note")
+            m_currentTask.type = Task::Unknown;
+        return;
+    }
+
+    if (!m_codeSnippet.isEmpty()) {
+        bool caretSeen = false;
+        bool valid = true;
+        for (int i = 0; i < lne.count() && valid; ++i) {
+            if (QChar(' ') == lne.at(i))
+                continue;
+            if (QChar('~') == lne.at(i))
+                continue;
+            if (QChar('^') == lne.at(i)) {
+                if (caretSeen)
+                    valid = false;
+                else
+                    caretSeen = true;
+                continue;
+            }
+            valid = false;
+        }
+
+        if (valid && caretSeen) {
+            QTextLayout::FormatRange fr;
+            fr.start = m_currentTask.description.count() + 1;
+            fr.length = m_codeSnippet.count() + lne.count() + 1;
+            fr.format.setFontStyleHint(QFont::TypeWriter);
+            m_currentTask.description.append(QLatin1Char('\n'));
+            m_currentTask.description.append(m_codeSnippet);
+            m_currentTask.description.append(QLatin1Char('\n'));
+            m_currentTask.description.append(lne);
+            m_currentTask.formats.append(fr);
+            return;
+        }
+    }
+
+    m_codeSnippet = lne;
+    IOutputParser::stdError(line);
+}
+
+void ClangParser::newTask(Task::TaskType type_, const QString &description_,
+                          const QString &file_, int line_, const QString &category_)
+{
+    if (!m_currentTask.isNull())
+        emit addTask(m_currentTask);
+    m_currentTask = Task(type_, description_, file_, line_, category_);
+}
+
+// Unit tests:
+
+#ifdef WITH_TESTS
+#   include <QTest>
+
+#   include "projectexplorer.h"
+#   include "metatypedeclarations.h"
+#   include "outputparser_test.h"
+
+void ProjectExplorerPlugin::testClangOutputParser_data()
+{
+    QTest::addColumn<QString>("input");
+    QTest::addColumn<OutputParserTester::Channel>("inputChannel");
+    QTest::addColumn<QString>("childStdOutLines");
+    QTest::addColumn<QString>("childStdErrLines");
+    QTest::addColumn<QList<ProjectExplorer::Task> >("tasks");
+    QTest::addColumn<QString>("outputLines");
+
+
+    QTest::newRow("pass-through stdout")
+            << QString::fromLatin1("Sometext") << OutputParserTester::STDOUT
+            << QString::fromLatin1("Sometext\n") << QString()
+            << QList<ProjectExplorer::Task>()
+            << QString();
+    QTest::newRow("pass-through stderr")
+            << QString::fromLatin1("Sometext") << OutputParserTester::STDERR
+            << QString() << QString::fromLatin1("Sometext\n")
+            << QList<ProjectExplorer::Task>()
+            << QString();
+
+    QTest::newRow("clang++ warning")
+            << QString::fromLatin1("clang++: warning: argument unused during compilation: '-mthreads'")
+            << OutputParserTester::STDERR
+            << QString() << QString()
+            << (QList<ProjectExplorer::Task>()
+                << Task(Task::Warning,
+                        QLatin1String("argument unused during compilation: '-mthreads'"),
+                        QString(), -1,
+                        Constants::TASK_CATEGORY_COMPILE))
+            << QString();
+    QTest::newRow("clang++ error")
+            << QString::fromLatin1("clang++: error: no input files [err_drv_no_input_files]")
+            << OutputParserTester::STDERR
+            << QString() << QString()
+            << (QList<ProjectExplorer::Task>()
+                << Task(Task::Error,
+                        QLatin1String("no input files [err_drv_no_input_files]"),
+                        QString(), -1,
+                        Constants::TASK_CATEGORY_COMPILE))
+            << QString();
+    QTest::newRow("complex warning")
+            << QString::fromLatin1("In file included from ..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qnamespace.h:45:\n"
+                                   "..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h(1425) :  warning: unknown attribute 'dllimport' ignored [-Wunknown-attributes]\n"
+                                   "class Q_CORE_EXPORT QSysInfo {\n"
+                                   "      ^")
+            << OutputParserTester::STDERR
+            << QString() << QString::fromLatin1("class Q_CORE_EXPORT QSysInfo {\n")
+            << (QList<ProjectExplorer::Task>()
+                << Task(Task::Unknown,
+                        QLatin1String("In file included from ..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qnamespace.h:45:"),
+                        QLatin1String("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qnamespace.h"), 45,
+                        Constants::TASK_CATEGORY_COMPILE)
+                << Task(Task::Warning,
+                        QLatin1String("unknown attribute 'dllimport' ignored [-Wunknown-attributes]\n"
+                                      "class Q_CORE_EXPORT QSysInfo {\n"
+                                      "      ^"),
+                        QLatin1String("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h"), 1425,
+                        Constants::TASK_CATEGORY_COMPILE))
+            << QString();
+        QTest::newRow("note")
+                << QString::fromLatin1("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h:1289:27: note: instantiated from:\n"
+                                       "#    define Q_CORE_EXPORT Q_DECL_IMPORT\n"
+                                       "                          ^")
+                << OutputParserTester::STDERR
+                << QString() << QString::fromLatin1("#    define Q_CORE_EXPORT Q_DECL_IMPORT\n")
+                << (QList<ProjectExplorer::Task>()
+                    << Task(Task::Unknown,
+                            QLatin1String("instantiated from:\n"
+                                          "#    define Q_CORE_EXPORT Q_DECL_IMPORT\n"
+                                          "                          ^"),
+                            QLatin1String("..\\..\\..\\QtSDK1.1\\Desktop\\Qt\\4.7.3\\mingw\\include/QtCore/qglobal.h"), 1289,
+                            Constants::TASK_CATEGORY_COMPILE))
+                << QString();
+}
+
+void ProjectExplorerPlugin::testClangOutputParser()
+{
+    OutputParserTester testbench;
+    testbench.appendOutputParser(new ClangParser);
+    QFETCH(QString, input);
+    QFETCH(OutputParserTester::Channel, inputChannel);
+    QFETCH(QList<Task>, tasks);
+    QFETCH(QString, childStdOutLines);
+    QFETCH(QString, childStdErrLines);
+    QFETCH(QString, outputLines);
+
+    testbench.testParsing(input, inputChannel,
+                          tasks, childStdOutLines, childStdErrLines,
+                          outputLines);
+}
+#endif
diff --git a/src/plugins/projectexplorer/clangparser.h b/src/plugins/projectexplorer/clangparser.h
new file mode 100644
index 00000000000..b5ca20ab79b
--- /dev/null
+++ b/src/plugins/projectexplorer/clangparser.h
@@ -0,0 +1,65 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at info@qt.nokia.com.
+**
+**************************************************************************/
+
+#ifndef CLANGPARSER_H
+#define CLANGPARSER_H
+
+#include "ioutputparser.h"
+
+#include <QtCore/QRegExp>
+
+namespace ProjectExplorer {
+
+class ClangParser : public ProjectExplorer::IOutputParser
+{
+    Q_OBJECT
+
+public:
+    ClangParser();
+    ~ClangParser();
+    virtual void stdError(const QString &line);
+
+private:
+    void newTask(Task::TaskType type_, const QString &description_,
+                 const QString &file_, int line_, const QString &category_);
+
+    QRegExp m_commandRegExp;
+    QRegExp m_inLineRegExp;
+    QRegExp m_messageRegExp;
+    QString m_codeSnippet;
+
+    Task m_currentTask;
+};
+
+} // namespace ProjectExplorer
+
+#endif // CLANGPARSER_H
diff --git a/src/plugins/projectexplorer/gcctoolchain.cpp b/src/plugins/projectexplorer/gcctoolchain.cpp
index b4e772dbb6d..ff58f70d493 100644
--- a/src/plugins/projectexplorer/gcctoolchain.cpp
+++ b/src/plugins/projectexplorer/gcctoolchain.cpp
@@ -31,6 +31,7 @@
 **************************************************************************/
 
 #include "gcctoolchain.h"
+#include "clangparser.h"
 #include "gcctoolchainfactories.h"
 #include "gccparser.h"
 #include "linuxiccparser.h"
@@ -692,7 +693,6 @@ void Internal::GccToolChainConfigWidget::handleAbiChange()
     emit dirty(toolChain());
 }
 
-
 // --------------------------------------------------------------------------
 // ClangToolChain
 // --------------------------------------------------------------------------
@@ -718,10 +718,15 @@ QString ClangToolChain::makeCommand() const
 QString ClangToolChain::mkspec() const
 {
     if (targetAbi().os() == Abi::MacOS)
-        return QLatin1String("macx-llvm");
+        return QLatin1String("unsupported/macx-clang");
     else if (targetAbi().os() == Abi::LinuxOS)
-        return QLatin1String("linux-llvm");
-    return QLatin1String("win32-llvm"); // Note: Not part of standard Qt yet!
+        return QLatin1String("unsupported/linux-clang");
+    return QLatin1String("unsupported/win32-clang"); // Note: Not part of Qt yet!
+}
+
+IOutputParser *ClangToolChain::outputParser() const
+{
+    return new ClangParser;
 }
 
 ToolChain *ClangToolChain::clone() const
@@ -966,7 +971,7 @@ void ProjectExplorerPlugin::testGccAbiGuessing_data()
             << (QStringList());
     QTest::newRow("broken input")
             << QString::fromLatin1("arm-none-foo-gnueabi")
-            << (QStringList() << QLatin1String("arm-unknown-unknown-elf-32bit"));
+            << (QStringList() << QLatin1String("arm-unknown-unknown-unknown-32bit"));
     QTest::newRow("totally broken input")
             << QString::fromLatin1("foo-bar-foo")
             << (QStringList());
diff --git a/src/plugins/projectexplorer/gcctoolchain.h b/src/plugins/projectexplorer/gcctoolchain.h
index 4b60e5f2fa8..15425cab184 100644
--- a/src/plugins/projectexplorer/gcctoolchain.h
+++ b/src/plugins/projectexplorer/gcctoolchain.h
@@ -121,6 +121,8 @@ public:
     QString makeCommand() const;
     QString mkspec() const;
 
+    IOutputParser *outputParser() const;
+
     ToolChain *clone() const;
 
 private:
diff --git a/src/plugins/projectexplorer/projectexplorer.pro b/src/plugins/projectexplorer/projectexplorer.pro
index 430a2aac35a..ae75f21ec78 100644
--- a/src/plugins/projectexplorer/projectexplorer.pro
+++ b/src/plugins/projectexplorer/projectexplorer.pro
@@ -8,6 +8,7 @@ include(customwizard/customwizard.pri)
 INCLUDEPATH += $$PWD/../../libs/utils
 HEADERS += projectexplorer.h \
     abi.h \
+    clangparser.h \
     gcctoolchain.h \
     projectexplorer_export.h \
     projectwindow.h \
@@ -102,6 +103,7 @@ HEADERS += projectexplorer.h \
 
 SOURCES += projectexplorer.cpp \
     abi.cpp \
+    clangparser.cpp \
     gcctoolchain.cpp \
     projectwindow.cpp \
     buildmanager.cpp \
-- 
GitLab