summaryrefslogtreecommitdiff
path: root/src/libs/utils/synchronousprocess.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/utils/synchronousprocess.cpp')
-rw-r--r--src/libs/utils/synchronousprocess.cpp356
1 files changed, 356 insertions, 0 deletions
diff --git a/src/libs/utils/synchronousprocess.cpp b/src/libs/utils/synchronousprocess.cpp
new file mode 100644
index 0000000000..71bcdffb6c
--- /dev/null
+++ b/src/libs/utils/synchronousprocess.cpp
@@ -0,0 +1,356 @@
+/***************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+**
+** Non-Open Source Usage
+**
+** Licensees may use this file in accordance with the Qt Beta Version
+** License Agreement, Agreement version 2.2 provided with the Software or,
+** alternatively, in accordance with the terms contained in a written
+** agreement between you and Nokia.
+**
+** GNU General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License versions 2.0 or 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the packaging
+** of this file. Please review the following information to ensure GNU
+** General Public Licensing requirements will be met:
+**
+** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt GPL Exception version
+** 1.2, included in the file GPL_EXCEPTION.txt in this package.
+**
+***************************************************************************/
+
+#include "synchronousprocess.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QTimer>
+#include <QtCore/QEventLoop>
+#include <QtCore/QTextCodec>
+
+#include <QtGui/QApplication>
+
+enum { debug = 0 };
+
+enum { defaultMaxHangTimerCount = 10 };
+
+namespace Core {
+namespace Utils {
+
+// ----------- SynchronousProcessResponse
+SynchronousProcessResponse::SynchronousProcessResponse() :
+ result(StartFailed),
+ exitCode(-1)
+{
+}
+
+void SynchronousProcessResponse::clear()
+{
+ result = StartFailed;
+ exitCode = -1;
+ stdOut.clear();
+ stdErr.clear();
+}
+
+QWORKBENCH_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse& r)
+{
+ QDebug nsp = str.nospace();
+ nsp << "SynchronousProcessResponse: result=" << r.result << " ex=" << r.exitCode << '\n'
+ << r.stdOut.size() << " bytes stdout, stderr=" << r.stdErr << '\n';
+ return str;
+}
+
+// Data for one channel buffer (stderr/stdout)
+struct ChannelBuffer {
+ ChannelBuffer();
+ void clearForRun();
+ QByteArray linesRead();
+
+ QByteArray data;
+ bool firstData;
+ bool bufferedSignalsEnabled;
+ bool firstBuffer;
+ int bufferPos;
+};
+
+ChannelBuffer::ChannelBuffer() :
+ firstData(true),
+ bufferedSignalsEnabled(false),
+ firstBuffer(true),
+ bufferPos(0)
+{
+}
+
+void ChannelBuffer::clearForRun()
+{
+ firstData = true;
+ firstBuffer = true;
+ bufferPos = 0;
+}
+
+/* Check for complete lines read from the device and return them, moving the
+ * buffer position. This is based on the assumption that '\n' is the new line
+ * marker in any sane codec. */
+QByteArray ChannelBuffer::linesRead()
+{
+ // Any new lines?
+ const int lastLineIndex = data.lastIndexOf('\n');
+ if (lastLineIndex == -1 || lastLineIndex <= bufferPos)
+ return QByteArray();
+ const int nextBufferPos = lastLineIndex + 1;
+ const QByteArray lines = data.mid(bufferPos, nextBufferPos - bufferPos);
+ bufferPos = nextBufferPos;
+ return lines;
+}
+
+// ----------- SynchronousProcessPrivate
+struct SynchronousProcessPrivate {
+ SynchronousProcessPrivate();
+ void clearForRun();
+
+ QTextCodec *m_stdOutCodec;
+ QProcess m_process;
+ QTimer m_timer;
+ QEventLoop m_eventLoop;
+ SynchronousProcessResponse m_result;
+ int m_hangTimerCount;
+ int m_maxHangTimerCount;
+
+ ChannelBuffer m_stdOut;
+ ChannelBuffer m_stdErr;
+};
+
+SynchronousProcessPrivate::SynchronousProcessPrivate() :
+ m_stdOutCodec(0),
+ m_hangTimerCount(0),
+ m_maxHangTimerCount(defaultMaxHangTimerCount)
+{
+}
+
+void SynchronousProcessPrivate::clearForRun()
+{
+ m_hangTimerCount = 0;
+ m_stdOut.clearForRun();
+ m_stdErr.clearForRun();
+ m_result.clear();
+}
+
+// ----------- SynchronousProcess
+SynchronousProcess::SynchronousProcess() :
+ m_d(new SynchronousProcessPrivate)
+{
+ m_d->m_timer.setInterval(1000);
+ connect(&m_d->m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
+ connect(&m_d->m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished(int,QProcess::ExitStatus)));
+ connect(&m_d->m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
+ connect(&m_d->m_process, SIGNAL(readyReadStandardOutput()),
+ this, SLOT(stdOutReady()));
+ connect(&m_d->m_process, SIGNAL(readyReadStandardError()),
+ this, SLOT(stdErrReady()));
+}
+
+SynchronousProcess::~SynchronousProcess()
+{
+ delete m_d;
+}
+
+void SynchronousProcess::setTimeout(int timeoutMS)
+{
+ m_d->m_maxHangTimerCount = qMax(2, timeoutMS / 1000);
+}
+
+int SynchronousProcess::timeout() const
+{
+ return 1000 * m_d->m_maxHangTimerCount;
+}
+
+void SynchronousProcess::setStdOutCodec(QTextCodec *c)
+{
+ m_d->m_stdOutCodec = c;
+}
+
+QTextCodec *SynchronousProcess::stdOutCodec() const
+{
+ return m_d->m_stdOutCodec;
+}
+
+bool SynchronousProcess::stdOutBufferedSignalsEnabled() const
+{
+ return m_d->m_stdOut.bufferedSignalsEnabled;
+}
+
+void SynchronousProcess::setStdOutBufferedSignalsEnabled(bool v)
+{
+ m_d->m_stdOut.bufferedSignalsEnabled = v;
+}
+
+bool SynchronousProcess::stdErrBufferedSignalsEnabled() const
+{
+ return m_d->m_stdErr.bufferedSignalsEnabled;
+}
+
+void SynchronousProcess::setStdErrBufferedSignalsEnabled(bool v)
+{
+ m_d->m_stdErr.bufferedSignalsEnabled = v;
+}
+
+QStringList SynchronousProcess::environment() const
+{
+ return m_d->m_process.environment();
+}
+
+void SynchronousProcess::setEnvironment(const QStringList &e)
+{
+ m_d->m_process.setEnvironment(e);
+}
+
+SynchronousProcessResponse SynchronousProcess::run(const QString &binary,
+ const QStringList &args)
+{
+ if (debug)
+ qDebug() << '>' << Q_FUNC_INFO << binary << args;
+
+ m_d->clearForRun();
+ m_d->m_timer.start();
+
+ QApplication::setOverrideCursor(Qt::WaitCursor);
+
+ m_d->m_process.start(binary, args, QIODevice::ReadOnly);
+ m_d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
+ if (m_d->m_result.result == SynchronousProcessResponse::Finished || m_d->m_result.result == SynchronousProcessResponse::FinishedError) {
+ processStdOut(false);
+ processStdErr(false);
+ }
+
+ m_d->m_result.stdOut = convertStdOut(m_d->m_stdOut.data);
+ m_d->m_result.stdErr = convertStdErr(m_d->m_stdErr.data);
+
+ m_d->m_timer.stop();
+ QApplication::restoreOverrideCursor();
+
+ if (debug)
+ qDebug() << '<' << Q_FUNC_INFO << binary << m_d->m_result;
+ return m_d->m_result;
+}
+
+void SynchronousProcess::slotTimeout()
+{
+ if (++m_d->m_hangTimerCount > m_d->m_maxHangTimerCount) {
+ m_d->m_process.kill();
+ m_d->m_result.result = SynchronousProcessResponse::Hang;
+ }
+
+ if (debug)
+ qDebug() << Q_FUNC_INFO << m_d->m_hangTimerCount;
+}
+
+void SynchronousProcess::finished(int exitCode, QProcess::ExitStatus e)
+{
+ if (debug)
+ qDebug() << Q_FUNC_INFO << exitCode << e;
+ m_d->m_hangTimerCount = 0;
+ switch (e) {
+ case QProcess::NormalExit:
+ m_d->m_result.result = exitCode ? SynchronousProcessResponse::FinishedError : SynchronousProcessResponse::Finished;
+ m_d->m_result.exitCode = exitCode;
+ break;
+ case QProcess::CrashExit:
+ m_d->m_result.result = SynchronousProcessResponse::TerminatedAbnormally;
+ m_d->m_result.exitCode = -1;
+ break;
+ }
+ m_d->m_eventLoop.quit();
+}
+
+void SynchronousProcess::error(QProcess::ProcessError e)
+{
+ m_d->m_hangTimerCount = 0;
+ if (debug)
+ qDebug() << Q_FUNC_INFO << e;
+ m_d->m_result.result = SynchronousProcessResponse::StartFailed;
+ m_d->m_eventLoop.quit();
+}
+
+void SynchronousProcess::stdOutReady()
+{
+ m_d->m_hangTimerCount = 0;
+ processStdOut(true);
+}
+
+void SynchronousProcess::stdErrReady()
+{
+ m_d->m_hangTimerCount = 0;
+ processStdErr(true);
+}
+
+QString SynchronousProcess::convertStdErr(const QByteArray &ba)
+{
+ return QString::fromLocal8Bit(ba).remove(QLatin1Char('\r'));
+}
+
+QString SynchronousProcess::convertStdOut(const QByteArray &ba) const
+{
+ QString stdOut = m_d->m_stdOutCodec ? m_d->m_stdOutCodec->toUnicode(ba) : QString::fromLocal8Bit(ba);
+ return stdOut.remove(QLatin1Char('\r'));
+}
+
+void SynchronousProcess::processStdOut(bool emitSignals)
+{
+ // Handle binary data
+ const QByteArray ba = m_d->m_process.readAllStandardOutput();
+ if (debug > 1)
+ qDebug() << Q_FUNC_INFO << emitSignals << ba;
+ if (!ba.isEmpty()) {
+ m_d->m_stdOut.data += ba;
+ if (emitSignals) {
+ // Emit binary signals
+ emit stdOut(ba, m_d->m_stdOut.firstData);
+ m_d->m_stdOut.firstData = false;
+ // Buffered. Emit complete lines?
+ if (m_d->m_stdOut.bufferedSignalsEnabled) {
+ const QByteArray lines = m_d->m_stdOut.linesRead();
+ if (!lines.isEmpty()) {
+ emit stdOutBuffered(convertStdOut(lines), m_d->m_stdOut.firstBuffer);
+ m_d->m_stdOut.firstBuffer = false;
+ }
+ }
+ }
+ }
+}
+
+void SynchronousProcess::processStdErr(bool emitSignals)
+{
+ // Handle binary data
+ const QByteArray ba = m_d->m_process.readAllStandardError();
+ if (debug > 1)
+ qDebug() << Q_FUNC_INFO << emitSignals << ba;
+ if (!ba.isEmpty()) {
+ m_d->m_stdErr.data += ba;
+ if (emitSignals) {
+ // Emit binary signals
+ emit stdErr(ba, m_d->m_stdErr.firstData);
+ m_d->m_stdErr.firstData = false;
+ if (m_d->m_stdErr.bufferedSignalsEnabled) {
+ // Buffered. Emit complete lines?
+ const QByteArray lines = m_d->m_stdErr.linesRead();
+ if (!lines.isEmpty()) {
+ emit stdErrBuffered(convertStdErr(lines), m_d->m_stdErr.firstBuffer);
+ m_d->m_stdErr.firstBuffer = false;
+ }
+ }
+ }
+ }
+}
+
+}
+}