synchronousprocess.cpp 21.3 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2 3 4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
Eike Ziller's avatar
Eike Ziller committed
7
** Contact: http://www.qt-project.org/
con's avatar
con committed
8
**
9
**
10
** GNU Lesser General Public License Usage
11
**
hjk's avatar
hjk committed
12 13 14 15 16 17
** 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.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21 22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23 24 25 26 27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
con's avatar
con committed
28
**
29
**************************************************************************/
con's avatar
con committed
30 31

#include "synchronousprocess.h"
32 33

#include "hostosinfo.h"
34
#include <qtcassert.h>
con's avatar
con committed
35

36 37 38 39 40 41 42
#include <QDebug>
#include <QTimer>
#include <QEventLoop>
#include <QTextCodec>
#include <QFileInfo>
#include <QDir>
#include <QMessageBox>
con's avatar
con committed
43

44
#include <QApplication>
con's avatar
con committed
45

46 47
#include <limits.h>

48 49 50 51
#ifdef Q_OS_UNIX
#    include <unistd.h>
#endif

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
/*!
    \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.
*/

con's avatar
con committed
83
enum { debug = 0 };
84
enum { syncDebug = 0 };
con's avatar
con committed
85 86 87 88 89

enum { defaultMaxHangTimerCount = 10 };

namespace Utils {

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
// 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
}

con's avatar
con committed
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
// ----------- SynchronousProcessResponse
SynchronousProcessResponse::SynchronousProcessResponse() :
   result(StartFailed),
   exitCode(-1)
{
}

void SynchronousProcessResponse::clear()
{
    result = StartFailed;
    exitCode = -1;
    stdOut.clear();
    stdErr.clear();
}

129 130 131 132
QString SynchronousProcessResponse::exitMessage(const QString &binary, int timeoutMS) const
{
    switch (result) {
    case Finished:
133
        return SynchronousProcess::tr("The command '%1' finished successfully.").arg(QDir::toNativeSeparators(binary));
134
    case FinishedError:
135
        return SynchronousProcess::tr("The command '%1' terminated with exit code %2.").arg(QDir::toNativeSeparators(binary)).arg(exitCode);
136 137
        break;
    case TerminatedAbnormally:
138
        return SynchronousProcess::tr("The command '%1' terminated abnormally.").arg(QDir::toNativeSeparators(binary));
139
    case StartFailed:
140
        return SynchronousProcess::tr("The command '%1' could not be started.").arg(QDir::toNativeSeparators(binary));
141 142
    case Hang:
        return SynchronousProcess::tr("The command '%1' did not respond within the timeout limit (%2 ms).").
143
                arg(QDir::toNativeSeparators(binary)).arg(timeoutMS);
144 145 146 147
    }
    return QString();
}

148
QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const SynchronousProcessResponse& r)
con's avatar
con committed
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
{
    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;
205
    TerminalControllingProcess m_process;
con's avatar
con committed
206 207 208 209 210
    QTimer m_timer;
    QEventLoop m_eventLoop;
    SynchronousProcessResponse m_result;
    int m_hangTimerCount;
    int m_maxHangTimerCount;
211
    bool m_startFailure;
212 213
    bool m_timeOutMessageBoxEnabled;
    QString m_binary;
con's avatar
con committed
214 215 216 217 218 219 220 221

    ChannelBuffer m_stdOut;
    ChannelBuffer m_stdErr;
};

SynchronousProcessPrivate::SynchronousProcessPrivate() :
    m_stdOutCodec(0),
    m_hangTimerCount(0),
222
    m_maxHangTimerCount(defaultMaxHangTimerCount),
223 224
    m_startFailure(false),
    m_timeOutMessageBoxEnabled(false)
con's avatar
con committed
225 226 227 228 229 230 231 232 233
{
}

void SynchronousProcessPrivate::clearForRun()
{
    m_hangTimerCount = 0;
    m_stdOut.clearForRun();
    m_stdErr.clearForRun();
    m_result.clear();
234
    m_startFailure = false;
235
    m_binary.clear();
con's avatar
con committed
236 237 238 239
}

