Commit 5efd8246 authored by Tobias Hunger's avatar Tobias Hunger

SynchronousProcess: Store raw bytes from stdout/stderr of the process

Only convert the raw output later in a stdOut() and stdErr() method of
the SynchronousProcessResponse.

This is necessary since we have processes that use different encodings
for different sections of the file (I am looking at you, git).

Also remove the signals for raw data on stdout/stderr, leaving only the
signals returning buffered QString lines. This should be safe, even
with UTF-16 output.

Change-Id: Ida613fa86d1468cbd33bc6b3a1506a849c2d1c0a
Reviewed-by: Tobias Hunger's avatarTobias Hunger <tobias.hunger@qt.io>
parent 86882018
...@@ -46,7 +46,7 @@ QString BuildableHelperLibrary::qtChooserToQmakePath(const QString &path) ...@@ -46,7 +46,7 @@ QString BuildableHelperLibrary::qtChooserToQmakePath(const QString &path)
SynchronousProcessResponse response = proc.runBlocking(path, QStringList(QLatin1String("-print-env"))); SynchronousProcessResponse response = proc.runBlocking(path, QStringList(QLatin1String("-print-env")));
if (response.result != SynchronousProcessResponse::Finished) if (response.result != SynchronousProcessResponse::Finished)
return QString(); return QString();
const QString output = response.stdOut; const QString output = response.stdOut();
int pos = output.indexOf(toolDir); int pos = output.indexOf(toolDir);
if (pos == -1) if (pos == -1)
return QString(); return QString();
......
...@@ -277,8 +277,8 @@ void ShellCommand::run(QFutureInterface<void> &future) ...@@ -277,8 +277,8 @@ void ShellCommand::run(QFutureInterface<void> &future)
Utils::SynchronousProcessResponse resp Utils::SynchronousProcessResponse resp
= runCommand(job.binary, job.arguments, job.timeoutS, job.workingDirectory, = runCommand(job.binary, job.arguments, job.timeoutS, job.workingDirectory,
job.exitCodeInterpreter); job.exitCodeInterpreter);
stdOut += resp.stdOut; stdOut += resp.stdOut();
stdErr += resp.stdErr; stdErr += resp.stdErr();
d->m_lastExecExitCode = resp.exitCode; d->m_lastExecExitCode = resp.exitCode;
d->m_lastExecSuccess = resp.result == Utils::SynchronousProcessResponse::Finished; d->m_lastExecSuccess = resp.result == Utils::SynchronousProcessResponse::Finished;
if (!d->m_lastExecSuccess) if (!d->m_lastExecSuccess)
...@@ -427,21 +427,20 @@ Utils::SynchronousProcessResponse ShellCommand::runSynchronous(const Utils::File ...@@ -427,21 +427,20 @@ Utils::SynchronousProcessResponse ShellCommand::runSynchronous(const Utils::File
&stdOut, &stdErr, true); &stdOut, &stdErr, true);
if (!d->m_aborted) { if (!d->m_aborted) {
response.codec = d->m_codec ? d->m_codec : QTextCodec::codecForLocale();
if (!stdErr.isEmpty()) { if (!stdErr.isEmpty()) {
response.stdErr = Utils::SynchronousProcess::normalizeNewlines( response.rawStdErr = stdErr;
d->m_codec ? d->m_codec->toUnicode(stdErr) : QString::fromLocal8Bit(stdErr));
if (!(d->m_flags & SuppressStdErr)) if (!(d->m_flags & SuppressStdErr))
proxy->append(response.stdErr); proxy->append(response.stdErr());
} }
if (!stdOut.isEmpty()) { if (!stdOut.isEmpty()) {
response.stdOut = Utils::SynchronousProcess::normalizeNewlines( response.rawStdOut = stdOut;
d->m_codec ? d->m_codec->toUnicode(stdOut) : QString::fromLocal8Bit(stdOut));
if (d->m_flags & ShowStdOut) { if (d->m_flags & ShowStdOut) {
if (d->m_flags & SilentOutput) if (d->m_flags & SilentOutput)
proxy->appendSilently(response.stdOut); proxy->appendSilently(response.stdOut());
else else
proxy->append(response.stdOut); proxy->append(response.stdOut());
} }
} }
} }
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#include <QApplication> #include <QApplication>
#include <limits.h> #include <limits.h>
#include <memory>
#ifdef Q_OS_UNIX #ifdef Q_OS_UNIX
# include <unistd.h> # include <unistd.h>
...@@ -115,8 +116,8 @@ void SynchronousProcessResponse::clear() ...@@ -115,8 +116,8 @@ void SynchronousProcessResponse::clear()
{ {
result = StartFailed; result = StartFailed;
exitCode = -1; exitCode = -1;
stdOut.clear(); rawStdOut.clear();
stdErr.clear(); rawStdErr.clear();
} }
QString SynchronousProcessResponse::exitMessage(const QString &binary, int timeoutS) const QString SynchronousProcessResponse::exitMessage(const QString &binary, int timeoutS) const
...@@ -137,22 +138,49 @@ QString SynchronousProcessResponse::exitMessage(const QString &binary, int timeo ...@@ -137,22 +138,49 @@ QString SynchronousProcessResponse::exitMessage(const QString &binary, int timeo
return QString(); return QString();
} }
QString SynchronousProcessResponse::allOutput() const QByteArray SynchronousProcessResponse::allRawOutput() const
{ {
if (!stdOut.isEmpty() && !stdErr.isEmpty()) { if (!rawStdOut.isEmpty() && !rawStdErr.isEmpty()) {
if (stdOut.endsWith(QLatin1Char('\n'))) QByteArray result;
return stdOut + stdErr; result.reserve(rawStdOut.size() + rawStdErr.size() + 1);
else result = rawStdOut;
return stdOut + QLatin1Char('\n') + stdErr; if (!result.endsWith('\n'))
result += '\n';
result += rawStdErr;
return result;
} }
return !stdOut.isEmpty() ? stdOut : stdErr; return !rawStdOut.isEmpty() ? rawStdOut : rawStdErr;
}
QString SynchronousProcessResponse::allOutput() const
{
const QString out = stdOut();
const QString err = stdErr();
QString result;
result.reserve(out.size() + err.size() + 1);
result = out;
if (!result.endsWith('\n'))
result += '\n';
result += err;
return result;
}
QString SynchronousProcessResponse::stdOut() const
{
return SynchronousProcess::normalizeNewlines(codec->toUnicode(rawStdOut));
}
QString SynchronousProcessResponse::stdErr() const
{
return SynchronousProcess::normalizeNewlines(codec->toUnicode(rawStdErr));
} }
QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse& r) QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse& r)
{ {
QDebug nsp = str.nospace(); QDebug nsp = str.nospace();
nsp << "SynchronousProcessResponse: result=" << r.result << " ex=" << r.exitCode << '\n' nsp << "SynchronousProcessResponse: result=" << r.result << " ex=" << r.exitCode << '\n'
<< r.stdOut.size() << " bytes stdout, stderr=" << r.stdErr << '\n'; << r.rawStdOut.size() << " bytes stdout, stderr=" << r.rawStdErr << '\n';
return str; return str;
} }
...@@ -169,52 +197,62 @@ class ChannelBuffer : public QObject ...@@ -169,52 +197,62 @@ class ChannelBuffer : public QObject
public: public:
void clearForRun(); void clearForRun();
QString linesRead(); QString linesRead();
void append(const QString &text, bool emitSignals); void append(const QByteArray &text, bool emitSignals);
QString data; QByteArray rawData;
int bufferPos = 0; QString incompleteLineBuffer; // lines not yet signaled
bool firstData = true; QTextCodec *codec = nullptr; // Not owner
std::unique_ptr<QTextCodec::ConverterState> codecState;
int rawDataPos = 0;
bool bufferedSignalsEnabled = false; bool bufferedSignalsEnabled = false;
bool firstBuffer = true; bool firstBuffer = true;
signals: signals:
void output(const QString &text, bool firstTime);
void outputBuffered(const QString &text, bool firstTime); void outputBuffered(const QString &text, bool firstTime);
}; };
void ChannelBuffer::clearForRun() void ChannelBuffer::clearForRun()
{ {
firstData = true;
firstBuffer = true; firstBuffer = true;
bufferPos = 0; rawDataPos = 0;
rawData.clear();
codecState.reset(new QTextCodec::ConverterState);
incompleteLineBuffer.clear();
} }
/* Check for complete lines read from the device and return them, moving the /* Check for complete lines read from the device and return them, moving the
* buffer position. */ * buffer position. */
QString ChannelBuffer::linesRead() QString ChannelBuffer::linesRead()
{ {
// Any new lines? // Convert and append the new input to the buffer of incomplete lines
const int lastLineIndex = qMax(data.lastIndexOf(QLatin1Char('\n')), const char *start = rawData.constData() + rawDataPos;
data.lastIndexOf(QLatin1Char('\r'))); const int len = rawData.size() - rawDataPos;
if (lastLineIndex == -1 || lastLineIndex <= bufferPos) incompleteLineBuffer.append(codec->toUnicode(start, len, codecState.get()));
rawDataPos = rawData.size();
// Any completed lines in the incompleteLineBuffer?
const int lastLineIndex = qMax(incompleteLineBuffer.lastIndexOf('\n'),
incompleteLineBuffer.lastIndexOf('\r'));
if (lastLineIndex == -1)
return QString(); return QString();
const int nextBufferPos = lastLineIndex + 1;
const QString lines = data.mid(bufferPos, nextBufferPos - bufferPos); // Get completed lines and remove them from the incompleteLinesBuffer:
bufferPos = nextBufferPos; const QString lines = SynchronousProcess::normalizeNewlines(incompleteLineBuffer.left(lastLineIndex));
incompleteLineBuffer = incompleteLineBuffer.mid(lastLineIndex + 1);
return lines; return lines;
} }
void ChannelBuffer::append(const QString &text, bool emitSignals) void ChannelBuffer::append(const QByteArray &text, bool emitSignals)
{ {
if (text.isEmpty()) if (text.isEmpty())
return; return;
data += text; rawData += text;
if (!emitSignals) if (!emitSignals)
return; return;
// Emit binary signals
emit output(text, firstData);
firstData = false;
// Buffered. Emit complete lines? // Buffered. Emit complete lines?
if (bufferedSignalsEnabled) { if (bufferedSignalsEnabled) {
const QString lines = linesRead(); const QString lines = linesRead();
...@@ -231,8 +269,6 @@ struct SynchronousProcessPrivate { ...@@ -231,8 +269,6 @@ struct SynchronousProcessPrivate {
void clearForRun(); void clearForRun();
QTextCodec *m_codec; QTextCodec *m_codec;
QTextCodec::ConverterState m_stdOutState;
QTextCodec::ConverterState m_stdErrState;
TerminalControllingProcess m_process; TerminalControllingProcess m_process;
QTimer m_timer; QTimer m_timer;
QEventLoop m_eventLoop; QEventLoop m_eventLoop;
...@@ -258,8 +294,11 @@ void SynchronousProcessPrivate::clearForRun() ...@@ -258,8 +294,11 @@ void SynchronousProcessPrivate::clearForRun()
{ {
m_hangTimerCount = 0; m_hangTimerCount = 0;
m_stdOut.clearForRun(); m_stdOut.clearForRun();
m_stdOut.codec = m_codec;
m_stdErr.clearForRun(); m_stdErr.clearForRun();
m_stdErr.codec = m_codec;
m_result.clear(); m_result.clear();
m_result.codec = m_codec;
m_startFailure = false; m_startFailure = false;
m_binary.clear(); m_binary.clear();
} }
...@@ -276,12 +315,16 @@ SynchronousProcess::SynchronousProcess() : ...@@ -276,12 +315,16 @@ SynchronousProcess::SynchronousProcess() :
connect(&d->m_process, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error), connect(&d->m_process, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
this, &SynchronousProcess::error); this, &SynchronousProcess::error);
connect(&d->m_process, &QProcess::readyReadStandardOutput, connect(&d->m_process, &QProcess::readyReadStandardOutput,
this, &SynchronousProcess::stdOutReady); this, [this]() {
d->m_hangTimerCount = 0;
processStdOut(true);
});
connect(&d->m_process, &QProcess::readyReadStandardError, connect(&d->m_process, &QProcess::readyReadStandardError,
this, &SynchronousProcess::stdErrReady); this, [this]() {
connect(&d->m_stdOut, &ChannelBuffer::output, this, &SynchronousProcess::stdOut); d->m_hangTimerCount = 0;
processStdErr(true);
});
connect(&d->m_stdOut, &ChannelBuffer::outputBuffered, this, &SynchronousProcess::stdOutBuffered); connect(&d->m_stdOut, &ChannelBuffer::outputBuffered, this, &SynchronousProcess::stdOutBuffered);
connect(&d->m_stdErr, &ChannelBuffer::output, this, &SynchronousProcess::stdErr);
connect(&d->m_stdErr, &ChannelBuffer::outputBuffered, this, &SynchronousProcess::stdErrBuffered); connect(&d->m_stdErr, &ChannelBuffer::outputBuffered, this, &SynchronousProcess::stdErrBuffered);
} }
...@@ -436,8 +479,8 @@ SynchronousProcessResponse SynchronousProcess::run(const QString &binary, ...@@ -436,8 +479,8 @@ SynchronousProcessResponse SynchronousProcess::run(const QString &binary,
processStdErr(false); processStdErr(false);
} }
d->m_result.stdOut = d->m_stdOut.data; d->m_result.rawStdOut = d->m_stdOut.rawData;
d->m_result.stdErr = d->m_stdErr.data; d->m_result.rawStdErr = d->m_stdErr.rawData;
d->m_timer.stop(); d->m_timer.stop();
if (isGuiThread()) if (isGuiThread())
...@@ -485,8 +528,8 @@ SynchronousProcessResponse SynchronousProcess::runBlocking(const QString &binary ...@@ -485,8 +528,8 @@ SynchronousProcessResponse SynchronousProcess::runBlocking(const QString &binary
processStdOut(false); processStdOut(false);
processStdErr(false); processStdErr(false);
d->m_result.stdOut = d->m_stdOut.data; d->m_result.rawStdOut = d->m_stdOut.rawData;
d->m_result.stdErr = d->m_stdErr.data; d->m_result.rawStdErr = d->m_stdErr.rawData;
return d->m_result; return d->m_result;
} }
...@@ -569,42 +612,16 @@ void SynchronousProcess::error(QProcess::ProcessError e) ...@@ -569,42 +612,16 @@ void SynchronousProcess::error(QProcess::ProcessError e)
d->m_eventLoop.quit(); d->m_eventLoop.quit();
} }
void SynchronousProcess::stdOutReady()
{
d->m_hangTimerCount = 0;
processStdOut(true);
}
void SynchronousProcess::stdErrReady()
{
d->m_hangTimerCount = 0;
processStdErr(true);
}
QString SynchronousProcess::convertOutput(const QByteArray &ba,
QTextCodec::ConverterState *state) const
{
return normalizeNewlines(d->m_codec->toUnicode(ba, ba.size(), state));
}
void SynchronousProcess::processStdOut(bool emitSignals) void SynchronousProcess::processStdOut(bool emitSignals)
{ {
// Handle binary data // Handle binary data
const QString stdOutput = convertOutput(d->m_process.readAllStandardOutput(), d->m_stdOut.append(d->m_process.readAllStandardOutput(), emitSignals);
&d->m_stdOutState);
if (debug > 1)
qDebug() << Q_FUNC_INFO << emitSignals << stdOutput;
d->m_stdOut.append(stdOutput, emitSignals);
} }
void SynchronousProcess::processStdErr(bool emitSignals) void SynchronousProcess::processStdErr(bool emitSignals)
{ {
// Handle binary data // Handle binary data
const QString stdError = convertOutput(d->m_process.readAllStandardError(), d->m_stdErr.append(d->m_process.readAllStandardError(), emitSignals);
&d->m_stdErrState);
if (debug > 1)
qDebug() << Q_FUNC_INFO << emitSignals << stdError;
d->m_stdErr.append(stdError, emitSignals);
} }
QSharedPointer<QProcess> SynchronousProcess::createProcess(unsigned flags) QSharedPointer<QProcess> SynchronousProcess::createProcess(unsigned flags)
......
...@@ -59,12 +59,19 @@ struct QTCREATOR_UTILS_EXPORT SynchronousProcessResponse ...@@ -59,12 +59,19 @@ struct QTCREATOR_UTILS_EXPORT SynchronousProcessResponse
// Helper to format an exit message. // Helper to format an exit message.
QString exitMessage(const QString &binary, int timeoutS) const; QString exitMessage(const QString &binary, int timeoutS) const;
QByteArray allRawOutput() const;
QString allOutput() const; QString allOutput() const;
QString stdOut() const;
QString stdErr() const;
Result result; Result result;
int exitCode; int exitCode;
QString stdOut;
QString stdErr; QTextCodec *codec = nullptr;
QByteArray rawStdOut;
QByteArray rawStdErr;
}; };
QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse &); QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse &);
...@@ -132,7 +139,7 @@ public: ...@@ -132,7 +139,7 @@ public:
// occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout // occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout
// occurs. Checking of the process' exit state/code still has to be done. // occurs. Checking of the process' exit state/code still has to be done.
static bool readDataFromProcess(QProcess &p, int timeoutS, static bool readDataFromProcess(QProcess &p, int timeoutS,
QByteArray *stdOut = 0, QByteArray *stdErr = 0, QByteArray *rawStdOut = 0, QByteArray *rawStdErr = 0,
bool timeOutMessageBox = false); bool timeOutMessageBox = false);
// Stop a process by first calling terminate() (allowing for signal handling) and // Stop a process by first calling terminate() (allowing for signal handling) and
// then kill(). // then kill().
...@@ -146,11 +153,8 @@ public: ...@@ -146,11 +153,8 @@ public:
static QString normalizeNewlines(const QString &text); static QString normalizeNewlines(const QString &text);
signals: signals:
void stdOut(const QString &text, bool firstTime); void stdOutBuffered(const QString &lines, bool firstTime);
void stdErr(const QString &text, bool firstTime); void stdErrBuffered(const QString &lines, bool firstTime);
void stdOutBuffered(const QString &data, bool firstTime);
void stdErrBuffered(const QString &data, bool firstTime);
public slots: public slots:
bool terminate(); bool terminate();
...@@ -159,11 +163,8 @@ private: ...@@ -159,11 +163,8 @@ private:
void slotTimeout(); void slotTimeout();
void finished(int exitCode, QProcess::ExitStatus e); void finished(int exitCode, QProcess::ExitStatus e);
void error(QProcess::ProcessError); void error(QProcess::ProcessError);
void stdOutReady();
void stdErrReady();
void processStdOut(bool emitSignals); void processStdOut(bool emitSignals);
void processStdErr(bool emitSignals); void processStdErr(bool emitSignals);
QString convertOutput(const QByteArray &, QTextCodec::ConverterState *state) const;
SynchronousProcessPrivate *d; SynchronousProcessPrivate *d;
}; };
......
...@@ -305,7 +305,7 @@ QAbstractItemModel *AndroidBuildApkStep::keystoreCertificates() ...@@ -305,7 +305,7 @@ QAbstractItemModel *AndroidBuildApkStep::keystoreCertificates()
QMessageBox::critical(0, tr("Error"), tr("Invalid password.")); QMessageBox::critical(0, tr("Error"), tr("Invalid password."));
m_keystorePasswd.clear(); m_keystorePasswd.clear();
} }
rawCerts = response.stdOut; rawCerts = response.stdOut();
} }
return new CertificatesModel(rawCerts, this); return new CertificatesModel(rawCerts, this);
} }
......
...@@ -85,10 +85,10 @@ static int updateVersionHelper(const QString &command) ...@@ -85,10 +85,10 @@ static int updateVersionHelper(const QString &command)
return 0; return 0;
// Astyle prints the version on stdout or stderr, depending on platform // Astyle prints the version on stdout or stderr, depending on platform
const int version = parseVersion(response.stdOut.trimmed()); const int version = parseVersion(response.stdOut().trimmed());
if (version != 0) if (version != 0)
return version; return version;
return parseVersion(response.stdErr.trimmed()); return parseVersion(response.stdErr().trimmed());
} }
void ArtisticStyleSettings::updateVersion() void ArtisticStyleSettings::updateVersion()
...@@ -176,7 +176,7 @@ void ArtisticStyleSettings::createDocumentationFile() const ...@@ -176,7 +176,7 @@ void ArtisticStyleSettings::createDocumentationFile() const
stream.writeStartElement(Constants::DOCUMENTATION_XMLROOT); stream.writeStartElement(Constants::DOCUMENTATION_XMLROOT);
// astyle writes its output to 'error'... // astyle writes its output to 'error'...
const QStringList lines = response.stdErr.split(QLatin1Char('\n')); const QStringList lines = response.stdErr().split(QLatin1Char('\n'));
QStringList keys; QStringList keys;
QStringList docu; QStringList docu;
for (QString line : lines) { for (QString line : lines) {
......
...@@ -104,7 +104,7 @@ FormatTask format(FormatTask task) ...@@ -104,7 +104,7 @@ FormatTask format(FormatTask task)
task.error = BeautifierPlugin::tr("Failed to format: %1.").arg(response.exitMessage(executable, 5)); task.error = BeautifierPlugin::tr("Failed to format: %1.").arg(response.exitMessage(executable, 5));
return task; return task;
} }
const QString output = response.stdErr; const QString output = response.stdErr();
if (!output.isEmpty()) if (!output.isEmpty())
task.error = executable + QLatin1String(": ") + output; task.error = executable + QLatin1String(": ") + output;
......
...@@ -1480,8 +1480,8 @@ ClearCasePlugin::runCleartool(const QString &workingDir, ...@@ -1480,8 +1480,8 @@ ClearCasePlugin::runCleartool(const QString &workingDir,
response.error = sp_resp.result != SynchronousProcessResponse::Finished; response.error = sp_resp.result != SynchronousProcessResponse::Finished;
if (response.error) if (response.error)
response.message = sp_resp.exitMessage(executable, timeOutS); response.message = sp_resp.exitMessage(executable, timeOutS);
response.stdErr = sp_resp.stdErr; response.stdErr = sp_resp.stdErr();
response.stdOut = sp_resp.stdOut; response.stdOut = sp_resp.stdOut();
return response; return response;
} }
......
...@@ -153,7 +153,7 @@ QStringList CMakeTool::supportedGenerators() const ...@@ -153,7 +153,7 @@ QStringList CMakeTool::supportedGenerators() const
Utils::SynchronousProcessResponse response = run(QLatin1String("--help")); Utils::SynchronousProcessResponse response = run(QLatin1String("--help"));
if (response.result == Utils::SynchronousProcessResponse::Finished) { if (response.result == Utils::SynchronousProcessResponse::Finished) {
bool inGeneratorSection = false; bool inGeneratorSection = false;
const QStringList lines = response.stdOut.split(QLatin1Char('\n')); const QStringList lines = response.stdOut().split(QLatin1Char('\n'));
foreach (const QString &line, lines) { foreach (const QString &line, lines) {
if (line.isEmpty()) if (line.isEmpty())
continue; continue;
...@@ -188,19 +188,19 @@ TextEditor::Keywords CMakeTool::keywords() ...@@ -188,19 +188,19 @@ TextEditor::Keywords CMakeTool::keywords()
Utils::SynchronousProcessResponse response; Utils::SynchronousProcessResponse response;
response = run(QLatin1String("--help-command-list")); response = run(QLatin1String("--help-command-list"));
if (response.result == Utils::SynchronousProcessResponse::Finished) if (response.result == Utils::SynchronousProcessResponse::Finished)
m_functions = response.stdOut.split(QLatin1Char('\n')); m_functions = response.stdOut().split(QLatin1Char('\n'));
response = run(QLatin1String("--help-commands")); response = run(QLatin1String("--help-commands"));
if (response.result == Utils::SynchronousProcessResponse::Finished) if (response.result == Utils::SynchronousProcessResponse::Finished)
parseFunctionDetailsOutput(response.stdOut); parseFunctionDetailsOutput(response.stdOut());
response = run(QLatin1String("--help-property-list")); response = run(QLatin1String("--help-property-list"));
if (response.result == Utils::SynchronousProcessResponse::Finished) if (response.result == Utils::SynchronousProcessResponse::Finished)
m_variables = parseVariableOutput(response.stdOut); m_variables = parseVariableOutput(response.stdOut());
response = run(QLatin1String("--help-variable-list")); response = run(QLatin1String("--help-variable-list"));