Process: Get rid of process launcher implementation

Change-Id: Ief42af62f07fa19667a84b92a263c0330acc2262
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2024-11-01 11:56:56 +01:00
parent f31275f12b
commit a27a4fc473
35 changed files with 13 additions and 2274 deletions

View File

@@ -8,7 +8,7 @@
"hidden" : true,
"cacheVariables": {
"BUILD_PLUGINS": "Core;Designer;DiffEditor;TextEditor;ProjectExplorer;CppEditor;CodePaster;Docker;Git;Help;QmakeProjectManager;CMakeProjectManager;ClangCodeModel;ClangTools;ClangFormat;Debugger;QtSupport;ResourceEditor;VcsBase;Welcome;LanguageClient;RemoteLinux",
"BUILD_EXECUTABLES": "QtCreator;qtcreator_ctrlc_stub;qtcreator_process_stub;win64interrupt;qtcreator_processlauncher",
"BUILD_EXECUTABLES": "QtCreator;qtcreator_ctrlc_stub;qtcreator_process_stub;win64interrupt",
"WITH_QMLDESIGNER": "OFF"
}
}

View File

@@ -90,9 +90,6 @@ add_qtc_library(Utils
infolabel.cpp infolabel.h
itemviews.cpp itemviews.h
jsontreeitem.cpp jsontreeitem.h
launcherinterface.cpp launcherinterface.h
launcherpackets.cpp launcherpackets.h
launchersocket.cpp launchersocket.h
layoutbuilder.cpp layoutbuilder.h
link.cpp link.h
listmodel.h

View File

@@ -1,207 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "launcherinterface.h"
#include "filepath.h"
#include "launchersocket.h"
#include "qtcassert.h"
#include "temporarydirectory.h"
#include "utilstr.h"
#include <QDebug>
#include <QDir>
#include <QLocalServer>
#include <QProcess>
#ifdef Q_OS_UNIX
#include <unistd.h>
#endif
namespace Utils {
namespace Internal {
static QString launcherSocketName()
{
return TemporaryDirectory::masterDirectoryPath()
+ QStringLiteral("/launcher-%1").arg(QString::number(qApp->applicationPid()));
}
class LauncherInterfacePrivate : public QObject
{
Q_OBJECT
public:
LauncherInterfacePrivate();
~LauncherInterfacePrivate() override;
void doStart();
void doStop();
void handleNewConnection();
void handleProcessError();
void handleProcessFinished();
void handleProcessStderr();
Internal::LauncherSocket *socket() const { return m_socket; }
void setPathToLauncher(const QString &path) { if (!path.isEmpty()) m_pathToLauncher = path; }
QString launcherFilePath() const { return m_pathToLauncher + QLatin1String("/qtcreator_processlauncher"); }
signals:
void errorOccurred(const QString &error);
private:
QLocalServer * const m_server;
Internal::LauncherSocket *const m_socket;
QProcess *m_process = nullptr;
QString m_pathToLauncher;
};
LauncherInterfacePrivate::LauncherInterfacePrivate()
: m_server(new QLocalServer(this)), m_socket(new LauncherSocket(this))
{
m_pathToLauncher = qApp->applicationDirPath() + '/' + QLatin1String(RELATIVE_LIBEXEC_PATH);
QObject::connect(m_server, &QLocalServer::newConnection,
this, &LauncherInterfacePrivate::handleNewConnection);
}
LauncherInterfacePrivate::~LauncherInterfacePrivate()
{
m_server->disconnect();
}
void LauncherInterfacePrivate::doStart()
{
const QString &socketName = launcherSocketName();
QLocalServer::removeServer(socketName);
if (!m_server->listen(socketName)) {
emit errorOccurred(m_server->errorString());
return;
}
m_process = new QProcess(this);
connect(m_process, &QProcess::errorOccurred, this, &LauncherInterfacePrivate::handleProcessError);
connect(m_process, &QProcess::finished,
this, &LauncherInterfacePrivate::handleProcessFinished);
connect(m_process, &QProcess::readyReadStandardError,
this, &LauncherInterfacePrivate::handleProcessStderr);
#ifdef Q_OS_UNIX
# if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
m_process->setUnixProcessParameters(QProcess::UnixProcessFlag::CreateNewSession);
# else
m_process->setChildProcessModifier([] {
setpgid(0, 0);
});
# endif
#endif
m_process->start(launcherFilePath(), QStringList(m_server->fullServerName()));
}
void LauncherInterfacePrivate::doStop()
{
m_server->close();
QTC_ASSERT(m_process, return);
m_socket->shutdown();
m_process->waitForFinished(-1); // Let the process interface finish so that it finishes
// reaping any possible processes it has started.
delete m_process;
m_process = nullptr;
}
void LauncherInterfacePrivate::handleNewConnection()
{
QLocalSocket * const socket = m_server->nextPendingConnection();
if (!socket)
return;
m_server->close();
m_socket->setSocket(socket);
}
void LauncherInterfacePrivate::handleProcessError()
{
if (m_process->error() == QProcess::FailedToStart) {
const QString launcherPathForUser
= QDir::toNativeSeparators(QDir::cleanPath(m_process->program()));
emit errorOccurred(Tr::tr("Failed to start process launcher at \"%1\": %2")
.arg(launcherPathForUser, m_process->errorString()));
}
}
void LauncherInterfacePrivate::handleProcessFinished()
{
emit errorOccurred(Tr::tr("Process launcher closed unexpectedly: %1")
.arg(m_process->errorString()));
}
void LauncherInterfacePrivate::handleProcessStderr()
{
qDebug() << "[launcher]" << m_process->readAllStandardError();
}
} // namespace Internal
using namespace Utils::Internal;
static QMutex s_instanceMutex;
static QString s_pathToLauncher;
static std::atomic_bool s_started = false;
LauncherInterface::LauncherInterface()
: m_private(new LauncherInterfacePrivate())
{
m_private->moveToThread(&m_thread);
QObject::connect(&m_thread, &QThread::finished, m_private, &QObject::deleteLater);
m_thread.start();
m_thread.moveToThread(qApp->thread());
m_private->setPathToLauncher(s_pathToLauncher);
const FilePath launcherFilePath = FilePath::fromString(m_private->launcherFilePath())
.cleanPath().withExecutableSuffix();
auto launcherIsNotExecutable = [&launcherFilePath] {
qWarning() << "The Creator's process launcher"
<< launcherFilePath << "is not executable.";
};
QTC_ASSERT(launcherFilePath.isExecutableFile(), launcherIsNotExecutable(); return);
s_started = true;
// Call in launcher's thread.
QMetaObject::invokeMethod(m_private, &LauncherInterfacePrivate::doStart);
}
LauncherInterface::~LauncherInterface()
{
QMutexLocker locker(&s_instanceMutex);
LauncherInterfacePrivate *p = instance()->m_private;
// Call in launcher's thread.
QMetaObject::invokeMethod(p, &LauncherInterfacePrivate::doStop, Qt::BlockingQueuedConnection);
m_thread.quit();
m_thread.wait();
}
void LauncherInterface::setPathToLauncher(const QString &pathToLauncher)
{
s_pathToLauncher = pathToLauncher;
}
bool LauncherInterface::isStarted()
{
return s_started;
}
void LauncherInterface::sendData(const QByteArray &data)
{
QMutexLocker locker(&s_instanceMutex);
instance()->m_private->socket()->sendData(data);
}
Internal::CallerHandle *LauncherInterface::registerHandle(QObject *parent, quintptr token)
{
QMutexLocker locker(&s_instanceMutex);
return instance()->m_private->socket()->registerHandle(parent, token);
}
void LauncherInterface::unregisterHandle(quintptr token)
{
QMutexLocker locker(&s_instanceMutex);
instance()->m_private->socket()->unregisterHandle(token);
}
} // namespace Utils
#include "launcherinterface.moc"

View File

@@ -1,45 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "utils_global.h"
#include "processreaper.h"
#include "singleton.h"
#include <QThread>
namespace Utils {
namespace Internal {
class CallerHandle;
class LauncherHandle;
class LauncherInterfacePrivate;
class ProcessLauncherImpl;
}
class QTCREATOR_UTILS_EXPORT LauncherInterface final
: public SingletonWithOptionalDependencies<LauncherInterface, ProcessReaper>
{
public:
static void setPathToLauncher(const QString &pathToLauncher);
private:
friend class Internal::CallerHandle;
friend class Internal::LauncherHandle;
friend class Internal::ProcessLauncherImpl;
static bool isStarted();
static void sendData(const QByteArray &data);
static Internal::CallerHandle *registerHandle(QObject *parent, quintptr token);
static void unregisterHandle(quintptr token);
LauncherInterface();
~LauncherInterface();
QThread m_thread;
Internal::LauncherInterfacePrivate *m_private;
friend class SingletonWithOptionalDependencies<LauncherInterface, ProcessReaper>;
};
} // namespace Utils

View File

@@ -1,195 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "launcherpackets.h"
#include <QByteArray>
namespace Utils {
namespace Internal {
LauncherPacket::~LauncherPacket() = default;
QByteArray LauncherPacket::serialize() const
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << static_cast<int>(0) << static_cast<quint8>(type) << token;
doSerialize(stream);
stream.device()->reset();
stream << static_cast<int>(data.size() - sizeof(int));
return data;
}
void LauncherPacket::deserialize(const QByteArray &data)
{
QDataStream stream(data);
doDeserialize(stream);
}
StartProcessPacket::StartProcessPacket(quintptr token)
: LauncherPacket(LauncherPacketType::StartProcess, token)
{
}
void StartProcessPacket::doSerialize(QDataStream &stream) const
{
stream << command
<< arguments
<< workingDir
<< env
<< int(processMode)
<< writeData
<< int(processChannelMode)
<< standardInputFile
<< belowNormalPriority
<< nativeArguments
<< lowPriority
<< unixTerminalDisabled
<< useCtrlCStub
<< reaperTimeout
<< createConsoleOnWindows
<< forceDefaultErrorMode;
}
void StartProcessPacket::doDeserialize(QDataStream &stream)
{
int processModeInt;
int processChannelModeInt;
stream >> command
>> arguments
>> workingDir
>> env
>> processModeInt
>> writeData
>> processChannelModeInt
>> standardInputFile
>> belowNormalPriority
>> nativeArguments
>> lowPriority
>> unixTerminalDisabled
>> useCtrlCStub
>> reaperTimeout
>> createConsoleOnWindows
>> forceDefaultErrorMode;
processMode = Utils::ProcessMode(processModeInt);
processChannelMode = QProcess::ProcessChannelMode(processChannelModeInt);
}
ProcessStartedPacket::ProcessStartedPacket(quintptr token)
: LauncherPacket(LauncherPacketType::ProcessStarted, token)
{
}
void ProcessStartedPacket::doSerialize(QDataStream &stream) const
{
stream << processId;
}
void ProcessStartedPacket::doDeserialize(QDataStream &stream)
{
stream >> processId;
}
ControlProcessPacket::ControlProcessPacket(quintptr token)
: LauncherPacket(LauncherPacketType::ControlProcess, token)
{
}
void ControlProcessPacket::doSerialize(QDataStream &stream) const
{
stream << int(signalType);
}
void ControlProcessPacket::doDeserialize(QDataStream &stream)
{
int signalTypeInt;
stream >> signalTypeInt;
signalType = SignalType(signalTypeInt);
}
void WritePacket::doSerialize(QDataStream &stream) const
{
stream << inputData;
}
void WritePacket::doDeserialize(QDataStream &stream)
{
stream >> inputData;
}
void ReadyReadPacket::doSerialize(QDataStream &stream) const
{
stream << standardChannel;
}
void ReadyReadPacket::doDeserialize(QDataStream &stream)
{
stream >> standardChannel;
}
ProcessDonePacket::ProcessDonePacket(quintptr token)
: LauncherPacket(LauncherPacketType::ProcessDone, token)
{
}
void ProcessDonePacket::doSerialize(QDataStream &stream) const
{
stream << exitCode
<< int(exitStatus)
<< int(error)
<< errorString
<< stdOut
<< stdErr;
}
void ProcessDonePacket::doDeserialize(QDataStream &stream)
{
int exitStatusInt, errorInt;
stream >> exitCode
>> exitStatusInt
>> errorInt
>> errorString
>> stdOut
>> stdErr;
exitStatus = QProcess::ExitStatus(exitStatusInt);
error = QProcess::ProcessError(errorInt);
}
ShutdownPacket::ShutdownPacket() : LauncherPacket(LauncherPacketType::Shutdown, 0) { }
void ShutdownPacket::doSerialize(QDataStream &stream) const { Q_UNUSED(stream); }
void ShutdownPacket::doDeserialize(QDataStream &stream) { Q_UNUSED(stream); }
void PacketParser::setDevice(QIODevice *device)
{
m_stream.setDevice(device);
m_sizeOfNextPacket = -1;
}
bool PacketParser::parse()
{
static const int commonPayloadSize = static_cast<int>(1 + sizeof(quintptr));
if (m_sizeOfNextPacket == -1) {
if (m_stream.device()->bytesAvailable() < static_cast<int>(sizeof m_sizeOfNextPacket))
return false;
m_stream >> m_sizeOfNextPacket;
if (m_sizeOfNextPacket < commonPayloadSize)
throw InvalidPacketSizeException(m_sizeOfNextPacket);
}
if (m_stream.device()->bytesAvailable() < m_sizeOfNextPacket)
return false;
quint8 type;
m_stream >> type;
m_type = static_cast<LauncherPacketType>(type);
m_stream >> m_token;
m_packetData = m_stream.device()->read(m_sizeOfNextPacket - commonPayloadSize);
m_sizeOfNextPacket = -1;
return true;
}
} // namespace Internal
} // namespace Utils

