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,
|
"hidden" : true,
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"BUILD_PLUGINS": "Core;Designer;DiffEditor;TextEditor;ProjectExplorer;CppEditor;CodePaster;Docker;Git;Help;QmakeProjectManager;CMakeProjectManager;ClangCodeModel;ClangTools;ClangFormat;Debugger;QtSupport;ResourceEditor;VcsBase;Welcome;LanguageClient;RemoteLinux",
|
"BUILD_PLUGINS": "Core;Designer;DiffEditor;TextEditor;ProjectExplorer;CppEditor;CodePaster;Docker;Git;Help;QmakeProjectManager;CMakeProjectManager;ClangCodeModel;ClangTools;ClangFormat;Debugger;QtSupport;ResourceEditor;VcsBase;Welcome;LanguageClient;RemoteLinux",
|
||||||
"BUILD_EXECUTABLES": "QtCreator;qtcreator_ctrlc_stub;qtcreator_process_stub;win64interrupt;qtcreator_processlauncher",
|
"BUILD_EXECUTABLES": "QtCreator;qtcreator_ctrlc_stub;qtcreator_process_stub;win64interrupt",
|
||||||
"WITH_QMLDESIGNER": "OFF"
|
"WITH_QMLDESIGNER": "OFF"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -90,9 +90,6 @@ add_qtc_library(Utils
|
|||||||
infolabel.cpp infolabel.h
|
infolabel.cpp infolabel.h
|
||||||
itemviews.cpp itemviews.h
|
itemviews.cpp itemviews.h
|
||||||
jsontreeitem.cpp jsontreeitem.h
|
jsontreeitem.cpp jsontreeitem.h
|
||||||
launcherinterface.cpp launcherinterface.h
|
|
||||||
launcherpackets.cpp launcherpackets.h
|
|
||||||
launchersocket.cpp launchersocket.h
|
|
||||||
layoutbuilder.cpp layoutbuilder.h
|
layoutbuilder.cpp layoutbuilder.h
|
||||||
link.cpp link.h
|
link.cpp link.h
|
||||||
listmodel.h
|
listmodel.h
|
||||||
|
@@ -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
|
Writer // This opens in ReadWrite mode and doesn't close the write channel
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ProcessImpl {
|
|
||||||
QProcess,
|
|
||||||
ProcessLauncher,
|
|
||||||
Default // Defaults to ProcessLauncherImpl, if QTC_USE_QPROCESS env var is set
|
|
||||||
// it equals to QProcessImpl.
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class TerminalMode {
|
enum class TerminalMode {
|
||||||
Off,
|
Off,
|
||||||
Run, // Start with process stub enabled
|
Run, // Start with process stub enabled
|
||||||
|
@@ -71,7 +71,6 @@ public:
|
|||||||
class QTCREATOR_UTILS_EXPORT ProcessSetupData
|
class QTCREATOR_UTILS_EXPORT ProcessSetupData
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ProcessImpl m_processImpl = ProcessImpl::Default;
|
|
||||||
ProcessMode m_processMode = ProcessMode::Reader;
|
ProcessMode m_processMode = ProcessMode::Reader;
|
||||||
TerminalMode m_terminalMode = TerminalMode::Off;
|
TerminalMode m_terminalMode = TerminalMode::Off;
|
||||||
|
|
||||||
@@ -165,8 +164,6 @@ private:
|
|||||||
// It's being called in Starting or Running state.
|
// It's being called in Starting or Running state.
|
||||||
virtual void sendControlSignal(ControlSignal controlSignal) = 0;
|
virtual void sendControlSignal(ControlSignal controlSignal) = 0;
|
||||||
|
|
||||||
virtual ProcessBlockingInterface *processBlockingInterface() const { return nullptr; }
|
|
||||||
|
|
||||||
friend class Process;
|
friend class Process;
|
||||||
friend class Internal::ProcessPrivate;
|
friend class Internal::ProcessPrivate;
|
||||||
};
|
};
|
||||||
|
@@ -7,9 +7,8 @@
|
|||||||
#include "environment.h"
|
#include "environment.h"
|
||||||
#include "guard.h"
|
#include "guard.h"
|
||||||
#include "hostosinfo.h"
|
#include "hostosinfo.h"
|
||||||
#include "launcherinterface.h"
|
|
||||||
#include "launchersocket.h"
|
|
||||||
#include "processhelper.h"
|
#include "processhelper.h"
|
||||||
|
#include "processinterface.h"
|
||||||
#include "processreaper.h"
|
#include "processreaper.h"
|
||||||
#include "stringutils.h"
|
#include "stringutils.h"
|
||||||
#include "terminalhooks.h"
|
#include "terminalhooks.h"
|
||||||
@@ -28,6 +27,7 @@
|
|||||||
#include <QTextCodec>
|
#include <QTextCodec>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QWaitCondition>
|
||||||
|
|
||||||
#ifdef QT_GUI_LIB
|
#ifdef QT_GUI_LIB
|
||||||
// qmlpuppet does not use that.
|
// qmlpuppet does not use that.
|
||||||
@@ -40,7 +40,6 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <limits>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
using namespace Utils::Internal;
|
using namespace Utils::Internal;
|
||||||
@@ -300,38 +299,6 @@ bool DefaultImpl::ensureProgramExists(const QString &program)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove QProcessBlockingImpl later, after Creator 13.0 is released at least.
|
|
||||||
|
|
||||||
// Rationale: QProcess::waitForReadyRead() waits only for one channel, either stdOut or stdErr.
|
|
||||||
// Since we can't predict where the data will come first,
|
|
||||||
// setting the QProcess::setReadChannel() in advance is a mis-design of the QProcess API.
|
|
||||||
// This issue does not affect GeneralProcessBlockingImpl, but it might be not as optimal
|
|
||||||
// as QProcessBlockingImpl. However, since we are blocking the caller thread anyway,
|
|
||||||
// the small overhead in speed doesn't play the most significant role, thus the proper
|
|
||||||
// behavior of Process::waitForReadyRead(), which listens to both channels, wins.
|
|
||||||
|
|
||||||
// class QProcessBlockingImpl : public ProcessBlockingInterface
|
|
||||||
// {
|
|
||||||
// public:
|
|
||||||
// QProcessBlockingImpl(QProcess *process) : m_process(process) {}
|
|
||||||
|
|
||||||
// private:
|
|
||||||
// bool waitForSignal(ProcessSignalType signalType, int msecs) final
|
|
||||||
// {
|
|
||||||
// switch (signalType) {
|
|
||||||
// case ProcessSignalType::Started:
|
|
||||||
// return m_process->waitForStarted(msecs);
|
|
||||||
// case ProcessSignalType::ReadyRead:
|
|
||||||
// return m_process->waitForReadyRead(msecs);
|
|
||||||
// case ProcessSignalType::Done:
|
|
||||||
// return m_process->waitForFinished(msecs);
|
|
||||||
// }
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// QProcess *m_process = nullptr;
|
|
||||||
// };
|
|
||||||
|
|
||||||
class PtyProcessImpl final : public DefaultImpl
|
class PtyProcessImpl final : public DefaultImpl
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -555,113 +522,6 @@ private:
|
|||||||
// QProcessBlockingImpl *m_blockingImpl = nullptr;
|
// QProcessBlockingImpl *m_blockingImpl = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint uniqueToken()
|
|
||||||
{
|
|
||||||
static std::atomic_uint globalUniqueToken = 0;
|
|
||||||
return ++globalUniqueToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProcessLauncherBlockingImpl : public ProcessBlockingInterface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ProcessLauncherBlockingImpl(CallerHandle *caller) : m_caller(caller) {}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool waitForSignal(ProcessSignalType signalType, QDeadlineTimer timeout) final
|
|
||||||
{
|
|
||||||
// TODO: Remove CallerHandle::SignalType
|
|
||||||
const CallerHandle::SignalType type = [signalType] {
|
|
||||||
switch (signalType) {
|
|
||||||
case ProcessSignalType::Started:
|
|
||||||
return CallerHandle::SignalType::Started;
|
|
||||||
case ProcessSignalType::ReadyRead:
|
|
||||||
return CallerHandle::SignalType::ReadyRead;
|
|
||||||
case ProcessSignalType::Done:
|
|
||||||
return CallerHandle::SignalType::Done;
|
|
||||||
}
|
|
||||||
QTC_CHECK(false);
|
|
||||||
return CallerHandle::SignalType::NoSignal;
|
|
||||||
}();
|
|
||||||
return m_caller->waitForSignal(type, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
CallerHandle *m_caller = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ProcessLauncherImpl final : public DefaultImpl
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
ProcessLauncherImpl() : m_token(uniqueToken())
|
|
||||||
{
|
|
||||||
m_handle = LauncherInterface::registerHandle(this, token());
|
|
||||||
m_handle->setProcessSetupData(&m_setup);
|
|
||||||
connect(m_handle, &CallerHandle::started,
|
|
||||||
this, &ProcessInterface::started);
|
|
||||||
connect(m_handle, &CallerHandle::readyRead,
|
|
||||||
this, &ProcessInterface::readyRead);
|
|
||||||
connect(m_handle, &CallerHandle::done,
|
|
||||||
this, &ProcessInterface::done);
|
|
||||||
m_blockingImpl = new ProcessLauncherBlockingImpl(m_handle);
|
|
||||||
}
|
|
||||||
~ProcessLauncherImpl() final
|
|
||||||
{
|
|
||||||
m_handle->close();
|
|
||||||
LauncherInterface::unregisterHandle(token());
|
|
||||||
m_handle = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
qint64 write(const QByteArray &data) final { return m_handle->write(data); }
|
|
||||||
void sendControlSignal(ControlSignal controlSignal) final {
|
|
||||||
switch (controlSignal) {
|
|
||||||
case ControlSignal::Terminate:
|
|
||||||
m_handle->terminate();
|
|
||||||
break;
|
|
||||||
case ControlSignal::Kill:
|
|
||||||
m_handle->kill();
|
|
||||||
break;
|
|
||||||
case ControlSignal::Interrupt:
|
|
||||||
ProcessHelper::interruptPid(m_handle->processId());
|
|
||||||
break;
|
|
||||||
case ControlSignal::KickOff:
|
|
||||||
QTC_CHECK(false);
|
|
||||||
break;
|
|
||||||
case ControlSignal::CloseWriteChannel:
|
|
||||||
m_handle->closeWriteChannel();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessBlockingInterface *processBlockingInterface() const override { return m_blockingImpl; }
|
|
||||||
|
|
||||||
void doDefaultStart(const QString &program, const QStringList &arguments) final
|
|
||||||
{
|
|
||||||
m_handle->start(program, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
quintptr token() const { return m_token; }
|
|
||||||
|
|
||||||
const uint m_token = 0;
|
|
||||||
// Lives in caller's thread.
|
|
||||||
CallerHandle *m_handle = nullptr;
|
|
||||||
ProcessLauncherBlockingImpl *m_blockingImpl = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
static ProcessImpl defaultProcessImplHelper()
|
|
||||||
{
|
|
||||||
const QString value = qtcEnvironmentVariable("QTC_USE_QPROCESS", "TRUE").toUpper();
|
|
||||||
if (value != "FALSE" && value != "0")
|
|
||||||
return ProcessImpl::QProcess;
|
|
||||||
return ProcessImpl::ProcessLauncher;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ProcessImpl defaultProcessImpl()
|
|
||||||
{
|
|
||||||
static const ProcessImpl impl = defaultProcessImplHelper();
|
|
||||||
return impl;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProcessInterfaceSignal
|
class ProcessInterfaceSignal
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -799,12 +659,7 @@ public:
|
|||||||
return new PtyProcessImpl;
|
return new PtyProcessImpl;
|
||||||
if (m_setup.m_terminalMode != TerminalMode::Off)
|
if (m_setup.m_terminalMode != TerminalMode::Off)
|
||||||
return Terminal::Hooks::instance().createTerminalProcessInterface();
|
return Terminal::Hooks::instance().createTerminalProcessInterface();
|
||||||
|
|
||||||
const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default
|
|
||||||
? defaultProcessImpl() : m_setup.m_processImpl;
|
|
||||||
if (impl == ProcessImpl::QProcess)
|
|
||||||
return new QProcessImpl;
|
return new QProcessImpl;
|
||||||
return new ProcessLauncherImpl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setProcessInterface(ProcessInterface *process)
|
void setProcessInterface(ProcessInterface *process)
|
||||||
@@ -820,8 +675,6 @@ public:
|
|||||||
connect(m_process.get(), &ProcessInterface::done,
|
connect(m_process.get(), &ProcessInterface::done,
|
||||||
this, &ProcessPrivate::handleDone);
|
this, &ProcessPrivate::handleDone);
|
||||||
|
|
||||||
m_blockingInterface.reset(process->processBlockingInterface());
|
|
||||||
if (!m_blockingInterface)
|
|
||||||
m_blockingInterface.reset(new GeneralProcessBlockingImpl(this));
|
m_blockingInterface.reset(new GeneralProcessBlockingImpl(this));
|
||||||
m_blockingInterface->setParent(this);
|
m_blockingInterface->setParent(this);
|
||||||
}
|
}
|
||||||
@@ -1157,11 +1010,6 @@ Process::~Process()
|
|||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Process::setProcessImpl(ProcessImpl processImpl)
|
|
||||||
{
|
|
||||||
d->m_setup.m_processImpl = processImpl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Process::setPtyData(const std::optional<Pty::Data> &data)
|
void Process::setPtyData(const std::optional<Pty::Data> &data)
|
||||||
{
|
{
|
||||||
d->m_setup.m_ptyData = data;
|
d->m_setup.m_ptyData = data;
|
||||||
@@ -2200,5 +2048,3 @@ void ProcessTaskAdapter::start()
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
|
||||||
#include "qtcprocess.moc"
|
|
||||||
|
@@ -91,8 +91,6 @@ public:
|
|||||||
void setControlEnvironment(const Environment &env); // Possible helper process (ssh on host etc)
|
void setControlEnvironment(const Environment &env); // Possible helper process (ssh on host etc)
|
||||||
const Environment &controlEnvironment() const;
|
const Environment &controlEnvironment() const;
|
||||||
|
|
||||||
void setProcessImpl(ProcessImpl processImpl);
|
|
||||||
|
|
||||||
void setPtyData(const std::optional<Pty::Data> &data);
|
void setPtyData(const std::optional<Pty::Data> &data);
|
||||||
std::optional<Pty::Data> ptyData() const;
|
std::optional<Pty::Data> ptyData() const;
|
||||||
|
|
||||||
|
@@ -177,12 +177,6 @@ QtcLibrary {
|
|||||||
"itemviews.h",
|
"itemviews.h",
|
||||||
"jsontreeitem.cpp",
|
"jsontreeitem.cpp",
|
||||||
"jsontreeitem.h",
|
"jsontreeitem.h",
|
||||||
"launcherinterface.cpp",
|
|
||||||
"launcherinterface.h",
|
|
||||||
"launcherpackets.cpp",
|
|
||||||
"launcherpackets.h",
|
|
||||||
"launchersocket.cpp",
|
|
||||||
"launchersocket.h",
|
|
||||||
"layoutbuilder.cpp",
|
"layoutbuilder.cpp",
|
||||||
"layoutbuilder.h",
|
"layoutbuilder.h",
|
||||||
"link.cpp",
|
"link.cpp",
|
||||||
|
@@ -400,7 +400,6 @@ DockerProcessImpl::~DockerProcessImpl()
|
|||||||
|
|
||||||
void DockerProcessImpl::start()
|
void DockerProcessImpl::start()
|
||||||
{
|
{
|
||||||
m_process.setProcessImpl(m_setup.m_processImpl);
|
|
||||||
m_process.setProcessMode(m_setup.m_processMode);
|
m_process.setProcessMode(m_setup.m_processMode);
|
||||||
m_process.setTerminalMode(m_setup.m_terminalMode);
|
m_process.setTerminalMode(m_setup.m_terminalMode);
|
||||||
m_process.setPtyData(m_setup.m_ptyData);
|
m_process.setPtyData(m_setup.m_ptyData);
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
#include "../mesoninfoparser.h"
|
#include "../mesoninfoparser.h"
|
||||||
#include "../mesontools.h"
|
#include "../mesontools.h"
|
||||||
|
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/processinterface.h>
|
#include <utils/processinterface.h>
|
||||||
#include <utils/singleton.h>
|
#include <utils/singleton.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
@@ -40,8 +39,6 @@ private slots:
|
|||||||
{
|
{
|
||||||
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath()
|
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath()
|
||||||
+ "/mesontest-XXXXXX");
|
+ "/mesontest-XXXXXX");
|
||||||
Utils::LauncherInterface::setPathToLauncher(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
|
|
||||||
const auto path = findTool(ToolType::Meson);
|
const auto path = findTool(ToolType::Meson);
|
||||||
if (!path)
|
if (!path)
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include "../mesontools.h"
|
#include "../mesontools.h"
|
||||||
|
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/processinterface.h>
|
#include <utils/processinterface.h>
|
||||||
#include <utils/singleton.h>
|
#include <utils/singleton.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
@@ -28,8 +27,6 @@ private slots:
|
|||||||
{
|
{
|
||||||
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath()
|
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath()
|
||||||
+ "/mesontest-XXXXXX");
|
+ "/mesontest-XXXXXX");
|
||||||
Utils::LauncherInterface::setPathToLauncher(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
|
|
||||||
const auto path = findTool(ToolType::Meson);
|
const auto path = findTool(ToolType::Meson);
|
||||||
if (!path)
|
if (!path)
|
||||||
|
@@ -674,7 +674,6 @@ void SshProcessInterfacePrivate::start()
|
|||||||
cmd.addArg(QString("%1:localhost:%1").arg(forwardPort));
|
cmd.addArg(QString("%1:localhost:%1").arg(forwardPort));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_process.setProcessImpl(q->m_setup.m_processImpl);
|
|
||||||
m_process.setProcessMode(q->m_setup.m_processMode);
|
m_process.setProcessMode(q->m_setup.m_processMode);
|
||||||
m_process.setTerminalMode(q->m_setup.m_terminalMode);
|
m_process.setTerminalMode(q->m_setup.m_terminalMode);
|
||||||
m_process.setPtyData(q->m_setup.m_ptyData);
|
m_process.setPtyData(q->m_setup.m_ptyData);
|
||||||
@@ -743,7 +742,6 @@ void SshProcessInterfacePrivate::clearForStart()
|
|||||||
|
|
||||||
void SshProcessInterfacePrivate::doStart()
|
void SshProcessInterfacePrivate::doStart()
|
||||||
{
|
{
|
||||||
m_process.setProcessImpl(q->m_setup.m_processImpl);
|
|
||||||
m_process.setProcessMode(q->m_setup.m_processMode);
|
m_process.setProcessMode(q->m_setup.m_processMode);
|
||||||
m_process.setTerminalMode(q->m_setup.m_terminalMode);
|
m_process.setTerminalMode(q->m_setup.m_terminalMode);
|
||||||
m_process.setPtyData(q->m_setup.m_ptyData);
|
m_process.setPtyData(q->m_setup.m_ptyData);
|
||||||
|
@@ -26,7 +26,6 @@ if (APPLE)
|
|||||||
add_subdirectory(iostool)
|
add_subdirectory(iostool)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(processlauncher)
|
|
||||||
if (WITH_QMLDESIGNER)
|
if (WITH_QMLDESIGNER)
|
||||||
add_subdirectory(qml2puppet)
|
add_subdirectory(qml2puppet)
|
||||||
add_subdirectory(sqlitetester)
|
add_subdirectory(sqlitetester)
|
||||||
|
@@ -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",
|
"cplusplustools.qbs",
|
||||||
"disclaim/disclaim.qbs",
|
"disclaim/disclaim.qbs",
|
||||||
"process_stub/process_stub.qbs",
|
"process_stub/process_stub.qbs",
|
||||||
"processlauncher/processlauncher.qbs",
|
|
||||||
"qml2puppet/qml2puppet.qbs",
|
"qml2puppet/qml2puppet.qbs",
|
||||||
"qtcdebugger/qtcdebugger.qbs",
|
"qtcdebugger/qtcdebugger.qbs",
|
||||||
"qtcreatorcrashhandler/qtcreatorcrashhandler.qbs",
|
"qtcreatorcrashhandler/qtcreatorcrashhandler.qbs",
|
||||||
|
@@ -6,10 +6,9 @@
|
|||||||
#include <utils/commandline.h>
|
#include <utils/commandline.h>
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
#include <utils/hostosinfo.h>
|
#include <utils/hostosinfo.h>
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/macroexpander.h>
|
#include <utils/macroexpander.h>
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
#include <utils/processinterface.h>
|
#include <utils/singleton.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -43,10 +42,6 @@ private slots:
|
|||||||
TemporaryDirectory::setMasterTemporaryDirectory(
|
TemporaryDirectory::setMasterTemporaryDirectory(
|
||||||
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
||||||
|
|
||||||
const QString libExecPath(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
LauncherInterface::setPathToLauncher(libExecPath);
|
|
||||||
|
|
||||||
testEnv.set("TEST_ECHO", "1");
|
testEnv.set("TEST_ECHO", "1");
|
||||||
|
|
||||||
if (HostOsInfo::isWindowsHost())
|
if (HostOsInfo::isWindowsHost())
|
||||||
|
@@ -7,8 +7,8 @@
|
|||||||
#include <utils/deviceshell.h>
|
#include <utils/deviceshell.h>
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
#include <utils/hostosinfo.h>
|
#include <utils/hostosinfo.h>
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
|
#include <utils/singleton.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -71,10 +71,6 @@ private slots:
|
|||||||
TemporaryDirectory::setMasterTemporaryDirectory(
|
TemporaryDirectory::setMasterTemporaryDirectory(
|
||||||
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
||||||
|
|
||||||
const QString libExecPath(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
LauncherInterface::setPathToLauncher(libExecPath);
|
|
||||||
|
|
||||||
std::iota(m_asciiTestData.begin(), m_asciiTestData.end(), 0);
|
std::iota(m_asciiTestData.begin(), m_asciiTestData.end(), 0);
|
||||||
|
|
||||||
const FilePath dockerExecutable = Environment::systemEnvironment()
|
const FilePath dockerExecutable = Environment::systemEnvironment()
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
#include <app/app_version.h>
|
#include <app/app_version.h>
|
||||||
|
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
|
#include <utils/singleton.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
@@ -32,9 +32,6 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/"
|
TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/"
|
||||||
+ Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
+ Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
||||||
const QString libExecPath(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
LauncherInterface::setPathToLauncher(libExecPath);
|
|
||||||
SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP));
|
SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP));
|
||||||
|
|
||||||
QMetaObject::invokeMethod(&app, [] { ProcessTestApp::invokeSubProcess(); }, Qt::QueuedConnection);
|
QMetaObject::invokeMethod(&app, [] { ProcessTestApp::invokeSubProcess(); }, Qt::QueuedConnection);
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
#include <utils/async.h>
|
#include <utils/async.h>
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
#include <utils/hostosinfo.h>
|
#include <utils/hostosinfo.h>
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
#include <utils/processinfo.h>
|
#include <utils/processinfo.h>
|
||||||
#include <utils/processinterface.h>
|
#include <utils/processinterface.h>
|
||||||
@@ -21,9 +20,6 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QtTest>
|
#include <QtTest>
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
using namespace Utils;
|
using namespace Utils;
|
||||||
|
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
@@ -167,9 +163,6 @@ void tst_Process::initTestCase()
|
|||||||
msgHandler = new MessageHandler;
|
msgHandler = new MessageHandler;
|
||||||
TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/"
|
TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/"
|
||||||
+ Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
+ Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
||||||
const QString libExecPath(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
LauncherInterface::setPathToLauncher(libExecPath);
|
|
||||||
SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP));
|
SubProcessConfig::setPathToProcessTestApp(QLatin1String(PROCESS_TESTAPP));
|
||||||
|
|
||||||
homeStr = QLatin1String("@HOME@");
|
homeStr = QLatin1String("@HOME@");
|
||||||
@@ -1286,29 +1279,20 @@ void tst_Process::stdinToShell()
|
|||||||
|
|
||||||
void tst_Process::eventLoopMode_data()
|
void tst_Process::eventLoopMode_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<ProcessImpl>("processImpl");
|
|
||||||
QTest::addColumn<EventLoopMode>("eventLoopMode");
|
QTest::addColumn<EventLoopMode>("eventLoopMode");
|
||||||
|
|
||||||
QTest::newRow("QProcess, blocking with event loop")
|
QTest::newRow("EventLoopMode::On") << EventLoopMode::On;
|
||||||
<< ProcessImpl::QProcess << EventLoopMode::On;
|
QTest::newRow("EventLoopMode::Off") << EventLoopMode::Off;
|
||||||
QTest::newRow("QProcess, blocking without event loop")
|
|
||||||
<< ProcessImpl::QProcess << EventLoopMode::Off;
|
|
||||||
QTest::newRow("ProcessLauncher, blocking with event loop")
|
|
||||||
<< ProcessImpl::ProcessLauncher << EventLoopMode::On;
|
|
||||||
QTest::newRow("ProcessLauncher, blocking without event loop")
|
|
||||||
<< ProcessImpl::ProcessLauncher << EventLoopMode::Off;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_Process::eventLoopMode()
|
void tst_Process::eventLoopMode()
|
||||||
{
|
{
|
||||||
QFETCH(ProcessImpl, processImpl);
|
|
||||||
QFETCH(EventLoopMode, eventLoopMode);
|
QFETCH(EventLoopMode, eventLoopMode);
|
||||||
|
|
||||||
{
|
{
|
||||||
SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {});
|
SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {});
|
||||||
Process process;
|
Process process;
|
||||||
subConfig.setupSubProcess(&process);
|
subConfig.setupSubProcess(&process);
|
||||||
process.setProcessImpl(processImpl);
|
|
||||||
process.runBlocking(10s, eventLoopMode);
|
process.runBlocking(10s, eventLoopMode);
|
||||||
QCOMPARE(process.result(), ProcessResult::FinishedWithSuccess);
|
QCOMPARE(process.result(), ProcessResult::FinishedWithSuccess);
|
||||||
}
|
}
|
||||||
@@ -1317,7 +1301,6 @@ void tst_Process::eventLoopMode()
|
|||||||
Process process;
|
Process process;
|
||||||
process.setCommand(
|
process.setCommand(
|
||||||
CommandLine{"there_is_a_big_chance_that_executable_with_that_name_does_not_exists"});
|
CommandLine{"there_is_a_big_chance_that_executable_with_that_name_does_not_exists"});
|
||||||
process.setProcessImpl(processImpl);
|
|
||||||
process.runBlocking(10s, eventLoopMode);
|
process.runBlocking(10s, eventLoopMode);
|
||||||
QCOMPARE(process.result(), ProcessResult::StartFailed);
|
QCOMPARE(process.result(), ProcessResult::StartFailed);
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
#include <utils/commandline.h>
|
#include <utils/commandline.h>
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
#include <valgrind/valgrindprocess.h>
|
#include <valgrind/valgrindprocess.h>
|
||||||
@@ -24,9 +23,6 @@ int main(int argc, char *argv[])
|
|||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/QtCreator-XXXXXX");
|
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/QtCreator-XXXXXX");
|
||||||
const QString libExecPath(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
Utils::LauncherInterface::setPathToLauncher(libExecPath);
|
|
||||||
|
|
||||||
qRegisterMetaType<Error>();
|
qRegisterMetaType<Error>();
|
||||||
|
|
||||||
|
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
#include <utils/devicefileaccess.h>
|
#include <utils/devicefileaccess.h>
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
|
#include <utils/singleton.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -60,9 +60,6 @@ private slots:
|
|||||||
{
|
{
|
||||||
TemporaryDirectory::setMasterTemporaryDirectory(
|
TemporaryDirectory::setMasterTemporaryDirectory(
|
||||||
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
||||||
|
|
||||||
libExecPath = qApp->applicationDirPath() + '/' + QLatin1String(TEST_RELATIVE_LIBEXEC_PATH);
|
|
||||||
LauncherInterface::setPathToLauncher(libExecPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanupTestCase() { Singleton::deleteAll(); }
|
void cleanupTestCase() { Singleton::deleteAll(); }
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
#include <utils/deviceshell.h>
|
#include <utils/deviceshell.h>
|
||||||
#include <utils/environment.h>
|
#include <utils/environment.h>
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/qtcprocess.h>
|
#include <utils/qtcprocess.h>
|
||||||
|
#include <utils/singleton.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
@@ -131,10 +131,6 @@ private slots:
|
|||||||
TemporaryDirectory::setMasterTemporaryDirectory(
|
TemporaryDirectory::setMasterTemporaryDirectory(
|
||||||
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
||||||
|
|
||||||
const QString libExecPath(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
LauncherInterface::setPathToLauncher(libExecPath);
|
|
||||||
|
|
||||||
if (TestShell::cmdLine().isEmpty()) {
|
if (TestShell::cmdLine().isEmpty()) {
|
||||||
QSKIP("Skipping deviceshell tests, as no compatible shell could be found");
|
QSKIP("Skipping deviceshell tests, as no compatible shell could be found");
|
||||||
}
|
}
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
#include <utils/async.h>
|
#include <utils/async.h>
|
||||||
#include <utils/filesearch.h>
|
#include <utils/filesearch.h>
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/scopedtimer.h>
|
#include <utils/scopedtimer.h>
|
||||||
|
#include <utils/singleton.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
@@ -114,10 +114,6 @@ private slots:
|
|||||||
TemporaryDirectory::setMasterTemporaryDirectory(
|
TemporaryDirectory::setMasterTemporaryDirectory(
|
||||||
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
|
||||||
|
|
||||||
const QString libExecPath(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
LauncherInterface::setPathToLauncher(libExecPath);
|
|
||||||
|
|
||||||
qDebug() << "This manual test compares the performance of the SubDirFileContainer with a "
|
qDebug() << "This manual test compares the performance of the SubDirFileContainer with a "
|
||||||
"manually written iterator using QDir::entryInfoList() and with QDirIterator.";
|
"manually written iterator using QDir::entryInfoList() and with QDirIterator.";
|
||||||
QTC_SCOPED_TIMER("GENERATING TEMPORARY FILES TREE");
|
QTC_SCOPED_TIMER("GENERATING TEMPORARY FILES TREE");
|
||||||
|
@@ -7,7 +7,6 @@
|
|||||||
#include <sqlitelibraryinitializer.h>
|
#include <sqlitelibraryinitializer.h>
|
||||||
|
|
||||||
#include <sqliteglobal.h>
|
#include <sqliteglobal.h>
|
||||||
#include <utils/launcherinterface.h>
|
|
||||||
#include <utils/singleton.h>
|
#include <utils/singleton.h>
|
||||||
#include <utils/temporarydirectory.h>
|
#include <utils/temporarydirectory.h>
|
||||||
|
|
||||||
@@ -38,8 +37,6 @@ int main(int argc, char *argv[])
|
|||||||
Sqlite::Database::activateLogging();
|
Sqlite::Database::activateLogging();
|
||||||
|
|
||||||
QGuiApplication application(argc, argv);
|
QGuiApplication application(argc, argv);
|
||||||
Utils::LauncherInterface::setPathToLauncher(qApp->applicationDirPath() + '/'
|
|
||||||
+ QLatin1String(TEST_RELATIVE_LIBEXEC_PATH));
|
|
||||||
testing::InitGoogleTest(&argc, argv);
|
testing::InitGoogleTest(&argc, argv);
|
||||||
#ifdef WITH_BENCHMARKS
|
#ifdef WITH_BENCHMARKS
|
||||||
benchmark::Initialize(&argc, argv);
|
benchmark::Initialize(&argc, argv);
|
||||||
|
Reference in New Issue
Block a user