2012-10-02 09:12:39 +02:00
|
|
|
/****************************************************************************
|
2008-12-02 12:01:29 +01:00
|
|
|
**
|
2013-01-28 17:12:19 +01:00
|
|
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
2012-10-02 09:12:39 +02:00
|
|
|
** Contact: http://www.qt-project.org/legal
|
2008-12-02 12:01:29 +01:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** This file is part of Qt Creator.
|
2008-12-02 12:01:29 +01:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** 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 Digia. For licensing terms and
|
|
|
|
|
** conditions see http://qt.digia.com/licensing. For further information
|
|
|
|
|
** use the contact form at http://qt.digia.com/contact-us.
|
2008-12-02 14:17:16 +01:00
|
|
|
**
|
2009-02-25 09:15:00 +01:00
|
|
|
** GNU Lesser General Public License Usage
|
2012-10-02 09:12:39 +02:00
|
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
|
|
|
** General Public License version 2.1 as published by the Free Software
|
|
|
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
|
|
|
** packaging of this file. Please review the following information to
|
|
|
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
|
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
|
|
|
**
|
|
|
|
|
** In addition, as a special exception, Digia gives you certain additional
|
|
|
|
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
2010-12-17 16:01:08 +01:00
|
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2008-12-02 12:01:29 +01:00
|
|
|
|
|
|
|
|
#include "synchronousprocess.h"
|
2012-09-27 17:20:00 +02:00
|
|
|
#include "qtcassert.h"
|
2012-08-23 15:53:58 +02:00
|
|
|
#include "hostosinfo.h"
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QEventLoop>
|
|
|
|
|
#include <QTextCodec>
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QDir>
|
|
|
|
|
#include <QMessageBox>
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QApplication>
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2009-07-20 12:42:22 +02:00
|
|
|
#include <limits.h>
|
|
|
|
|
|
2010-05-21 17:41:51 +02:00
|
|
|
#ifdef Q_OS_UNIX
|
|
|
|
|
# include <unistd.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2011-03-02 17:13:33 +01:00
|
|
|
/*!
|
|
|
|
|
\class Utils::SynchronousProcess
|
|
|
|
|
|
|
|
|
|
\brief 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2008-12-02 12:01:29 +01:00
|
|
|
enum { debug = 0 };
|
2010-03-01 12:29:10 +01:00
|
|
|
enum { syncDebug = 0 };
|
2008-12-02 12:01:29 +01:00
|
|
|
|
|
|
|
|
enum { defaultMaxHangTimerCount = 10 };
|
|
|
|
|
|
|
|
|
|
namespace Utils {
|
|
|
|
|
|
2010-05-21 17:41:51 +02:00
|
|
|
// A special QProcess derivative allowing for terminal control.
|
|
|
|
|
class TerminalControllingProcess : public QProcess {
|
|
|
|
|
public:
|
|
|
|
|
TerminalControllingProcess() : m_flags(0) {}
|
|
|
|
|
|
|
|
|
|
unsigned flags() const { return m_flags; }
|
|
|
|
|
void setFlags(unsigned tc) { m_flags = tc; }
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
virtual void setupChildProcess();
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
unsigned m_flags;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void TerminalControllingProcess::setupChildProcess()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_UNIX
|
|
|
|
|
// Disable terminal by becoming a session leader.
|
|
|
|
|
if (m_flags & SynchronousProcess::UnixTerminalDisabled)
|
|
|
|
|
setsid();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2008-12-02 12:01:29 +01:00
|
|
|
// ----------- SynchronousProcessResponse
|
|
|
|
|
SynchronousProcessResponse::SynchronousProcessResponse() :
|
|
|
|
|
result(StartFailed),
|
|
|
|
|
exitCode(-1)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcessResponse::clear()
|
|
|
|
|
{
|
|
|
|
|
result = StartFailed;
|
|
|
|
|
exitCode = -1;
|
|
|
|
|
stdOut.clear();
|
|
|
|
|
stdErr.clear();
|
|
|
|
|
}
|
|
|
|
|
|
2010-05-21 17:41:51 +02:00
|
|
|
QString SynchronousProcessResponse::exitMessage(const QString &binary, int timeoutMS) const
|
|
|
|
|
{
|
|
|
|
|
switch (result) {
|
|
|
|
|
case Finished:
|
2011-07-11 14:22:10 +00:00
|
|
|
return SynchronousProcess::tr("The command '%1' finished successfully.").arg(QDir::toNativeSeparators(binary));
|
2010-05-21 17:41:51 +02:00
|
|
|
case FinishedError:
|
2011-07-11 14:22:10 +00:00
|
|
|
return SynchronousProcess::tr("The command '%1' terminated with exit code %2.").arg(QDir::toNativeSeparators(binary)).arg(exitCode);
|
2010-05-21 17:41:51 +02:00
|
|
|
break;
|
|
|
|
|
case TerminatedAbnormally:
|
2011-07-11 14:22:10 +00:00
|
|
|
return SynchronousProcess::tr("The command '%1' terminated abnormally.").arg(QDir::toNativeSeparators(binary));
|
2010-05-21 17:41:51 +02:00
|
|
|
case StartFailed:
|
2011-07-11 14:22:10 +00:00
|
|
|
return SynchronousProcess::tr("The command '%1' could not be started.").arg(QDir::toNativeSeparators(binary));
|
2010-05-21 17:41:51 +02:00
|
|
|
case Hang:
|
|
|
|
|
return SynchronousProcess::tr("The command '%1' did not respond within the timeout limit (%2 ms).").
|
2011-07-11 14:22:10 +00:00
|
|
|
arg(QDir::toNativeSeparators(binary)).arg(timeoutMS);
|
2010-05-21 17:41:51 +02:00
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
2009-05-08 12:09:21 +02:00
|
|
|
QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse& r)
|
2008-12-02 12:01:29 +01:00
|
|
|
{
|
|
|
|
|
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;
|
2010-05-21 17:41:51 +02:00
|
|
|
TerminalControllingProcess m_process;
|
2008-12-02 12:01:29 +01:00
|
|
|
QTimer m_timer;
|
|
|
|
|
QEventLoop m_eventLoop;
|
|
|
|
|
SynchronousProcessResponse m_result;
|
|
|
|
|
int m_hangTimerCount;
|
|
|
|
|
int m_maxHangTimerCount;
|
2008-12-04 17:17:16 +01:00
|
|
|
bool m_startFailure;
|
2010-05-21 17:41:51 +02:00
|
|
|
bool m_timeOutMessageBoxEnabled;
|
2013-01-12 23:42:16 +02:00
|
|
|
bool m_waitingForUser;
|
2010-05-21 17:41:51 +02:00
|
|
|
QString m_binary;
|
2008-12-02 12:01:29 +01:00
|
|
|
|
|
|
|
|
ChannelBuffer m_stdOut;
|
|
|
|
|
ChannelBuffer m_stdErr;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SynchronousProcessPrivate::SynchronousProcessPrivate() :
|
|
|
|
|
m_stdOutCodec(0),
|
|
|
|
|
m_hangTimerCount(0),
|
2008-12-04 17:17:16 +01:00
|
|
|
m_maxHangTimerCount(defaultMaxHangTimerCount),
|
2010-05-21 17:41:51 +02:00
|
|
|
m_startFailure(false),
|
2013-01-12 23:42:16 +02:00
|
|
|
m_timeOutMessageBoxEnabled(false),
|
|
|
|
|
m_waitingForUser(false)
|
2008-12-02 12:01:29 +01:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcessPrivate::clearForRun()
|
|
|
|
|
{
|
|
|
|
|
m_hangTimerCount = 0;
|
|
|
|
|
m_stdOut.clearForRun();
|
|
|
|
|
m_stdErr.clearForRun();
|
|
|
|
|
m_result.clear();
|
2008-12-04 17:17:16 +01:00
|
|
|
m_startFailure = false;
|
2010-05-21 17:41:51 +02:00
|
|
|
m_binary.clear();
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ----------- SynchronousProcess
|
|
|
|
|
SynchronousProcess::SynchronousProcess() :
|
2011-09-07 14:26:11 +02:00
|
|
|
d(new SynchronousProcessPrivate)
|
2008-12-02 12:01:29 +01:00
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_timer.setInterval(1000);
|
|
|
|
|
connect(&d->m_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
|
|
|
|
|
connect(&d->m_process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(finished(int,QProcess::ExitStatus)));
|
|
|
|
|
connect(&d->m_process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(error(QProcess::ProcessError)));
|
|
|
|
|
connect(&d->m_process, SIGNAL(readyReadStandardOutput()),
|
2008-12-02 12:01:29 +01:00
|
|
|
this, SLOT(stdOutReady()));
|
2011-09-07 14:26:11 +02:00
|
|
|
connect(&d->m_process, SIGNAL(readyReadStandardError()),
|
2008-12-02 12:01:29 +01:00
|
|
|
this, SLOT(stdErrReady()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SynchronousProcess::~SynchronousProcess()
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
disconnect(&d->m_timer, 0, this, 0);
|
|
|
|
|
disconnect(&d->m_process, 0, this, 0);
|
|
|
|
|
delete d;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::setTimeout(int timeoutMS)
|
|
|
|
|
{
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
if (timeoutMS >= 0)
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_maxHangTimerCount = qMax(2, timeoutMS / 1000);
|
Remove braces for single lines of conditions
#!/usr/bin/env ruby
Dir.glob('**/*.cpp') { |file|
# skip ast (excluding paste, astpath, and canv'ast'imer)
next if file =~ /ast[^eip]|keywords\.|qualifiers|preprocessor|names.cpp/i
s = File.read(file)
next if s.include?('qlalr')
orig = s.dup
s.gsub!(/\n *if [^\n]*{\n[^\n]*\n\s+}(\s+else if [^\n]* {\n[^\n]*\n\s+})*(\s+else {\n[^\n]*\n\s+})?\n/m) { |m|
res = $&
if res =~ /^\s*(\/\/|[A-Z_]{3,})/ # C++ comment or macro (Q_UNUSED, SDEBUG), do not touch braces
res
else
res.gsub!('} else', 'else')
res.gsub!(/\n +} *\n/m, "\n")
res.gsub(/ *{$/, '')
end
}
s.gsub!(/ *$/, '')
File.open(file, 'wb').write(s) if s != orig
}
Change-Id: I3b30ee60df0986f66c02132c65fc38a3fbb6bbdc
Reviewed-by: hjk <qthjk@ovi.com>
2013-01-08 03:32:53 +02:00
|
|
|
else
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_maxHangTimerCount = INT_MAX;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int SynchronousProcess::timeout() const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_maxHangTimerCount == INT_MAX ? -1 : 1000 * d->m_maxHangTimerCount;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::setStdOutCodec(QTextCodec *c)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_stdOutCodec = c;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTextCodec *SynchronousProcess::stdOutCodec() const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_stdOutCodec;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SynchronousProcess::stdOutBufferedSignalsEnabled() const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_stdOut.bufferedSignalsEnabled;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::setStdOutBufferedSignalsEnabled(bool v)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_stdOut.bufferedSignalsEnabled = v;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SynchronousProcess::stdErrBufferedSignalsEnabled() const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_stdErr.bufferedSignalsEnabled;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::setStdErrBufferedSignalsEnabled(bool v)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_stdErr.bufferedSignalsEnabled = v;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList SynchronousProcess::environment() const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_process.environment();
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
2010-05-21 17:41:51 +02:00
|
|
|
bool SynchronousProcess::timeOutMessageBoxEnabled() const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_timeOutMessageBoxEnabled;
|
2010-05-21 17:41:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::setTimeOutMessageBoxEnabled(bool v)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_timeOutMessageBoxEnabled = v;
|
2010-05-21 17:41:51 +02:00
|
|
|
}
|
|
|
|
|
|
2008-12-02 12:01:29 +01:00
|
|
|
void SynchronousProcess::setEnvironment(const QStringList &e)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_process.setEnvironment(e);
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
2009-12-11 16:43:39 +01:00
|
|
|
void SynchronousProcess::setProcessEnvironment(const QProcessEnvironment &environment)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_process.setProcessEnvironment(environment);
|
2009-12-11 16:43:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcessEnvironment SynchronousProcess::processEnvironment() const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_process.processEnvironment();
|
2009-12-11 16:43:39 +01:00
|
|
|
}
|
|
|
|
|
|
2010-05-21 17:41:51 +02:00
|
|
|
unsigned SynchronousProcess::flags() const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_process.flags();
|
2010-05-21 17:41:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::setFlags(unsigned tc)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_process.setFlags(tc);
|
2010-05-21 17:41:51 +02:00
|
|
|
}
|
|
|
|
|
|
2009-07-14 17:20:20 +02:00
|
|
|
void SynchronousProcess::setWorkingDirectory(const QString &workingDirectory)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_process.setWorkingDirectory(workingDirectory);
|
2009-07-14 17:20:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SynchronousProcess::workingDirectory() const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_process.workingDirectory();
|
2009-07-14 17:20:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcess::ProcessChannelMode SynchronousProcess::processChannelMode () const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
return d->m_process.processChannelMode();
|
2009-07-14 17:20:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::setProcessChannelMode(QProcess::ProcessChannelMode m)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_process.setProcessChannelMode(m);
|
2009-07-14 17:20:20 +02:00
|
|
|
}
|
|
|
|
|
|
2008-12-02 12:01:29 +01:00
|
|
|
SynchronousProcessResponse SynchronousProcess::run(const QString &binary,
|
|
|
|
|
const QStringList &args)
|
|
|
|
|
{
|
|
|
|
|
if (debug)
|
|
|
|
|
qDebug() << '>' << Q_FUNC_INFO << binary << args;
|
|
|
|
|
|
2011-09-07 14:26:11 +02:00
|
|
|
d->clearForRun();
|
2008-12-02 12:01:29 +01:00
|
|
|
|
2008-12-04 17:17:16 +01:00
|
|
|
// 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.
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_binary = binary;
|
|
|
|
|
d->m_process.start(binary, args, QIODevice::ReadOnly);
|
|
|
|
|
d->m_process.closeWriteChannel();
|
|
|
|
|
if (!d->m_startFailure) {
|
|
|
|
|
d->m_timer.start();
|
2008-12-04 17:17:16 +01:00
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
|
|
|
|
|
if (d->m_result.result == SynchronousProcessResponse::Finished || d->m_result.result == SynchronousProcessResponse::FinishedError) {
|
2008-12-04 17:17:16 +01:00
|
|
|
processStdOut(false);
|
|
|
|
|
processStdErr(false);
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_result.stdOut = convertStdOut(d->m_stdOut.data);
|
|
|
|
|
d->m_result.stdErr = convertStdErr(d->m_stdErr.data);
|
2008-12-04 17:17:16 +01:00
|
|
|
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_timer.stop();
|
2008-12-04 17:17:16 +01:00
|
|
|
QApplication::restoreOverrideCursor();
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (debug)
|
2011-09-07 14:26:11 +02:00
|
|
|
qDebug() << '<' << Q_FUNC_INFO << binary << d->m_result;
|
|
|
|
|
return d->m_result;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
2010-05-21 17:41:51 +02:00
|
|
|
static inline bool askToKill(const QString &binary = QString())
|
|
|
|
|
{
|
|
|
|
|
const QString title = SynchronousProcess::tr("Process not Responding");
|
|
|
|
|
QString msg = binary.isEmpty() ?
|
|
|
|
|
SynchronousProcess::tr("The process is not responding.") :
|
2011-10-05 13:27:18 +00:00
|
|
|
SynchronousProcess::tr("The process '%1' is not responding.").arg(QDir::toNativeSeparators(binary));
|
2010-05-21 17:41:51 +02:00
|
|
|
msg += QLatin1Char(' ');
|
2010-09-29 12:48:00 +02:00
|
|
|
msg += SynchronousProcess::tr("Would you like to terminate it?");
|
2010-05-21 17:41:51 +02:00
|
|
|
// Restore the cursor that is set to wait while running.
|
|
|
|
|
const bool hasOverrideCursor = QApplication::overrideCursor() != 0;
|
|
|
|
|
if (hasOverrideCursor)
|
|
|
|
|
QApplication::restoreOverrideCursor();
|
|
|
|
|
QMessageBox::StandardButton answer = QMessageBox::question(0, title, msg, QMessageBox::Yes|QMessageBox::No);
|
|
|
|
|
if (hasOverrideCursor)
|
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
|
|
|
|
return answer == QMessageBox::Yes;
|
|
|
|
|
}
|
|
|
|
|
|
2008-12-02 12:01:29 +01:00
|
|
|
void SynchronousProcess::slotTimeout()
|
|
|
|
|
{
|
2013-01-12 23:42:16 +02:00
|
|
|
if (!d->m_waitingForUser && (++d->m_hangTimerCount > d->m_maxHangTimerCount)) {
|
2008-12-04 17:17:16 +01:00
|
|
|
if (debug)
|
|
|
|
|
qDebug() << Q_FUNC_INFO << "HANG detected, killing";
|
2013-01-12 23:42:16 +02:00
|
|
|
d->m_waitingForUser = true;
|
2011-09-07 14:26:11 +02:00
|
|
|
const bool terminate = !d->m_timeOutMessageBoxEnabled || askToKill(d->m_binary);
|
2013-01-12 23:42:16 +02:00
|
|
|
d->m_waitingForUser = false;
|
2010-05-21 17:41:51 +02:00
|
|
|
if (terminate) {
|
2011-09-07 14:26:11 +02:00
|
|
|
SynchronousProcess::stopProcess(d->m_process);
|
|
|
|
|
d->m_result.result = SynchronousProcessResponse::Hang;
|
2010-05-21 17:41:51 +02:00
|
|
|
} else {
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_hangTimerCount = 0;
|
2010-05-21 17:41:51 +02:00
|
|
|
}
|
2008-12-04 17:17:16 +01:00
|
|
|
} else {
|
|
|
|
|
if (debug)
|
2011-09-07 14:26:11 +02:00
|
|
|
qDebug() << Q_FUNC_INFO << d->m_hangTimerCount;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::finished(int exitCode, QProcess::ExitStatus e)
|
|
|
|
|
{
|
|
|
|
|
if (debug)
|
|
|
|
|
qDebug() << Q_FUNC_INFO << exitCode << e;
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_hangTimerCount = 0;
|
2008-12-02 12:01:29 +01:00
|
|
|
switch (e) {
|
|
|
|
|
case QProcess::NormalExit:
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_result.result = exitCode ? SynchronousProcessResponse::FinishedError : SynchronousProcessResponse::Finished;
|
|
|
|
|
d->m_result.exitCode = exitCode;
|
2008-12-02 12:01:29 +01:00
|
|
|
break;
|
|
|
|
|
case QProcess::CrashExit:
|
2008-12-04 17:17:16 +01:00
|
|
|
// Was hang detected before and killed?
|
2011-09-07 14:26:11 +02:00
|
|
|
if (d->m_result.result != SynchronousProcessResponse::Hang)
|
|
|
|
|
d->m_result.result = SynchronousProcessResponse::TerminatedAbnormally;
|
|
|
|
|
d->m_result.exitCode = -1;
|
2008-12-02 12:01:29 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_eventLoop.quit();
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::error(QProcess::ProcessError e)
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_hangTimerCount = 0;
|
2008-12-02 12:01:29 +01:00
|
|
|
if (debug)
|
|
|
|
|
qDebug() << Q_FUNC_INFO << e;
|
2008-12-04 17:17:16 +01:00
|
|
|
// Was hang detected before and killed?
|
2011-09-07 14:26:11 +02:00
|
|
|
if (d->m_result.result != SynchronousProcessResponse::Hang)
|
|
|
|
|
d->m_result.result = SynchronousProcessResponse::StartFailed;
|
|
|
|
|
d->m_startFailure = true;
|
|
|
|
|
d->m_eventLoop.quit();
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::stdOutReady()
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_hangTimerCount = 0;
|
2008-12-02 12:01:29 +01:00
|
|
|
processStdOut(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::stdErrReady()
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_hangTimerCount = 0;
|
2008-12-02 12:01:29 +01:00
|
|
|
processStdErr(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SynchronousProcess::convertStdErr(const QByteArray &ba)
|
|
|
|
|
{
|
2009-02-19 12:09:05 +01:00
|
|
|
return QString::fromLocal8Bit(ba.constData(), ba.size()).remove(QLatin1Char('\r'));
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SynchronousProcess::convertStdOut(const QByteArray &ba) const
|
|
|
|
|
{
|
2011-09-07 14:26:11 +02:00
|
|
|
QString stdOut = d->m_stdOutCodec ? d->m_stdOutCodec->toUnicode(ba) : QString::fromLocal8Bit(ba.constData(), ba.size());
|
2008-12-02 12:01:29 +01:00
|
|
|
return stdOut.remove(QLatin1Char('\r'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::processStdOut(bool emitSignals)
|
|
|
|
|
{
|
|
|
|
|
// Handle binary data
|
2011-09-07 14:26:11 +02:00
|
|
|
const QByteArray ba = d->m_process.readAllStandardOutput();
|
2008-12-02 12:01:29 +01:00
|
|
|
if (debug > 1)
|
|
|
|
|
qDebug() << Q_FUNC_INFO << emitSignals << ba;
|
|
|
|
|
if (!ba.isEmpty()) {
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_stdOut.data += ba;
|
2008-12-02 12:01:29 +01:00
|
|
|
if (emitSignals) {
|
|
|
|
|
// Emit binary signals
|
2011-09-07 14:26:11 +02:00
|
|
|
emit stdOut(ba, d->m_stdOut.firstData);
|
|
|
|
|
d->m_stdOut.firstData = false;
|
2008-12-02 12:01:29 +01:00
|
|
|
// Buffered. Emit complete lines?
|
2011-09-07 14:26:11 +02:00
|
|
|
if (d->m_stdOut.bufferedSignalsEnabled) {
|
|
|
|
|
const QByteArray lines = d->m_stdOut.linesRead();
|
2008-12-02 12:01:29 +01:00
|
|
|
if (!lines.isEmpty()) {
|
2011-09-07 14:26:11 +02:00
|
|
|
emit stdOutBuffered(convertStdOut(lines), d->m_stdOut.firstBuffer);
|
|
|
|
|
d->m_stdOut.firstBuffer = false;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SynchronousProcess::processStdErr(bool emitSignals)
|
|
|
|
|
{
|
|
|
|
|
// Handle binary data
|
2011-09-07 14:26:11 +02:00
|
|
|
const QByteArray ba = d->m_process.readAllStandardError();
|
2008-12-02 12:01:29 +01:00
|
|
|
if (debug > 1)
|
|
|
|
|
qDebug() << Q_FUNC_INFO << emitSignals << ba;
|
|
|
|
|
if (!ba.isEmpty()) {
|
2011-09-07 14:26:11 +02:00
|
|
|
d->m_stdErr.data += ba;
|
2008-12-02 12:01:29 +01:00
|
|
|
if (emitSignals) {
|
|
|
|
|
// Emit binary signals
|
2011-09-07 14:26:11 +02:00
|
|
|
emit stdErr(ba, d->m_stdErr.firstData);
|
|
|
|
|
d->m_stdErr.firstData = false;
|
|
|
|
|
if (d->m_stdErr.bufferedSignalsEnabled) {
|
2008-12-02 12:01:29 +01:00
|
|
|
// Buffered. Emit complete lines?
|
2011-09-07 14:26:11 +02:00
|
|
|
const QByteArray lines = d->m_stdErr.linesRead();
|
2008-12-02 12:01:29 +01:00
|
|
|
if (!lines.isEmpty()) {
|
2011-09-07 14:26:11 +02:00
|
|
|
emit stdErrBuffered(convertStdErr(lines), d->m_stdErr.firstBuffer);
|
|
|
|
|
d->m_stdErr.firstBuffer = false;
|
2008-12-02 12:01:29 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-05-21 17:41:51 +02:00
|
|
|
QSharedPointer<QProcess> SynchronousProcess::createProcess(unsigned flags)
|
|
|
|
|
{
|
|
|
|
|
TerminalControllingProcess *process = new TerminalControllingProcess;
|
|
|
|
|
process->setFlags(flags);
|
|
|
|
|
return QSharedPointer<QProcess>(process);
|
|
|
|
|
}
|
|
|
|
|
|
2010-03-01 10:06:05 +01:00
|
|
|
// Static utilities: Keep running as long as it gets data.
|
|
|
|
|
bool SynchronousProcess::readDataFromProcess(QProcess &p, int timeOutMS,
|
2010-05-21 17:41:51 +02:00
|
|
|
QByteArray *stdOut, QByteArray *stdErr,
|
|
|
|
|
bool showTimeOutMessageBox)
|
2010-03-01 10:06:05 +01:00
|
|
|
{
|
2010-03-01 12:29:10 +01:00
|
|
|
if (syncDebug)
|
|
|
|
|
qDebug() << ">readDataFromProcess" << timeOutMS;
|
2010-03-01 10:06:05 +01:00
|
|
|
if (p.state() != QProcess::Running) {
|
|
|
|
|
qWarning("readDataFromProcess: Process in non-running state passed in.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-17 08:01:25 +02:00
|
|
|
QTC_ASSERT(p.readChannel() == QProcess::StandardOutput, return false);
|
2010-03-01 12:29:10 +01:00
|
|
|
|
2010-03-01 10:06:05 +01:00
|
|
|
// Keep the process running until it has no longer has data
|
|
|
|
|
bool finished = false;
|
|
|
|
|
bool hasData = false;
|
|
|
|
|
do {
|
|
|
|
|
finished = p.waitForFinished(timeOutMS);
|
2010-03-01 12:29:10 +01:00
|
|
|
hasData = false;
|
|
|
|
|
// First check 'stdout'
|
|
|
|
|
if (p.bytesAvailable()) { // applies to readChannel() only
|
|
|
|
|
hasData = true;
|
2010-03-01 10:06:05 +01:00
|
|
|
const QByteArray newStdOut = p.readAllStandardOutput();
|
|
|
|
|
if (stdOut)
|
|
|
|
|
stdOut->append(newStdOut);
|
2010-03-01 12:29:10 +01:00
|
|
|
}
|
|
|
|
|
// Check 'stderr' separately. This is a special handling
|
|
|
|
|
// for 'git pull' and the like which prints its progress on stderr.
|
|
|
|
|
const QByteArray newStdErr = p.readAllStandardError();
|
|
|
|
|
if (!newStdErr.isEmpty()) {
|
|
|
|
|
hasData = true;
|
2010-03-01 10:06:05 +01:00
|
|
|
if (stdErr)
|
|
|
|
|
stdErr->append(newStdErr);
|
|
|
|
|
}
|
2010-05-21 17:41:51 +02:00
|
|
|
// Prompt user, pretend we have data if says 'No'.
|
|
|
|
|
const bool hang = !hasData && !finished;
|
|
|
|
|
if (hang && showTimeOutMessageBox) {
|
|
|
|
|
if (!askToKill())
|
|
|
|
|
hasData = true;
|
|
|
|
|
}
|
2010-03-01 10:06:05 +01:00
|
|
|
} while (hasData && !finished);
|
2010-03-01 12:29:10 +01:00
|
|
|
if (syncDebug)
|
|
|
|
|
qDebug() << "<readDataFromProcess" << finished;
|
2010-03-01 10:06:05 +01:00
|
|
|
return finished;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool SynchronousProcess::stopProcess(QProcess &p)
|
|
|
|
|
{
|
|
|
|
|
if (p.state() != QProcess::Running)
|
|
|
|
|
return true;
|
|
|
|
|
p.terminate();
|
|
|
|
|
if (p.waitForFinished(300))
|
|
|
|
|
return true;
|
|
|
|
|
p.kill();
|
|
|
|
|
return p.waitForFinished(300);
|
|
|
|
|
}
|
|
|
|
|
|
2009-02-19 17:47:44 +01:00
|
|
|
// Path utilities
|
|
|
|
|
|
|
|
|
|
// Locate a binary in a directory, applying all kinds of
|
|
|
|
|
// extensions the operating system supports.
|
|
|
|
|
static QString checkBinary(const QDir &dir, const QString &binary)
|
|
|
|
|
{
|
|
|
|
|
// naive UNIX approach
|
|
|
|
|
const QFileInfo info(dir.filePath(binary));
|
|
|
|
|
if (info.isFile() && info.isExecutable())
|
|
|
|
|
return info.absoluteFilePath();
|
|
|
|
|
|
|
|
|
|
// Does the OS have some weird extension concept or does the
|
|
|
|
|
// binary have a 3 letter extension?
|
2012-08-23 15:53:58 +02:00
|
|
|
if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
|
2009-02-19 17:47:44 +01:00
|
|
|
return QString();
|
|
|
|
|
const int dotIndex = binary.lastIndexOf(QLatin1Char('.'));
|
|
|
|
|
if (dotIndex != -1 && dotIndex == binary.size() - 4)
|
|
|
|
|
return QString();
|
|
|
|
|
|
2012-08-23 15:53:58 +02:00
|
|
|
switch (HostOsInfo::hostOs()) {
|
|
|
|
|
case HostOsInfo::HostOsLinux:
|
|
|
|
|
case HostOsInfo::HostOsOtherUnix:
|
|
|
|
|
case HostOsInfo::HostOsOther:
|
2009-02-19 17:47:44 +01:00
|
|
|
break;
|
2012-08-23 15:53:58 +02:00
|
|
|
case HostOsInfo::HostOsWindows: {
|
2009-02-19 17:47:44 +01:00
|
|
|
static const char *windowsExtensions[] = {".cmd", ".bat", ".exe", ".com" };
|
|
|
|
|
// Check the Windows extensions using the order
|
|
|
|
|
const int windowsExtensionCount = sizeof(windowsExtensions)/sizeof(const char*);
|
|
|
|
|
for (int e = 0; e < windowsExtensionCount; e ++) {
|
|
|
|
|
const QFileInfo windowsBinary(dir.filePath(binary + QLatin1String(windowsExtensions[e])));
|
|
|
|
|
if (windowsBinary.isFile() && windowsBinary.isExecutable())
|
|
|
|
|
return windowsBinary.absoluteFilePath();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
2012-08-23 15:53:58 +02:00
|
|
|
case HostOsInfo::HostOsMac: {
|
2009-02-19 17:47:44 +01:00
|
|
|
// Check for Mac app folders
|
|
|
|
|
const QFileInfo appFolder(dir.filePath(binary + QLatin1String(".app")));
|
|
|
|
|
if (appFolder.isDir()) {
|
|
|
|
|
QString macBinaryPath = appFolder.absoluteFilePath();
|
|
|
|
|
macBinaryPath += QLatin1String("/Contents/MacOS/");
|
|
|
|
|
macBinaryPath += binary;
|
|
|
|
|
const QFileInfo macBinary(macBinaryPath);
|
|
|
|
|
if (macBinary.isFile() && macBinary.isExecutable())
|
|
|
|
|
return macBinary.absoluteFilePath();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SynchronousProcess::locateBinary(const QString &path, const QString &binary)
|
|
|
|
|
{
|
|
|
|
|
// Absolute file?
|
|
|
|
|
const QFileInfo absInfo(binary);
|
|
|
|
|
if (absInfo.isAbsolute())
|
|
|
|
|
return checkBinary(absInfo.dir(), absInfo.fileName());
|
|
|
|
|
|
|
|
|
|
// Windows finds binaries in the current directory
|
2012-08-23 15:53:58 +02:00
|
|
|
if (HostOsInfo::isWindowsHost()) {
|
2009-02-19 17:47:44 +01:00
|
|
|
const QString currentDirBinary = checkBinary(QDir::current(), binary);
|
|
|
|
|
if (!currentDirBinary.isEmpty())
|
|
|
|
|
return currentDirBinary;
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-31 16:39:20 +02:00
|
|
|
const QStringList paths = path.split(HostOsInfo::pathListSeparator());
|
2009-02-19 17:47:44 +01:00
|
|
|
if (paths.empty())
|
|
|
|
|
return QString();
|
|
|
|
|
const QStringList::const_iterator cend = paths.constEnd();
|
|
|
|
|
for (QStringList::const_iterator it = paths.constBegin(); it != cend; ++it) {
|
|
|
|
|
const QDir dir(*it);
|
|
|
|
|
const QString rc = checkBinary(dir, binary);
|
|
|
|
|
if (!rc.isEmpty())
|
|
|
|
|
return rc;
|
|
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString SynchronousProcess::locateBinary(const QString &binary)
|
|
|
|
|
{
|
|
|
|
|
const QByteArray path = qgetenv("PATH");
|
|
|
|
|
return locateBinary(QString::fromLocal8Bit(path), binary);
|
|
|
|
|
}
|
|
|
|
|
|
2008-12-02 14:09:21 +01:00
|
|
|
} // namespace Utils
|