Commit 61a87799 authored by Andre Hartmann's avatar Andre Hartmann Committed by André Hartmann

CustomParser: Add warning parser and output channel filter

There have been several requests on the mailing list or the
bug tracker to support parsing warnings for alien compilers
(sometimes slightly modified GCC).

Instead of natively supporting every compiler, users of less
frequently used compilers should use the custom parser to
parse errors and warnings.

The output channel filter for error and warning parser allows
to scan standard output, standard error or both channels.

Also added tests for twisted capture positions.

Task-number: QTCREATORBUG-11003
Change-Id: I5a5bd6f88cf21cde1c74962225067d4543693678
Reviewed-by: default avatarTobias Hunger <tobias.hunger@theqtcompany.com>
parent 5a7d3e78
......@@ -150,6 +150,10 @@
\image qtcreator-custom-parser.png
The custom error parser enables you to capture errors and warnings separately.
You can configure the error parser in the \uicontrol Error tab and the warning
parser in the \uicontrol Warning tab:
\list 1
\li In the \uicontrol {Error message capture pattern} field, specify
......@@ -163,6 +167,10 @@
expression groups to \uicontrol {File name}, \uicontrol {Line number},
and \uicontrol Message.
\li In the \uicontrol {Capture Output Channels} field, specify whether
messages from standard output, standard error, or both channels
should be captured.
\li In the \uicontrol {Test} group, you can test how the message that
you enter in the \uicontrol {Error message} field is matched when
using the current settings.
......
......@@ -40,115 +40,147 @@
using namespace Utils;
using namespace ProjectExplorer;
CustomParserSettings::CustomParserSettings() :
fileNameCap(1),
lineNumberCap(2),
messageCap(3)
{ }
bool CustomParserExpression::operator ==(const CustomParserExpression &other) const
{
return pattern() == other.pattern() && fileNameCap() == other.fileNameCap()
&& lineNumberCap() == other.lineNumberCap() && messageCap() == other.messageCap()
&& channel() == other.channel() && example() == other.example();
}
bool CustomParserSettings::operator ==(const CustomParserSettings &other) const
QString CustomParserExpression::pattern() const
{
return errorPattern == other.errorPattern && fileNameCap == other.fileNameCap
&& lineNumberCap == other.lineNumberCap && messageCap == other.messageCap;
return m_regExp.pattern();
}
CustomParser::CustomParser(const CustomParserSettings &settings) :
m_parserChannels(ParseBothChannels)
void ProjectExplorer::CustomParserExpression::setPattern(const QString &pattern)
{
setObjectName(QLatin1String("CustomParser"));
m_regExp.setPattern(pattern);
QTC_CHECK(m_regExp.isValid());
}
setSettings(settings);
CustomParserExpression::CustomParserChannel CustomParserExpression::channel() const
{
return m_channel;
}
CustomParser::~CustomParser()
void CustomParserExpression::setChannel(CustomParserExpression::CustomParserChannel channel)
{
QTC_ASSERT(channel > ParseNoChannel && channel <= ParseBothChannels,
channel = ParseBothChannels);
m_channel = channel;
}
void CustomParser::setErrorPattern(const QString &errorPattern)
QString CustomParserExpression::example() const
{
m_errorRegExp.setPattern(errorPattern);
QTC_CHECK(m_errorRegExp.isValid());
return m_example;
}
QString CustomParser::errorPattern() const
void CustomParserExpression::setExample(const QString &example)
{
return m_errorRegExp.pattern();
m_example = example;
}
int CustomParser::lineNumberCap() const
int CustomParserExpression::messageCap() const
{
return m_messageCap;
}
void CustomParserExpression::setMessageCap(int messageCap)
{
m_messageCap = messageCap;
}
int CustomParserExpression::lineNumberCap() const
{
return m_lineNumberCap;
}
void CustomParser::setLineNumberCap(int lineNumberCap)
void CustomParserExpression::setLineNumberCap(int lineNumberCap)
{
m_lineNumberCap = lineNumberCap;
}
int CustomParser::fileNameCap() const
int CustomParserExpression::fileNameCap() const
{
return m_fileNameCap;
}
void CustomParser::setFileNameCap(int fileNameCap)
void CustomParserExpression::setFileNameCap(int fileNameCap)
{
m_fileNameCap = fileNameCap;
}
int CustomParser::messageCap() const
bool CustomParserSettings::operator ==(const CustomParserSettings &other) const
{
return m_messageCap;
return error == other.error && warning == other.warning;
}
void CustomParser::setMessageCap(int messageCap)
CustomParser::CustomParser(const CustomParserSettings &settings)
{
setObjectName(QLatin1String("CustomParser"));
setSettings(settings);
}
CustomParser::~CustomParser()
{
m_messageCap = messageCap;
}
void CustomParser::stdError(const QString &line)
{
if (m_parserChannels & ParseStdErrChannel)
if (parseLine(line))
return;
if (parseLine(line, CustomParserExpression::ParseStdErrChannel))
return;
IOutputParser::stdError(line);
}
void CustomParser::stdOutput(const QString &line)
{
if (m_parserChannels & ParseStdOutChannel)
if (parseLine(line))
return;
if (parseLine(line, CustomParserExpression::ParseStdOutChannel))
return;
IOutputParser::stdOutput(line);
}
void CustomParser::setSettings(const CustomParserSettings &settings)
{
setErrorPattern(settings.errorPattern);
setFileNameCap(settings.fileNameCap);
setLineNumberCap(settings.lineNumberCap);
setMessageCap(settings.messageCap);
m_error = settings.error;
m_warning = settings.warning;
}
bool CustomParser::parseLine(const QString &rawLine)
bool CustomParser::hasMatch(const QString &line, CustomParserExpression::CustomParserChannel channel,
const CustomParserExpression &expression, Task::TaskType taskType)
{
if (m_errorRegExp.pattern().isEmpty())
if (!(channel & expression.channel()))
return false;
if (expression.pattern().isEmpty())
return false;
const QRegularExpressionMatch match = m_errorRegExp.match(rawLine.trimmed());
const QRegularExpressionMatch match = expression.match(line);
if (!match.hasMatch())
return false;
const FileName fileName = FileName::fromUserInput(match.captured(m_fileNameCap));
const int lineNumber = match.captured(m_lineNumberCap).toInt();
const QString message = match.captured(m_messageCap);
const FileName fileName = FileName::fromUserInput(match.captured(expression.fileNameCap()));
const int lineNumber = match.captured(expression.lineNumberCap()).toInt();
const QString message = match.captured(expression.messageCap());
Task task = Task(Task::Error, message, fileName, lineNumber, Constants::TASK_CATEGORY_COMPILE);
const Task task = Task(taskType, message, fileName, lineNumber, Constants::TASK_CATEGORY_COMPILE);
emit addTask(task, 1);
return true;
}
bool CustomParser::parseLine(const QString &rawLine, CustomParserExpression::CustomParserChannel channel)
{
const QString line = rawLine.trimmed();
if (hasMatch(line, channel, m_error, Task::Error))
return true;
return hasMatch(line, channel, m_warning, Task::Warning);
}
// Unit tests:
#ifdef WITH_TESTS
......@@ -162,10 +194,16 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<OutputParserTester::Channel>("inputChannel");
QTest::addColumn<QString>("pattern");
QTest::addColumn<int>("fileNameCap");
QTest::addColumn<int>("lineNumberCap");
QTest::addColumn<int>("messageCap");
QTest::addColumn<CustomParserExpression::CustomParserChannel>("filterErrorChannel");
QTest::addColumn<CustomParserExpression::CustomParserChannel>("filterWarningChannel");
QTest::addColumn<QString>("errorPattern");
QTest::addColumn<int>("errorFileNameCap");
QTest::addColumn<int>("errorLineNumberCap");
QTest::addColumn<int>("errorMessageCap");
QTest::addColumn<QString>("warningPattern");
QTest::addColumn<int>("warningFileNameCap");
QTest::addColumn<int>("warningLineNumberCap");
QTest::addColumn<int>("warningMessageCap");
QTest::addColumn<QString>("childStdOutLines");
QTest::addColumn<QString>("childStdErrLines");
QTest::addColumn<QList<Task> >("tasks");
......@@ -175,11 +213,12 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
const QString simplePattern = QLatin1String("^([a-z]+\\.[a-z]+):(\\d+): error: ([^\\s].+)$");
const FileName fileName = FileName::fromUserInput(QLatin1String("main.c"));
QTest::newRow("empty pattern")
QTest::newRow("empty patterns")
<< QString::fromLatin1("Sometext")
<< OutputParserTester::STDOUT
<< QString::fromLatin1("")
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< QString() << 1 << 2 << 3
<< QString() << 1 << 2 << 3
<< QString::fromLatin1("Sometext\n") << QString()
<< QList<Task>()
<< QString();
......@@ -187,8 +226,9 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
QTest::newRow("pass-through stdout")
<< QString::fromLatin1("Sometext")
<< OutputParserTester::STDOUT
<< simplePattern
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 1 << 2 << 3
<< QString::fromLatin1("Sometext\n") << QString()
<< QList<Task>()
<< QString();
......@@ -196,35 +236,59 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
QTest::newRow("pass-through stderr")
<< QString::fromLatin1("Sometext")
<< OutputParserTester::STDERR
<< simplePattern
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 1 << 2 << 3
<< QString() << QString::fromLatin1("Sometext\n")
<< QList<Task>()
<< QString();
const QString simpleError = QLatin1String("main.c:9: error: `sfasdf' undeclared (first use this function)");
const QString simpleErrorPassThrough = simpleError + QLatin1Char('\n');
const QString message = QLatin1String("`sfasdf' undeclared (first use this function)");
QTest::newRow("simple error")
<< simpleError
<< OutputParserTester::STDERR
<< simplePattern
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 0 << 0 << 0
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, message, fileName, 9, categoryCompile)
)
<< QString();
const QString simpleError2 = QLatin1String("Error: main.c:19: `sfasdf' undeclared (first use this function)");
const QString simplePattern2 = QLatin1String("^Error: ([a-z]+\\.[a-z]+):(\\d+): ([^\\s].+)$");
QTest::newRow("simple error on wrong channel")
<< simpleError
<< OutputParserTester::STDOUT
<< CustomParserExpression::ParseStdErrChannel << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 0 << 0 << 0
<< simpleErrorPassThrough << QString()
<< QList<Task>()
<< QString();
QTest::newRow("simple error on other wrong channel")
<< simpleError
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseStdOutChannel << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< QString() << 0 << 0 << 0
<< QString() << simpleErrorPassThrough
<< QList<Task>()
<< QString();
const QString simpleError2 = QLatin1String("Error: Line 19 in main.c: `sfasdf' undeclared (first use this function)");
const QString simplePattern2 = QLatin1String("^Error: Line (\\d+) in ([a-z]+\\.[a-z]+): ([^\\s].+)$");
const int lineNumber2 = 19;
QTest::newRow("another simple error on stderr")
<< simpleError2
<< OutputParserTester::STDERR
<< simplePattern2
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern2 << 2 << 1 << 3
<< QString() << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, message, fileName, lineNumber2, categoryCompile)
......@@ -234,14 +298,91 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
QTest::newRow("another simple error on stdout")
<< simpleError2
<< OutputParserTester::STDOUT
<< simplePattern2
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern2 << 2 << 1 << 3
<< QString() << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, message, fileName, lineNumber2, categoryCompile)
)
<< QString();
const QString simpleWarningPattern = QLatin1String("^([a-z]+\\.[a-z]+):(\\d+): warning: ([^\\s].+)$");
const QString simpleWarning = QLatin1String("main.c:1234: warning: `helloWorld' declared but not used");
const QString warningMessage = QLatin1String("`helloWorld' declared but not used");
QTest::newRow("simple warning")
<< simpleWarning
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< QString() << 1 << 2 << 3
<< simpleWarningPattern << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Warning, warningMessage, fileName, 1234, categoryCompile)
)
<< QString();
const QString simpleWarning2 = QLatin1String("Warning: `helloWorld' declared but not used (main.c:19)");
const QString simpleWarningPassThrough2 = simpleWarning2 + QLatin1Char('\n');
const QString simpleWarningPattern2 = QLatin1String("^Warning: (.*) \\(([a-z]+\\.[a-z]+):(\\d+)\\)$");
QTest::newRow("another simple warning on stdout")
<< simpleWarning2
<< OutputParserTester::STDOUT
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseStdOutChannel
<< simplePattern2 << 1 << 2 << 3
<< simpleWarningPattern2 << 2 << 3 << 1
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Warning, warningMessage, fileName, lineNumber2, categoryCompile)
)
<< QString();
QTest::newRow("warning on wrong channel")
<< simpleWarning2
<< OutputParserTester::STDOUT
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseStdErrChannel
<< QString() << 1 << 2 << 3
<< simpleWarningPattern2 << 2 << 3 << 1
<< simpleWarningPassThrough2 << QString()
<< QList<Task>()
<< QString();
QTest::newRow("warning on other wrong channel")
<< simpleWarning2
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseStdOutChannel
<< QString() << 1 << 2 << 3
<< simpleWarningPattern2 << 2 << 3 << 1
<< QString() << simpleWarningPassThrough2
<< QList<Task>()
<< QString();
QTest::newRow("error and *warning*")
<< simpleWarning
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< simpleWarningPattern << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Warning, warningMessage, fileName, 1234, categoryCompile)
)
<< QString();
QTest::newRow("*error* when equal pattern")
<< simpleError
<< OutputParserTester::STDERR
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< simplePattern << 1 << 2 << 3
<< simplePattern << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, message, fileName, 9, categoryCompile)
)
<< QString();
const QString unitTestError = QLatin1String("../LedDriver/LedDriverTest.c:63: FAIL: Expected 0x0080 Was 0xffff");
const FileName unitTestFileName = FileName::fromUserInput(QLatin1String("../LedDriver/LedDriverTest.c"));
const QString unitTestMessage = QLatin1String("Expected 0x0080 Was 0xffff");
......@@ -251,8 +392,9 @@ void ProjectExplorerPlugin::testCustomOutputParsers_data()
QTest::newRow("unit test error")
<< unitTestError
<< OutputParserTester::STDOUT
<< unitTestPattern
<< 1 << 2 << 3
<< CustomParserExpression::ParseBothChannels << CustomParserExpression::ParseBothChannels
<< unitTestPattern << 1 << 2 << 3
<< QString() << 1 << 2 << 3
<< QString() << QString()
<< (QList<Task>()
<< Task(Task::Error, unitTestMessage, unitTestFileName, unitTestLineNumber, categoryCompile)
......@@ -264,20 +406,35 @@ void ProjectExplorerPlugin::testCustomOutputParsers()
{
QFETCH(QString, input);
QFETCH(OutputParserTester::Channel, inputChannel);
QFETCH(QString, pattern);
QFETCH(int, fileNameCap);
QFETCH(int, lineNumberCap);
QFETCH(int, messageCap);
QFETCH(CustomParserExpression::CustomParserChannel, filterErrorChannel);
QFETCH(CustomParserExpression::CustomParserChannel, filterWarningChannel);
QFETCH(QString, errorPattern);
QFETCH(int, errorFileNameCap);
QFETCH(int, errorLineNumberCap);
QFETCH(int, errorMessageCap);
QFETCH(QString, warningPattern);
QFETCH(int, warningFileNameCap);
QFETCH(int, warningLineNumberCap);
QFETCH(int, warningMessageCap);
QFETCH(QString, childStdOutLines);
QFETCH(QString, childStdErrLines);
QFETCH(QList<Task>, tasks);
QFETCH(QString, outputLines);
CustomParserSettings settings;
settings.error.setPattern(errorPattern);
settings.error.setFileNameCap(errorFileNameCap);
settings.error.setLineNumberCap(errorLineNumberCap);
settings.error.setMessageCap(errorMessageCap);
settings.error.setChannel(filterErrorChannel);
settings.warning.setPattern(warningPattern);
settings.warning.setFileNameCap(warningFileNameCap);
settings.warning.setLineNumberCap(warningLineNumberCap);
settings.warning.setMessageCap(warningMessageCap);
settings.warning.setChannel(filterWarningChannel);
CustomParser *parser = new CustomParser;
parser->setErrorPattern(pattern);
parser->setFileNameCap(fileNameCap);
parser->setLineNumberCap(lineNumberCap);
parser->setMessageCap(messageCap);
parser->setSettings(settings);
OutputParserTester testbench;
testbench.appendOutputParser(parser);
......
......@@ -39,39 +39,28 @@
namespace ProjectExplorer {
class CustomParserSettings
{
public:
CustomParserSettings();
bool operator ==(const CustomParserSettings &other) const;
bool operator !=(const CustomParserSettings &other) const { return !operator==(other); }
QString errorPattern;
int fileNameCap;
int lineNumberCap;
int messageCap;
};
class CustomParser : public ProjectExplorer::IOutputParser
class CustomParserExpression
{
public:
enum CustomParserChannels {
enum CustomParserChannel {
ParseNoChannel = 0,
ParseStdErrChannel = 1,
ParseStdOutChannel = 2,
ParseBothChannels = 3
};
CustomParser(const CustomParserSettings &settings = CustomParserSettings());
~CustomParser();
void stdError(const QString &line);
void stdOutput(const QString &line);
bool operator ==(const CustomParserExpression &other) const;
void setSettings(const CustomParserSettings &settings);
QString pattern() const;
void setPattern(const QString &pattern);
QRegularExpressionMatch match(const QString &line) const { return m_regExp.match(line); }
CustomParserExpression::CustomParserChannel channel() const;
void setChannel(CustomParserExpression::CustomParserChannel channel);
QString example() const;
void setExample(const QString &example);
void setErrorPattern(const QString &errorPattern);
QString errorPattern() const;
int fileNameCap() const;
void setFileNameCap(int fileNameCap);
int lineNumberCap() const;
......@@ -80,16 +69,45 @@ public:
void setMessageCap(int messageCap);
private:
bool parseLine(const QString &rawLine);
QRegularExpression m_regExp;
CustomParserExpression::CustomParserChannel m_channel = ParseBothChannels;
QString m_example;
int m_fileNameCap = 1;
int m_lineNumberCap = 2;
int m_messageCap = 3;
};
class CustomParserSettings
{
public:
bool operator ==(const CustomParserSettings &other) const;
bool operator !=(const CustomParserSettings &other) const { return !operator==(other); }
QRegularExpression m_errorRegExp;
int m_fileNameCap;
int m_lineNumberCap;
int m_messageCap;
CustomParserExpression error;
CustomParserExpression warning;
};
CustomParserChannels m_parserChannels;
class CustomParser : public ProjectExplorer::IOutputParser
{
public:
CustomParser(const CustomParserSettings &settings = CustomParserSettings());
~CustomParser();
void stdError(const QString &line);
void stdOutput(const QString &line);
void setSettings(const CustomParserSettings &settings);
private:
bool hasMatch(const QString &line, CustomParserExpression::CustomParserChannel channel,
const CustomParserExpression &expression, Task::TaskType taskType);
bool parseLine(const QString &rawLine, CustomParserExpression::CustomParserChannel channel);
CustomParserExpression m_error;
CustomParserExpression m_warning;
};
} // namespace ProjectExplorer
Q_DECLARE_METATYPE(ProjectExplorer::CustomParserExpression::CustomParserChannel);
#endif // CUSTOMPARSER_H
......@@ -31,6 +31,7 @@
#include "customparserconfigdialog.h"
#include "ui_customparserconfigdialog.h"
#include <QLineEdit>
#include <QPushButton>
#include <QRegularExpression>
......@@ -44,10 +45,15 @@ CustomParserConfigDialog::CustomParserConfigDialog(QDialog *parent) :
ui->setupUi(this);