// ----------- SynchronousProcess
SynchronousProcess::SynchronousProcess() :
hjk's avatar
hjk committed
240
    d(new SynchronousProcessPrivate)
con's avatar
con committed
241
{
hjk's avatar
hjk committed
242 243 244 245 246
    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()),
con's avatar
con committed
247
            this, SLOT(stdOutReady()));
hjk's avatar
hjk committed
248
    connect(&d->m_process, SIGNAL(readyReadStandardError()),
con's avatar
con committed
249 250 251 252 253
            this, SLOT(stdErrReady()));
}

SynchronousProcess::~SynchronousProcess()
{
hjk's avatar
hjk committed
254 255 256
    disconnect(&d->m_timer, 0, this, 0);
    disconnect(&d->m_process, 0, this, 0);
    delete d;
con's avatar
con committed
257 258 259 260
}

void SynchronousProcess::setTimeout(int timeoutMS)
{
261
    if (timeoutMS >= 0) {
hjk's avatar
hjk committed
262
        d->m_maxHangTimerCount = qMax(2, timeoutMS / 1000);
263
    } else {
hjk's avatar
hjk committed
264
        d->m_maxHangTimerCount = INT_MAX;
265
    }
con's avatar
con committed
266 267 268 269
}

int SynchronousProcess::timeout() const
{
hjk's avatar
hjk committed
270
    return d->m_maxHangTimerCount == INT_MAX ? -1 : 1000 * d->m_maxHangTimerCount;
con's avatar
con committed
271 272 273 274
}

void SynchronousProcess::setStdOutCodec(QTextCodec *c)
{
hjk's avatar
hjk committed
275
    d->m_stdOutCodec = c;
con's avatar
con committed
276 277 278 279
}

QTextCodec *SynchronousProcess::stdOutCodec() const
{
hjk's avatar
hjk committed
280
    return d->m_stdOutCodec;
con's avatar
con committed
281 282 283 284
}

bool SynchronousProcess::stdOutBufferedSignalsEnabled() const
{
hjk's avatar
hjk committed
285
    return d->m_stdOut.bufferedSignalsEnabled;
con's avatar
con committed
286 287 288 289
}

void SynchronousProcess::setStdOutBufferedSignalsEnabled(bool v)
{
hjk's avatar
hjk committed
290
    d->m_stdOut.bufferedSignalsEnabled = v;
con's avatar
con committed
291 292 293 294
}

bool SynchronousProcess::stdErrBufferedSignalsEnabled() const
{
hjk's avatar
hjk committed
295
    return d->m_stdErr.bufferedSignalsEnabled;
con's avatar
con committed
296 297 298 299
}

void SynchronousProcess::setStdErrBufferedSignalsEnabled(bool v)
{
hjk's avatar
hjk committed
300
    d->m_stdErr.bufferedSignalsEnabled = v;
con's avatar
con committed
301 302 303 304
}

QStringList SynchronousProcess::environment() const
{
hjk's avatar
hjk committed
305
    return d->m_process.environment();
con's avatar
con committed
306 307
}

308 309
bool SynchronousProcess::timeOutMessageBoxEnabled() const
{
hjk's avatar
hjk committed
310
    return d->m_timeOutMessageBoxEnabled;
311 312 313 314
}

void SynchronousProcess::setTimeOutMessageBoxEnabled(bool v)
{
hjk's avatar
hjk committed
315
    d->m_timeOutMessageBoxEnabled = v;
316 317
}

con's avatar
con committed
318 319
void SynchronousProcess::setEnvironment(const QStringList &e)
{
hjk's avatar
hjk committed
320
    d->m_process.setEnvironment(e);
con's avatar
con committed
321 322
}

323 324
void SynchronousProcess::setProcessEnvironment(const QProcessEnvironment &environment)
{
hjk's avatar
hjk committed
325
    d->m_process.setProcessEnvironment(environment);
326 327 328 329
}

QProcessEnvironment SynchronousProcess::processEnvironment() const
{
hjk's avatar
hjk committed
330
    return d->m_process.processEnvironment();
331 332
}

333 334
unsigned SynchronousProcess::flags() const
{
hjk's avatar
hjk committed
335
    return d->m_process.flags();
336 337 338 339
}

