bare-metal: Add command line parser for SDCC toolchain

This commit implements a parser which handles an often encountered
compiler warnings and errors; a missed warnings and errors can be
added later.

Change-Id: Ie5cdd1193dae3ebed64d4f78316ae4175bdb0b22
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Denis Shienkov
2019-05-07 14:25:21 +03:00
parent d0bb7ee721
commit 6c87ddcea1
6 changed files with 399 additions and 1 deletions

View File

@@ -25,6 +25,7 @@ SOURCES += baremetalplugin.cpp \
sdcctoolchain.cpp \
iarewparser.cpp \
keilparser.cpp \
sdccparser.cpp \
HEADERS += baremetalplugin.h \
baremetalconstants.h \
@@ -49,6 +50,7 @@ HEADERS += baremetalplugin.h \
sdcctoolchain.h \
iarewparser.h \
keilparser.h \
sdccparser.h \
RESOURCES += \
baremetal.qrc

View File

@@ -37,5 +37,6 @@ QtcPlugin {
"sdcctoolchain.cpp", "sdcctoolchain.h",
"iarewparser.cpp", "iarewparser.h",
"keilparser.cpp", "keilparser.h",
"sdccparser.cpp", "sdccparser.h",
]
}

View File

@@ -49,6 +49,8 @@ private slots:
void testIarOutputParsers();
void testKeilOutputParsers_data();
void testKeilOutputParsers();
void testSdccOutputParsers_data();
void testSdccOutputParsers();
#endif // WITH_TESTS
};

View File

