forked from qt-creator/qt-creator
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:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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"
|
@@ -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
|
@@ -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
|
@@ -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);
|
@@ -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
|
@@ -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
|
@@ -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
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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;
|
||||
return new QProcessImpl;
|
||||
}
|
||||
|
||||
void setProcessInterface(ProcessInterface *process)
|
||||
@@ -820,9 +675,7 @@ 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.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"
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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",
|
||||
|
@@ -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);
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
@@ -26,7 +26,6 @@ if (APPLE)
|
||||
add_subdirectory(iostool)
|
||||
endif()
|
||||
|
||||
add_subdirectory(processlauncher)
|
||||
if (WITH_QMLDESIGNER)
|
||||
add_subdirectory(qml2puppet)
|
||||
add_subdirectory(sqlitetester)
|
||||
|
@@ -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}
|
||||
)
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -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"
|
@@ -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
|
@@ -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();
|
||||
}
|
@@ -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",
|
||||
]
|
||||
}
|
||||
}
|
@@ -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",
|
||||
|
@@ -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())
|
||||
|
@@ -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()
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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>();
|
||||
|
||||
|
@@ -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(); }
|
||||
|
@@ -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");
|
||||
}
|
||||
|
@@ -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");
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user