View File

@@ -1,210 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "processenums.h"
#include <QDataStream>
#include <QProcess>
#include <QStringList>
QT_BEGIN_NAMESPACE
class QByteArray;
QT_END_NAMESPACE
namespace Utils {
namespace Internal {
enum class LauncherPacketType {
// client -> launcher packets:
Shutdown,
StartProcess,
WriteIntoProcess,
ControlProcess,
// launcher -> client packets:
ProcessStarted,
ReadyReadStandardOutput,
ReadyReadStandardError,
ProcessDone
};
class PacketParser
{
public:
class InvalidPacketSizeException
{
public:
InvalidPacketSizeException(int size) : size(size) { }
const int size;
};
void setDevice(QIODevice *device);
bool parse();
LauncherPacketType type() const { return m_type; }
quintptr token() const { return m_token; }
const QByteArray &packetData() const { return m_packetData; }
private:
QDataStream m_stream;
LauncherPacketType m_type = LauncherPacketType::Shutdown;
quintptr m_token = 0;
QByteArray m_packetData;
int m_sizeOfNextPacket = -1;
};
class LauncherPacket
{
public:
virtual ~LauncherPacket();
template<class Packet> static Packet extractPacket(quintptr token, const QByteArray &data)
{
Packet p(token);
p.deserialize(data);
return p;
}
QByteArray serialize() const;
void deserialize(const QByteArray &data);
const LauncherPacketType type;
const quintptr token = 0;
protected:
LauncherPacket(LauncherPacketType type, quintptr token) : type(type), token(token) { }
private:
virtual void doSerialize(QDataStream &stream) const = 0;
virtual void doDeserialize(QDataStream &stream) = 0;
};
class StartProcessPacket : public LauncherPacket
{
public:
StartProcessPacket(quintptr token);
QString command;
QStringList arguments;
QString workingDir;
QStringList env;
ProcessMode processMode = ProcessMode::Reader;
QByteArray writeData;
QProcess::ProcessChannelMode processChannelMode = QProcess::SeparateChannels;
QString standardInputFile;
bool belowNormalPriority = false;
QString nativeArguments;
bool lowPriority = false;
bool unixTerminalDisabled = false;
bool useCtrlCStub = false;
int reaperTimeout = 500;
bool createConsoleOnWindows = false;
bool forceDefaultErrorMode = false;
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
class ProcessStartedPacket : public LauncherPacket
{
public:
ProcessStartedPacket(quintptr token);
int processId = 0;
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
class ControlProcessPacket : public LauncherPacket
{
public:
ControlProcessPacket(quintptr token);
enum class SignalType {
Kill, // Calls QProcess::kill
Terminate, // Calls QProcess::terminate
Close, // Puts the process into the reaper, no confirmation signal is being sent.
CloseWriteChannel
};
SignalType signalType = SignalType::Kill;
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
class WritePacket : public LauncherPacket
{
public:
WritePacket(quintptr token) : LauncherPacket(LauncherPacketType::WriteIntoProcess, token) { }
QByteArray inputData;
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
class ShutdownPacket : public LauncherPacket
{
public:
ShutdownPacket();
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
class ReadyReadPacket : public LauncherPacket
{
public:
QByteArray standardChannel;
protected:
ReadyReadPacket(LauncherPacketType type, quintptr token) : LauncherPacket(type, token) { }
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
class ReadyReadStandardOutputPacket : public ReadyReadPacket
{
public:
ReadyReadStandardOutputPacket(quintptr token)
: ReadyReadPacket(LauncherPacketType::ReadyReadStandardOutput, token) { }
};
class ReadyReadStandardErrorPacket : public ReadyReadPacket
{
public:
ReadyReadStandardErrorPacket(quintptr token)
: ReadyReadPacket(LauncherPacketType::ReadyReadStandardError, token) { }
};
class ProcessDonePacket : public LauncherPacket
{
public:
ProcessDonePacket(quintptr token);
QByteArray stdOut;
QByteArray stdErr;
int exitCode = 0;
QProcess::ExitStatus exitStatus = QProcess::NormalExit;
QProcess::ProcessError error = QProcess::UnknownError;
QString errorString;
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
} // namespace Internal
} // namespace Utils
Q_DECLARE_METATYPE(Utils::Internal::LauncherPacketType);

View File

@@ -1,669 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "launchersocket.h"
#include "algorithm.h"
#include "launcherinterface.h"
#include "qtcassert.h"
#include "utilstr.h"
#include <QLocalSocket>
#include <QMutexLocker>
namespace Utils {
namespace Internal {
class LauncherSignal
{
public:
CallerHandle::SignalType signalType() const { return m_signalType; }
virtual ~LauncherSignal() = default;
protected:
LauncherSignal(CallerHandle::SignalType signalType) : m_signalType(signalType) {}
private:
const CallerHandle::SignalType m_signalType;
};
class LauncherStartedSignal : public LauncherSignal
{
public:
LauncherStartedSignal(int processId)
: LauncherSignal(CallerHandle::SignalType::Started)
, m_processId(processId) {}
int processId() const { return m_processId; }
private:
const int m_processId;
};
class LauncherReadyReadSignal : public LauncherSignal
{
public:
LauncherReadyReadSignal(const QByteArray &stdOut, const QByteArray &stdErr)
: LauncherSignal(CallerHandle::SignalType::ReadyRead)
, m_stdOut(stdOut)
, m_stdErr(stdErr) {}
QByteArray stdOut() const { return m_stdOut; }
QByteArray stdErr() const { return m_stdErr; }
private:
QByteArray m_stdOut;
QByteArray m_stdErr;
};
class LauncherDoneSignal : public LauncherSignal
{
public:
LauncherDoneSignal(const ProcessResultData &resultData)
: LauncherSignal(CallerHandle::SignalType::Done)
, m_resultData(resultData) {}
ProcessResultData resultData() const { return m_resultData; }
private:
const ProcessResultData m_resultData;
};
CallerHandle::~CallerHandle()
{
qDeleteAll(m_signals);
}
void CallerHandle::flush()
{
flushFor(SignalType::NoSignal);
}
bool CallerHandle::flushFor(SignalType signalType)
{
QTC_ASSERT(isCalledFromCallersThread(), return {});
QList<LauncherSignal *> oldSignals;
{
QMutexLocker locker(&m_mutex);
const QList<SignalType> storedSignals =
Utils::transform(std::as_const(m_signals), [](const LauncherSignal *launcherSignal) {
return launcherSignal->signalType();
});
// If we are flushing for ReadyRead or Done - flush all.
// If we are flushing for Started:
// - if Started was buffered - flush Started only.
// - otherwise if Done signal was buffered - flush all.
const bool flushAll = (signalType != SignalType::Started)
|| (!storedSignals.contains(SignalType::Started)
&& storedSignals.contains(SignalType::Done));
if (flushAll) {
oldSignals = m_signals;
m_signals = {};
} else {
auto matchingIndex = storedSignals.lastIndexOf(signalType);
if (matchingIndex >= 0) {
oldSignals = m_signals.mid(0, matchingIndex + 1);
m_signals = m_signals.mid(matchingIndex + 1);
}
}
}
bool signalMatched = false;
for (const LauncherSignal *storedSignal : std::as_const(oldSignals)) {
const SignalType storedSignalType = storedSignal->signalType();
if (storedSignalType == signalType)
signalMatched = true;
switch (storedSignalType) {
case SignalType::NoSignal:
break;
case SignalType::Started:
handleStarted(static_cast<const LauncherStartedSignal *>(storedSignal));
break;
case SignalType::ReadyRead:
handleReadyRead(static_cast<const LauncherReadyReadSignal *>(storedSignal));
break;
case SignalType::Done:
signalMatched = true;
handleDone(static_cast<const LauncherDoneSignal *>(storedSignal));
break;
}
delete storedSignal;
}
return signalMatched;
}
// Called from caller's thread exclusively.
bool CallerHandle::shouldFlush() const
{
QTC_ASSERT(isCalledFromCallersThread(), return false);
QMutexLocker locker(&m_mutex);
return !m_signals.isEmpty();
}
void CallerHandle::handleStarted(const LauncherStartedSignal *launcherSignal)
{
QTC_ASSERT(isCalledFromCallersThread(), return);
m_processState = QProcess::Running;
m_processId = launcherSignal->processId();
emit started(m_processId);
}
void CallerHandle::handleReadyRead(const LauncherReadyReadSignal *launcherSignal)
{
QTC_ASSERT(isCalledFromCallersThread(), return);
emit readyRead(launcherSignal->stdOut(), launcherSignal->stdErr());
}
void CallerHandle::handleDone(const LauncherDoneSignal *launcherSignal)
{
QTC_ASSERT(isCalledFromCallersThread(), return);
m_processState = QProcess::NotRunning;
emit done(launcherSignal->resultData());
m_processId = 0;
}
// Called from launcher's thread exclusively.
void CallerHandle::appendSignal(LauncherSignal *newSignal)
{
QTC_ASSERT(!isCalledFromCallersThread(), return);
QTC_ASSERT(newSignal->signalType() != SignalType::NoSignal, delete newSignal; return);
QMutexLocker locker(&m_mutex);
QTC_ASSERT(isCalledFromLaunchersThread(), return);
m_signals.append(newSignal);
}
QProcess::ProcessState CallerHandle::state() const
{
return m_processState;
}
void CallerHandle::sendControlPacket(ControlProcessPacket::SignalType signalType)
{
if (m_processState == QProcess::NotRunning)
return;
// TODO: In case m_processState == QProcess::Starting and the launcher socket isn't ready yet
// we might want to remove posted start packet and finish the process immediately.
// In addition, we may always try to check if correspodning start packet for the m_token
// is still awaiting and do the same (remove the packet from the stack and finish immediately).
ControlProcessPacket packet(m_token);
packet.signalType = signalType;
sendPacket(packet);
}
void CallerHandle::terminate()
{
QTC_ASSERT(isCalledFromCallersThread(), return);
sendControlPacket(ControlProcessPacket::SignalType::Terminate);
}
void CallerHandle::kill()
{
QTC_ASSERT(isCalledFromCallersThread(), return);
sendControlPacket(ControlProcessPacket::SignalType::Kill);
}
void CallerHandle::close()
{
QTC_ASSERT(isCalledFromCallersThread(), return);
sendControlPacket(ControlProcessPacket::SignalType::Close);
}
void CallerHandle::closeWriteChannel()
{
QTC_ASSERT(isCalledFromCallersThread(), return);
sendControlPacket(ControlProcessPacket::SignalType::CloseWriteChannel);
}
qint64 CallerHandle::processId() const
{
QTC_ASSERT(isCalledFromCallersThread(), return 0);
return m_processId;
}
void CallerHandle::start(const QString &program, const QStringList &arguments)
{
QTC_ASSERT(isCalledFromCallersThread(), return);
if (!m_launcherHandle || m_launcherHandle->isSocketError()) {
const QString errorString = Tr::tr("Process launcher socket error.");
const ProcessResultData result = { 0, QProcess::NormalExit, QProcess::FailedToStart,
errorString };
emit done(result);
return;
}
auto startWhenRunning = [&program, &oldProgram = m_command] {
qWarning() << "Trying to start" << program << "while" << oldProgram
<< "is still running for the same Process instance."
<< "The current call will be ignored.";
};
QTC_ASSERT(m_processState == QProcess::NotRunning, startWhenRunning(); return);
auto processLauncherNotStarted = [&program] {
qWarning() << "Trying to start" << program << "while process launcher wasn't started yet.";
};
QTC_ASSERT(LauncherInterface::isStarted(), processLauncherNotStarted());
QMutexLocker locker(&m_mutex);
m_command = program;
m_arguments = arguments;
m_processState = QProcess::Starting;
StartProcessPacket p(m_token);
p.command = m_command;
p.arguments = m_arguments;
p.env = m_setup->m_environment.toStringList();
if (p.env.isEmpty())
p.env = Environment::systemEnvironment().toStringList();
p.workingDir = m_setup->m_workingDirectory.path();
p.processMode = m_setup->m_processMode;
p.writeData = m_setup->m_writeData;
p.processChannelMode = m_setup->m_processChannelMode;
p.standardInputFile = m_setup->m_standardInputFile;
p.belowNormalPriority = m_setup->m_belowNormalPriority;
p.nativeArguments = m_setup->m_nativeArguments;
p.lowPriority = m_setup->m_lowPriority;
p.unixTerminalDisabled = m_setup->m_unixTerminalDisabled;
p.useCtrlCStub = m_setup->m_useCtrlCStub;
p.reaperTimeout = m_setup->m_reaperTimeout.count();
p.createConsoleOnWindows = m_setup->m_createConsoleOnWindows;
p.forceDefaultErrorMode = m_setup->m_forceDefaultErrorMode;
sendPacket(p);
}
// Called from caller's thread exclusively.
void CallerHandle::sendPacket(const Internal::LauncherPacket &packet)
{
LauncherInterface::sendData(packet.serialize());
}
qint64 CallerHandle::write(const QByteArray &data)
{
QTC_ASSERT(isCalledFromCallersThread(), return -1);
if (m_processState != QProcess::Running)
return -1;
WritePacket p(m_token);
p.inputData = data;
sendPacket(p);
return data.size();
}
QString CallerHandle::program() const
{
QMutexLocker locker(&m_mutex);
return m_command;
}
QStringList CallerHandle::arguments() const
{
QMutexLocker locker(&m_mutex);
return m_arguments;
}
void CallerHandle::setProcessSetupData(ProcessSetupData *setup)
{
QTC_ASSERT(isCalledFromCallersThread(), return);
m_setup = setup;
}
bool CallerHandle::waitForSignal(SignalType signalType, QDeadlineTimer timeout)
{
QTC_ASSERT(isCalledFromCallersThread(), return false);
QTC_ASSERT(m_launcherHandle, return false);
return m_launcherHandle->waitForSignal(signalType, timeout);
}
// Called from caller's or launcher's thread.
bool CallerHandle::isCalledFromCallersThread() const
{
return QThread::currentThread() == thread();
}
// Called from caller's or launcher's thread. Call me with mutex locked.
bool CallerHandle::isCalledFromLaunchersThread() const
{
if (!m_launcherHandle)
return false;
return QThread::currentThread() == m_launcherHandle->thread();
}
// Called from caller's thread exclusively.
bool LauncherHandle::waitForSignal(CallerHandle::SignalType newSignal, QDeadlineTimer timeout)
{
QTC_ASSERT(!isCalledFromLaunchersThread(), return false);
while (true) {
if (timeout.hasExpired())
break;
if (!doWaitForSignal(timeout))
break;
// Matching (or Done) signal was flushed
if (m_callerHandle->flushFor(newSignal))
return true;
// Otherwise continue awaiting (e.g. when ReadyRead came while waitForFinished())
}
return false;
}
// Called from caller's thread exclusively.
bool LauncherHandle::doWaitForSignal(QDeadlineTimer deadline)
{
QMutexLocker locker(&m_mutex);
QTC_ASSERT(isCalledFromCallersThread(), return false);
// 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_callerHandle->shouldFlush())
return true;
return m_waitCondition.wait(&m_mutex, deadline);
}
// Called from launcher's thread exclusively. Call me with mutex locked.
void LauncherHandle::flushCaller()
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
if (!m_callerHandle)
return;
m_waitCondition.wakeOne();
// call in callers thread
QMetaObject::invokeMethod(m_callerHandle, &CallerHandle::flush);
}
void LauncherHandle::handlePacket(LauncherPacketType type, const QByteArray &payload)
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
switch (type) {
case LauncherPacketType::ProcessStarted:
handleStartedPacket(payload);
break;
case LauncherPacketType::ReadyReadStandardOutput:
handleReadyReadStandardOutput(payload);
break;
case LauncherPacketType::ReadyReadStandardError:
handleReadyReadStandardError(payload);
break;
case LauncherPacketType::ProcessDone:
handleDonePacket(payload);
break;
default:
QTC_ASSERT(false, break);
}
}
void LauncherHandle::handleStartedPacket(const QByteArray &packetData)
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
QMutexLocker locker(&m_mutex);
if (!m_callerHandle)
return;
const auto packet = LauncherPacket::extractPacket<ProcessStartedPacket>(m_token, packetData);
m_callerHandle->appendSignal(new LauncherStartedSignal(packet.processId));
flushCaller();
}
void LauncherHandle::handleReadyReadStandardOutput(const QByteArray &packetData)
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
QMutexLocker locker(&m_mutex);
if (!m_callerHandle)
return;
const auto packet = LauncherPacket::extractPacket<ReadyReadStandardOutputPacket>(m_token, packetData);
if (packet.standardChannel.isEmpty())
return;
m_callerHandle->appendSignal(new LauncherReadyReadSignal(packet.standardChannel, {}));
flushCaller();
}
void LauncherHandle::handleReadyReadStandardError(const QByteArray &packetData)
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
QMutexLocker locker(&m_mutex);
if (!m_callerHandle)
return;
const auto packet = LauncherPacket::extractPacket<ReadyReadStandardErrorPacket>(m_token, packetData);
if (packet.standardChannel.isEmpty())
return;
m_callerHandle->appendSignal(new LauncherReadyReadSignal({}, packet.standardChannel));
flushCaller();
}
void LauncherHandle::handleDonePacket(const QByteArray &packetData)
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
QMutexLocker locker(&m_mutex);
if (!m_callerHandle)
return;
const auto packet = LauncherPacket::extractPacket<ProcessDonePacket>(m_token, packetData);
const QByteArray stdOut = packet.stdOut;
const QByteArray stdErr = packet.stdErr;
const ProcessResultData result = { packet.exitCode, packet.exitStatus,
packet.error, packet.errorString };
if (!stdOut.isEmpty() || !stdErr.isEmpty())
m_callerHandle->appendSignal(new LauncherReadyReadSignal(stdOut, stdErr));
m_callerHandle->appendSignal(new LauncherDoneSignal(result));
flushCaller();
}
void LauncherHandle::handleSocketError(const QString &message)
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
m_socketError = true; // TODO: ???
QMutexLocker locker(&m_mutex);
if (!m_callerHandle)
return;
// TODO: FailedToStart may be wrong in case process has already started
const QString errorString = Tr::tr("Internal socket error: %1").arg(message);
const ProcessResultData result = { 0, QProcess::NormalExit, QProcess::FailedToStart,
errorString };
m_callerHandle->appendSignal(new LauncherDoneSignal(result));
flushCaller();
}
bool LauncherHandle::isCalledFromLaunchersThread() const
{
return QThread::currentThread() == thread();
}
// call me with mutex locked
bool LauncherHandle::isCalledFromCallersThread() const
{
if (!m_callerHandle)
return false;
return QThread::currentThread() == m_callerHandle->thread();
}
LauncherSocket::LauncherSocket(QObject *parent) : QObject(parent)
{
qRegisterMetaType<Utils::Internal::LauncherPacketType>();
qRegisterMetaType<quintptr>("quintptr");
}
LauncherSocket::~LauncherSocket()
{
QMutexLocker locker(&m_mutex);
auto displayHandles = [&handles = m_handles] {
qWarning() << "Destroying process launcher while" << handles.count()
<< "processes are still alive. The following processes are still alive:";
for (LauncherHandle *handle : handles) {
CallerHandle *callerHandle = handle->callerHandle();
if (callerHandle->state() != QProcess::NotRunning) {
qWarning() << " " << callerHandle->program() << callerHandle->arguments()
<< "in thread" << (void *)callerHandle->thread();
} else {
qWarning() << " Not running process in thread" << (void *)callerHandle->thread();
}
}
};
QTC_ASSERT(m_handles.isEmpty(), displayHandles());
}
void LauncherSocket::sendData(const QByteArray &data)
{
auto storeRequest = [this](const QByteArray &data)
{
QMutexLocker locker(&m_mutex);
m_requests.push_back(data);
return m_requests.size() == 1; // Returns true if requests handling should be triggered.
};
if (storeRequest(data)) // Call handleRequests() in launcher's thread.
QMetaObject::invokeMethod(this, &LauncherSocket::handleRequests);
}
CallerHandle *LauncherSocket::registerHandle(QObject *parent, quintptr token)
{
QTC_ASSERT(!isCalledFromLaunchersThread(), return nullptr);
QMutexLocker locker(&m_mutex);
if (m_handles.contains(token))
return nullptr; // TODO: issue a warning
CallerHandle *callerHandle = new CallerHandle(parent, token);
LauncherHandle *launcherHandle = new LauncherHandle(token);
callerHandle->setLauncherHandle(launcherHandle);
launcherHandle->setCallerHandle(callerHandle);
launcherHandle->moveToThread(thread());
// Call it after moving LauncherHandle to the launcher's thread.
// Since this method is invoked from caller's thread, CallerHandle will live in caller's thread.
m_handles.insert(token, launcherHandle);
connect(this, &LauncherSocket::errorOccurred,
launcherHandle, &LauncherHandle::handleSocketError);
return callerHandle;
}
void LauncherSocket::unregisterHandle(quintptr token)
{
QTC_ASSERT(!isCalledFromLaunchersThread(), return);
QMutexLocker locker(&m_mutex);
auto it = m_handles.constFind(token);
if (it == m_handles.constEnd())
return; // TODO: issue a warning
LauncherHandle *launcherHandle = it.value();
CallerHandle *callerHandle = launcherHandle->callerHandle();
launcherHandle->setCallerHandle(nullptr);
callerHandle->setLauncherHandle(nullptr);
launcherHandle->deleteLater();
callerHandle->deleteLater();
m_handles.erase(it);
}
LauncherHandle *LauncherSocket::handleForToken(quintptr token) const
{
QTC_ASSERT(isCalledFromLaunchersThread(), return nullptr);
QMutexLocker locker(&m_mutex);
return m_handles.value(token);
}
void LauncherSocket::setSocket(QLocalSocket *socket)
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
QTC_ASSERT(!m_socket, return);
m_socket.store(socket);
m_packetParser.setDevice(m_socket);
connect(m_socket, &QLocalSocket::errorOccurred,
this, &LauncherSocket::handleSocketError);
connect(m_socket, &QLocalSocket::readyRead,
this, &LauncherSocket::handleSocketDataAvailable);
connect(m_socket, &QLocalSocket::disconnected,
this, &LauncherSocket::handleSocketDisconnected);
handleRequests();
}
void LauncherSocket::shutdown()
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
const auto socket = m_socket.exchange(nullptr);
if (!socket)
return;
socket->disconnect();
socket->write(ShutdownPacket().serialize());
socket->waitForBytesWritten(1000);
socket->deleteLater(); // or schedule a queued call to delete later?
}
void LauncherSocket::handleSocketError()
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
auto socket = m_socket.load();
if (socket->error() != QLocalSocket::PeerClosedError)
handleError(Tr::tr("Socket error: %1").arg(socket->errorString()));
}
void LauncherSocket::handleSocketDataAvailable()
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
try {
if (!m_packetParser.parse())
return;
} catch (const PacketParser::InvalidPacketSizeException &e) {
handleError(Tr::tr("Internal protocol error: invalid packet size %1.").arg(e.size));
return;
}
LauncherHandle *handle = handleForToken(m_packetParser.token());
if (handle) {
switch (m_packetParser.type()) {
case LauncherPacketType::ProcessStarted:
case LauncherPacketType::ReadyReadStandardOutput:
case LauncherPacketType::ReadyReadStandardError:
case LauncherPacketType::ProcessDone:
handle->handlePacket(m_packetParser.type(), m_packetParser.packetData());
break;
default:
handleError(Tr::tr("Internal protocol error: invalid packet type %1.")
.arg(static_cast<int>(m_packetParser.type())));
return;
}
} else {
// qDebug() << "No handler for token" << m_packetParser.token() << m_handles;
// in this case the Process was canceled and deleted
}
handleSocketDataAvailable();
}
void LauncherSocket::handleSocketDisconnected()
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
handleError(Tr::tr("Launcher socket closed unexpectedly."));
}
void LauncherSocket::handleError(const QString &error)
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
const auto socket = m_socket.exchange(nullptr);
socket->disconnect();
socket->deleteLater();
emit errorOccurred(error);
}
void LauncherSocket::handleRequests()
{
QTC_ASSERT(isCalledFromLaunchersThread(), return);
const auto socket = m_socket.load();
if (!socket)
return;
std::vector<QByteArray> requests;
{
QMutexLocker locker(&m_mutex);
requests = m_requests;
m_requests.clear();
}
for (const QByteArray &request : std::as_const(requests))
socket->write(request);
}
bool LauncherSocket::isCalledFromLaunchersThread() const
{
return QThread::currentThread() == thread();
}
} // namespace Internal
} // namespace Utils