void SynchronousProcess::setFlags(unsigned tc)
{
hjk's avatar
hjk committed
340
    d->m_process.setFlags(tc);
341 342
}

343 344
void SynchronousProcess::setWorkingDirectory(const QString &workingDirectory)
{
hjk's avatar
hjk committed
345
    d->m_process.setWorkingDirectory(workingDirectory);
346 347 348 349
}

QString SynchronousProcess::workingDirectory() const
{
hjk's avatar
hjk committed
350
    return d->m_process.workingDirectory();
351 352 353 354
}

QProcess::ProcessChannelMode SynchronousProcess::processChannelMode () const
{
hjk's avatar
hjk committed
355
    return d->m_process.processChannelMode();
356 357 358 359
}

void SynchronousProcess::setProcessChannelMode(QProcess::ProcessChannelMode m)
{
hjk's avatar
hjk committed
360
    d->m_process.setProcessChannelMode(m);
361 362
}

con's avatar
con committed
363 364 365 366 367 368
SynchronousProcessResponse SynchronousProcess::run(const QString &binary,
                                                 const QStringList &args)
{
    if (debug)
        qDebug() << '>' << Q_FUNC_INFO << binary << args;

hjk's avatar
hjk committed
369
    d->clearForRun();
con's avatar
con committed
370

371 372 373
    // 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.
hjk's avatar
hjk committed
374 375 376 377 378
    d->m_binary = binary;
    d->m_process.start(binary, args, QIODevice::ReadOnly);
    d->m_process.closeWriteChannel();
    if (!d->m_startFailure) {
        d->m_timer.start();
379
        QApplication::setOverrideCursor(Qt::WaitCursor);
hjk's avatar
hjk committed
380 381
        d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
        if (d->m_result.result == SynchronousProcessResponse::Finished || d->m_result.result == SynchronousProcessResponse::FinishedError) {
382 383 384 385
            processStdOut(false);
            processStdErr(false);
        }

hjk's avatar
hjk committed
386 387
        d->m_result.stdOut = convertStdOut(d->m_stdOut.data);
        d->m_result.stdErr = convertStdErr(d->m_stdErr.data);
388

hjk's avatar
hjk committed
389
        d->m_timer.stop();
390
        QApplication::restoreOverrideCursor();
con's avatar
con committed
391 392 393
    }

    if (debug)
hjk's avatar
hjk committed
394 395
        qDebug() << '<' << Q_FUNC_INFO << binary << d->m_result;
    return  d->m_result;
con's avatar
con committed
396 397
}

398 399 400 401 402
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.") :
403
                  SynchronousProcess::tr("The process '%1' is not responding.").arg(QDir::toNativeSeparators(binary));
404
    msg += QLatin1Char(' ');
Jarek Kobus's avatar
Jarek Kobus committed
405
    msg += SynchronousProcess::tr("Would you like to terminate it?");
406 407 408 409 410 411 412 413 414 415
    // 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;
}

con's avatar
con committed
416 417
void SynchronousProcess::slotTimeout()
{
hjk's avatar
hjk committed
418
    if (++d->m_hangTimerCount > d->m_maxHangTimerCount) {
419 420
        if (debug)
            qDebug() << Q_FUNC_INFO << "HANG detected, killing";
hjk's avatar
hjk committed
421
        const bool terminate = !d->m_timeOutMessageBoxEnabled || askToKill(d->m_binary);
422
        if (terminate) {
hjk's avatar
hjk committed
423 424
            SynchronousProcess::stopProcess(d->m_process);
            d->m_result.result = SynchronousProcessResponse::Hang;
425
        } else {
hjk's avatar
hjk committed
426
            d->m_hangTimerCount = 0;
427
        }
428 429
    } else {
        if (debug)
hjk's avatar
hjk committed
430
            qDebug() << Q_FUNC_INFO << d->m_hangTimerCount;
con's avatar
con committed
431 432 433 434 435 436 437
    }
}

