summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoerg Bornemann <joerg.bornemann@theqtcompany.com>2015-04-10 17:13:43 +0200
committerJoerg Bornemann <joerg.bornemann@theqtcompany.com>2015-05-11 10:21:25 +0000
commita17aa85c4ae30d9a44fc2b10e919d94c3dfa3af3 (patch)
tree61e0a4b4f6d3b981e4931d69d5156ebeeabf2efb
parent98a33f71e75047a529b32f3c95b9fd479873371d (diff)
implement job server
Instead of trying to block recursive jom calls, use a job server similar to what GNU make provides. Task-number: QTCREATORBUG-10846 Change-Id: I0e17bb3a4e22e911da58f90e1bebc20aa5e6c75a Reviewed-by: Oliver Wolff <oliver.wolff@theqtcompany.com>
-rw-r--r--CMakeLists.txt5
-rw-r--r--src/app/main.cpp53
-rw-r--r--src/jomlib/filetime.h1
-rw-r--r--src/jomlib/jobclient.cpp114
-rw-r--r--src/jomlib/jobclient.h69
-rw-r--r--src/jomlib/jobclientacquirehelper.cpp40
-rw-r--r--src/jomlib/jobclientacquirehelper.h47
-rw-r--r--src/jomlib/jobserver.cpp70
-rw-r--r--src/jomlib/jobserver.h51
-rw-r--r--src/jomlib/jomlib.pro11
-rw-r--r--src/jomlib/targetexecutor.cpp137
-rw-r--r--src/jomlib/targetexecutor.h13
12 files changed, 553 insertions, 58 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1c48734..3c0dd02 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,6 +42,8 @@ set(JOM_MOCS
src/jomlib/targetexecutor.h
src/jomlib/process.h
src/jomlib/commandexecutor.h
+ src/jomlib/jobclient.h
+ src/jomlib/jobclientacquirehelper.h
)
set(JOM_SRCS
@@ -52,6 +54,9 @@ set(JOM_SRCS
src/jomlib/filetime.cpp
src/jomlib/helperfunctions.cpp
src/jomlib/iocompletionport.cpp
+ src/jomlib/jobclient.cpp
+ src/jomlib/jobclientacquirehelper.cpp
+ src/jomlib/jobserver.cpp
src/jomlib/macrotable.cpp
src/jomlib/makefile.cpp
src/jomlib/makefilefactory.cpp
diff --git a/src/app/main.cpp b/src/app/main.cpp
index 4192720..3ed24af 100644
--- a/src/app/main.cpp
+++ b/src/app/main.cpp
@@ -19,6 +19,7 @@
****************************************************************************/
#include "application.h"
#include <helperfunctions.h>
+#include <jobserver.h>
#include <options.h>
#include <parser.h>
#include <preprocessor.h>
@@ -107,6 +108,42 @@ QStringList getCommandLineArguments()
return commandLineArguments;
}
+static bool initJobServer(const Application &app, ProcessEnvironment *environment,
+ JobServer **outJobServer)
+{
+ bool mustCreateJobServer = false;
+ if (app.isSubJOM()) {
+ int inheritedMaxNumberOfJobs = g_options.maxNumberOfJobs;
+ const QString str = environment->value(QLatin1String("_JOMJOBCOUNT_"));
+ if (!str.isEmpty()) {
+ bool ok;
+ const int n = str.toInt(&ok);
+ if (ok && n > 0)
+ inheritedMaxNumberOfJobs = n;
+ }
+ if (g_options.isMaxNumberOfJobsSet
+ && g_options.maxNumberOfJobs != inheritedMaxNumberOfJobs)
+ {
+ fprintf(stderr, "jom: Overriding inherited number of jobs %d with %d. "
+ "New jobserver created.\n",
+ inheritedMaxNumberOfJobs, g_options.maxNumberOfJobs);
+ mustCreateJobServer = true;
+ }
+ } else {
+ mustCreateJobServer = true;
+ }
+
+ if (mustCreateJobServer) {
+ JobServer *jobServer = new JobServer(environment);
+ *outJobServer = jobServer;
+ if (!jobServer->start(g_options.maxNumberOfJobs)) {
+ fprintf(stderr, "Cannot start job server: %s.", qPrintable(jobServer->errorString()));
+ return false;
+ }
+ }
+ return true;
+}
+
int main(int argc, char* argv[])
{
int result = 0;
@@ -152,15 +189,11 @@ int main(int argc, char* argv[])
mkfile->dumpTargets();
}
- // ### HACK: pass the modified MAKEFLAGS variable to our environment.
- if (g_options.isMaxNumberOfJobsSet) {
- ProcessEnvironment environment = mkfile->macroTable()->environment();
- const QString makeFlags = mkfile->macroTable()->macroValue(QLatin1String("MAKEFLAGS"));
- environment[QLatin1String("MAKEFLAGS")] = makeFlags;
- MacroTable *mt = const_cast<MacroTable *>(mkfile->macroTable());
- mt->setEnvironment(environment);
- qSetEnvironmentVariable(QLatin1String("MAKEFLAGS"), makeFlags);
- }
+ JobServer *jobServer = 0;
+ ProcessEnvironment processEnvironment = mkfile->macroTable()->environment();
+ if (!initJobServer(app, &processEnvironment, &jobServer))
+ return 3;
+ QScopedPointer<JobServer> jobServerDeleter(jobServer);
if (options->printWorkingDir) {
printf("jom: Entering directory '%s\n",
@@ -168,7 +201,7 @@ int main(int argc, char* argv[])
fflush(stdout);
}
- TargetExecutor executor(mkfile->macroTable()->environment());
+ TargetExecutor executor(processEnvironment);
QObject::connect(&executor, SIGNAL(finished(int)), &app, SLOT(exit(int)));
g_pTargetExecutor = &executor;
executor.apply(mkfile.data(), mf.activeTargets());
diff --git a/src/jomlib/filetime.h b/src/jomlib/filetime.h
index 148120e..d9d159c 100644
--- a/src/jomlib/filetime.h
+++ b/src/jomlib/filetime.h
@@ -50,6 +50,7 @@ public:
void clear();
bool isValid() const;
QString toString() const;
+ InternalType internalRepresentation() const { return m_fileTime; }
static FileTime currentTime();
diff --git a/src/jomlib/jobclient.cpp b/src/jomlib/jobclient.cpp
new file mode 100644
index 0000000..4391794
--- /dev/null
+++ b/src/jomlib/jobclient.cpp
@@ -0,0 +1,114 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the jom project on Trolltech Labs.
+ **
+ ** This file may be used under the terms of the GNU General Public
+ ** License version 2.0 or 3.0 as published by the Free Software Foundation
+ ** and appearing in the file LICENSE.GPL included in the packaging of
+ ** this file. Please review the following information to ensure GNU
+ ** General Public Licensing requirements will be met:
+ ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+ ** http://www.gnu.org/copyleft/gpl.html.
+ **
+ ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ **
+ ****************************************************************************/
+
+#include "jobclient.h"
+#include "jobclientacquirehelper.h"
+#include "helperfunctions.h"
+
+#include <QSystemSemaphore>
+#include <QThread>
+
+namespace NMakeFile {
+
+JobClient::JobClient(ProcessEnvironment *environment, QObject *parent)
+ : QObject(parent)
+ , m_environment(environment)
+ , m_semaphore(0)
+ , m_acquireThread(new QThread(this))
+ , m_acquireHelper(0)
+ , m_isAcquiring(false)
+{
+}
+
+JobClient::~JobClient()
+{
+ if (isAcquiring())
+ qWarning("JobClient destroyed while still acquiring.");
+ m_acquireThread->quit();
+ m_acquireThread->wait(2500);
+ delete m_acquireHelper;
+ delete m_semaphore;
+}
+
+bool JobClient::start()
+{
+ Q_ASSERT(!m_semaphore && !m_acquireHelper);
+ Q_ASSERT(!m_acquireThread->isRunning());
+
+ const QString semaphoreKey = m_environment->value(QLatin1String("_JOMSRVKEY_"));
+ if (semaphoreKey.isEmpty()) {
+ setError(QLatin1String("Cannot determine jobserver name."));
+ return false;
+ }
+ m_semaphore = new QSystemSemaphore(semaphoreKey);
+ if (m_semaphore->error() != QSystemSemaphore::NoError) {
+ setError(m_semaphore->errorString());
+ return false;
+ }
+
+ m_acquireHelper = new JobClientAcquireHelper(m_semaphore);
+ m_acquireHelper->moveToThread(m_acquireThread);
+ connect(this, &JobClient::startAcquisition, m_acquireHelper, &JobClientAcquireHelper::acquire);
+ connect(m_acquireHelper, &JobClientAcquireHelper::acquired, this, &JobClient::onHelperAcquired);
+ m_acquireThread->start();
+ return true;
+}
+
+void JobClient::asyncAcquire()
+{
+ Q_ASSERT(m_semaphore);
+ Q_ASSERT(m_acquireHelper);
+ Q_ASSERT(m_acquireThread->isRunning());
+
+ m_isAcquiring = true;
+ emit startAcquisition();
+}
+
+void JobClient::onHelperAcquired()
+{
+ m_isAcquiring = false;
+ emit acquired();
+}
+
+bool JobClient::isAcquiring() const
+{
+ return m_isAcquiring;
+}
+
+void JobClient::release()
+{
+ Q_ASSERT(m_semaphore);
+
+ if (!m_semaphore->release())
+ qWarning("QSystemSemaphore::release failed: %s (%d)",
+ qPrintable(m_semaphore->errorString()), m_semaphore->error());
+}
+
+QString JobClient::errorString() const
+{
+ return m_errorString;
+}
+
+void JobClient::setError(const QString &errorMessage)
+{
+ m_errorString = errorMessage;
+}
+
+} // namespace NMakeFile
diff --git a/src/jomlib/jobclient.h b/src/jomlib/jobclient.h
new file mode 100644
index 0000000..b6e844d
--- /dev/null
+++ b/src/jomlib/jobclient.h
@@ -0,0 +1,69 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the jom project on Trolltech Labs.
+ **
+ ** This file may be used under the terms of the GNU General Public
+ ** License version 2.0 or 3.0 as published by the Free Software Foundation
+ ** and appearing in the file LICENSE.GPL included in the packaging of
+ ** this file. Please review the following information to ensure GNU
+ ** General Public Licensing requirements will be met:
+ ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+ ** http://www.gnu.org/copyleft/gpl.html.
+ **
+ ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ **
+ ****************************************************************************/
+
+#ifndef JOBCLIENT_H
+#define JOBCLIENT_H
+
+#include "processenvironment.h"
+#include <QObject>
+
+QT_BEGIN_NAMESPACE
+class QSystemSemaphore;
+class QThread;
+QT_END_NAMESPACE
+
+namespace NMakeFile {
+
+class JobClientAcquireHelper;
+
+class JobClient : public QObject
+{
+ Q_OBJECT
+public:
+ explicit JobClient(ProcessEnvironment *environment, QObject *parent = 0);
+ ~JobClient();
+
+ bool start();
+ void asyncAcquire();
+ bool isAcquiring() const;
+ void release();
+ QString errorString() const;
+
+signals:
+ void startAcquisition();
+ void acquired();
+
+private slots:
+ void onHelperAcquired();
+
+private:
+ void setError(const QString &errorMessage);
+
+ ProcessEnvironment *m_environment;
+ QString m_errorString;
+ QSystemSemaphore *m_semaphore;
+ QThread *m_acquireThread;
+ JobClientAcquireHelper *m_acquireHelper;
+ bool m_isAcquiring;
+};
+
+} // namespace NMakeFile
+
+#endif // JOBCLIENT_H
diff --git a/src/jomlib/jobclientacquirehelper.cpp b/src/jomlib/jobclientacquirehelper.cpp
new file mode 100644
index 0000000..a92913b
--- /dev/null
+++ b/src/jomlib/jobclientacquirehelper.cpp
@@ -0,0 +1,40 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the jom project on Trolltech Labs.
+ **
+ ** This file may be used under the terms of the GNU General Public
+ ** License version 2.0 or 3.0 as published by the Free Software Foundation
+ ** and appearing in the file LICENSE.GPL included in the packaging of
+ ** this file. Please review the following information to ensure GNU
+ ** General Public Licensing requirements will be met:
+ ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+ ** http://www.gnu.org/copyleft/gpl.html.
+ **
+ ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ **
+ ****************************************************************************/
+
+#include "jobclientacquirehelper.h"
+
+namespace NMakeFile {
+
+JobClientAcquireHelper::JobClientAcquireHelper(QSystemSemaphore *semaphore)
+ : m_semaphore(semaphore)
+{
+}
+
+void JobClientAcquireHelper::acquire()
+{
+ if (!m_semaphore->acquire()) {
+ qWarning("QSystemSemaphore::acquire failed: %s (%d)",
+ qPrintable(m_semaphore->errorString()), m_semaphore->error());
+ return;
+ }
+ emit acquired();
+}
+
+} // namespace NMakeFile
diff --git a/src/jomlib/jobclientacquirehelper.h b/src/jomlib/jobclientacquirehelper.h
new file mode 100644
index 0000000..d0f82c8
--- /dev/null
+++ b/src/jomlib/jobclientacquirehelper.h
@@ -0,0 +1,47 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the jom project on Trolltech Labs.
+ **
+ ** This file may be used under the terms of the GNU General Public
+ ** License version 2.0 or 3.0 as published by the Free Software Foundation
+ ** and appearing in the file LICENSE.GPL included in the packaging of
+ ** this file. Please review the following information to ensure GNU
+ ** General Public Licensing requirements will be met:
+ ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+ ** http://www.gnu.org/copyleft/gpl.html.
+ **
+ ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ **
+ ****************************************************************************/
+
+#ifndef JOBCLIENTACQUIRETHREAD_H
+#define JOBCLIENTACQUIRETHREAD_H
+
+#include <QObject>
+#include <QSystemSemaphore>
+
+namespace NMakeFile {
+
+class JobClientAcquireHelper : public QObject
+{
+ Q_OBJECT
+public:
+ explicit JobClientAcquireHelper(QSystemSemaphore *semaphore);
+
+public slots:
+ void acquire();
+
+signals:
+ void acquired();
+
+private:
+ QSystemSemaphore *m_semaphore;
+};
+
+} // namespace NMakeFile
+
+#endif // JOBCLIENTACQUIRETHREAD_H
diff --git a/src/jomlib/jobserver.cpp b/src/jomlib/jobserver.cpp
new file mode 100644
index 0000000..cd4ce67
--- /dev/null
+++ b/src/jomlib/jobserver.cpp
@@ -0,0 +1,70 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the jom project on Trolltech Labs.
+ **
+ ** This file may be used under the terms of the GNU General Public
+ ** License version 2.0 or 3.0 as published by the Free Software Foundation
+ ** and appearing in the file LICENSE.GPL included in the packaging of
+ ** this file. Please review the following information to ensure GNU
+ ** General Public Licensing requirements will be met:
+ ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+ ** http://www.gnu.org/copyleft/gpl.html.
+ **
+ ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ **
+ ****************************************************************************/
+
+#include "jobserver.h"
+#include "filetime.h"
+#include "helperfunctions.h"
+#include <QByteArray>
+#include <QCoreApplication>
+#include <QSystemSemaphore>
+
+namespace NMakeFile {
+
+JobServer::JobServer(ProcessEnvironment *environment)
+ : m_semaphore(0)
+ , m_environment(environment)
+{
+}
+
+JobServer::~JobServer()
+{
+ delete m_semaphore;
+}
+
+bool JobServer::start(int maxNumberOfJobs)
+{
+ Q_ASSERT(m_environment);
+
+ const uint randomId = (FileTime::currentTime().internalRepresentation() % UINT_MAX)
+ ^ reinterpret_cast<uint>(&maxNumberOfJobs);
+ const QString semaphoreKey = QLatin1String("jomsrv-")
+ + QString::number(QCoreApplication::applicationPid()) + QLatin1Char('-')
+ + QString::number(randomId);
+ m_semaphore = new QSystemSemaphore(semaphoreKey, maxNumberOfJobs - 1, QSystemSemaphore::Create);
+ if (m_semaphore->error() != QSystemSemaphore::NoError) {
+ setError(m_semaphore->errorString());
+ return false;
+ }
+ m_environment->insert(QLatin1String("_JOMSRVKEY_"), semaphoreKey);
+ m_environment->insert(QLatin1String("_JOMJOBCOUNT_"), QString::number(maxNumberOfJobs));
+ return true;
+}
+
+QString JobServer::errorString() const
+{
+ return m_errorString;
+}
+
+void JobServer::setError(const QString &errorMessage)
+{
+ m_errorString = errorMessage;
+}
+
+} // namespace NMakeFile
diff --git a/src/jomlib/jobserver.h b/src/jomlib/jobserver.h
new file mode 100644
index 0000000..30251bb
--- /dev/null
+++ b/src/jomlib/jobserver.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd.
+ ** Contact: http://www.qt.io/licensing/
+ **
+ ** This file is part of the jom project on Trolltech Labs.
+ **
+ ** This file may be used under the terms of the GNU General Public
+ ** License version 2.0 or 3.0 as published by the Free Software Foundation
+ ** and appearing in the file LICENSE.GPL included in the packaging of
+ ** this file. Please review the following information to ensure GNU
+ ** General Public Licensing requirements will be met:
+ ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+ ** http://www.gnu.org/copyleft/gpl.html.
+ **
+ ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
+ ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ **
+ ****************************************************************************/
+
+#ifndef JOBSERVER_H
+#define JOBSERVER_H
+
+#include "processenvironment.h"
+
+QT_BEGIN_NAMESPACE
+class QSystemSemaphore;
+QT_END_NAMESPACE
+
+namespace NMakeFile {
+
+class JobServer
+{
+public:
+ JobServer(ProcessEnvironment *environment);
+ ~JobServer();
+
+ bool start(int maxNumberOfJobs);
+ QString errorString() const;
+
+private:
+ void setError(const QString &errorMessage);
+
+ QString m_errorString;
+ QSystemSemaphore *m_semaphore;
+ ProcessEnvironment *m_environment;
+};
+
+} // namespace NMakeFile
+
+#endif // JOBSERVER_H
diff --git a/src/jomlib/jomlib.pro b/src/jomlib/jomlib.pro
index e81e65e..eefb768 100644
--- a/src/jomlib/jomlib.pro
+++ b/src/jomlib/jomlib.pro
@@ -57,6 +57,7 @@ HEADERS += \
fastfileinfo.h \
filetime.h \
helperfunctions.h \
+ jobserver.h \
makefile.h \
makefilefactory.h \
makefilelinereader.h \
@@ -70,12 +71,16 @@ HEADERS += \
targetexecutor.h \
commandexecutor.h \
process.h \
- processenvironment.h
+ processenvironment.h \
+ iocompletionport.h \
+ jobclient.h \
+ jobclientacquirehelper.h
SOURCES += \
fastfileinfo.cpp \
filetime.cpp \
helperfunctions.cpp \
+ jobserver.cpp \
macrotable.cpp \
makefile.cpp \
makefilefactory.cpp \
@@ -88,7 +93,9 @@ SOURCES += \
ppexpr_grammar.cpp \
ppexprparser.cpp \
targetexecutor.cpp \
- commandexecutor.cpp
+ commandexecutor.cpp \
+ jobclient.cpp \
+ jobclientacquirehelper.cpp
OTHER_FILES += \
ppexpr.g \
diff --git a/src/jomlib/targetexecutor.cpp b/src/jomlib/targetexecutor.cpp
index 3ea3f04..a9a6228 100644
--- a/src/jomlib/targetexecutor.cpp
+++ b/src/jomlib/targetexecutor.cpp
@@ -21,6 +21,7 @@
#include "targetexecutor.h"
#include "commandexecutor.h"
#include "dependencygraph.h"
+#include "jobclient.h"
#include "options.h"
#include "exception.h"
@@ -31,25 +32,29 @@
namespace NMakeFile {
TargetExecutor::TargetExecutor(const ProcessEnvironment &environment)
-: m_bAborted(false),
- m_allCommandsSuccessfullyExecuted(true)
+ : m_environment(environment)
+ , m_jobClient(0)
+ , m_bAborted(false)
+ , m_allCommandsSuccessfullyExecuted(true)
{
m_makefile = 0;
m_depgraph = new DependencyGraph();
- for (int i=0; i < g_options.maxNumberOfJobs; ++i) {
- CommandExecutor* process = new CommandExecutor(this, environment);
- if (i == 0) process->setBufferedOutput(false);
- connect(process, SIGNAL(finished(CommandExecutor*, bool)), this, SLOT(onChildFinished(CommandExecutor*, bool)));
- m_availableProcesses.append(process);
- m_processes.append(process);
- }
+ for (int i = 0; i < g_options.maxNumberOfJobs; ++i) {
+ CommandExecutor* executor = new CommandExecutor(this, environment);
+ connect(executor, SIGNAL(finished(CommandExecutor*, bool)),
+ this, SLOT(onChildFinished(CommandExecutor*, bool)));
- foreach (CommandExecutor *process, m_processes)
- foreach (CommandExecutor *otherProcess, m_processes)
- if (process != otherProcess)
- connect(process, SIGNAL(environmentChanged(const ProcessEnvironment &)),
- otherProcess, SLOT(setEnvironment(const ProcessEnvironment &)));
+ foreach (CommandExecutor *other, m_processes) {
+ connect(executor, SIGNAL(environmentChanged(const ProcessEnvironment &)),
+ other, SLOT(setEnvironment(const ProcessEnvironment &)));
+ connect(other, SIGNAL(environmentChanged(const ProcessEnvironment &)),
+ executor, SLOT(setEnvironment(const ProcessEnvironment &)));
+ }
+ m_processes.append(executor);
+ }
+ m_availableProcesses = m_processes;
+ m_availableProcesses.first()->setBufferedOutput(false);
}
TargetExecutor::~TargetExecutor()
@@ -57,16 +62,22 @@ TargetExecutor::~TargetExecutor()
delete m_depgraph;
}
-bool TargetExecutor::hasPendingTargets() const
-{
- return !m_pendingTargets.isEmpty() || m_availableProcesses.count() < m_processes.count();
-}
-
void TargetExecutor::apply(Makefile* mkfile, const QStringList& targets)
{
m_bAborted = false;
m_allCommandsSuccessfullyExecuted = true;
m_makefile = mkfile;
+ m_jobAcquisitionCount = 0;
+ m_nextTarget = 0;
+
+ if (!m_jobClient) {
+ m_jobClient = new JobClient(&m_environment, this);
+ if (!m_jobClient->start()) {
+ const QString msg = QLatin1String("Can't connect to job server: %1");
+ throw Exception(msg.arg(m_jobClient->errorString()));
+ }
+ connect(m_jobClient, &JobClient::acquired, this, &TargetExecutor::buildNextTarget);
+ }
DescriptionBlock* descblock;
if (targets.isEmpty()) {
@@ -93,39 +104,40 @@ void TargetExecutor::apply(Makefile* mkfile, const QStringList& targets)
else
m_depgraph->dump();
finishBuild(0);
+ return;
}
+
+ QMetaObject::invokeMethod(this, "startProcesses", Qt::QueuedConnection);
}
void TargetExecutor::startProcesses()
{
- if (m_bAborted)
+ if (m_bAborted || m_jobClient->isAcquiring() || m_availableProcesses.isEmpty())
return;
try {
- DescriptionBlock* nextTarget = 0;
- while (!m_availableProcesses.isEmpty() && (nextTarget = m_depgraph->findAvailableTarget())) {
- if (nextTarget->m_commands.isEmpty()) {
- // Short cut for targets without commands.
- // We're not really interested in these.
- m_depgraph->removeLeaf(nextTarget);
- continue;
- }
-
- CommandExecutor* process = m_availableProcesses.takeFirst();
- process->start(nextTarget);
- if (m_bAborted)
- return;
- }
+ if (!m_nextTarget)
+ findNextTarget();
- if (m_availableProcesses.count() == g_options.maxNumberOfJobs) {
- if (m_pendingTargets.isEmpty()) {
- finishBuild(0);
+ if (m_nextTarget) {
+ if (numberOfRunningProcesses() == 0) {
+ // Use up the internal job token.
+ buildNextTarget();
} else {
- m_depgraph->clear();
- nextTarget = m_pendingTargets.takeFirst();
- m_makefile->invalidateTimeStamps();
- m_depgraph->build(nextTarget);
- QMetaObject::invokeMethod(this, "startProcesses", Qt::QueuedConnection);
+ // Acquire a job token from the server. Will call buildNextTarget() when done.
+ m_jobAcquisitionCount++;
+ m_jobClient->asyncAcquire();
+ }
+ } else {
+ if (numberOfRunningProcesses() == 0) {
+ if (m_pendingTargets.isEmpty()) {
+ finishBuild(0);
+ } else {
+ m_depgraph->clear();
+ m_makefile->invalidateTimeStamps();
+ m_depgraph->build(m_pendingTargets.takeFirst());
+ QMetaObject::invokeMethod(this, "startProcesses", Qt::QueuedConnection);
+ }
}
}
} catch (Exception &e) {
@@ -135,6 +147,25 @@ void TargetExecutor::startProcesses()
}
}
+void TargetExecutor::buildNextTarget()
+{
+ Q_ASSERT(m_nextTarget);
+
+ if (m_bAborted)
+ return;
+
+ try {
+ CommandExecutor *executor = m_availableProcesses.takeFirst();
+ executor->start(m_nextTarget);
+ m_nextTarget = 0;
+ QMetaObject::invokeMethod(this, "startProcesses", Qt::QueuedConnection);
+ } catch (const Exception &e) {
+ m_bAborted = true;
+ fprintf(stderr, "Error: %s\n", qPrintable(e.message()));
+ finishBuild(1);
+ }
+}
+
void TargetExecutor::waitForProcesses()
{
foreach (CommandExecutor* process, m_processes)
@@ -153,11 +184,28 @@ void TargetExecutor::finishBuild(int exitCode)
emit finished(exitCode);
}
+void TargetExecutor::findNextTarget()
+{
+ forever {
+ m_nextTarget = m_depgraph->findAvailableTarget();
+ if (m_nextTarget && m_nextTarget->m_commands.isEmpty()) {
+ // Short cut for targets without commands.
+ m_depgraph->removeLeaf(m_nextTarget);
+ } else {
+ return;
+ }
+ }
+}
+
void TargetExecutor::onChildFinished(CommandExecutor* executor, bool commandFailed)
{
Q_CHECK_PTR(executor->target());
FastFileInfo::clearCacheForFile(executor->target()->targetName());
m_depgraph->removeLeaf(executor->target());
+ if (m_jobAcquisitionCount > 0) {
+ m_jobClient->release();
+ m_jobAcquisitionCount--;
+ }
m_availableProcesses.append(executor);
if (!executor->isBufferedOutputSet()) {
executor->setBufferedOutput(true);
@@ -187,6 +235,11 @@ void TargetExecutor::onChildFinished(CommandExecutor* executor, bool commandFail
QMetaObject::invokeMethod(this, "startProcesses", Qt::QueuedConnection);
}
+int TargetExecutor::numberOfRunningProcesses() const
+{
+ return m_processes.count() - m_availableProcesses.count();
+}
+
void TargetExecutor::removeTempFiles()
{
foreach (QObject* child, children()) {
diff --git a/src/jomlib/targetexecutor.h b/src/jomlib/targetexecutor.h
index 7f61f2b..f5497d2 100644
--- a/src/jomlib/targetexecutor.h
+++ b/src/jomlib/targetexecutor.h
@@ -33,6 +33,7 @@ namespace NMakeFile {
class CommandExecutor;
class DependencyGraph;
+class JobClient;
class TargetExecutor : public QObject {
Q_OBJECT
@@ -42,28 +43,32 @@ public:
void apply(Makefile* mkfile, const QStringList& targets);
void removeTempFiles();
- bool hasPendingTargets() const;
signals:
void finished(int exitCode);
-public slots:
- void startProcesses();
-
private slots:
+ void startProcesses();
+ void buildNextTarget();
void onChildFinished(CommandExecutor*, bool commandFailed);
private:
+ int numberOfRunningProcesses() const;
void waitForProcesses();
void finishBuild(int exitCode);
+ void findNextTarget();
private:
+ ProcessEnvironment m_environment;
Makefile* m_makefile;
DependencyGraph* m_depgraph;
QList<DescriptionBlock*> m_pendingTargets;
+ JobClient *m_jobClient;
bool m_bAborted;
+ int m_jobAcquisitionCount;
QList<CommandExecutor*> m_availableProcesses;
QList<CommandExecutor*> m_processes;
+ DescriptionBlock *m_nextTarget;
bool m_allCommandsSuccessfullyExecuted;
};