1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (info@qt.nokia.com)
10 ** GNU Lesser General Public License Usage
12 ** This file may be used under the terms of the GNU Lesser General Public
13 ** License version 2.1 as published by the Free Software Foundation and
14 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
15 ** Please review the following information to ensure the GNU Lesser General
16 ** Public License version 2.1 requirements will be met:
17 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
19 ** In addition, as a special exception, Nokia gives you certain additional
20 ** rights. These rights are described in the Nokia Qt LGPL Exception
21 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
25 ** Alternatively, this file may be used in accordance with the terms and
26 ** conditions contained in a signed written agreement between you and Nokia.
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at info@qt.nokia.com.
31 **************************************************************************/
33 #include "buildmanager.h"
35 #include "buildprogress.h"
36 #include "buildsteplist.h"
37 #include "compileoutputwindow.h"
38 #include "projectexplorerconstants.h"
39 #include "projectexplorer.h"
41 #include "projectexplorersettings.h"
43 #include "taskwindow.h"
45 #include "buildconfiguration.h"
47 #include <coreplugin/icore.h>
48 #include <coreplugin/progressmanager/progressmanager.h>
49 #include <coreplugin/progressmanager/futureprogress.h>
50 #include <projectexplorer/session.h>
51 #include <extensionsystem/pluginmanager.h>
52 #include <utils/qtcassert.h>
54 #include <QtCore/QDir>
55 #include <QtCore/QTimer>
56 #include <QtCore/QMetaType>
57 #include <QtCore/QList>
58 #include <QtCore/QHash>
59 #include <QtCore/QFutureWatcher>
61 #include <qtconcurrent/QtConcurrentTools>
63 #include <QtGui/QApplication>
64 #include <QtGui/QMainWindow>
66 static inline QString msgProgress(int progress, int total)
68 return ProjectExplorer::BuildManager::tr("Finished %1 of %n build steps", 0, total).arg(progress);
71 namespace ProjectExplorer {
72 //NBS TODO this class has too many different variables which hold state:
73 // m_buildQueue, m_running, m_canceled, m_progress, m_maxProgress, m_activeBuildSteps and ...
74 // I might need to reduce that.
75 struct BuildManagerPrivate {
76 BuildManagerPrivate();
78 Internal::CompileOutputWindow *m_outputWindow;
80 Internal::TaskWindow *m_taskWindow;
82 QList<BuildStep *> m_buildQueue;
83 QStringList m_configurations; // the corresponding configuration to the m_buildQueue
84 ProjectExplorerPlugin *m_projectExplorerPlugin;
86 QFutureWatcher<bool> m_watcher;
87 QFutureInterface<bool> m_futureInterfaceForAysnc;
88 BuildStep *m_currentBuildStep;
89 QString m_currentConfiguration;
90 // used to decide if we are building a project to decide when to emit buildStateChanged(Project *)
91 QHash<Project *, int> m_activeBuildSteps;
92 Project *m_previousBuildStepProject;
93 // is set to true while canceling, so that nextBuildStep knows that the BuildStep finished because of canceling
95 bool m_doNotEnterEventLoop;
96 QEventLoop *m_eventLoop;
98 // Progress reporting to the progress manager
101 QFutureInterface<void> *m_progressFutureInterface;
102 QFutureWatcher<void> m_progressWatcher;
105 BuildManagerPrivate::BuildManagerPrivate() :
107 , m_previousBuildStepProject(0)
109 , m_doNotEnterEventLoop(false)
112 , m_progressFutureInterface(0)
116 BuildManager::BuildManager(ProjectExplorerPlugin *parent)
117 : QObject(parent), d(new BuildManagerPrivate)
119 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
120 d->m_projectExplorerPlugin = parent;
122 connect(&d->m_watcher, SIGNAL(finished()),
123 this, SLOT(nextBuildQueue()));
125 connect(&d->m_watcher, SIGNAL(progressValueChanged(int)),
126 this, SLOT(progressChanged()));
127 connect(&d->m_watcher, SIGNAL(progressTextChanged(QString)),
128 this, SLOT(progressTextChanged()));
129 connect(&d->m_watcher, SIGNAL(progressRangeChanged(int, int)),
130 this, SLOT(progressChanged()));
132 connect(parent->session(), SIGNAL(aboutToRemoveProject(ProjectExplorer::Project *)),
133 this, SLOT(aboutToRemoveProject(ProjectExplorer::Project *)));
135 d->m_outputWindow = new Internal::CompileOutputWindow(this);
136 pm->addObject(d->m_outputWindow);
138 d->m_taskHub = pm->getObject<TaskHub>();
139 d->m_taskWindow = new Internal::TaskWindow(d->m_taskHub);
140 pm->addObject(d->m_taskWindow);
142 qRegisterMetaType<ProjectExplorer::BuildStep::OutputFormat>();
143 qRegisterMetaType<ProjectExplorer::BuildStep::OutputNewlineSetting>();
145 connect(d->m_taskWindow, SIGNAL(tasksChanged()),
146 this, SLOT(updateTaskCount()));
148 connect(d->m_taskWindow, SIGNAL(tasksCleared()),
149 this,SIGNAL(tasksCleared()));
151 connect(&d->m_progressWatcher, SIGNAL(canceled()),
152 this, SLOT(cancel()));
153 connect(&d->m_progressWatcher, SIGNAL(finished()),
154 this, SLOT(finish()));
157 void BuildManager::extensionsInitialized()
159 d->m_taskHub->addCategory(Constants::TASK_CATEGORY_COMPILE,
160 tr("Compile", "Category for compiler isses listened under 'Build Issues'"));
161 d->m_taskHub->addCategory(Constants::TASK_CATEGORY_BUILDSYSTEM,
162 tr("Build System", "Category for build system isses listened under 'Build Issues'"));
165 BuildManager::~BuildManager()
168 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
170 pm->removeObject(d->m_taskWindow);
171 delete d->m_taskWindow;
173 pm->removeObject(d->m_outputWindow);
174 delete d->m_outputWindow;
179 void BuildManager::aboutToRemoveProject(ProjectExplorer::Project *p)
181 QHash<Project *, int>::iterator it = d->m_activeBuildSteps.find(p);
182 QHash<Project *, int>::iterator end = d->m_activeBuildSteps.end();
183 if (it != end && *it > 0) {
184 // We are building the project that's about to be removed.
185 // We cancel the whole queue, which isn't the nicest thing to do
191 bool BuildManager::isBuilding() const
193 // we are building even if we are not running yet
194 return !d->m_buildQueue.isEmpty() || d->m_running;
197 void BuildManager::cancel()
200 d->m_canceling = true;
201 d->m_watcher.cancel();
202 if (d->m_currentBuildStep->runInGuiThread()) {
203 // This is evil. A nested event loop.
204 d->m_currentBuildStep->cancel();
205 if (d->m_doNotEnterEventLoop) {
206 d->m_doNotEnterEventLoop = false;
208 d->m_eventLoop = new QEventLoop;
209 d->m_eventLoop->exec();
210 delete d->m_eventLoop;
214 d->m_watcher.waitForFinished();
217 // The cancel message is added to the output window via a single shot timer
218 // since the canceling is likely to have generated new addToOutputWindow signals
219 // which are waiting in the event queue to be processed
220 // (And we want those to be before the cancel message.)
221 QTimer::singleShot(0, this, SLOT(emitCancelMessage()));
223 disconnectOutput(d->m_currentBuildStep);
224 decrementActiveBuildSteps(d->m_currentBuildStep->buildConfiguration()->target()->project());
226 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, tr("Build canceled")); //TODO NBS fix in qtconcurrent
232 void BuildManager::updateTaskCount()
234 Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager();
236 d->m_taskWindow->errorTaskCount(Constants::TASK_CATEGORY_BUILDSYSTEM)
237 + d->m_taskWindow->errorTaskCount(Constants::TASK_CATEGORY_COMPILE);
239 progressManager->setApplicationLabel(QString::number(errors));
241 progressManager->setApplicationLabel(QString());
246 void BuildManager::finish()
248 QApplication::alert(Core::ICore::instance()->mainWindow(), 3000);
251 void BuildManager::emitCancelMessage()
253 addToOutputWindow(tr("Canceled build."), BuildStep::ErrorMessageOutput);
256 void BuildManager::clearBuildQueue()
258 foreach (BuildStep *bs, d->m_buildQueue) {
259 decrementActiveBuildSteps(bs->buildConfiguration()->target()->project());
260 disconnectOutput(bs);
263 d->m_buildQueue.clear();
264 d->m_running = false;
265 d->m_previousBuildStepProject = 0;
266 d->m_currentBuildStep = 0;
268 d->m_progressFutureInterface->reportCanceled();
269 d->m_progressFutureInterface->reportFinished();
270 d->m_progressWatcher.setFuture(QFuture<void>());
271 delete d->m_progressFutureInterface;
272 d->m_progressFutureInterface = 0;
273 d->m_maxProgress = 0;
275 emit buildQueueFinished(false);
279 void BuildManager::toggleOutputWindow()
281 d->m_outputWindow->toggle(false);
284 void BuildManager::showTaskWindow()
286 d->m_taskWindow->popup(false);
289 void BuildManager::toggleTaskWindow()
291 d->m_taskWindow->toggle(false);
294 bool BuildManager::tasksAvailable() const
297 d->m_taskWindow->taskCount(Constants::TASK_CATEGORY_BUILDSYSTEM)
298 + d->m_taskWindow->taskCount(Constants::TASK_CATEGORY_COMPILE);
302 void BuildManager::startBuildQueue()
304 if (d->m_buildQueue.isEmpty()) {
305 emit buildQueueFinished(true);
309 // Progress Reporting
310 Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager();
311 d->m_progressFutureInterface = new QFutureInterface<void>;
312 d->m_progressWatcher.setFuture(d->m_progressFutureInterface->future());
313 d->m_outputWindow->clearContents();
314 d->m_taskHub->clearTasks(Constants::TASK_CATEGORY_COMPILE);
315 d->m_taskHub->clearTasks(Constants::TASK_CATEGORY_BUILDSYSTEM);
316 progressManager->setApplicationLabel(QString());
317 Core::FutureProgress *progress = progressManager->addTask(d->m_progressFutureInterface->future(),
319 Constants::TASK_BUILD,
320 Core::ProgressManager::KeepOnFinish | Core::ProgressManager::ShowInApplicationIcon);
321 connect(progress, SIGNAL(clicked()), this, SLOT(showBuildResults()));
322 progress->setWidget(new Internal::BuildProgress(d->m_taskWindow));
324 d->m_progressFutureInterface->setProgressRange(0, d->m_maxProgress * 100);
327 d->m_canceling = false;
328 d->m_progressFutureInterface->reportStarted();
332 d->m_progressFutureInterface->setProgressRange(0, d->m_maxProgress * 100);
333 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, msgProgress(d->m_progress, d->m_maxProgress));
337 void BuildManager::showBuildResults()
339 if (tasksAvailable())
342 toggleOutputWindow();
343 //toggleTaskWindow();
346 void BuildManager::addToTaskWindow(const ProjectExplorer::Task &task)
348 d->m_outputWindow->registerPositionOf(task);
349 // Distribute to all others
350 d->m_taskHub->addTask(task);
353 void BuildManager::addToOutputWindow(const QString &string, BuildStep::OutputFormat format,
354 BuildStep::OutputNewlineSetting newLineSetting)
356 QString stringToWrite = string;
357 if (newLineSetting == BuildStep::DoAppendNewline)
358 stringToWrite += QLatin1Char('\n');
359 d->m_outputWindow->appendText(stringToWrite, format);
362 void BuildManager::buildStepFinishedAsync()
364 disconnect(d->m_currentBuildStep, SIGNAL(finished()),
365 this, SLOT(buildStepFinishedAsync()));
366 d->m_futureInterfaceForAysnc = QFutureInterface<bool>();
367 if (d->m_canceling) {
369 d->m_eventLoop->exit();
371 d->m_doNotEnterEventLoop = true;
377 void BuildManager::nextBuildQueue()
382 disconnectOutput(d->m_currentBuildStep);
384 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, msgProgress(d->m_progress, d->m_maxProgress));
385 decrementActiveBuildSteps(d->m_currentBuildStep->buildConfiguration()->target()->project());
387 bool result = d->m_watcher.result();
390 const QString projectName = d->m_currentBuildStep->buildConfiguration()->target()->project()->displayName();
391 const QString targetName = d->m_currentBuildStep->buildConfiguration()->target()->displayName();
392 addToOutputWindow(tr("Error while building project %1 (target: %2)").arg(projectName, targetName), BuildStep::ErrorOutput);
393 addToOutputWindow(tr("When executing build step '%1'").arg(d->m_currentBuildStep->displayName()), BuildStep::ErrorOutput);
394 // NBS TODO fix in qtconcurrent
395 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, tr("Error while building project %1 (target: %2)").arg(projectName, targetName));
404 void BuildManager::progressChanged()
406 if (!d->m_progressFutureInterface)
408 int range = d->m_watcher.progressMaximum() - d->m_watcher.progressMinimum();
410 int percent = (d->m_watcher.progressValue() - d->m_watcher.progressMinimum()) * 100 / range;
411 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress * 100 + percent, msgProgress(d->m_progress, d->m_maxProgress)
412 + QLatin1Char('\n') + d->m_watcher.progressText());
416 void BuildManager::progressTextChanged()
418 int range = d->m_watcher.progressMaximum() - d->m_watcher.progressMinimum();
421 percent = (d->m_watcher.progressValue() - d->m_watcher.progressMinimum()) * 100 / range;
422 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100 + percent, msgProgress(d->m_progress, d->m_maxProgress) +
423 QLatin1Char('\n') + d->m_watcher.progressText());
426 void BuildManager::nextStep()
428 if (!d->m_buildQueue.empty()) {
429 d->m_currentBuildStep = d->m_buildQueue.front();
430 d->m_buildQueue.pop_front();
432 if (d->m_currentBuildStep->buildConfiguration()->target()->project() != d->m_previousBuildStepProject) {
433 const QString projectName = d->m_currentBuildStep->buildConfiguration()->target()->project()->displayName();
434 addToOutputWindow(tr("Running build steps for project %1...")
435 .arg(projectName), BuildStep::MessageOutput);
436 d->m_previousBuildStepProject = d->m_currentBuildStep->buildConfiguration()->target()->project();
438 if (d->m_currentBuildStep->runInGuiThread()) {
439 connect (d->m_currentBuildStep, SIGNAL(finished()),
440 this, SLOT(buildStepFinishedAsync()));
441 d->m_watcher.setFuture(d->m_futureInterfaceForAysnc.future());
442 d->m_currentBuildStep->run(d->m_futureInterfaceForAysnc);
444 d->m_watcher.setFuture(QtConcurrent::run(&BuildStep::run, d->m_currentBuildStep));
447 d->m_running = false;
448 d->m_previousBuildStepProject = 0;
449 d->m_progressFutureInterface->reportFinished();
450 d->m_progressWatcher.setFuture(QFuture<void>());
451 d->m_currentBuildStep = 0;
452 delete d->m_progressFutureInterface;
453 d->m_progressFutureInterface = 0;
454 d->m_maxProgress = 0;
455 emit buildQueueFinished(true);
459 bool BuildManager::buildQueueAppend(QList<BuildStep *> steps)
461 int count = steps.size();
464 for (; i < count; ++i) {
465 BuildStep *bs = steps.at(i);
466 connect(bs, SIGNAL(addTask(ProjectExplorer::Task)),
467 this, SLOT(addToTaskWindow(ProjectExplorer::Task)));
468 connect(bs, SIGNAL(addOutput(QString, ProjectExplorer::BuildStep::OutputFormat, ProjectExplorer::BuildStep::OutputNewlineSetting)),
469 this, SLOT(addToOutputWindow(QString, ProjectExplorer::BuildStep::OutputFormat, ProjectExplorer::BuildStep::OutputNewlineSetting)));
475 BuildStep *bs = steps.at(i);
478 // print something for the user
479 const QString projectName = bs->buildConfiguration()->target()->project()->displayName();
480 const QString targetName = bs->buildConfiguration()->target()->displayName();
481 addToOutputWindow(tr("Error while building project %1 (target: %2)").arg(projectName, targetName), BuildStep::ErrorOutput);
482 addToOutputWindow(tr("When executing build step '%1'").arg(bs->displayName()), BuildStep::ErrorOutput);
484 // disconnect the buildsteps again
485 for (int j = 0; j <= i; ++j)
486 disconnectOutput(steps.at(j));
490 // Everthing init() well
491 for (i = 0; i < count; ++i) {
493 d->m_buildQueue.append(steps.at(i));
494 incrementActiveBuildSteps(steps.at(i)->buildConfiguration()->target()->project());
499 bool BuildManager::buildList(BuildStepList *bsl)
501 return buildLists(QList<BuildStepList *>() << bsl);
504 bool BuildManager::buildLists(QList<BuildStepList *> bsls)
506 QList<BuildStep *> steps;
507 foreach(BuildStepList *list, bsls)
508 steps.append(list->steps());
510 bool success = buildQueueAppend(steps);
512 d->m_outputWindow->popup(false);
516 if (ProjectExplorerPlugin::instance()->projectExplorerSettings().showCompilerOutput)
517 d->m_outputWindow->popup(false);
522 void BuildManager::appendStep(BuildStep *step)
524 bool success = buildQueueAppend(QList<BuildStep *>() << step);
526 d->m_outputWindow->popup(false);
529 if (ProjectExplorerPlugin::instance()->projectExplorerSettings().showCompilerOutput)
530 d->m_outputWindow->popup(false);
534 bool BuildManager::isBuilding(Project *pro)
536 QHash<Project *, int>::iterator it = d->m_activeBuildSteps.find(pro);
537 QHash<Project *, int>::iterator end = d->m_activeBuildSteps.end();
538 if (it == end || *it == 0)
544 bool BuildManager::isBuilding(BuildStep *step)
546 return (d->m_currentBuildStep == step) || d->m_buildQueue.contains(step);
549 void BuildManager::incrementActiveBuildSteps(Project *pro)
551 QHash<Project *, int>::iterator it = d->m_activeBuildSteps.find(pro);
552 QHash<Project *, int>::iterator end = d->m_activeBuildSteps.end();
554 d->m_activeBuildSteps.insert(pro, 1);
555 emit buildStateChanged(pro);
556 } else if (*it == 0) {
558 emit buildStateChanged(pro);
564 void BuildManager::decrementActiveBuildSteps(Project *pro)
566 QHash<Project *, int>::iterator it = d->m_activeBuildSteps.find(pro);
567 QHash<Project *, int>::iterator end = d->m_activeBuildSteps.end();
569 Q_ASSERT(false && "BuildManager d->m_activeBuildSteps says project is not building, but apparently a build step was still in the queue.");
570 } else if (*it == 1) {
572 emit buildStateChanged(pro);
578 void BuildManager::disconnectOutput(BuildStep *bs)
580 disconnect(bs, SIGNAL(addTask(ProjectExplorer::Task)),
581 this, SLOT(addToTaskWindow(ProjectExplorer::Task)));
582 disconnect(bs, SIGNAL(addOutput(QString, ProjectExplorer::BuildStep::OutputFormat,
583 ProjectExplorer::BuildStep::OutputNewlineSetting)),
584 this, SLOT(addToOutputWindow(QString, ProjectExplorer::BuildStep::OutputFormat,
585 ProjectExplorer::BuildStep::OutputNewlineSetting)));
588 } // namespace ProjectExplorer