aboutsummaryrefslogtreecommitdiffstats
path: root/src/libexec
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2016-12-20 10:06:49 +0100
committerJoerg Bornemann <joerg.bornemann@qt.io>2017-01-25 11:30:02 +0000
commit557775b1ad3316ec158b9d930bf733b4fa502456 (patch)
tree1911bcdcf65c7ed1d106f101ef15e7083c56af7e /src/libexec
parentab89e84d71b277744df98a7037263e7bd44efbeb (diff)
Use a dedicated launcher process to run all ProcessCommands
On Linux (and very likely other Unices as well), QProcess::start() incurs a certain overhead, because forking off the child process duplicates some of the parent process' resources. This overhead appears to be proportional to the amount of memory allocated by the parent process, presumably due to page table entries getting copied. This has consequences for qbs, particularly when being used from an IDE such as Qt Creator, which has a higher memory footprint than the command line tool. When using a high job count, as is typical on machines with lots of CPU cores or in a distributed compilation environment, the following problems were observed: - High CPU load in the starting process (Qt Creator). Profiling showed that most of the time was spent in fork() and related functions. - As a result, the number of parallel jobs stalled at a value well below the requested one, slowing down the build. - In some cases, QProcess::start() failed altogether, emitting a message such as "fork() failed: Could not allocate memory". We solve these issues by outsourcing the starting of ProcessCommands to a dedicated launcher tool with modest memory requirements. For each qbs process, we have one instance of this tool running while a build job is going on. Communication with qbs happens via QLocalSocket. The protocol is encapsulated in a QProcess replacement, so almost no changes to existing code were necessary. No performance regressions were observed when using lower job counts. This patch will also enable us to properly support the incredibuild tool in IDEs such as Qt Creator, which you do not want to start via ibconsole. [ChangeLog] Improved scalability of parallel builds on Linux by starting Process commands via a dedicated launcher process. Change-Id: I8966c00a2d67a94c3a380f0e089d61eda122209e Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@qt.io> Reviewed-by: Joerg Bornemann <joerg.bornemann@qt.io>
Diffstat (limited to 'src/libexec')
-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
9 files changed, 590 insertions, 0 deletions
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
+ }
+}