aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/lib/corelib/api/jobs.cpp17
-rw-r--r--src/lib/corelib/api/jobs.h5
-rw-r--r--src/lib/corelib/buildgraph/processcommandexecutor.cpp8
-rw-r--r--src/lib/corelib/buildgraph/processcommandexecutor.h6
-rw-r--r--src/lib/corelib/corelib.pro3
-rw-r--r--src/lib/corelib/corelib.qbs9
-rw-r--r--src/lib/corelib/tools/launcherinterface.cpp148
-rw-r--r--src/lib/corelib/tools/launcherinterface.h88
-rw-r--r--src/lib/corelib/tools/launcherpackets.cpp175
-rw-r--r--src/lib/corelib/tools/launcherpackets.h178
-rw-r--r--src/lib/corelib/tools/launchersocket.cpp133
-rw-r--r--src/lib/corelib/tools/launchersocket.h91
-rw-r--r--src/lib/corelib/tools/qbsprocess.cpp173
-rw-r--r--src/lib/corelib/tools/qbsprocess.h106
-rw-r--r--src/lib/corelib/tools/tools.pri8
-rw-r--r--src/libexec/libexec.pro2
-rw-r--r--src/libexec/libexec.qbs1
-rw-r--r--src/libexec/qbs_processlauncher/launcherlogging.cpp46
-rw-r--r--src/libexec/qbs_processlauncher/launcherlogging.h54
-rw-r--r--src/libexec/qbs_processlauncher/launchersockethandler.cpp289
-rw-r--r--src/libexec/qbs_processlauncher/launchersockethandler.h92
-rw-r--r--src/libexec/qbs_processlauncher/processlauncher-main.cpp46
-rw-r--r--src/libexec/qbs_processlauncher/qbs_processlauncher.pro21
-rw-r--r--src/libexec/qbs_processlauncher/qbs_processlauncher.qbs39
24 files changed, 1730 insertions, 8 deletions
diff --git a/src/lib/corelib/api/jobs.cpp b/src/lib/corelib/api/jobs.cpp
index 6589aa359..b1b5b7e5f 100644
--- a/src/lib/corelib/api/jobs.cpp
+++ b/src/lib/corelib/api/jobs.cpp
@@ -41,6 +41,7 @@
#include "internaljobs.h"
#include "project_p.h"
#include <language/language.h>
+#include <tools/launcherinterface.h>
#include <tools/qbsassert.h>
#include <QtCore/qtimer.h>
@@ -161,6 +162,8 @@ AbstractJob::~AbstractJob()
*/
ErrorInfo AbstractJob::error() const
{
+ if (m_error.hasError())
+ return m_error;
return internalJob()->error();
}
@@ -300,6 +303,8 @@ void SetupProjectJob::finish()
BuildJob::BuildJob(const Logger &logger, QObject *parent)
: AbstractJob(new InternalBuildJob(logger), parent)
{
+ connect(&LauncherInterface::instance(), &LauncherInterface::errorOccurred,
+ this, &BuildJob::handleLauncherError);
InternalBuildJob *job = static_cast<InternalBuildJob *>(internalJob());
connect(job, &BuildGraphTouchingJob::reportCommandDescription,
this, &BuildJob::reportCommandDescription);
@@ -312,9 +317,21 @@ void BuildJob::build(const TopLevelProjectPtr &project, const QList<ResolvedProd
{
if (!lockProject(project))
return;
+ LauncherInterface::startLauncher();
qobject_cast<InternalBuildJob *>(internalJob())->build(project, products, options);
}
+void BuildJob::handleLauncherError(const ErrorInfo &error)
+{
+ setError(error);
+ cancel();
+}
+
+void BuildJob::finish()
+{
+ LauncherInterface::stopLauncher();
+}
+
/*!
* \class CleanJob
diff --git a/src/lib/corelib/api/jobs.h b/src/lib/corelib/api/jobs.h
index cf9f337f2..04a0093c1 100644
--- a/src/lib/corelib/api/jobs.h
+++ b/src/lib/corelib/api/jobs.h
@@ -81,6 +81,7 @@ protected:
Internal::InternalJob *internalJob() const { return m_internalJob; }
bool lockProject(const Internal::TopLevelProjectPtr &project);
+ void setError(const ErrorInfo &error) { m_error = error; }
signals:
void taskStarted(const QString &description, int maximumProgressValue, qbs::AbstractJob *job);
@@ -99,6 +100,7 @@ private:
Internal::InternalJob * const m_internalJob;
Internal::TopLevelProjectPtr m_project;
+ ErrorInfo m_error;
State m_state;
};
@@ -136,6 +138,9 @@ private:
void build(const Internal::TopLevelProjectPtr &project,
const QList<qbs::Internal::ResolvedProductPtr> &products,
const BuildOptions &options);
+ void handleLauncherError(const ErrorInfo &error);
+
+ void finish();
};
diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.cpp b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
index 9eae591eb..befa66563 100644
--- a/src/lib/corelib/buildgraph/processcommandexecutor.cpp
+++ b/src/lib/corelib/buildgraph/processcommandexecutor.cpp
@@ -71,9 +71,9 @@ namespace Internal {
ProcessCommandExecutor::ProcessCommandExecutor(const Logger &logger, QObject *parent)
: AbstractCommandExecutor(logger, parent)
{
- connect(&m_process, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
+ connect(&m_process, static_cast<void (QbsProcess::*)(QProcess::ProcessError)>(&QbsProcess::error),
this, &ProcessCommandExecutor::onProcessError);
- connect(&m_process, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
+ connect(&m_process, static_cast<void (QbsProcess::*)(int)>(&QbsProcess::finished),
this, &ProcessCommandExecutor::onProcessFinished);
}
@@ -169,9 +169,7 @@ void ProcessCommandExecutor::cancel()
// We don't want this command to be reported as failing, since we explicitly terminated it.
disconnect(this, &ProcessCommandExecutor::reportProcessResult, 0, 0);
- m_process.terminate();
- if (!m_process.waitForFinished(1000))
- m_process.kill();
+ m_process.cancel();
}
QString ProcessCommandExecutor::filterProcessOutput(const QByteArray &_output,
diff --git a/src/lib/corelib/buildgraph/processcommandexecutor.h b/src/lib/corelib/buildgraph/processcommandexecutor.h
index d04f305c3..b96f6a0f1 100644
--- a/src/lib/corelib/buildgraph/processcommandexecutor.h
+++ b/src/lib/corelib/buildgraph/processcommandexecutor.h
@@ -42,8 +42,8 @@
#include "abstractcommandexecutor.h"
-#include <QtCore/qprocess.h>
-#include <QtCore/qprocess.h>
+#include <tools/qbsprocess.h>
+
#include <QtCore/qstring.h>
namespace qbs {
@@ -87,7 +87,7 @@ private:
QStringList m_arguments;
QString m_shellInvocation;
- QProcess m_process;
+ QbsProcess m_process;
QProcessEnvironment m_buildEnvironment;
QProcessEnvironment m_commandEnvironment;
QString m_responseFileName;
diff --git a/src/lib/corelib/corelib.pro b/src/lib/corelib/corelib.pro
index f7046582e..0ca361850 100644
--- a/src/lib/corelib/corelib.pro
+++ b/src/lib/corelib/corelib.pro
@@ -1,6 +1,9 @@
TARGET = qbscore
include(../library.pri)
+isEmpty(QBS_RELATIVE_LIBEXEC_PATH):QBS_RELATIVE_LIBEXEC_PATH=../libexec/qbs
+DEFINES += QBS_RELATIVE_LIBEXEC_PATH=\\\"$${QBS_RELATIVE_LIBEXEC_PATH}\\\"
+
QT += core-private network script
qbs_enable_unit_tests:QT += testlib
qbs_enable_project_file_updates: QT += gui
diff --git a/src/lib/corelib/corelib.qbs b/src/lib/corelib/corelib.qbs
index 85528550b..eee8600a5 100644
--- a/src/lib/corelib/corelib.qbs
+++ b/src/lib/corelib/corelib.qbs
@@ -18,6 +18,7 @@ QbsLibrary {
property stringList projectFileUpdateDefines:
qbsbuildconfig.enableProjectFileUpdates ? ["QBS_ENABLE_PROJECT_FILE_UPDATES"] : []
cpp.defines: base.concat([
+ 'QBS_RELATIVE_LIBEXEC_PATH="' + qbsbuildconfig.relativeLibexecPath + '"',
"QBS_VERSION=\"" + version + "\"",
"QT_CREATOR", "QML_BUILD_STATIC_LIB", // needed for QmlJS
"SRCDIR=\"" + path + "\""
@@ -360,6 +361,12 @@ QbsLibrary {
"jsliterals.cpp",
"jsliterals.h",
"installoptions.cpp",
+ "launcherinterface.cpp",
+ "launcherinterface.h",
+ "launcherpackets.cpp",
+ "launcherpackets.h",
+ "launchersocket.cpp",
+ "launchersocket.h",
"msvcinfo.cpp",
"msvcinfo.h",
"pathutils.h",
@@ -379,6 +386,8 @@ QbsLibrary {
"projectgeneratormanager.cpp",
"qbsassert.cpp",
"qbsassert.h",
+ "qbsprocess.cpp",
+ "qbsprocess.h",
"qttools.cpp",
"qttools.h",
"scannerpluginmanager.cpp",
diff --git a/src/lib/corelib/tools/launcherinterface.cpp b/src/lib/corelib/tools/launcherinterface.cpp
new file mode 100644
index 000000000..7e9679379
--- /dev/null
+++ b/src/lib/corelib/tools/launcherinterface.cpp
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "launcherinterface.h"
+
+#include "launcherpackets.h"
+#include "launchersocket.h"
+#include "qbsassert.h"
+#include <logging/logger.h>
+#include <logging/translator.h>
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qprocess.h>
+#include <QtNetwork/qlocalserver.h>
+
+namespace qbs {
+namespace Internal {
+
+static QString launcherSocketName()
+{
+ return QString::fromLatin1("qbs_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(ErrorInfo(m_server->errorString()));
+ return;
+ }
+ m_process = new QProcess(this);
+ connect(m_process,
+ static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
+ this, &LauncherInterface::handleProcessError);
+ connect(m_process, static_cast<void (QProcess::*)(int)>(&QProcess::finished),
+ this, &LauncherInterface::handleProcessFinished);
+ connect(m_process, &QProcess::readyReadStandardError,
+ this, &LauncherInterface::handleProcessStderr);
+ m_process->start(qApp->applicationDirPath() + QLatin1Char('/')
+ + QLatin1String(QBS_RELATIVE_LIBEXEC_PATH)
+ + QLatin1String("/qbs_processlauncher"),
+ QStringList(m_server->fullServerName()));
+}
+
+void LauncherInterface::doStop()
+{
+ if (--m_startRequests > 0)
+ return;
+ m_server->close();
+ if (!m_process)
+ return;
+ m_process->disconnect();
+ if (m_socket->isReady())
+ 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)
+ handleProcessFinished();
+}
+
+void LauncherInterface::handleProcessFinished()
+{
+ if (!m_socket->isReady()) {
+ emit errorOccurred(ErrorInfo(m_process->errorString()));
+ return;
+ }
+}
+
+void LauncherInterface::handleProcessStderr()
+{
+ qDebug() << "[launcher]" << m_process->readAllStandardError();
+}
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/tools/launcherinterface.h b/src/lib/corelib/tools/launcherinterface.h
new file mode 100644
index 000000000..6ee0d80b5
--- /dev/null
+++ b/src/lib/corelib/tools/launcherinterface.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBS_LAUNCHERINTERFACE_H
+#define QBS_LAUNCHERINTERFACE_H
+
+#include <QtCore/qobject.h>
+
+QT_BEGIN_NAMESPACE
+class QProcess;
+class QLocalServer;
+QT_END_NAMESPACE
+
+namespace qbs {
+class ErrorInfo;
+namespace Internal {
+class LauncherSocket;
+
+class LauncherInterface : public QObject
+{
+ Q_OBJECT
+public:
+ static LauncherInterface &instance();
+ ~LauncherInterface();
+
+ static void startLauncher() { instance().doStart(); }
+ static void stopLauncher() { instance().doStop(); }
+ static LauncherSocket *socket() { return instance().m_socket; }
+
+signals:
+ void errorOccurred(const ErrorInfo &error);
+
+private:
+ LauncherInterface();
+
+ void doStart();
+ void doStop();
+ void handleNewConnection();
+ void handleProcessError();
+ void handleProcessFinished();
+ void handleProcessStderr();
+
+ QLocalServer * const m_server;
+ LauncherSocket * const m_socket;
+ QProcess * m_process = nullptr;
+ int m_startRequests = 0;
+};
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // Include guard
diff --git a/src/lib/corelib/tools/launcherpackets.cpp b/src/lib/corelib/tools/launcherpackets.cpp
new file mode 100644
index 000000000..9c131bd69
--- /dev/null
+++ b/src/lib/corelib/tools/launcherpackets.cpp
@@ -0,0 +1,175 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "launcherpackets.h"
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qendian.h>
+
+namespace qbs {
+namespace Internal {
+
+LauncherPacket::~LauncherPacket() { }
+
+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 LauncherPacket::token);
+ 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 qbs
diff --git a/src/lib/corelib/tools/launcherpackets.h b/src/lib/corelib/tools/launcherpackets.h
new file mode 100644
index 000000000..0988761e7
--- /dev/null
+++ b/src/lib/corelib/tools/launcherpackets.h
@@ -0,0 +1,178 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBS_LAUNCHERPACKETS_H
+#define QBS_LAUNCHERPACKETS_H
+
+#include <QtCore/qdatastream.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+QT_END_NAMESPACE
+
+namespace qbs {
+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;
+ quintptr m_token;
+ 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;
+
+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;
+ 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::ProcessError error;
+ int exitCode;
+
+private:
+ void doSerialize(QDataStream &stream) const override;
+ void doDeserialize(QDataStream &stream) override;
+};
+
+} // namespace Internal
+} // namespace qbs
+
+Q_DECLARE_METATYPE(qbs::Internal::LauncherPacketType);
+
+#endif // Include guard
diff --git a/src/lib/corelib/tools/launchersocket.cpp b/src/lib/corelib/tools/launchersocket.cpp
new file mode 100644
index 000000000..fe809601e
--- /dev/null
+++ b/src/lib/corelib/tools/launchersocket.cpp
@@ -0,0 +1,133 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "launchersocket.h"
+
+#include "qbsassert.h"
+
+#include <logging/translator.h>
+
+#include <QtCore/qtimer.h>
+#include <QtNetwork/qlocalsocket.h>
+
+namespace qbs {
+namespace Internal {
+
+LauncherSocket::LauncherSocket(QObject *parent) : QObject(parent)
+{
+ qRegisterMetaType<qbs::Internal::LauncherPacketType>();
+ qRegisterMetaType<quintptr>("quintptr");
+}
+
+void LauncherSocket::sendData(const QByteArray &data)
+{
+ QMutexLocker locker(&m_requestsMutex);
+ m_requests << data;
+ if (m_requests.count() == 1)
+ QTimer::singleShot(0, this, &LauncherSocket::handleRequests);
+}
+
+void LauncherSocket::shutdown()
+{
+ m_socket->disconnect();
+ m_socket->write(ShutdownPacket().serialize());
+ m_socket->waitForBytesWritten(1000);
+ m_socket->deleteLater();
+ m_socket = nullptr;
+}
+
+void LauncherSocket::setSocket(QLocalSocket *socket)
+{
+ QBS_ASSERT(!m_socket, return);
+ m_socket = socket;
+ m_packetParser.setDevice(m_socket);
+ connect(m_socket,
+ static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
+ this, &LauncherSocket::handleSocketError);
+ connect(m_socket, &QLocalSocket::readyRead,
+ this, &LauncherSocket::handleSocketDataAvailable);
+ emit ready();
+}
+
+void LauncherSocket::handleSocketError()
+{
+ handleError(Tr::tr("Socket error: %1").arg(m_socket->errorString()));
+}
+
+void LauncherSocket::handleSocketDataAvailable()
+{
+ 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;
+ }
+ switch (m_packetParser.type()) {
+ case LauncherPacketType::ProcessError:
+ case LauncherPacketType::ProcessFinished:
+ emit packetArrived(m_packetParser.type(), m_packetParser.token(),
+ m_packetParser.packetData());
+ break;
+ default:
+ handleError(Tr::tr("Internal protocol error: invalid packet type %1.")
+ .arg(static_cast<int>(m_packetParser.type())));
+ return;
+ }
+ handleSocketDataAvailable();
+}
+
+void LauncherSocket::handleError(const QString &error)
+{
+ m_socket->disconnect();
+ m_socket->deleteLater();
+ m_socket = nullptr;
+ emit errorOccurred(error);
+}
+
+void LauncherSocket::handleRequests()
+{
+ QMutexLocker locker(&m_requestsMutex);
+ for (auto it = m_requests.cbegin(); it != m_requests.cend(); ++it)
+ m_socket->write(*it);
+ m_requests.clear();
+}
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/tools/launchersocket.h b/src/lib/corelib/tools/launchersocket.h
new file mode 100644
index 000000000..130d9f337
--- /dev/null
+++ b/src/lib/corelib/tools/launchersocket.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBS_LAUNCHERSOCKET_H
+#define QBS_LAUNCHERSOCKET_H
+
+#include "launcherpackets.h"
+
+#include <QtCore/qbytearraylist.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qobject.h>
+
+QT_BEGIN_NAMESPACE
+class QLocalSocket;
+QT_END_NAMESPACE
+
+namespace qbs {
+namespace Internal {
+class LauncherInterface;
+
+class LauncherSocket : public QObject
+{
+ Q_OBJECT
+ friend class LauncherInterface;
+public:
+ bool isReady() const { return m_socket; }
+ void sendData(const QByteArray &data);
+
+signals:
+ void ready();
+ void errorOccurred(const QString &error);
+ void packetArrived(qbs::Internal::LauncherPacketType type, quintptr token,
+ const QByteArray &payload);
+
+private:
+ LauncherSocket(QObject *parent);
+
+ void setSocket(QLocalSocket *socket);
+ void shutdown();
+
+ void handleSocketError();
+ void handleSocketDataAvailable();
+ void handleError(const QString &error);
+ void handleRequests();
+
+ QLocalSocket *m_socket = nullptr;
+ PacketParser m_packetParser;
+ QByteArrayList m_requests;
+ QMutex m_requestsMutex;
+};
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // Include guard
diff --git a/src/lib/corelib/tools/qbsprocess.cpp b/src/lib/corelib/tools/qbsprocess.cpp
new file mode 100644
index 000000000..8495d72a0
--- /dev/null
+++ b/src/lib/corelib/tools/qbsprocess.cpp
@@ -0,0 +1,173 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qbsprocess.h"
+
+#include "launcherinterface.h"
+#include "launchersocket.h"
+#include "qbsassert.h"
+
+#include <logging/translator.h>
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qtimer.h>
+
+namespace qbs {
+namespace Internal {
+
+QbsProcess::QbsProcess(QObject *parent) : QObject(parent)
+{
+ connect(LauncherInterface::socket(), &LauncherSocket::ready,
+ this, &QbsProcess::handleSocketReady);
+ connect(LauncherInterface::socket(), &LauncherSocket::errorOccurred,
+ this, &QbsProcess::handleSocketError);
+ connect(LauncherInterface::socket(), &LauncherSocket::packetArrived,
+ this, &QbsProcess::handlePacket);
+}
+
+void QbsProcess::start(const QString &command, const QStringList &arguments)
+{
+ if (m_socketError) {
+ m_error = QProcess::FailedToStart;
+ emit error(m_error);
+ return;
+ }
+ m_command = command;
+ m_arguments = arguments;
+ m_state = QProcess::Starting;
+ if (LauncherInterface::socket()->isReady())
+ doStart();
+}
+
+void QbsProcess::doStart()
+{
+ m_state = QProcess::Running;
+ StartProcessPacket p(token());
+ p.command = m_command;
+ p.arguments = m_arguments;
+ p.env = m_environment.toStringList();
+ p.workingDir = m_workingDirectory;
+ sendPacket(p);
+}
+
+void QbsProcess::cancel()
+{
+ sendPacket(StopProcessPacket(token()));
+}
+
+QByteArray QbsProcess::readAllStandardOutput()
+{
+ return readAndClear(m_stdout);
+}
+
+QByteArray QbsProcess::readAllStandardError()
+{
+ return readAndClear(m_stderr);
+}
+
+void QbsProcess::sendPacket(const LauncherPacket &packet)
+{
+ LauncherInterface::socket()->sendData(packet.serialize());
+}
+
+QByteArray QbsProcess::readAndClear(QByteArray &data)
+{
+ const QByteArray tmp = data;
+ data.clear();
+ return tmp;
+}
+
+void QbsProcess::handlePacket(LauncherPacketType type, quintptr token, const QByteArray &payload)
+{
+ if (token != this->token())
+ return;
+ switch (type) {
+ case LauncherPacketType::ProcessError:
+ handleErrorPacket(payload);
+ break;
+ case LauncherPacketType::ProcessFinished:
+ handleFinishedPacket(payload);
+ break;
+ default:
+ QBS_ASSERT(false, break);
+ }
+}
+
+void QbsProcess::handleSocketReady()
+{
+ m_socketError = false;
+ if (m_state == QProcess::Starting)
+ doStart();
+}
+
+void QbsProcess::handleSocketError(const QString &message)
+{
+ m_socketError = true;
+ m_errorString = Tr::tr("Internal socket error: %1").arg(message);
+ if (m_state != QProcess::NotRunning) {
+ m_state = QProcess::NotRunning;
+ m_error = QProcess::FailedToStart;
+ emit error(m_error);
+ }
+}
+
+void QbsProcess::handleErrorPacket(const QByteArray &packetData)
+{
+ QBS_ASSERT(m_state != QProcess::NotRunning, return);
+ const auto packet = LauncherPacket::extractPacket<ProcessErrorPacket>(token(), packetData);
+ m_error = packet.error;
+ m_errorString = packet.errorString;
+ m_state = QProcess::NotRunning;
+ emit error(m_error);
+}
+
+void QbsProcess::handleFinishedPacket(const QByteArray &packetData)
+{
+ QBS_ASSERT(m_state == QProcess::Running, return);
+ m_state = QProcess::NotRunning;
+ const auto packet = LauncherPacket::extractPacket<ProcessFinishedPacket>(token(), packetData);
+ m_exitCode = packet.exitCode;
+ m_stdout = packet.stdOut;
+ m_stderr = packet.stdErr;
+ m_errorString = packet.errorString;
+ emit finished(m_exitCode);
+}
+
+} // namespace Internal
+} // namespace qbs
diff --git a/src/lib/corelib/tools/qbsprocess.h b/src/lib/corelib/tools/qbsprocess.h
new file mode 100644
index 000000000..9d2343010
--- /dev/null
+++ b/src/lib/corelib/tools/qbsprocess.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBS_QBSPROCESS_H
+#define QBS_QBSPROCESS_H
+
+#include "launcherpackets.h"
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qstringlist.h>
+
+namespace qbs {
+namespace Internal {
+
+class QbsProcess : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QbsProcess(QObject *parent = 0);
+
+ QProcess::ProcessState state() const { return m_state; }
+ void setProcessEnvironment(const QProcessEnvironment &env) { m_environment = env; }
+ void setWorkingDirectory(const QString &workingDir) { m_workingDirectory = workingDir; }
+ QString workingDirectory() const { return m_workingDirectory; }
+ void start(const QString &command, const QStringList &arguments);
+ void cancel();
+ QByteArray readAllStandardOutput();
+ QByteArray readAllStandardError();
+ int exitCode() const { return m_exitCode; }
+ QProcess::ProcessError error() const { return m_error; }
+ QString errorString() const { return m_errorString; }
+
+signals:
+ void error(QProcess::ProcessError error);
+ void finished(int exitCode);
+
+private:
+ void doStart();
+ void sendPacket(const LauncherPacket &packet);
+ QByteArray readAndClear(QByteArray &data);
+
+ void handleSocketError(const QString &message);
+ void handlePacket(qbs::Internal::LauncherPacketType type, quintptr token,
+ const QByteArray &payload);
+ void handleErrorPacket(const QByteArray &packetData);
+ void handleFinishedPacket(const QByteArray &packetData);
+ void handleSocketReady();
+
+ quintptr token() const { return reinterpret_cast<quintptr>(this); }
+
+ QString m_command;
+ QStringList m_arguments;
+ QProcessEnvironment m_environment;
+ QString m_workingDirectory;
+ QByteArray m_stdout;
+ QByteArray m_stderr;
+ QString m_errorString;
+ QProcess::ProcessError m_error = QProcess::UnknownError;
+ QProcess::ProcessState m_state = QProcess::NotRunning;
+ int m_exitCode;
+ int m_connectionAttempts = 0;
+ bool m_socketError = false;
+};
+
+} // namespace Internal
+} // namespace qbs
+
+#endif // QBSPROCESS_H
diff --git a/src/lib/corelib/tools/tools.pri b/src/lib/corelib/tools/tools.pri
index 337deb4a3..a9c8c8bc0 100644
--- a/src/lib/corelib/tools/tools.pri
+++ b/src/lib/corelib/tools/tools.pri
@@ -15,6 +15,9 @@ HEADERS += \
$$PWD/generateoptions.h \
$$PWD/id.h \
$$PWD/jsliterals.h \
+ $$PWD/launcherinterface.h \
+ $$PWD/launcherpackets.h \
+ $$PWD/launchersocket.h \
$$PWD/msvcinfo.h \
$$PWD/persistence.h \
$$PWD/scannerpluginmanager.h \
@@ -31,6 +34,7 @@ HEADERS += \
$$PWD/processutils.h \
$$PWD/progressobserver.h \
$$PWD/projectgeneratormanager.h \
+ $$PWD/qbsprocess.h \
$$PWD/shellutils.h \
$$PWD/toolchains.h \
$$PWD/hostosinfo.h \
@@ -61,6 +65,9 @@ SOURCES += \
$$PWD/generateoptions.cpp \
$$PWD/id.cpp \
$$PWD/jsliterals.cpp \
+ $$PWD/launcherinterface.cpp \
+ $$PWD/launcherpackets.cpp \
+ $$PWD/launchersocket.cpp \
$$PWD/msvcinfo.cpp \
$$PWD/persistence.cpp \
$$PWD/scannerpluginmanager.cpp \
@@ -74,6 +81,7 @@ SOURCES += \
$$PWD/profiling.cpp \
$$PWD/progressobserver.cpp \
$$PWD/projectgeneratormanager.cpp \
+ $$PWD/qbsprocess.cpp \
$$PWD/shellutils.cpp \
$$PWD/buildoptions.cpp \
$$PWD/installoptions.cpp \
diff --git a/src/libexec/libexec.pro b/src/libexec/libexec.pro
index 967108504..75b1d0844 100644
--- a/src/libexec/libexec.pro
+++ b/src/libexec/libexec.pro
@@ -1 +1,3 @@
TEMPLATE = subdirs
+
+SUBDIRS += qbs_processlauncher
diff --git a/src/libexec/libexec.qbs b/src/libexec/libexec.qbs
index 489864a26..a43e26157 100644
--- a/src/libexec/libexec.qbs
+++ b/src/libexec/libexec.qbs
@@ -2,5 +2,6 @@ import qbs
Project {
references: [
+ "qbs_processlauncher/qbs_processlauncher.qbs",
]
}
diff --git a/src/libexec/qbs_processlauncher/launcherlogging.cpp b/src/libexec/qbs_processlauncher/launcherlogging.cpp
new file mode 100644
index 000000000..10c42127d
--- /dev/null
+++ b/src/libexec/qbs_processlauncher/launcherlogging.cpp
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "launcherlogging.h"
+
+namespace qbs {
+namespace Internal {
+Q_LOGGING_CATEGORY(launcherLog, "qbs.launcher")
+}
+}
diff --git a/src/libexec/qbs_processlauncher/launcherlogging.h b/src/libexec/qbs_processlauncher/launcherlogging.h
new file mode 100644
index 000000000..356433a9f
--- /dev/null
+++ b/src/libexec/qbs_processlauncher/launcherlogging.h
@@ -0,0 +1,54 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#ifndef QBS_LAUCHERLOGGING_H
+#define QBS_LAUCHERLOGGING_H
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qstring.h>
+
+namespace qbs {
+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; }
+}
+}
+
+#endif // Include guard
diff --git a/src/libexec/qbs_processlauncher/launchersockethandler.cpp b/src/libexec/qbs_processlauncher/launchersockethandler.cpp
new file mode 100644
index 000000000..1f3f96034
--- /dev/null
+++ b/src/libexec/qbs_processlauncher/launchersockethandler.cpp
@@ -0,0 +1,289 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "launchersockethandler.h"
+
+#include "launcherlogging.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qtimer.h>
+#include <QtNetwork/qlocalsocket.h>
+
+namespace qbs {
+namespace Internal {
+
+// TODO: add stop state and timer here.
+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(const QString &serverPath, QObject *parent)
+ : QObject(parent),
+ m_serverPath(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,
+ static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
+ this, &LauncherSocketHandler::handleSocketError);
+ m_socket->connectToServer(m_serverPath);
+}
+
+void LauncherSocketHandler::handleSocketData()
+{
+ try {
+ if (!m_packetParser.parse())
+ return;
+ } catch (const PacketParser::InvalidPacketSizeException &e) {
+ logWarn(QString::fromLatin1("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(QString::fromLatin1("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(QString::fromLatin1("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();
+}
+
+void LauncherSocketHandler::handleProcessError()
+{
+ if (senderProcess()->error() != QProcess::FailedToStart)
+ return;
+ senderProcess()->stopStopProcedure();
+ ProcessErrorPacket packet(senderProcess()->token());
+ packet.error = senderProcess()->error();
+ packet.errorString = senderProcess()->errorString();
+ sendPacket(packet);
+}
+
+void LauncherSocketHandler::handleProcessFinished()
+{
+ senderProcess()->stopStopProcedure();
+ ProcessFinishedPacket packet(senderProcess()->token());
+ packet.error = senderProcess()->error();
+ packet.errorString = senderProcess()->errorString();
+ packet.exitCode = senderProcess()->exitCode();
+ packet.exitStatus = senderProcess()->exitStatus();
+ packet.stdErr = senderProcess()->readAllStandardError();
+ packet.stdOut = senderProcess()->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.
+ senderProcess()->disconnect();
+ m_processes.remove(senderProcess()->token());
+ ProcessFinishedPacket packet(senderProcess()->token());
+ packet.error = QProcess::Crashed;
+ packet.exitCode = -1;
+ packet.exitStatus = QProcess::CrashExit;
+ packet.stdErr = senderProcess()->readAllStandardError();
+ packet.stdOut = senderProcess()->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)
+{
+ Process * const p = new Process(token, this);
+ connect(p, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
+ this, &LauncherSocketHandler::handleProcessError);
+ connect(p, static_cast<void (QProcess::*)(int)>(&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 qbs
+
+#include <launchersockethandler.moc>
diff --git a/src/libexec/qbs_processlauncher/launchersockethandler.h b/src/libexec/qbs_processlauncher/launchersockethandler.h
new file mode 100644
index 000000000..e96c02e13
--- /dev/null
+++ b/src/libexec/qbs_processlauncher/launchersockethandler.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** 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 Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** 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-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QBS_LAUNCHERSOCKETHANDLER_H
+#define QBS_LAUNCHERSOCKETHANDLER_H
+
+#include <launcherpackets.h>
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qobject.h>
+
+QT_BEGIN_NAMESPACE
+class QLocalSocket;
+QT_END_NAMESPACE
+
+namespace qbs {
+namespace Internal {
+class Process;
+
+class LauncherSocketHandler : public QObject
+{
+ Q_OBJECT
+public:
+ explicit LauncherSocketHandler(const QString &socketPath, QObject *parent = nullptr);
+ ~LauncherSocketHandler();
+
+ 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 qbs
+
+#endif // Include guard
diff --git a/src/libexec/qbs_processlauncher/processlauncher-main.cpp b/src/libexec/qbs_processlauncher/processlauncher-main.cpp
new file mode 100644
index 000000000..9d51c9fa1
--- /dev/null
+++ b/src/libexec/qbs_processlauncher/processlauncher-main.cpp
@@ -0,0 +1,46 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qbs.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** 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.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "launcherlogging.h"
+#include "launchersockethandler.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qtimer.h>
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+ if (app.arguments().count() != 2) {
+ qbs::Internal::logError("Need exactly one argument (path to socket)");
+ return 1;
+ }
+
+ qbs::Internal::LauncherSocketHandler launcher(app.arguments().last());
+ QTimer::singleShot(0, &launcher, &qbs::Internal::LauncherSocketHandler::start);
+ return app.exec();
+}
diff --git a/src/libexec/qbs_processlauncher/qbs_processlauncher.pro b/src/libexec/qbs_processlauncher/qbs_processlauncher.pro
new file mode 100644
index 000000000..84142b5c5
--- /dev/null
+++ b/src/libexec/qbs_processlauncher/qbs_processlauncher.pro
@@ -0,0 +1,21 @@
+include(../libexec.pri)
+
+TARGET = qbs_processlauncher
+CONFIG += console c++11
+CONFIG -= app_bundle
+QT = core network
+
+TOOLS_DIR = $$PWD/../../lib/corelib/tools
+
+INCLUDEPATH += $$TOOLS_DIR
+
+HEADERS += \
+ launcherlogging.h \
+ launchersockethandler.h \
+ $$TOOLS_DIR/launcherpackets.h
+
+SOURCES += \
+ launcherlogging.cpp \
+ launchersockethandler.cpp \
+ processlauncher-main.cpp \
+ $$TOOLS_DIR/launcherpackets.cpp
diff --git a/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs b/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs
new file mode 100644
index 000000000..c641296ad
--- /dev/null
+++ b/src/libexec/qbs_processlauncher/qbs_processlauncher.qbs
@@ -0,0 +1,39 @@
+import qbs
+import qbs.FileInfo
+
+QbsProduct {
+ type: "application"
+ name: "qbs_processlauncher"
+ consoleApplication: true
+ destinationDirectory: FileInfo.joinPaths(project.buildDirectory,
+ qbsbuildconfig.libexecInstallDir)
+
+ Depends { name: "Qt.network" }
+
+ cpp.cxxLanguageVersion: "c++11"
+ cpp.includePaths: base.concat(pathToProtocolSources)
+
+ files: [
+ "launcherlogging.cpp",
+ "launcherlogging.h",
+ "launchersockethandler.cpp",
+ "launchersockethandler.h",
+ "processlauncher-main.cpp",
+ ]
+
+ property string pathToProtocolSources: sourceDirectory + "/../../lib/corelib/tools"
+ Group {
+ name: "protocol sources"
+ prefix: pathToProtocolSources + '/'
+ files: [
+ "launcherpackets.cpp",
+ "launcherpackets.h",
+ ]
+ }
+
+ Group {
+ fileTagsFilter: product.type
+ qbs.install: true
+ qbs.installDir: qbsbuildconfig.libexecInstallDir
+ }
+}