View File

@@ -1,214 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include "launcherpackets.h"
#include "processinterface.h"
#include <QDeadlineTimer>
#include <QHash>
#include <QMutex>
#include <QObject>
#include <QProcess>
#include <QWaitCondition>
#include <atomic>
#include <memory>
#include <vector>
QT_BEGIN_NAMESPACE
class QLocalSocket;
QT_END_NAMESPACE
namespace Utils {
namespace Internal {
class LauncherInterfacePrivate;
class LauncherHandle;
class LauncherSignal;
class LauncherStartedSignal;
class LauncherReadyReadSignal;
class LauncherDoneSignal;
// All the methods and data fields in this class are called / accessed from the caller's thread.
// Exceptions are explicitly marked.
class CallerHandle : public QObject
{
Q_OBJECT
public:
enum class SignalType {
NoSignal,
Started,
ReadyRead,
Done
};
Q_ENUM(SignalType)
CallerHandle(QObject *parent, quintptr token)
: QObject(parent), m_token(token) {}
~CallerHandle() override;
LauncherHandle *launcherHandle() const { return m_launcherHandle; }
void setLauncherHandle(LauncherHandle *handle) { QMutexLocker locker(&m_mutex); m_launcherHandle = handle; }
bool waitForSignal(CallerHandle::SignalType signalType, QDeadlineTimer timeout);
// Returns the list of flushed signals.
void flush();
bool flushFor(SignalType signalType);
bool shouldFlush() const;
// Called from launcher's thread exclusively.
void appendSignal(LauncherSignal *launcherSignal);
// Called from caller's or launcher's thread.
QProcess::ProcessState state() const;
void sendControlPacket(ControlProcessPacket::SignalType signalType);
void terminate();
void kill();
void close();
void closeWriteChannel();
qint64 processId() const;
void start(const QString &program, const QStringList &arguments);
qint64 write(const QByteArray &data);
// Called from caller's or launcher's thread.
QString program() const;
// Called from caller's or launcher's thread.
QStringList arguments() const;
void setProcessSetupData(ProcessSetupData *setup);
signals:
void started(qint64 processId, qint64 applicationMainThreadId = 0);
void readyRead(const QByteArray &outputData, const QByteArray &errorData);
void done(const Utils::ProcessResultData &resultData);
private:
// Called from caller's thread exclusively.
void sendPacket(const Internal::LauncherPacket &packet);
// Called from caller's or launcher's thread.
bool isCalledFromCallersThread() const;
// Called from caller's or launcher's thread. Call me with mutex locked.
bool isCalledFromLaunchersThread() const;
QByteArray readAndClear(QByteArray &data) const
{
const QByteArray tmp = data;
data.clear();
return tmp;
}
void handleStarted(const LauncherStartedSignal *launcherSignal);
void handleReadyRead(const LauncherReadyReadSignal *launcherSignal);
void handleDone(const LauncherDoneSignal *launcherSignal);
// Lives in launcher's thread. Modified from caller's thread.
LauncherHandle *m_launcherHandle = nullptr;
mutable QMutex m_mutex;
// Accessed from caller's and launcher's thread
QList<LauncherSignal *> m_signals;
const quintptr m_token;
// Modified from caller's thread, read from launcher's thread
std::atomic<QProcess::ProcessState> m_processState = QProcess::NotRunning;
int m_processId = 0;
QString m_command;
QStringList m_arguments;
ProcessSetupData *m_setup = nullptr;
};
// Moved to the launcher thread, returned to caller's thread.
// It's assumed that this object will be alive at least
// as long as the corresponding Process is alive.
class LauncherHandle : public QObject
{
public:
// Called from caller's thread, moved to launcher's thread afterwards.
LauncherHandle(quintptr token) : m_token(token) {}
// Called from caller's thread exclusively.
bool waitForSignal(CallerHandle::SignalType newSignal, QDeadlineTimer timeout);
CallerHandle *callerHandle() const { return m_callerHandle; }
void setCallerHandle(CallerHandle *handle) { QMutexLocker locker(&m_mutex); m_callerHandle = handle; }
// Called from launcher's thread exclusively.
void handleSocketError(const QString &message);
void handlePacket(LauncherPacketType type, const QByteArray &payload);
// Called from caller's thread exclusively.
bool isSocketError() const { return m_socketError; }
private:
// Called from caller's thread exclusively.
bool doWaitForSignal(QDeadlineTimer deadline);
// Called from launcher's thread exclusively. Call me with mutex locked.
void flushCaller();
// Called from launcher's thread exclusively.
void handleStartedPacket(const QByteArray &packetData);
void handleReadyReadStandardOutput(const QByteArray &packetData);
void handleReadyReadStandardError(const QByteArray &packetData);
void handleDonePacket(const QByteArray &packetData);
// Called from caller's or launcher's thread.
bool isCalledFromLaunchersThread() const;
bool isCalledFromCallersThread() const;
// Lives in caller's thread. Modified only in caller's thread. TODO: check usages - all should be with mutex
CallerHandle *m_callerHandle = nullptr;
mutable QMutex m_mutex;
QWaitCondition m_waitCondition;
const quintptr m_token;
std::atomic_bool m_socketError = false;
};
class LauncherSocket : public QObject
{
Q_OBJECT
friend class LauncherInterfacePrivate;
public:
// Called from caller's thread exclusively.
void sendData(const QByteArray &data);
CallerHandle *registerHandle(QObject *parent, quintptr token);
void unregisterHandle(quintptr token);
signals:
void errorOccurred(const QString &error);
private:
// Called from caller's thread, moved to launcher's thread.
LauncherSocket(QObject *parent = nullptr);
// Called from launcher's thread exclusively.
~LauncherSocket() override;
// Called from launcher's thread exclusively.
LauncherHandle *handleForToken(quintptr token) const;
// Called from launcher's thread exclusively.
void setSocket(QLocalSocket *socket);
void shutdown();
// Called from launcher's thread exclusively.
void handleSocketError();
void handleSocketDataAvailable();
void handleSocketDisconnected();
void handleError(const QString &error);
void handleRequests();
// Called from caller's or launcher's thread.
bool isCalledFromLaunchersThread() const;
std::atomic<QLocalSocket *> m_socket{nullptr};
PacketParser m_packetParser;
std::vector<QByteArray> m_requests;
mutable QMutex m_mutex;
QHash<quintptr, LauncherHandle *> m_handles;
};
} // namespace Internal
} // namespace Utils

