Initial import of processlauncher copy from qbs project

Change-Id: I9d646dd1a820e4e69c808998d9f12ff2f5dabe6d
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2021-07-07 11:36:03 +02:00
parent f16276c792
commit 58d00f37d4
20 changed files with 1344 additions and 0 deletions

View File

@@ -76,6 +76,9 @@ add_qtc_library(Utils
itemviews.cpp itemviews.h
json.cpp json.h
jsontreeitem.cpp jsontreeitem.h
launcherinterface.cpp launcherinterface.h
launcherpackets.cpp launcherpackets.h
launchersocket.cpp launchersocket.h
layoutbuilder.cpp layoutbuilder.h
linecolumn.cpp linecolumn.h
link.cpp link.h

View File

@@ -0,0 +1,169 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "launcherinterface.h"
#include "launcherpackets.h"
#include "launchersocket.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h>
#include <QtCore/qdir.h>
#include <QtCore/qprocess.h>
#include <QtNetwork/qlocalserver.h>
#ifdef Q_OS_UNIX
#include <unistd.h>
#endif
namespace Utils {
namespace Internal {
class LauncherProcess : public QProcess
{
public:
LauncherProcess(QObject *parent) : QProcess(parent)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) && defined(Q_OS_UNIX)
setChildProcessModifier([this] { setupChildProcess_impl(); });
#endif
}
private:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
void setupChildProcess() override
{
setupChildProcess_impl();
}
#endif
void setupChildProcess_impl()
{
#ifdef Q_OS_UNIX
const auto pid = static_cast<pid_t>(processId());
setpgid(pid, pid);
#endif
}
};
} // namespace Internal
using namespace Utils::Internal;
static QString launcherSocketName()
{
return QStringLiteral("qtcreator_processlauncher-%1")
.arg(QString::number(qApp->applicationPid()));
}
LauncherInterface::LauncherInterface()
: m_server(new QLocalServer(this)), m_socket(new LauncherSocket(this))
{
QObject::connect(m_server, &QLocalServer::newConnection,
this, &LauncherInterface::handleNewConnection);
}
LauncherInterface &LauncherInterface::instance()
{
static LauncherInterface p;
return p;
}
LauncherInterface::~LauncherInterface()
{
m_server->disconnect();
}
void LauncherInterface::doStart()
{
if (++m_startRequests > 1)
return;
const QString &socketName = launcherSocketName();
QLocalServer::removeServer(socketName);
if (!m_server->listen(socketName)) {
emit errorOccurred(m_server->errorString());
return;
}
m_process = new LauncherProcess(this);
connect(m_process, &QProcess::errorOccurred, this, &LauncherInterface::handleProcessError);
connect(m_process,
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, &LauncherInterface::handleProcessFinished);
connect(m_process, &QProcess::readyReadStandardError,
this, &LauncherInterface::handleProcessStderr);
m_process->start(qApp->applicationDirPath() + QLatin1Char('/')
+ QLatin1String(RELATIVE_LIBEXEC_PATH)
+ QLatin1String("/qtcreator_processlauncher"),
QStringList(m_server->fullServerName()));
}
void LauncherInterface::doStop()
{
if (--m_startRequests > 0)
return;
m_server->close();
if (!m_process)
return;
m_process->disconnect();
m_socket->shutdown();
m_process->waitForFinished(3000);
m_process->deleteLater();
m_process = nullptr;
}
void LauncherInterface::handleNewConnection()
{
QLocalSocket * const socket = m_server->nextPendingConnection();
if (!socket)
return;
m_server->close();
m_socket->setSocket(socket);
}
void LauncherInterface::handleProcessError()
{
if (m_process->error() == QProcess::FailedToStart) {
const QString launcherPathForUser
= QDir::toNativeSeparators(QDir::cleanPath(m_process->program()));
emit errorOccurred(QCoreApplication::translate("Utils::LauncherSocket",
"Failed to start process launcher at '%1': %2")
.arg(launcherPathForUser, m_process->errorString()));
}
}
void LauncherInterface::handleProcessFinished()
{
emit errorOccurred(QCoreApplication::translate("Utils::LauncherSocket",
"Process launcher closed unexpectedly: %1")
.arg(m_process->errorString()));
}
void LauncherInterface::handleProcessStderr()
{
qDebug() << "[launcher]" << m_process->readAllStandardError();
}
} // namespace Utils

