forked from qt-creator/qt-creator
QtcProcess: Introduce TextChannelMode and textOnChannel signals
This is alternative to setStd[Out/Err](Line)Callback() methods. In this way there may be many clients connected to textOnStandard[Output/Error]() signals. This should also simplify handling the lifetime of user callback. Change-Id: If82baa1f3f9c432ed431926619b9bbf11d770a84 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
@@ -37,6 +37,21 @@ enum class EventLoopMode {
|
||||
On // Avoid
|
||||
};
|
||||
|
||||
enum class Channel {
|
||||
Output,
|
||||
Error
|
||||
};
|
||||
|
||||
enum class TextChannelMode {
|
||||
// Keep | Emit | Emit
|
||||
// raw | text | content
|
||||
// data | sig |
|
||||
// -----+------+--------
|
||||
Off, // yes | no | -
|
||||
SingleLine, // no | yes | Single lines
|
||||
MultiLine // yes | yes | All the available data
|
||||
};
|
||||
|
||||
enum class ProcessResult {
|
||||
// Finished successfully. Unless an ExitCodeInterpreter is set
|
||||
// this corresponds to a return code 0.
|
||||
@@ -53,6 +68,7 @@ enum class ProcessResult {
|
||||
};
|
||||
|
||||
using ExitCodeInterpreter = std::function<ProcessResult(int /*exitCode*/)>;
|
||||
using TextChannelCallback = std::function<void(const QString & /*text*/)>;
|
||||
|
||||
} // namespace Utils
|
||||
|
||||
|
@@ -187,6 +187,7 @@ public:
|
||||
QTextCodec *codec = nullptr; // Not owner
|
||||
std::unique_ptr<QTextCodec::ConverterState> codecState;
|
||||
std::function<void(const QString &lines)> outputCallback;
|
||||
TextChannelMode m_textChannelMode = TextChannelMode::Off;
|
||||
|
||||
bool emitSingleLines = true;
|
||||
bool keepRawData = true;
|
||||
@@ -1831,32 +1832,74 @@ void QtcProcess::runBlocking(EventLoopMode eventLoopMode)
|
||||
}
|
||||
}
|
||||
|
||||
void QtcProcess::setStdOutCallback(const std::function<void (const QString &)> &callback)
|
||||
void QtcProcess::setStdOutCallback(const TextChannelCallback &callback)
|
||||
{
|
||||
d->m_stdOut.outputCallback = callback;
|
||||
d->m_stdOut.emitSingleLines = false;
|
||||
}
|
||||
|
||||
void QtcProcess::setStdOutLineCallback(const std::function<void (const QString &)> &callback)
|
||||
void QtcProcess::setStdOutLineCallback(const TextChannelCallback &callback)
|
||||
{
|
||||
d->m_stdOut.outputCallback = callback;
|
||||
d->m_stdOut.emitSingleLines = true;
|
||||
d->m_stdOut.keepRawData = false;
|
||||
}
|
||||
|
||||
void QtcProcess::setStdErrCallback(const std::function<void (const QString &)> &callback)
|
||||
void QtcProcess::setStdErrCallback(const TextChannelCallback &callback)
|
||||
{
|
||||
d->m_stdErr.outputCallback = callback;
|
||||
d->m_stdErr.emitSingleLines = false;
|
||||
}
|
||||
|
||||
void QtcProcess::setStdErrLineCallback(const std::function<void (const QString &)> &callback)
|
||||
void QtcProcess::setStdErrLineCallback(const TextChannelCallback &callback)
|
||||
{
|
||||
d->m_stdErr.outputCallback = callback;
|
||||
d->m_stdErr.emitSingleLines = true;
|
||||
d->m_stdErr.keepRawData = false;
|
||||
}
|
||||
|
||||
void QtcProcess::setTextChannelMode(Channel channel, TextChannelMode mode)
|
||||
{
|
||||
const TextChannelCallback outputCb = [this](const QString &text) {
|
||||
GuardLocker locker(d->m_guard);
|
||||
emit textOnStandardOutput(text);
|
||||
};
|
||||
const TextChannelCallback errorCb = [this](const QString &text) {
|
||||
GuardLocker locker(d->m_guard);
|
||||
emit textOnStandardError(text);
|
||||
};
|
||||
const TextChannelCallback callback = (channel == Channel::Output) ? outputCb : errorCb;
|
||||
ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr;
|
||||
QTC_ASSERT(buffer->m_textChannelMode == TextChannelMode::Off, qWarning()
|
||||
<< "QtcProcess::setTextChannelMode(): Changing text channel mode for"
|
||||
<< (channel == Channel::Output ? "Output": "Error")
|
||||
<< "channel while it was previously set for this channel.");
|
||||
buffer->m_textChannelMode = mode;
|
||||
switch (mode) {
|
||||
case TextChannelMode::Off:
|
||||
buffer->outputCallback = {};
|
||||
buffer->emitSingleLines = true;
|
||||
buffer->keepRawData = true;
|
||||
break;
|
||||
case TextChannelMode::SingleLine:
|
||||
buffer->outputCallback = callback;
|
||||
buffer->emitSingleLines = true;
|
||||
buffer->keepRawData = false;
|
||||
break;
|
||||
case TextChannelMode::MultiLine:
|
||||
buffer->outputCallback = callback;
|
||||
buffer->emitSingleLines = false;
|
||||
buffer->keepRawData = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TextChannelMode QtcProcess::textChannelMode(Channel channel) const
|
||||
{
|
||||
ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr;
|
||||
return buffer->m_textChannelMode;
|
||||
}
|
||||
|
||||
void QtcProcessPrivate::slotTimeout()
|
||||
{
|
||||
if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) {
|
||||
|
@@ -148,10 +148,13 @@ public:
|
||||
void setTimeOutMessageBoxEnabled(bool);
|
||||
void setExitCodeInterpreter(const ExitCodeInterpreter &interpreter);
|
||||
|
||||
void setStdOutCallback(const std::function<void(const QString &)> &callback);
|
||||
void setStdOutLineCallback(const std::function<void(const QString &)> &callback);
|
||||
void setStdErrCallback(const std::function<void(const QString &)> &callback);
|
||||
void setStdErrLineCallback(const std::function<void(const QString &)> &callback);
|
||||
void setStdOutCallback(const TextChannelCallback &callback);
|
||||
void setStdOutLineCallback(const TextChannelCallback &callback);
|
||||
void setStdErrCallback(const TextChannelCallback &callback);
|
||||
void setStdErrLineCallback(const TextChannelCallback &callback);
|
||||
|
||||
void setTextChannelMode(Channel channel, TextChannelMode mode);
|
||||
TextChannelMode textChannelMode(Channel channel) const;
|
||||
|
||||
bool readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS = 30);
|
||||
|
||||
@@ -182,6 +185,8 @@ signals:
|
||||
void done(); // On Starting | Running -> NotRunning state transition
|
||||
void readyReadStandardOutput();
|
||||
void readyReadStandardError();
|
||||
void textOnStandardOutput(const QString &text);
|
||||
void textOnStandardError(const QString &text);
|
||||
|
||||
private:
|
||||
friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const QtcProcess &r);
|
||||
|
@@ -112,7 +112,10 @@ private slots:
|
||||
void exitCode();
|
||||
void runBlockingStdOut_data();
|
||||
void runBlockingStdOut();
|
||||
void runBlockingSignal_data();
|
||||
void runBlockingSignal();
|
||||
void lineCallback();
|
||||
void lineSignal();
|
||||
void waitForStartedAfterStarted();
|
||||
void waitForStartedAfterStarted2();
|
||||
void waitForStartedAndFinished();
|
||||
@@ -937,13 +940,46 @@ void tst_QtcProcess::runBlockingStdOut()
|
||||
QVERIFY2(readLastLine, "Last line was read.");
|
||||
}
|
||||
|
||||
void tst_QtcProcess::runBlockingSignal_data()
|
||||
{
|
||||
runBlockingStdOut_data();
|
||||
}
|
||||
|
||||
void tst_QtcProcess::runBlockingSignal()
|
||||
{
|
||||
QFETCH(bool, withEndl);
|
||||
QFETCH(int, timeOutS);
|
||||
QFETCH(ProcessResult, expectedResult);
|
||||
|
||||
SubProcessConfig subConfig(ProcessTestApp::RunBlockingStdOut::envVar(), withEndl ? "true" : "false");
|
||||
QtcProcess process;
|
||||
subConfig.setupSubProcess(&process);
|
||||
|
||||
process.setTimeoutS(timeOutS);
|
||||
bool readLastLine = false;
|
||||
process.setTextChannelMode(Channel::Output, TextChannelMode::MultiLine);
|
||||
connect(&process, &QtcProcess::textOnStandardOutput,
|
||||
this, [&readLastLine, &process](const QString &out) {
|
||||
if (out.startsWith(s_runBlockingStdOutSubProcessMagicWord)) {
|
||||
readLastLine = true;
|
||||
process.kill();
|
||||
}
|
||||
});
|
||||
process.runBlocking();
|
||||
|
||||
// See also QTCREATORBUG-25667 for why it is a bad idea to use QtcProcess::runBlocking
|
||||
// with interactive cli tools.
|
||||
QCOMPARE(process.result(), expectedResult);
|
||||
QVERIFY2(readLastLine, "Last line was read.");
|
||||
}
|
||||
|
||||
void tst_QtcProcess::lineCallback()
|
||||
{
|
||||
SubProcessConfig subConfig(ProcessTestApp::LineCallback::envVar(), {});
|
||||
QtcProcess process;
|
||||
subConfig.setupSubProcess(&process);
|
||||
|
||||
QStringList lines = QString(s_lineCallbackData).split('|');
|
||||
const QStringList lines = QString(s_lineCallbackData).split('|');
|
||||
int lineNumber = 0;
|
||||
process.setStdErrLineCallback([lines, &lineNumber](const QString &actual) {
|
||||
QString expected = lines.at(lineNumber);
|
||||
@@ -960,6 +996,31 @@ void tst_QtcProcess::lineCallback()
|
||||
QCOMPARE(lineNumber, lines.size());
|
||||
}
|
||||
|
||||
void tst_QtcProcess::lineSignal()
|
||||
{
|
||||
SubProcessConfig subConfig(ProcessTestApp::LineCallback::envVar(), {});
|
||||
QtcProcess process;
|
||||
subConfig.setupSubProcess(&process);
|
||||
|
||||
const QStringList lines = QString(s_lineCallbackData).split('|');
|
||||
int lineNumber = 0;
|
||||
process.setTextChannelMode(Channel::Error, TextChannelMode::SingleLine);
|
||||
connect(&process, &QtcProcess::textOnStandardError,
|
||||
this, [lines, &lineNumber](const QString &actual) {
|
||||
QString expected = lines.at(lineNumber);
|
||||
expected.replace("\r\n", "\n");
|
||||
// Omit some initial lines generated by Qt, e.g.
|
||||
// Warning: Ignoring WAYLAND_DISPLAY on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.
|
||||
if (lineNumber == 0 && actual != expected)
|
||||
return;
|
||||
++lineNumber;
|
||||
QCOMPARE(actual, expected);
|
||||
});
|
||||
process.start();
|
||||
process.waitForFinished();
|
||||
QCOMPARE(lineNumber, lines.size());
|
||||
}
|
||||
|
||||
void tst_QtcProcess::waitForStartedAfterStarted()
|
||||
{
|
||||
SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {});
|
||||
|
Reference in New Issue
Block a user