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, "hidden" : true,
"cacheVariables": { "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_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" "WITH_QMLDESIGNER": "OFF"
} }
} }

View File

@@ -90,9 +90,6 @@ add_qtc_library(Utils
infolabel.cpp infolabel.h infolabel.cpp infolabel.h
itemviews.cpp itemviews.h itemviews.cpp itemviews.h
jsontreeitem.cpp jsontreeitem.h jsontreeitem.cpp jsontreeitem.h
launcherinterface.cpp launcherinterface.h
launcherpackets.cpp launcherpackets.h
launchersocket.cpp launchersocket.h
layoutbuilder.cpp layoutbuilder.h layoutbuilder.cpp layoutbuilder.h
link.cpp link.h link.cpp link.h
listmodel.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 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 { enum class TerminalMode {
Off, Off,
Run, // Start with process stub enabled Run, // Start with process stub enabled

View File

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

View File

@@ -7,9 +7,8 @@
#include "environment.h" #include "environment.h"
#include "guard.h" #include "guard.h"
#include "hostosinfo.h" #include "hostosinfo.h"
#include "launcherinterface.h"
#include "launchersocket.h"
#include "processhelper.h" #include "processhelper.h"
#include "processinterface.h"
#include "processreaper.h" #include "processreaper.h"
#include "stringutils.h" #include "stringutils.h"
#include "terminalhooks.h" #include "terminalhooks.h"
@@ -28,6 +27,7 @@
#include <QTextCodec> #include <QTextCodec>
#include <QThread> #include <QThread>
#include <QTimer> #include <QTimer>
#include <QWaitCondition>
#ifdef QT_GUI_LIB #ifdef QT_GUI_LIB
// qmlpuppet does not use that. // qmlpuppet does not use that.
@@ -40,7 +40,6 @@
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <limits>
#include <memory> #include <memory>
using namespace Utils::Internal; using namespace Utils::Internal;
@@ -300,38 +299,6 @@ bool DefaultImpl::ensureProgramExists(const QString &program)
return false; 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 class PtyProcessImpl final : public DefaultImpl
{ {
public: public:
@@ -555,113 +522,6 @@ private:
// QProcessBlockingImpl *m_blockingImpl = nullptr; // 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 class ProcessInterfaceSignal
{ {
public: public:
@@ -799,12 +659,7 @@ public:
return new PtyProcessImpl; return new PtyProcessImpl;
if (m_setup.m_terminalMode != TerminalMode::Off) if (m_setup.m_terminalMode != TerminalMode::Off)
return Terminal::Hooks::instance().createTerminalProcessInterface(); 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 QProcessImpl;
return new ProcessLauncherImpl;
} }
void setProcessInterface(ProcessInterface *process) void setProcessInterface(ProcessInterface *process)
@@ -820,8 +675,6 @@ public:
connect(m_process.get(), &ProcessInterface::done, connect(m_process.get(), &ProcessInterface::done,
this, &ProcessPrivate::handleDone); this, &ProcessPrivate::handleDone);
m_blockingInterface.reset(process->processBlockingInterface());
if (!m_blockingInterface)
m_blockingInterface.reset(new GeneralProcessBlockingImpl(this)); m_blockingInterface.reset(new GeneralProcessBlockingImpl(this));
m_blockingInterface->setParent(this); m_blockingInterface->setParent(this);
} }
@@ -1157,11 +1010,6 @@ Process::~Process()
delete d; delete d;
} }
void Process::setProcessImpl(ProcessImpl processImpl)
{
d->m_setup.m_processImpl = processImpl;
}
void Process::setPtyData(const std::optional<Pty::Data> &data) void Process::setPtyData(const std::optional<Pty::Data> &data)
{ {
d->m_setup.m_ptyData = data; d->m_setup.m_ptyData = data;
@@ -2200,5 +2048,3 @@ void ProcessTaskAdapter::start()
} }
} // namespace Utils } // namespace Utils
#include "qtcprocess.moc"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -26,7 +26,6 @@ if (APPLE)
add_subdirectory(iostool) add_subdirectory(iostool)
endif() endif()
add_subdirectory(processlauncher)
if (WITH_QMLDESIGNER) if (WITH_QMLDESIGNER)
add_subdirectory(qml2puppet) add_subdirectory(qml2puppet)
add_subdirectory(sqlitetester) 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", "cplusplustools.qbs",
"disclaim/disclaim.qbs", "disclaim/disclaim.qbs",
"process_stub/process_stub.qbs", "process_stub/process_stub.qbs",
"processlauncher/processlauncher.qbs",
"qml2puppet/qml2puppet.qbs", "qml2puppet/qml2puppet.qbs",
"qtcdebugger/qtcdebugger.qbs", "qtcdebugger/qtcdebugger.qbs",
"qtcreatorcrashhandler/qtcreatorcrashhandler.qbs", "qtcreatorcrashhandler/qtcreatorcrashhandler.qbs",

View File