void SynchronousProcess::finished(int exitCode, QProcess::ExitStatus e)
{
    if (debug)
        qDebug() << Q_FUNC_INFO << exitCode << e;
hjk's avatar
hjk committed
438
    d->m_hangTimerCount = 0;
con's avatar
con committed
439 440
    switch (e) {
    case QProcess::NormalExit:
hjk's avatar
hjk committed
441 442
        d->m_result.result = exitCode ? SynchronousProcessResponse::FinishedError : SynchronousProcessResponse::Finished;
        d->m_result.exitCode = exitCode;
con's avatar
con committed
443 444
        break;
    case QProcess::CrashExit:
445
        // Was hang detected before and killed?
hjk's avatar
hjk committed
446 447 448
        if (d->m_result.result != SynchronousProcessResponse::Hang)
            d->m_result.result = SynchronousProcessResponse::TerminatedAbnormally;
        d->m_result.exitCode = -1;
con's avatar
con committed
449 450
        break;
    }
hjk's avatar
hjk committed
451
    d->m_eventLoop.quit();
con's avatar
con committed
452 453 454 455
}

void SynchronousProcess::error(QProcess::ProcessError e)
{
hjk's avatar
hjk committed
456
    d->m_hangTimerCount = 0;
con's avatar
con committed
457 458
    if (debug)
        qDebug() << Q_FUNC_INFO << e;
459
    // Was hang detected before and killed?
hjk's avatar
hjk committed
460 461 462 463
    if (d->m_result.result != SynchronousProcessResponse::Hang)
        d->m_result.result = SynchronousProcessResponse::StartFailed;
    d->m_startFailure = true;
    d->m_eventLoop.quit();
con's avatar
con committed
464 465 466 467
}

void SynchronousProcess::stdOutReady()
{
hjk's avatar
hjk committed
468
    d->m_hangTimerCount = 0;
con's avatar
con committed
469 470 471 472 473
    processStdOut(true);
}

void SynchronousProcess::stdErrReady()
{
hjk's avatar
hjk committed
474
    d->m_hangTimerCount = 0;
con's avatar
con committed
475 476 477 478 479
    processStdErr(true);
}

QString SynchronousProcess::convertStdErr(const QByteArray &ba)
{
hjk's avatar
hjk committed
480
    return QString::fromLocal8Bit(ba.constData(), ba.size()).remove(QLatin1Char('\r'));
con's avatar
con committed
481 482 483 484
}

QString SynchronousProcess::convertStdOut(const QByteArray &ba) const
{
hjk's avatar
hjk committed
485
    QString stdOut = d->m_stdOutCodec ? d->m_stdOutCodec->toUnicode(ba) : QString::fromLocal8Bit(ba.constData(), ba.size());
con's avatar
con committed
486 487 488 489 490 491
    return stdOut.remove(QLatin1Char('\r'));
}

void SynchronousProcess::processStdOut(bool emitSignals)
{
    // Handle binary data
hjk's avatar
hjk committed
492
    const QByteArray ba = d->m_process.readAllStandardOutput();
con's avatar
con committed
493 494 495
    if (debug > 1)
        qDebug() << Q_FUNC_INFO << emitSignals << ba;
    if (!ba.isEmpty()) {
hjk's avatar
hjk committed
496
        d->m_stdOut.data += ba;
con's avatar
con committed
497 498
        if (emitSignals) {
            // Emit binary signals
hjk's avatar
hjk committed
499 500
            emit stdOut(ba, d->m_stdOut.firstData);
            d->m_stdOut.firstData = false;
con's avatar
con committed
501
            // Buffered. Emit complete lines?
hjk's avatar
hjk committed
502 503
            if (d->m_stdOut.bufferedSignalsEnabled) {
                const QByteArray lines = d->m_stdOut.linesRead();
con's avatar
con committed
504
                if (!lines.isEmpty()) {
hjk's avatar
hjk committed
505 506
                    emit stdOutBuffered(convertStdOut(lines), d->m_stdOut.firstBuffer);
                    d->m_stdOut.firstBuffer = false;
con's avatar
con committed
507 508 509 510 511 512 513 514 515
                }
            }
        }
    }
}

