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 qt-info@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 BuildStep *m_currentBuildStep;
88 QString m_currentConfiguration;
89 // used to decide if we are building a project to decide when to emit buildStateChanged(Project *)
90 QHash<Project *, int> m_activeBuildSteps;
91 Project *m_previousBuildStepProject;
92 // is set to true while canceling, so that nextBuildStep knows that the BuildStep finished because of canceling
95 // Progress reporting to the progress manager
98 QFutureInterface<void> *m_progressFutureInterface;
99 QFutureWatcher<void> m_progressWatcher;
102 BuildManagerPrivate::BuildManagerPrivate() :
104 , m_previousBuildStepProject(0)
107 , m_progressFutureInterface(0)
111 BuildManager::BuildManager(ProjectExplorerPlugin *parent)
112 : QObject(parent), d(new BuildManagerPrivate)
114 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
115 d->m_projectExplorerPlugin = parent;
117 connect(&d->m_watcher, SIGNAL(finished()),
118 this, SLOT(nextBuildQueue()));
120 connect(&d->m_watcher, SIGNAL(progressValueChanged(int)),
121 this, SLOT(progressChanged()));
122 connect(&d->m_watcher, SIGNAL(progressTextChanged(QString)),
123 this, SLOT(progressTextChanged()));
124 connect(&d->m_watcher, SIGNAL(progressRangeChanged(int, int)),
125 this, SLOT(progressChanged()));
127 connect(parent->session(), SIGNAL(aboutToRemoveProject(ProjectExplorer::Project *)),
128 this, SLOT(aboutToRemoveProject(ProjectExplorer::Project *)));
130 d->m_outputWindow = new Internal::CompileOutputWindow(this);
131 pm->addObject(d->m_outputWindow);
133 d->m_taskHub = pm->getObject<TaskHub>();
134 d->m_taskWindow = new Internal::TaskWindow(d->m_taskHub);
135 pm->addObject(d->m_taskWindow);
137 qRegisterMetaType<ProjectExplorer::BuildStep::OutputFormat>();
139 connect(d->m_taskWindow, SIGNAL(tasksChanged()),
140 this, SLOT(updateTaskCount()));
142 connect(d->m_taskWindow, SIGNAL(tasksCleared()),
143 this,SIGNAL(tasksCleared()));
145 connect(&d->m_progressWatcher, SIGNAL(canceled()),
146 this, SLOT(cancel()));
147 connect(&d->m_progressWatcher, SIGNAL(finished()),
148 this, SLOT(finish()));
151 void BuildManager::extensionsInitialized()
153 d->m_taskHub->addCategory(Constants::TASK_CATEGORY_COMPILE, tr("Compile", "Category for compiler isses listened under 'Build Issues'"));
154 d->m_taskHub->addCategory(Constants::TASK_CATEGORY_BUILDSYSTEM, tr("Build System", "Category for build system isses listened under 'Build Issues'"));
157 BuildManager::~BuildManager()
160 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
162 pm->removeObject(d->m_taskWindow);
163 delete d->m_taskWindow;
165 pm->removeObject(d->m_outputWindow);
166 delete d->m_outputWindow;
169 void BuildManager::aboutToRemoveProject(ProjectExplorer::Project *p)
171 QHash<Project *, int>::iterator it = d->m_activeBuildSteps.find(p);
172 QHash<Project *, int>::iterator end = d->m_activeBuildSteps.end();
173 if (it != end && *it > 0) {
174 // We are building the project that's about to be removed.
175 // We cancel the whole queue, which isn't the nicest thing to do
181 bool BuildManager::isBuilding() const
183 // we are building even if we are not running yet
184 return !d->m_buildQueue.isEmpty() || d->m_running;
187 void BuildManager::cancel()
190 d->m_canceling = true;
191 d->m_watcher.cancel();
192 d->m_watcher.waitForFinished();
194 // The cancel message is added to the output window via a single shot timer
195 // since the canceling is likely to have generated new addToOutputWindow signals
196 // which are waiting in the event queue to be processed
197 // (And we want those to be before the cancel message.)
198 QTimer::singleShot(0, this, SLOT(emitCancelMessage()));
200 disconnect(d->m_currentBuildStep, SIGNAL(addTask(ProjectExplorer::Task)),
201 this, SLOT(addToTaskWindow(ProjectExplorer::Task)));
202 disconnect(d->m_currentBuildStep, SIGNAL(addOutput(QString, ProjectExplorer::BuildStep::OutputFormat)),
203 this, SLOT(addToOutputWindow(QString, ProjectExplorer::BuildStep::OutputFormat)));
204 decrementActiveBuildSteps(d->m_currentBuildStep->buildConfiguration()->target()->project());
206 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, tr("Build canceled")); //TODO NBS fix in qtconcurrent
212 void BuildManager::updateTaskCount()
214 Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager();
215 const int errors = d->m_taskWindow->errorTaskCount();
217 progressManager->setApplicationLabel(QString::number(errors));
219 progressManager->setApplicationLabel(QString());
224 void BuildManager::finish()
226 QApplication::alert(Core::ICore::instance()->mainWindow(), 3000);
229 void BuildManager::emitCancelMessage()
231 emit addToOutputWindow(tr("Canceled build."), BuildStep::ErrorMessageOutput);
234 void BuildManager::clearBuildQueue()
236 foreach (BuildStep *bs, d->m_buildQueue) {
237 decrementActiveBuildSteps(bs->buildConfiguration()->target()->project());
238 disconnect(bs, SIGNAL(addTask(ProjectExplorer::Task)),
239 this, SLOT(addToTaskWindow(ProjectExplorer::Task)));
240 disconnect(bs, SIGNAL(addOutput(QString, ProjectExplorer::BuildStep::OutputFormat)),
241 this, SLOT(addToOutputWindow(QString, ProjectExplorer::BuildStep::OutputFormat)));
244 d->m_buildQueue.clear();
245 d->m_running = false;
246 d->m_previousBuildStepProject = 0;
247 d->m_currentBuildStep = 0;
249 d->m_progressFutureInterface->reportCanceled();
250 d->m_progressFutureInterface->reportFinished();
251 d->m_progressWatcher.setFuture(QFuture<void>());
252 delete d->m_progressFutureInterface;
253 d->m_progressFutureInterface = 0;
254 d->m_maxProgress = 0;
256 emit buildQueueFinished(false);
260 void BuildManager::toggleOutputWindow()
262 d->m_outputWindow->toggle(false);
265 void BuildManager::showTaskWindow()
267 d->m_taskWindow->popup(false);
270 void BuildManager::toggleTaskWindow()
272 d->m_taskWindow->toggle(false);
275 bool BuildManager::tasksAvailable() const
277 return d->m_taskWindow->taskCount() > 0;
280 void BuildManager::startBuildQueue()
282 if (d->m_buildQueue.isEmpty()) {
283 emit buildQueueFinished(true);
287 // Progress Reporting
288 Core::ProgressManager *progressManager = Core::ICore::instance()->progressManager();
289 d->m_progressFutureInterface = new QFutureInterface<void>;
290 d->m_progressWatcher.setFuture(d->m_progressFutureInterface->future());
291 d->m_outputWindow->clearContents();
292 d->m_taskHub->clearTasks(Constants::TASK_CATEGORY_COMPILE);
293 d->m_taskHub->clearTasks(Constants::TASK_CATEGORY_BUILDSYSTEM);
294 progressManager->setApplicationLabel(QString());
295 Core::FutureProgress *progress = progressManager->addTask(d->m_progressFutureInterface->future(),
297 Constants::TASK_BUILD,
298 Core::ProgressManager::KeepOnFinish | Core::ProgressManager::ShowInApplicationIcon);
299 connect(progress, SIGNAL(clicked()), this, SLOT(showBuildResults()));
300 progress->setWidget(new Internal::BuildProgress(d->m_taskWindow));
302 d->m_progressFutureInterface->setProgressRange(0, d->m_maxProgress * 100);
305 d->m_canceling = false;
306 d->m_progressFutureInterface->reportStarted();
310 d->m_progressFutureInterface->setProgressRange(0, d->m_maxProgress * 100);
311 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, msgProgress(d->m_progress, d->m_maxProgress));
315 void BuildManager::showBuildResults()
317 if (d->m_taskWindow->taskCount() != 0)
320 toggleOutputWindow();
321 //toggleTaskWindow();
324 void BuildManager::addToTaskWindow(const ProjectExplorer::Task &task)
326 d->m_outputWindow->registerPositionOf(task);
327 // Distribute to all others
328 d->m_taskHub->addTask(task);
331 void BuildManager::addToOutputWindow(const QString &string, ProjectExplorer::BuildStep::OutputFormat format)
333 d->m_outputWindow->appendText(string, format);
336 void BuildManager::nextBuildQueue()
341 disconnect(d->m_currentBuildStep, SIGNAL(addTask(ProjectExplorer::Task)),
342 this, SLOT(addToTaskWindow(ProjectExplorer::Task)));
343 disconnect(d->m_currentBuildStep, SIGNAL(addOutput(QString, ProjectExplorer::BuildStep::OutputFormat)),
344 this, SLOT(addToOutputWindow(QString, ProjectExplorer::BuildStep::OutputFormat)));
347 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, msgProgress(d->m_progress, d->m_maxProgress));
348 decrementActiveBuildSteps(d->m_currentBuildStep->buildConfiguration()->target()->project());
350 bool result = d->m_watcher.result();
353 const QString projectName = d->m_currentBuildStep->buildConfiguration()->target()->project()->displayName();
354 const QString targetName = d->m_currentBuildStep->buildConfiguration()->target()->displayName();
355 addToOutputWindow(tr("Error while building project %1 (target: %2)").arg(projectName, targetName), BuildStep::ErrorOutput);
356 addToOutputWindow(tr("When executing build step '%1'").arg(d->m_currentBuildStep->displayName()), BuildStep::ErrorOutput);
357 // NBS TODO fix in qtconcurrent
358 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100, tr("Error while building project %1 (target: %2)").arg(projectName, targetName));
367 void BuildManager::progressChanged()
369 if (!d->m_progressFutureInterface)
371 int range = d->m_watcher.progressMaximum() - d->m_watcher.progressMinimum();
373 int percent = (d->m_watcher.progressValue() - d->m_watcher.progressMinimum()) * 100 / range;
374 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress * 100 + percent, msgProgress(d->m_progress, d->m_maxProgress) + "\n" + d->m_watcher.progressText());
378 void BuildManager::progressTextChanged()
380 int range = d->m_watcher.progressMaximum() - d->m_watcher.progressMinimum();
383 percent = (d->m_watcher.progressValue() - d->m_watcher.progressMinimum()) * 100 / range;
384 d->m_progressFutureInterface->setProgressValueAndText(d->m_progress*100 + percent, msgProgress(d->m_progress, d->m_maxProgress) + "\n" + d->m_watcher.progressText());
387 void BuildManager::nextStep()
389 if (!d->m_buildQueue.empty()) {
390 d->m_currentBuildStep = d->m_buildQueue.front();
391 d->m_buildQueue.pop_front();
393 if (d->m_currentBuildStep->buildConfiguration()->target()->project() != d->m_previousBuildStepProject) {
394 const QString projectName = d->m_currentBuildStep->buildConfiguration()->target()->project()->displayName();
395 addToOutputWindow(tr("Running build steps for project %1...")
396 .arg(projectName), BuildStep::MessageOutput);
397 d->m_previousBuildStepProject = d->m_currentBuildStep->buildConfiguration()->target()->project();
399 d->m_watcher.setFuture(QtConcurrent::run(&BuildStep::run, d->m_currentBuildStep));
401 d->m_running = false;
402 d->m_previousBuildStepProject = 0;
403 d->m_progressFutureInterface->reportFinished();
404 d->m_progressWatcher.setFuture(QFuture<void>());
405 d->m_currentBuildStep = 0;
406 delete d->m_progressFutureInterface;
407 d->m_progressFutureInterface = 0;
408 d->m_maxProgress = 0;
409 emit buildQueueFinished(true);
413 bool BuildManager::buildQueueAppend(QList<BuildStep *> steps)
415 int count = steps.size();
418 for (; i < count; ++i) {
419 BuildStep *bs = steps.at(i);
420 connect(bs, SIGNAL(addTask(ProjectExplorer::Task)),
421 this, SLOT(addToTaskWindow(ProjectExplorer::Task)));
422 connect(bs, SIGNAL(addOutput(QString, ProjectExplorer::BuildStep::OutputFormat)),
423 this, SLOT(addToOutputWindow(QString, ProjectExplorer::BuildStep::OutputFormat)));
429 BuildStep *bs = steps.at(i);
432 // print something for the user
433 const QString projectName = bs->buildConfiguration()->target()->project()->displayName();
434 const QString targetName = bs->buildConfiguration()->target()->displayName();
435 addToOutputWindow(tr("Error while building project %1 (target: %2)").arg(projectName, targetName), BuildStep::ErrorOutput);
436 addToOutputWindow(tr("When executing build step '%1'").arg(bs->displayName()), BuildStep::ErrorOutput);
438 // disconnect the buildsteps again
439 for (int j = 0; j <= i; ++j) {
440 BuildStep *bs = steps.at(j);
441 disconnect(bs, SIGNAL(addTask(ProjectExplorer::Task)),
442 this, SLOT(addToTaskWindow(ProjectExplorer::Task)));
443 disconnect(bs, SIGNAL(addOutput(QString, ProjectExplorer::BuildStep::OutputFormat)),
444 this, SLOT(addToOutputWindow(QString, ProjectExplorer::BuildStep::OutputFormat)));
449 // Everthing init() well
450 for (i = 0; i < count; ++i) {
452 d->m_buildQueue.append(steps.at(i));
453 incrementActiveBuildSteps(steps.at(i)->buildConfiguration()->target()->project());
458 bool BuildManager::buildList(BuildStepList *bsl)
460 return buildLists(QList<BuildStepList *>() << bsl);
463 bool BuildManager::buildLists(QList<BuildStepList *> bsls)
465 QList<BuildStep *> steps;
466 foreach(BuildStepList *list, bsls)
467 steps.append(list->steps());
469 bool success = buildQueueAppend(steps);
471 d->m_outputWindow->popup(false);
475 if (ProjectExplorerPlugin::instance()->projectExplorerSettings().showCompilerOutput)
476 d->m_outputWindow->popup(false);
481 void BuildManager::appendStep(BuildStep *step)
483 bool success = buildQueueAppend(QList<BuildStep *>() << step);
485 d->m_outputWindow->popup(false);
488 if (ProjectExplorerPlugin::instance()->projectExplorerSettings().showCompilerOutput)
489 d->m_outputWindow->popup(false);
493 bool BuildManager::isBuilding(Project *pro)
495 QHash<Project *, int>::iterator it = d->m_activeBuildSteps.find(pro);
496 QHash<Project *, int>::iterator end = d->m_activeBuildSteps.end();
497 if (it == end || *it == 0)
503 bool BuildManager::isBuilding(BuildStep *step)
505 return (d->m_currentBuildStep == step) || d->m_buildQueue.contains(step);
508 void BuildManager::incrementActiveBuildSteps(Project *pro)
510 QHash<Project *, int>::iterator it = d->m_activeBuildSteps.find(pro);
511 QHash<Project *, int>::iterator end = d->m_activeBuildSteps.end();
513 d->m_activeBuildSteps.insert(pro, 1);
514 emit buildStateChanged(pro);
515 } else if (*it == 0) {
517 emit buildStateChanged(pro);
523 void BuildManager::decrementActiveBuildSteps(Project *pro)
525 QHash<Project *, int>::iterator it = d->m_activeBuildSteps.find(pro);
526 QHash<Project *, int>::iterator end = d->m_activeBuildSteps.end();
528 Q_ASSERT(false && "BuildManager d->m_activeBuildSteps says project is not building, but apparently a build step was still in the queue.");
529 } else if (*it == 1) {
531 emit buildStateChanged(pro);
537 } // namespace ProjectExplorer