ProjectExplorer: Let IOutputParser handle newlines

This makes IOutputParser structurally more similar to
Utils::OutputFormatter, which makes it simpler to explore possibilities
of somehow uniting these two related classes.

Task-number: QTCREATORBUG-22665
Change-Id: Ibb12ab6c8c785d863b9a921102a929864d0a5251
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Christian Kandeler
2020-03-31 14:34:08 +02:00
parent 45e7b78dc3
commit b15d1951a2
8 changed files with 138 additions and 118 deletions

View File

@@ -201,7 +201,7 @@ void CMakeProcess::processStandardError()
static QString rest;
rest = lineSplit(rest, m_process->readAllStandardError(), [this](const QString &s) {
m_parser->stdError(s);
m_parser->handleStderr(s);
Core::MessageManager::write(s);
});
}

View File

@@ -45,6 +45,7 @@
#include <QDir>
#include <QHash>
#include <QPair>
#include <QTextDecoder>
#include <QUrl>
#include <algorithm>
@@ -108,15 +109,11 @@ public:
std::unique_ptr<IOutputParser> m_outputParserChain;
ProcessParameters m_param;
Utils::FileInProjectFinder m_fileFinder;
QByteArray deferredText;
bool m_ignoreReturnValue = false;
bool m_skipFlush = false;
bool m_lowPriority = false;
void readData(void (AbstractProcessStep::*func)(const QString &), bool isUtf8 = false);
void processLine(const QByteArray &data,
void (AbstractProcessStep::*func)(const QString &),
bool isUtf8 = false);
std::unique_ptr<QTextDecoder> stdoutStream;
std::unique_ptr<QTextDecoder> stderrStream;
};
AbstractProcessStep::AbstractProcessStep(BuildStepList *bsl, Core::Id id) :
@@ -224,6 +221,10 @@ void AbstractProcessStep::doRun()
return;
}
d->stdoutStream = std::make_unique<QTextDecoder>(buildEnvironment().hasKey("VSLANG")
? QTextCodec::codecForName("UTF-8") : QTextCodec::codecForLocale());
d->stderrStream = std::make_unique<QTextDecoder>(QTextCodec::codecForLocale());
d->m_process.reset(new Utils::QtcProcess());
d->m_process->setUseCtrlCStub(Utils::HostOsInfo::isWindowsHost());
d->m_process->setWorkingDirectory(wd.absolutePath());
@@ -347,40 +348,7 @@ void AbstractProcessStep::processReadyReadStdOutput()
{
if (!d->m_process)
return;
d->m_process->setReadChannel(QProcess::StandardOutput);
const bool utf8Output = buildEnvironment().hasKey("VSLANG");
d->readData(&AbstractProcessStep::stdOutput, utf8Output);
}
void AbstractProcessStep::Private::readData(void (AbstractProcessStep::*func)(const QString &),
bool isUtf8)
{
while (m_process->bytesAvailable()) {
const bool hasLine = m_process->canReadLine();
const QByteArray data = hasLine ? m_process->readLine() : m_process->readAll();
int startPos = 0;
int crPos = -1;
while ((crPos = data.indexOf('\r', startPos)) >= 0) {
if (data.size() > crPos + 1 && data.at(crPos + 1) == '\n')
break;
processLine(data.mid(startPos, crPos - startPos + 1), func, isUtf8);
startPos = crPos + 1;
}
if (hasLine)
processLine(data.mid(startPos), func, isUtf8);
else if (startPos < data.count())
deferredText += data.mid(startPos);
}
}
void AbstractProcessStep::Private::processLine(const QByteArray &data,
void (AbstractProcessStep::*func)(const QString &),
bool isUtf8)
{
const QByteArray text = deferredText + data;
deferredText.clear();
const QString line = isUtf8 ? QString::fromUtf8(text) : QString::fromLocal8Bit(text);
(q->*func)(line);
stdOutput(d->stdoutStream->toUnicode(d->m_process->readAllStandardOutput()));
}
/*!
@@ -389,19 +357,18 @@ void AbstractProcessStep::Private::processLine(const QByteArray &data,
The default implementation adds the line to the application output window.
*/
void AbstractProcessStep::stdOutput(const QString &line)
void AbstractProcessStep::stdOutput(const QString &output)
{
if (d->m_outputParserChain)
d->m_outputParserChain->stdOutput(line);
emit addOutput(line, BuildStep::OutputFormat::Stdout, BuildStep::DontAppendNewline);
d->m_outputParserChain->handleStdout(output);
emit addOutput(output, BuildStep::OutputFormat::Stdout, BuildStep::DontAppendNewline);
}
void AbstractProcessStep::processReadyReadStdError()
{
if (!d->m_process)
return;
d->m_process->setReadChannel(QProcess::StandardError);
d->readData(&AbstractProcessStep::stdError);
stdError(d->stderrStream->toUnicode(d->m_process->readAllStandardError()));
}
/*!
@@ -410,11 +377,11 @@ void AbstractProcessStep::processReadyReadStdError()
The default implementation adds the line to the application output window.
*/
void AbstractProcessStep::stdError(const QString &line)
void AbstractProcessStep::stdError(const QString &output)
{
if (d->m_outputParserChain)
d->m_outputParserChain->stdError(line);
emit addOutput(line, BuildStep::OutputFormat::Stderr, BuildStep::DontAppendNewline);
d->m_outputParserChain->handleStderr(output);
emit addOutput(output, BuildStep::OutputFormat::Stderr, BuildStep::DontAppendNewline);
}
void AbstractProcessStep::finish(bool success)
@@ -432,7 +399,7 @@ void AbstractProcessStep::taskAdded(const Task &task, int linkedOutputLines, int
// flush out any pending tasks before proceeding:
if (!d->m_skipFlush && d->m_outputParserChain) {
d->m_skipFlush = true;
d->m_outputParserChain->flush();
d->m_outputParserChain->flushTasks();
d->m_skipFlush = false;
}
@@ -463,15 +430,10 @@ void AbstractProcessStep::slotProcessFinished(int, QProcess::ExitStatus)
QProcess *process = d->m_process.get();
if (!process) // Happens when the process was canceled and handed over to the Reaper.
process = qobject_cast<QProcess *>(sender()); // The process was canceled!
const QString stdErrLine = process ? QString::fromLocal8Bit(process->readAllStandardError()) : QString();
for (const QString &l : stdErrLine.split('\n'))
stdError(l);
const QString stdOutLine = process ? QString::fromLocal8Bit(process->readAllStandardOutput()) : QString();
for (const QString &l : stdOutLine.split('\n'))
stdOutput(l);
if (process) {
stdError(d->stderrStream->toUnicode(process->readAllStandardError()));
stdOutput(d->stdoutStream->toUnicode(process->readAllStandardOutput()));
}
cleanUp(process);
}