@@ -6,10 +6,9 @@
#include <utils/commandline.h> #include <utils/commandline.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/launcherinterface.h>
#include <utils/macroexpander.h> #include <utils/macroexpander.h>
#include <utils/qtcprocess.h> #include <utils/qtcprocess.h>
#include <utils/processinterface.h> #include <utils/singleton.h>
#include <utils/temporarydirectory.h> #include <utils/temporarydirectory.h>
#include <QObject> #include <QObject>
@@ -43,10 +42,6 @@ private slots:
TemporaryDirectory::setMasterTemporaryDirectory( TemporaryDirectory::setMasterTemporaryDirectory(
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX"); 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"); testEnv.set("TEST_ECHO", "1");
if (HostOsInfo::isWindowsHost()) if (HostOsInfo::isWindowsHost())

View File

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

View File

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

View File

@@ -8,7 +8,6 @@
#include <utils/async.h> #include <utils/async.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/launcherinterface.h>
#include <utils/qtcprocess.h> #include <utils/qtcprocess.h>
#include <utils/processinfo.h> #include <utils/processinfo.h>
#include <utils/processinterface.h> #include <utils/processinterface.h>
@@ -21,9 +20,6 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <QtTest> #include <QtTest>
#include <iostream>
#include <fstream>
using namespace Utils; using namespace Utils;
using namespace std::chrono; using namespace std::chrono;
@@ -167,9 +163,6 @@ void tst_Process::initTestCase()
msgHandler = new MessageHandler; msgHandler = new MessageHandler;
TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/" TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/"
+ Core::Constants::IDE_CASED_ID + "-XXXXXX"); + Core::Constants::IDE_CASED_ID + "-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
LauncherInterface::setPathToLauncher(libExecPath);
SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP)); SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP));
homeStr = QLatin1String("@HOME@"); homeStr = QLatin1String("@HOME@");
@@ -1286,29 +1279,20 @@ void tst_Process::stdinToShell()
void tst_Process::eventLoopMode_data() void tst_Process::eventLoopMode_data()
{ {
QTest::addColumn<ProcessImpl>("processImpl");
QTest::addColumn<EventLoopMode>("eventLoopMode"); QTest::addColumn<EventLoopMode>("eventLoopMode");
QTest::newRow("QProcess, blocking with event loop") QTest::newRow("EventLoopMode::On") << EventLoopMode::On;
<< ProcessImpl::QProcess << EventLoopMode::On; QTest::newRow("EventLoopMode::Off") << EventLoopMode::Off;
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;
} }
void tst_Process::eventLoopMode() void tst_Process::eventLoopMode()
{ {
QFETCH(ProcessImpl, processImpl);
QFETCH(EventLoopMode, eventLoopMode); QFETCH(EventLoopMode, eventLoopMode);
{ {
SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {}); SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {});
Process process; Process process;
subConfig.setupSubProcess(&process); subConfig.setupSubProcess(&process);
process.setProcessImpl(processImpl);
process.runBlocking(10s, eventLoopMode); process.runBlocking(10s, eventLoopMode);
QCOMPARE(process.result(), ProcessResult::FinishedWithSuccess); QCOMPARE(process.result(), ProcessResult::FinishedWithSuccess);
} }
@@ -1317,7 +1301,6 @@ void tst_Process::eventLoopMode()
Process process; Process process;
process.setCommand( process.setCommand(
CommandLine{"there_is_a_big_chance_that_executable_with_that_name_does_not_exists"}); CommandLine{"there_is_a_big_chance_that_executable_with_that_name_does_not_exists"});
process.setProcessImpl(processImpl);
process.runBlocking(10s, eventLoopMode); process.runBlocking(10s, eventLoopMode);
QCOMPARE(process.result(), ProcessResult::StartFailed); 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 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <utils/commandline.h> #include <utils/commandline.h>
#include <utils/launcherinterface.h>
#include <utils/temporarydirectory.h> #include <utils/temporarydirectory.h>
#include <valgrind/valgrindprocess.h> #include <valgrind/valgrindprocess.h>
@@ -24,9 +23,6 @@ int main(int argc, char *argv[])
QApplication app(argc, argv); QApplication app(argc, argv);
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/QtCreator-XXXXXX"); Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/QtCreator-XXXXXX");
const QString libExecPath(qApp->applicationDirPath() + '/'
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
Utils::LauncherInterface::setPathToLauncher(libExecPath);
qRegisterMetaType<Error>(); qRegisterMetaType<Error>();

View File

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

View File

@@ -5,8 +5,8 @@
#include <utils/deviceshell.h> #include <utils/deviceshell.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/launcherinterface.h>
#include <utils/qtcprocess.h> #include <utils/qtcprocess.h>
#include <utils/singleton.h>
#include <utils/temporarydirectory.h> #include <utils/temporarydirectory.h>
#include <QObject> #include <QObject>
@@ -131,10 +131,6 @@ private slots:
TemporaryDirectory::setMasterTemporaryDirectory( TemporaryDirectory::setMasterTemporaryDirectory(
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX"); 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()) { if (TestShell::cmdLine().isEmpty()) {
QSKIP("Skipping deviceshell tests, as no compatible shell could be found"); QSKIP("Skipping deviceshell tests, as no compatible shell could be found");
} }

View File

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

View File

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