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 itemviews.cpp itemviews.h
json.cpp json.h json.cpp json.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
linecolumn.cpp linecolumn.h linecolumn.cpp linecolumn.h
link.cpp link.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/layoutbuilder.cpp \
$$PWD/variablechooser.cpp \ $$PWD/variablechooser.cpp \
$$PWD/futuresynchronizer.cpp \ $$PWD/futuresynchronizer.cpp \
$$PWD/launcherinterface.cpp \
$$PWD/launcherpackets.cpp \
$$PWD/launchersocket.cpp \
$$PWD/qtcsettings.cpp \ $$PWD/qtcsettings.cpp \
$$PWD/link.cpp \ $$PWD/link.cpp \
$$PWD/linecolumn.cpp \ $$PWD/linecolumn.cpp \
@@ -302,6 +305,9 @@ HEADERS += \
$$PWD/variablechooser.h \ $$PWD/variablechooser.h \
$$PWD/set_algorithm.h \ $$PWD/set_algorithm.h \
$$PWD/futuresynchronizer.h \ $$PWD/futuresynchronizer.h \
$$PWD/launcherinterface.h \
$$PWD/launcherpackets.h \
$$PWD/launchersocket.h \
$$PWD/qtcsettings.h $$PWD/qtcsettings.h
FORMS += $$PWD/filewizardpage.ui \ FORMS += $$PWD/filewizardpage.ui \

View File

@@ -159,6 +159,12 @@ Project {
"json.h", "json.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",
"linecolumn.cpp", "linecolumn.cpp",

View File

@@ -33,6 +33,7 @@ if (APPLE)
add_subdirectory(iostool) add_subdirectory(iostool)
endif() endif()
add_subdirectory(processlauncher)
add_subdirectory(qml2puppet) add_subdirectory(qml2puppet)
add_subdirectory(qtcdebugger) ## windows only add_subdirectory(qtcdebugger) ## windows only
# add_subdirectory(qtcrashhandler) # 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 \ valgrindfake \
3rdparty \ 3rdparty \
buildoutputparser \ buildoutputparser \
processlauncher \
qtc-askpass qtc-askpass
isEmpty(QTC_SKIP_SDKTOOL): SUBDIRS += sdktool isEmpty(QTC_SKIP_SDKTOOL): SUBDIRS += sdktool

View File

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