View File

@@ -0,0 +1,72 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "utils_global.h"
#include <QtCore/qobject.h>
QT_BEGIN_NAMESPACE
class QLocalServer;
QT_END_NAMESPACE
namespace Utils {
namespace Internal {
class LauncherProcess;
class LauncherSocket;
}
class QTCREATOR_UTILS_EXPORT LauncherInterface : public QObject
{
Q_OBJECT
public:
static LauncherInterface &instance();
~LauncherInterface() override;
static void startLauncher() { instance().doStart(); }
static void stopLauncher() { instance().doStop(); }
static Internal::LauncherSocket *socket() { return instance().m_socket; }
signals:
void errorOccurred(const QString &error);
private:
LauncherInterface();
void doStart();
void doStop();
void handleNewConnection();
void handleProcessError();
void handleProcessFinished();
void handleProcessStderr();
QLocalServer * const m_server;
Internal::LauncherSocket *const m_socket;
Internal::LauncherProcess *m_process = nullptr;
int m_startRequests = 0;
};
} // namespace Utils

View File

@@ -0,0 +1,160 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "launcherpackets.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qcoreapplication.h>
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;
}
void StartProcessPacket::doDeserialize(QDataStream &stream)
{
stream >> command >> arguments >> workingDir >> env;
}
StopProcessPacket::StopProcessPacket(quintptr token)
: LauncherPacket(LauncherPacketType::StopProcess, token)
{
}
void StopProcessPacket::doSerialize(QDataStream &stream) const
{
Q_UNUSED(stream);
}
void StopProcessPacket::doDeserialize(QDataStream &stream)
{
Q_UNUSED(stream);
}
ProcessErrorPacket::ProcessErrorPacket(quintptr token)
: LauncherPacket(LauncherPacketType::ProcessError, token)
{
}
void ProcessErrorPacket::doSerialize(QDataStream &stream) const
{
stream << static_cast<quint8>(error) << errorString;
}
void ProcessErrorPacket::doDeserialize(QDataStream &stream)
{
quint8 e;
stream >> e;
error = static_cast<QProcess::ProcessError>(e);
stream >> errorString;
}
ProcessFinishedPacket::ProcessFinishedPacket(quintptr token)
: LauncherPacket(LauncherPacketType::ProcessFinished, token)
{
}
void ProcessFinishedPacket::doSerialize(QDataStream &stream) const
{
stream << errorString << stdOut << stdErr
<< static_cast<quint8>(exitStatus) << static_cast<quint8>(error)
<< exitCode;
}
void ProcessFinishedPacket::doDeserialize(QDataStream &stream)
{
stream >> errorString >> stdOut >> stdErr;
quint8 val;
stream >> val;
exitStatus = static_cast<QProcess::ExitStatus>(val);
stream >> val;
error = static_cast<QProcess::ProcessError>(val);
stream >> exitCode;
}
ShutdownPacket::ShutdownPacket() : LauncherPacket(LauncherPacketType::Shutdown, 0) { }
void ShutdownPacket::doSerialize(QDataStream &stream) const { Q_UNUSED(stream); }
void ShutdownPacket::doDeserialize(QDataStream &stream) { Q_UNUSED(stream); }
void PacketParser::setDevice(QIODevice *device)
{
m_stream.setDevice(device);
m_sizeOfNextPacket = -1;
}
bool PacketParser::parse()
{
static const int commonPayloadSize = static_cast<int>(1 + sizeof(quintptr));
if (m_sizeOfNextPacket == -1) {
if (m_stream.device()->bytesAvailable() < static_cast<int>(sizeof m_sizeOfNextPacket))
return false;
m_stream >> m_sizeOfNextPacket;
if (m_sizeOfNextPacket < commonPayloadSize)
throw InvalidPacketSizeException(m_sizeOfNextPacket);
}
if (m_stream.device()->bytesAvailable() < m_sizeOfNextPacket)
return false;
quint8 type;
m_stream >> type;
m_type = static_cast<LauncherPacketType>(type);
m_stream >> m_token;
m_packetData = m_stream.device()->read(m_sizeOfNextPacket - commonPayloadSize);
m_sizeOfNextPacket = -1;
return true;
}
} // namespace Internal
} // namespace Utils

