diff options
author | Joerg Bornemann <joerg.bornemann@theqtcompany.com> | 2015-04-10 17:13:43 +0200 |
---|---|---|
committer | Joerg Bornemann <joerg.bornemann@theqtcompany.com> | 2015-05-11 10:21:25 +0000 |
commit | a17aa85c4ae30d9a44fc2b10e919d94c3dfa3af3 (patch) | |
tree | 61e0a4b4f6d3b981e4931d69d5156ebeeabf2efb | |
parent | 98a33f71e75047a529b32f3c95b9fd479873371d (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.txt | 5 | ||||
-rw-r--r-- | src/app/main.cpp | 53 | ||||
-rw-r--r-- | src/jomlib/filetime.h | 1 | ||||
-rw-r--r-- | src/jomlib/jobclient.cpp | 114 | ||||
-rw-r--r-- | src/jomlib/jobclient.h | 69 | ||||
-rw-r--r-- | src/jomlib/jobclientacquirehelper.cpp | 40 | ||||
-rw-r--r-- | src/jomlib/jobclientacquirehelper.h | 47 | ||||
-rw-r--r-- | src/jomlib/jobserver.cpp | 70 | ||||
-rw-r--r-- | src/jomlib/jobserver.h | 51 | ||||
-rw-r--r-- | src/jomlib/jomlib.pro | 11 | ||||
-rw-r--r-- | src/jomlib/targetexecutor.cpp | 137 | ||||
-rw-r--r-- | src/jomlib/targetexecutor.h | 13 |
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; }; |