@@ -0,0 +1,335 @@
/****************************************************************************
**
** 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 "sdccparser.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 == "syntax error") {
return Task::TaskType::Error;
}
return Task::TaskType::Unknown;
}
SdccParser::SdccParser()
{
setObjectName("SdccParser");
}
Core::Id SdccParser::id()
{
return "BareMetal.OutputParser.Sdcc";
}
void SdccParser::newTask(const Task &task)
{
doFlush();
m_lastTask = task;
m_lines = 1;
}
void SdccParser::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 SdccParser::stdError(const QString &line)
{
IOutputParser::stdError(line);
const QString lne = rightTrimmed(line);
QRegularExpression re;
QRegularExpressionMatch match;
re.setPattern("^(.+\\.\\S+):(\\d+): (warning|error) (\\d+): (.+)$");
match = re.match(lne);
if (match.hasMatch()) {
enum CaptureIndex { FilePathIndex = 1, LineNumberIndex,
MessageTypeIndex, MessageCodeIndex, MessageTextIndex };
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(MessageTextIndex);
const Task task(type, descr, fileName, lineno, Constants::TASK_CATEGORY_COMPILE);
newTask(task);
return;
}
re.setPattern("^(.+\\.\\S+):(\\d+): (syntax error): (.+)$");
match = re.match(lne);
if (match.hasMatch()) {
enum CaptureIndex { FilePathIndex = 1, LineNumberIndex,
MessageTypeIndex, MessageTextIndex };
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(MessageTextIndex);
const Task task(type, descr, fileName, lineno, Constants::TASK_CATEGORY_COMPILE);
newTask(task);
return;
}
re.setPattern("^at (\\d+): (error) \\d+: (.+)$");
match = re.match(lne);
if (match.hasMatch()) {
enum CaptureIndex { MessageCodeIndex = 1, MessageTypeIndex, MessageTextIndex };
const Task::TaskType type = taskType(match.captured(MessageTypeIndex));
const QString descr = match.captured(MessageTextIndex);
const Task task(type, descr, {}, -1, Constants::TASK_CATEGORY_COMPILE);
newTask(task);
return;
}
re.setPattern("^\\?ASlink-(Warning|Error)-(.+)$");
match = re.match(lne);
if (match.hasMatch()) {
enum CaptureIndex { MessageTypeIndex = 1, MessageTextIndex };
const Task::TaskType type = taskType(match.captured(MessageTypeIndex));
const QString descr = match.captured(MessageTextIndex);
const Task task(type, descr, {}, -1, Constants::TASK_CATEGORY_COMPILE);
newTask(task);
return;
}
if (!m_lastTask.isNull()) {
amendDescription(lne);
return;
}
doFlush();
}
void SdccParser::stdOutput(const QString &line)
{
IOutputParser::stdOutput(line);
}
void SdccParser::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::testSdccOutputParsers_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;
// Compiler messages.
QTest::newRow("Compiler single line warning")
<< QString::fromLatin1("c:\\foo\\main.c:63: warning 123: Some warning")
<< OutputParserTester::STDERR
<< QString()
<< QString::fromLatin1("c:\\foo\\main.c:63: warning 123: Some warning\n")
<< (QList<Task>() << Task(Task::Warning,
QLatin1String("Some warning"),
Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")),
63,
categoryCompile))
<< QString();
QTest::newRow("Compiler multi line warning")
<< QString::fromLatin1("c:\\foo\\main.c:63: warning 123: Some warning\n"
"details #1\n"
" details #2")
<< OutputParserTester::STDERR
<< QString()
<< QString::fromLatin1("c:\\foo\\main.c:63: warning 123: Some warning\n"
"details #1\n"
" details #2\n")
<< (QList<Task>() << Task(Task::Warning,
QLatin1String("Some warning\n"
"details #1\n"
" details #2"),
Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")),
63,
categoryCompile))
<< QString();
QTest::newRow("Compiler single line error")
<< QString::fromLatin1("c:\\foo\\main.c:63: error 123: Some error")
<< OutputParserTester::STDERR
<< QString()
<< QString::fromLatin1("c:\\foo\\main.c:63: error 123: Some error\n")
<< (QList<Task>() << Task(Task::Error,
QLatin1String("Some error"),
Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")),
63,
categoryCompile))
<< QString();
QTest::newRow("Compiler multi line error")
<< QString::fromLatin1("c:\\foo\\main.c:63: error 123: Some error\n"
"details #1\n"
" details #2")
<< OutputParserTester::STDERR
<< QString()
<< QString::fromLatin1("c:\\foo\\main.c:63: error 123: Some error\n"
"details #1\n"
" details #2\n")
<< (QList<Task>() << Task(Task::Error,
QLatin1String("Some error\n"
"details #1\n"
" details #2"),
Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")),
63,
categoryCompile))
<< QString();
QTest::newRow("Compiler syntax error")
<< QString::fromLatin1("c:\\foo\\main.c:63: syntax error: Some error")
<< OutputParserTester::STDERR
<< QString()
<< QString::fromLatin1("c:\\foo\\main.c:63: syntax error: Some error\n")
<< (QList<Task>() << Task(Task::Error,
QLatin1String("Some error"),
Utils::FileName::fromUserInput(QLatin1String("c:\\foo\\main.c")),
63,
categoryCompile))
<< QString();
QTest::newRow("Compiler bad option error")
<< QString::fromLatin1("at 1: error 123: Some error")
<< OutputParserTester::STDERR
<< QString()
<< QString::fromLatin1("at 1: error 123: Some error\n")
<< (QList<Task>() << Task(Task::Error,
QLatin1String("Some error"),
Utils::FileName(),
-1,
categoryCompile))
<< QString();
QTest::newRow("Linker warning")
<< QString::fromLatin1("?ASlink-Warning-Couldn't find library 'foo.lib'")
<< OutputParserTester::STDERR
<< QString()
<< QString::fromLatin1("?ASlink-Warning-Couldn't find library 'foo.lib'\n")
<< (QList<Task>() << Task(Task::Warning,
QLatin1String("Couldn't find library 'foo.lib'"),
Utils::FileName(),
-1,
categoryCompile))
<< QString();
QTest::newRow("Linker error")
<< QString::fromLatin1("?ASlink-Error-<cannot open> : \"foo.rel\"")
<< OutputParserTester::STDERR
<< QString()
<< QString::fromLatin1("?ASlink-Error-<cannot open> : \"foo.rel\"\n")
<< (QList<Task>() << Task(Task::Error,
QLatin1String("<cannot open> : \"foo.rel\""),
Utils::FileName(),
-1,
categoryCompile))
<< QString();
}
void BareMetalPlugin::testSdccOutputParsers()
{
OutputParserTester testbench;
testbench.appendOutputParser(new SdccParser);
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

View File

@@ -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 {
// SdccParser
class SdccParser final : public ProjectExplorer::IOutputParser
{
Q_OBJECT
public:
SdccParser();
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

View File

@@ -24,6 +24,7 @@
****************************************************************************/
#include "baremetalconstants.h"
#include "sdccparser.h"
#include "sdcctoolchain.h"
#include <projectexplorer/abiwidget.h>
@@ -327,7 +328,7 @@ void SdccToolChain::addToEnvironment(Environment &env) const
IOutputParser *SdccToolChain::outputParser() const
{
return nullptr;
return new SdccParser;
}
QVariantMap SdccToolChain::toMap() const