From 69cca88dbb9288d7b70f4e811cc4093b9173fbe8 Mon Sep 17 00:00:00 2001
From: Daniel Teske <daniel.teske@nokia.com>
Date: Thu, 7 Jul 2011 15:15:43 +0200
Subject: [PATCH] Add gui thread mode to BuildStep

Change-Id: I11501635bc5c17db557ea8379eb63c9d3915a05e
Reviewed-on: http://codereview.qt.nokia.com/1316
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Christian Kandeler <christian.kandeler@nokia.com>
---
 src/plugins/projectexplorer/buildmanager.cpp | 44 +++++++++++++++++++-
 src/plugins/projectexplorer/buildmanager.h   |  1 +
 src/plugins/projectexplorer/buildstep.cpp    | 30 ++++++++++++-
 src/plugins/projectexplorer/buildstep.h      |  4 ++
 4 files changed, 75 insertions(+), 4 deletions(-)

diff --git a/src/plugins/projectexplorer/buildmanager.cpp b/src/plugins/projectexplorer/buildmanager.cpp
index d729439ad6c..13f54b1c633 100644
--- a/src/plugins/projectexplorer/buildmanager.cpp
+++ b/src/plugins/projectexplorer/buildmanager.cpp
@@ -84,6 +84,7 @@ struct BuildManagerPrivate {
     ProjectExplorerPlugin *m_projectExplorerPlugin;
     bool m_running;
     QFutureWatcher<bool> m_watcher;
+    QFutureInterface<bool> m_futureInterfaceForAysnc;
     BuildStep *m_currentBuildStep;
     QString m_currentConfiguration;
     // used to decide if we are building a project to decide when to emit buildStateChanged(Project *)
@@ -91,6 +92,8 @@ struct BuildManagerPrivate {
     Project *m_previousBuildStepProject;
     // is set to true while canceling, so that nextBuildStep knows that the BuildStep finished because of canceling
     bool m_canceling;
+    bool m_doNotEnterEventLoop;
+    QEventLoop *m_eventLoop;
 
     // Progress reporting to the progress manager
     int m_progress;
@@ -103,6 +106,8 @@ BuildManagerPrivate::BuildManagerPrivate() :
     m_running(false)
   , m_previousBuildStepProject(0)
   , m_canceling(false)
+  , m_doNotEnterEventLoop(false)
+  , m_eventLoop(0)
   , m_maxProgress(0)
   , m_progressFutureInterface(0)
 {
@@ -190,7 +195,20 @@ void BuildManager::cancel()
     if (d->m_running) {
         d->m_canceling = true;
         d->m_watcher.cancel();
-        d->m_watcher.waitForFinished();
+        if (d->m_currentBuildStep->runInGuiThread()) {
+            // This is evil. A nested event loop.
+            d->m_currentBuildStep->cancel();
+            if (d->m_doNotEnterEventLoop) {
+                d->m_doNotEnterEventLoop = false;
+            } else {
+                d->m_eventLoop = new QEventLoop;
+                d->m_eventLoop->exec();
+                delete d->m_eventLoop;
+                d->m_eventLoop = 0;
+            }
+        } else {
+            d->m_watcher.waitForFinished();
+        }
 
         // The cancel message is added to the output window via a single shot timer
         // since the canceling is likely to have generated new addToOutputWindow signals
@@ -332,6 +350,21 @@ void BuildManager::addToOutputWindow(const QString &string, BuildStep::OutputFor
     d->m_outputWindow->appendText(stringToWrite, format);
 }
 
+void BuildManager::buildStepFinishedAsync()
+{
+    disconnect(d->m_currentBuildStep, SIGNAL(finished()),
+               this, SLOT(buildStepFinishedAsync()));
+    d->m_futureInterfaceForAysnc = QFutureInterface<bool>();
+    if (d->m_canceling) {
+        if (d->m_eventLoop)
+            d->m_eventLoop->exit();
+        else
+            d->m_doNotEnterEventLoop = true;
+    } else {
+        nextBuildQueue();
+    }
+}
+
 void BuildManager::nextBuildQueue()
 {
     if (d->m_canceling)
@@ -393,7 +426,14 @@ void BuildManager::nextStep()
                               .arg(projectName), BuildStep::MessageOutput);
             d->m_previousBuildStepProject = d->m_currentBuildStep->buildConfiguration()->target()->project();
         }
-        d->m_watcher.setFuture(QtConcurrent::run(&BuildStep::run, d->m_currentBuildStep));
+        if (d->m_currentBuildStep->runInGuiThread()) {
+            connect (d->m_currentBuildStep, SIGNAL(finished()),
+                     this, SLOT(buildStepFinishedAsync()));
+            d->m_watcher.setFuture(d->m_futureInterfaceForAysnc.future());
+            d->m_currentBuildStep->run(d->m_futureInterfaceForAysnc);
+        } else {
+            d->m_watcher.setFuture(QtConcurrent::run(&BuildStep::run, d->m_currentBuildStep));
+        }
     } else {
         d->m_running = false;
         d->m_previousBuildStepProject = 0;
diff --git a/src/plugins/projectexplorer/buildmanager.h b/src/plugins/projectexplorer/buildmanager.h
index 574a2cc77aa..1933cacdc4e 100644
--- a/src/plugins/projectexplorer/buildmanager.h
+++ b/src/plugins/projectexplorer/buildmanager.h
@@ -87,6 +87,7 @@ private slots:
     void addToOutputWindow(const QString &string, ProjectExplorer::BuildStep::OutputFormat,
         ProjectExplorer::BuildStep::OutputNewlineSetting = BuildStep::DoAppendNewline);
 
+    void buildStepFinishedAsync();
     void nextBuildQueue();
     void progressChanged();
     void progressTextChanged();
diff --git a/src/plugins/projectexplorer/buildstep.cpp b/src/plugins/projectexplorer/buildstep.cpp
index e4c397a3624..2a5ddaa961a 100644
--- a/src/plugins/projectexplorer/buildstep.cpp
+++ b/src/plugins/projectexplorer/buildstep.cpp
@@ -69,13 +69,18 @@
     \fn void ProjectExplorer::BuildStep::run(QFutureInterface<bool> &fi)
 
     Reimplement this. This function is called when the target is build.
-    This function is NOT run in the gui thread. It runs in its own thread
-    If you need an event loop, you need to create one.
+    By default this function is NOT run in the gui thread. It runs in its
+    own thread. If you need an event loop, you need to create one.
+    This function should block until the task is done
 
     The absolute minimal implementation is:
     \code
     fi.reportResult(true);
     \endcode
+
+    By returning true from \sa runInGuiThread() this function is called in the
+    gui thread. Then the function should not block and instead the
+    finished() signal should be emitted.
 */
 
 /*!
@@ -106,6 +111,17 @@
     It should be in plain text, with the format in the parameter.
 */
 
+/*!
+    \fn void ProjectExplorer::BuildStep::cancel() const
+
+    This function needs to be reimplemented only for BuildSteps that return false from \sa runInGuiThread.
+*/
+
+/*!
+    \fn  void ProjectExplorer::BuildStep::finished()
+    \brief This signal needs to be emitted if the BuildStep runs in the gui thread.
+*/
+
 using namespace ProjectExplorer;
 
 BuildStep::BuildStep(BuildStepList *bsl, const QString &id) :
@@ -150,6 +166,16 @@ bool BuildStep::immutable() const
     return false;
 }
 
+bool BuildStep::runInGuiThread() const
+{
+    return false;
+}
+
+void BuildStep::cancel()
+{
+    // Do nothing
+}
+
 IBuildStepFactory::IBuildStepFactory(QObject *parent) :
     QObject(parent)
 { }
diff --git a/src/plugins/projectexplorer/buildstep.h b/src/plugins/projectexplorer/buildstep.h
index b4b3d116ebc..886f6c048f9 100644
--- a/src/plugins/projectexplorer/buildstep.h
+++ b/src/plugins/projectexplorer/buildstep.h
@@ -68,6 +68,8 @@ public:
     virtual BuildStepConfigWidget *createConfigWidget() = 0;
 
     virtual bool immutable() const;
+    virtual bool runInGuiThread() const;
+    virtual void cancel();
 
     BuildConfiguration *buildConfiguration() const;
     DeployConfiguration *deployConfiguration() const;
@@ -81,6 +83,8 @@ signals:
 
     void addOutput(const QString &string, ProjectExplorer::BuildStep::OutputFormat format,
         ProjectExplorer::BuildStep::OutputNewlineSetting newlineSetting = DoAppendNewline) const;
+
+    void finished();
 };
 
 class PROJECTEXPLORER_EXPORT IBuildStepFactory :
-- 
GitLab