diff --git a/src/plugins/baremetal/baremetal.pro b/src/plugins/baremetal/baremetal.pro index c4c1ee217c2..d69f2eaec3c 100644 --- a/src/plugins/baremetal/baremetal.pro +++ b/src/plugins/baremetal/baremetal.pro @@ -22,6 +22,7 @@ SOURCES += baremetalplugin.cpp \ stlinkutilgdbserverprovider.cpp \ iarewtoolchain.cpp \ keiltoolchain.cpp \ + iarewparser.cpp \ HEADERS += baremetalplugin.h \ baremetalconstants.h \ @@ -43,6 +44,7 @@ HEADERS += baremetalplugin.h \ stlinkutilgdbserverprovider.h \ iarewtoolchain.h \ keiltoolchain.h \ + iarewparser.h \ RESOURCES += \ baremetal.qrc diff --git a/src/plugins/baremetal/baremetal.qbs b/src/plugins/baremetal/baremetal.qbs index 52dab017bea..77de2188dac 100644 --- a/src/plugins/baremetal/baremetal.qbs +++ b/src/plugins/baremetal/baremetal.qbs @@ -33,5 +33,6 @@ QtcPlugin { "stlinkutilgdbserverprovider.cpp", "stlinkutilgdbserverprovider.h", "iarewtoolchain.cpp", "iarewtoolchain.h", "keiltoolchain.cpp", "keiltoolchain.h", + "iarewparser.cpp", "iarewparser.h", ] } diff --git a/src/plugins/baremetal/baremetalplugin.h b/src/plugins/baremetal/baremetalplugin.h index 7824b43a10c..fb9a0bd4318 100644 --- a/src/plugins/baremetal/baremetalplugin.h +++ b/src/plugins/baremetal/baremetalplugin.h @@ -42,6 +42,12 @@ class BareMetalPlugin : public ExtensionSystem::IPlugin void extensionsInitialized() final; class BareMetalPluginPrivate *d; + +#ifdef WITH_TESTS +private slots: + void testIarOutputParsers_data(); + void testIarOutputParsers(); +#endif // WITH_TESTS }; } // namespace Internal diff --git a/src/plugins/baremetal/iarewparser.cpp b/src/plugins/baremetal/iarewparser.cpp new file mode 100644 index 00000000000..42a6b74d1e1 --- /dev/null +++ b/src/plugins/baremetal/iarewparser.cpp @@ -0,0 +1,446 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 "iarewparser.h" + +#include +#include + +#include +#include + +#include + +using namespace ProjectExplorer; + +namespace BareMetal { +namespace Internal { + +static Task::TaskType taskType(const QString &msgType) +{ + if (msgType == "Warning") + return Task::TaskType::Warning; + else if (msgType == "Error" || msgType == "Fatal error") + return Task::TaskType::Error; + return Task::TaskType::Unknown; +} + +IarParser::IarParser() +{ + setObjectName("IarParser"); +} + +Core::Id IarParser::id() +{ + return "BareMetal.OutputParser.Iar"; +} + +void IarParser::newTask(const Task &task) +{ + doFlush(); + m_lastTask = task; + m_lines = 1; +} + +void IarParser::amendDescription() +{ + while (!m_descriptionParts.isEmpty()) + m_lastTask.description.append(m_descriptionParts.takeFirst()); + + while (!m_snippets.isEmpty()) { + const QString snippet = m_snippets.takeFirst(); + const int start = m_lastTask.description.count() + 1; + m_lastTask.description.append(QLatin1Char('\n')); + m_lastTask.description.append(snippet); + + 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 IarParser::amendFilePath() +{ + if (m_filePathParts.isEmpty()) + return; + QString filePath; + while (!m_filePathParts.isEmpty()) + filePath.append(m_filePathParts.takeFirst().trimmed()); + m_lastTask.file = Utils::FileName::fromUserInput(filePath); + m_expectFilePath = false; +} + +void IarParser::stdError(const QString &line) +{ + IOutputParser::stdError(line); + + const QString lne = rightTrimmed(line); + + QRegularExpression re; + QRegularExpressionMatch match; + + re.setPattern("^(Error|Fatal error)\\[(.+)\\]:\\s(.+)\\s\\[(.+)$"); + match = re.match(lne); + if (match.hasMatch()) { + enum CaptureIndex { MessageTypeIndex = 1, MessageCodeIndex, + DescriptionIndex, FilepathBeginIndex }; + const Task::TaskType type = taskType(match.captured(MessageTypeIndex)); + const QString descr = QString("[%1]: %2").arg(match.captured(MessageCodeIndex), + match.captured(DescriptionIndex)); + // This task has a file path, but this patch are split on + // some lines, which will be received later. + const Task task(type, descr, {}, -1, Constants::TASK_CATEGORY_COMPILE); + newTask(task); + // Prepare first part of a file path. + QString firstPart = match.captured(FilepathBeginIndex); + firstPart.remove("referenced from "); + m_filePathParts.push_back(firstPart); + m_expectFilePath = true; + m_expectSnippet = false; + return; + } + + re.setPattern("^.*(Error|Fatal error)\\[(.+)\\]:\\s(.+)$"); + match = re.match(lne); + if (match.hasMatch()) { + enum CaptureIndex { MessageTypeIndex = 1, MessageCodeIndex, + DescriptionIndex }; + const Task::TaskType type = taskType(match.captured(MessageTypeIndex)); + const QString descr = QString("[%1]: %2").arg(match.captured(MessageCodeIndex), + match.captured(DescriptionIndex)); + // This task has not a file path. The description details + // will be received later on the next lines. + const Task task(type, descr, {}, -1, Constants::TASK_CATEGORY_COMPILE); + newTask(task); + m_expectSnippet = true; + m_expectFilePath = false; + m_expectDescription = false; + return; + } + + re.setPattern("^\"(.+)\",(\\d+)?\\s+(Warning|Error|Fatal error)\\[(.+)\\].+$"); + match = re.match(lne); + if (match.hasMatch()) { + enum CaptureIndex { FilePathIndex = 1, LineNumberIndex, + MessageTypeIndex, MessageCodeIndex }; + 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)); + // A full description will be received later on next lines. + const Task task(type, {}, fileName, lineno, Constants::TASK_CATEGORY_COMPILE); + newTask(task); + const QString firstPart = QString("[%1]: ").arg(match.captured(MessageCodeIndex)); + m_descriptionParts.append(firstPart); + m_expectDescription = true; + m_expectSnippet = false; + m_expectFilePath = false; + return; + } + + if (lne.isEmpty()) { + // + } else if (!lne.startsWith(QLatin1Char(' '))) { + return; + } else if (m_expectFilePath) { + if (lne.endsWith(QLatin1Char(']'))) { + const QString lastPart = lne.left(lne.size() - 1); + m_filePathParts.push_back(lastPart); + } else { + m_filePathParts.push_back(lne); + return; + } + } else if (m_expectSnippet) { + if (!lne.endsWith("Fatal error detected, aborting.")) { + m_snippets.push_back(lne); + return; + } + } else if (m_expectDescription) { + if (!lne.startsWith(" ")) { + m_descriptionParts.push_back(lne.trimmed()); + return; + } + } + + doFlush(); +} + +void IarParser::stdOutput(const QString &line) +{ + IOutputParser::stdOutput(line); + + const QString lne = rightTrimmed(line); + if (!lne.startsWith("Error in command line")) + return; + + const Task task(Task::TaskType::Error, line.trimmed(), {}, + -1, Constants::TASK_CATEGORY_COMPILE); + newTask(task); + doFlush(); +} + +void IarParser::doFlush() +{ + if (m_lastTask.isNull()) + return; + + amendDescription(); + amendFilePath(); + + m_expectSnippet = true; + m_expectFilePath = false; + m_expectDescription = false; + + 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 +#include + +namespace BareMetal { +namespace Internal { + +void BareMetalPlugin::testIarOutputParsers_data() +{ + QTest::addColumn("input"); + QTest::addColumn("inputChannel"); + QTest::addColumn("childStdOutLines"); + QTest::addColumn("childStdErrLines"); + QTest::addColumn >("tasks"); + QTest::addColumn("outputLines"); + + QTest::newRow("pass-through stdout") + << "Sometext" << OutputParserTester::STDOUT + << "Sometext\n" << QString() + << QList() + << QString(); + QTest::newRow("pass-through stderr") + << "Sometext" << OutputParserTester::STDERR + << QString() << "Sometext\n" + << QList() + << QString(); + + const Core::Id categoryCompile = Constants::TASK_CATEGORY_COMPILE; + + // For std out. + QTest::newRow("Error in command line") + << QString::fromLatin1("Error in command line: Some error") + << OutputParserTester::STDOUT + << QString::fromLatin1("Error in command line: Some error\n") + << QString() + << (QList() << Task(Task::Error, + QLatin1String("Error in command line: Some error"), + Utils::FileName(), + -1, + categoryCompile)) + << QString(); + + // For std error. + QTest::newRow("No details warning") + << QString::fromLatin1("\"c:\\foo\\main.c\",63 Warning[Pe223]:\n" + " Some warning \"foo\" bar") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"c:\\foo\\main.c\",63 Warning[Pe223]:\n" + " Some warning \"foo\" bar\n") + << (QList() << Task(Task::Warning, + QLatin1String("[Pe223]: Some warning \"foo\" bar"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("Details warning") + << QString::fromLatin1(" some_detail;\n" + " ^\n" + "\"c:\\foo\\main.c\",63 Warning[Pe223]:\n" + " Some warning") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1(" some_detail;\n" + " ^\n" + "\"c:\\foo\\main.c\",63 Warning[Pe223]:\n" + " Some warning\n") + << (QList() << Task(Task::Warning, + QLatin1String("[Pe223]: Some warning\n" + " some_detail;\n" + " ^"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("No details split-description warning") + << QString::fromLatin1("\"c:\\foo\\main.c\",63 Warning[Pe223]:\n" + " Some warning\n" + " , split") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"c:\\foo\\main.c\",63 Warning[Pe223]:\n" + " Some warning\n" + " , split\n") + << (QList() << Task(Task::Warning, + QLatin1String("[Pe223]: Some warning, split"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("No details error") + << QString::fromLatin1("\"c:\\foo\\main.c\",63 Error[Pe223]:\n" + " Some error") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"c:\\foo\\main.c\",63 Error[Pe223]:\n" + " Some error\n") + << (QList() << Task(Task::Error, + QLatin1String("[Pe223]: Some error"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("Details error") + << QString::fromLatin1(" some_detail;\n" + " ^\n" + "\"c:\\foo\\main.c\",63 Error[Pe223]:\n" + " Some error") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1(" some_detail;\n" + " ^\n" + "\"c:\\foo\\main.c\",63 Error[Pe223]:\n" + " Some error\n") + << (QList() << Task(Task::Error, + QLatin1String("[Pe223]: Some error\n" + " some_detail;\n" + " ^"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("No details split-description error") + << QString::fromLatin1("\"c:\\foo\\main.c\",63 Error[Pe223]:\n" + " Some error\n" + " , split") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("\"c:\\foo\\main.c\",63 Error[Pe223]:\n" + " Some error\n" + " , split\n") + << (QList() << Task(Task::Error, + QLatin1String("[Pe223]: Some error, split"), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")), + 63, + categoryCompile)) + << QString(); + + QTest::newRow("No definition for") + << QString::fromLatin1("Error[Li005]: Some error \"foo\" [referenced from c:\\fo\n" + " o\\bar\\mai\n" + " n.c.o\n" + "]") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("Error[Li005]: Some error \"foo\" [referenced from c:\\fo\n" + " o\\bar\\mai\n" + " n.c.o\n" + "]\n") + << (QList() << Task(Task::Error, + QLatin1String("[Li005]: Some error \"foo\""), + Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\bar\\main.c.o")), + -1, + categoryCompile)) + << QString(); + + QTest::newRow("More than one source file specified") + << QString::fromLatin1("Fatal error[Su011]: Some error:\n" + " c:\\foo.c\n" + " c:\\bar.c\n" + "Fatal error detected, aborting.") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("Fatal error[Su011]: Some error:\n" + " c:\\foo.c\n" + " c:\\bar.c\n" + "Fatal error detected, aborting.\n") + << (QList() << Task(Task::Error, + QLatin1String("[Su011]: Some error:\n" + " c:\\foo.c\n" + " c:\\bar.c"), + Utils::FileName(), + -1, + categoryCompile)) + << QString(); + + QTest::newRow("At end of source") + << QString::fromLatin1("At end of source Error[Pe040]: Some error \";\"") + << OutputParserTester::STDERR + << QString() + << QString::fromLatin1("At end of source Error[Pe040]: Some error \";\"\n") + << (QList() << Task(Task::Error, + QLatin1String("[Pe040]: Some error \";\""), + Utils::FileName(), + -1, + categoryCompile)) + << QString(); +} + +void BareMetalPlugin::testIarOutputParsers() +{ + OutputParserTester testbench; + testbench.appendOutputParser(new IarParser); + QFETCH(QString, input); + QFETCH(OutputParserTester::Channel, inputChannel); + QFETCH(QList, 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/iarewparser.h b/src/plugins/baremetal/iarewparser.h new file mode 100644 index 00000000000..bedaf45964a --- /dev/null +++ b/src/plugins/baremetal/iarewparser.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2019 Denis Shienkov +** 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 +#include + +namespace BareMetal { +namespace Internal { + +// IarParser + +class IarParser final : public ProjectExplorer::IOutputParser +{ + Q_OBJECT + +public: + IarParser(); + static Core::Id id(); + +private: + void newTask(const ProjectExplorer::Task &task); + void amendDescription(); + void amendFilePath(); + + void stdError(const QString &line) override; + void stdOutput(const QString &line) override; + void doFlush() override; + + ProjectExplorer::Task m_lastTask; + int m_lines = 0; + bool m_expectSnippet = true; + bool m_expectFilePath = false; + bool m_expectDescription = false; + QStringList m_snippets; + QStringList m_filePathParts; + QStringList m_descriptionParts; +}; + +} // namespace Internal +} // namespace BareMetal diff --git a/src/plugins/baremetal/iarewtoolchain.cpp b/src/plugins/baremetal/iarewtoolchain.cpp index 4a60e8f81c1..b8392f5cdf3 100644 --- a/src/plugins/baremetal/iarewtoolchain.cpp +++ b/src/plugins/baremetal/iarewtoolchain.cpp @@ -24,6 +24,7 @@ ****************************************************************************/ #include "baremetalconstants.h" +#include "iarewparser.h" #include "iarewtoolchain.h" #include @@ -330,7 +331,7 @@ void IarToolChain::addToEnvironment(Environment &env) const IOutputParser *IarToolChain::outputParser() const { - return nullptr; + return new IarParser; } QVariantMap IarToolChain::toMap() const