View File

@@ -64,8 +64,8 @@ protected:
virtual void processFinished(int exitCode, QProcess::ExitStatus status);
virtual void processStartupFailed();
virtual bool processSucceeded(int exitCode, QProcess::ExitStatus status);
virtual void stdOutput(const QString &line);
virtual void stdError(const QString &line);
virtual void stdOutput(const QString &output);
virtual void stdError(const QString &output);
void doCancel() override;

View File

@@ -26,6 +26,8 @@
#include "ioutputparser.h"
#include "task.h"
#include <utils/synchronousprocess.h>
/*!
\class ProjectExplorer::IOutputParser
@@ -42,13 +44,6 @@
ownership.
*/
/*!
\fn IOutputParser *ProjectExplorer::IOutputParser::takeOutputParserChain()
Removes the appended outputparser chain from this parser, transferring
ownership of the parser chain to the caller.
*/
/*!
\fn IOutputParser *ProjectExplorer::IOutputParser::childParser() const
@@ -89,18 +84,6 @@
Should be emitted for each task seen in the output.
*/
/*!
\fn void ProjectExplorer::IOutputParser::outputAdded(const QString &string, ProjectExplorer::BuildStep::OutputFormat format)
Subparsers have their addOutput signal connected to this slot.
*/
/*!
\fn void ProjectExplorer::IOutputParser::outputAdded(const QString &string, ProjectExplorer::BuildStep::OutputFormat format)
This function can be overwritten to change the string.
*/
/*!
\fn void ProjectExplorer::IOutputParser::taskAdded(const ProjectExplorer::Task &task)
@@ -123,50 +106,116 @@
namespace ProjectExplorer {
class OutputChannelState
{
public:
using LineHandler = void (IOutputParser::*)(const QString &line);
OutputChannelState(IOutputParser *parser, LineHandler lineHandler)
: parser(parser), lineHandler(lineHandler) {}
void handleData(const QString &newData)
{
pendingData += newData;
pendingData = Utils::SynchronousProcess::normalizeNewlines(pendingData);
while (true) {
const int eolPos = pendingData.indexOf('\n');
if (eolPos == -1)
break;
const QString line = pendingData.left(eolPos + 1);
pendingData.remove(0, eolPos + 1);
(parser->*lineHandler)(line);
}
}
void flush()
{
if (!pendingData.isEmpty()) {
(parser->*lineHandler)(pendingData);
pendingData.clear();
}
}
IOutputParser * const parser;
LineHandler lineHandler;
QString pendingData;
};
class IOutputParser::IOutputParserPrivate
{
public:
IOutputParserPrivate(IOutputParser *parser)
: stdoutState(parser, &IOutputParser::stdOutput),
stderrState(parser, &IOutputParser::stdError)
{}
IOutputParser *childParser = nullptr;
Utils::FilePath workingDir;
OutputChannelState stdoutState;
OutputChannelState stderrState;
};
IOutputParser::IOutputParser() : d(new IOutputParserPrivate(this))
{
}
IOutputParser::~IOutputParser()
{
delete m_parser;
delete d->childParser;
delete d;
}
void IOutputParser::handleStdout(const QString &data)
{
d->stdoutState.handleData(data);
}
void IOutputParser::handleStderr(const QString &data)
{
d->stderrState.handleData(data);
}
void IOutputParser::appendOutputParser(IOutputParser *parser)
{
if (!parser)
return;
if (m_parser) {
m_parser->appendOutputParser(parser);
if (d->childParser) {
d->childParser->appendOutputParser(parser);
return;
}
m_parser = parser;
d->childParser = parser;
connect(parser, &IOutputParser::addTask, this, &IOutputParser::taskAdded);
}
IOutputParser *IOutputParser::childParser() const
{
return m_parser;
return d->childParser;
}
void IOutputParser::setChildParser(IOutputParser *parser)
{
if (m_parser != parser)
delete m_parser;
m_parser = parser;
if (d->childParser != parser)
delete d->childParser;
d->childParser = parser;
if (parser)
connect(parser, &IOutputParser::addTask, this, &IOutputParser::taskAdded);
}
void IOutputParser::stdOutput(const QString &line)
{
if (m_parser)
m_parser->stdOutput(line);
if (d->childParser)
d->childParser->stdOutput(line);
}
void IOutputParser::stdError(const QString &line)
{
if (m_parser)
m_parser->stdError(line);
if (d->childParser)
d->childParser->stdError(line);
}
Utils::FilePath IOutputParser::workingDirectory() const { return d->workingDir; }
void IOutputParser::taskAdded(const Task &task, int linkedOutputLines, int skipLines)
{
emit addTask(task, linkedOutputLines, skipLines);
@@ -177,21 +226,29 @@ void IOutputParser::doFlush()
bool IOutputParser::hasFatalErrors() const
{
return m_parser && m_parser->hasFatalErrors();
return d->childParser && d->childParser->hasFatalErrors();
}
void IOutputParser::setWorkingDirectory(const Utils::FilePath &fn)
{
m_workingDir = fn;
if (m_parser)
m_parser->setWorkingDirectory(fn);
d->workingDir = fn;
if (d->childParser)
d->childParser->setWorkingDirectory(fn);
}
void IOutputParser::flush()
{
flushTasks();
d->stdoutState.flush();
d->stderrState.flush();
flushTasks();
}
void IOutputParser::flushTasks()
{
doFlush();
if (m_parser)
m_parser->flush();
if (d->childParser)
d->childParser->flushTasks();
}
QString IOutputParser::rightTrimmed(const QString &in)

View File

@@ -38,22 +38,23 @@ class PROJECTEXPLORER_EXPORT IOutputParser : public QObject
{
Q_OBJECT
public:
IOutputParser() = default;
IOutputParser();
~IOutputParser() override;
void handleStdout(const QString &data);
void handleStderr(const QString &data);
void appendOutputParser(IOutputParser *parser);
IOutputParser *childParser() const;
void setChildParser(IOutputParser *parser);
virtual void stdOutput(const QString &line);
virtual void stdError(const QString &line);
virtual bool hasFatalErrors() const;
void setWorkingDirectory(const Utils::FilePath &fn);
void flush(); // flush out pending tasks
void flush(); // flush pending tasks & output
void flushTasks(); // flush pending tasks only
static QString rightTrimmed(const QString &in);
@@ -63,13 +64,16 @@ signals:
void addTask(const ProjectExplorer::Task &task, int linkedOutputLines = 0, int skipLines = 0);
protected:
Utils::FilePath workingDirectory() const { return m_workingDir; }
virtual void stdOutput(const QString &line);
virtual void stdError(const QString &line);
Utils::FilePath workingDirectory() const;
private:
virtual void doFlush();
IOutputParser *m_parser = nullptr;
Utils::FilePath m_workingDir;
class IOutputParserPrivate;
IOutputParserPrivate * const d;
};
} // namespace ProjectExplorer

View File

@@ -53,14 +53,11 @@ void OutputParserTester::testParsing(const QString &lines,
reset();
Q_ASSERT(childParser());
const QStringList inputLines = lines.split('\n');
for (const QString &input : inputLines) {
if (inputChannel == STDOUT)
childParser()->stdOutput(input + '\n');
handleStdout(lines + '\n');
else
childParser()->stdError(input + '\n');
}
childParser()->flush();
handleStderr(lines + '\n');
flush();
// delete the parser(s) to test
emit aboutToDeleteParser();

View File

@@ -140,9 +140,9 @@ static void parse(QFutureInterface<void> &future, const QString &output,
{
const QStringList lines = output.split('\n');
future.setProgressRange(0, lines.count());
const auto parserFunc = isStderr ? &IOutputParser::stdError : &IOutputParser::stdOutput;
const auto parserFunc = isStderr ? &IOutputParser::handleStderr : &IOutputParser::handleStdout;
for (const QString &line : lines) {
(parser.get()->*parserFunc)(line);
(parser.get()->*parserFunc)(line + '\n');
future.setProgressValue(future.progressValue() + 1);
if (future.isCanceled())
return;

View File

@@ -383,11 +383,11 @@ void QbsBuildStep::handleProcessResult(
emit addOutput(executable.toUserOutput() + ' ' + QtcProcess::joinArgs(arguments),
OutputFormat::Stdout);
for (const QString &line : stdErr) {
m_parser->stdError(line);
m_parser->handleStderr(line + '\n');
emit addOutput(line, OutputFormat::Stderr);
}
for (const QString &line : stdOut) {
m_parser->stdOutput(line);
m_parser->handleStdout(line + '\n');
emit addOutput(line, OutputFormat::Stdout);
}
m_parser->flush();