View File

@@ -0,0 +1,161 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QtCore/qdatastream.h>
#include <QtCore/qprocess.h>
#include <QtCore/qstringlist.h>
QT_BEGIN_NAMESPACE
class QByteArray;
QT_END_NAMESPACE
namespace Utils {
namespace Internal {
enum class LauncherPacketType {
Shutdown, StartProcess, StopProcess, ProcessError, ProcessFinished
};
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;
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
class StopProcessPacket : public LauncherPacket
{
public:
StopProcessPacket(quintptr token);
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 ProcessErrorPacket : public LauncherPacket
{
public:
ProcessErrorPacket(quintptr token);
QProcess::ProcessError error = QProcess::UnknownError;
QString errorString;
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
class ProcessFinishedPacket : public LauncherPacket
{
public:
ProcessFinishedPacket(quintptr token);
QString errorString;
QByteArray stdOut;
QByteArray stdErr;
QProcess::ExitStatus exitStatus = QProcess::ExitStatus::NormalExit;
QProcess::ProcessError error = QProcess::ProcessError::UnknownError;
int exitCode = 0;
private:
void doSerialize(QDataStream &stream) const override;
void doDeserialize(QDataStream &stream) override;
};
} // namespace Internal
} // namespace Utils
Q_DECLARE_METATYPE(Utils::Internal::LauncherPacketType);

View File

@@ -0,0 +1,141 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "launchersocket.h"
#include "qtcassert.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qtimer.h>
#include <QtNetwork/qlocalsocket.h>
namespace Utils {
namespace Internal {
LauncherSocket::LauncherSocket(QObject *parent) : QObject(parent)
{
qRegisterMetaType<Utils::Internal::LauncherPacketType>();
qRegisterMetaType<quintptr>("quintptr");
}
void LauncherSocket::sendData(const QByteArray &data)
{
if (!isReady())
return;
std::lock_guard<std::mutex> locker(m_requestsMutex);
m_requests.push_back(data);
if (m_requests.size() == 1)
QTimer::singleShot(0, this, &LauncherSocket::handleRequests);
}
void LauncherSocket::shutdown()
{
const auto socket = m_socket.exchange(nullptr);
if (!socket)
return;
socket->disconnect();
socket->write(ShutdownPacket().serialize());
socket->waitForBytesWritten(1000);
socket->deleteLater();
}
void LauncherSocket::setSocket(QLocalSocket *socket)
{
QTC_ASSERT(!m_socket, return);
m_socket.store(socket);
m_packetParser.setDevice(m_socket);
connect(m_socket,
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
#else
&QLocalSocket::errorOccurred,
#endif
this, &LauncherSocket::handleSocketError);
connect(m_socket, &QLocalSocket::readyRead,
this, &LauncherSocket::handleSocketDataAvailable);
connect(m_socket, &QLocalSocket::disconnected,
this, &LauncherSocket::handleSocketDisconnected);
emit ready();
}
void LauncherSocket::handleSocketError()
{
auto socket = m_socket.load();
if (socket->error() != QLocalSocket::PeerClosedError)
handleError(QCoreApplication::translate("Utils::LauncherSocket",
"Socket error: %1").arg(socket->errorString()));
}
void LauncherSocket::handleSocketDataAvailable()
{
try {
if (!m_packetParser.parse())
return;
} catch (const PacketParser::InvalidPacketSizeException &e) {
handleError(QCoreApplication::translate("Utils::LauncherSocket",
"Internal protocol error: invalid packet size %1.").arg(e.size));
return;
}
switch (m_packetParser.type()) {
case LauncherPacketType::ProcessError:
case LauncherPacketType::ProcessFinished:
emit packetArrived(m_packetParser.type(), m_packetParser.token(),
m_packetParser.packetData());
break;
default:
handleError(QCoreApplication::translate("Utils::LauncherSocket",
"Internal protocol error: invalid packet type %1.")
.arg(static_cast<int>(m_packetParser.type())));
return;
}
handleSocketDataAvailable();
}
void LauncherSocket::handleSocketDisconnected()
{
handleError(QCoreApplication::translate("Utils::LauncherSocket",
"Launcher socket closed unexpectedly"));
}
void LauncherSocket::handleError(const QString &error)
{
const auto socket = m_socket.exchange(nullptr);
socket->disconnect();
socket->deleteLater();
emit errorOccurred(error);
}
void LauncherSocket::handleRequests()
{
const auto socket = m_socket.load();
QTC_ASSERT(socket, return);
std::lock_guard<std::mutex> locker(m_requestsMutex);
for (const QByteArray &request : qAsConst(m_requests))
socket->write(request);
m_requests.clear();
}
} // namespace Internal
} // namespace Utils

View File

@@ -0,0 +1,77 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include "launcherpackets.h"
#include <QtCore/qobject.h>
#include <mutex>
#include <vector>
QT_BEGIN_NAMESPACE
class QLocalSocket;
QT_END_NAMESPACE
namespace Utils {
class LauncherInterface;
namespace Internal {
class LauncherSocket : public QObject
{
Q_OBJECT
friend class Utils::LauncherInterface;
public:
bool isReady() const { return m_socket.load(); }
void sendData(const QByteArray &data);
signals:
void ready();
void errorOccurred(const QString &error);
void packetArrived(Utils::Internal::LauncherPacketType type, quintptr token,
const QByteArray &payload);
private:
LauncherSocket(QObject *parent);
void setSocket(QLocalSocket *socket);
void shutdown();
void handleSocketError();
void handleSocketDataAvailable();
void handleSocketDisconnected();
void handleError(const QString &error);
void handleRequests();
std::atomic<QLocalSocket *> m_socket{nullptr};
PacketParser m_packetParser;
std::vector<QByteArray> m_requests;
std::mutex m_requestsMutex;
};
} // namespace Internal
} // namespace Utils

View File

@@ -142,6 +142,9 @@ SOURCES += \
$$PWD/layoutbuilder.cpp \
$$PWD/variablechooser.cpp \
$$PWD/futuresynchronizer.cpp \
$$PWD/launcherinterface.cpp \
$$PWD/launcherpackets.cpp \
$$PWD/launchersocket.cpp \
$$PWD/qtcsettings.cpp \
$$PWD/link.cpp \
$$PWD/linecolumn.cpp \
@@ -302,6 +305,9 @@ HEADERS += \
$$PWD/variablechooser.h \
$$PWD/set_algorithm.h \
$$PWD/futuresynchronizer.h \
$$PWD/launcherinterface.h \
$$PWD/launcherpackets.h \
$$PWD/launchersocket.h \
$$PWD/qtcsettings.h
FORMS += $$PWD/filewizardpage.ui \

View File

@@ -159,6 +159,12 @@ Project {
"json.h",
"jsontreeitem.cpp",
"jsontreeitem.h",
"launcherinterface.cpp",
"launcherinterface.h",
"launcherpackets.cpp",
"launcherpackets.h",
"launchersocket.cpp",
"launchersocket.h",
"layoutbuilder.cpp",
"layoutbuilder.h",
"linecolumn.cpp",

View File

@@ -33,6 +33,7 @@ if (APPLE)
add_subdirectory(iostool)
endif()
add_subdirectory(processlauncher)
add_subdirectory(qml2puppet)
add_subdirectory(qtcdebugger) ## windows only
# add_subdirectory(qtcrashhandler)

View File

@@ -0,0 +1,14 @@
set(UTILSDIR "${PROJECT_SOURCE_DIR}/src/libs/utils")
add_qtc_executable(qtcreator_processlauncher
INCLUDES "${UTILSDIR}"
DEPENDS Qt5::Core Qt5::Network
SOURCES
launcherlogging.cpp
launcherlogging.h
launchersockethandler.cpp
launchersockethandler.h
processlauncher-main.cpp
${UTILSDIR}/launcherpackets.cpp
${UTILSDIR}/launcherpackets.h
)

View File

@@ -0,0 +1,32 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "launcherlogging.h"
namespace Utils {
namespace Internal {
Q_LOGGING_CATEGORY(launcherLog, "qtc.utils.launcher", QtWarningMsg)
}
}

View File

@@ -0,0 +1,38 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <QtCore/qloggingcategory.h>
#include <QtCore/qstring.h>
namespace Utils {
namespace Internal {
Q_DECLARE_LOGGING_CATEGORY(launcherLog)
template<typename T> void logDebug(const T &msg) { qCDebug(launcherLog) << msg; }
template<typename T> void logWarn(const T &msg) { qCWarning(launcherLog) << msg; }
template<typename T> void logError(const T &msg) { qCCritical(launcherLog) << msg; }
}
}

View File

@@ -0,0 +1,281 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "launchersockethandler.h"
#include "launcherlogging.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qprocess.h>
#include <QtCore/qtimer.h>
#include <QtNetwork/qlocalsocket.h>
namespace Utils {
namespace Internal {
class Process : public QProcess
{
Q_OBJECT
public:
Process(quintptr token, QObject *parent = nullptr) :
QProcess(parent), m_token(token), m_stopTimer(new QTimer(this))
{
m_stopTimer->setSingleShot(true);
connect(m_stopTimer, &QTimer::timeout, this, &Process::cancel);
}
void cancel()
{
switch (m_stopState) {
case StopState::Inactive:
m_stopState = StopState::Terminating;
m_stopTimer->start(3000);
terminate();
break;
case StopState::Terminating:
m_stopState = StopState::Killing;
m_stopTimer->start(3000);
kill();
break;
case StopState::Killing:
m_stopState = StopState::Inactive;
emit failedToStop();
break;
}
}
void stopStopProcedure()
{
m_stopState = StopState::Inactive;
m_stopTimer->stop();
}
quintptr token() const { return m_token; }
signals:
void failedToStop();
private:
const quintptr m_token;
QTimer * const m_stopTimer;
enum class StopState { Inactive, Terminating, Killing } m_stopState = StopState::Inactive;
};
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()
{
m_socket->disconnect();
if (m_socket->state() != QLocalSocket::UnconnectedState) {
logWarn("socket handler destroyed while connection was active");
m_socket->close();
}
for (auto it = m_processes.cbegin(); it != m_processes.cend(); ++it)
it.value()->disconnect();
}
void LauncherSocketHandler::start()
{
connect(m_socket, &QLocalSocket::disconnected,
this, &LauncherSocketHandler::handleSocketClosed);
connect(m_socket, &QLocalSocket::readyRead, this, &LauncherSocketHandler::handleSocketData);
connect(m_socket,
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
#else
&QLocalSocket::errorOccurred,
#endif
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::StopProcess:
handleStopPacket();
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()
{
for (auto it = m_processes.cbegin(); it != m_processes.cend(); ++it) {
if (it.value()->state() != QProcess::NotRunning) {
logWarn("client closed connection while process still running");
break;
}
}
m_socket->disconnect();
qApp->quit();
}
void LauncherSocketHandler::handleProcessError()
{
Process * proc = senderProcess();
if (proc->error() != QProcess::FailedToStart)
return;
proc->stopStopProcedure();
ProcessErrorPacket packet(proc->token());
packet.error = proc->error();
packet.errorString = proc->errorString();
sendPacket(packet);
}
void LauncherSocketHandler::handleProcessFinished()
{
Process * proc = senderProcess();
proc->stopStopProcedure();
ProcessFinishedPacket packet(proc->token());
packet.error = proc->error();
packet.errorString = proc->errorString();
packet.exitCode = proc->exitCode();
packet.exitStatus = proc->exitStatus();
packet.stdErr = proc->readAllStandardError();
packet.stdOut = proc->readAllStandardOutput();
sendPacket(packet);
}
void LauncherSocketHandler::handleStopFailure()
{
// Process did not react to a kill signal. Rare, but not unheard of.
// Forget about the associated Process object and report process exit to the client.
Process * proc = senderProcess();
proc->disconnect();
m_processes.remove(proc->token());
ProcessFinishedPacket packet(proc->token());
packet.error = QProcess::Crashed;
packet.exitCode = -1;
packet.exitStatus = QProcess::CrashExit;
packet.stdErr = proc->readAllStandardError();
packet.stdOut = proc->readAllStandardOutput();
sendPacket(packet);
}
void LauncherSocketHandler::handleStartPacket()
{
Process *& 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);
process->start(packet.command, packet.arguments);
}
void LauncherSocketHandler::handleStopPacket()
{
Process * const process = m_processes.value(m_packetParser.token());
if (!process) {
logWarn("got stop request for unknown process");
return;
}
if (process->state() == QProcess::NotRunning) {
// This can happen if the process finishes on its own at about the same time the client
// sends the request.
logDebug("got stop request when process was not running");
return;
}
process->cancel();
}
void LauncherSocketHandler::handleShutdownPacket()
{
logDebug("got shutdown request, closing down");
for (auto it = m_processes.cbegin(); it != m_processes.cend(); ++it) {
it.value()->disconnect();
if (it.value()->state() != QProcess::NotRunning) {
logWarn("got shutdown request while process was running");
it.value()->terminate();
}
}
m_socket->disconnect();
qApp->quit();
}
void LauncherSocketHandler::sendPacket(const LauncherPacket &packet)
{
m_socket->write(packet.serialize());
}
Process *LauncherSocketHandler::setupProcess(quintptr token)
{
const auto p = new Process(token, this);
connect(p, &QProcess::errorOccurred, this, &LauncherSocketHandler::handleProcessError);
connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, &LauncherSocketHandler::handleProcessFinished);
connect(p, &Process::failedToStop, this, &LauncherSocketHandler::handleStopFailure);
return p;
}
Process *LauncherSocketHandler::senderProcess() const
{
return static_cast<Process *>(sender());
}
} // namespace Internal
} // namespace Utils
#include <launchersockethandler.moc>

View File

@@ -0,0 +1,75 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <launcherpackets.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qhash.h>
#include <QtCore/qobject.h>
QT_BEGIN_NAMESPACE
class QLocalSocket;
QT_END_NAMESPACE
namespace Utils {
namespace Internal {
class Process;
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 handleProcessError();
void handleProcessFinished();
void handleStopFailure();
void handleStartPacket();
void handleStopPacket();
void handleShutdownPacket();
void sendPacket(const LauncherPacket &packet);
Process *setupProcess(quintptr token);
Process *senderProcess() const;
const QString m_serverPath;
QLocalSocket * const m_socket;
PacketParser m_packetParser;
QHash<quintptr, Process *> m_processes;
};
} // namespace Internal
} // namespace Utils

View File

@@ -0,0 +1,57 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "launcherlogging.h"
#include "launchersockethandler.h"
#include <QtCore/qcoreapplication.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;
}
Utils::Internal::LauncherSocketHandler launcher(app.arguments().constLast());
QTimer::singleShot(0, &launcher, &Utils::Internal::LauncherSocketHandler::start);
return app.exec();
}