void SynchronousProcess::processStdErr(bool emitSignals)
{
    // Handle binary data
hjk's avatar
hjk committed
516
    const QByteArray ba = d->m_process.readAllStandardError();
con's avatar
con committed
517 518 519
    if (debug > 1)
        qDebug() << Q_FUNC_INFO << emitSignals << ba;
    if (!ba.isEmpty()) {
hjk's avatar
hjk committed
520
        d->m_stdErr.data += ba;
con's avatar
con committed
521 522
        if (emitSignals) {
            // Emit binary signals
hjk's avatar
hjk committed
523 524 525
            emit stdErr(ba, d->m_stdErr.firstData);
            d->m_stdErr.firstData = false;
            if (d->m_stdErr.bufferedSignalsEnabled) {
con's avatar
con committed
526
                // Buffered. Emit complete lines?
hjk's avatar
hjk committed
527
                const QByteArray lines = d->m_stdErr.linesRead();
con's avatar
con committed
528
                if (!lines.isEmpty()) {
hjk's avatar
hjk committed
529 530
                    emit stdErrBuffered(convertStdErr(lines), d->m_stdErr.firstBuffer);
                    d->m_stdErr.firstBuffer = false;
con's avatar
con committed
531 532 533 534 535 536
                }
            }
        }
    }
}

537 538 539 540 541 542 543
QSharedPointer<QProcess> SynchronousProcess::createProcess(unsigned flags)
{
    TerminalControllingProcess *process = new TerminalControllingProcess;
    process->setFlags(flags);
    return QSharedPointer<QProcess>(process);
}

544 545
// Static utilities: Keep running as long as it gets data.
bool SynchronousProcess::readDataFromProcess(QProcess &p, int timeOutMS,
546 547
                                             QByteArray *stdOut, QByteArray *stdErr,
                                             bool showTimeOutMessageBox)
548
{
549 550
    if (syncDebug)
        qDebug() << ">readDataFromProcess" << timeOutMS;
551 552 553 554 555
    if (p.state() != QProcess::Running) {
        qWarning("readDataFromProcess: Process in non-running state passed in.");
        return false;
    }

556
    QTC_ASSERT(p.readChannel() == QProcess::StandardOutput, return false);
557

558 559 560 561 562
    // Keep the process running until it has no longer has data
    bool finished = false;
    bool hasData = false;
    do {
        finished = p.waitForFinished(timeOutMS);
563 564 565 566
        hasData = false;
        // First check 'stdout'
        if (p.bytesAvailable()) { // applies to readChannel() only
            hasData = true;
567 568 569
            const QByteArray newStdOut = p.readAllStandardOutput();
            if (stdOut)
                stdOut->append(newStdOut);
570 571 572 573 574 575
        }
        // 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;
576 577 578
            if (stdErr)
                stdErr->append(newStdErr);
        }
579 580 581 582 583 584
        // Prompt user, pretend we have data if says 'No'.
        const bool hang = !hasData && !finished;
        if (hang && showTimeOutMessageBox) {
            if (!askToKill())
                hasData = true;
        }
585
    } while (hasData && !finished);
586 587
    if (syncDebug)
        qDebug() << "<readDataFromProcess" << finished;
588 589 590 591 592 593 594 595 596 597 598 599 600 601
    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);
}

602 603 604 605 606 607 608 609 610 611 612 613 614
// 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?
615
    if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
616 617 618 619 620
        return QString();
    const int dotIndex = binary.lastIndexOf(QLatin1Char('.'));
    if (dotIndex != -1 && dotIndex == binary.size() - 4)
        return  QString();

621 622 623 624
    switch (HostOsInfo::hostOs()) {
    case HostOsInfo::HostOsLinux:
    case HostOsInfo::HostOsOtherUnix:
    case HostOsInfo::HostOsOther:
625
        break;
626
    case HostOsInfo::HostOsWindows: {
627 628 629 630 631 632 633 634 635 636
            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;
637
    case HostOsInfo::HostOsMac: {
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
            // 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
662
    if (HostOsInfo::isWindowsHost()) {
663 664 665 666 667
        const QString currentDirBinary = checkBinary(QDir::current(), binary);
        if (!currentDirBinary.isEmpty())
            return currentDirBinary;
    }

668
    const QStringList paths = path.split(HostOsInfo::pathListSeparator());
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
    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);
}

hjk's avatar
hjk committed
687
} // namespace Utils