diff options
author | Denis Shienkov <denis.shienkov@gmail.com> | 2019-03-16 15:26:35 +0300 |
---|---|---|
committer | Denis Shienkov <denis.shienkov@gmail.com> | 2019-03-18 18:19:56 +0000 |
commit | 1b56fe716f060f799326958307c1fdf9d22d677b (patch) | |
tree | f4e7d62193931bf40a86b2c9eaae54cdd9cda750 /src/plugins/baremetal | |
parent | 84f91f255c6551ed537051b7138905b0e34a08c3 (diff) |
bare-metal: Add output parser for KEIL toolchain
This error parser handles output for the ARM and MCS51 architectures.
Change-Id: Ib79fbe1b9b0b12eaa09975ef724160cf275c5aa2
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Diffstat (limited to 'src/plugins/baremetal')
-rw-r--r-- | src/plugins/baremetal/baremetal.pro | 2 | ||||
-rw-r--r-- | src/plugins/baremetal/baremetal.qbs | 1 | ||||
-rw-r--r-- | src/plugins/baremetal/baremetalplugin.h | 2 | ||||
-rw-r--r-- | src/plugins/baremetal/keilparser.cpp | 512 | ||||
-rw-r--r-- | src/plugins/baremetal/keilparser.h | 57 | ||||
-rw-r--r-- | src/plugins/baremetal/keiltoolchain.cpp | 3 |
6 files changed, 576 insertions, 1 deletions
diff --git a/src/plugins/baremetal/baremetal.pro b/src/plugins/baremetal/baremetal.pro index d69f2eaec3c..495ddf3acde 100644 --- a/src/plugins/baremetal/baremetal.pro +++ b/src/plugins/baremetal/baremetal.pro @@ -23,6 +23,7 @@ SOURCES += baremetalplugin.cpp \ iarewtoolchain.cpp \ keiltoolchain.cpp \ iarewparser.cpp \ + keilparser.cpp \ HEADERS += baremetalplugin.h \ baremetalconstants.h \ @@ -45,6 +46,7 @@ HEADERS += baremetalplugin.h \ iarewtoolchain.h \ keiltoolchain.h \ iarewparser.h \ + keilparser.h \ RESOURCES += \ baremetal.qrc diff --git a/src/plugins/baremetal/baremetal.qbs b/src/plugins/baremetal/baremetal.qbs index 77de2188dac..d67a1c56240 100644 --- a/src/plugins/baremetal/baremetal.qbs +++ b/src/plugins/baremetal/baremetal.qbs @@ -34,5 +34,6 @@ QtcPlugin { "iarewtoolchain.cpp", "iarewtoolchain.h", "keiltoolchain.cpp", "keiltoolchain.h", "iarewparser.cpp", "iarewparser.h", + "keilparser.cpp", "keilparser.h", ] } diff --git a/src/plugins/baremetal/baremetalplugin.h b/src/plugins/baremetal/baremetalplugin.h index fb9a0bd4318..151c63338e7 100644 --- a/src/plugins/baremetal/baremetalplugin.h +++ b/src/plugins/baremetal/baremetalplugin.h @@ -47,6 +47,8 @@ class BareMetalPlugin : public ExtensionSystem::IPlugin private slots: void testIarOutputParsers_data(); void testIarOutputParsers(); + void testKeilOutputParsers_data(); + void testKeilOutputParsers(); #endif // WITH_TESTS }; diff --git a/src/plugins/baremetal/keilparser.cpp b/src/plugins/baremetal/keilparser.cpp new file mode 100644 index 00000000000..229d916f754 --- /dev/null +++ b/src/plugins/baremetal/keilparser.cpp @@ -0,0 +1,512 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "keilparser.h" + +#include <projectexplorer/projectexplorerconstants.h> +#include <projectexplorer/task.h> + +#include <texteditor/fontsettings.h> +#include <texteditor/texteditorsettings.h> + +#include <QRegularExpression> + +using namespace ProjectExplorer; + +namespace BareMetal { +namespace Internal { + +static Task::TaskType taskType(const QString &msgType) +{ + if (msgType == "Warning" || msgType == "WARNING") { + return Task::TaskType::Warning; + } else if (msgType == "Error" || msgType == "ERROR" + || msgType == "Fatal error" || msgType == "FATAL ERROR") { + return Task::TaskType::Error; + } + return Task::TaskType::Unknown; +} + +KeilParser::KeilParser() +{ + setObjectName("KeilParser"); +} + +Core::Id KeilParser::id() +{ + return "BareMetal.OutputParser.Keil"; +} + +void KeilParser::newTask(const Task &task) +{ + doFlush(); + m_lastTask = task; + m_lines = 1; +} + +void KeilParser::amendDescription(const QString &desc) +{ + const int start = m_lastTask.description.count() + 1; + m_lastTask.description.append(QLatin1Char('\n')); + m_lastTask.description.append(desc); + + QTextLayout::FormatRange fr; + fr.start = start; + fr.length = m_lastTask.description.count() + 1; + fr.format.setFont(TextEditor::TextEditorSettings::fontSettings().font()); + fr.format.setFontStyleHint(QFont::Monospace); + m_lastTask.formats.append(fr); + + ++m_lines; +} + +void KeilParser::stdError(const QString &line) +{ + IOutputParser::stdError(line); + + const QString lne = rightTrimmed(line); + + QRegularExpression re; + QRegularExpressionMatch match; + + // ARM compiler specific patterns. + + re.setPattern("^\"(.+)\", line (\\d+).*:\\s+(Warning|Error):(\\s+|.+)([#|L].+)$"); + match = re.match(lne); + if (match.hasMatch()) { + enum CaptureIndex { FilePathIndex = 1, LineNumberIndex, + MessageTypeIndex, MessageNoteIndex, DescriptionIndex }; + const Utils::FileName fileName = Utils::FileName::fromUserInput( + match.captured(FilePathIndex)); + const int lineno = match.captured(LineNumberIndex).toInt(); + const Task::TaskType type = taskType(match.captured(MessageTypeIndex)); + const QString descr = match.captured(DescriptionIndex); + const Task task(type, descr, fileName, lineno, Constants::TASK_CATEGORY_COMPILE); + newTask(task); + return; + } + + re.setPattern("^(Error|Fatal error):\\s(.+)$"); + match = re.match(lne); + if (match.hasMatch()) { + enum CaptureIndex { MessageTypeIndex = 1, DescriptionIndex }; + const Task::TaskType type = taskType(match.captured(MessageTypeIndex)); + const QString descr = match.captured(DescriptionIndex); + const Task task(type, descr, {}, -1, Constants::TASK_CATEGORY_COMPILE); + newTask(task); + return; + } + + if (lne.startsWith(QLatin1Char(' '))) { + amendDescription(lne); + return; + } + + doFlush(); +} + +void KeilParser::stdOutput(const QString &line) +{ + IOutputParser::stdOutput(line); + + const QString lne = rightTrimmed(line); + + QRegularExpression re; + QRegularExpressionMatch match; + + // MSC51 compiler specific patterns. + + re.setPattern("^\\*{3} (WARNING|ERROR) (\\w+) IN LINE (\\d+) OF (.+\\.\\S+): (.+)$"); + match = re.match(lne); + if (match.hasMatch()) { + enum CaptureIndex { MessageTypeIndex = 1, MessageCodeIndex, LineNumberIndex, + FilePathIndex, MessageTextIndex }; + const Task::TaskType type = taskType(match.captured(MessageTypeIndex)); + const int lineno = match.captured(LineNumberIndex).toInt(); + const Utils::FileName fileName = Utils::FileName::fromUserInput( + match.captured(FilePathIndex)); + const QString descr = QString("%1: %2").arg(match.captured(MessageCodeIndex), + match.captured(MessageTextIndex)); + const Task task(type, descr, fileName, lineno, Constants::TASK_CATEGORY_COMPILE); + newTask(task); + } + + re.setPattern("^\\*{3} (WARNING|ERROR) (#\\w+) IN (\\d+) \\((.+), LINE \\d+\\): (.+)$"); + match = re.match(lne); + if (match.hasMatch()) { + enum CaptureIndex { MessageTypeIndex = 1, MessageCodeIndex, LineNumberIndex, + FilePathIndex, MessageTextIndex }; + const Task::TaskType type = taskType(match.captured(MessageTypeIndex)); + const int lineno = match.captured(LineNumberIndex).toInt(); + const Utils::FileName fileName = Utils::FileName::fromUserInput( + match.captured(FilePathIndex)); + const QString descr = QString("%1: %2").arg(match.captured(MessageCodeIndex), + match.captured(MessageTextIndex)); + const Task task(type, descr, fileName, lineno, Constants::TASK_CATEGORY_COMPILE); + newTask(task); + } + + re.setPattern("^\\*{3} (FATAL ERROR) (.+)$"); + match = re.match(lne); + if (match.hasMatch()) { + enum CaptureIndex { MessageTypeIndex = 1, MessageDescriptionIndex }; + const Task::TaskType type = taskType(match.captured(MessageTypeIndex)); + const QString descr = match.captured(MessageDescriptionIndex); + const Task task(type, descr, {}, -1, Constants::TASK_CATEGORY_COMPILE); + newTask(task); + return; + } + + re.setPattern("^(A|C)51 FATAL[ |-]ERROR"); + match = re.match(lne); + if (match.hasMatch()) { + const QString key = match.captured(1); + QString descr; + if (key == QLatin1Char('A')) + descr = "Assembler fatal error"; + else if (key == QLatin1Char('C')) + descr = "Compiler fatal error"; + const Task task(Task::TaskType::Error, descr, {}, -1, + Constants::TASK_CATEGORY_COMPILE); + newTask(task); + return; + } + + if (lne.startsWith(QLatin1Char(' '))) { + amendDescription(lne); + return; + } + + doFlush(); +} + +void KeilParser::doFlush() +{ + if (m_lastTask.isNull()) + return; + + Task t = m_lastTask; + m_lastTask.clear(); + emit addTask(t, m_lines, 1); + m_lines = 0; +} + +} // namespace Internal +} // namespace BareMetal + +// Unit tests: + +#ifdef WITH_TESTS +#include "baremetalplugin.h" +#include <projectexplorer/outputparser_test.h> +#include <QTest> + +namespace BareMetal { +namespace Internal { + +void BareMetalPlugin::testKeilOutputParsers_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") + << "Sometext" << OutputParserTester::STDOUT + << "Sometext\n" << QString() + << QList<Task>() + << QString(); + QTest::newRow("pass-through stderr") + << "Sometext" << OutputParserTester::STDERR + << QString() << "Sometext\n" + << QList<Task>() + << QString(); + + const Core::Id categoryCompile = Constants::TASK_CATEGORY_COMPILE; + + // ARM compiler specific patterns. + + QTest::newRow("ARM: No details warning") + << QString::fromLatin1("\"c:\\foo\\main.c\", line 63: Warning: #1234: Some warning") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"c:\\foo\\main.c\", line 63: Warning: #1234: Some warning\n") + << (QList<Task>() << Task(Task::Warning, + QLatin1String("#1234: Some warning"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("ARM: Details warning") + << QString::fromLatin1("\"c:\\foo\\main.c\", line 63: Warning: #1234: Some warning\n" + " int f;\n" + " ^") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"c:\\foo\\main.c\", line 63: Warning: #1234: Some warning\n" + " int f;\n" + " ^\n") + << (QList<Task>() << Task(Task::Warning, + QLatin1String("#1234: Some warning\n" + " int f;\n" + " ^"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("ARM: No details error") + << QString::fromLatin1("\"c:\\foo\\main.c\", line 63: Error: #1234: Some error") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"c:\\foo\\main.c\", line 63: Error: #1234: Some error\n") + << (QList<Task>() << Task(Task::Error, + QLatin1String("#1234: Some error"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("ARM: No details error with column") + << QString::fromLatin1("\"flash.sct\", line 51 (column 20): Error: L1234: Some error") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"flash.sct\", line 51 (column 20): Error: L1234: Some error\n") + << (QList<Task>() << Task(Task::Error, + QLatin1String("L1234: Some error"), + Utils::FileName::fromUserInput(QLatin1String("flash.sct")), + 51, + categoryCompile)) + << QString(); + + QTest::newRow("ARM: Details error") + << QString::fromLatin1("\"c:\\foo\\main.c\", line 63: Error: #1234: Some error\n" + " int f;\n" + " ^") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"c:\\foo\\main.c\", line 63: Error: #1234: Some error\n" + " int f;\n" + " ^\n") + << (QList<Task>() << Task(Task::Error, + QLatin1String("#1234: Some error\n" + " int f;\n" + " ^"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("ARM: At end of source") + << QString::fromLatin1("\"c:\\foo\\main.c\", line 71: Error: At end of source: #40: Some error") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"c:\\foo\\main.c\", line 71: Error: At end of source: #40: Some error\n") + << (QList<Task>() << Task(Task::Error, + QLatin1String("#40: Some error"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 71, + categoryCompile)) + << QString(); + + QTest::newRow("ARM: Starts with error") + << QString::fromLatin1("Error: L6226E: Some error.") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("Error: L6226E: Some error.\n") + << (QList<Task>() << Task(Task::Error, + QLatin1String("L6226E: Some error."), + Utils::FileName(), + -1, + categoryCompile)) + << QString(); + + // MCS51 compiler specific patterns. + + // Assembler messages. + QTest::newRow("MCS51: Assembler simple warning") + << QString::fromLatin1("*** WARNING #A9 IN 15 (c:\\foo\\dscr.a51, LINE 15): Some warning") + << OutputParserTester::STDOUT + << QString::fromLatin1("*** WARNING #A9 IN 15 (c:\\foo\\dscr.a51, LINE 15): Some warning\n") + << QString() + << (QList<Task>() << Task(Task::Warning, + QLatin1String("#A9: Some warning"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\dscr.a51")), + 15, + categoryCompile)) + << QString(); + + QTest::newRow("MCS51: Assembler simple error") + << QString::fromLatin1("*** ERROR #A9 IN 15 (c:\\foo\\dscr.a51, LINE 15): Some error") + << OutputParserTester::STDOUT + << QString::fromLatin1("*** ERROR #A9 IN 15 (c:\\foo\\dscr.a51, LINE 15): Some error\n") + << QString() + << (QList<Task>() << Task(Task::Error, + QLatin1String("#A9: Some error"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\dscr.a51")), + 15, + categoryCompile)) + << QString(); + + QTest::newRow("MCS51: Assembler fatal error") + << QString::fromLatin1("A51 FATAL ERROR -\n" + " Some detail 1\n" + " Some detail N") + << OutputParserTester::STDOUT + << QString::fromLatin1("A51 FATAL ERROR -\n" + " Some detail 1\n" + " Some detail N\n") + << QString() + << (QList<Task>() << Task(Task::Error, + QLatin1String("Assembler fatal error\n" + " Some detail 1\n" + " Some detail N"), + Utils::FileName(), + -1, + categoryCompile)) + << QString(); + + // Compiler messages. + QTest::newRow("MCS51: Compiler simple warning") + << QString::fromLatin1("*** WARNING C123 IN LINE 13 OF c:\\foo.c: Some warning") + << OutputParserTester::STDOUT + << QString::fromLatin1("*** WARNING C123 IN LINE 13 OF c:\\foo.c: Some warning\n") + << QString() + << (QList<Task>() << Task(Task::Warning, + QLatin1String("C123: Some warning"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo.c")), + 13, + categoryCompile)) + << QString(); + + QTest::newRow("MCS51: Compiler extended warning") + << QString::fromLatin1("*** WARNING C123 IN LINE 13 OF c:\\foo.c: Some warning : 'extended text'") + << OutputParserTester::STDOUT + << QString::fromLatin1("*** WARNING C123 IN LINE 13 OF c:\\foo.c: Some warning : 'extended text'\n") + << QString() + << (QList<Task>() << Task(Task::Warning, + QLatin1String("C123: Some warning : 'extended text'"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo.c")), + 13, + categoryCompile)) + << QString(); + + QTest::newRow("MCS51: Compiler simple error") + << QString::fromLatin1("*** ERROR C123 IN LINE 13 OF c:\\foo.c: Some error") + << OutputParserTester::STDOUT + << QString::fromLatin1("*** ERROR C123 IN LINE 13 OF c:\\foo.c: Some error\n") + << QString() + << (QList<Task>() << Task(Task::Error, + QLatin1String("C123: Some error"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo.c")), + 13, + categoryCompile)) + << QString(); + + QTest::newRow("MCS51: Compiler extended error") + << QString::fromLatin1("*** ERROR C123 IN LINE 13 OF c:\\foo.c: Some error : 'extended text'") + << OutputParserTester::STDOUT + << QString::fromLatin1("*** ERROR C123 IN LINE 13 OF c:\\foo.c: Some error : 'extended text'\n") + << QString() + << (QList<Task>() << Task(Task::Error, + QLatin1String("C123: Some error : 'extended text'"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo.c")), + 13, + categoryCompile)) + << QString(); + + QTest::newRow("MCS51: Compiler fatal error") + << QString::fromLatin1("C51 FATAL-ERROR -\n" + " Some detail 1\n" + " Some detail N") + << OutputParserTester::STDOUT + << QString::fromLatin1("C51 FATAL-ERROR -\n" + " Some detail 1\n" + " Some detail N\n") + << QString() + << (QList<Task>() << Task(Task::Error, + QLatin1String("Compiler fatal error\n" + " Some detail 1\n" + " Some detail N"), + Utils::FileName(), + -1, + categoryCompile)) + << QString(); + + // Linker messages. + QTest::newRow("MCS51: Linker simple fatal error") + << QString::fromLatin1("*** FATAL ERROR L456: Some error") + << OutputParserTester::STDOUT + << QString::fromLatin1("*** FATAL ERROR L456: Some error\n") + << QString() + << (QList<Task>() << Task(Task::Error, + QLatin1String("L456: Some error"), + Utils::FileName(), + -1, + categoryCompile)) + << QString(); + + QTest::newRow("MCS51: Linker extended fatal error") + << QString::fromLatin1("*** FATAL ERROR L456: Some error\n" + " Some detail 1\n" + " Some detail N") + << OutputParserTester::STDOUT + << QString::fromLatin1("*** FATAL ERROR L456: Some error\n" + " Some detail 1\n" + " Some detail N\n") + << QString() + << (QList<Task>() << Task(Task::Error, + QLatin1String("L456: Some error\n" + " Some detail 1\n" + " Some detail N"), + Utils::FileName(), + -1, + categoryCompile)) + << QString(); +} + +void BareMetalPlugin::testKeilOutputParsers() +{ + OutputParserTester testbench; + testbench.appendOutputParser(new KeilParser); + 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); +} + +} // namespace Internal +} // namespace BareMetal + +#endif // WITH_TESTS diff --git a/src/plugins/baremetal/keilparser.h b/src/plugins/baremetal/keilparser.h new file mode 100644 index 00000000000..16b88f1cc85 --- /dev/null +++ b/src/plugins/baremetal/keilparser.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov <denis.shienkov@gmail.com> +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <projectexplorer/ioutputparser.h> +#include <projectexplorer/task.h> + +namespace BareMetal { +namespace Internal { + +// KeilParser + +class KeilParser final : public ProjectExplorer::IOutputParser +{ + Q_OBJECT + +public: + KeilParser(); + static Core::Id id(); + +private: + void newTask(const ProjectExplorer::Task &task); + void amendDescription(const QString &desc); + + void stdError(const QString &line) override; + void stdOutput(const QString &line) override; + void doFlush() override; + + ProjectExplorer::Task m_lastTask; + int m_lines = 0; +}; + +} // namespace Internal +} // namespace BareMetal diff --git a/src/plugins/baremetal/keiltoolchain.cpp b/src/plugins/baremetal/keiltoolchain.cpp index 94cb1b56bab..647fbb8f133 100644 --- a/src/plugins/baremetal/keiltoolchain.cpp +++ b/src/plugins/baremetal/keiltoolchain.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "baremetalconstants.h" +#include "keilparser.h" #include "keiltoolchain.h" #include <projectexplorer/abiwidget.h> @@ -342,7 +343,7 @@ void KeilToolchain::addToEnvironment(Environment &env) const IOutputParser *KeilToolchain::outputParser() const { - return nullptr; + return new KeilParser; } QVariantMap KeilToolchain::toMap() const |