View File

@@ -0,0 +1,21 @@
include(../../qtcreatortool.pri)
TARGET = qtcreator_processlauncher
CONFIG += console c++17
CONFIG -= app_bundle
QT = core network
UTILS_DIR = $$PWD/../../libs/utils
INCLUDEPATH += $$UTILS_DIR
HEADERS += \
launcherlogging.h \
launchersockethandler.h \
$$UTILS_DIR/launcherpackets.h
SOURCES += \
launcherlogging.cpp \
launchersockethandler.cpp \
processlauncher-main.cpp \
$$UTILS_DIR/launcherpackets.cpp

View File

@@ -0,0 +1,28 @@
import qbs
import qbs.FileInfo
QtcTool {
name: "qtcreator_processlauncher"
Depends { name: "Qt.network" }
cpp.includePaths: base.concat(pathToUtils)
files: [
"launcherlogging.cpp",
"launcherlogging.h",
"launchersockethandler.cpp",
"launchersockethandler.h",
"processlauncher-main.cpp",
]
property string pathToUtils: sourceDirectory + "/../../libs/utils"
Group {
name: "protocol sources"
prefix: pathToUtils + '/'
files: [
"launcherpackets.cpp",
"launcherpackets.h",
]
}
}

View File

@@ -7,6 +7,7 @@ SUBDIRS = qtpromaker \
valgrindfake \
3rdparty \
buildoutputparser \
processlauncher \
qtc-askpass
isEmpty(QTC_SKIP_SDKTOOL): SUBDIRS += sdktool

View File

@@ -7,6 +7,7 @@ Project {
"buildoutputparser/buildoutputparser.qbs",
"clangbackend/clangbackend.qbs",
"cplusplustools.qbs",
"processlauncher/processlauncher.qbs",
"qml2puppet/qml2puppet.qbs",
"qtcdebugger/qtcdebugger.qbs",
"qtcreatorcrashhandler/qtcreatorcrashhandler.qbs",