diff options
author | hjk <hjk@qt.io> | 2021-05-05 18:21:22 +0200 |
---|---|---|
committer | hjk <hjk@qt.io> | 2021-05-10 09:47:51 +0000 |
commit | c23cdd926262a977a987cd887be8f4fd02386d92 (patch) | |
tree | 4b0a6dad23ed35fb4ce2fcb8d87f6254681a1629 /src/libs/utils/synchronousprocess.cpp | |
parent | 040d0cc1ef877a72b20717b193efa2104240db2b (diff) | |
download | qt-creator-c23cdd926262a977a987cd887be8f4fd02386d92.tar.gz |
Utils: Merge {synchronous,qtc}process.{h,cpp} file pairs
Mechanical to prepare merging the actual classes.
Adapting #includes.
Change-Id: I77a2c28129287778bc870c30cb890cd26bc2e62b
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Diffstat (limited to 'src/libs/utils/synchronousprocess.cpp')
-rw-r--r-- | src/libs/utils/synchronousprocess.cpp | 591 |
1 files changed, 0 insertions, 591 deletions
diff --git a/src/libs/utils/synchronousprocess.cpp b/src/libs/utils/synchronousprocess.cpp deleted file mode 100644 index 39a4bb9baf..0000000000 --- a/src/libs/utils/synchronousprocess.cpp +++ /dev/null @@ -1,591 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** 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 "synchronousprocess.h" -#include "executeondestruction.h" -#include "hostosinfo.h" -#include "qtcassert.h" -#include "qtcprocess.h" - -#include <QDebug> -#include <QDir> -#include <QLoggingCategory> -#include <QMessageBox> -#include <QTextCodec> -#include <QThread> -#include <QTimer> - -#include <QApplication> - -#include <algorithm> -#include <limits.h> -#include <memory> - -/*! - \class Utils::SynchronousProcess - - \brief The SynchronousProcess class runs a synchronous process in its own - event loop that blocks only user input events. Thus, it allows for the GUI to - repaint and append output to log windows. - - The stdOut(), stdErr() signals are emitted unbuffered as the process - writes them. - - The stdOutBuffered(), stdErrBuffered() signals are emitted with complete - lines based on the '\\n' marker if they are enabled using - stdOutBufferedSignalsEnabled()/setStdErrBufferedSignalsEnabled(). - They would typically be used for log windows. - - There is a timeout handling that takes effect after the last data have been - read from stdout/stdin (as opposed to waitForFinished(), which measures time - since it was invoked). It is thus also suitable for slow processes that continously - output data (like version system operations). - - The property timeOutMessageBoxEnabled influences whether a message box is - shown asking the user if they want to kill the process on timeout (default: false). - - There are also static utility functions for dealing with fully synchronous - processes, like reading the output with correct timeout handling. - - Caution: This class should NOT be used if there is a chance that the process - triggers opening dialog boxes (for example, by file watchers triggering), - as this will cause event loop problems. -*/ - -enum { debug = 0 }; -enum { syncDebug = 0 }; - -enum { defaultMaxHangTimerCount = 10 }; - -namespace Utils { - -static Q_LOGGING_CATEGORY(processLog, "qtc.utils.synchronousprocess", QtWarningMsg); - -// ----------- SynchronousProcessResponse -void SynchronousProcessResponse::clear() -{ - result = StartFailed; - exitCode = -1; - rawStdOut.clear(); - rawStdErr.clear(); -} - -QString SynchronousProcessResponse::exitMessage(const QString &binary, int timeoutS) const -{ - switch (result) { - case Finished: - return SynchronousProcess::tr("The command \"%1\" finished successfully.").arg(QDir::toNativeSeparators(binary)); - case FinishedError: - return SynchronousProcess::tr("The command \"%1\" terminated with exit code %2.").arg(QDir::toNativeSeparators(binary)).arg(exitCode); - case TerminatedAbnormally: - return SynchronousProcess::tr("The command \"%1\" terminated abnormally.").arg(QDir::toNativeSeparators(binary)); - case StartFailed: - return SynchronousProcess::tr("The command \"%1\" could not be started.").arg(QDir::toNativeSeparators(binary)); - case Hang: - return SynchronousProcess::tr("The command \"%1\" did not respond within the timeout limit (%2 s).") - .arg(QDir::toNativeSeparators(binary)).arg(timeoutS); - } - return QString(); -} - -QByteArray SynchronousProcessResponse::allRawOutput() const -{ - if (!rawStdOut.isEmpty() && !rawStdErr.isEmpty()) { - QByteArray result = rawStdOut; - if (!result.endsWith('\n')) - result += '\n'; - result += rawStdErr; - return result; - } - return !rawStdOut.isEmpty() ? rawStdOut : rawStdErr; -} - -QString SynchronousProcessResponse::allOutput() const -{ - const QString out = stdOut(); - const QString err = stdErr(); - - if (!out.isEmpty() && !err.isEmpty()) { - QString result = out; - if (!result.endsWith('\n')) - result += '\n'; - result += err; - return result; - } - return !out.isEmpty() ? out : err; -} - -QString SynchronousProcessResponse::stdOut() const -{ - return QtcProcess::normalizeNewlines(codec->toUnicode(rawStdOut)); -} - -QString SynchronousProcessResponse::stdErr() const -{ - return QtcProcess::normalizeNewlines(codec->toUnicode(rawStdErr)); -} - -QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse& r) -{ - QDebug nsp = str.nospace(); - nsp << "SynchronousProcessResponse: result=" << r.result << " ex=" << r.exitCode << '\n' - << r.rawStdOut.size() << " bytes stdout, stderr=" << r.rawStdErr << '\n'; - return str; -} - -SynchronousProcessResponse::Result defaultExitCodeInterpreter(int code) -{ - return code ? SynchronousProcessResponse::FinishedError - : SynchronousProcessResponse::Finished; -} - -// Data for one channel buffer (stderr/stdout) -class ChannelBuffer : public QObject -{ - Q_OBJECT - -public: - void clearForRun(); - - QString linesRead(); - void append(const QByteArray &text, bool emitSignals); - - QByteArray rawData; - QString incompleteLineBuffer; // lines not yet signaled - QTextCodec *codec = nullptr; // Not owner - std::unique_ptr<QTextCodec::ConverterState> codecState; - int rawDataPos = 0; - bool bufferedSignalsEnabled = false; - bool firstBuffer = true; - -signals: - void outputBuffered(const QString &text, bool firstTime); -}; - -void ChannelBuffer::clearForRun() -{ - firstBuffer = true; - rawDataPos = 0; - rawData.clear(); - codecState.reset(new QTextCodec::ConverterState); - incompleteLineBuffer.clear(); -} - -/* Check for complete lines read from the device and return them, moving the - * buffer position. */ -QString ChannelBuffer::linesRead() -{ - // Convert and append the new input to the buffer of incomplete lines - const char *start = rawData.constData() + rawDataPos; - const int len = rawData.size() - rawDataPos; - 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(); - - // Get completed lines and remove them from the incompleteLinesBuffer: - const QString lines = QtcProcess::normalizeNewlines(incompleteLineBuffer.left(lastLineIndex + 1)); - incompleteLineBuffer = incompleteLineBuffer.mid(lastLineIndex + 1); - - return lines; -} - -void ChannelBuffer::append(const QByteArray &text, bool emitSignals) -{ - if (text.isEmpty()) - return; - rawData += text; - if (!emitSignals) - return; - - // Buffered. Emit complete lines? - if (bufferedSignalsEnabled) { - const QString lines = linesRead(); - if (!lines.isEmpty()) { - emit outputBuffered(lines, firstBuffer); - firstBuffer = false; - } - } -} - -// ----------- SynchronousProcessPrivate -class SynchronousProcessPrivate { -public: - void clearForRun(); - - QTextCodec *m_codec = QTextCodec::codecForLocale(); - QtcProcess m_process; - QTimer m_timer; - QEventLoop m_eventLoop; - SynchronousProcessResponse m_result; - FilePath m_binary; - ChannelBuffer m_stdOut; - ChannelBuffer m_stdErr; - ExitCodeInterpreter m_exitCodeInterpreter = defaultExitCodeInterpreter; - - int m_hangTimerCount = 0; - int m_maxHangTimerCount = defaultMaxHangTimerCount; - bool m_startFailure = false; - bool m_timeOutMessageBoxEnabled = false; - bool m_waitingForUser = false; -}; - -void SynchronousProcessPrivate::clearForRun() -{ - m_hangTimerCount = 0; - m_stdOut.clearForRun(); - m_stdOut.codec = m_codec; - m_stdErr.clearForRun(); - m_stdErr.codec = m_codec; - m_result.clear(); - m_result.codec = m_codec; - m_startFailure = false; - m_binary = {}; -} - -// ----------- SynchronousProcess -SynchronousProcess::SynchronousProcess() : - d(new SynchronousProcessPrivate) -{ - d->m_timer.setInterval(1000); - connect(&d->m_timer, &QTimer::timeout, this, &SynchronousProcess::slotTimeout); - connect(&d->m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), - this, &SynchronousProcess::finished); - connect(&d->m_process, &QProcess::errorOccurred, this, &SynchronousProcess::error); - connect(&d->m_process, &QProcess::readyReadStandardOutput, - this, [this]() { - d->m_hangTimerCount = 0; - processStdOut(true); - }); - connect(&d->m_process, &QProcess::readyReadStandardError, - this, [this]() { - d->m_hangTimerCount = 0; - processStdErr(true); - }); - connect(&d->m_stdOut, &ChannelBuffer::outputBuffered, this, &SynchronousProcess::stdOutBuffered); - connect(&d->m_stdErr, &ChannelBuffer::outputBuffered, this, &SynchronousProcess::stdErrBuffered); -} - -SynchronousProcess::~SynchronousProcess() -{ - disconnect(&d->m_timer, nullptr, this, nullptr); - disconnect(&d->m_process, nullptr, this, nullptr); - delete d; -} - -void SynchronousProcess::setTimeoutS(int timeoutS) -{ - if (timeoutS > 0) - d->m_maxHangTimerCount = qMax(2, timeoutS); - else - d->m_maxHangTimerCount = INT_MAX / 1000; -} - -int SynchronousProcess::timeoutS() const -{ - return d->m_maxHangTimerCount == (INT_MAX / 1000) ? -1 : d->m_maxHangTimerCount; -} - -void SynchronousProcess::setCodec(QTextCodec *c) -{ - QTC_ASSERT(c, return); - d->m_codec = c; -} - -QTextCodec *SynchronousProcess::codec() const -{ - return d->m_codec; -} - -bool SynchronousProcess::stdOutBufferedSignalsEnabled() const -{ - return d->m_stdOut.bufferedSignalsEnabled; -} - -void SynchronousProcess::setStdOutBufferedSignalsEnabled(bool v) -{ - d->m_stdOut.bufferedSignalsEnabled = v; -} - -bool SynchronousProcess::stdErrBufferedSignalsEnabled() const -{ - return d->m_stdErr.bufferedSignalsEnabled; -} - -void SynchronousProcess::setStdErrBufferedSignalsEnabled(bool v) -{ - d->m_stdErr.bufferedSignalsEnabled = v; -} - -Environment SynchronousProcess::environment() const -{ - return d->m_process.environment(); -} - -bool SynchronousProcess::timeOutMessageBoxEnabled() const -{ - return d->m_timeOutMessageBoxEnabled; -} - -void SynchronousProcess::setTimeOutMessageBoxEnabled(bool v) -{ - d->m_timeOutMessageBoxEnabled = v; -} - -void SynchronousProcess::setEnvironment(const Environment &e) -{ - d->m_process.setEnvironment(Environment(e)); -} - -void SynchronousProcess::setDisableUnixTerminal() -{ - d->m_process.setDisableUnixTerminal(); -} - -void SynchronousProcess::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter) -{ - QTC_ASSERT(interpreter, return); - d->m_exitCodeInterpreter = interpreter; -} - -ExitCodeInterpreter SynchronousProcess::exitCodeInterpreter() const -{ - return d->m_exitCodeInterpreter; -} - -void SynchronousProcess::setWorkingDirectory(const QString &workingDirectory) -{ - d->m_process.setWorkingDirectory(workingDirectory); -} - -QString SynchronousProcess::workingDirectory() const -{ - return d->m_process.workingDirectory(); -} - -QProcess::ProcessChannelMode SynchronousProcess::processChannelMode () const -{ - return d->m_process.processChannelMode(); -} - -void SynchronousProcess::setProcessChannelMode(QProcess::ProcessChannelMode m) -{ - d->m_process.setProcessChannelMode(m); -} - -static bool isGuiThread() -{ - return QThread::currentThread() == QCoreApplication::instance()->thread(); -} - -SynchronousProcessResponse SynchronousProcess::run(const CommandLine &cmd, - const QByteArray &writeData) -{ - qCDebug(processLog).noquote() << "Starting:" << cmd.toUserOutput(); - ExecuteOnDestruction logResult([this] { - qCDebug(processLog) << d->m_result; - }); - - d->clearForRun(); - - d->m_binary = cmd.executable(); - // using QProcess::start() and passing program, args and OpenMode results in a different - // quoting of arguments than using QProcess::setArguments() beforehand and calling start() - // only with the OpenMode - d->m_process.setCommand(cmd); - if (!writeData.isEmpty()) { - connect(&d->m_process, &QProcess::started, this, [this, writeData] { - d->m_process.write(writeData); - d->m_process.closeWriteChannel(); - }); - } - d->m_process.setOpenMode(writeData.isEmpty() ? QIODevice::ReadOnly : QIODevice::ReadWrite); - d->m_process.start(); - - // On Windows, start failure is triggered immediately if the - // executable cannot be found in the path. Do not start the - // event loop in that case. - if (!d->m_startFailure) { - d->m_timer.start(); - if (isGuiThread()) - QApplication::setOverrideCursor(Qt::WaitCursor); - d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents); - processStdOut(false); - processStdErr(false); - - d->m_result.rawStdOut = d->m_stdOut.rawData; - d->m_result.rawStdErr = d->m_stdErr.rawData; - - d->m_timer.stop(); - if (isGuiThread()) - QApplication::restoreOverrideCursor(); - } - - return d->m_result; -} - -SynchronousProcessResponse SynchronousProcess::runBlocking(const CommandLine &cmd) -{ - qCDebug(processLog).noquote() << "Starting blocking:" << cmd.toUserOutput(); - ExecuteOnDestruction logResult([this] { - qCDebug(processLog) << d->m_result; - }); - - d->clearForRun(); - - d->m_binary = cmd.executable(); - d->m_process.setOpenMode(QIODevice::ReadOnly); - d->m_process.setCommand(cmd); - d->m_process.start(); - if (!d->m_process.waitForStarted(d->m_maxHangTimerCount * 1000)) { - d->m_result.result = SynchronousProcessResponse::StartFailed; - return d->m_result; - } - d->m_process.closeWriteChannel(); - if (!d->m_process.waitForFinished(d->m_maxHangTimerCount * 1000)) { - d->m_result.result = SynchronousProcessResponse::Hang; - d->m_process.terminate(); - if (!d->m_process.waitForFinished(1000)) { - d->m_process.kill(); - d->m_process.waitForFinished(1000); - } - } - - if (d->m_process.state() != QProcess::NotRunning) - return d->m_result; - - d->m_result.exitCode = d->m_process.exitCode(); - if (d->m_result.result == SynchronousProcessResponse::StartFailed) { - if (d->m_process.exitStatus() != QProcess::NormalExit) - d->m_result.result = SynchronousProcessResponse::TerminatedAbnormally; - else - d->m_result.result = (exitCodeInterpreter())(d->m_result.exitCode); - } - processStdOut(false); - processStdErr(false); - - d->m_result.rawStdOut = d->m_stdOut.rawData; - d->m_result.rawStdErr = d->m_stdErr.rawData; - - return d->m_result; -} - -bool SynchronousProcess::terminate() -{ - return d->m_process.stopProcess(); -} - -static inline bool askToKill(const QString &binary = QString()) -{ - if (!isGuiThread()) - return true; - const QString title = SynchronousProcess::tr("Process not Responding"); - QString msg = binary.isEmpty() ? - SynchronousProcess::tr("The process is not responding.") : - SynchronousProcess::tr("The process \"%1\" is not responding.").arg(QDir::toNativeSeparators(binary)); - msg += QLatin1Char(' '); - msg += SynchronousProcess::tr("Would you like to terminate it?"); - // Restore the cursor that is set to wait while running. - const bool hasOverrideCursor = QApplication::overrideCursor() != nullptr; - if (hasOverrideCursor) - QApplication::restoreOverrideCursor(); - QMessageBox::StandardButton answer = QMessageBox::question(nullptr, title, msg, QMessageBox::Yes|QMessageBox::No); - if (hasOverrideCursor) - QApplication::setOverrideCursor(Qt::WaitCursor); - return answer == QMessageBox::Yes; -} - -void SynchronousProcess::slotTimeout() -{ - if (!d->m_waitingForUser && (++d->m_hangTimerCount > d->m_maxHangTimerCount)) { - if (debug) - qDebug() << Q_FUNC_INFO << "HANG detected, killing"; - d->m_waitingForUser = true; - const bool terminate = !d->m_timeOutMessageBoxEnabled || askToKill(d->m_binary.toString()); - d->m_waitingForUser = false; - if (terminate) { - d->m_process.stopProcess(); - d->m_result.result = SynchronousProcessResponse::Hang; - } else { - d->m_hangTimerCount = 0; - } - } else { - if (debug) - qDebug() << Q_FUNC_INFO << d->m_hangTimerCount; - } -} - -void SynchronousProcess::finished(int exitCode, QProcess::ExitStatus e) -{ - if (debug) - qDebug() << Q_FUNC_INFO << exitCode << e; - d->m_hangTimerCount = 0; - - switch (e) { - case QProcess::NormalExit: - d->m_result.result = d->m_exitCodeInterpreter(exitCode); - d->m_result.exitCode = exitCode; - break; - case QProcess::CrashExit: - // Was hang detected before and killed? - if (d->m_result.result != SynchronousProcessResponse::Hang) - d->m_result.result = SynchronousProcessResponse::TerminatedAbnormally; - d->m_result.exitCode = -1; - break; - } - d->m_eventLoop.quit(); -} - -void SynchronousProcess::error(QProcess::ProcessError e) -{ - d->m_hangTimerCount = 0; - if (debug) - qDebug() << Q_FUNC_INFO << e; - // Was hang detected before and killed? - if (d->m_result.result != SynchronousProcessResponse::Hang) - d->m_result.result = SynchronousProcessResponse::StartFailed; - d->m_startFailure = true; - d->m_eventLoop.quit(); -} - -void SynchronousProcess::processStdOut(bool emitSignals) -{ - // Handle binary data - d->m_stdOut.append(d->m_process.readAllStandardOutput(), emitSignals); -} - -void SynchronousProcess::processStdErr(bool emitSignals) -{ - // Handle binary data - d->m_stdErr.append(d->m_process.readAllStandardError(), emitSignals); -} - -} // namespace Utils - -#include "synchronousprocess.moc" |