View File

@@ -15,13 +15,6 @@ enum class ProcessMode {
Writer // This opens in ReadWrite mode and doesn't close the write channel
};
enum class ProcessImpl {
QProcess,
ProcessLauncher,
Default // Defaults to ProcessLauncherImpl, if QTC_USE_QPROCESS env var is set
// it equals to QProcessImpl.
};
enum class TerminalMode {
Off,
Run, // Start with process stub enabled

View File

@@ -71,7 +71,6 @@ public:
class QTCREATOR_UTILS_EXPORT ProcessSetupData
{
public:
ProcessImpl m_processImpl = ProcessImpl::Default;
ProcessMode m_processMode = ProcessMode::Reader;
TerminalMode m_terminalMode = TerminalMode::Off;
@@ -165,8 +164,6 @@ private:
// It's being called in Starting or Running state.
virtual void sendControlSignal(ControlSignal controlSignal) = 0;
virtual ProcessBlockingInterface *processBlockingInterface() const { return nullptr; }
friend class Process;
friend class Internal::ProcessPrivate;
};

View File

@@ -7,9 +7,8 @@
#include "environment.h"
#include "guard.h"
#include "hostosinfo.h"
#include "launcherinterface.h"
#include "launchersocket.h"
#include "processhelper.h"
#include "processinterface.h"
#include "processreaper.h"
#include "stringutils.h"
#include "terminalhooks.h"
@@ -28,6 +27,7 @@
#include <QTextCodec>
#include <QThread>
#include <QTimer>
#include <QWaitCondition>
#ifdef QT_GUI_LIB
// qmlpuppet does not use that.
@@ -40,7 +40,6 @@
#include <chrono>
#include <functional>
#include <iostream>
#include <limits>
#include <memory>
using namespace Utils::Internal;
@@ -300,38 +299,6 @@ bool DefaultImpl::ensureProgramExists(const QString &program)
return false;
}
// TODO: Remove QProcessBlockingImpl later, after Creator 13.0 is released at least.
// Rationale: QProcess::waitForReadyRead() waits only for one channel, either stdOut or stdErr.
// Since we can't predict where the data will come first,
// setting the QProcess::setReadChannel() in advance is a mis-design of the QProcess API.
// This issue does not affect GeneralProcessBlockingImpl, but it might be not as optimal
// as QProcessBlockingImpl. However, since we are blocking the caller thread anyway,
// the small overhead in speed doesn't play the most significant role, thus the proper
// behavior of Process::waitForReadyRead(), which listens to both channels, wins.
// 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;
// };
class PtyProcessImpl final : public DefaultImpl
{
public:
@@ -555,113 +522,6 @@ private:
// QProcessBlockingImpl *m_blockingImpl = nullptr;
};
static uint uniqueToken()
{
static std::atomic_uint globalUniqueToken = 0;
return ++globalUniqueToken;
}
class ProcessLauncherBlockingImpl : public ProcessBlockingInterface
{
public:
ProcessLauncherBlockingImpl(CallerHandle *caller) : m_caller(caller) {}
private:
bool waitForSignal(ProcessSignalType signalType, QDeadlineTimer timeout) 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, timeout);
}
CallerHandle *m_caller = nullptr;
};
class ProcessLauncherImpl final : public DefaultImpl
{
Q_OBJECT
public:
ProcessLauncherImpl() : m_token(uniqueToken())
{
m_handle = LauncherInterface::registerHandle(this, token());
m_handle->setProcessSetupData(&m_setup);
connect(m_handle, &CallerHandle::started,
this, &ProcessInterface::started);
connect(m_handle, &CallerHandle::readyRead,
this, &ProcessInterface::readyRead);
connect(m_handle, &CallerHandle::done,
this, &ProcessInterface::done);
m_blockingImpl = new ProcessLauncherBlockingImpl(m_handle);
}
~ProcessLauncherImpl() final
{
m_handle->close();
LauncherInterface::unregisterHandle(token());
m_handle = nullptr;
}
private:
qint64 write(const QByteArray &data) final { return m_handle->write(data); }
void sendControlSignal(ControlSignal controlSignal) final {
switch (controlSignal) {
case ControlSignal::Terminate:
m_handle->terminate();
break;
case ControlSignal::Kill:
m_handle->kill();
break;
case ControlSignal::Interrupt:
ProcessHelper::interruptPid(m_handle->processId());
break;
case ControlSignal::KickOff:
QTC_CHECK(false);
break;
case ControlSignal::CloseWriteChannel:
m_handle->closeWriteChannel();
break;
}
}
ProcessBlockingInterface *processBlockingInterface() const override { return m_blockingImpl; }
void doDefaultStart(const QString &program, const QStringList &arguments) final
{
m_handle->start(program, arguments);
}
quintptr token() const { return m_token; }
const uint m_token = 0;
// Lives in caller's thread.
CallerHandle *m_handle = nullptr;
ProcessLauncherBlockingImpl *m_blockingImpl = nullptr;
};
static ProcessImpl defaultProcessImplHelper()
{
const QString value = qtcEnvironmentVariable("QTC_USE_QPROCESS", "TRUE").toUpper();
if (value != "FALSE" && value != "0")
return ProcessImpl::QProcess;
return ProcessImpl::ProcessLauncher;
}
static ProcessImpl defaultProcessImpl()
{
static const ProcessImpl impl = defaultProcessImplHelper();
return impl;
}
class ProcessInterfaceSignal
{
public:
@@ -799,12 +659,7 @@ public:
return new PtyProcessImpl;
if (m_setup.m_terminalMode != TerminalMode::Off)
return Terminal::Hooks::instance().createTerminalProcessInterface();
const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default
? defaultProcessImpl() : m_setup.m_processImpl;
if (impl == ProcessImpl::QProcess)
return new QProcessImpl;
return new ProcessLauncherImpl;
}
void setProcessInterface(ProcessInterface *process)
@@ -820,8 +675,6 @@ public:
connect(m_process.get(), &ProcessInterface::done,
this, &ProcessPrivate::handleDone);
m_blockingInterface.reset(process->processBlockingInterface());
if (!m_blockingInterface)
m_blockingInterface.reset(new GeneralProcessBlockingImpl(this));
m_blockingInterface->setParent(this);
}
@@ -1157,11 +1010,6 @@ Process::~Process()
delete d;
}
void Process::setProcessImpl(ProcessImpl processImpl)
{
d->m_setup.m_processImpl = processImpl;
}
void Process::setPtyData(const std::optional<Pty::Data> &data)
{
d->m_setup.m_ptyData = data;
@@ -2200,5 +2048,3 @@ void ProcessTaskAdapter::start()
}
} // namespace Utils
#include "qtcprocess.moc"

