2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
2010-10-14 18:05:43 +02:00
|
|
|
|
|
|
|
|
#include "qtcprocess.h"
|
|
|
|
|
|
2022-04-25 20:12:43 +02:00
|
|
|
#include "algorithm.h"
|
2022-08-24 09:43:33 +02:00
|
|
|
#include "environment.h"
|
2022-05-24 09:08:41 +02:00
|
|
|
#include "guard.h"
|
2021-05-05 18:21:22 +02:00
|
|
|
#include "hostosinfo.h"
|
2021-07-09 11:29:32 +02:00
|
|
|
#include "launcherinterface.h"
|
|
|
|
|
#include "launchersocket.h"
|
2021-09-06 14:48:08 +02:00
|
|
|
#include "processreaper.h"
|
2022-02-18 00:56:14 +01:00
|
|
|
#include "processutils.h"
|
2022-01-21 16:15:17 +01:00
|
|
|
#include "terminalprocess_p.h"
|
2022-10-10 15:59:26 +02:00
|
|
|
#include "threadutils.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>
|
2022-02-21 15:41:09 +01:00
|
|
|
#include <chrono>
|
2021-08-19 23:04:39 +02:00
|
|
|
#include <functional>
|
2022-04-11 15:54:29 +02:00
|
|
|
#include <iostream>
|
2021-08-19 23:04:39 +02:00
|
|
|
#include <limits>
|
2021-05-05 18:21:22 +02:00
|
|
|
#include <memory>
|
|
|
|
|
|
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
|
|
|
|
2022-02-16 23:55:44 +01:00
|
|
|
const char QTC_PROCESS_BLOCKING_TYPE[] = "__BLOCKING_TYPE__";
|
2022-02-17 10:22:09 +01:00
|
|
|
const char QTC_PROCESS_NUMBER[] = "__NUMBER__";
|
2022-02-21 15:41:09 +01:00
|
|
|
const char QTC_PROCESS_STARTTIME[] = "__STARTTIME__";
|
2022-02-16 23:55:44 +01:00
|
|
|
|
2021-08-19 23:04:39 +02:00
|
|
|
class MeasureAndRun
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
MeasureAndRun(const char *functionName)
|
|
|
|
|
: m_functionName(functionName)
|
2022-08-24 09:43:33 +02:00
|
|
|
, m_measureProcess(qtcEnvironmentVariableIsSet("QTC_MEASURE_PROCESS"))
|
2021-08-19 23:04:39 +02:00
|
|
|
{}
|
|
|
|
|
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();
|
2022-10-10 15:59:26 +02:00
|
|
|
const bool mainThread = isMainThread();
|
2021-08-19 23:04:39 +02:00
|
|
|
const int hitThisAll = m_hitThisAll.fetch_add(1) + 1;
|
|
|
|
|
const int hitAllAll = m_hitAllAll.fetch_add(1) + 1;
|
2022-10-10 15:59:26 +02:00
|
|
|
const int hitThisMain = mainThread
|
2021-08-19 23:04:39 +02:00
|
|
|
? m_hitThisMain.fetch_add(1) + 1
|
|
|
|
|
: m_hitThisMain.load();
|
2022-10-10 15:59:26 +02:00
|
|
|
const int hitAllMain = mainThread
|
2021-08-19 23:04:39 +02:00
|
|
|
? 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);
|
2022-10-10 15:59:26 +02:00
|
|
|
const qint64 totalThisMain = toMs(mainThread
|
2021-08-19 23:04:39 +02:00
|
|
|
? m_totalThisMain.fetch_add(currentNsecs) + currentNsecs
|
|
|
|
|
: m_totalThisMain.load());
|
2022-10-10 15:59:26 +02:00
|
|
|
const qint64 totalAllMain = toMs(mainThread
|
2021-08-19 23:04:39 +02:00
|
|
|
? m_totalAllMain.fetch_add(currentNsecs) + currentNsecs
|
|
|
|
|
: m_totalAllMain.load());
|
|
|
|
|
printMeasurement(QLatin1String(m_functionName), hitThisAll, toMs(currentNsecs),
|
2022-10-10 15:59:26 +02:00
|
|
|
totalThisAll, hitAllAll, totalAllAll, mainThread,
|
2021-08-19 23:04:39 +02:00
|
|
|
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));
|
|
|
|
|
}
|
2022-10-06 17:06:36 +02:00
|
|
|
static QString formatField(int number, int fieldWidth, const QString &suffix = {})
|
2021-08-19 23:04:39 +02:00
|
|
|
{
|
|
|
|
|
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)
|
2022-02-21 15:41:09 +01:00
|
|
|
static Q_LOGGING_CATEGORY(processStdoutLog, "qtc.utils.qtcprocess.stdout", QtWarningMsg)
|
|
|
|
|
static Q_LOGGING_CATEGORY(processStderrLog, "qtc.utils.qtcprocess.stderr", 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
|
|
|
|
2022-05-06 11:06:13 +02:00
|
|
|
QByteArray readAllData() { return std::exchange(rawData, {}); }
|
|
|
|
|
|
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;
|
2022-10-07 12:05:37 +02:00
|
|
|
TextChannelMode m_textChannelMode = TextChannelMode::Off;
|
2021-06-14 15:09:11 +02:00
|
|
|
|
|
|
|
|
bool emitSingleLines = true;
|
|
|
|
|
bool keepRawData = true;
|
2021-05-06 11:15:28 +02:00
|
|
|
};
|
|
|
|
|
|
2022-02-28 17:07:24 +01:00
|
|
|
class DefaultImpl : public ProcessInterface
|
|
|
|
|
{
|
|
|
|
|
private:
|
2022-04-12 00:23:20 +02:00
|
|
|
virtual void start() final;
|
2022-02-28 17:07:24 +01:00
|
|
|
virtual void doDefaultStart(const QString &program, const QStringList &arguments) = 0;
|
|
|
|
|
bool dissolveCommand(QString *program, QStringList *arguments);
|
|
|
|
|
bool ensureProgramExists(const QString &program);
|
|
|
|
|
};
|
|
|
|
|
|
2022-04-12 00:23:20 +02:00
|
|
|
void DefaultImpl::start()
|
2022-02-28 17:07:24 +01:00
|
|
|
{
|
|
|
|
|
QString program;
|
|
|
|
|
QStringList arguments;
|
|
|
|
|
if (!dissolveCommand(&program, &arguments))
|
|
|
|
|
return;
|
|
|
|
|
if (!ensureProgramExists(program))
|
|
|
|
|
return;
|
|
|
|
|
s_start.measureAndRun(&DefaultImpl::doDefaultStart, this, program, arguments);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DefaultImpl::dissolveCommand(QString *program, QStringList *arguments)
|
|
|
|
|
{
|
2022-04-12 08:50:46 +02:00
|
|
|
const CommandLine &commandLine = m_setup.m_commandLine;
|
2022-02-28 17:07:24 +01:00
|
|
|
QString commandString;
|
|
|
|
|
ProcessArgs processArgs;
|
|
|
|
|
const bool success = ProcessArgs::prepareCommand(commandLine, &commandString, &processArgs,
|
2022-04-12 08:50:46 +02:00
|
|
|
&m_setup.m_environment,
|
|
|
|
|
&m_setup.m_workingDirectory);
|
2022-02-28 17:07:24 +01:00
|
|
|
|
|
|
|
|
if (commandLine.executable().osType() == OsTypeWindows) {
|
|
|
|
|
QString args;
|
2022-04-12 08:50:46 +02:00
|
|
|
if (m_setup.m_useCtrlCStub) {
|
|
|
|
|
if (m_setup.m_lowPriority)
|
2022-02-28 17:07:24 +01:00
|
|
|
ProcessArgs::addArg(&args, "-nice");
|
|
|
|
|
ProcessArgs::addArg(&args, QDir::toNativeSeparators(commandString));
|
|
|
|
|
commandString = QCoreApplication::applicationDirPath()
|
|
|
|
|
+ QLatin1String("/qtcreator_ctrlc_stub.exe");
|
2022-04-12 08:50:46 +02:00
|
|
|
} else if (m_setup.m_lowPriority) {
|
|
|
|
|
m_setup.m_belowNormalPriority = true;
|
2022-02-28 17:07:24 +01:00
|
|
|
}
|
|
|
|
|
ProcessArgs::addArgs(&args, processArgs.toWindowsArgs());
|
2022-04-12 08:50:46 +02:00
|
|
|
m_setup.m_nativeArguments = args;
|
2022-02-28 17:07:24 +01:00
|
|
|
// Note: Arguments set with setNativeArgs will be appended to the ones
|
|
|
|
|
// passed with start() below.
|
2022-10-06 16:51:07 +02:00
|
|
|
*arguments = {};
|
2022-02-28 17:07:24 +01:00
|
|
|
} else {
|
|
|
|
|
if (!success) {
|
2022-06-24 13:19:04 +02:00
|
|
|
const ProcessResultData result = {0,
|
|
|
|
|
QProcess::NormalExit,
|
|
|
|
|
QProcess::FailedToStart,
|
|
|
|
|
QtcProcess::tr("Error in command line.")};
|
2022-04-11 11:28:39 +02:00
|
|
|
emit done(result);
|
2022-02-28 17:07:24 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
*arguments = processArgs.toUnixArgs();
|
|
|
|
|
}
|
|
|
|
|
*program = commandString;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static FilePath resolve(const FilePath &workingDir, const FilePath &filePath)
|
|
|
|
|
{
|
|
|
|
|
if (filePath.isAbsolutePath())
|
|
|
|
|
return filePath;
|
|
|
|
|
|
|
|
|
|
const FilePath fromWorkingDir = workingDir.resolvePath(filePath);
|
|
|
|
|
if (fromWorkingDir.exists() && fromWorkingDir.isExecutableFile())
|
|
|
|
|
return fromWorkingDir;
|
|
|
|
|
return filePath.searchInPath();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DefaultImpl::ensureProgramExists(const QString &program)
|
|
|
|
|
{
|
2022-04-12 08:50:46 +02:00
|
|
|
const FilePath programFilePath = resolve(m_setup.m_workingDirectory,
|
2022-02-28 17:07:24 +01:00
|
|
|
FilePath::fromString(program));
|
|
|
|
|
if (programFilePath.exists() && programFilePath.isExecutableFile())
|
|
|
|
|
return true;
|
|
|
|
|
|
2022-06-24 13:19:04 +02:00
|
|
|
const QString errorString
|
|
|
|
|
= QtcProcess::tr("The program \"%1\" does not exist or is not executable.").arg(program);
|
2022-04-11 11:28:39 +02:00
|
|
|
const ProcessResultData result = { 0, QProcess::NormalExit, QProcess::FailedToStart,
|
|
|
|
|
errorString };
|
|
|
|
|
emit done(result);
|
2022-02-28 17:07:24 +01:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
class QProcessBlockingImpl : public ProcessBlockingInterface
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
QProcessBlockingImpl(QProcess *process) : m_process(process) {}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool waitForSignal(ProcessSignalType signalType, int msecs) final
|
|
|
|
|
{
|
|
|
|
|
switch (signalType) {
|
|
|
|
|
case ProcessSignalType::Started:
|
|
|
|
|
return m_process->waitForStarted(msecs);
|
|
|
|
|
case ProcessSignalType::ReadyRead:
|
|
|
|
|
return m_process->waitForReadyRead(msecs);
|
|
|
|
|
case ProcessSignalType::Done:
|
|
|
|
|
return m_process->waitForFinished(msecs);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcess *m_process = nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
2022-04-08 17:24:59 +02:00
|
|
|
class QProcessImpl final : public DefaultImpl
|
2021-07-09 09:09:05 +02:00
|
|
|
{
|
|
|
|
|
public:
|
2022-06-08 14:24:40 +02:00
|
|
|
QProcessImpl()
|
|
|
|
|
: m_process(new ProcessHelper(this))
|
|
|
|
|
, m_blockingImpl(new QProcessBlockingImpl(m_process))
|
2021-07-09 09:09:05 +02:00
|
|
|
{
|
2022-07-19 22:32:39 +02:00
|
|
|
connect(m_process, &QProcess::started, this, &QProcessImpl::handleStarted);
|
|
|
|
|
connect(m_process, &QProcess::finished, this, &QProcessImpl::handleFinished);
|
|
|
|
|
connect(m_process, &QProcess::errorOccurred, this, &QProcessImpl::handleError);
|
2022-04-11 15:54:29 +02:00
|
|
|
connect(m_process, &QProcess::readyReadStandardOutput, this, [this] {
|
|
|
|
|
emit readyRead(m_process->readAllStandardOutput(), {});
|
|
|
|
|
});
|
|
|
|
|
connect(m_process, &QProcess::readyReadStandardError, this, [this] {
|
|
|
|
|
emit readyRead({}, m_process->readAllStandardError());
|
|
|
|
|
});
|
2021-07-09 09:09:05 +02:00
|
|
|
}
|
2022-06-03 14:28:30 +02:00
|
|
|
~QProcessImpl() final { ProcessReaper::reap(m_process, m_setup.m_reaperTimeout); }
|
2022-04-08 17:24:59 +02:00
|
|
|
|
2022-04-12 00:23:20 +02:00
|
|
|
private:
|
2022-04-08 17:24:59 +02:00
|
|
|
qint64 write(const QByteArray &data) final { return m_process->write(data); }
|
2022-04-11 19:45:04 +02:00
|
|
|
void sendControlSignal(ControlSignal controlSignal) final {
|
|
|
|
|
switch (controlSignal) {
|
|
|
|
|
case ControlSignal::Terminate:
|
|
|
|
|
ProcessHelper::terminateProcess(m_process);
|
|
|
|
|
break;
|
|
|
|
|
case ControlSignal::Kill:
|
|
|
|
|
m_process->kill();
|
|
|
|
|
break;
|
|
|
|
|
case ControlSignal::Interrupt:
|
|
|
|
|
ProcessHelper::interruptProcess(m_process);
|
|
|
|
|
break;
|
|
|
|
|
case ControlSignal::KickOff:
|
|
|
|
|
QTC_CHECK(false);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-08 17:24:59 +02:00
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
virtual ProcessBlockingInterface *processBlockingInterface() const { return m_blockingImpl; }
|
2021-07-09 09:09:05 +02:00
|
|
|
|
2022-04-08 17:24:59 +02:00
|
|
|
void doDefaultStart(const QString &program, const QStringList &arguments) final
|
2022-02-16 01:48:40 +01:00
|
|
|
{
|
2022-04-27 18:57:02 +02:00
|
|
|
QTC_ASSERT(QThread::currentThread()->eventDispatcher(),
|
|
|
|
|
qWarning("QtcProcess::start(): Starting a process in a non QThread thread "
|
|
|
|
|
"may cause infinite hang when destroying the running process."));
|
2022-02-16 01:48:40 +01:00
|
|
|
ProcessStartHandler *handler = m_process->processStartHandler();
|
2022-04-12 08:50:46 +02:00
|
|
|
handler->setProcessMode(m_setup.m_processMode);
|
|
|
|
|
handler->setWriteData(m_setup.m_writeData);
|
|
|
|
|
if (m_setup.m_belowNormalPriority)
|
2022-02-16 01:48:40 +01:00
|
|
|
handler->setBelowNormalPriority();
|
2022-04-12 08:50:46 +02:00
|
|
|
handler->setNativeArguments(m_setup.m_nativeArguments);
|
|
|
|
|
m_process->setProcessEnvironment(m_setup.m_environment.toProcessEnvironment());
|
|
|
|
|
m_process->setWorkingDirectory(m_setup.m_workingDirectory.path());
|
|
|
|
|
m_process->setStandardInputFile(m_setup.m_standardInputFile);
|
2022-04-19 02:28:30 -07:00
|
|
|
m_process->setProcessChannelMode(m_setup.m_processChannelMode);
|
2022-04-12 08:50:46 +02:00
|
|
|
if (m_setup.m_lowPriority)
|
2022-02-16 01:48:40 +01:00
|
|
|
m_process->setLowPriority();
|
2022-04-12 08:50:46 +02:00
|
|
|
if (m_setup.m_unixTerminalDisabled)
|
2022-02-16 01:48:40 +01:00
|
|
|
m_process->setUnixTerminalDisabled();
|
2022-04-12 08:50:46 +02:00
|
|
|
m_process->setUseCtrlCStub(m_setup.m_useCtrlCStub);
|
2022-02-16 01:48:40 +01:00
|
|
|
m_process->start(program, arguments, handler->openMode());
|
|
|
|
|
handler->handleProcessStart();
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-06 16:20:47 +02:00
|
|
|
void handleStarted()
|
|
|
|
|
{
|
2021-12-16 10:43:58 +01:00
|
|
|
m_process->processStartHandler()->handleProcessStarted();
|
2022-04-11 16:37:59 +02:00
|
|
|
emit started(m_process->processId());
|
2021-08-06 16:20:47 +02:00
|
|
|
}
|
2022-04-11 11:28:39 +02:00
|
|
|
|
|
|
|
|
void handleError(QProcess::ProcessError error)
|
|
|
|
|
{
|
|
|
|
|
if (error != QProcess::FailedToStart)
|
|
|
|
|
return;
|
|
|
|
|
const ProcessResultData result = { m_process->exitCode(), m_process->exitStatus(),
|
|
|
|
|
error, m_process->errorString() };
|
|
|
|
|
emit done(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void handleFinished(int exitCode, QProcess::ExitStatus exitStatus)
|
|
|
|
|
{
|
|
|
|
|
const ProcessResultData result = { exitCode, exitStatus,
|
|
|
|
|
m_process->error(), m_process->errorString() };
|
|
|
|
|
emit done(result);
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
ProcessHelper *m_process = nullptr;
|
|
|
|
|
QProcessBlockingImpl *m_blockingImpl = nullptr;
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
class ProcessLauncherBlockingImpl : public ProcessBlockingInterface
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
ProcessLauncherBlockingImpl(CallerHandle *caller) : m_caller(caller) {}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
bool waitForSignal(ProcessSignalType signalType, int msecs) final
|
|
|
|
|
{
|
|
|
|
|
// TODO: Remove CallerHandle::SignalType
|
|
|
|
|
const CallerHandle::SignalType type = [signalType] {
|
|
|
|
|
switch (signalType) {
|
|
|
|
|
case ProcessSignalType::Started:
|
|
|
|
|
return CallerHandle::SignalType::Started;
|
|
|
|
|
case ProcessSignalType::ReadyRead:
|
|
|
|
|
return CallerHandle::SignalType::ReadyRead;
|
|
|
|
|
case ProcessSignalType::Done:
|
|
|
|
|
return CallerHandle::SignalType::Done;
|
|
|
|
|
}
|
|
|
|
|
QTC_CHECK(false);
|
|
|
|
|
return CallerHandle::SignalType::NoSignal;
|
|
|
|
|
}();
|
|
|
|
|
return m_caller->waitForSignal(type, msecs);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CallerHandle *m_caller = nullptr;
|
|
|
|
|
};
|
|
|
|
|
|
2022-04-08 17:24:59 +02:00
|
|
|
class ProcessLauncherImpl final : public DefaultImpl
|
2021-07-09 09:09:05 +02:00
|
|
|
{
|
2021-07-13 15:53:35 +02:00
|
|
|
Q_OBJECT
|
2021-07-09 09:09:05 +02:00
|
|
|
public:
|
2022-02-21 13:14:43 +01:00
|
|
|
ProcessLauncherImpl() : m_token(uniqueToken())
|
2021-07-09 11:29:32 +02:00
|
|
|
{
|
2022-02-21 13:14:43 +01:00
|
|
|
m_handle = LauncherInterface::registerHandle(this, token());
|
2022-04-12 08:50:46 +02:00
|
|
|
m_handle->setProcessSetupData(&m_setup);
|
2021-08-23 17:58:44 +02:00
|
|
|
connect(m_handle, &CallerHandle::started,
|
2021-07-14 16:02:25 +02:00
|
|
|
this, &ProcessInterface::started);
|
2022-04-11 15:54:29 +02:00
|
|
|
connect(m_handle, &CallerHandle::readyRead,
|
|
|
|
|
this, &ProcessInterface::readyRead);
|
2022-04-11 11:28:39 +02:00
|
|
|
connect(m_handle, &CallerHandle::done,
|
|
|
|
|
this, &ProcessInterface::done);
|
2022-06-08 14:24:40 +02:00
|
|
|
m_blockingImpl = new ProcessLauncherBlockingImpl(m_handle);
|
2021-07-14 16:02:25 +02:00
|
|
|
}
|
2022-04-08 17:24:59 +02:00
|
|
|
~ProcessLauncherImpl() final
|
2021-07-14 16:02:25 +02:00
|
|
|
{
|
2022-05-16 11:05:17 +02:00
|
|
|
m_handle->close();
|
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
|
|
|
|
2022-04-12 00:23:20 +02:00
|
|
|
private:
|
2022-04-08 17:24:59 +02:00
|
|
|
qint64 write(const QByteArray &data) final { return m_handle->write(data); }
|
2022-04-11 19:45:04 +02:00
|
|
|
void sendControlSignal(ControlSignal controlSignal) final {
|
|
|
|
|
switch (controlSignal) {
|
|
|
|
|
case ControlSignal::Terminate:
|
|
|
|
|
m_handle->terminate();
|
|
|
|
|
break;
|
|
|
|
|
case ControlSignal::Kill:
|
|
|
|
|
m_handle->kill();
|
|
|
|
|
break;
|
|
|
|
|
case ControlSignal::Interrupt:
|
2022-04-12 08:50:46 +02:00
|
|
|
if (m_setup.m_useCtrlCStub) // bypass launcher and interrupt directly
|
2022-04-11 19:45:04 +02:00
|
|
|
ProcessHelper::interruptPid(m_handle->processId());
|
|
|
|
|
break;
|
|
|
|
|
case ControlSignal::KickOff:
|
|
|
|
|
QTC_CHECK(false);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-08 17:24:59 +02:00
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
virtual ProcessBlockingInterface *processBlockingInterface() const { return m_blockingImpl; }
|
2021-07-09 09:09:05 +02:00
|
|
|
|
2022-04-08 17:24:59 +02:00
|
|
|
void doDefaultStart(const QString &program, const QStringList &arguments) final
|
2022-02-16 01:48:40 +01:00
|
|
|
{
|
2022-02-16 03:21:51 +01:00
|
|
|
m_handle->start(program, arguments);
|
2022-02-16 01:48:40 +01:00
|
|
|
}
|
2021-07-13 15:53:35 +02:00
|
|
|
|
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;
|
2022-06-08 14:24:40 +02:00
|
|
|
ProcessLauncherBlockingImpl *m_blockingImpl = nullptr;
|
2021-07-09 09:09:05 +02:00
|
|
|
};
|
|
|
|
|
|
2022-02-18 00:56:14 +01:00
|
|
|
static ProcessImpl defaultProcessImpl()
|
2021-07-09 09:09:05 +02:00
|
|
|
{
|
2022-08-24 09:43:33 +02:00
|
|
|
if (qtcEnvironmentVariableIsSet("QTC_USE_QPROCESS"))
|
2022-02-18 00:56:14 +01:00
|
|
|
return ProcessImpl::QProcess;
|
|
|
|
|
return ProcessImpl::ProcessLauncher;
|
2022-02-10 18:09:42 +01:00
|
|
|
}
|
|
|
|
|
|
2022-04-25 20:12:43 +02:00
|
|
|
class ProcessInterfaceSignal
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-06-08 08:49:27 +02:00
|
|
|
ProcessSignalType signalType() const { return m_signalType; }
|
2022-04-25 20:12:43 +02:00
|
|
|
virtual ~ProcessInterfaceSignal() = default;
|
|
|
|
|
protected:
|
2022-06-08 08:49:27 +02:00
|
|
|
ProcessInterfaceSignal(ProcessSignalType signalType) : m_signalType(signalType) {}
|
2022-04-25 20:12:43 +02:00
|
|
|
private:
|
2022-06-08 08:49:27 +02:00
|
|
|
const ProcessSignalType m_signalType;
|
2022-04-25 20:12:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class StartedSignal : public ProcessInterfaceSignal
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
StartedSignal(qint64 processId, qint64 applicationMainThreadId)
|
2022-06-08 08:49:27 +02:00
|
|
|
: ProcessInterfaceSignal(ProcessSignalType::Started)
|
2022-04-25 20:12:43 +02:00
|
|
|
, m_processId(processId)
|
|
|
|
|
, m_applicationMainThreadId(applicationMainThreadId) {}
|
|
|
|
|
qint64 processId() const { return m_processId; }
|
|
|
|
|
qint64 applicationMainThreadId() const { return m_applicationMainThreadId; }
|
|
|
|
|
private:
|
|
|
|
|
const qint64 m_processId;
|
|
|
|
|
const qint64 m_applicationMainThreadId;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class ReadyReadSignal : public ProcessInterfaceSignal
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
ReadyReadSignal(const QByteArray &stdOut, const QByteArray &stdErr)
|
2022-06-08 08:49:27 +02:00
|
|
|
: ProcessInterfaceSignal(ProcessSignalType::ReadyRead)
|
2022-04-25 20:12:43 +02:00
|
|
|
, m_stdOut(stdOut)
|
|
|
|
|
, m_stdErr(stdErr) {}
|
|
|
|
|
QByteArray stdOut() const { return m_stdOut; }
|
|
|
|
|
QByteArray stdErr() const { return m_stdErr; }
|
|
|
|
|
private:
|
2022-06-02 16:31:56 +02:00
|
|
|
const QByteArray m_stdOut;
|
|
|
|
|
const QByteArray m_stdErr;
|
2022-04-25 20:12:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class DoneSignal : public ProcessInterfaceSignal
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
DoneSignal(const ProcessResultData &resultData)
|
2022-06-08 08:49:27 +02:00
|
|
|
: ProcessInterfaceSignal(ProcessSignalType::Done)
|
2022-04-25 20:12:43 +02:00
|
|
|
, m_resultData(resultData) {}
|
|
|
|
|
ProcessResultData resultData() const { return m_resultData; }
|
|
|
|
|
private:
|
|
|
|
|
const ProcessResultData m_resultData;
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
class GeneralProcessBlockingImpl;
|
|
|
|
|
|
2022-04-25 20:12:43 +02:00
|
|
|
class ProcessInterfaceHandler : public QObject
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-06-08 14:24:40 +02:00
|
|
|
ProcessInterfaceHandler(GeneralProcessBlockingImpl *caller, ProcessInterface *process);
|
2022-04-25 20:12:43 +02:00
|
|
|
|
|
|
|
|
// Called from caller's thread exclusively.
|
2022-06-08 14:24:40 +02:00
|
|
|
bool waitForSignal(ProcessSignalType newSignal, int msecs);
|
2022-04-25 20:47:09 +02:00
|
|
|
void moveToCallerThread();
|
2022-04-25 20:12:43 +02:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// Called from caller's thread exclusively.
|
|
|
|
|
bool doWaitForSignal(QDeadlineTimer deadline);
|
|
|
|
|
|
|
|
|
|
// Called from caller's thread when not waiting for signal,
|
|
|
|
|
// otherwise called from temporary thread.
|
|
|
|
|
void handleStarted(qint64 processId, qint64 applicationMainThreadId);
|
|
|
|
|
void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData);
|
|
|
|
|
void handleDone(const ProcessResultData &data);
|
|
|
|
|
void appendSignal(ProcessInterfaceSignal *newSignal);
|
|
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
GeneralProcessBlockingImpl *m_caller = nullptr;
|
2022-04-25 20:12:43 +02:00
|
|
|
QMutex m_mutex;
|
|
|
|
|
QWaitCondition m_waitCondition;
|
|
|
|
|
};
|
|
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
class GeneralProcessBlockingImpl : public ProcessBlockingInterface
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
GeneralProcessBlockingImpl(QtcProcessPrivate *parent);
|
|
|
|
|
|
|
|
|
|
void flush() { flushSignals(takeAllSignals()); }
|
|
|
|
|
bool flushFor(ProcessSignalType signalType) {
|
|
|
|
|
return flushSignals(takeSignalsFor(signalType), &signalType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool shouldFlush() const { QMutexLocker locker(&m_mutex); return !m_signals.isEmpty(); }
|
|
|
|
|
// Called from ProcessInterfaceHandler thread exclusively.
|
|
|
|
|
void appendSignal(ProcessInterfaceSignal *launcherSignal);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
// Called from caller's thread exclusively
|
|
|
|
|
bool waitForSignal(ProcessSignalType newSignal, int msecs) final;
|
|
|
|
|
|
|
|
|
|
QList<ProcessInterfaceSignal *> takeAllSignals();
|
|
|
|
|
QList<ProcessInterfaceSignal *> takeSignalsFor(ProcessSignalType signalType);
|
|
|
|
|
bool flushSignals(const QList<ProcessInterfaceSignal *> &signalList,
|
|
|
|
|
ProcessSignalType *signalType = nullptr);
|
|
|
|
|
|
|
|
|
|
void handleStartedSignal(const StartedSignal *launcherSignal);
|
|
|
|
|
void handleReadyReadSignal(const ReadyReadSignal *launcherSignal);
|
|
|
|
|
void handleDoneSignal(const DoneSignal *launcherSignal);
|
|
|
|
|
|
|
|
|
|
QtcProcessPrivate *m_caller = nullptr;
|
|
|
|
|
std::unique_ptr<ProcessInterfaceHandler> m_processHandler;
|
|
|
|
|
mutable QMutex m_mutex;
|
|
|
|
|
QList<ProcessInterfaceSignal *> m_signals;
|
|
|
|
|
};
|
|
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
class QtcProcessPrivate : public QObject
|
|
|
|
|
{
|
|
|
|
|
public:
|
2022-03-29 18:01:08 +02:00
|
|
|
explicit QtcProcessPrivate(QtcProcess *parent)
|
2021-09-02 17:03:49 +02:00
|
|
|
: QObject(parent)
|
|
|
|
|
, q(parent)
|
2022-06-08 09:29:28 +02:00
|
|
|
, m_killTimer(this)
|
2022-05-09 15:02:23 +02:00
|
|
|
{
|
|
|
|
|
m_setup.m_controlEnvironment = Environment::systemEnvironment();
|
2022-06-08 09:29:28 +02:00
|
|
|
m_killTimer.setSingleShot(true);
|
|
|
|
|
connect(&m_killTimer, &QTimer::timeout, this, [this] {
|
|
|
|
|
m_killTimer.stop();
|
|
|
|
|
sendControlSignal(ControlSignal::Kill);
|
|
|
|
|
});
|
2022-10-06 16:51:07 +02:00
|
|
|
setupDebugLog();
|
2022-05-09 15:02:23 +02:00
|
|
|
}
|
2022-02-10 18:09:42 +01:00
|
|
|
|
2022-10-06 16:51:07 +02:00
|
|
|
void setupDebugLog();
|
|
|
|
|
void storeEventLoopDebugInfo(const QVariant &value);
|
|
|
|
|
|
2022-02-14 11:14:08 +01:00
|
|
|
ProcessInterface *createProcessInterface()
|
2022-02-10 19:25:03 +01:00
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
if (m_setup.m_terminalMode != TerminalMode::Off)
|
2022-02-21 15:52:03 +01:00
|
|
|
return new TerminalImpl();
|
|
|
|
|
|
2022-03-29 18:01:08 +02:00
|
|
|
const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default
|
|
|
|
|
? defaultProcessImpl() : m_setup.m_processImpl;
|
2022-02-21 15:52:03 +01:00
|
|
|
if (impl == ProcessImpl::QProcess)
|
2022-02-21 13:14:43 +01:00
|
|
|
return new QProcessImpl();
|
|
|
|
|
return new ProcessLauncherImpl();
|
2022-02-10 19:25:03 +01:00
|
|
|
}
|
|
|
|
|
|
2022-02-14 11:14:08 +01:00
|
|
|
void setProcessInterface(ProcessInterface *process)
|
2021-05-14 15:21:54 +02:00
|
|
|
{
|
2022-02-14 11:14:08 +01:00
|
|
|
m_process.reset(process);
|
2022-06-08 14:24:40 +02:00
|
|
|
m_process->setParent(this);
|
|
|
|
|
connect(m_process.get(), &ProcessInterface::started,
|
|
|
|
|
this, &QtcProcessPrivate::handleStarted);
|
|
|
|
|
connect(m_process.get(), &ProcessInterface::readyRead,
|
|
|
|
|
this, &QtcProcessPrivate::handleReadyRead);
|
|
|
|
|
connect(m_process.get(), &ProcessInterface::done,
|
|
|
|
|
this, &QtcProcessPrivate::handleDone);
|
|
|
|
|
|
|
|
|
|
m_blockingInterface.reset(process->processBlockingInterface());
|
|
|
|
|
if (!m_blockingInterface)
|
|
|
|
|
m_blockingInterface.reset(new GeneralProcessBlockingImpl(this));
|
|
|
|
|
m_blockingInterface->setParent(this);
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
2022-02-01 14:51:23 +01:00
|
|
|
CommandLine fullCommandLine() const
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
if (!m_setup.m_runAsRoot || HostOsInfo::isWindowsHost())
|
|
|
|
|
return m_setup.m_commandLine;
|
2022-02-01 14:51:23 +01:00
|
|
|
CommandLine rootCommand("sudo", {"-A"});
|
2022-03-29 18:01:08 +02:00
|
|
|
rootCommand.addCommandLineAsArgs(m_setup.m_commandLine);
|
2022-02-01 14:51:23 +01:00
|
|
|
return rootCommand;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Environment fullEnvironment() const
|
|
|
|
|
{
|
2022-05-09 15:02:23 +02:00
|
|
|
Environment env = m_setup.m_environment;
|
2022-06-01 15:19:31 +02:00
|
|
|
if (!env.isValid()) {
|
2022-05-09 15:02:23 +02:00
|
|
|
// FIXME: Either switch to using EnvironmentChange instead of full Environments, or
|
|
|
|
|
// feed the full environment into the QtcProcess instead of fixing it up here.
|
|
|
|
|
// qWarning("QtcProcess::start: Empty environment set when running '%s'.",
|
|
|
|
|
// qPrintable(m_setup.m_commandLine.executable().toString()));
|
|
|
|
|
env = m_setup.m_commandLine.executable().deviceEnvironment();
|
2022-02-01 14:51:23 +01:00
|
|
|
}
|
2022-05-09 15:02:23 +02:00
|
|
|
// TODO: needs SshSettings
|
|
|
|
|
// if (m_runAsRoot)
|
|
|
|
|
// RunControl::provideAskPassEntry(env);
|
2022-02-01 14:51:23 +01:00
|
|
|
return env;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-14 15:21:54 +02:00
|
|
|
QtcProcess *q;
|
2022-06-08 14:24:40 +02:00
|
|
|
std::unique_ptr<ProcessBlockingInterface> m_blockingInterface;
|
2022-02-14 11:14:08 +01:00
|
|
|
std::unique_ptr<ProcessInterface> m_process;
|
2022-03-29 18:01:08 +02:00
|
|
|
ProcessSetupData m_setup;
|
2021-05-05 16:05:53 +02:00
|
|
|
|
2021-05-06 11:15:28 +02:00
|
|
|
void slotTimeout();
|
2022-04-11 16:37:59 +02:00
|
|
|
void handleStarted(qint64 processId, qint64 applicationMainThreadId);
|
2022-04-11 15:54:29 +02:00
|
|
|
void handleReadyRead(const QByteArray &outputData, const QByteArray &errorData);
|
2022-04-11 11:28:39 +02:00
|
|
|
void handleDone(const ProcessResultData &data);
|
2021-05-06 11:15:28 +02:00
|
|
|
void clearForRun();
|
|
|
|
|
|
2022-10-06 15:59:16 +02:00
|
|
|
void emitGuardedSignal(void (QtcProcess::* signalName)()) {
|
|
|
|
|
GuardLocker locker(m_guard);
|
|
|
|
|
emit (q->*signalName)();
|
|
|
|
|
}
|
2022-04-04 13:17:36 +02:00
|
|
|
|
2022-03-02 04:12:25 +01:00
|
|
|
ProcessResult interpretExitCode(int exitCode);
|
2021-05-06 15:54:02 +02:00
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
bool waitForSignal(ProcessSignalType signalType, int msecs);
|
2022-04-25 20:47:09 +02:00
|
|
|
Qt::ConnectionType connectionType() const;
|
2022-06-08 09:29:28 +02:00
|
|
|
void sendControlSignal(ControlSignal controlSignal);
|
2022-04-25 20:12:43 +02:00
|
|
|
|
2022-06-08 09:29:28 +02:00
|
|
|
QTimer m_killTimer;
|
2022-04-12 11:18:09 +02:00
|
|
|
QProcess::ProcessState m_state = QProcess::NotRunning;
|
2022-04-11 16:37:59 +02:00
|
|
|
qint64 m_processId = 0;
|
|
|
|
|
qint64 m_applicationMainThreadId = 0;
|
2022-04-11 11:28:39 +02:00
|
|
|
ProcessResultData m_resultData;
|
|
|
|
|
|
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;
|
2022-03-02 04:12:25 +01:00
|
|
|
ProcessResult m_result = ProcessResult::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;
|
|
|
|
|
bool m_timeOutMessageBoxEnabled = false;
|
|
|
|
|
bool m_waitingForUser = false;
|
2022-04-04 16:35:26 +02:00
|
|
|
|
2022-05-24 09:08:41 +02:00
|
|
|
Guard m_guard;
|
2021-05-05 16:05:53 +02:00
|
|
|
};
|
|
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
ProcessInterfaceHandler::ProcessInterfaceHandler(GeneralProcessBlockingImpl *caller,
|
2022-04-25 20:12:43 +02:00
|
|
|
ProcessInterface *process)
|
|
|
|
|
: m_caller(caller)
|
|
|
|
|
{
|
2022-06-08 14:24:40 +02:00
|
|
|
process->disconnect();
|
2022-04-25 20:12:43 +02:00
|
|
|
connect(process, &ProcessInterface::started,
|
|
|
|
|
this, &ProcessInterfaceHandler::handleStarted);
|
|
|
|
|
connect(process, &ProcessInterface::readyRead,
|
|
|
|
|
this, &ProcessInterfaceHandler::handleReadyRead);
|
|
|
|
|
connect(process, &ProcessInterface::done,
|
|
|
|
|
this, &ProcessInterfaceHandler::handleDone);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from caller's thread exclusively.
|
2022-06-08 14:24:40 +02:00
|
|
|
bool ProcessInterfaceHandler::waitForSignal(ProcessSignalType newSignal, int msecs)
|
2022-04-25 20:12:43 +02:00
|
|
|
{
|
|
|
|
|
QDeadlineTimer deadline(msecs);
|
|
|
|
|
while (true) {
|
|
|
|
|
if (deadline.hasExpired())
|
|
|
|
|
break;
|
|
|
|
|
if (!doWaitForSignal(deadline))
|
|
|
|
|
break;
|
|
|
|
|
// Matching (or Done) signal was flushed
|
|
|
|
|
if (m_caller->flushFor(newSignal))
|
|
|
|
|
return true;
|
|
|
|
|
// Otherwise continue awaiting (e.g. when ReadyRead came while waitForFinished())
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 20:47:09 +02:00
|
|
|
// Called from caller's thread exclusively.
|
|
|
|
|
void ProcessInterfaceHandler::moveToCallerThread()
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(this, [this] {
|
2022-06-02 18:10:06 +02:00
|
|
|
moveToThread(m_caller->thread());
|
|
|
|
|
}, Qt::BlockingQueuedConnection);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 20:12:43 +02:00
|
|
|
// Called from caller's thread exclusively.
|
|
|
|
|
bool ProcessInterfaceHandler::doWaitForSignal(QDeadlineTimer deadline)
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
|
|
|
|
|
// Flush, if we have any stored signals.
|
|
|
|
|
// This must be called when holding laucher's mutex locked prior to the call to wait,
|
|
|
|
|
// so that it's done atomically.
|
|
|
|
|
if (m_caller->shouldFlush())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
return m_waitCondition.wait(&m_mutex, deadline);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from ProcessInterfaceHandler thread exclusively
|
|
|
|
|
void ProcessInterfaceHandler::handleStarted(qint64 processId, qint64 applicationMainThreadId)
|
|
|
|
|
{
|
|
|
|
|
appendSignal(new StartedSignal(processId, applicationMainThreadId));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from ProcessInterfaceHandler thread exclusively
|
|
|
|
|
void ProcessInterfaceHandler::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData)
|
|
|
|
|
{
|
|
|
|
|
appendSignal(new ReadyReadSignal(outputData, errorData));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from ProcessInterfaceHandler thread exclusively
|
|
|
|
|
void ProcessInterfaceHandler::handleDone(const ProcessResultData &data)
|
|
|
|
|
{
|
|
|
|
|
appendSignal(new DoneSignal(data));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ProcessInterfaceHandler::appendSignal(ProcessInterfaceSignal *newSignal)
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
m_caller->appendSignal(newSignal);
|
|
|
|
|
}
|
|
|
|
|
m_waitCondition.wakeOne();
|
|
|
|
|
// call in callers thread
|
2022-06-08 14:24:40 +02:00
|
|
|
QMetaObject::invokeMethod(m_caller, &GeneralProcessBlockingImpl::flush);
|
2022-04-25 20:12:43 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
GeneralProcessBlockingImpl::GeneralProcessBlockingImpl(QtcProcessPrivate *parent)
|
|
|
|
|
: m_caller(parent)
|
|
|
|
|
, m_processHandler(new ProcessInterfaceHandler(this, parent->m_process.get()))
|
2022-04-25 20:47:09 +02:00
|
|
|
{
|
2022-06-08 14:24:40 +02:00
|
|
|
// In order to move the process interface into another thread together with handle
|
|
|
|
|
parent->m_process.get()->setParent(m_processHandler.get());
|
|
|
|
|
m_processHandler->setParent(this);
|
|
|
|
|
}
|
2022-06-08 09:29:28 +02:00
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
bool GeneralProcessBlockingImpl::waitForSignal(ProcessSignalType newSignal, int msecs)
|
|
|
|
|
{
|
2022-04-25 20:47:09 +02:00
|
|
|
m_processHandler->setParent(nullptr);
|
|
|
|
|
|
|
|
|
|
QThread thread;
|
|
|
|
|
thread.start();
|
|
|
|
|
// Note: the thread may have started before and it's appending new signals before
|
|
|
|
|
// waitForSignal() is called. However, in this case they won't be flushed since
|
|
|
|
|
// the caller here is blocked, so all signals should be buffered and we are going
|
|
|
|
|
// to flush them from inside waitForSignal().
|
|
|
|
|
m_processHandler->moveToThread(&thread);
|
2022-06-08 14:24:40 +02:00
|
|
|
const bool result = m_processHandler->waitForSignal(newSignal, msecs);
|
2022-04-25 20:47:09 +02:00
|
|
|
m_processHandler->moveToCallerThread();
|
|
|
|
|
m_processHandler->setParent(this);
|
|
|
|
|
thread.quit();
|
|
|
|
|
thread.wait();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 20:12:43 +02:00
|
|
|
// Called from caller's thread exclusively
|
2022-06-08 14:24:40 +02:00
|
|
|
QList<ProcessInterfaceSignal *> GeneralProcessBlockingImpl::takeAllSignals()
|
2022-04-25 20:12:43 +02:00
|
|
|
{
|
2022-06-08 08:27:26 +02:00
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
return std::exchange(m_signals, {});
|
|
|
|
|
}
|
2022-04-25 20:12:43 +02:00
|
|
|
|
2022-06-08 08:27:26 +02:00
|
|
|
// Called from caller's thread exclusively
|
2022-06-08 14:24:40 +02:00
|
|
|
QList<ProcessInterfaceSignal *> GeneralProcessBlockingImpl::takeSignalsFor(ProcessSignalType signalType)
|
2022-06-08 08:27:26 +02:00
|
|
|
{
|
|
|
|
|
// If we are flushing for ReadyRead or Done - flush all.
|
2022-06-08 08:49:27 +02:00
|
|
|
if (signalType != ProcessSignalType::Started)
|
2022-06-08 08:27:26 +02:00
|
|
|
return takeAllSignals();
|
|
|
|
|
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
2022-10-07 14:46:06 +02:00
|
|
|
const QList<ProcessSignalType> storedSignals = transform(std::as_const(m_signals),
|
2022-06-08 08:27:26 +02:00
|
|
|
[](const ProcessInterfaceSignal *aSignal) {
|
|
|
|
|
return aSignal->signalType();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// If we are flushing for Started:
|
|
|
|
|
// - if Started was buffered - flush Started only (even when Done was buffered)
|
|
|
|
|
// - otherwise if Done signal was buffered - flush all.
|
2022-06-08 08:49:27 +02:00
|
|
|
if (!storedSignals.contains(ProcessSignalType::Started)
|
|
|
|
|
&& storedSignals.contains(ProcessSignalType::Done)) {
|
2022-06-08 08:27:26 +02:00
|
|
|
return std::exchange(m_signals, {}); // avoid takeAllSignals() because of mutex locked
|
2022-06-08 08:49:27 +02:00
|
|
|
}
|
2022-06-08 08:27:26 +02:00
|
|
|
|
|
|
|
|
QList<ProcessInterfaceSignal *> oldSignals;
|
|
|
|
|
const auto matchingIndex = storedSignals.lastIndexOf(signalType);
|
|
|
|
|
if (matchingIndex >= 0) {
|
|
|
|
|
oldSignals = m_signals.mid(0, matchingIndex + 1);
|
|
|
|
|
m_signals = m_signals.mid(matchingIndex + 1);
|
2022-04-25 20:12:43 +02:00
|
|
|
}
|
2022-06-08 08:27:26 +02:00
|
|
|
return oldSignals;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from caller's thread exclusively
|
2022-06-08 14:24:40 +02:00
|
|
|
bool GeneralProcessBlockingImpl::flushSignals(const QList<ProcessInterfaceSignal *> &signalList,
|
2022-06-08 08:49:27 +02:00
|
|
|
ProcessSignalType *signalType)
|
2022-06-08 08:27:26 +02:00
|
|
|
{
|
2022-04-25 20:12:43 +02:00
|
|
|
bool signalMatched = false;
|
2022-10-07 14:46:06 +02:00
|
|
|
for (const ProcessInterfaceSignal *storedSignal : std::as_const(signalList)) {
|
2022-06-08 08:49:27 +02:00
|
|
|
const ProcessSignalType storedSignalType = storedSignal->signalType();
|
2022-06-08 08:27:26 +02:00
|
|
|
if (signalType && storedSignalType == *signalType)
|
2022-04-25 20:12:43 +02:00
|
|
|
signalMatched = true;
|
|
|
|
|
switch (storedSignalType) {
|
2022-06-08 08:49:27 +02:00
|
|
|
case ProcessSignalType::Started:
|
2022-04-25 20:12:43 +02:00
|
|
|
handleStartedSignal(static_cast<const StartedSignal *>(storedSignal));
|
|
|
|
|
break;
|
2022-06-08 08:49:27 +02:00
|
|
|
case ProcessSignalType::ReadyRead:
|
2022-04-25 20:12:43 +02:00
|
|
|
handleReadyReadSignal(static_cast<const ReadyReadSignal *>(storedSignal));
|
|
|
|
|
break;
|
2022-06-08 08:49:27 +02:00
|
|
|
case ProcessSignalType::Done:
|
2022-06-08 08:27:26 +02:00
|
|
|
if (signalType)
|
|
|
|
|
signalMatched = true;
|
2022-04-25 20:12:43 +02:00
|
|
|
handleDoneSignal(static_cast<const DoneSignal *>(storedSignal));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
delete storedSignal;
|
|
|
|
|
}
|
|
|
|
|
return signalMatched;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 14:24:40 +02:00
|
|
|
void GeneralProcessBlockingImpl::handleStartedSignal(const StartedSignal *aSignal)
|
|
|
|
|
{
|
|
|
|
|
m_caller->handleStarted(aSignal->processId(), aSignal->applicationMainThreadId());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GeneralProcessBlockingImpl::handleReadyReadSignal(const ReadyReadSignal *aSignal)
|
|
|
|
|
{
|
|
|
|
|
m_caller->handleReadyRead(aSignal->stdOut(), aSignal->stdErr());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GeneralProcessBlockingImpl::handleDoneSignal(const DoneSignal *aSignal)
|
|
|
|
|
{
|
|
|
|
|
m_caller->handleDone(aSignal->resultData());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Called from ProcessInterfaceHandler thread exclusively.
|
|
|
|
|
void GeneralProcessBlockingImpl::appendSignal(ProcessInterfaceSignal *newSignal)
|
|
|
|
|
{
|
|
|
|
|
QMutexLocker locker(&m_mutex);
|
|
|
|
|
m_signals.append(newSignal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcessPrivate::waitForSignal(ProcessSignalType newSignal, int msecs)
|
|
|
|
|
{
|
|
|
|
|
const QDeadlineTimer timeout(msecs);
|
|
|
|
|
const QDeadlineTimer currentKillTimeout(m_killTimer.remainingTime());
|
|
|
|
|
const bool needsSplit = m_killTimer.isActive() ? timeout > currentKillTimeout : false;
|
|
|
|
|
const QDeadlineTimer mainTimeout = needsSplit ? currentKillTimeout : timeout;
|
|
|
|
|
|
|
|
|
|
bool result = m_blockingInterface->waitForSignal(newSignal, mainTimeout.remainingTime());
|
|
|
|
|
if (!result && needsSplit) {
|
|
|
|
|
m_killTimer.stop();
|
|
|
|
|
sendControlSignal(ControlSignal::Kill);
|
|
|
|
|
result = m_blockingInterface->waitForSignal(newSignal, timeout.remainingTime());
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-25 20:47:09 +02:00
|
|
|
Qt::ConnectionType QtcProcessPrivate::connectionType() const
|
|
|
|
|
{
|
|
|
|
|
return (m_process->thread() == thread()) ? Qt::DirectConnection
|
|
|
|
|
: Qt::BlockingQueuedConnection;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-08 09:29:28 +02:00
|
|
|
void QtcProcessPrivate::sendControlSignal(ControlSignal controlSignal)
|
2022-04-25 20:47:09 +02:00
|
|
|
{
|
|
|
|
|
QTC_ASSERT(QThread::currentThread() == thread(), return);
|
|
|
|
|
if (!m_process || (m_state == QProcess::NotRunning))
|
|
|
|
|
return;
|
|
|
|
|
|
2022-06-08 09:29:28 +02:00
|
|
|
QMetaObject::invokeMethod(m_process.get(), [this, controlSignal] {
|
2022-04-25 20:47:09 +02:00
|
|
|
m_process->sendControlSignal(controlSignal);
|
|
|
|
|
}, connectionType());
|
|
|
|
|
}
|
|
|
|
|
|
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;
|
2022-03-02 04:12:25 +01:00
|
|
|
m_result = ProcessResult::StartFailed;
|
2022-04-12 11:18:09 +02:00
|
|
|
|
2022-06-08 09:29:28 +02:00
|
|
|
m_killTimer.stop();
|
2022-04-12 11:18:09 +02:00
|
|
|
m_state = QProcess::NotRunning;
|
2022-04-11 16:37:59 +02:00
|
|
|
m_processId = 0;
|
|
|
|
|
m_applicationMainThreadId = 0;
|
2022-04-11 11:28:39 +02:00
|
|
|
m_resultData = {};
|
2021-05-06 11:15:28 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-02 04:12:25 +01:00
|
|
|
ProcessResult QtcProcessPrivate::interpretExitCode(int exitCode)
|
2021-05-06 15:54:02 +02:00
|
|
|
{
|
|
|
|
|
if (m_exitCodeInterpreter)
|
|
|
|
|
return m_exitCodeInterpreter(exitCode);
|
|
|
|
|
|
|
|
|
|
// default:
|
2022-03-02 04:12:25 +01:00
|
|
|
return exitCode ? ProcessResult::FinishedWithError : ProcessResult::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-03-29 18:01:08 +02:00
|
|
|
: QObject(parent),
|
|
|
|
|
d(new QtcProcessPrivate(this))
|
2016-06-08 13:30:30 +02:00
|
|
|
{
|
2022-07-04 11:09:11 +02:00
|
|
|
qRegisterMetaType<ProcessResultData>("ProcessResultData");
|
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()
|
|
|
|
|
{
|
2022-05-24 09:08:41 +02:00
|
|
|
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting QtcProcess instance directly from "
|
2022-05-19 22:14:58 +02:00
|
|
|
"one of its signal handlers will lead to crash!"));
|
2021-05-05 16:05:53 +02:00
|
|
|
delete d;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-10 19:25:03 +01:00
|
|
|
void QtcProcess::setProcessImpl(ProcessImpl processImpl)
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_processImpl = processImpl;
|
2022-02-10 19:25:03 +01:00
|
|
|
}
|
|
|
|
|
|
2021-08-07 11:41:23 +02:00
|
|
|
ProcessMode QtcProcess::processMode() const
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
return d->m_setup.m_processMode;
|
2022-02-10 19:25:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setTerminalMode(TerminalMode mode)
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_terminalMode = mode;
|
2021-08-07 11:41:23 +02:00
|
|
|
}
|
|
|
|
|
|
2022-02-18 00:56:14 +01:00
|
|
|
TerminalMode QtcProcess::terminalMode() const
|
2022-02-03 13:20:15 +01:00
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
return d->m_setup.m_terminalMode;
|
2022-02-10 19:25:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setProcessMode(ProcessMode processMode)
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
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)
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_environment = env;
|
2021-07-23 11:13:59 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
const Environment &QtcProcess::environment() const
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
return d->m_setup.m_environment;
|
2021-05-05 16:05:53 +02:00
|
|
|
}
|
|
|
|
|
|
2022-05-09 15:02:23 +02:00
|
|
|
void QtcProcess::setControlEnvironment(const Environment &environment)
|
2022-02-17 16:27:11 +01:00
|
|
|
{
|
2022-05-09 15:02:23 +02:00
|
|
|
d->m_setup.m_controlEnvironment = environment;
|
2022-02-17 16:27:11 +01:00
|
|
|
}
|
|
|
|
|
|
2022-05-09 15:02:23 +02:00
|
|
|
const Environment &QtcProcess::controlEnvironment() const
|
2022-02-17 16:27:11 +01:00
|
|
|
{
|
2022-05-09 15:02:23 +02:00
|
|
|
return d->m_setup.m_controlEnvironment;
|
2022-02-17 16:27:11 +01:00
|
|
|
}
|
|
|
|
|
|
2021-05-05 16:05:53 +02:00
|
|
|
void QtcProcess::setCommand(const CommandLine &cmdLine)
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
if (d->m_setup.m_workingDirectory.needsDevice() && cmdLine.executable().needsDevice()) {
|
|
|
|
|
QTC_CHECK(d->m_setup.m_workingDirectory.host() == cmdLine.executable().host());
|
2021-06-03 10:39:15 +02:00
|
|
|
}
|
2022-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_commandLine = cmdLine;
|
2021-05-05 16:05:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CommandLine &QtcProcess::commandLine() const
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
return d->m_setup.m_commandLine;
|
2021-05-05 16:05:53 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-03 10:39:15 +02:00
|
|
|
FilePath QtcProcess::workingDirectory() const
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
return d->m_setup.m_workingDirectory;
|
2021-06-03 10:39:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setWorkingDirectory(const FilePath &dir)
|
2021-05-14 15:21:54 +02:00
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
if (dir.needsDevice() && d->m_setup.m_commandLine.executable().needsDevice()) {
|
|
|
|
|
QTC_CHECK(dir.host() == d->m_setup.m_commandLine.executable().host());
|
2021-06-03 10:39:15 +02:00
|
|
|
}
|
2022-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_workingDirectory = dir;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
2014-05-16 15:57:02 +02:00
|
|
|
void QtcProcess::setUseCtrlCStub(bool enabled)
|
|
|
|
|
{
|
2022-03-14 17:02:11 +01:00
|
|
|
d->m_setup.m_useCtrlCStub = enabled;
|
2014-05-16 15:57:02 +02:00
|
|
|
}
|
|
|
|
|
|
2010-10-14 18:05:43 +02:00
|
|
|
void QtcProcess::start()
|
|
|
|
|
{
|
2022-04-12 11:18:09 +02:00
|
|
|
QTC_ASSERT(state() == QProcess::NotRunning, return);
|
2022-09-08 14:59:43 +02:00
|
|
|
QTC_ASSERT(!(d->m_process && d->m_guard.isLocked()),
|
|
|
|
|
qWarning("Restarting the QtcProcess directly from one of its signal handlers will "
|
|
|
|
|
"lead to crash! Consider calling close() prior to direct restart."));
|
2022-04-12 11:18:09 +02:00
|
|
|
d->clearForRun();
|
|
|
|
|
d->m_state = QProcess::Starting;
|
2022-02-21 12:49:14 +01:00
|
|
|
ProcessInterface *processImpl = nullptr;
|
2022-03-29 18:01:08 +02:00
|
|
|
if (d->m_setup.m_commandLine.executable().needsDevice()) {
|
2022-05-06 11:59:17 +02:00
|
|
|
QTC_ASSERT(s_deviceHooks.processImplHook, d->m_state = QProcess::NotRunning; return);
|
2022-04-28 16:15:46 +02:00
|
|
|
processImpl = s_deviceHooks.processImplHook(commandLine().executable());
|
2022-02-21 12:49:14 +01:00
|
|
|
} else {
|
|
|
|
|
processImpl = d->createProcessInterface();
|
2022-02-03 13:20:15 +01:00
|
|
|
}
|
2022-05-06 11:59:17 +02:00
|
|
|
QTC_ASSERT(processImpl, d->m_state = QProcess::NotRunning; return);
|
2022-04-12 00:23:20 +02:00
|
|
|
d->setProcessInterface(processImpl);
|
2022-04-12 08:50:46 +02:00
|
|
|
d->m_process->m_setup = d->m_setup;
|
|
|
|
|
d->m_process->m_setup.m_commandLine = d->fullCommandLine();
|
|
|
|
|
d->m_process->m_setup.m_environment = d->fullEnvironment();
|
2022-10-06 15:59:16 +02:00
|
|
|
d->emitGuardedSignal(&QtcProcess::starting);
|
2022-02-16 01:48:40 +01:00
|
|
|
d->m_process->start();
|
2010-10-14 18:05:43 +02:00
|
|
|
}
|
|
|
|
|
|
2011-08-03 12:04:46 +02:00
|
|
|
void QtcProcess::terminate()
|
|
|
|
|
{
|
2022-04-25 20:47:09 +02:00
|
|
|
d->sendControlSignal(ControlSignal::Terminate);
|
2022-04-11 19:45:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::kill()
|
|
|
|
|
{
|
2022-04-25 20:47:09 +02:00
|
|
|
d->sendControlSignal(ControlSignal::Kill);
|
2011-08-03 12:04:46 +02:00
|
|
|
}
|
|
|
|
|
|
2013-05-03 13:52:52 +02:00
|
|
|
void QtcProcess::interrupt()
|
|
|
|
|
{
|
2022-04-25 20:47:09 +02:00
|
|
|
d->sendControlSignal(ControlSignal::Interrupt);
|
2022-04-11 19:45:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::kickoffProcess()
|
|
|
|
|
{
|
2022-04-25 20:47:09 +02:00
|
|
|
d->sendControlSignal(ControlSignal::KickOff);
|
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-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_lowPriority = true;
|
2021-05-05 16:05:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setDisableUnixTerminal()
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02: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-03-29 18:01:08 +02: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-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_runAsRoot = on;
|
2022-02-01 14:51:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcess::isRunAsRoot() const
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
return d->m_setup.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-03-29 18:01:08 +02: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");
|
2022-03-29 18:01:08 +02:00
|
|
|
if (!d->m_setup.m_workingDirectory.isEmpty()) {
|
2021-09-09 23:03:39 +02:00
|
|
|
parts.append("-C");
|
2022-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_workingDirectory.path();
|
2021-09-09 23:03:39 +02:00
|
|
|
}
|
|
|
|
|
parts.append("-i");
|
2022-06-01 15:19:31 +02:00
|
|
|
if (d->m_setup.m_environment.isValid()) {
|
2022-03-29 18:01:08 +02:00
|
|
|
const QStringList envVars = d->m_setup.m_environment.toStringList();
|
2021-09-09 23:03:39 +02:00
|
|
|
std::transform(envVars.cbegin(), envVars.cend(),
|
|
|
|
|
std::back_inserter(parts), ProcessArgs::quoteArgUnix);
|
|
|
|
|
}
|
2022-03-29 18:01:08 +02:00
|
|
|
parts.append(d->m_setup.m_commandLine.executable().path());
|
|
|
|
|
parts.append(d->m_setup.m_commandLine.splitArguments());
|
2021-09-09 23:03:39 +02:00
|
|
|
return parts.join(" ");
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-11 17:38:16 +01:00
|
|
|
void QtcProcess::setExtraData(const QString &key, const QVariant &value)
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_extraData.insert(key, value);
|
2022-02-11 17:38:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariant QtcProcess::extraData(const QString &key) const
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
return d->m_setup.m_extraData.value(key);
|
2022-02-11 17:38:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::setExtraData(const QVariantHash &extraData)
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_extraData = extraData;
|
2022-02-11 17:38:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariantHash QtcProcess::extraData() const
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
return d->m_setup.m_extraData;
|
2022-02-11 17:38:16 +01:00
|
|
|
}
|
|
|
|
|
|
2022-06-03 14:28:30 +02:00
|
|
|
void QtcProcess::setReaperTimeout(int msecs)
|
|
|
|
|
{
|
|
|
|
|
d->m_setup.m_reaperTimeout = msecs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int QtcProcess::reaperTimeout() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_setup.m_reaperTimeout;
|
|
|
|
|
}
|
|
|
|
|
|
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-30 12:31:36 +02:00
|
|
|
static bool askToKill(const QString &command)
|
|
|
|
|
{
|
|
|
|
|
#ifdef QT_GUI_LIB
|
2022-10-10 15:59:26 +02:00
|
|
|
if (!isMainThread())
|
2021-04-30 12:31:36 +02:00
|
|
|
return true;
|
2022-02-16 17:51:35 +01:00
|
|
|
const QString title = QtcProcess::tr("Process Not Responding");
|
2021-04-30 12:31:36 +02:00
|
|
|
QString msg = command.isEmpty() ?
|
|
|
|
|
QtcProcess::tr("The process is not responding.") :
|
|
|
|
|
QtcProcess::tr("The process \"%1\" is not responding.").arg(command);
|
|
|
|
|
msg += ' ';
|
2022-02-16 17:51:35 +01:00
|
|
|
msg += QtcProcess::tr("Terminate the process?");
|
2021-04-30 12:31:36 +02:00
|
|
|
// 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.
|
|
|
|
|
|
2022-07-05 18:07:05 +02:00
|
|
|
bool QtcProcess::readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS)
|
2021-04-30 12:31:36 +02:00
|
|
|
{
|
|
|
|
|
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;
|
2022-07-05 18:01:44 +02:00
|
|
|
hasData = hang && !askToKill(d->m_setup.m_commandLine.executable().path());
|
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;
|
2022-06-16 15:13:07 +02:00
|
|
|
const auto newEnd = std::unique(res.begin(), res.end(), [](const QChar c1, const QChar c2) {
|
2021-04-30 17:21:43 +02:00
|
|
|
return c1 == '\r' && c2 == '\r'; // QTCREATORBUG-24556
|
|
|
|
|
});
|
|
|
|
|
res.chop(std::distance(newEnd, res.end()));
|
|
|
|
|
res.replace("\r\n", "\n");
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-02 04:12:25 +01:00
|
|
|
ProcessResult QtcProcess::result() const
|
2021-05-12 14:25:50 +02:00
|
|
|
{
|
2021-05-17 11:17:53 +02:00
|
|
|
return d->m_result;
|
2021-05-12 14:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
2022-04-08 17:24:59 +02:00
|
|
|
ProcessResultData QtcProcess::resultData() const
|
2021-05-12 14:25:50 +02:00
|
|
|
{
|
2022-04-11 11:28:39 +02:00
|
|
|
return d->m_resultData;
|
2022-04-08 17:24:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int QtcProcess::exitCode() const
|
|
|
|
|
{
|
|
|
|
|
return resultData().m_exitCode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcess::ExitStatus QtcProcess::exitStatus() const
|
|
|
|
|
{
|
|
|
|
|
return resultData().m_exitStatus;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcess::ProcessError QtcProcess::error() const
|
|
|
|
|
{
|
|
|
|
|
return resultData().m_error;
|
2021-05-12 14:25:50 +02:00
|
|
|
}
|
|
|
|
|
|
2022-04-08 17:24:59 +02:00
|
|
|
QString QtcProcess::errorString() const
|
|
|
|
|
{
|
|
|
|
|
return resultData().m_errorString;
|
|
|
|
|
}
|
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())
|
2022-10-06 17:06:36 +02:00
|
|
|
return {};
|
2021-05-05 15:04:30 +02:00
|
|
|
const int dotIndex = binary.lastIndexOf(QLatin1Char('.'));
|
|
|
|
|
if (dotIndex != -1 && dotIndex == binary.size() - 4)
|
2022-10-06 17:06:36 +02:00
|
|
|
return {};
|
2021-05-05 15:04:30 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
2022-10-06 17:06:36 +02:00
|
|
|
return {};
|
2021-05-05 15:04:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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())
|
2022-10-06 17:06:36 +02:00
|
|
|
return {};
|
2021-05-05 15:04:30 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2022-10-06 17:06:36 +02:00
|
|
|
return {};
|
2021-05-05 15:04:30 +02:00
|
|
|
}
|
|
|
|
|
|
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-04-11 16:37:59 +02:00
|
|
|
qint64 QtcProcess::applicationMainThreadId() const
|
2022-01-25 11:29:23 +01:00
|
|
|
{
|
2022-04-11 16:37:59 +02:00
|
|
|
return d->m_applicationMainThreadId;
|
2022-01-25 11:29:23 +01:00
|
|
|
}
|
|
|
|
|
|
2022-04-25 14:15:23 +02:00
|
|
|
QProcess::ProcessChannelMode QtcProcess::processChannelMode() const
|
|
|
|
|
{
|
|
|
|
|
return d->m_setup.m_processChannelMode;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-14 15:21:54 +02:00
|
|
|
void QtcProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode)
|
|
|
|
|
{
|
2022-04-19 02:28:30 -07:00
|
|
|
d->m_setup.m_processChannelMode = mode;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QProcess::ProcessState QtcProcess::state() const
|
|
|
|
|
{
|
2022-04-12 11:18:09 +02:00
|
|
|
return d->m_state;
|
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
|
|
|
qint64 QtcProcess::processId() const
|
|
|
|
|
{
|
2022-04-11 16:37:59 +02:00
|
|
|
return d->m_processId;
|
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);
|
2022-04-25 07:18:00 +02:00
|
|
|
if (d->m_state == QProcess::Running)
|
|
|
|
|
return true;
|
|
|
|
|
if (d->m_state == QProcess::NotRunning)
|
2022-04-12 11:18:09 +02:00
|
|
|
return false;
|
2022-06-08 14:24:40 +02:00
|
|
|
return s_waitForStarted.measureAndRun(&QtcProcessPrivate::waitForSignal, d,
|
|
|
|
|
ProcessSignalType::Started, 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);
|
2022-04-12 11:18:09 +02:00
|
|
|
if (d->m_state == QProcess::NotRunning)
|
|
|
|
|
return false;
|
2022-06-08 14:24:40 +02:00
|
|
|
return d->waitForSignal(ProcessSignalType::ReadyRead, msecs);
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool QtcProcess::waitForFinished(int msecs)
|
|
|
|
|
{
|
2022-02-10 18:09:42 +01:00
|
|
|
QTC_ASSERT(d->m_process, return false);
|
2022-04-12 11:18:09 +02:00
|
|
|
if (d->m_state == QProcess::NotRunning)
|
|
|
|
|
return false;
|
2022-06-08 14:24:40 +02:00
|
|
|
return d->waitForSignal(ProcessSignalType::Done, msecs);
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray QtcProcess::readAllStandardOutput()
|
|
|
|
|
{
|
2022-05-06 11:06:13 +02:00
|
|
|
return d->m_stdOut.readAllData();
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QByteArray QtcProcess::readAllStandardError()
|
|
|
|
|
{
|
2022-05-06 11:06:13 +02:00
|
|
|
return d->m_stdErr.readAllData();
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
2022-04-27 14:19:49 +02:00
|
|
|
qint64 QtcProcess::write(const QString &input)
|
2021-05-14 15:21:54 +02:00
|
|
|
{
|
2022-04-27 14:19:49 +02:00
|
|
|
// Non-windows is assumed to be UTF-8
|
|
|
|
|
if (commandLine().executable().osType() != OsTypeWindows)
|
|
|
|
|
return writeRaw(input.toUtf8());
|
|
|
|
|
|
|
|
|
|
if (HostOsInfo::hostOs() == OsTypeWindows)
|
|
|
|
|
return writeRaw(input.toLocal8Bit());
|
|
|
|
|
|
|
|
|
|
// "remote" Windows target on non-Windows host is unlikely,
|
|
|
|
|
// but the true encoding is not accessible. Use UTF8 as best guess.
|
|
|
|
|
QTC_CHECK(false);
|
|
|
|
|
return writeRaw(input.toUtf8());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
qint64 QtcProcess::writeRaw(const QByteArray &input)
|
|
|
|
|
{
|
2022-04-25 20:47:09 +02:00
|
|
|
QTC_ASSERT(processMode() == ProcessMode::Writer, return -1);
|
|
|
|
|
QTC_ASSERT(d->m_process, return -1);
|
|
|
|
|
QTC_ASSERT(state() == QProcess::Running, return -1);
|
|
|
|
|
QTC_ASSERT(QThread::currentThread() == thread(), return -1);
|
|
|
|
|
qint64 result = -1;
|
|
|
|
|
QMetaObject::invokeMethod(d->m_process.get(), [this, input] {
|
|
|
|
|
d->m_process->write(input);
|
|
|
|
|
}, d->connectionType(), &result);
|
|
|
|
|
return result;
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcess::close()
|
|
|
|
|
{
|
2022-04-25 20:47:09 +02:00
|
|
|
QTC_ASSERT(QThread::currentThread() == thread(), return);
|
2022-04-11 18:21:32 +02:00
|
|
|
if (d->m_process) {
|
2022-04-25 20:47:09 +02:00
|
|
|
// Note: the m_process may be inside ProcessInterfaceHandler's thread.
|
|
|
|
|
QTC_ASSERT(d->m_process->thread() == thread(), return);
|
2022-04-11 18:21:32 +02:00
|
|
|
d->m_process->disconnect();
|
|
|
|
|
d->m_process.release()->deleteLater();
|
|
|
|
|
}
|
2022-06-08 14:24:40 +02:00
|
|
|
if (d->m_blockingInterface) {
|
|
|
|
|
d->m_blockingInterface->disconnect();
|
|
|
|
|
d->m_blockingInterface.release()->deleteLater();
|
2022-04-25 20:12:43 +02:00
|
|
|
}
|
2022-04-11 18:21:32 +02:00
|
|
|
d->clearForRun();
|
2021-05-14 15:21:54 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-03 14:28:30 +02:00
|
|
|
/*
|
|
|
|
|
Calls terminate() directly and after a delay of reaperTimeout() it calls kill()
|
|
|
|
|
if the process is still running.
|
|
|
|
|
*/
|
|
|
|
|
void QtcProcess::stop()
|
2022-06-02 18:10:06 +02:00
|
|
|
{
|
|
|
|
|
if (state() == QProcess::NotRunning)
|
|
|
|
|
return;
|
|
|
|
|
|
2022-06-08 09:29:28 +02:00
|
|
|
d->sendControlSignal(ControlSignal::Terminate);
|
|
|
|
|
d->m_killTimer.start(d->m_process->m_setup.m_reaperTimeout);
|
2022-06-02 18:10:06 +02:00
|
|
|
}
|
|
|
|
|
|
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.
|
|
|
|
|
*/
|
|
|
|
|
|
2022-04-05 13:47:11 +02:00
|
|
|
QString QtcProcess::exitMessage() const
|
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()) {
|
2022-03-02 04:12:25 +01:00
|
|
|
case ProcessResult::FinishedWithSuccess:
|
2021-05-14 13:12:46 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" finished successfully.").arg(fullCmd);
|
2022-03-02 04:12:25 +01:00
|
|
|
case ProcessResult::FinishedWithError:
|
2021-05-14 13:12:46 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" terminated with exit code %2.")
|
|
|
|
|
.arg(fullCmd).arg(exitCode());
|
2022-03-02 04:12:25 +01:00
|
|
|
case ProcessResult::TerminatedAbnormally:
|
2021-05-14 13:12:46 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" terminated abnormally.").arg(fullCmd);
|
2022-03-02 04:12:25 +01:00
|
|
|
case ProcessResult::StartFailed:
|
2021-05-14 13:12:46 +02:00
|
|
|
return QtcProcess::tr("The command \"%1\" could not be started.").arg(fullCmd);
|
2022-03-02 04:12:25 +01:00
|
|
|
case ProcessResult::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
|
|
|
}
|
2022-10-06 17:06:36 +02:00
|
|
|
return {};
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
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);
|
2022-06-17 14:17:14 +02:00
|
|
|
const QString out = cleanedStdOut();
|
|
|
|
|
const QString err = cleanedStdErr();
|
2021-05-05 18:21:22 +02:00
|
|
|
|
|
|
|
|
if (!out.isEmpty() && !err.isEmpty()) {
|
|
|
|
|
QString result = out;
|
|
|
|
|
if (!result.endsWith('\n'))
|
|
|
|
|
result += '\n';
|
|
|
|
|
result += err;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
return !out.isEmpty() ? out : err;
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-16 15:13:07 +02:00
|
|
|
QByteArray QtcProcess::rawStdOut() const
|
|
|
|
|
{
|
|
|
|
|
QTC_CHECK(d->m_stdOut.keepRawData);
|
|
|
|
|
return d->m_stdOut.rawData;
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
2022-06-16 15:13:07 +02:00
|
|
|
return 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
|
|
|
{
|
2022-09-19 08:02:23 +02:00
|
|
|
QTC_CHECK(d->m_stdErr.keepRawData);
|
2022-06-16 15:13:07 +02:00
|
|
|
return d->m_codec->toUnicode(d->m_stdErr.rawData);
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2022-06-16 15:13:07 +02:00
|
|
|
QString QtcProcess::cleanedStdOut() const
|
2021-05-12 14:25:50 +02:00
|
|
|
{
|
2022-06-16 15:13:07 +02:00
|
|
|
return normalizeNewlines(stdOut());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString QtcProcess::cleanedStdErr() const
|
|
|
|
|
{
|
|
|
|
|
return normalizeNewlines(stdErr());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QStringList splitLines(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
QStringList result = text.split('\n');
|
|
|
|
|
for (QString &line : result) {
|
|
|
|
|
if (line.endsWith('\r'))
|
|
|
|
|
line.chop(1);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QStringList QtcProcess::stdOutLines() const
|
|
|
|
|
{
|
2022-06-17 14:17:14 +02:00
|
|
|
return splitLines(cleanedStdOut());
|
2022-06-16 15:13:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QStringList QtcProcess::stdErrLines() const
|
|
|
|
|
{
|
2022-06-17 14:17:14 +02:00
|
|
|
return splitLines(cleanedStdErr());
|
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="
|
2022-03-02 04:12:25 +01:00
|
|
|
<< int(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)
|
|
|
|
|
{
|
2022-03-29 18:01:08 +02:00
|
|
|
d->m_setup.m_writeData = writeData;
|
2021-05-06 17:54:28 +02:00
|
|
|
}
|
|
|
|
|
|
2022-03-02 04:12:25 +01:00
|
|
|
void QtcProcess::runBlocking(EventLoopMode eventLoopMode)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2022-10-06 16:51:07 +02:00
|
|
|
// Attach a dynamic property with info about blocking type
|
|
|
|
|
d->storeEventLoopDebugInfo(int(eventLoopMode));
|
2022-02-16 23:55:44 +01:00
|
|
|
|
2022-02-10 18:09:42 +01:00
|
|
|
QtcProcess::start();
|
2022-10-06 16:51:07 +02:00
|
|
|
|
|
|
|
|
// Remove the dynamic property so that it's not reused in subseqent start()
|
|
|
|
|
d->storeEventLoopDebugInfo({});
|
|
|
|
|
|
2022-03-02 04:12:25 +01:00
|
|
|
if (eventLoopMode == EventLoopMode::On) {
|
2022-04-11 11:28:39 +02:00
|
|
|
// Start failure is triggered immediately if the executable cannot be found in the path.
|
|
|
|
|
// In this case the process is left in NotRunning state.
|
|
|
|
|
// Do not start the event loop in that case.
|
|
|
|
|
if (state() == QProcess::Starting) {
|
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
|
2022-10-10 15:59:26 +02:00
|
|
|
if (isMainThread())
|
2021-05-20 13:14:15 +02:00
|
|
|
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;
|
|
|
|
|
timer.stop();
|
2021-05-05 18:21:22 +02:00
|
|
|
#ifdef QT_GUI_LIB
|
2022-10-10 15:59:26 +02:00
|
|
|
if (isMainThread())
|
2021-05-20 13:14:15 +02:00
|
|
|
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)) {
|
2022-03-02 04:12:25 +01:00
|
|
|
d->m_result = ProcessResult::StartFailed;
|
2021-05-20 13:14:15 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!waitForFinished(d->m_maxHangTimerCount * 1000)) {
|
2022-03-02 04:12:25 +01:00
|
|
|
d->m_result = ProcessResult::Hang;
|
2021-05-20 13:14:15 +02:00
|
|
|
terminate();
|
|
|
|
|
if (!waitForFinished(1000)) {
|
|
|
|
|
kill();
|
|
|
|
|
waitForFinished(1000);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-07 12:05:37 +02:00
|
|
|
void QtcProcess::setStdOutCallback(const TextChannelCallback &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
|
|
|
}
|
|
|
|
|
|
2022-10-07 12:05:37 +02:00
|
|
|
void QtcProcess::setStdOutLineCallback(const TextChannelCallback &callback)
|
2021-06-04 11:40:35 +02:00
|
|
|
{
|
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
|
|
|
}
|
|
|
|
|
|
2022-10-07 12:05:37 +02:00
|
|
|
void QtcProcess::setStdErrCallback(const TextChannelCallback &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
|
|
|
}
|
|
|
|
|
|
2022-10-07 12:05:37 +02:00
|
|
|
void QtcProcess::setStdErrLineCallback(const TextChannelCallback &callback)
|
2021-06-04 11:40:35 +02:00
|
|
|
{
|
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
|
|
|
}
|
|
|
|
|
|
2022-10-07 12:05:37 +02:00
|
|
|
void QtcProcess::setTextChannelMode(Channel channel, TextChannelMode mode)
|
|
|
|
|
{
|
|
|
|
|
const TextChannelCallback outputCb = [this](const QString &text) {
|
|
|
|
|
GuardLocker locker(d->m_guard);
|
|
|
|
|
emit textOnStandardOutput(text);
|
|
|
|
|
};
|
|
|
|
|
const TextChannelCallback errorCb = [this](const QString &text) {
|
|
|
|
|
GuardLocker locker(d->m_guard);
|
|
|
|
|
emit textOnStandardError(text);
|
|
|
|
|
};
|
|
|
|
|
const TextChannelCallback callback = (channel == Channel::Output) ? outputCb : errorCb;
|
|
|
|
|
ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr;
|
|
|
|
|
QTC_ASSERT(buffer->m_textChannelMode == TextChannelMode::Off, qWarning()
|
|
|
|
|
<< "QtcProcess::setTextChannelMode(): Changing text channel mode for"
|
|
|
|
|
<< (channel == Channel::Output ? "Output": "Error")
|
|
|
|
|
<< "channel while it was previously set for this channel.");
|
|
|
|
|
buffer->m_textChannelMode = mode;
|
|
|
|
|
switch (mode) {
|
|
|
|
|
case TextChannelMode::Off:
|
|
|
|
|
buffer->outputCallback = {};
|
|
|
|
|
buffer->emitSingleLines = true;
|
|
|
|
|
buffer->keepRawData = true;
|
|
|
|
|
break;
|
|
|
|
|
case TextChannelMode::SingleLine:
|
|
|
|
|
buffer->outputCallback = callback;
|
|
|
|
|
buffer->emitSingleLines = true;
|
|
|
|
|
buffer->keepRawData = false;
|
|
|
|
|
break;
|
|
|
|
|
case TextChannelMode::MultiLine:
|
|
|
|
|
buffer->outputCallback = callback;
|
|
|
|
|
buffer->emitSingleLines = false;
|
|
|
|
|
buffer->keepRawData = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TextChannelMode QtcProcess::textChannelMode(Channel channel) const
|
|
|
|
|
{
|
|
|
|
|
ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr;
|
|
|
|
|
return buffer->m_textChannelMode;
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2022-03-29 18:01:08 +02:00
|
|
|
|| askToKill(m_setup.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) {
|
2022-06-16 10:56:26 +02:00
|
|
|
q->stop();
|
|
|
|
|
q->waitForFinished();
|
2022-03-02 04:12:25 +01:00
|
|
|
m_result = ProcessResult::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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-11 16:37:59 +02:00
|
|
|
void QtcProcessPrivate::handleStarted(qint64 processId, qint64 applicationMainThreadId)
|
|
|
|
|
{
|
2022-04-12 11:18:09 +02:00
|
|
|
QTC_CHECK(m_state == QProcess::Starting);
|
|
|
|
|
m_state = QProcess::Running;
|
|
|
|
|
|
2022-04-11 16:37:59 +02:00
|
|
|
m_processId = processId;
|
|
|
|
|
m_applicationMainThreadId = applicationMainThreadId;
|
2022-10-06 15:59:16 +02:00
|
|
|
emitGuardedSignal(&QtcProcess::started);
|
2022-04-11 16:37:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-04-11 15:54:29 +02:00
|
|
|
void QtcProcessPrivate::handleReadyRead(const QByteArray &outputData, const QByteArray &errorData)
|
|
|
|
|
{
|
2022-04-12 11:18:09 +02:00
|
|
|
QTC_CHECK(m_state == QProcess::Running);
|
|
|
|
|
|
2022-04-11 15:54:29 +02:00
|
|
|
// TODO: check why we need this timer?
|
|
|
|
|
m_hangTimerCount = 0;
|
|
|
|
|
// TODO: store a copy of m_processChannelMode on start()? Currently we assert that state
|
|
|
|
|
// is NotRunning when setting the process channel mode.
|
2022-06-23 11:22:30 +02:00
|
|
|
|
|
|
|
|
if (!outputData.isEmpty()) {
|
|
|
|
|
if (m_process->m_setup.m_processChannelMode == QProcess::ForwardedOutputChannel
|
|
|
|
|
|| m_process->m_setup.m_processChannelMode == QProcess::ForwardedChannels) {
|
|
|
|
|
std::cout << outputData.constData() << std::flush;
|
|
|
|
|
} else {
|
|
|
|
|
m_stdOut.append(outputData);
|
2022-10-06 15:59:16 +02:00
|
|
|
emitGuardedSignal(&QtcProcess::readyReadStandardOutput);
|
2022-06-23 11:22:30 +02:00
|
|
|
}
|
2022-04-19 02:28:30 -07:00
|
|
|
}
|
2022-06-23 11:22:30 +02:00
|
|
|
if (!errorData.isEmpty()) {
|
|
|
|
|
if (m_process->m_setup.m_processChannelMode == QProcess::ForwardedErrorChannel
|
|
|
|
|
|| m_process->m_setup.m_processChannelMode == QProcess::ForwardedChannels) {
|
|
|
|
|
std::cerr << errorData.constData() << std::flush;
|
|
|
|
|
} else {
|
|
|
|
|
m_stdErr.append(errorData);
|
2022-10-06 15:59:16 +02:00
|
|
|
emitGuardedSignal(&QtcProcess::readyReadStandardError);
|
2022-06-23 11:22:30 +02:00
|
|
|
}
|
2022-04-11 15:54:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-11 11:28:39 +02:00
|
|
|
void QtcProcessPrivate::handleDone(const ProcessResultData &data)
|
2021-05-05 18:21:22 +02:00
|
|
|
{
|
2022-06-08 14:24:40 +02:00
|
|
|
m_killTimer.stop();
|
2022-04-11 11:28:39 +02:00
|
|
|
m_resultData = data;
|
|
|
|
|
|
2022-04-12 11:18:09 +02:00
|
|
|
switch (m_state) {
|
|
|
|
|
case QProcess::NotRunning:
|
|
|
|
|
QTC_CHECK(false); // Can't happen
|
|
|
|
|
break;
|
|
|
|
|
case QProcess::Starting:
|
|
|
|
|
QTC_CHECK(m_resultData.m_error == QProcess::FailedToStart);
|
|
|
|
|
break;
|
|
|
|
|
case QProcess::Running:
|
|
|
|
|
QTC_CHECK(m_resultData.m_error != QProcess::FailedToStart);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
m_state = QProcess::NotRunning;
|
|
|
|
|
|
2022-04-11 11:28:39 +02:00
|
|
|
// This code (255) is being returned by QProcess when FailedToStart error occurred
|
|
|
|
|
if (m_resultData.m_error == QProcess::FailedToStart)
|
|
|
|
|
m_resultData.m_exitCode = 0xFF;
|
|
|
|
|
|
|
|
|
|
// HACK: See QIODevice::errorString() implementation.
|
|
|
|
|
if (m_resultData.m_error == QProcess::UnknownError)
|
|
|
|
|
m_resultData.m_errorString.clear();
|
2022-06-22 15:42:24 +02:00
|
|
|
else if (m_result != ProcessResult::Hang)
|
|
|
|
|
m_result = ProcessResult::StartFailed;
|
2022-03-04 17:10:39 +01:00
|
|
|
|
2021-05-05 18:21:22 +02:00
|
|
|
if (debug)
|
2022-04-11 11:28:39 +02:00
|
|
|
qDebug() << Q_FUNC_INFO << m_resultData.m_exitCode << m_resultData.m_exitStatus;
|
2021-05-06 11:15:28 +02:00
|
|
|
m_hangTimerCount = 0;
|
2021-05-05 18:21:22 +02:00
|
|
|
|
2022-04-14 08:50:16 +02:00
|
|
|
if (m_resultData.m_error != QProcess::FailedToStart) {
|
|
|
|
|
switch (m_resultData.m_exitStatus) {
|
|
|
|
|
case QProcess::NormalExit:
|
|
|
|
|
m_result = interpretExitCode(m_resultData.m_exitCode);
|
|
|
|
|
break;
|
|
|
|
|
case QProcess::CrashExit:
|
|
|
|
|
// Was hang detected before and killed?
|
|
|
|
|
if (m_result != ProcessResult::Hang)
|
|
|
|
|
m_result = ProcessResult::TerminatedAbnormally;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
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();
|
2022-04-11 11:28:39 +02:00
|
|
|
|
2022-10-06 15:59:16 +02:00
|
|
|
emitGuardedSignal(&QtcProcess::done);
|
2022-04-11 16:37:59 +02:00
|
|
|
m_processId = 0;
|
|
|
|
|
m_applicationMainThreadId = 0;
|
2021-05-05 18:21:22 +02:00
|
|
|
}
|
|
|
|
|
|
2022-10-06 16:51:07 +02:00
|
|
|
static QString blockingMessage(const QVariant &variant)
|
|
|
|
|
{
|
|
|
|
|
if (!variant.isValid())
|
|
|
|
|
return "non blocking";
|
|
|
|
|
if (variant.toInt() == int(EventLoopMode::On))
|
|
|
|
|
return "blocking with event loop";
|
|
|
|
|
return "blocking without event loop";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcessPrivate::setupDebugLog()
|
|
|
|
|
{
|
|
|
|
|
if (!processLog().isDebugEnabled())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto now = [] {
|
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
connect(q, &QtcProcess::starting, this, [=] {
|
|
|
|
|
const quint64 msNow = now();
|
|
|
|
|
setProperty(QTC_PROCESS_STARTTIME, msNow);
|
|
|
|
|
|
|
|
|
|
static std::atomic_int startCounter = 0;
|
|
|
|
|
const int currentNumber = startCounter.fetch_add(1);
|
|
|
|
|
qCDebug(processLog).nospace().noquote()
|
|
|
|
|
<< "Process " << currentNumber << " starting ("
|
|
|
|
|
<< qPrintable(blockingMessage(property(QTC_PROCESS_BLOCKING_TYPE)))
|
|
|
|
|
<< "): " << m_setup.m_commandLine.toUserOutput();
|
|
|
|
|
setProperty(QTC_PROCESS_NUMBER, currentNumber);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
connect(q, &QtcProcess::done, this, [=] {
|
|
|
|
|
if (!m_process.get())
|
|
|
|
|
return;
|
|
|
|
|
const QVariant n = property(QTC_PROCESS_NUMBER);
|
|
|
|
|
if (!n.isValid())
|
|
|
|
|
return;
|
|
|
|
|
const quint64 msNow = now();
|
|
|
|
|
const quint64 msStarted = property(QTC_PROCESS_STARTTIME).toULongLong();
|
|
|
|
|
const quint64 msElapsed = msNow - msStarted;
|
|
|
|
|
|
|
|
|
|
const int number = n.toInt();
|
|
|
|
|
const QString stdOut = q->cleanedStdOut();
|
|
|
|
|
const QString stdErr = q->cleanedStdErr();
|
|
|
|
|
qCDebug(processLog).nospace()
|
|
|
|
|
<< "Process " << number << " finished: result=" << int(m_result)
|
|
|
|
|
<< ", ex=" << m_resultData.m_exitCode
|
|
|
|
|
<< ", " << stdOut.size() << " bytes stdout: " << stdOut.left(20)
|
|
|
|
|
<< ", " << stdErr.size() << " bytes stderr: " << stdErr.left(1000)
|
|
|
|
|
<< ", " << msElapsed << " ms elapsed";
|
|
|
|
|
if (processStdoutLog().isDebugEnabled() && !stdOut.isEmpty())
|
|
|
|
|
qCDebug(processStdoutLog).nospace() << "Process " << number << " sdout: " << stdOut;
|
|
|
|
|
if (processStderrLog().isDebugEnabled() && !stdErr.isEmpty())
|
|
|
|
|
qCDebug(processStderrLog).nospace() << "Process " << number << " stderr: " << stdErr;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void QtcProcessPrivate::storeEventLoopDebugInfo(const QVariant &value)
|
|
|
|
|
{
|
|
|
|
|
if (processLog().isDebugEnabled())
|
|
|
|
|
setProperty(QTC_PROCESS_BLOCKING_TYPE, value);
|
|
|
|
|
}
|
|
|
|
|
|
2013-07-12 09:00:12 +02:00
|
|
|
} // namespace Utils
|
2021-07-09 09:09:05 +02:00
|
|
|
|
|
|
|
|
#include "qtcprocess.moc"
|