2012-10-02 09:12:39 +02:00
|
|
|
/****************************************************************************
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** This file is part of Qt Creator.
|
2010-10-14 18:05:43 +02: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
|
2016-01-15 14:58:39 +01:00
|
|
|
** 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.
|
2010-10-14 18:05:43 +02:00
|
|
|
**
|
2016-01-15 14:58:39 +01:00
|
|
|
** 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.
|
2010-12-17 17:14:20 +01:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2010-10-14 18:05:43 +02:00
|
|
|
|
|
|
|
|
#include "qtcprocess.h"
|
|
|
|
|
|
2021-07-09 11:29:32 +02:00
|
|
|
#include "commandline.h"
|
2021-05-05 18:21:22 +02:00
|
|
|
#include "executeondestruction.h"
|
|
|
|
|
#include "hostosinfo.h"
|
2021-07-09 11:29:32 +02:00
|
|
|
#include "launcherinterface.h"
|
|
|
|
|
#include "launcherpackets.h"
|
|
|
|
|
#include "launchersocket.h"
|
2021-09-06 14:48:08 +02:00
|
|
|
#include "processreaper.h"
|
2021-05-05 18:21:22 +02:00
|
|
|
#include "qtcassert.h"
|
2021-07-09 11:29:32 +02:00
|
|
|
#include "stringutils.h"
|
2022-01-21 16:15:17 +01:00
|
|
|
#include "terminalprocess_p.h"
|
2013-05-03 13:52:52 +02:00
|
|
|
|
2021-04-30 12:31:36 +02:00
|
|
|
#include <QCoreApplication>
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QDebug>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <QDir>
|
2021-09-01 10:30:25 +02:00
|
|
|
#include <QElapsedTimer>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <QLoggingCategory>
|
2021-08-19 23:04:39 +02:00
|
|
|
#include <QScopeGuard>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <QTextCodec>
|
2021-04-30 12:31:36 +02:00
|
|
|
#include <QThread>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <QTimer>
|
2021-04-30 12:31:36 +02:00
|
|
|
|
|
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
// qmlpuppet does not use that.
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
#endif
|
2011-02-28 10:14:52 +01:00
|
|
|
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <algorithm>
|
2021-08-19 23:04:39 +02:00
|
|
|
#include <atomic>
|
|
|
|
|
#include <functional>
|
|
|
|
|
#include <limits>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <memory>
|
|
|
|
|
|
2011-08-03 12:04:46 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2021-05-05 10:28:07 +02:00
|
|
|
#ifdef QTCREATOR_PCH_H
|
2021-05-01 21:04:48 +02:00
|
|
|
#define CALLBACK WINAPI
|
2021-05-05 10:28:07 +02:00
|
|
|
#endif
|
2011-08-03 12:04:46 +02:00
|
|
|
#include <qt_windows.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
using namespace Utils::Internal;
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
namespace Utils {
|
2021-05-06 14:02:50 +02:00
|
|
|
namespace Internal {
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2021-08-19 23:04:39 +02:00
|
|
|
class MeasureAndRun
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
MeasureAndRun(const char *functionName)
|
|
|
|
|
: m_functionName(functionName)
|
|
|
|
|
, m_measureProcess(qEnvironmentVariableIsSet("QTC_MEASURE_PROCESS"))
|
|
|
|
|
{}
|
|
|
|
|
template <typename Function, typename... Args>
|
|
|
|
|
std::invoke_result_t<Function, Args...> measureAndRun(Function &&function, Args&&... args)
|
|
|
|
|
{
|
|
|
|
|
if (!m_measureProcess)
|
|
|
|
|
return std::invoke(std::forward<Function>(function), std::forward<Args>(args)...);
|
|
|
|
|
QElapsedTimer timer;
|
|
|
|
|
timer.start();
|
|
|
|
|
auto cleanup = qScopeGuard([this, &timer] {
|
|
|
|
|
const qint64 currentNsecs = timer.nsecsElapsed();
|
|
|
|
|
const bool isMainThread = QThread::currentThread() == qApp->thread();
|
|
|
|
|
const int hitThisAll = m_hitThisAll.fetch_add(1) + 1;
|
|
|
|
|
const int hitAllAll = m_hitAllAll.fetch_add(1) + 1;
|
|
|
|
|
const int hitThisMain = isMainThread
|
|
|
|
|
? m_hitThisMain.fetch_add(1) + 1
|
|
|
|
|
: m_hitThisMain.load();
|
|
|
|
|
const int hitAllMain = isMainThread
|
|
|
|
|
? m_hitAllMain.fetch_add(1) + 1
|
|
|
|
|
: m_hitAllMain.load();
|
|
|
|
|
const qint64 totalThisAll = toMs(m_totalThisAll.fetch_add(currentNsecs) + currentNsecs);
|
|
|
|
|
const qint64 totalAllAll = toMs(m_totalAllAll.fetch_add(currentNsecs) + currentNsecs);
|
|
|
|
|
const qint64 totalThisMain = toMs(isMainThread
|
|
|
|
|
? m_totalThisMain.fetch_add(currentNsecs) + currentNsecs
|
|
|
|
|
: m_totalThisMain.load());
|
|
|
|
|
const qint64 totalAllMain = toMs(isMainThread
|
|
|
|
|
? m_totalAllMain.fetch_add(currentNsecs) + currentNsecs
|
|
|
|
|
: m_totalAllMain.load());
|
|
|
|
|
printMeasurement(QLatin1String(m_functionName), hitThisAll, toMs(currentNsecs),
|
|
|
|
|
totalThisAll, hitAllAll, totalAllAll, isMainThread,
|
|
|
|
|
hitThisMain, totalThisMain, hitAllMain, totalAllMain);
|
|
|
|
|
});
|
|
|
|
|
return std::invoke(std::forward<Function>(function), std::forward<Args>(args)...);
|
|
|
|
|
}
|
|
|
|
|
private:
|
|
|
|
|
static void printHeader()
|
|
|
|
|
{
|
|
|
|
|
// [function/thread]: function:(T)his|(A)ll, thread:(M)ain|(A)ll
|
|
|
|
|
qDebug() << "+----------------+-------+---------+----------+-------+----------+---------+-------+----------+-------+----------+";
|
|
|
|
|
qDebug() << "| [Function/Thread] = [(T|A)/(M|A)], where: (T)his function, (A)ll functions / threads, (M)ain thread |";
|
|
|
|
|
qDebug() << "+----------------+-------+---------+----------+-------+----------+---------+-------+----------+-------+----------+";
|
|
|
|
|
qDebug() << "| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |";
|
|
|
|
|
qDebug() << "| | [T/A] | [T/A] | [T/A] | [A/A] | [A/A] | | [T/M] | [T/M] | [A/M] | [A/M] |";
|
|
|
|
|
qDebug() << "| Function | Hit | Current | Total | Hit | Total | Current | Hit | Total | Hit | Total |";
|
|
|
|
|
qDebug() << "| Name | Count | Measu- | Measu- | Count | Measu- | is Main | Count | Measu- | Count | Measu- |";
|
|
|
|
|
qDebug() << "| | | rement | rement | | rement | Thread | | rement | | rement |";
|
|
|
|
|
qDebug() << "+----------------+-------+---------+----------+-------+----------+---------+-------+----------+-------+----------+";
|
|
|
|
|
}
|
|
|
|
|
static void printMeasurement(const QString &functionName, int hitThisAll, int currentNsecs,
|
|
|
|
|
int totalThisAll, int hitAllAll, int totalAllAll, bool isMainThread,
|
|
|
|
|
int hitThisMain, int totalThisMain, int hitAllMain, int totalAllMain)
|
|
|
|
|
{
|
|
|
|
|
static const int repeatHeaderLineCount = 25;
|
|
|
|
|
if (s_lineCounter.fetch_add(1) % repeatHeaderLineCount == 0)
|
|
|
|
|
printHeader();
|
|
|
|
|
|
|
|
|
|
const QString &functionNameField = QString("%1").arg(functionName, 14);
|
|
|
|
|
const QString &hitThisAllField = formatField(hitThisAll, 5);
|
|
|
|
|
const QString ¤tNsecsField = formatField(currentNsecs, 7, " ms");
|
|
|
|
|
const QString &totalThisAllField = formatField(totalThisAll, 8, " ms");
|
|
|
|
|
const QString &hitAllAllField = formatField(hitAllAll, 5);
|
|
|
|
|
const QString &totalAllAllField = formatField(totalAllAll, 8, " ms");
|
|
|
|
|
const QString &mainThreadField = isMainThread ? QString("%1").arg("yes", 7)
|
|
|
|
|
: QString("%1").arg("no", 7);
|
|
|
|
|
const QString &hitThisMainField = formatField(hitThisMain, 5);
|
|
|
|
|
const QString &totalThisMainField = formatField(totalThisMain, 8, " ms");
|
|
|
|
|
const QString &hitAllMainField = formatField(hitAllMain, 5);
|
|
|
|
|
const QString &totalAllMainField = formatField(totalAllMain, 8, " ms");
|
|
|
|
|
|
|
|
|
|
const QString &totalString = QString("| %1 | %2 | %3 | %4 | %5 | %6 | %7 | %8 | %9 | %10 | %11 |")
|
|
|
|
|
.arg(functionNameField, hitThisAllField, currentNsecsField,
|
|
|
|
|
totalThisAllField, hitAllAllField, totalAllAllField, mainThreadField,
|
|
|
|
|
hitThisMainField, totalThisMainField, hitAllMainField, totalAllMainField);
|
|
|
|
|
qDebug("%s", qPrintable(totalString));
|
|
|
|
|
}
|
|
|
|
|
static QString formatField(int number, int fieldWidth, const QString &suffix = QString())
|
|
|
|
|
{
|
|
|
|
|
return QString("%1%2").arg(number, fieldWidth - suffix.count()).arg(suffix);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int toMs(quint64 nsesc) // nanoseconds to miliseconds
|
|
|
|
|
{
|
|
|
|
|
static const int halfMillion = 500000;
|
|
|
|
|
static const int million = 2 * halfMillion;
|
|
|
|
|
return int((nsesc + halfMillion) / million);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-01 17:04:16 +02:00
|
|
|
const char * const m_functionName;
|
2021-08-19 23:04:39 +02:00
|
|
|
const bool m_measureProcess;
|
|
|
|
|
std::atomic_int m_hitThisAll = 0;
|
|
|
|
|
std::atomic_int m_hitThisMain = 0;
|
|
|
|
|
std::atomic_int64_t m_totalThisAll = 0;
|
|
|
|
|
std::atomic_int64_t m_totalThisMain = 0;
|
|
|
|
|
static std::atomic_int m_hitAllAll;
|
|
|
|
|
static std::atomic_int m_hitAllMain;
|
|
|
|
|
static std::atomic_int64_t m_totalAllAll;
|
|
|
|
|
static std::atomic_int64_t m_totalAllMain;
|
|
|
|
|
static std::atomic_int s_lineCounter;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::atomic_int MeasureAndRun::m_hitAllAll = 0;
|
|
|
|
|
std::atomic_int MeasureAndRun::m_hitAllMain = 0;
|
|
|
|
|
std::atomic_int64_t MeasureAndRun::m_totalAllAll = 0;
|
|
|
|
|
std::atomic_int64_t MeasureAndRun::m_totalAllMain = 0;
|
|
|
|
|
std::atomic_int MeasureAndRun::s_lineCounter = 0;
|
|
|
|
|
|
|
|
|
|
static MeasureAndRun s_start = MeasureAndRun("start");
|
|
|
|
|
static MeasureAndRun s_waitForStarted = MeasureAndRun("waitForStarted");
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
enum { debug = 0 };
|
|
|
|
|
enum { syncDebug = 0 };
|
|
|
|
|
|
|
|
|
|
enum { defaultMaxHangTimerCount = 10 };
|
|
|
|
|
|
2021-06-15 13:27:09 +02:00
|
|
|
static Q_LOGGING_CATEGORY(processLog, "qtc.utils.qtcprocess", QtWarningMsg)
|
2021-05-06 11:15:28 +02:00
|
|
|
|
2021-05-26 17:40:57 +02:00
|
|
|
static DeviceProcessHooks s_deviceHooks;
|
2021-04-27 15:18:58 +02:00
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
// Data for one channel buffer (stderr/stdout)
|
2021-05-17 10:09:17 +02:00
|
|
|
class ChannelBuffer
|
2021-05-05 16:05:53 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2021-05-06 11:15:28 +02:00
|
|
|
void clearForRun();
|
|
|
|
|
|
2021-06-14 15:09:11 +02:00
|
|
|
void handleRest();
|
2021-05-14 15:21:54 +02:00
|
|
|
void append(const QByteArray &text);
|
2021-05-06 11:15:28 +02:00
|
|
|
|
|
|
|
|
QByteArray rawData;
|
|
|
|
|
QString incompleteLineBuffer; // lines not yet signaled
|
|
|
|
|
QTextCodec *codec = nullptr; // Not owner
|
|
|
|
|
std::unique_ptr<QTextCodec::ConverterState> codecState;
|
|
|
|
|
std::function<void(const QString &lines)> outputCallback;
|
2021-06-14 15:09:11 +02:00
|
|
|
|
|
|
|
|
bool emitSingleLines = true;
|
|
|
|
|
bool keepRawData = true;
|
2021-05-06 11:15:28 +02:00
|
|
|
};
|
|
|
|
|
|
2022-02-10 18:09:42 +01:00
|
|
|
class ProcessSetupData
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-02-10 19:25:03 +01:00
|
|
|
QtcProcess::ProcessImpl m_processImpl = QtcProcess::DefaultImpl;
|
|
|
|
|
ProcessMode m_processMode = ProcessMode::Reader;
|
|
|
|
|
QtcProcess::TerminalMode m_terminalMode = QtcProcess::TerminalOff;
|
|
|
|
|
|
2022-02-10 18:09:42 +01:00
|
|
|
QString m_nativeArguments;
|
|
|
|
|
QString m_standardInputFile;
|
|
|
|
|
QString m_initialErrorString;
|
|
|
|
|
bool m_belowNormalPriority = false;
|
|
|
|
|
bool m_lowPriority = false;
|
|
|
|
|
bool m_unixTerminalDisabled = false;
|
|
|
|
|
bool m_abortOnMetaChars = true;
|
|
|
|
|
QProcess::ProcessChannelMode m_procesChannelMode = QProcess::SeparateChannels;
|
|
|
|
|
};
|
|
|
|
|
|
2021-07-09 09:09:05 +02:00
|
|
|
class ProcessInterface : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
2022-02-10 18:09:42 +01:00
|
|
|
|
2021-07-09 09:09:05 +02:00
|
|
|
public:
|
2021-09-02 17:03:49 +02:00
|
|
|
ProcessInterface(QObject *parent, ProcessMode processMode)
|
|
|
|
|
: QObject(parent)
|
2021-08-06 16:20:47 +02:00
|
|
|
, m_processMode(processMode) {}
|
2021-07-09 09:09:05 +02:00
|
|
|
|
|
|
|
|
virtual QByteArray readAllStandardOutput() = 0;
|
|
|
|
|
virtual QByteArray readAllStandardError() = 0;
|
|
|
|
|
|
|
|
|
|
virtual void setProcessEnvironment(const QProcessEnvironment &environment) = 0;
|
|
|
|
|
virtual void setWorkingDirectory(const QString &dir) = 0;
|
2021-08-06 16:20:47 +02:00
|
|
|
virtual void start(const QString &program, const QStringList &arguments,
|
|
|
|
|
const QByteArray &writeData) = 0;
|
2022-01-26 17:01:22 +01:00
|
|
|
virtual void customStart(const CommandLine &, const FilePath &workingDirectory,
|
|
|
|
|
const Environment &) { Q_UNUSED(workingDirectory); QTC_CHECK(false); }
|
2022-01-21 16:15:17 +01:00
|
|
|
virtual bool isCustomStart() const { return false; }
|
2021-07-09 09:09:05 +02:00
|
|
|
virtual void terminate() = 0;
|
|
|
|
|
virtual void kill() = 0;
|
|
|
|
|
virtual void close() = 0;
|
|
|
|
|
virtual qint64 write(const QByteArray &data) = 0;
|
|
|
|
|
|
|
|
|
|
virtual QString program() const = 0;
|
|
|
|
|
virtual QProcess::ProcessError error() const = 0;
|
|
|
|
|
virtual QProcess::ProcessState state() const = 0;
|
|
|
|
|
virtual qint64 processId() const = 0;
|
2021-09-02 16:38:40 +02:00
|
|
|
virtual int exitCode() const = 0;
|
2021-07-09 09:09:05 +02:00
|
|
|
virtual QProcess::ExitStatus exitStatus() const = 0;
|
|
|
|
|
virtual QString errorString() const = 0;
|
|
|
|
|
virtual void setErrorString(const QString &str) = 0;
|
|
|
|
|
|
|
|
|
|
virtual bool waitForStarted(int msecs) = 0;
|
|
|
|
|
virtual bool waitForReadyRead(int msecs) = 0;
|
|
|
|
|
virtual bool waitForFinished(int msecs) = 0;
|
|
|
|
|
|
2022-01-25 11:29:23 +01:00
|
|
|
virtual void kickoffProcess() { QTC_CHECK(false); }
|
|
|
|
|
virtual void interruptProcess() { QTC_CHECK(false); }
|
|
|
|
|
virtual qint64 applicationMainThreadID() const { QTC_CHECK(false); return -1; }
|
|
|
|
|
|
2022-02-10 18:09:42 +01:00
|
|
|
const ProcessMode m_processMode;
|
|
|
|
|
ProcessSetupData *m_setup = nullptr;
|
2021-07-09 09:09:05 +02:00
|
|
|
|
|
|
|
|
signals:
|
|
|
|
|
void started();
|
|
|
|
|
void finished(int exitCode, QProcess::ExitStatus status);
|
|
|
|
|
void errorOccurred(QProcess::ProcessError error);
|
|
|
|
|
void readyReadStandardOutput();
|
|
|
|
|
void readyReadStandardError();
|
2021-05-14 15:21:54 +02:00
|
|
|
};
|
|
|
|
|
|
2022-01-21 16:15:17 +01:00
|
|
|
class TerminalImpl : public ProcessInterface
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
TerminalImpl(QObject *parent, QtcProcess::ProcessImpl processImpl,
|
|
|
|
|
QtcProcess::TerminalMode terminalMode)
|
|
|
|
|
: ProcessInterface(parent, ProcessMode::Reader)
|
|
|
|
|
, m_terminal(this, processImpl, terminalMode)
|
|
|
|
|
{
|
|
|
|
|
connect(&m_terminal, &Internal::TerminalProcess::started,
|
|
|
|
|
this, &ProcessInterface::started);
|
|
|
|
|
connect(&m_terminal, &Internal::TerminalProcess::finished,
|
|
|
|
|
this, &ProcessInterface::finished);
|
|
|
|
|
connect(&m_terminal, &Internal::TerminalProcess::errorOccurred,
|
|
|
|
|
this, &ProcessInterface::errorOccurred);
|
|
|
|
|
}
|
|
|
|
|
~TerminalImpl() override
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray readAllStandardOutput() override { QTC_CHECK(false); return {}; }
|
|
|
|
|
QByteArray readAllStandardError() override { QTC_CHECK(false); return {}; }
|
|
|
|
|
|
2022-01-26 17:01:22 +01:00
|
|
|
void setProcessEnvironment(const QProcessEnvironment &) override { QTC_CHECK(false); }
|
|
|
|
|
void setWorkingDirectory(const QString &) override { QTC_CHECK(false); }
|
|
|
|
|
void start(const QString &, const QStringList &, const QByteArray &) override
|
2022-01-21 16:15:17 +01:00
|
|
|
{ QTC_CHECK(false); }
|
|
|
|
|
void customStart(const CommandLine &command, const FilePath &workingDirectory,
|
|
|
|
|
const Environment &environment) override
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
m_terminal.setAbortOnMetaChars(m_setup->m_abortOnMetaChars);
|
2022-01-21 16:15:17 +01:00
|
|
|
m_terminal.setCommand(command);
|
|
|
|
|
m_terminal.setWorkingDirectory(workingDirectory);
|
|
|
|
|
m_terminal.setEnvironment(environment);
|
|
|
|
|
m_terminal.start();
|
|
|
|
|
}
|
|
|
|
|
bool isCustomStart() const override { return true; }
|
|
|
|
|
void terminate() override { m_terminal.stopProcess(); }
|
|
|
|
|
void kill() override { m_terminal.stopProcess(); }
|
|
|
|
|
void close() override { m_terminal.stopProcess(); }
|
2022-01-26 17:01:22 +01:00
|
|
|
qint64 write(const QByteArray &) override { QTC_CHECK(false); return -1; }
|
2022-01-21 16:15:17 +01:00
|
|
|
|
|
|
|
|
QString program() const override { QTC_CHECK(false); return {}; }
|
|
|
|
|
QProcess::ProcessError error() const override { return m_terminal.error(); }
|
|
|
|
|
QProcess::ProcessState state() const override { return m_terminal.state(); }
|
|
|
|
|
qint64 processId() const override { return m_terminal.processId(); }
|
|
|
|
|
int exitCode() const override { return m_terminal.exitCode(); }
|
|
|
|
|
QProcess::ExitStatus exitStatus() const override { return m_terminal.exitStatus(); }
|
|
|
|
|
QString errorString() const override { return m_terminal.errorString(); }
|
2022-01-26 17:01:22 +01:00
|
|
|
void setErrorString(const QString &) override { QTC_CHECK(false); }
|
2022-01-21 16:15:17 +01:00
|
|
|
|
|
|
|
|
// intentionally no-op without an assert
|
2022-01-26 17:01:22 +01:00
|
|
|
bool waitForStarted(int) override { return false; }
|
|
|
|
|
bool waitForReadyRead(int) override { QTC_CHECK(false); return false; }
|
2022-01-21 16:15:17 +01:00
|
|
|
// intentionally no-op without an assert
|
2022-01-26 17:01:22 +01:00
|
|
|
bool waitForFinished(int) override { return false; }
|
2022-01-21 16:15:17 +01:00
|
|
|
|
2022-01-25 11:29:23 +01:00
|
|
|
void kickoffProcess() override { m_terminal.kickoffProcess(); }
|
|
|
|
|
void interruptProcess() override { m_terminal.interruptProcess(); }
|
|
|
|
|
qint64 applicationMainThreadID() const override { return m_terminal.applicationMainThreadID(); }
|
|
|
|
|
|
2022-01-21 16:15:17 +01:00
|
|
|
private:
|
|
|
|
|
Internal::TerminalProcess m_terminal;
|
|
|
|
|
};
|
|
|
|
|
|
2021-07-09 09:09:05 +02:00
|
|
|
class QProcessImpl : public ProcessInterface
|
|
|
|
|
{
|
|
|
|
|
public:
|
2021-09-02 17:03:49 +02:00
|
|
|
QProcessImpl(QObject *parent, ProcessMode processMode)
|
|
|
|
|
: ProcessInterface(parent, processMode)
|
2021-09-06 14:48:08 +02:00
|
|
|
, m_process(new ProcessHelper(parent))
|
2021-07-09 09:09:05 +02:00
|
|
|
{
|
2021-09-06 14:48:08 +02:00
|
|
|
connect(m_process, &QProcess::started,
|
2021-08-06 16:20:47 +02:00
|
|
|
this, &QProcessImpl::handleStarted);
|
2021-09-06 14:48:08 +02:00
|
|
|
connect(m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
2021-07-09 09:09:05 +02:00
|
|
|
this, &ProcessInterface::finished);
|
2021-09-06 14:48:08 +02:00
|
|
|
connect(m_process, &QProcess::errorOccurred,
|
2021-07-09 09:09:05 +02:00
|
|
|
this, &ProcessInterface::errorOccurred);
|
2021-09-06 14:48:08 +02:00
|
|
|
connect(m_process, &QProcess::readyReadStandardOutput,
|
2021-07-09 09:09:05 +02:00
|
|
|
this, &ProcessInterface::readyReadStandardOutput);
|
2021-09-06 14:48:08 +02:00
|
|
|
connect(m_process, &QProcess::readyReadStandardError,
|
2021-07-09 09:09:05 +02:00
|
|
|
this, &ProcessInterface::readyReadStandardError);
|
|
|
|
|
}
|
2021-09-06 14:48:08 +02:00
|
|
|
~QProcessImpl() override
|
|
|
|
|
{
|
|
|
|
|
ProcessReaper::reap(m_process);
|
|
|
|
|
}
|
2021-07-09 09:09:05 +02:00
|
|
|
|
2021-09-06 14:48:08 +02:00
|
|
|
QByteArray readAllStandardOutput() override { return m_process->readAllStandardOutput(); }
|
|
|
|
|
QByteArray readAllStandardError() override { return m_process->readAllStandardError(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
|
|
|
|
|
void setProcessEnvironment(const QProcessEnvironment &environment) override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ m_process->setProcessEnvironment(environment); }
|
2021-07-09 09:09:05 +02:00
|
|
|
void setWorkingDirectory(const QString &dir) override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ m_process->setWorkingDirectory(dir); }
|
2022-02-10 18:09:42 +01:00
|
|
|
|
2021-08-06 16:20:47 +02:00
|
|
|
void start(const QString &program, const QStringList &arguments, const QByteArray &writeData) override
|
|
|
|
|
{
|
2021-12-16 10:43:58 +01:00
|
|
|
ProcessStartHandler *handler = m_process->processStartHandler();
|
2022-02-10 18:09:42 +01:00
|
|
|
handler->setProcessMode(m_processMode);
|
2021-12-16 10:43:58 +01:00
|
|
|
handler->setWriteData(writeData);
|
2022-02-10 18:09:42 +01:00
|
|
|
if (m_setup->m_belowNormalPriority)
|
2021-12-16 10:43:58 +01:00
|
|
|
handler->setBelowNormalPriority();
|
2022-02-10 18:09:42 +01:00
|
|
|
handler->setNativeArguments(m_setup->m_nativeArguments);
|
|
|
|
|
m_process->setStandardInputFile(m_setup->m_standardInputFile);
|
|
|
|
|
m_process->setProcessChannelMode(m_setup->m_procesChannelMode);
|
|
|
|
|
m_process->setErrorString(m_setup->m_initialErrorString);
|
|
|
|
|
if (m_setup->m_lowPriority)
|
2021-09-06 14:48:08 +02:00
|
|
|
m_process->setLowPriority();
|
2022-02-10 18:09:42 +01:00
|
|
|
if (m_setup->m_unixTerminalDisabled)
|
2021-09-06 14:48:08 +02:00
|
|
|
m_process->setUnixTerminalDisabled();
|
2021-12-16 10:43:58 +01:00
|
|
|
m_process->start(program, arguments, handler->openMode());
|
|
|
|
|
handler->handleProcessStart();
|
2021-08-06 16:20:47 +02:00
|
|
|
}
|
2022-02-10 18:09:42 +01:00
|
|
|
|
2021-07-09 09:09:05 +02:00
|
|
|
void terminate() override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ m_process->terminate(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
void kill() override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ m_process->kill(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
void close() override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ m_process->close(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
qint64 write(const QByteArray &data) override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->write(data); }
|
2021-07-09 09:09:05 +02:00
|
|
|
|
|
|
|
|
QString program() const override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->program(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
QProcess::ProcessError error() const override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->error(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
QProcess::ProcessState state() const override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->state(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
qint64 processId() const override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->processId(); }
|
2021-09-02 16:38:40 +02:00
|
|
|
int exitCode() const override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->exitCode(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
QProcess::ExitStatus exitStatus() const override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->exitStatus(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
QString errorString() const override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->errorString(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
void setErrorString(const QString &str) override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ m_process->setErrorString(str); }
|
2021-07-09 09:09:05 +02:00
|
|
|
|
|
|
|
|
bool waitForStarted(int msecs) override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->waitForStarted(msecs); }
|
2021-07-09 09:09:05 +02:00
|
|
|
bool waitForReadyRead(int msecs) override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->waitForReadyRead(msecs); }
|
2021-07-09 09:09:05 +02:00
|
|
|
bool waitForFinished(int msecs) override
|
2021-09-06 14:48:08 +02:00
|
|
|
{ return m_process->waitForFinished(msecs); }
|
2021-07-09 09:09:05 +02:00
|
|
|
|
|
|
|
|
private:
|
2021-08-06 16:20:47 +02:00
|
|
|
void handleStarted()
|
|
|
|
|
{
|
2021-12-16 10:43:58 +01:00
|
|
|
m_process->processStartHandler()->handleProcessStarted();
|
2021-08-06 16:20:47 +02:00
|
|
|
emit started();
|
|
|
|
|
}
|
2021-09-06 14:48:08 +02:00
|
|
|
ProcessHelper *m_process;
|
2021-07-09 09:09:05 +02:00
|
|
|
};
|
|
|
|
|
|
2021-08-02 16:17:43 +02:00
|
|
|
static uint uniqueToken()
|
|
|
|
|
{
|
|
|
|
|
static std::atomic_uint globalUniqueToken = 0;
|
|
|
|
|
return ++globalUniqueToken;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-09 09:09:05 +02:00
|
|
|
class ProcessLauncherImpl : public ProcessInterface
|
|
|
|
|
{
|
2021-07-13 15:53:35 +02:00
|
|
|
Q_OBJECT
|
2021-07-09 09:09:05 +02:00
|
|
|
public:
|
2021-09-02 17:03:49 +02:00
|
|
|
ProcessLauncherImpl(QObject *parent, ProcessMode processMode)
|
|
|
|
|
: ProcessInterface(parent, processMode), m_token(uniqueToken())
|
2021-07-09 11:29:32 +02:00
|
|
|
{
|
2021-09-02 17:03:49 +02:00
|
|
|
m_handle = LauncherInterface::registerHandle(parent, token(), processMode);
|
2021-08-23 17:58:44 +02:00
|
|
|
connect(m_handle, &CallerHandle::errorOccurred,
|
2021-07-14 16:02:25 +02:00
|
|
|
this, &ProcessInterface::errorOccurred);
|
2021-08-23 17:58:44 +02:00
|
|
|
connect(m_handle, &CallerHandle::started,
|
2021-07-14 16:02:25 +02:00
|
|
|
this, &ProcessInterface::started);
|
2021-08-23 17:58:44 +02:00
|
|
|
connect(m_handle, &CallerHandle::finished,
|
2021-07-14 16:02:25 +02:00
|
|
|
this, &ProcessInterface::finished);
|
2021-08-23 17:58:44 +02:00
|
|
|
connect(m_handle, &CallerHandle::readyReadStandardOutput,
|
2021-07-14 16:02:25 +02:00
|
|
|
this, &ProcessInterface::readyReadStandardOutput);
|
2021-08-23 17:58:44 +02:00
|
|
|
connect(m_handle, &CallerHandle::readyReadStandardError,
|
2021-07-14 16:02:25 +02:00
|
|
|
this, &ProcessInterface::readyReadStandardError);
|
|
|
|
|
}
|
|
|
|
|
~ProcessLauncherImpl() override
|
|
|
|
|
{
|
|
|
|
|
cancel();
|
2021-08-30 18:21:41 +02:00
|
|
|
LauncherInterface::unregisterHandle(token());
|
2021-09-02 17:03:49 +02:00
|
|
|
m_handle = nullptr;
|
2021-07-09 11:29:32 +02:00
|
|
|
}
|
2021-07-09 09:09:05 +02:00
|
|
|
|
2021-07-14 16:02:25 +02:00
|
|
|
QByteArray readAllStandardOutput() override { return m_handle->readAllStandardOutput(); }
|
|
|
|
|
QByteArray readAllStandardError() override { return m_handle->readAllStandardError(); }
|
2021-07-09 09:09:05 +02:00
|
|
|
|
2021-07-09 11:29:32 +02:00
|
|
|
void setProcessEnvironment(const QProcessEnvironment &environment) override
|
2021-07-14 16:02:25 +02:00
|
|
|
{ m_handle->setProcessEnvironment(environment); }
|
|
|
|
|
void setWorkingDirectory(const QString &dir) override { m_handle->setWorkingDirectory(dir); }
|
2022-02-10 18:09:42 +01:00
|
|
|
|
2021-08-06 16:20:47 +02:00
|
|
|
void start(const QString &program, const QStringList &arguments, const QByteArray &writeData) override
|
2021-08-09 12:17:13 +02:00
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
m_handle->setStandardInputFile(m_setup->m_standardInputFile);
|
|
|
|
|
m_handle->setProcessChannelMode(m_setup->m_procesChannelMode);
|
|
|
|
|
m_handle->setErrorString(m_setup->m_initialErrorString);
|
|
|
|
|
if (m_setup->m_belowNormalPriority)
|
2021-08-09 12:17:13 +02:00
|
|
|
m_handle->setBelowNormalPriority();
|
2022-02-10 18:09:42 +01:00
|
|
|
m_handle->setNativeArguments(m_setup->m_nativeArguments);
|
|
|
|
|
if (m_setup->m_lowPriority)
|
2021-08-09 14:01:14 +02:00
|
|
|
m_handle->setLowPriority();
|
2022-02-10 18:09:42 +01:00
|
|
|
if (m_setup->m_unixTerminalDisabled)
|
2021-08-09 14:01:14 +02:00
|
|
|
m_handle->setUnixTerminalDisabled();
|
2021-08-09 12:17:13 +02:00
|
|
|
m_handle->start(program, arguments, writeData);
|
|
|
|
|
}
|
2022-02-10 18:09:42 +01:00
|
|
|
|
2021-07-09 11:29:32 +02:00
|
|
|
void terminate() override { cancel(); } // TODO: what are differences among terminate, kill and close?
|
|
|
|
|
void kill() override { cancel(); } // TODO: see above
|
|
|
|
|
void close() override { cancel(); } // TODO: see above
|
2021-08-06 14:43:03 +02:00
|
|
|
qint64 write(const QByteArray &data) override { return m_handle->write(data); }
|
2021-07-09 09:09:05 +02:00
|
|
|
|
2021-07-14 16:02:25 +02:00
|
|
|
QString program() const override { return m_handle->program(); }
|
|
|
|
|
QProcess::ProcessError error() const override { return m_handle->error(); }
|
|
|
|
|
QProcess::ProcessState state() const override { return m_handle->state(); }
|
|
|
|
|
qint64 processId() const override { return m_handle->processId(); }
|
2021-09-02 16:38:40 +02:00
|
|
|
int exitCode() const override { return m_handle->exitCode(); }
|
2021-07-14 16:02:25 +02:00
|
|
|
QProcess::ExitStatus exitStatus() const override { return m_handle->exitStatus(); }
|
|
|
|
|
QString errorString() const override { return m_handle->errorString(); }
|
|
|
|
|
void setErrorString(const QString &str) override { m_handle->setErrorString(str); }
|
|
|
|
|
|
|
|
|
|
bool waitForStarted(int msecs) override { return m_handle->waitForStarted(msecs); }
|
2021-08-02 13:26:33 +02:00
|
|
|
bool waitForReadyRead(int msecs) override { return m_handle->waitForReadyRead(msecs); }
|
2021-07-14 16:02:25 +02:00
|
|
|
bool waitForFinished(int msecs) override { return m_handle->waitForFinished(msecs); }
|
2021-07-09 09:09:05 +02:00
|
|
|
|
2021-07-09 11:29:32 +02:00
|
|
|
private:
|
2021-07-13 15:53:35 +02:00
|
|
|
typedef void (ProcessLauncherImpl::*PreSignal)(void);
|
|
|
|
|
|
2021-07-09 11:29:32 +02:00
|
|
|
void cancel();
|
|
|
|
|
|
|
|
|
|
void handleSocketError(const QString &message);
|
|
|
|
|
void handleSocketReady();
|
|
|
|
|
|
2021-08-02 16:17:43 +02:00
|
|
|
quintptr token() const { return m_token; }
|
2021-07-09 11:29:32 +02:00
|
|
|
|
2021-08-02 16:17:43 +02:00
|
|
|
const uint m_token = 0;
|
2022-01-21 16:15:17 +01:00
|
|
|
// Lives in caller's thread.
|
2021-08-23 17:58:44 +02:00
|
|
|
CallerHandle *m_handle = nullptr;
|
2021-07-09 09:09:05 +02:00
|
|
|
};
|
|
|
|
|
|
2021-07-09 11:29:32 +02:00
|
|
|
void ProcessLauncherImpl::cancel()
|
|
|
|
|
{
|
2021-07-14 16:02:25 +02:00
|
|
|
m_handle->cancel();
|
2021-07-09 11:29:32 +02:00
|
|
|
}
|
|
|
|
|
|
2022-02-10 18:09:42 +01:00
|
|
|
static QtcProcess::ProcessImpl defaultProcessImpl()
|
2021-07-09 09:09:05 +02:00
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (qEnvironmentVariableIsSet("QTC_USE_QPROCESS"))
|
|
|
|
|
return QtcProcess::QProcessImpl;
|
|
|
|
|
return QtcProcess::ProcessLauncherImpl;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
class QtcProcessPrivate : public QObject
|
|
|
|
|
{
|
|
|
|
|
public:
|
2021-10-26 11:10:06 +02:00
|
|
|
enum StartFailure {
|
|
|
|
|
NoFailure,
|
|
|
|
|
WrongFileNameFailure,
|
|
|
|
|
OtherFailure
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-10 19:25:03 +01:00
|
|
|
explicit QtcProcessPrivate(QtcProcess *parent)
|
2021-09-02 17:03:49 +02:00
|
|
|
: QObject(parent)
|
|
|
|
|
, q(parent)
|
2022-02-10 18:09:42 +01:00
|
|
|
{}
|
|
|
|
|
|
2022-02-10 19:25:03 +01:00
|
|
|
void setupNewProcessInstance()
|
|
|
|
|
{
|
|
|
|
|
const QtcProcess::ProcessImpl impl =
|
|
|
|
|
m_setup.m_processImpl == QtcProcess::DefaultImpl
|
|
|
|
|
? defaultProcessImpl()
|
|
|
|
|
: m_setup.m_processImpl;
|
|
|
|
|
|
|
|
|
|
if (m_setup.m_terminalMode != QtcProcess::TerminalOff)
|
|
|
|
|
m_process = new TerminalImpl(parent(), impl, m_setup.m_terminalMode);
|
|
|
|
|
else if (impl == QtcProcess::QProcessImpl)
|
|
|
|
|
m_process = new QProcessImpl(parent(), m_setup.m_processMode);
|
|
|
|
|
else
|
|
|
|
|
m_process = new ProcessLauncherImpl(parent(), m_setup.m_processMode);
|
|
|
|
|
|
|
|
|
|
m_process->m_setup = &m_setup;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-10 18:09:42 +01:00
|
|
|
void ensureProcessInstanceExists()
|
2021-05-14 15:21:54 +02:00
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (m_process)
|
|
|
|
|
return;
|
|
|
|
|
|
2022-02-10 19:25:03 +01:00
|
|
|
setupNewProcessInstance();
|
2022-02-10 18:09:42 +01:00
|
|
|
|
2021-07-09 09:09:05 +02:00
|
|
|
connect(m_process, &ProcessInterface::started,
|
2021-05-14 15:21:54 +02:00
|
|
|
q, &QtcProcess::started);
|
2021-07-09 09:09:05 +02:00
|
|
|
connect(m_process, &ProcessInterface::finished,
|
2021-06-03 12:25:33 +02:00
|
|
|
this, &QtcProcessPrivate::slotFinished);
|
2021-07-09 09:09:05 +02:00
|
|
|
connect(m_process, &ProcessInterface::errorOccurred,
|
2021-10-26 11:10:06 +02:00
|
|
|
this, [this](QProcess::ProcessError error) { handleError(error, OtherFailure); });
|
2021-07-09 09:09:05 +02:00
|
|
|
connect(m_process, &ProcessInterface::readyReadStandardOutput,
|
2021-05-14 15:21:54 +02:00
|
|
|
this, &QtcProcessPrivate::handleReadyReadStandardOutput);
|
2021-07-09 09:09:05 +02:00
|
|
|
connect(m_process, &ProcessInterface::readyReadStandardError,
|
2021-05-14 15:21:54 +02:00
|
|
|
this, &QtcProcessPrivate::handleReadyReadStandardError);
|
|
|
|
|
}
|
2021-05-06 11:15:28 +02:00
|
|
|
|
2021-05-14 15:21:54 +02:00
|
|
|
void handleReadyReadStandardOutput()
|
|
|
|
|
{
|
|
|
|
|
m_stdOut.append(m_process->readAllStandardOutput());
|
|
|
|
|
m_hangTimerCount = 0;
|
|
|
|
|
emit q->readyReadStandardOutput();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void handleReadyReadStandardError()
|
|
|
|
|
{
|
|
|
|
|
m_stdErr.append(m_process->readAllStandardError());
|
|
|
|
|
m_hangTimerCount = 0;
|
2021-06-18 15:50:30 +02:00
|
|
|
emit q->readyReadStandardError();
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
2021-08-31 15:24:20 +02:00
|
|
|
FilePath resolve(const FilePath &workingDir, const FilePath &filePath) const
|
|
|
|
|
{
|
|
|
|
|
if (filePath.isAbsolutePath())
|
|
|
|
|
return filePath;
|
|
|
|
|
|
2021-09-08 16:13:43 +02:00
|
|
|
const FilePath fromWorkingDir = workingDir.resolvePath(filePath);
|
2021-08-31 15:24:20 +02:00
|
|
|
if (fromWorkingDir.exists() && fromWorkingDir.isExecutableFile())
|
|
|
|
|
return fromWorkingDir;
|
|
|
|
|
return filePath.searchInPath();
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-01 14:51:23 +01:00
|
|
|
void defaultStart(const CommandLine &commandLine, const FilePath &workingDirectory,
|
|
|
|
|
const Environment &environment)
|
2022-01-21 16:15:17 +01:00
|
|
|
{
|
|
|
|
|
if (processLog().isDebugEnabled()) {
|
|
|
|
|
static int n = 0;
|
2022-02-01 14:51:23 +01:00
|
|
|
qCDebug(processLog) << "STARTING PROCESS: " << ++n << " " << commandLine.toUserOutput();
|
2022-01-21 16:15:17 +01:00
|
|
|
}
|
|
|
|
|
|
2022-02-10 18:09:42 +01:00
|
|
|
ensureProcessInstanceExists();
|
2022-02-01 14:51:23 +01:00
|
|
|
m_process->setProcessEnvironment(environment.toProcessEnvironment());
|
|
|
|
|
m_process->setWorkingDirectory(workingDirectory.path());
|
2022-01-21 16:15:17 +01:00
|
|
|
|
2022-02-01 14:51:23 +01:00
|
|
|
QString commandString;
|
2022-01-21 16:15:17 +01:00
|
|
|
ProcessArgs arguments;
|
2022-02-01 14:51:23 +01:00
|
|
|
const bool success = ProcessArgs::prepareCommand(commandLine, &commandString, &arguments,
|
|
|
|
|
&environment, &workingDirectory);
|
2022-01-21 16:15:17 +01:00
|
|
|
|
2022-02-01 14:51:23 +01:00
|
|
|
if (commandLine.executable().osType() == OsTypeWindows) {
|
2022-01-21 16:15:17 +01:00
|
|
|
QString args;
|
|
|
|
|
if (m_useCtrlCStub) {
|
2022-02-10 18:09:42 +01:00
|
|
|
if (m_setup.m_lowPriority)
|
2022-01-21 16:15:17 +01:00
|
|
|
ProcessArgs::addArg(&args, "-nice");
|
2022-02-01 14:51:23 +01:00
|
|
|
ProcessArgs::addArg(&args, QDir::toNativeSeparators(commandString));
|
|
|
|
|
commandString = QCoreApplication::applicationDirPath()
|
2022-01-21 16:15:17 +01:00
|
|
|
+ QLatin1String("/qtcreator_ctrlc_stub.exe");
|
|
|
|
|
}
|
|
|
|
|
ProcessArgs::addArgs(&args, arguments.toWindowsArgs());
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
m_process->setNativeArguments(args);
|
|
|
|
|
#endif
|
|
|
|
|
// Note: Arguments set with setNativeArgs will be appended to the ones
|
|
|
|
|
// passed with start() below.
|
2022-02-01 14:51:23 +01:00
|
|
|
start(commandString, QStringList(), workingDirectory, m_writeData);
|
2022-01-21 16:15:17 +01:00
|
|
|
} else {
|
|
|
|
|
if (!success) {
|
|
|
|
|
q->setErrorString(tr("Error in command line."));
|
|
|
|
|
// Should be FailedToStart, but we cannot set the process error from the outside,
|
|
|
|
|
// so it would be inconsistent.
|
|
|
|
|
emit q->errorOccurred(QProcess::UnknownError);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-02-01 14:51:23 +01:00
|
|
|
start(commandString, arguments.toUnixArgs(), workingDirectory, m_writeData);
|
2022-01-21 16:15:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-01 14:51:23 +01:00
|
|
|
void start(const QString &program, const QStringList &arguments,
|
|
|
|
|
const FilePath &workingDirectory, const QByteArray &writeData)
|
2021-08-31 15:24:20 +02:00
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
ensureProcessInstanceExists();
|
2022-02-01 14:51:23 +01:00
|
|
|
const FilePath programFilePath = resolve(workingDirectory, FilePath::fromString(program));
|
2021-08-31 15:24:20 +02:00
|
|
|
if (programFilePath.exists() && programFilePath.isExecutableFile()) {
|
2021-08-19 23:04:39 +02:00
|
|
|
s_start.measureAndRun(&ProcessInterface::start, m_process, program, arguments, writeData);
|
2021-08-31 15:24:20 +02:00
|
|
|
} else {
|
|
|
|
|
m_process->setErrorString(QLatin1String(
|
|
|
|
|
"The program \"%1\" does not exist or is not executable.").arg(program));
|
2021-10-26 11:10:06 +02:00
|
|
|
handleError(QProcess::FailedToStart, WrongFileNameFailure);
|
2021-08-31 15:24:20 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-01 14:51:23 +01:00
|
|
|
CommandLine fullCommandLine() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_runAsRoot || HostOsInfo::isWindowsHost())
|
|
|
|
|
return m_commandLine;
|
|
|
|
|
CommandLine rootCommand("sudo", {"-A"});
|
|
|
|
|
rootCommand.addCommandLineAsArgs(m_commandLine);
|
|
|
|
|
return rootCommand;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Environment fullEnvironment() const
|
|
|
|
|
{
|
|
|
|
|
Environment env;
|
|
|
|
|
if (m_haveEnv) {
|
|
|
|
|
if (m_environment.size() == 0)
|
|
|
|
|
qWarning("QtcProcess::start: Empty environment set when running '%s'.",
|
|
|
|
|
qPrintable(m_commandLine.executable().toString()));
|
|
|
|
|
env = m_environment;
|
|
|
|
|
} else {
|
|
|
|
|
env = Environment::systemEnvironment();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: needs SshSettings
|
|
|
|
|
// if (m_runAsRoot)
|
|
|
|
|
// RunControl::provideAskPassEntry(env);
|
|
|
|
|
return env;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-14 15:21:54 +02:00
|
|
|
QtcProcess *q;
|
2022-02-10 18:09:42 +01:00
|
|
|
ProcessInterface *m_process = nullptr;
|
2021-05-05 16:05:53 +02:00
|
|
|
CommandLine m_commandLine;
|
2021-06-03 10:39:15 +02:00
|
|
|
FilePath m_workingDirectory;
|
2021-05-05 16:05:53 +02:00
|
|
|
Environment m_environment;
|
2021-05-06 17:54:28 +02:00
|
|
|
QByteArray m_writeData;
|
2022-02-01 14:51:23 +01:00
|
|
|
bool m_runAsRoot = false;
|
2021-05-05 16:05:53 +02:00
|
|
|
bool m_haveEnv = false;
|
|
|
|
|
bool m_useCtrlCStub = false;
|
2022-02-10 18:09:42 +01:00
|
|
|
ProcessSetupData m_setup;
|
2021-05-05 16:05:53 +02:00
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void slotTimeout();
|
|
|
|
|
void slotFinished(int exitCode, QProcess::ExitStatus e);
|
2021-10-26 11:10:06 +02:00
|
|
|
void handleError(QProcess::ProcessError error, StartFailure startFailure);
|
2021-05-06 11:15:28 +02:00
|
|
|
void clearForRun();
|
|
|
|
|
|
2021-05-12 14:25:50 +02:00
|
|
|
QtcProcess::Result interpretExitCode(int exitCode);
|
2021-05-06 15:54:02 +02:00
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
QTextCodec *m_codec = QTextCodec::codecForLocale();
|
2021-10-20 17:38:11 +02:00
|
|
|
QEventLoop *m_eventLoop = nullptr;
|
2021-05-17 11:17:53 +02:00
|
|
|
QtcProcess::Result m_result = QtcProcess::StartFailed;
|
2021-05-06 11:15:28 +02:00
|
|
|
ChannelBuffer m_stdOut;
|
|
|
|
|
ChannelBuffer m_stdErr;
|
2021-05-06 15:54:02 +02:00
|
|
|
ExitCodeInterpreter m_exitCodeInterpreter;
|
2021-05-06 11:15:28 +02:00
|
|
|
|
|
|
|
|
int m_hangTimerCount = 0;
|
|
|
|
|
int m_maxHangTimerCount = defaultMaxHangTimerCount;
|
2021-10-26 11:10:06 +02:00
|
|
|
StartFailure m_startFailure = NoFailure;
|
2021-05-06 11:15:28 +02:00
|
|
|
bool m_timeOutMessageBoxEnabled = false;
|
|
|
|
|
bool m_waitingForUser = false;
|
2021-05-05 16:05:53 +02:00
|
|
|
};
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcessPrivate::clearForRun()
|
|
|
|
|
{
|
|
|
|
|
m_hangTimerCount = 0;
|
|
|
|
|
m_stdOut.clearForRun();
|
|
|
|
|
m_stdOut.codec = m_codec;
|
|
|
|
|
m_stdErr.clearForRun();
|
|
|
|
|
m_stdErr.codec = m_codec;
|
2021-05-17 11:17:53 +02:00
|
|
|
m_result = QtcProcess::StartFailed;
|
2021-10-26 11:10:06 +02:00
|
|
|
m_startFailure = NoFailure;
|
2021-05-06 11:15:28 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-12 14:25:50 +02:00
|
|
|
QtcProcess::Result QtcProcessPrivate::interpretExitCode(int exitCode)
|
2021-05-06 15:54:02 +02:00
|
|
|
{
|
|
|
|
|
if (m_exitCodeInterpreter)
|
|
|
|
|
return m_exitCodeInterpreter(exitCode);
|
|
|
|
|
|
|
|
|
|
// default:
|
2021-05-28 13:19:38 +02:00
|
|
|
return exitCode ? QtcProcess::FinishedWithError : QtcProcess::FinishedWithSuccess;
|
2021-05-06 15:54:02 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
} // Internal
|
|
|
|
|
|
2021-05-06 14:02:50 +02:00
|
|
|
/*!
|
|
|
|
|
\class Utils::QtcProcess
|
|
|
|
|
|
|
|
|
|
\brief The QtcProcess class provides functionality for with processes.
|
|
|
|
|
|
|
|
|
|
\sa Utils::ProcessArgs
|
|
|
|
|
*/
|
|
|
|
|
|
2022-02-10 19:25:03 +01:00
|
|
|
QtcProcess::QtcProcess(QObject *parent)
|
2022-01-27 17:00:01 +01:00
|
|
|
: QObject(parent),
|
2022-02-10 19:25:03 +01:00
|
|
|
d(new QtcProcessPrivate(this))
|
2016-06-08 13:30:30 +02:00
|
|
|
{
|
|
|
|
|
static int qProcessExitStatusMeta = qRegisterMetaType<QProcess::ExitStatus>();
|
|
|
|
|
static int qProcessProcessErrorMeta = qRegisterMetaType<QProcess::ProcessError>();
|
2019-07-23 10:58:00 +02:00
|
|
|
Q_UNUSED(qProcessExitStatusMeta)
|
|
|
|
|
Q_UNUSED(qProcessProcessErrorMeta)
|
2016-06-08 13:30:30 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
QtcProcess::~QtcProcess()
|
|
|
|
|
{
|
|
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-10 19:25:03 +01:00
|
|
|
void QtcProcess::setProcessImpl(ProcessImpl processImpl)
|
|
|
|
|
{
|
|
|
|
|
d->m_setup.m_processImpl = processImpl;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-07 11:41:23 +02:00
|
|
|
ProcessMode QtcProcess::processMode() const
|
|
|
|
|
{
|
2022-02-10 19:25:03 +01:00
|
|
|
return d->m_setup.m_processMode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setTerminalMode(TerminalMode mode)
|
|
|
|
|
{
|
|
|
|
|
d->m_setup.m_terminalMode = mode;
|
2021-08-07 11:41:23 +02:00
|
|
|
}
|
|
|
|
|
|
2022-02-03 13:20:15 +01:00
|
|
|
QtcProcess::TerminalMode QtcProcess::terminalMode() const
|
|
|
|
|
{
|
2022-02-10 19:25:03 +01:00
|
|
|
return d->m_setup.m_terminalMode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setProcessMode(ProcessMode processMode)
|
|
|
|
|
{
|
|
|
|
|
d->m_setup.m_processMode = processMode;
|
2022-02-03 13:20:15 +01:00
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
void QtcProcess::setEnvironment(const Environment &env)
|
|
|
|
|
{
|
|
|
|
|
d->m_environment = env;
|
|
|
|
|
d->m_haveEnv = true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-23 11:13:59 +02:00
|
|
|
void QtcProcess::unsetEnvironment()
|
|
|
|
|
{
|
|
|
|
|
d->m_environment = Environment();
|
|
|
|
|
d->m_haveEnv = false;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
const Environment &QtcProcess::environment() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_environment;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-03 13:20:15 +01:00
|
|
|
bool QtcProcess::hasEnvironment() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_haveEnv;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
void QtcProcess::setCommand(const CommandLine &cmdLine)
|
|
|
|
|
{
|
2021-06-03 10:39:15 +02:00
|
|
|
if (d->m_workingDirectory.needsDevice() && cmdLine.executable().needsDevice()) {
|
|
|
|
|
QTC_CHECK(d->m_workingDirectory.host() == cmdLine.executable().host());
|
|
|
|
|
}
|
2022-02-01 14:51:23 +01:00
|
|
|
d->m_commandLine = cmdLine;
|
2021-05-05 16:05:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CommandLine &QtcProcess::commandLine() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_commandLine;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-03 10:39:15 +02:00
|
|
|
FilePath QtcProcess::workingDirectory() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_workingDirectory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setWorkingDirectory(const FilePath &dir)
|
2021-05-14 15:21:54 +02:00
|
|
|
{
|
2021-06-03 10:39:15 +02:00
|
|
|
if (dir.needsDevice() && d->m_commandLine.executable().needsDevice()) {
|
|
|
|
|
QTC_CHECK(dir.host() == d->m_commandLine.executable().host());
|
|
|
|
|
}
|
|
|
|
|
d->m_workingDirectory = dir;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
2014-05-16 15:57:02 +02:00
|
|
|
void QtcProcess::setUseCtrlCStub(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
// Do not use the stub in debug mode. Activating the stub will shut down
|
|
|
|
|
// Qt Creator otherwise, because they share the same Windows console.
|
|
|
|
|
// See QTCREATORBUG-11995 for details.
|
|
|
|
|
#ifndef QT_DEBUG
|
2021-05-05 16:05:53 +02:00
|
|
|
d->m_useCtrlCStub = enabled;
|
2014-05-20 15:17:32 +02:00
|
|
|
#else
|
|
|
|
|
Q_UNUSED(enabled)
|
2014-05-16 15:57:02 +02:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
void QtcProcess::start()
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
d->ensureProcessInstanceExists();
|
2022-02-03 13:20:15 +01:00
|
|
|
if (d->m_commandLine.executable().needsDevice()) {
|
|
|
|
|
QTC_ASSERT(s_deviceHooks.startProcessHook, return);
|
|
|
|
|
s_deviceHooks.startProcessHook(*this);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-02-01 14:51:23 +01:00
|
|
|
d->clearForRun();
|
|
|
|
|
const CommandLine cmd = d->fullCommandLine();
|
|
|
|
|
const Environment env = d->fullEnvironment();
|
2022-01-21 16:15:17 +01:00
|
|
|
if (d->m_process->isCustomStart())
|
2022-02-01 14:51:23 +01:00
|
|
|
d->m_process->customStart(cmd, d->m_workingDirectory, env);
|
2022-01-21 16:15:17 +01:00
|
|
|
else
|
2022-02-01 14:51:23 +01:00
|
|
|
d->defaultStart(cmd, d->m_workingDirectory, env);
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
2011-08-03 12:04:46 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2013-05-03 13:52:52 +02:00
|
|
|
static BOOL sendMessage(UINT message, HWND hwnd, LPARAM lParam)
|
2011-08-03 12:04:46 +02:00
|
|
|
{
|
|
|
|
|
DWORD dwProcessID;
|
|
|
|
|
GetWindowThreadProcessId(hwnd, &dwProcessID);
|
2011-08-09 13:43:56 +02:00
|
|
|
if ((DWORD)lParam == dwProcessID) {
|
2013-05-03 13:52:52 +02:00
|
|
|
SendNotifyMessage(hwnd, message, 0, 0);
|
2011-08-03 12:04:46 +02:00
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
2013-05-03 13:52:52 +02:00
|
|
|
|
|
|
|
|
BOOL CALLBACK sendShutDownMessageToAllWindowsOfProcess_enumWnd(HWND hwnd, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
static UINT uiShutDownMessage = RegisterWindowMessage(L"qtcctrlcstub_shutdown");
|
|
|
|
|
return sendMessage(uiShutDownMessage, hwnd, lParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BOOL CALLBACK sendInterruptMessageToAllWindowsOfProcess_enumWnd(HWND hwnd, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
static UINT uiInterruptMessage = RegisterWindowMessage(L"qtcctrlcstub_interrupt");
|
|
|
|
|
return sendMessage(uiInterruptMessage, hwnd, lParam);
|
|
|
|
|
}
|
2011-08-03 12:04:46 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
void QtcProcess::terminate()
|
|
|
|
|
{
|
|
|
|
|
#ifdef Q_OS_WIN
|
2021-05-05 16:05:53 +02:00
|
|
|
if (d->m_useCtrlCStub)
|
2020-11-02 11:19:12 +01:00
|
|
|
EnumWindows(sendShutDownMessageToAllWindowsOfProcess_enumWnd, processId());
|
2011-08-03 12:04:46 +02:00
|
|
|
else
|
|
|
|
|
#endif
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
d->m_process->terminate();
|
2011-08-03 12:04:46 +02:00
|
|
|
}
|
|
|
|
|
|
2013-05-03 13:52:52 +02:00
|
|
|
void QtcProcess::interrupt()
|
|
|
|
|
{
|
2013-09-12 16:34:34 +02:00
|
|
|
#ifdef Q_OS_WIN
|
2021-05-05 16:05:53 +02:00
|
|
|
QTC_ASSERT(d->m_useCtrlCStub, return);
|
2020-11-02 11:19:12 +01:00
|
|
|
EnumWindows(sendInterruptMessageToAllWindowsOfProcess_enumWnd, processId());
|
2013-09-12 16:34:34 +02:00
|
|
|
#endif
|
2013-05-03 13:52:52 +02:00
|
|
|
}
|
2010-10-14 18:05:43 +02:00
|
|
|
|
2021-08-12 12:33:28 +02:00
|
|
|
bool QtcProcess::startDetached(const CommandLine &cmd, const FilePath &workingDirectory, qint64 *pid)
|
|
|
|
|
{
|
|
|
|
|
return QProcess::startDetached(cmd.executable().toUserOutput(),
|
|
|
|
|
cmd.splitArguments(),
|
|
|
|
|
workingDirectory.toUserOutput(),
|
|
|
|
|
pid);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
void QtcProcess::setLowPriority()
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
d->m_setup.m_lowPriority = true;
|
2021-05-05 16:05:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setDisableUnixTerminal()
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
d->m_setup.m_unixTerminalDisabled = true;
|
2021-05-05 16:05:53 +02:00
|
|
|
}
|
|
|
|
|
|
2022-01-21 16:15:17 +01:00
|
|
|
void QtcProcess::setAbortOnMetaChars(bool abort)
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
d->m_setup.m_abortOnMetaChars = abort;
|
2022-01-21 16:15:17 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-08 14:51:49 +02:00
|
|
|
void QtcProcess::setRunAsRoot(bool on)
|
|
|
|
|
{
|
2022-02-01 14:51:23 +01:00
|
|
|
d->m_runAsRoot = on;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcess::isRunAsRoot() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_runAsRoot;
|
2021-09-08 14:51:49 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-28 17:39:14 +02:00
|
|
|
void QtcProcess::setStandardInputFile(const QString &inputFile)
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
d->m_setup.m_standardInputFile = inputFile;
|
2021-06-28 17:39:14 +02:00
|
|
|
}
|
|
|
|
|
|
2021-09-09 23:03:39 +02:00
|
|
|
QString QtcProcess::toStandaloneCommandLine() const
|
|
|
|
|
{
|
|
|
|
|
QStringList parts;
|
|
|
|
|
parts.append("/usr/bin/env");
|
|
|
|
|
if (!d->m_workingDirectory.isEmpty()) {
|
|
|
|
|
parts.append("-C");
|
|
|
|
|
d->m_workingDirectory.path();
|
|
|
|
|
}
|
|
|
|
|
parts.append("-i");
|
|
|
|
|
if (d->m_environment.size() > 0) {
|
|
|
|
|
const QStringList envVars = d->m_environment.toStringList();
|
|
|
|
|
std::transform(envVars.cbegin(), envVars.cend(),
|
|
|
|
|
std::back_inserter(parts), ProcessArgs::quoteArgUnix);
|
|
|
|
|
}
|
|
|
|
|
parts.append(d->m_commandLine.executable().path());
|
|
|
|
|
parts.append(d->m_commandLine.splitArguments());
|
|
|
|
|
return parts.join(" ");
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-26 17:40:57 +02:00
|
|
|
void QtcProcess::setRemoteProcessHooks(const DeviceProcessHooks &hooks)
|
2021-04-27 15:18:58 +02:00
|
|
|
{
|
2021-05-26 17:40:57 +02:00
|
|
|
s_deviceHooks = hooks;
|
2021-04-27 15:18:58 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-29 17:15:48 +02:00
|
|
|
bool QtcProcess::stopProcess()
|
|
|
|
|
{
|
|
|
|
|
if (state() == QProcess::NotRunning)
|
|
|
|
|
return true;
|
|
|
|
|
terminate();
|
|
|
|
|
if (waitForFinished(300))
|
|
|
|
|
return true;
|
|
|
|
|
kill();
|
|
|
|
|
return waitForFinished(300);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-30 12:31:36 +02:00
|
|
|
static bool askToKill(const QString &command)
|
|
|
|
|
{
|
|
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
if (QThread::currentThread() != QCoreApplication::instance()->thread())
|
|
|
|
|
return true;
|
|
|
|
|
const QString title = QtcProcess::tr("Process not Responding");
|
|
|
|
|
QString msg = command.isEmpty() ?
|
|
|
|
|
QtcProcess::tr("The process is not responding.") :
|
|
|
|
|
QtcProcess::tr("The process \"%1\" is not responding.").arg(command);
|
|
|
|
|
msg += ' ';
|
|
|
|
|
msg += QtcProcess::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;
|
|
|
|
|
#else
|
|
|
|
|
Q_UNUSED(command)
|
|
|
|
|
return true;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 07:49:33 +02:00
|
|
|
// Helper for running a process synchronously in the foreground with timeout
|
2021-06-22 04:33:47 +02:00
|
|
|
// detection (taking effect after no more output
|
2021-05-04 07:49:33 +02:00
|
|
|
// occurs on stderr/stdout as opposed to waitForFinished()). Returns false if a timeout
|
|
|
|
|
// occurs. Checking of the process' exit state/code still has to be done.
|
|
|
|
|
|
2021-04-30 12:31:36 +02:00
|
|
|
bool QtcProcess::readDataFromProcess(int timeoutS,
|
|
|
|
|
QByteArray *stdOut,
|
|
|
|
|
QByteArray *stdErr,
|
|
|
|
|
bool showTimeOutMessageBox)
|
|
|
|
|
{
|
|
|
|
|
enum { syncDebug = 0 };
|
|
|
|
|
if (syncDebug)
|
|
|
|
|
qDebug() << ">readDataFromProcess" << timeoutS;
|
|
|
|
|
if (state() != QProcess::Running) {
|
|
|
|
|
qWarning("readDataFromProcess: Process in non-running state passed in.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Keep the process running until it has no longer has data
|
|
|
|
|
bool finished = false;
|
|
|
|
|
bool hasData = false;
|
|
|
|
|
do {
|
|
|
|
|
finished = waitForFinished(timeoutS > 0 ? timeoutS * 1000 : -1)
|
|
|
|
|
|| state() == QProcess::NotRunning;
|
|
|
|
|
// First check 'stdout'
|
2021-07-29 15:35:53 +02:00
|
|
|
const QByteArray newStdOut = readAllStandardOutput();
|
|
|
|
|
if (!newStdOut.isEmpty()) {
|
2021-04-30 12:31:36 +02:00
|
|
|
hasData = true;
|
|
|
|
|
if (stdOut)
|
|
|
|
|
stdOut->append(newStdOut);
|
|
|
|
|
}
|
|
|
|
|
// Check 'stderr' separately. This is a special handling
|
|
|
|
|
// for 'git pull' and the like which prints its progress on stderr.
|
2021-07-29 15:35:53 +02:00
|
|
|
const QByteArray newStdErr = readAllStandardError();
|
2021-04-30 12:31:36 +02:00
|
|
|
if (!newStdErr.isEmpty()) {
|
|
|
|
|
hasData = true;
|
|
|
|
|
if (stdErr)
|
|
|
|
|
stdErr->append(newStdErr);
|
|
|
|
|
}
|
|
|
|
|
// Prompt user, pretend we have data if says 'No'.
|
|
|
|
|
const bool hang = !hasData && !finished;
|
2021-05-14 15:21:54 +02:00
|
|
|
hasData = hang && showTimeOutMessageBox && !askToKill(d->m_process->program());
|
2021-04-30 12:31:36 +02:00
|
|
|
} while (hasData && !finished);
|
|
|
|
|
if (syncDebug)
|
|
|
|
|
qDebug() << "<readDataFromProcess" << finished;
|
|
|
|
|
return finished;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-30 17:21:43 +02:00
|
|
|
QString QtcProcess::normalizeNewlines(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
QString res = text;
|
|
|
|
|
const auto newEnd = std::unique(res.begin(), res.end(), [](const QChar &c1, const QChar &c2) {
|
|
|
|
|
return c1 == '\r' && c2 == '\r'; // QTCREATORBUG-24556
|
|
|
|
|
});
|
|
|
|
|
res.chop(std::distance(newEnd, res.end()));
|
|
|
|
|
res.replace("\r\n", "\n");
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-12 14:25:50 +02:00
|
|
|
QtcProcess::Result QtcProcess::result() const
|
|
|
|
|
{
|
2021-05-17 11:17:53 +02:00
|
|
|
return d->m_result;
|
2021-05-12 14:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setResult(Result result)
|
|
|
|
|
{
|
2021-05-17 11:17:53 +02:00
|
|
|
d->m_result = result;
|
2021-05-12 14:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int QtcProcess::exitCode() const
|
|
|
|
|
{
|
2021-10-26 11:10:06 +02:00
|
|
|
if (d->m_startFailure == QtcProcessPrivate::WrongFileNameFailure)
|
2021-09-02 16:38:40 +02:00
|
|
|
return 255; // This code is being returned by QProcess when FailedToStart error occurred
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
return d->m_process->exitCode();
|
|
|
|
|
return 0;
|
2021-05-12 14:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
2014-02-05 10:43:21 +01:00
|
|
|
|
2021-05-05 15:04:30 +02: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?
|
|
|
|
|
if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
|
|
|
|
|
return QString();
|
|
|
|
|
const int dotIndex = binary.lastIndexOf(QLatin1Char('.'));
|
|
|
|
|
if (dotIndex != -1 && dotIndex == binary.size() - 4)
|
|
|
|
|
return QString();
|
|
|
|
|
|
|
|
|
|
switch (HostOsInfo::hostOs()) {
|
|
|
|
|
case OsTypeLinux:
|
|
|
|
|
case OsTypeOtherUnix:
|
|
|
|
|
case OsTypeOther:
|
|
|
|
|
break;
|
|
|
|
|
case OsTypeWindows: {
|
|
|
|
|
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;
|
|
|
|
|
case OsTypeMac: {
|
|
|
|
|
// 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 QtcProcess::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
|
|
|
|
|
if (HostOsInfo::isWindowsHost()) {
|
|
|
|
|
const QString currentDirBinary = checkBinary(QDir::current(), binary);
|
|
|
|
|
if (!currentDirBinary.isEmpty())
|
|
|
|
|
return currentDirBinary;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QStringList paths = path.split(HostOsInfo::pathListSeparator());
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-26 17:40:57 +02:00
|
|
|
Environment QtcProcess::systemEnvironmentForBinary(const FilePath &filePath)
|
|
|
|
|
{
|
|
|
|
|
if (filePath.needsDevice()) {
|
|
|
|
|
QTC_ASSERT(s_deviceHooks.systemEnvironmentForBinary, return {});
|
|
|
|
|
return s_deviceHooks.systemEnvironmentForBinary(filePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Environment::systemEnvironment();
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-25 11:29:23 +01:00
|
|
|
void QtcProcess::kickoffProcess()
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
d->m_process->kickoffProcess();
|
2022-01-25 11:29:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::interruptProcess()
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
d->m_process->interruptProcess();
|
2022-01-25 11:29:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 QtcProcess::applicationMainThreadID() const
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
return d->m_process->applicationMainThreadID();
|
|
|
|
|
return -1;
|
2022-01-25 11:29:23 +01:00
|
|
|
}
|
|
|
|
|
|
2021-05-14 15:21:54 +02:00
|
|
|
void QtcProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode)
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
d->m_setup.m_procesChannelMode = mode;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcess::ProcessError QtcProcess::error() const
|
|
|
|
|
{
|
2021-10-26 11:10:06 +02:00
|
|
|
if (d->m_startFailure == QtcProcessPrivate::WrongFileNameFailure)
|
2021-08-31 15:24:20 +02:00
|
|
|
return QProcess::FailedToStart;
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
return d->m_process->error();
|
|
|
|
|
return QProcess::UnknownError;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcess::ProcessState QtcProcess::state() const
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
return d->m_process->state();
|
|
|
|
|
return QProcess::NotRunning;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-22 11:21:35 +01:00
|
|
|
bool QtcProcess::isRunning() const
|
|
|
|
|
{
|
|
|
|
|
return state() == QProcess::Running;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-14 15:21:54 +02:00
|
|
|
QString QtcProcess::errorString() const
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
return d->m_process->errorString();
|
|
|
|
|
return d->m_setup.m_initialErrorString;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setErrorString(const QString &str)
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
d->m_process->setErrorString(str);
|
|
|
|
|
else
|
|
|
|
|
d->m_setup.m_initialErrorString = str;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 QtcProcess::processId() const
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
return d->m_process->processId();
|
|
|
|
|
return 0;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcess::waitForStarted(int msecs)
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
QTC_ASSERT(d->m_process, return false);
|
2021-08-19 23:04:39 +02:00
|
|
|
return s_waitForStarted.measureAndRun(&ProcessInterface::waitForStarted, d->m_process, msecs);
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcess::waitForReadyRead(int msecs)
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
QTC_ASSERT(d->m_process, return false);
|
2021-05-14 15:21:54 +02:00
|
|
|
return d->m_process->waitForReadyRead(msecs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcess::waitForFinished(int msecs)
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
QTC_ASSERT(d->m_process, return false);
|
2021-05-14 15:21:54 +02:00
|
|
|
return d->m_process->waitForFinished(msecs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray QtcProcess::readAllStandardOutput()
|
|
|
|
|
{
|
|
|
|
|
QByteArray buf = d->m_stdOut.rawData;
|
|
|
|
|
d->m_stdOut.rawData.clear();
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray QtcProcess::readAllStandardError()
|
|
|
|
|
{
|
|
|
|
|
QByteArray buf = d->m_stdErr.rawData;
|
|
|
|
|
d->m_stdErr.rawData.clear();
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcess::ExitStatus QtcProcess::exitStatus() const
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
return d->m_process->exitStatus();
|
|
|
|
|
return QProcess::ExitStatus::NormalExit;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::kill()
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
d->m_process->kill();
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 QtcProcess::write(const QByteArray &input)
|
|
|
|
|
{
|
2021-08-07 11:41:23 +02:00
|
|
|
QTC_ASSERT(processMode() == ProcessMode::Writer, return -1);
|
2022-02-10 18:09:42 +01:00
|
|
|
QTC_ASSERT(d->m_process, return -1);
|
2021-05-14 15:21:54 +02:00
|
|
|
return d->m_process->write(input);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::close()
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
if (d->m_process)
|
|
|
|
|
d->m_process->close();
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-14 15:09:11 +02:00
|
|
|
void QtcProcess::beginFeed()
|
|
|
|
|
{
|
|
|
|
|
d->clearForRun();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::endFeed()
|
|
|
|
|
{
|
|
|
|
|
d->slotFinished(0, QProcess::NormalExit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::feedStdOut(const QByteArray &data)
|
|
|
|
|
{
|
|
|
|
|
d->m_stdOut.append(data);
|
|
|
|
|
d->m_hangTimerCount = 0;
|
|
|
|
|
emit readyReadStandardOutput();
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 15:04:30 +02:00
|
|
|
QString QtcProcess::locateBinary(const QString &binary)
|
|
|
|
|
{
|
|
|
|
|
const QByteArray path = qgetenv("PATH");
|
|
|
|
|
return locateBinary(QString::fromLocal8Bit(path), binary);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 18:21:22 +02:00
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
\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.
|
|
|
|
|
|
2021-06-04 11:40:35 +02:00
|
|
|
The callbacks set with setStdOutCallback(), setStdErrCallback() are called
|
2021-05-06 08:56:42 +02:00
|
|
|
with complete lines based on the '\\n' marker.
|
2021-05-05 18:21:22 +02:00
|
|
|
They would typically be used for log windows.
|
|
|
|
|
|
2021-06-04 11:40:35 +02:00
|
|
|
Alternatively you can used setStdOutLineCallback() and setStdErrLineCallback()
|
|
|
|
|
to process the output line by line.
|
|
|
|
|
|
2021-05-05 18:21:22 +02:00
|
|
|
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
|
|
|
|
|
continuously 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2021-05-14 13:12:46 +02:00
|
|
|
QString QtcProcess::exitMessage()
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-05-14 13:12:46 +02:00
|
|
|
const QString fullCmd = commandLine().toUserOutput();
|
2021-05-12 14:25:50 +02:00
|
|
|
switch (result()) {
|
2021-05-28 13:19:38 +02:00
|
|
|
case FinishedWithSuccess:
|
2021-05-14 13:12:46 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" finished successfully.").arg(fullCmd);
|
2021-05-28 13:19:38 +02:00
|
|
|
case FinishedWithError:
|
2021-05-14 13:12:46 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" terminated with exit code %2.")
|
|
|
|
|
.arg(fullCmd).arg(exitCode());
|
2021-05-05 18:21:22 +02:00
|
|
|
case TerminatedAbnormally:
|
2021-05-14 13:12:46 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" terminated abnormally.").arg(fullCmd);
|
2021-05-05 18:21:22 +02:00
|
|
|
case StartFailed:
|
2021-05-14 13:12:46 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" could not be started.").arg(fullCmd);
|
2021-05-05 18:21:22 +02:00
|
|
|
case Hang:
|
2021-05-06 11:15:28 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" did not respond within the timeout limit (%2 s).")
|
2021-06-08 08:22:52 +02:00
|
|
|
.arg(fullCmd).arg(d->m_maxHangTimerCount);
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
return QString();
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-12 14:25:50 +02:00
|
|
|
QByteArray QtcProcess::allRawOutput() const
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-06-14 15:09:11 +02:00
|
|
|
QTC_CHECK(d->m_stdOut.keepRawData);
|
|
|
|
|
QTC_CHECK(d->m_stdErr.keepRawData);
|
2021-05-17 10:07:16 +02:00
|
|
|
if (!d->m_stdOut.rawData.isEmpty() && !d->m_stdErr.rawData.isEmpty()) {
|
|
|
|
|
QByteArray result = d->m_stdOut.rawData;
|
2021-05-05 18:21:22 +02:00
|
|
|
if (!result.endsWith('\n'))
|
|
|
|
|
result += '\n';
|
2021-05-17 10:07:16 +02:00
|
|
|
result += d->m_stdErr.rawData;
|
2021-05-05 18:21:22 +02:00
|
|
|
return result;
|
|
|
|
|
}
|
2021-05-17 10:07:16 +02:00
|
|
|
return !d->m_stdOut.rawData.isEmpty() ? d->m_stdOut.rawData : d->m_stdErr.rawData;
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-12 14:25:50 +02:00
|
|
|
QString QtcProcess::allOutput() const
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-06-14 15:09:11 +02:00
|
|
|
QTC_CHECK(d->m_stdOut.keepRawData);
|
|
|
|
|
QTC_CHECK(d->m_stdErr.keepRawData);
|
2021-05-05 18:21:22 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-12 14:25:50 +02:00
|
|
|
QString QtcProcess::stdOut() const
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-06-14 15:09:11 +02:00
|
|
|
QTC_CHECK(d->m_stdOut.keepRawData);
|
2021-05-17 10:07:16 +02:00
|
|
|
return normalizeNewlines(d->m_codec->toUnicode(d->m_stdOut.rawData));
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-12 14:25:50 +02:00
|
|
|
QString QtcProcess::stdErr() const
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-08-11 16:41:49 +02:00
|
|
|
// FIXME: The tighter check below is actually good theoretically, but currently
|
|
|
|
|
// ShellCommand::runFullySynchronous triggers it and disentangling there
|
|
|
|
|
// is not trivial. So weaken it a bit for now.
|
|
|
|
|
//QTC_CHECK(d->m_stdErr.keepRawData);
|
|
|
|
|
QTC_CHECK(d->m_stdErr.keepRawData || d->m_stdErr.rawData.isEmpty());
|
2021-05-17 10:07:16 +02:00
|
|
|
return normalizeNewlines(d->m_codec->toUnicode(d->m_stdErr.rawData));
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-12 14:25:50 +02:00
|
|
|
QByteArray QtcProcess::rawStdOut() const
|
|
|
|
|
{
|
2021-06-14 15:09:11 +02:00
|
|
|
QTC_CHECK(d->m_stdOut.keepRawData);
|
2021-05-17 10:07:16 +02:00
|
|
|
return d->m_stdOut.rawData;
|
2021-05-12 14:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const QtcProcess &r)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
QDebug nsp = str.nospace();
|
2021-05-12 14:25:50 +02:00
|
|
|
nsp << "QtcProcess: result="
|
2021-05-17 11:17:53 +02:00
|
|
|
<< r.d->m_result << " ex=" << r.exitCode() << '\n'
|
2021-05-17 10:07:16 +02:00
|
|
|
<< r.d->m_stdOut.rawData.size() << " bytes stdout, stderr=" << r.d->m_stdErr.rawData << '\n';
|
2021-05-05 18:21:22 +02:00
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ChannelBuffer::clearForRun()
|
|
|
|
|
{
|
|
|
|
|
rawData.clear();
|
|
|
|
|
codecState.reset(new QTextCodec::ConverterState);
|
|
|
|
|
incompleteLineBuffer.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check for complete lines read from the device and return them, moving the
|
|
|
|
|
* buffer position. */
|
2021-06-14 15:09:11 +02:00
|
|
|
void ChannelBuffer::append(const QByteArray &text)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-06-14 15:09:11 +02:00
|
|
|
if (text.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (keepRawData)
|
|
|
|
|
rawData += text;
|
|
|
|
|
|
|
|
|
|
// Line-wise operation below:
|
|
|
|
|
if (!outputCallback)
|
|
|
|
|
return;
|
|
|
|
|
|
2021-05-05 18:21:22 +02:00
|
|
|
// Convert and append the new input to the buffer of incomplete lines
|
2021-06-14 15:09:11 +02:00
|
|
|
incompleteLineBuffer.append(codec->toUnicode(text.constData(), text.size(), codecState.get()));
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2021-06-14 15:09:11 +02:00
|
|
|
do {
|
|
|
|
|
// Any completed lines in the incompleteLineBuffer?
|
|
|
|
|
int pos = -1;
|
|
|
|
|
if (emitSingleLines) {
|
|
|
|
|
const int posn = incompleteLineBuffer.indexOf('\n');
|
|
|
|
|
const int posr = incompleteLineBuffer.indexOf('\r');
|
|
|
|
|
if (posn != -1) {
|
|
|
|
|
if (posr != -1) {
|
|
|
|
|
if (posn == posr + 1)
|
|
|
|
|
pos = posn; // \r followed by \n -> line end, use the \n.
|
|
|
|
|
else
|
|
|
|
|
pos = qMin(posr, posn); // free floating \r and \n: Use the first one.
|
|
|
|
|
} else {
|
|
|
|
|
pos = posn;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pos = posr; // Make sure internal '\r' triggers a line output
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pos = qMax(incompleteLineBuffer.lastIndexOf('\n'),
|
|
|
|
|
incompleteLineBuffer.lastIndexOf('\r'));
|
|
|
|
|
}
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2021-06-14 15:09:11 +02:00
|
|
|
if (pos == -1)
|
|
|
|
|
break;
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2021-06-14 15:09:11 +02:00
|
|
|
// Get completed lines and remove them from the incompleteLinesBuffer:
|
|
|
|
|
const QString line = QtcProcess::normalizeNewlines(incompleteLineBuffer.left(pos + 1));
|
|
|
|
|
incompleteLineBuffer = incompleteLineBuffer.mid(pos + 1);
|
|
|
|
|
|
|
|
|
|
QTC_ASSERT(outputCallback, return);
|
|
|
|
|
outputCallback(line);
|
2021-06-04 11:40:35 +02:00
|
|
|
|
2021-06-14 15:09:11 +02:00
|
|
|
if (!emitSingleLines)
|
|
|
|
|
break;
|
|
|
|
|
} while (true);
|
2021-06-04 11:40:35 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-14 15:09:11 +02:00
|
|
|
void ChannelBuffer::handleRest()
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-06-14 15:09:11 +02:00
|
|
|
if (outputCallback && !incompleteLineBuffer.isEmpty()) {
|
|
|
|
|
outputCallback(incompleteLineBuffer);
|
|
|
|
|
incompleteLineBuffer.clear();
|
2021-06-04 11:40:35 +02:00
|
|
|
}
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setTimeoutS(int timeoutS)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
if (timeoutS > 0)
|
|
|
|
|
d->m_maxHangTimerCount = qMax(2, timeoutS);
|
|
|
|
|
else
|
|
|
|
|
d->m_maxHangTimerCount = INT_MAX / 1000;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setCodec(QTextCodec *c)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(c, return);
|
|
|
|
|
d->m_codec = c;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setTimeOutMessageBoxEnabled(bool v)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
d->m_timeOutMessageBoxEnabled = v;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setExitCodeInterpreter(const ExitCodeInterpreter &interpreter)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
d->m_exitCodeInterpreter = interpreter;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 17:54:28 +02:00
|
|
|
void QtcProcess::setWriteData(const QByteArray &writeData)
|
|
|
|
|
{
|
|
|
|
|
d->m_writeData = writeData;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-05 18:21:22 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
|
|
|
|
static bool isGuiThread()
|
|
|
|
|
{
|
|
|
|
|
return QThread::currentThread() == QCoreApplication::instance()->thread();
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2022-01-24 19:57:52 +01:00
|
|
|
void QtcProcess::runBlocking(QtcProcess::EventLoopMode eventLoopMode)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
// FIXME: Implement properly
|
|
|
|
|
|
2021-07-16 10:59:45 +02:00
|
|
|
if (d->m_commandLine.executable().needsDevice()) {
|
2021-05-20 13:14:15 +02:00
|
|
|
QtcProcess::start();
|
2021-05-06 17:51:09 +02:00
|
|
|
waitForFinished();
|
2021-05-12 14:25:50 +02:00
|
|
|
return;
|
2021-05-05 18:21:22 +02:00
|
|
|
};
|
|
|
|
|
|
2021-05-20 13:14:15 +02:00
|
|
|
qCDebug(processLog).noquote() << "Starting blocking:" << d->m_commandLine.toUserOutput()
|
2022-01-24 19:57:52 +01:00
|
|
|
<< " process user events: " << (eventLoopMode == QtcProcess::WithEventLoop);
|
2021-05-12 14:25:50 +02:00
|
|
|
ExecuteOnDestruction logResult([this] { qCDebug(processLog) << *this; });
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2022-02-10 18:09:42 +01:00
|
|
|
QtcProcess::start();
|
2022-01-24 19:57:52 +01:00
|
|
|
if (eventLoopMode == QtcProcess::WithEventLoop) {
|
2021-05-20 13:14:15 +02: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.
|
2021-10-26 11:10:06 +02:00
|
|
|
if (d->m_startFailure == QtcProcessPrivate::NoFailure) {
|
2021-10-20 17:38:11 +02:00
|
|
|
QTimer timer(this);
|
|
|
|
|
connect(&timer, &QTimer::timeout, d, &QtcProcessPrivate::slotTimeout);
|
|
|
|
|
timer.setInterval(1000);
|
|
|
|
|
timer.start();
|
2021-05-05 18:21:22 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
2021-05-20 13:14:15 +02:00
|
|
|
if (isGuiThread())
|
|
|
|
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
2021-05-05 18:21:22 +02:00
|
|
|
#endif
|
2021-10-20 17:38:11 +02:00
|
|
|
QEventLoop eventLoop(this);
|
|
|
|
|
QTC_ASSERT(!d->m_eventLoop, return);
|
|
|
|
|
d->m_eventLoop = &eventLoop;
|
|
|
|
|
eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
|
|
|
|
|
d->m_eventLoop = nullptr;
|
2021-05-14 15:21:54 +02:00
|
|
|
d->m_stdOut.append(d->m_process->readAllStandardOutput());
|
|
|
|
|
d->m_stdErr.append(d->m_process->readAllStandardError());
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2021-10-20 17:38:11 +02:00
|
|
|
timer.stop();
|
2021-05-05 18:21:22 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
2021-05-20 13:14:15 +02:00
|
|
|
if (isGuiThread())
|
|
|
|
|
QApplication::restoreOverrideCursor();
|
2021-05-05 18:21:22 +02:00
|
|
|
#endif
|
2021-05-20 13:14:15 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!waitForStarted(d->m_maxHangTimerCount * 1000)) {
|
|
|
|
|
d->m_result = QtcProcess::StartFailed;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!waitForFinished(d->m_maxHangTimerCount * 1000)) {
|
|
|
|
|
d->m_result = QtcProcess::Hang;
|
|
|
|
|
terminate();
|
|
|
|
|
if (!waitForFinished(1000)) {
|
|
|
|
|
kill();
|
|
|
|
|
waitForFinished(1000);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2021-05-20 13:14:15 +02:00
|
|
|
if (state() != QProcess::NotRunning)
|
|
|
|
|
return;
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2021-05-14 15:21:54 +02:00
|
|
|
d->m_stdOut.append(d->m_process->readAllStandardOutput());
|
|
|
|
|
d->m_stdErr.append(d->m_process->readAllStandardError());
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setStdOutCallback(const std::function<void (const QString &)> &callback)
|
2021-05-06 08:56:42 +02:00
|
|
|
{
|
|
|
|
|
d->m_stdOut.outputCallback = callback;
|
2021-06-14 15:09:11 +02:00
|
|
|
d->m_stdOut.emitSingleLines = false;
|
2021-05-06 08:56:42 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-04 11:40:35 +02:00
|
|
|
void QtcProcess::setStdOutLineCallback(const std::function<void (const QString &)> &callback)
|
|
|
|
|
{
|
2021-06-14 15:09:11 +02:00
|
|
|
d->m_stdOut.outputCallback = callback;
|
|
|
|
|
d->m_stdOut.emitSingleLines = true;
|
|
|
|
|
d->m_stdOut.keepRawData = false;
|
2021-06-04 11:40:35 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcess::setStdErrCallback(const std::function<void (const QString &)> &callback)
|
2021-05-06 08:56:42 +02:00
|
|
|
{
|
|
|
|
|
d->m_stdErr.outputCallback = callback;
|
2021-06-14 15:09:11 +02:00
|
|
|
d->m_stdErr.emitSingleLines = false;
|
2021-05-06 08:56:42 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-04 11:40:35 +02:00
|
|
|
void QtcProcess::setStdErrLineCallback(const std::function<void (const QString &)> &callback)
|
|
|
|
|
{
|
2021-06-14 15:09:11 +02:00
|
|
|
d->m_stdErr.outputCallback = callback;
|
|
|
|
|
d->m_stdErr.emitSingleLines = true;
|
|
|
|
|
d->m_stdErr.keepRawData = false;
|
2021-06-04 11:40:35 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void QtcProcessPrivate::slotTimeout()
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-05-06 11:15:28 +02:00
|
|
|
if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) {
|
2021-05-05 18:21:22 +02:00
|
|
|
if (debug)
|
|
|
|
|
qDebug() << Q_FUNC_INFO << "HANG detected, killing";
|
2021-05-06 11:15:28 +02:00
|
|
|
m_waitingForUser = true;
|
2021-06-14 15:09:11 +02:00
|
|
|
const bool terminate = !m_timeOutMessageBoxEnabled
|
|
|
|
|
|| askToKill(m_commandLine.executable().toString());
|
2021-05-06 11:15:28 +02:00
|
|
|
m_waitingForUser = false;
|
2021-05-05 18:21:22 +02:00
|
|
|
if (terminate) {
|
2021-05-06 11:15:28 +02:00
|
|
|
q->stopProcess();
|
2021-05-17 11:17:53 +02:00
|
|
|
m_result = QtcProcess::Hang;
|
2021-05-05 18:21:22 +02:00
|
|
|
} else {
|
2021-05-06 11:15:28 +02:00
|
|
|
m_hangTimerCount = 0;
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (debug)
|
2021-05-06 11:15:28 +02:00
|
|
|
qDebug() << Q_FUNC_INFO << m_hangTimerCount;
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-28 14:48:25 +02:00
|
|
|
void QtcProcessPrivate::slotFinished(int exitCode, QProcess::ExitStatus status)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
|
|
|
|
if (debug)
|
2021-05-28 14:48:25 +02:00
|
|
|
qDebug() << Q_FUNC_INFO << exitCode << status;
|
2021-05-06 11:15:28 +02:00
|
|
|
m_hangTimerCount = 0;
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2021-05-28 14:48:25 +02:00
|
|
|
switch (status) {
|
2021-05-05 18:21:22 +02:00
|
|
|
case QProcess::NormalExit:
|
2021-05-17 11:17:53 +02:00
|
|
|
m_result = interpretExitCode(exitCode);
|
2021-05-05 18:21:22 +02:00
|
|
|
break;
|
|
|
|
|
case QProcess::CrashExit:
|
|
|
|
|
// Was hang detected before and killed?
|
2021-05-17 11:17:53 +02:00
|
|
|
if (m_result != QtcProcess::Hang)
|
|
|
|
|
m_result = QtcProcess::TerminatedAbnormally;
|
2021-05-05 18:21:22 +02:00
|
|
|
break;
|
|
|
|
|
}
|
2021-10-20 17:38:11 +02:00
|
|
|
if (m_eventLoop)
|
|
|
|
|
m_eventLoop->quit();
|
2021-06-14 15:09:11 +02:00
|
|
|
|
|
|
|
|
m_stdOut.handleRest();
|
|
|
|
|
m_stdErr.handleRest();
|
|
|
|
|
|
2021-05-28 14:48:25 +02:00
|
|
|
emit q->finished();
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2021-10-26 11:10:06 +02:00
|
|
|
void QtcProcessPrivate::handleError(QProcess::ProcessError error, StartFailure startFailure)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2021-05-06 11:15:28 +02:00
|
|
|
m_hangTimerCount = 0;
|
2021-05-05 18:21:22 +02:00
|
|
|
if (debug)
|
2021-06-22 04:05:20 +02:00
|
|
|
qDebug() << Q_FUNC_INFO << error;
|
2021-05-05 18:21:22 +02:00
|
|
|
// Was hang detected before and killed?
|
2021-05-17 11:17:53 +02:00
|
|
|
if (m_result != QtcProcess::Hang)
|
|
|
|
|
m_result = QtcProcess::StartFailed;
|
2021-10-26 11:10:06 +02:00
|
|
|
m_startFailure = startFailure;
|
2021-10-20 17:38:11 +02:00
|
|
|
if (m_eventLoop)
|
|
|
|
|
m_eventLoop->quit();
|
2021-06-22 04:05:20 +02:00
|
|
|
|
|
|
|
|
emit q->errorOccurred(error);
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2013-07-12 09:00:12 +02:00
|
|
|
} // namespace Utils
|
2021-07-09 09:09:05 +02:00
|
|
|
|
|
|
|
|
#include "qtcprocess.moc"
|