View File

@@ -91,8 +91,6 @@ public:
void setControlEnvironment(const Environment &env); // Possible helper process (ssh on host etc)
const Environment &controlEnvironment() const;
void setProcessImpl(ProcessImpl processImpl);
void setPtyData(const std::optional<Pty::Data> &data);
std::optional<Pty::Data> ptyData() const;

View File

@@ -177,12 +177,6 @@ QtcLibrary {
"itemviews.h",
"jsontreeitem.cpp",
"jsontreeitem.h",
"launcherinterface.cpp",
"launcherinterface.h",
"launcherpackets.cpp",
"launcherpackets.h",
"launchersocket.cpp",
"launchersocket.h",
"layoutbuilder.cpp",
"layoutbuilder.h",
"link.cpp",

View File

@@ -400,7 +400,6 @@ DockerProcessImpl::~DockerProcessImpl()
void DockerProcessImpl::start()
{
m_process.setProcessImpl(m_setup.m_processImpl);
m_process.setProcessMode(m_setup.m_processMode);
m_process.setTerminalMode(m_setup.m_terminalMode);
m_process.setPtyData(m_setup.m_ptyData);

View File

@@ -4,7 +4,6 @@
#include "../mesoninfoparser.h"
#include "../mesontools.h"
#include <utils/launcherinterface.h>
#include <utils/processinterface.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
@@ -40,8 +39,6 @@ private slots:
{
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath()
+ "/mesontest-XXXXXX");
Utils::LauncherInterface::setPathToLauncher(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
const auto path = findTool(ToolType::Meson);
if (!path)

View File

@@ -3,7 +3,6 @@
#include "../mesontools.h"
#include <utils/launcherinterface.h>
#include <utils/processinterface.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
@@ -28,8 +27,6 @@ private slots:
{
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath()
+ "/mesontest-XXXXXX");
Utils::LauncherInterface::setPathToLauncher(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
const auto path = findTool(ToolType::Meson);
if (!path)

View File

@@ -674,7 +674,6 @@ void SshProcessInterfacePrivate::start()
cmd.addArg(QString("%1:localhost:%1").arg(forwardPort));
}
m_process.setProcessImpl(q->m_setup.m_processImpl);
m_process.setProcessMode(q->m_setup.m_processMode);
m_process.setTerminalMode(q->m_setup.m_terminalMode);
m_process.setPtyData(q->m_setup.m_ptyData);
@@ -743,7 +742,6 @@ void SshProcessInterfacePrivate::clearForStart()
void SshProcessInterfacePrivate::doStart()
{
m_process.setProcessImpl(q->m_setup.m_processImpl);
m_process.setProcessMode(q->m_setup.m_processMode);
m_process.setTerminalMode(q->m_setup.m_terminalMode);
m_process.setPtyData(q->m_setup.m_ptyData);

View File

@@ -26,7 +26,6 @@ if (APPLE)
add_subdirectory(iostool)
endif()
add_subdirectory(processlauncher)
if (WITH_QMLDESIGNER)
add_subdirectory(qml2puppet)
add_subdirectory(sqlitetester)

View File

@@ -1,35 +0,0 @@
set(LIBSDIR "${PROJECT_SOURCE_DIR}/src/libs")
set(UTILSDIR "${PROJECT_SOURCE_DIR}/src/libs/utils")
add_qtc_executable(qtcreator_processlauncher
INCLUDES "${LIBSDIR}"
DEPENDS Qt::Core Qt::Network
DEFINES UTILS_STATIC_LIBRARY
SOURCES
launcherlogging.cpp
launcherlogging.h
launchersockethandler.cpp
launchersockethandler.h
processlauncher-main.cpp
${UTILSDIR}/launcherpackets.cpp
${UTILSDIR}/launcherpackets.h
${UTILSDIR}/processenums.h
${UTILSDIR}/processhelper.cpp
${UTILSDIR}/processhelper.h
${UTILSDIR}/processreaper.cpp
${UTILSDIR}/processreaper.h
${UTILSDIR}/qtcassert.cpp
${UTILSDIR}/qtcassert.h
${UTILSDIR}/singleton.cpp
${UTILSDIR}/singleton.h
${UTILSDIR}/threadutils.cpp
${UTILSDIR}/threadutils.h
)
if (MSVC)
find_library(DbgHelpLib dbghelp)
endif()
extend_qtc_executable(qtcreator_processlauncher CONDITION MSVC
DEPENDS ${DbgHelpLib}
)

View File

@@ -1,10 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "launcherlogging.h"
namespace Utils {
namespace Internal {
Q_LOGGING_CATEGORY(launcherLog, "qtc.utils.launcher", QtWarningMsg)
}
}

View File

@@ -1,16 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QtCore/qloggingcategory.h>
#include <QtCore/qstring.h>
namespace Utils {
namespace Internal {
Q_DECLARE_LOGGING_CATEGORY(launcherLog)
template<typename T> void logDebug(const T &msg) { qCDebug(launcherLog) << msg; }
template<typename T> void logWarn(const T &msg) { qCWarning(launcherLog) << msg; }
template<typename T> void logError(const T &msg) { qCCritical(launcherLog) << msg; }
}
}

View File

@@ -1,284 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "launchersockethandler.h"
#include "launcherlogging.h"
#include <utils/processhelper.h>
#include <utils/processreaper.h>
#include <QCoreApplication>
#include <QLocalSocket>
#include <QProcess>
namespace Utils {
namespace Internal {
class ProcessWithToken : public ProcessHelper
{
Q_OBJECT
public:
ProcessWithToken(quintptr token, QObject *parent = nullptr) :
ProcessHelper(parent), m_token(token) { }
quintptr token() const { return m_token; }
void setReaperTimeout(int msecs) { m_reaperTimeout = msecs; };
int reaperTimeout() const { return m_reaperTimeout; }
private:
const quintptr m_token;
int m_reaperTimeout = 500;
};
LauncherSocketHandler::LauncherSocketHandler(QString serverPath, QObject *parent)
: QObject(parent),
m_serverPath(std::move(serverPath)),
m_socket(new QLocalSocket(this))
{
m_packetParser.setDevice(m_socket);
}
LauncherSocketHandler::~LauncherSocketHandler()
{
for (auto it = m_processes.cbegin(); it != m_processes.cend(); ++it) {
ProcessWithToken *p = it.value();
if (p->state() != QProcess::NotRunning)
logWarn(QStringLiteral("Shutting down while process %1 is running").arg(p->program()));
ProcessReaper::reap(p);
}
m_socket->disconnect();
m_socket->disconnectFromServer();
if (m_socket->state() != QLocalSocket::UnconnectedState
&& !m_socket->waitForDisconnected()) {
logWarn("Could not disconnect from server");
m_socket->close();
}
}
void LauncherSocketHandler::start()
{
connect(m_socket, &QLocalSocket::disconnected,
this, &LauncherSocketHandler::handleSocketClosed);
connect(m_socket, &QLocalSocket::readyRead, this, &LauncherSocketHandler::handleSocketData);
connect(m_socket,
&QLocalSocket::errorOccurred,
this, &LauncherSocketHandler::handleSocketError);
m_socket->connectToServer(m_serverPath);
}
void LauncherSocketHandler::handleSocketData()
{
try {
if (!m_packetParser.parse())
return;
} catch (const PacketParser::InvalidPacketSizeException &e) {
logWarn(QStringLiteral("Internal protocol error: Invalid packet size %1")
.arg(e.size));
return;
}
switch (m_packetParser.type()) {
case LauncherPacketType::StartProcess:
handleStartPacket();
break;
case LauncherPacketType::WriteIntoProcess:
handleWritePacket();
break;
case LauncherPacketType::ControlProcess:
handleControlPacket();
break;
case LauncherPacketType::Shutdown:
handleShutdownPacket();
return;
default:
logWarn(QStringLiteral("Internal protocol error: Invalid packet type %1")
.arg(static_cast<int>(m_packetParser.type())));
return;
}
handleSocketData();
}
void LauncherSocketHandler::handleSocketError()
{
if (m_socket->error() != QLocalSocket::PeerClosedError) {
logError(QStringLiteral("Socket error: %1").arg(m_socket->errorString()));
m_socket->disconnect();
qApp->quit();
}
}
void LauncherSocketHandler::handleSocketClosed()
{
logWarn("The connection has closed unexpectedly, shutting down");
m_socket->disconnect();
qApp->quit();
}
void LauncherSocketHandler::handleProcessError(ProcessWithToken *process)
{
// In case of FailedToStart we won't receive finished signal, so we send the error
// packet and remove the process here and now. For all other errors we should expect
// corresponding finished signal to appear, so we will send the error data together with
// the finished packet later on.
if (process->error() == QProcess::FailedToStart)
handleProcessFinished(process);
}
void LauncherSocketHandler::handleProcessStarted(ProcessWithToken *process)
{
ProcessStartedPacket packet(process->token());
packet.processId = process->processId();
process->processStartHandler()->handleProcessStarted();
sendPacket(packet);
}
void LauncherSocketHandler::handleReadyReadStandardOutput(ProcessWithToken *process)
{
ReadyReadStandardOutputPacket packet(process->token());
packet.standardChannel = process->readAllStandardOutput();
sendPacket(packet);
}
void LauncherSocketHandler::handleReadyReadStandardError(ProcessWithToken *process)
{
ReadyReadStandardErrorPacket packet(process->token());
packet.standardChannel = process->readAllStandardError();
sendPacket(packet);
}
void LauncherSocketHandler::handleProcessFinished(ProcessWithToken *process)
{
ProcessDonePacket packet(process->token());
packet.exitCode = process->exitCode();
packet.exitStatus = process->exitStatus();
packet.error = process->error();
packet.errorString = process->errorString();
if (process->processChannelMode() != QProcess::MergedChannels)
packet.stdErr = process->readAllStandardError();
packet.stdOut = process->readAllStandardOutput();
sendPacket(packet);
removeProcess(process->token());
}
void LauncherSocketHandler::handleStartPacket()
{
ProcessWithToken *& process = m_processes[m_packetParser.token()];
if (!process)
process = setupProcess(m_packetParser.token());
if (process->state() != QProcess::NotRunning) {
logWarn("Got start request while process was running");
return;
}
const auto packet = LauncherPacket::extractPacket<StartProcessPacket>(
m_packetParser.token(),
m_packetParser.packetData());
process->setEnvironment(packet.env);
process->setWorkingDirectory(packet.workingDir);
// Forwarding is handled by the LauncherInterface
process->setProcessChannelMode(packet.processChannelMode == QProcess::MergedChannels
? QProcess::MergedChannels : QProcess::SeparateChannels);
process->setStandardInputFile(packet.standardInputFile);
ProcessStartHandler *handler = process->processStartHandler();
handler->setWindowsSpecificStartupFlags(packet.belowNormalPriority,
packet.createConsoleOnWindows,
packet.forceDefaultErrorMode);
handler->setProcessMode(packet.processMode);
handler->setWriteData(packet.writeData);
handler->setNativeArguments(packet.nativeArguments);
if (packet.lowPriority)
process->setLowPriority();
if (packet.unixTerminalDisabled)
process->setUnixTerminalDisabled();
process->setUseCtrlCStub(packet.useCtrlCStub);
process->setReaperTimeout(packet.reaperTimeout);
process->start(packet.command, packet.arguments, handler->openMode());
handler->handleProcessStart();
}
void LauncherSocketHandler::handleWritePacket()
{
ProcessWithToken * const process = m_processes.value(m_packetParser.token());
if (!process) {
logWarn("Got write request for unknown process");
return;
}
if (process->state() != QProcess::Running) {
logDebug("Can't write into not running process");
return;
}
const auto packet = LauncherPacket::extractPacket<WritePacket>(
m_packetParser.token(),
m_packetParser.packetData());
process->write(packet.inputData);
}
void LauncherSocketHandler::handleControlPacket()
{
ProcessWithToken * const process = m_processes.value(m_packetParser.token());
if (!process) {
// This can happen when the process finishes on its own at about the same time the client
// sends the request. In this case the process was already deleted.
logDebug("Got stop request for unknown process");
return;
}
const auto packet = LauncherPacket::extractPacket<ControlProcessPacket>(
m_packetParser.token(),
m_packetParser.packetData());
switch (packet.signalType) {
case ControlProcessPacket::SignalType::Terminate:
process->terminate();
break;
case ControlProcessPacket::SignalType::Kill:
process->kill();
break;
case ControlProcessPacket::SignalType::Close:
removeProcess(process->token());
break;
case ControlProcessPacket::SignalType::CloseWriteChannel:
process->closeWriteChannel();
break;
}
}
void LauncherSocketHandler::handleShutdownPacket()
{
logDebug("Got shutdown request, closing down");
m_socket->disconnect();
qApp->quit();
}
void LauncherSocketHandler::sendPacket(const LauncherPacket &packet)
{
m_socket->write(packet.serialize());
}
ProcessWithToken *LauncherSocketHandler::setupProcess(quintptr token)
{
const auto p = new ProcessWithToken(token, this);
connect(p, &QProcess::started, this, [this, p] { handleProcessStarted(p); });
connect(p, &QProcess::errorOccurred, this, [this, p] { handleProcessError(p); });
connect(p, &QProcess::finished, this, [this, p] { handleProcessFinished(p); });
connect(p, &QProcess::readyReadStandardOutput,
this, [this, p] { handleReadyReadStandardOutput(p); });
connect(p, &QProcess::readyReadStandardError,
this, [this, p] { handleReadyReadStandardError(p); });
return p;
}
void LauncherSocketHandler::removeProcess(quintptr token)
{
const auto it = m_processes.constFind(token);
if (it == m_processes.constEnd())
return;
ProcessWithToken *process = it.value();
m_processes.erase(it);
ProcessReaper::reap(process, std::chrono::milliseconds(process->reaperTimeout()));
}
} // namespace Internal
} // namespace Utils
#include "launchersockethandler.moc"

View File

@@ -1,57 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <utils/launcherpackets.h>
#include <QByteArray>
#include <QHash>
#include <QObject>
QT_BEGIN_NAMESPACE
class QLocalSocket;
QT_END_NAMESPACE
namespace Utils {
namespace Internal {
class ProcessWithToken;
class LauncherSocketHandler : public QObject
{
Q_OBJECT
public:
explicit LauncherSocketHandler(QString socketPath, QObject *parent = nullptr);
~LauncherSocketHandler() override;
void start();
private:
void handleSocketData();
void handleSocketError();
void handleSocketClosed();
void handleProcessStarted(ProcessWithToken *process);
void handleProcessError(ProcessWithToken *process);
void handleProcessFinished(ProcessWithToken *process);
void handleReadyReadStandardOutput(ProcessWithToken *process);
void handleReadyReadStandardError(ProcessWithToken *process);
void handleStartPacket();
void handleWritePacket();
void handleControlPacket();
void handleShutdownPacket();
void sendPacket(const LauncherPacket &packet);
ProcessWithToken *setupProcess(quintptr token);
void removeProcess(quintptr token);
const QString m_serverPath;
QLocalSocket * const m_socket;
PacketParser m_packetParser;
QHash<quintptr, ProcessWithToken *> m_processes;
};
} // namespace Internal
} // namespace Utils

View File

@@ -1,40 +0,0 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "launcherlogging.h"
#include "launchersockethandler.h"
#include <utils/singleton.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qscopeguard.h>
#include <QtCore/qtimer.h>
#ifdef Q_OS_WIN
#include <QtCore/qt_windows.h>
BOOL WINAPI consoleCtrlHandler(DWORD)
{
// Ignore Ctrl-C / Ctrl-Break. QtCreator will tell us to exit gracefully.
return TRUE;
}
#endif
int main(int argc, char *argv[])
{
#ifdef Q_OS_WIN
SetConsoleCtrlHandler(consoleCtrlHandler, TRUE);
#endif
QCoreApplication app(argc, argv);
if (app.arguments().size() != 2) {
Utils::Internal::logError("Need exactly one argument (path to socket)");
return 1;
}
const QScopeGuard cleanup([] { Utils::Singleton::deleteAll(); });
Utils::Internal::LauncherSocketHandler launcher(app.arguments().constLast());
QTimer::singleShot(0, &launcher, &Utils::Internal::LauncherSocketHandler::start);
return app.exec();
}

View File

@@ -1,46 +0,0 @@
import qbs
import qbs.FileInfo
QtcTool {
name: "qtcreator_processlauncher"
Depends { name: "Qt.network" }
cpp.defines: base.concat("UTILS_STATIC_LIBRARY")
cpp.includePaths: base.concat(pathToLibs)
Properties {
condition: qbs.targetOS.contains("windows")
cpp.dynamicLibraries: qbs.toolchain.contains("msvc") ? ["user32", "dbghelp"] : ["user32"]
}
files: [
"launcherlogging.cpp",
"launcherlogging.h",
"launchersockethandler.cpp",
"launchersockethandler.h",
"processlauncher-main.cpp",
]
property string pathToLibs: sourceDirectory + "/../../libs"
property string pathToUtils: sourceDirectory + "/../../libs/utils"
Group {
name: "protocol sources"
prefix: pathToUtils + '/'
files: [
"launcherpackets.cpp",
"launcherpackets.h",
"processenums.h",
"processhelper.cpp",
"processhelper.h",
"processreaper.cpp",
"processreaper.h",
"qtcassert.cpp",
"qtcassert.h",
"singleton.cpp",
"singleton.h",
"threadutils.cpp",
"threadutils.h",
]
}
}

View File

@@ -8,7 +8,6 @@ Project {
"cplusplustools.qbs",
"disclaim/disclaim.qbs",
"process_stub/process_stub.qbs",
"processlauncher/processlauncher.qbs",
"qml2puppet/qml2puppet.qbs",
"qtcdebugger/qtcdebugger.qbs",
"qtcreatorcrashhandler/qtcreatorcrashhandler.qbs",

View File

@@ -6,10 +6,9 @@
#include <utils/commandline.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/launcherinterface.h>
#include <utils/macroexpander.h>
#include <utils/qtcprocess.h>
#include <utils/processinterface.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
#include <QObject>
@@ -43,10 +42,6 @@ private slots:
TemporaryDirectory::setMasterTemporaryDirectory(
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
LauncherInterface::setPathToLauncher(libExecPath);
testEnv.set("TEST_ECHO", "1");
if (HostOsInfo::isWindowsHost())

View File

@@ -7,8 +7,8 @@
#include <utils/deviceshell.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/launcherinterface.h>
#include <utils/qtcprocess.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
#include <QObject>
@@ -71,10 +71,6 @@ private slots:
TemporaryDirectory::setMasterTemporaryDirectory(
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
LauncherInterface::setPathToLauncher(libExecPath);
std::iota(m_asciiTestData.begin(), m_asciiTestData.end(), 0);
const FilePath dockerExecutable = Environment::systemEnvironment()

View File

@@ -5,8 +5,8 @@
#include <app/app_version.h>
#include <utils/launcherinterface.h>
#include <utils/qtcprocess.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
#include <QCoreApplication>
@@ -32,9 +32,6 @@ int main(int argc, char **argv)
TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/"
+ Core::Constants::IDE_CASED_ID + "-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
LauncherInterface::setPathToLauncher(libExecPath);
SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP));
QMetaObject::invokeMethod(&app, [] { ProcessTestApp::invokeSubProcess(); }, Qt::QueuedConnection);

View File

@@ -8,7 +8,6 @@
#include <utils/async.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/launcherinterface.h>
#include <utils/qtcprocess.h>
#include <utils/processinfo.h>
#include <utils/processinterface.h>
@@ -21,9 +20,6 @@
#include <QRegularExpression>
#include <QtTest>
#include <iostream>
#include <fstream>
using namespace Utils;
using namespace std::chrono;
@@ -167,9 +163,6 @@ void tst_Process::initTestCase()
msgHandler = new MessageHandler;
TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/"
+ Core::Constants::IDE_CASED_ID + "-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
LauncherInterface::setPathToLauncher(libExecPath);
SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP));
homeStr = QLatin1String("@HOME@");
@@ -1286,29 +1279,20 @@ void tst_Process::stdinToShell()
void tst_Process::eventLoopMode_data()
{
QTest::addColumn<ProcessImpl>("processImpl");
QTest::addColumn<EventLoopMode>("eventLoopMode");
QTest::newRow("QProcess, blocking with event loop")
<< ProcessImpl::QProcess << EventLoopMode::On;
QTest::newRow("QProcess, blocking without event loop")
<< ProcessImpl::QProcess << EventLoopMode::Off;
QTest::newRow("ProcessLauncher, blocking with event loop")
<< ProcessImpl::ProcessLauncher << EventLoopMode::On;
QTest::newRow("ProcessLauncher, blocking without event loop")
<< ProcessImpl::ProcessLauncher << EventLoopMode::Off;
QTest::newRow("EventLoopMode::On") << EventLoopMode::On;
QTest::newRow("EventLoopMode::Off") << EventLoopMode::Off;
}
void tst_Process::eventLoopMode()
{
QFETCH(ProcessImpl, processImpl);
QFETCH(EventLoopMode, eventLoopMode);
{
SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {});
Process process;
subConfig.setupSubProcess(&process);
process.setProcessImpl(processImpl);
process.runBlocking(10s, eventLoopMode);
QCOMPARE(process.result(), ProcessResult::FinishedWithSuccess);
}
@@ -1317,7 +1301,6 @@ void tst_Process::eventLoopMode()
Process process;
process.setCommand(
CommandLine{"there_is_a_big_chance_that_executable_with_that_name_does_not_exists"});
process.setProcessImpl(processImpl);
process.runBlocking(10s, eventLoopMode);
QCOMPARE(process.result(), ProcessResult::StartFailed);
}

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <utils/commandline.h>
#include <utils/launcherinterface.h>
#include <utils/temporarydirectory.h>
#include <valgrind/valgrindprocess.h>
@@ -24,9 +23,6 @@ int main(int argc, char *argv[])
QApplication app(argc, argv);
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/QtCreator-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
Utils::LauncherInterface::setPathToLauncher(libExecPath);
qRegisterMetaType<Error>();

View File

@@ -8,8 +8,8 @@
#include <utils/devicefileaccess.h>
#include <utils/environment.h>
#include <utils/launcherinterface.h>
#include <utils/qtcprocess.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
#include <QObject>
@@ -60,9 +60,6 @@ private slots:
{
TemporaryDirectory::setMasterTemporaryDirectory(
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
libExecPath = qApp->applicationDirPath() + '/' + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH);
LauncherInterface::setPathToLauncher(libExecPath);
}
void cleanupTestCase() { Singleton::deleteAll(); }

View File

@@ -5,8 +5,8 @@
#include <utils/deviceshell.h>
#include <utils/environment.h>
#include <utils/launcherinterface.h>
#include <utils/qtcprocess.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
#include <QObject>
@@ -131,10 +131,6 @@ private slots:
TemporaryDirectory::setMasterTemporaryDirectory(
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
LauncherInterface::setPathToLauncher(libExecPath);
if (TestShell::cmdLine().isEmpty()) {
QSKIP("Skipping deviceshell tests, as no compatible shell could be found");
}

View File

@@ -5,8 +5,8 @@
#include <utils/async.h>
#include <utils/filesearch.h>
#include <utils/launcherinterface.h>
#include <utils/scopedtimer.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
#include <QDirIterator>
@@ -114,10 +114,6 @@ private slots:
TemporaryDirectory::setMasterTemporaryDirectory(
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
LauncherInterface::setPathToLauncher(libExecPath);
qDebug() << "This manual test compares the performance of the SubDirFileContainer with a "
"manually written iterator using QDir::entryInfoList() and with QDirIterator.";
QTC_SCOPED_TIMER("GENERATING TEMPORARY FILES TREE");

View File

@@ -7,7 +7,6 @@
#include <sqlitelibraryinitializer.h>
#include <sqliteglobal.h>
#include <utils/launcherinterface.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h>
@@ -38,8 +37,6 @@ int main(int argc, char *argv[])
Sqlite::Database::activateLogging();
QGuiApplication application(argc, argv);
Utils::LauncherInterface::setPathToLauncher(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
testing::InitGoogleTest(&argc, argv);
#ifdef WITH_BENCHMARKS
benchmark::Initialize(&argc, argv);