1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "qmakeproject.h"
27
28 #include "qmakebuildconfiguration.h"
29 #include "qmakebuildinfo.h"
30 #include "qmakenodes.h"
31 #include "qmakenodetreebuilder.h"
32 #include "qmakeprojectimporter.h"
33 #include "qmakeprojectmanagerconstants.h"
34 #include "qmakestep.h"
35
36 #include <coreplugin/documentmanager.h>
37 #include <coreplugin/icontext.h>
38 #include <coreplugin/icore.h>
39 #include <coreplugin/progressmanager/progressmanager.h>
40
41 #include <cpptools/cppmodelmanager.h>
42 #include <cpptools/cppprojectupdater.h>
43 #include <cpptools/generatedcodemodelsupport.h>
44 #include <cpptools/projectinfo.h>
45
46 #include <projectexplorer/buildinfo.h>
47 #include <projectexplorer/buildmanager.h>
48 #include <projectexplorer/buildtargetinfo.h>
49 #include <projectexplorer/deploymentdata.h>
50 #include <projectexplorer/headerpath.h>
51 #include <projectexplorer/projectexplorer.h>
52 #include <projectexplorer/projectexplorerconstants.h>
53 #include <projectexplorer/rawprojectpart.h>
54 #include <projectexplorer/runconfiguration.h>
55 #include <projectexplorer/target.h>
56 #include <projectexplorer/taskhub.h>
57 #include <projectexplorer/toolchain.h>
58 #include <projectexplorer/toolchainmanager.h>
59
60 #include <proparser/qmakevfs.h>
61 #include <proparser/qmakeglobals.h>
62
63 #include <qtsupport/profilereader.h>
64 #include <qtsupport/qtcppkitinfo.h>
65 #include <qtsupport/qtkitinformation.h>
66 #include <qtsupport/qtversionmanager.h>
67
68 #include <utils/algorithm.h>
69 #include <utils/runextensions.h>
70 #include <qmljs/qmljsmodelmanagerinterface.h>
71
72 #include <QDebug>
73 #include <QDir>
74 #include <QFileSystemWatcher>
75 #include <QFuture>
76 #include <QLoggingCategory>
77 #include <QTimer>
78
79 using namespace QmakeProjectManager::Internal;
80 using namespace ProjectExplorer;
81 using namespace QtSupport;
82 using namespace Utils;
83
84 namespace QmakeProjectManager {
85 namespace Internal {
86
87 const int UPDATE_INTERVAL = 3000;
88
89 static Q_LOGGING_CATEGORY(qmakeBuildSystemLog, "qtc.qmake.buildsystem", QtWarningMsg);
90
91 #define TRACE(msg) \
92 if (qmakeBuildSystemLog().isDebugEnabled()) { \
93 qCDebug(qmakeBuildSystemLog) \
94 << qPrintable(buildConfiguration()->displayName()) \
95 << ", guards project: " << int(m_guard.guardsProject()) \
96 << ", isParsing: " << int(isParsing()) \
97 << ", hasParsingData: " << int(hasParsingData()) \
98 << ", " << __FUNCTION__ \
99 << msg; \
100 }
101
102 class QmakePriFileDocument : public Core::IDocument
103 {
104 public:
QmakePriFileDocument(QmakePriFile * qmakePriFile,const Utils::FilePath & filePath)105 QmakePriFileDocument(QmakePriFile *qmakePriFile, const Utils::FilePath &filePath) :
106 IDocument(nullptr), m_priFile(qmakePriFile)
107 {
108 setId("Qmake.PriFile");
109 setMimeType(QLatin1String(QmakeProjectManager::Constants::PROFILE_MIMETYPE));
110 setFilePath(filePath);
111 Core::DocumentManager::addDocument(this);
112 }
113
reloadBehavior(ChangeTrigger state,ChangeType type) const114 ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const override
115 {
116 Q_UNUSED(state)
117 Q_UNUSED(type)
118 return BehaviorSilent;
119 }
reload(QString * errorString,ReloadFlag flag,ChangeType type)120 bool reload(QString *errorString, ReloadFlag flag, ChangeType type) override
121 {
122 Q_UNUSED(errorString)
123 Q_UNUSED(flag)
124 Q_UNUSED(type)
125 if (m_priFile)
126 m_priFile->scheduleUpdate();
127 return true;
128 }
129
setPriFile(QmakePriFile * priFile)130 void setPriFile(QmakePriFile *priFile) { m_priFile = priFile; }
131
132 private:
133 QmakePriFile *m_priFile;
134 };
135
136 /// Watches folders for QmakePriFile nodes
137 /// use one file system watcher to watch all folders
138 /// such minimizing system ressouce usage
139
140 class CentralizedFolderWatcher : public QObject
141 {
142 Q_OBJECT
143 public:
144 CentralizedFolderWatcher(QmakeBuildSystem *BuildSystem);
145
146 void watchFolders(const QList<QString> &folders, QmakePriFile *file);
147 void unwatchFolders(const QList<QString> &folders, QmakePriFile *file);
148
149 private:
150 void folderChanged(const QString &folder);
151 void onTimer();
152 void delayedFolderChanged(const QString &folder);
153
154 QmakeBuildSystem *m_buildSystem;
155 QSet<QString> recursiveDirs(const QString &folder);
156 QFileSystemWatcher m_watcher;
157 QMultiMap<QString, QmakePriFile *> m_map;
158
159 QSet<QString> m_recursiveWatchedFolders;
160 QTimer m_compressTimer;
161 QSet<QString> m_changedFolders;
162 };
163
164 } // namespace Internal
165
166 /*!
167 \class QmakeProject
168
169 QmakeProject manages information about an individual qmake project file (.pro).
170 */
171
QmakeProject(const FilePath & fileName)172 QmakeProject::QmakeProject(const FilePath &fileName) :
173 Project(QmakeProjectManager::Constants::PROFILE_MIMETYPE, fileName)
174 {
175 setId(Constants::QMAKEPROJECT_ID);
176 setProjectLanguages(Core::Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID));
177 setDisplayName(fileName.completeBaseName());
178 setCanBuildProducts();
179 setHasMakeInstallEquivalent(true);
180 }
181
~QmakeProject()182 QmakeProject::~QmakeProject()
183 {
184 delete m_projectImporter;
185 m_projectImporter = nullptr;
186
187 // Make sure root node (and associated readers) are shut hown before proceeding
188 setRootProjectNode(nullptr);
189 }
190
fromMap(const QVariantMap & map,QString * errorMessage)191 Project::RestoreResult QmakeProject::fromMap(const QVariantMap &map, QString *errorMessage)
192 {
193 RestoreResult result = Project::fromMap(map, errorMessage);
194 if (result != RestoreResult::Ok)
195 return result;
196
197 // Prune targets without buildconfigurations:
198 // This can happen esp. when updating from a old version of Qt Creator
199 QList<Target *>ts = targets();
200 foreach (Target *t, ts) {
201 if (t->buildConfigurations().isEmpty()) {
202 qWarning() << "Removing" << t->id().name() << "since it has no buildconfigurations!";
203 removeTarget(t);
204 }
205 }
206
207 return RestoreResult::Ok;
208 }
209
deploymentKnowledge() const210 DeploymentKnowledge QmakeProject::deploymentKnowledge() const
211 {
212 return DeploymentKnowledge::Approximative; // E.g. QTCREATORBUG-21855
213 }
214
215 //
216 // QmakeBuildSystem
217 //
218
QmakeBuildSystem(QmakeBuildConfiguration * bc)219 QmakeBuildSystem::QmakeBuildSystem(QmakeBuildConfiguration *bc)
220 : BuildSystem(bc)
221 , m_qmakeVfs(new QMakeVfs)
222 , m_cppCodeModelUpdater(new CppTools::CppProjectUpdater)
223 {
224 setParseDelay(0);
225
226 m_rootProFile = std::make_unique<QmakeProFile>(this, projectFilePath());
227
228 connect(BuildManager::instance(), &BuildManager::buildQueueFinished,
229 this, &QmakeBuildSystem::buildFinished);
230
231 connect(bc->target(),
232 &Target::activeBuildConfigurationChanged,
233 this,
234 [this](BuildConfiguration *bc) {
235 if (bc == buildConfiguration())
236 scheduleUpdateAllNowOrLater();
237 // FIXME: This is too eager in the presence of not handling updates
238 // when the build configuration is not active, see startAsyncTimer
239 // below.
240 // else
241 // m_cancelEvaluate = true;
242 });
243
244 connect(bc->project(), &Project::activeTargetChanged,
245 this, &QmakeBuildSystem::activeTargetWasChanged);
246
247 connect(bc->project(), &Project::projectFileIsDirty,
248 this, &QmakeBuildSystem::scheduleUpdateAllLater);
249
250 connect(bc, &BuildConfiguration::buildDirectoryChanged,
251 this, &QmakeBuildSystem::scheduleUpdateAllNowOrLater);
252 connect(bc, &BuildConfiguration::environmentChanged,
253 this, &QmakeBuildSystem::scheduleUpdateAllNowOrLater);
254
255 connect(ToolChainManager::instance(), &ToolChainManager::toolChainUpdated,
256 this, [this](ToolChain *tc) {
257 if (ToolChainKitAspect::cxxToolChain(kit()) == tc)
258 scheduleUpdateAllNowOrLater();
259 });
260
261 connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged,
262 this, [this](const QList<int> &,const QList<int> &, const QList<int> &changed) {
263 if (changed.contains(QtKitAspect::qtVersionId(kit())))
264 scheduleUpdateAllNowOrLater();
265 });
266 }
267
~QmakeBuildSystem()268 QmakeBuildSystem::~QmakeBuildSystem()
269 {
270 m_guard = {};
271 delete m_cppCodeModelUpdater;
272 m_cppCodeModelUpdater = nullptr;
273 m_asyncUpdateState = ShuttingDown;
274
275 // Make sure root node (and associated readers) are shut hown before proceeding
276 m_rootProFile.reset();
277 if (m_qmakeGlobalsRefCnt > 0) {
278 m_qmakeGlobalsRefCnt = 0;
279 deregisterFromCacheManager();
280 }
281
282 m_cancelEvaluate = true;
283 QTC_CHECK(m_qmakeGlobalsRefCnt == 0);
284 delete m_qmakeVfs;
285 m_qmakeVfs = nullptr;
286
287 if (m_asyncUpdateFutureInterface) {
288 m_asyncUpdateFutureInterface->reportCanceled();
289 m_asyncUpdateFutureInterface->reportFinished();
290 m_asyncUpdateFutureInterface.reset();
291 }
292 }
293
updateCodeModels()294 void QmakeBuildSystem::updateCodeModels()
295 {
296 if (!buildConfiguration()->isActive())
297 return;
298
299 updateCppCodeModel();
300 updateQmlJSCodeModel();
301 }
302
updateDocuments()303 void QmakeBuildSystem::updateDocuments()
304 {
305 QSet<FilePath> projectDocuments;
306 project()->rootProjectNode()->forEachProjectNode([&projectDocuments](const ProjectNode *n) {
307 projectDocuments.insert(n->filePath());
308
309 });
310 const auto priFileForPath = [p = project()](const FilePath &fp) -> QmakePriFile * {
311 const Node * const n = p->nodeForFilePath(fp, [](const Node *n) {
312 return dynamic_cast<const QmakePriFileNode *>(n); });
313 QTC_ASSERT(n, return nullptr);
314 return static_cast<const QmakePriFileNode *>(n)->priFile();
315 };
316 const auto docGenerator = [&](const FilePath &fp)
317 -> std::unique_ptr<Core::IDocument> {
318 QmakePriFile * const priFile = priFileForPath(fp);
319 QTC_ASSERT(priFile, return std::make_unique<Core::IDocument>());
320 return std::make_unique<QmakePriFileDocument>(priFile, fp);
321 };
322 const auto docUpdater = [&](Core::IDocument *doc) {
323 QmakePriFile * const priFile = priFileForPath(doc->filePath());
324 QTC_ASSERT(priFile, return);
325 static_cast<QmakePriFileDocument *>(doc)->setPriFile(priFile);
326 };
327 project()->setExtraProjectFiles(projectDocuments, docGenerator, docUpdater);
328 }
329
updateCppCodeModel()330 void QmakeBuildSystem::updateCppCodeModel()
331 {
332 m_toolChainWarnings.clear();
333
334 QtSupport::CppKitInfo kitInfo(kit());
335 QTC_ASSERT(kitInfo.isValid(), return);
336
337 QList<ProjectExplorer::ExtraCompiler *> generators;
338 RawProjectParts rpps;
339 for (const QmakeProFile *pro : rootProFile()->allProFiles()) {
340 warnOnToolChainMismatch(pro);
341
342 RawProjectPart rpp;
343 rpp.setDisplayName(pro->displayName());
344 rpp.setProjectFileLocation(pro->filePath().toString());
345 rpp.setBuildSystemTarget(pro->filePath().toString());
346 switch (pro->projectType()) {
347 case ProjectType::ApplicationTemplate:
348 rpp.setBuildTargetType(BuildTargetType::Executable);
349 break;
350 case ProjectType::SharedLibraryTemplate:
351 case ProjectType::StaticLibraryTemplate:
352 rpp.setBuildTargetType(BuildTargetType::Library);
353 break;
354 default:
355 rpp.setBuildTargetType(BuildTargetType::Unknown);
356 break;
357 }
358 const QString includeFileBaseDir = pro->sourceDir().toString();
359 rpp.setFlagsForCxx({kitInfo.cxxToolChain, pro->variableValue(Variable::CppFlags),
360 includeFileBaseDir});
361 rpp.setFlagsForC({kitInfo.cToolChain, pro->variableValue(Variable::CFlags),
362 includeFileBaseDir});
363 rpp.setMacros(ProjectExplorer::Macro::toMacros(pro->cxxDefines()));
364 rpp.setPreCompiledHeaders(pro->variableValue(Variable::PrecompiledHeader));
365 rpp.setSelectedForBuilding(pro->includedInExactParse());
366
367 // Qt Version
368 if (pro->variableValue(Variable::Config).contains(QLatin1String("qt")))
369 rpp.setQtVersion(kitInfo.projectPartQtVersion);
370 else
371 rpp.setQtVersion(Utils::QtVersion::None);
372
373 // Header paths
374 ProjectExplorer::HeaderPaths headerPaths;
375 foreach (const QString &inc, pro->variableValue(Variable::IncludePath)) {
376 const ProjectExplorer::HeaderPath headerPath{inc, HeaderPathType::User};
377 if (!headerPaths.contains(headerPath))
378 headerPaths += headerPath;
379 }
380
381 if (kitInfo.qtVersion && !kitInfo.qtVersion->frameworkPath().isEmpty())
382 headerPaths += {kitInfo.qtVersion->frameworkPath().toString(),
383 HeaderPathType::Framework};
384 rpp.setHeaderPaths(headerPaths);
385
386 // Files and generators
387 const QStringList cumulativeSourceFiles = pro->variableValue(Variable::CumulativeSource);
388 QStringList fileList = pro->variableValue(Variable::ExactSource) + cumulativeSourceFiles;
389 QList<ProjectExplorer::ExtraCompiler *> proGenerators = pro->extraCompilers();
390 foreach (ProjectExplorer::ExtraCompiler *ec, proGenerators) {
391 ec->forEachTarget([&](const Utils::FilePath &generatedFile) {
392 fileList += generatedFile.toString();
393 });
394 }
395 generators.append(proGenerators);
396 fileList.prepend(CppTools::CppModelManager::configurationFileName());
397 rpp.setFiles(fileList, [cumulativeSourceFiles](const QString &filePath) {
398 // Keep this lambda thread-safe!
399 return !cumulativeSourceFiles.contains(filePath);
400 });
401
402 rpps.append(rpp);
403 }
404
405 m_cppCodeModelUpdater->update({project(), kitInfo, activeParseEnvironment(), rpps}, generators);
406 }
407
updateQmlJSCodeModel()408 void QmakeBuildSystem::updateQmlJSCodeModel()
409 {
410 QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance();
411 if (!modelManager)
412 return;
413
414 QmlJS::ModelManagerInterface::ProjectInfo projectInfo =
415 modelManager->defaultProjectInfoForProject(project());
416
417 const QList<QmakeProFile *> proFiles = rootProFile()->allProFiles();
418
419 projectInfo.importPaths.clear();
420
421 bool hasQmlLib = false;
422 for (QmakeProFile *file : proFiles) {
423 for (const QString &path : file->variableValue(Variable::QmlImportPath)) {
424 projectInfo.importPaths.maybeInsert(FilePath::fromString(path),
425 QmlJS::Dialect::Qml);
426 }
427 const QStringList &exactResources = file->variableValue(Variable::ExactResource);
428 const QStringList &cumulativeResources = file->variableValue(Variable::CumulativeResource);
429 projectInfo.activeResourceFiles.append(exactResources);
430 projectInfo.allResourceFiles.append(exactResources);
431 projectInfo.allResourceFiles.append(cumulativeResources);
432 QString errorMessage;
433 foreach (const QString &rc, exactResources) {
434 QString contents;
435 int id = m_qmakeVfs->idForFileName(rc, QMakeVfs::VfsExact);
436 if (m_qmakeVfs->readFile(id, &contents, &errorMessage) == QMakeVfs::ReadOk)
437 projectInfo.resourceFileContents[rc] = contents;
438 }
439 foreach (const QString &rc, cumulativeResources) {
440 QString contents;
441 int id = m_qmakeVfs->idForFileName(rc, QMakeVfs::VfsCumulative);
442 if (m_qmakeVfs->readFile(id, &contents, &errorMessage) == QMakeVfs::ReadOk)
443 projectInfo.resourceFileContents[rc] = contents;
444 }
445 if (!hasQmlLib) {
446 QStringList qtLibs = file->variableValue(Variable::Qt);
447 hasQmlLib = qtLibs.contains(QLatin1String("declarative")) ||
448 qtLibs.contains(QLatin1String("qml")) ||
449 qtLibs.contains(QLatin1String("quick"));
450 }
451 }
452
453 // If the project directory has a pro/pri file that includes a qml or quick or declarative
454 // library then chances of the project being a QML project is quite high.
455 // This assumption fails when there are no QDeclarativeEngine/QDeclarativeView (QtQuick 1)
456 // or QQmlEngine/QQuickView (QtQuick 2) instances.
457 if (hasQmlLib)
458 project()->addProjectLanguage(ProjectExplorer::Constants::QMLJS_LANGUAGE_ID);
459
460 projectInfo.activeResourceFiles.removeDuplicates();
461 projectInfo.allResourceFiles.removeDuplicates();
462
463 modelManager->updateProjectInfo(projectInfo, project());
464 }
465
scheduleAsyncUpdateFile(QmakeProFile * file,QmakeProFile::AsyncUpdateDelay delay)466 void QmakeBuildSystem::scheduleAsyncUpdateFile(QmakeProFile *file, QmakeProFile::AsyncUpdateDelay delay)
467 {
468 if (m_asyncUpdateState == ShuttingDown)
469 return;
470
471 if (m_cancelEvaluate) {
472 // A cancel is in progress
473 // That implies that a full update is going to happen afterwards
474 // So we don't need to do anything
475 return;
476 }
477
478 file->setParseInProgressRecursive(true);
479
480 if (m_asyncUpdateState == AsyncFullUpdatePending) {
481 // Just postpone
482 startAsyncTimer(delay);
483 } else if (m_asyncUpdateState == AsyncPartialUpdatePending
484 || m_asyncUpdateState == Base) {
485 // Add the node
486 m_asyncUpdateState = AsyncPartialUpdatePending;
487
488 bool add = true;
489 auto it = m_partialEvaluate.begin();
490 while (it != m_partialEvaluate.end()) {
491 if (*it == file) {
492 add = false;
493 break;
494 } else if (file->isParent(*it)) { // We already have the parent in the list, nothing to do
495 it = m_partialEvaluate.erase(it);
496 } else if ((*it)->isParent(file)) { // The node is the parent of a child already in the list
497 add = false;
498 break;
499 } else {
500 ++it;
501 }
502 }
503
504 if (add)
505 m_partialEvaluate.append(file);
506
507 // Cancel running code model update
508 m_cppCodeModelUpdater->cancel();
509
510 startAsyncTimer(delay);
511 } else if (m_asyncUpdateState == AsyncUpdateInProgress) {
512 // A update is in progress
513 // And this slot only gets called if a file changed on disc
514 // So we'll play it safe and schedule a complete evaluate
515 // This might trigger if due to version control a few files
516 // change a partial update gets in progress and then another
517 // batch of changes come in, which triggers a full update
518 // even if that's not really needed
519 scheduleUpdateAll(delay);
520 }
521 }
522
scheduleUpdateAllNowOrLater()523 void QmakeBuildSystem::scheduleUpdateAllNowOrLater()
524 {
525 if (m_firstParseNeeded)
526 scheduleUpdateAll(QmakeProFile::ParseNow);
527 else
528 scheduleUpdateAll(QmakeProFile::ParseLater);
529 }
530
qmakeBuildConfiguration() const531 QmakeBuildConfiguration *QmakeBuildSystem::qmakeBuildConfiguration() const
532 {
533 return static_cast<QmakeBuildConfiguration *>(BuildSystem::buildConfiguration());
534 }
535
scheduleUpdateAll(QmakeProFile::AsyncUpdateDelay delay)536 void QmakeBuildSystem::scheduleUpdateAll(QmakeProFile::AsyncUpdateDelay delay)
537 {
538 if (m_asyncUpdateState == ShuttingDown) {
539 TRACE("suppressed: we are shutting down");
540 return;
541 }
542
543 if (m_cancelEvaluate) { // we are in progress of canceling
544 // and will start the evaluation after that
545 TRACE("suppressed: was previously canceled");
546 return;
547 }
548
549 if (!buildConfiguration()->isActive()) {
550 TRACE("firstParseNeeded: " << int(m_firstParseNeeded)
551 << ", suppressed: buildconfig not active");
552 return;
553 }
554
555 TRACE("firstParseNeeded: " << int(m_firstParseNeeded) << ", delay: " << delay);
556
557 rootProFile()->setParseInProgressRecursive(true);
558
559 if (m_asyncUpdateState == AsyncUpdateInProgress) {
560 m_cancelEvaluate = true;
561 m_asyncUpdateState = AsyncFullUpdatePending;
562 return;
563 }
564
565 m_partialEvaluate.clear();
566 m_asyncUpdateState = AsyncFullUpdatePending;
567
568 // Cancel running code model update
569 m_cppCodeModelUpdater->cancel();
570 startAsyncTimer(delay);
571 }
572
startAsyncTimer(QmakeProFile::AsyncUpdateDelay delay)573 void QmakeBuildSystem::startAsyncTimer(QmakeProFile::AsyncUpdateDelay delay)
574 {
575 if (!buildConfiguration()->isActive()) {
576 TRACE("skipped, not active")
577 return;
578 }
579
580 const int interval = qMin(parseDelay(),
581 delay == QmakeProFile::ParseLater ? UPDATE_INTERVAL : 0);
582 TRACE("interval: " << interval);
583 requestParseWithCustomDelay(interval);
584 }
585
incrementPendingEvaluateFutures()586 void QmakeBuildSystem::incrementPendingEvaluateFutures()
587 {
588 if (m_pendingEvaluateFuturesCount == 0) {
589 // The guard actually might already guard the project if this
590 // here is the re-start of a previously aborted parse due to e.g.
591 // changing build directories while parsing.
592 if (!m_guard.guardsProject())
593 m_guard = guardParsingRun();
594 }
595 ++m_pendingEvaluateFuturesCount;
596 TRACE("pending inc to: " << m_pendingEvaluateFuturesCount);
597 m_asyncUpdateFutureInterface->setProgressRange(m_asyncUpdateFutureInterface->progressMinimum(),
598 m_asyncUpdateFutureInterface->progressMaximum()
599 + 1);
600 }
601
decrementPendingEvaluateFutures()602 void QmakeBuildSystem::decrementPendingEvaluateFutures()
603 {
604 --m_pendingEvaluateFuturesCount;
605 TRACE("pending dec to: " << m_pendingEvaluateFuturesCount);
606
607 if (!rootProFile()) {
608 TRACE("closing project");
609 return; // We are closing the project!
610 }
611
612 m_asyncUpdateFutureInterface->setProgressValue(m_asyncUpdateFutureInterface->progressValue()
613 + 1);
614 if (m_pendingEvaluateFuturesCount == 0) {
615 // We are done!
616 setRootProjectNode(QmakeNodeTreeBuilder::buildTree(this));
617
618 if (!m_rootProFile->validParse())
619 m_asyncUpdateFutureInterface->reportCanceled();
620
621 m_asyncUpdateFutureInterface->reportFinished();
622 m_asyncUpdateFutureInterface.reset();
623 m_cancelEvaluate = false;
624
625 // TODO clear the profile cache ?
626 if (m_asyncUpdateState == AsyncFullUpdatePending || m_asyncUpdateState == AsyncPartialUpdatePending) {
627 // Already parsing!
628 rootProFile()->setParseInProgressRecursive(true);
629 startAsyncTimer(QmakeProFile::ParseLater);
630 } else if (m_asyncUpdateState != ShuttingDown){
631 // After being done, we need to call:
632
633 m_asyncUpdateState = Base;
634 updateBuildSystemData();
635 updateCodeModels();
636 updateDocuments();
637 target()->updateDefaultDeployConfigurations();
638 m_guard.markAsSuccess(); // Qmake always returns (some) data, even when it failed:-)
639 TRACE("success" << int(m_guard.isSuccess()));
640 m_guard = {}; // This triggers emitParsingFinished by destroying the previous guard.
641
642 m_firstParseNeeded = false;
643 TRACE("first parse succeeded");
644
645 emitBuildSystemUpdated();
646 }
647 }
648 }
649
wasEvaluateCanceled()650 bool QmakeBuildSystem::wasEvaluateCanceled()
651 {
652 return m_cancelEvaluate;
653 }
654
asyncUpdate()655 void QmakeBuildSystem::asyncUpdate()
656 {
657 TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM);
658 setParseDelay(UPDATE_INTERVAL);
659 TRACE("");
660
661 if (m_invalidateQmakeVfsContents) {
662 m_invalidateQmakeVfsContents = false;
663 m_qmakeVfs->invalidateContents();
664 } else {
665 m_qmakeVfs->invalidateCache();
666 }
667
668 m_asyncUpdateFutureInterface.reset(new QFutureInterface<void>);
669 m_asyncUpdateFutureInterface->setProgressRange(0, 0);
670 Core::ProgressManager::addTask(m_asyncUpdateFutureInterface->future(),
671 tr("Reading Project \"%1\"").arg(project()->displayName()),
672 Constants::PROFILE_EVALUATE);
673
674 m_asyncUpdateFutureInterface->reportStarted();
675 const auto watcher = new QFutureWatcher<void>(this);
676 connect(watcher, &QFutureWatcher<void>::canceled, this, [this, watcher] {
677 if (!m_qmakeGlobals)
678 return;
679 watcher->disconnect();
680 m_qmakeGlobals->killProcesses();
681 });
682 connect(watcher, &QFutureWatcher<void>::finished, this, [watcher] {
683 watcher->disconnect();
684 watcher->deleteLater();
685 });
686 watcher->setFuture(m_asyncUpdateFutureInterface->future());
687
688 const Kit *const k = kit();
689 QtSupport::BaseQtVersion *const qtVersion = QtSupport::QtKitAspect::qtVersion(k);
690 if (!qtVersion || !qtVersion->isValid()) {
691 const QString errorMessage
692 = k ? tr("Cannot parse project \"%1\": The currently selected kit \"%2\" does not "
693 "have a valid Qt.")
694 .arg(project()->displayName(), k->displayName())
695 : tr("Cannot parse project \"%1\": No kit selected.").arg(project()->displayName());
696 proFileParseError(errorMessage, project()->projectFilePath());
697 m_asyncUpdateFutureInterface->reportCanceled();
698 m_asyncUpdateFutureInterface->reportFinished();
699 m_asyncUpdateFutureInterface.reset();
700 return;
701 }
702
703 // Make sure we ignore requests for re-evaluation for files whose QmakePriFile objects
704 // will get deleted during the parse.
705 const auto docUpdater = [](Core::IDocument *doc) {
706 static_cast<QmakePriFileDocument *>(doc)->setPriFile(nullptr);
707 };
708 if (m_asyncUpdateState != AsyncFullUpdatePending) {
709 QSet<FilePath> projectFilePaths;
710 for (QmakeProFile * const file : qAsConst(m_partialEvaluate)) {
711 QVector<QmakePriFile *> priFiles = file->children();
712 for (int i = 0; i < priFiles.count(); ++i) {
713 const QmakePriFile * const priFile = priFiles.at(i);
714 projectFilePaths << priFile->filePath();
715 priFiles << priFile->children();
716 }
717 }
718 project()->updateExtraProjectFiles(projectFilePaths, docUpdater);
719 }
720
721 if (m_asyncUpdateState == AsyncFullUpdatePending) {
722 project()->updateExtraProjectFiles(docUpdater);
723 rootProFile()->asyncUpdate();
724 } else {
725 foreach (QmakeProFile *file, m_partialEvaluate)
726 file->asyncUpdate();
727 }
728
729 m_partialEvaluate.clear();
730 m_asyncUpdateState = AsyncUpdateInProgress;
731 }
732
buildFinished(bool success)733 void QmakeBuildSystem::buildFinished(bool success)
734 {
735 if (success)
736 m_invalidateQmakeVfsContents = true;
737 }
738
projectIssues(const Kit * k) const739 Tasks QmakeProject::projectIssues(const Kit *k) const
740 {
741 Tasks result = Project::projectIssues(k);
742 const QtSupport::BaseQtVersion *const qtFromKit = QtSupport::QtKitAspect::qtVersion(k);
743 if (!qtFromKit)
744 result.append(createProjectTask(Task::TaskType::Error, tr("No Qt version set in kit.")));
745 else if (!qtFromKit->isValid())
746 result.append(createProjectTask(Task::TaskType::Error, tr("Qt version is invalid.")));
747 if (!ToolChainKitAspect::cxxToolChain(k))
748 result.append(createProjectTask(Task::TaskType::Error, tr("No C++ compiler set in kit.")));
749
750 // A project can be considered part of more than one Qt version, for instance if it is an
751 // example shipped via the installer.
752 // Report a problem if and only if the project is considered to be part of *only* a Qt
753 // that is not the one from the current kit.
754 const QList<BaseQtVersion *> qtsContainingThisProject
755 = QtVersionManager::versions([filePath = projectFilePath()](const BaseQtVersion *qt) {
756 return qt->isValid() && qt->isQtSubProject(filePath);
757 });
758 if (!qtsContainingThisProject.isEmpty()
759 && !qtsContainingThisProject.contains(const_cast<BaseQtVersion *>(qtFromKit))) {
760 result.append(CompileTask(Task::Warning,
761 tr("Project is part of Qt sources that do not match "
762 "the Qt defined in the kit.")));
763 }
764
765 return result;
766 }
767
768 // Find the folder that contains a file with a certain name (recurse down)
folderOf(FolderNode * in,const FilePath & fileName)769 static FolderNode *folderOf(FolderNode *in, const FilePath &fileName)
770 {
771 foreach (FileNode *fn, in->fileNodes())
772 if (fn->filePath() == fileName)
773 return in;
774 foreach (FolderNode *folder, in->folderNodes())
775 if (FolderNode *pn = folderOf(folder, fileName))
776 return pn;
777 return nullptr;
778 }
779
780 // Find the QmakeProFileNode that contains a certain file.
781 // First recurse down to folder, then find the pro-file.
fileNodeOf(FolderNode * in,const FilePath & fileName)782 static FileNode *fileNodeOf(FolderNode *in, const FilePath &fileName)
783 {
784 for (FolderNode *folder = folderOf(in, fileName); folder; folder = folder->parentFolderNode()) {
785 if (auto *proFile = dynamic_cast<QmakeProFileNode *>(folder)) {
786 foreach (FileNode *fileNode, proFile->fileNodes()) {
787 if (fileNode->filePath() == fileName)
788 return fileNode;
789 }
790 }
791 }
792 return nullptr;
793 }
794
buildDir(const FilePath & proFilePath) const795 FilePath QmakeBuildSystem::buildDir(const FilePath &proFilePath) const
796 {
797 const QDir srcDirRoot = QDir(projectDirectory().toString());
798 const QString relativeDir = srcDirRoot.relativeFilePath(proFilePath.parentDir().toString());
799 const FilePath buildConfigBuildDir = buildConfiguration()->buildDirectory();
800 FilePath buildDir = buildConfigBuildDir.isEmpty()
801 ? projectDirectory()
802 : buildConfigBuildDir;
803 // FIXME: Convoluted.
804 buildDir.setPath(QDir::cleanPath(QDir(buildDir.path()).absoluteFilePath(relativeDir)));
805 return buildDir;
806 }
807
proFileParseError(const QString & errorMessage,const FilePath & filePath)808 void QmakeBuildSystem::proFileParseError(const QString &errorMessage, const FilePath &filePath)
809 {
810 TaskHub::addTask(BuildSystemTask(Task::Error, errorMessage, filePath));
811 }
812
createProFileReader(const QmakeProFile * qmakeProFile)813 QtSupport::ProFileReader *QmakeBuildSystem::createProFileReader(const QmakeProFile *qmakeProFile)
814 {
815 if (!m_qmakeGlobals) {
816 m_qmakeGlobals = std::make_unique<QMakeGlobals>();
817 m_qmakeGlobalsRefCnt = 0;
818
819 QStringList qmakeArgs;
820
821 Kit *k = kit();
822 QmakeBuildConfiguration *bc = qmakeBuildConfiguration();
823
824 Environment env = bc->environment();
825 if (QMakeStep *qs = bc->qmakeStep())
826 qmakeArgs = qs->parserArguments();
827 else
828 qmakeArgs = bc->configCommandLineArguments();
829
830 QtSupport::BaseQtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(k);
831 m_qmakeSysroot = SysRootKitAspect::sysRoot(k).toString();
832
833 if (qtVersion && qtVersion->isValid()) {
834 m_qmakeGlobals->qmake_abslocation = QDir::cleanPath(qtVersion->qmakeFilePath().toString());
835 qtVersion->applyProperties(m_qmakeGlobals.get());
836 }
837 m_qmakeGlobals->setDirectories(rootProFile()->sourceDir().toString(),
838 buildDir(rootProFile()->filePath()).toString());
839
840 Environment::const_iterator eit = env.constBegin(), eend = env.constEnd();
841 for (; eit != eend; ++eit)
842 m_qmakeGlobals->environment.insert(env.key(eit), env.expandedValueForKey(env.key(eit)));
843
844 m_qmakeGlobals->setCommandLineArguments(buildDir(rootProFile()->filePath()).toString(), qmakeArgs);
845 m_qmakeGlobals->runSystemFunction = bc->runSystemFunction();
846
847 QtSupport::ProFileCacheManager::instance()->incRefCount();
848
849 // On ios, qmake is called recursively, and the second call with a different
850 // spec.
851 // macx-ios-clang just creates supporting makefiles, and to avoid being
852 // slow does not evaluate everything, and contains misleading information
853 // (that is never used).
854 // macx-xcode correctly evaluates the variables and generates the xcodeproject
855 // that is actually used to build the application.
856 //
857 // It is important to override the spec file only for the creator evaluator,
858 // and not the qmake buildstep used to build the app (as we use the makefiles).
859 const char IOSQT[] = "Qt4ProjectManager.QtVersion.Ios"; // from Ios::Constants
860 if (qtVersion && qtVersion->type() == QLatin1String(IOSQT))
861 m_qmakeGlobals->xqmakespec = QLatin1String("macx-xcode");
862 }
863 ++m_qmakeGlobalsRefCnt;
864
865 auto reader = new QtSupport::ProFileReader(m_qmakeGlobals.get(), m_qmakeVfs);
866
867 // FIXME: Currently intentional.
868 // Core parts of the ProParser hard-assert on non-local items
869 reader->setOutputDir(buildDir(qmakeProFile->filePath()).path());
870
871 return reader;
872 }
873
qmakeGlobals()874 QMakeGlobals *QmakeBuildSystem::qmakeGlobals()
875 {
876 return m_qmakeGlobals.get();
877 }
878
qmakeVfs()879 QMakeVfs *QmakeBuildSystem::qmakeVfs()
880 {
881 return m_qmakeVfs;
882 }
883
qmakeSysroot()884 QString QmakeBuildSystem::qmakeSysroot()
885 {
886 return m_qmakeSysroot;
887 }
888
destroyProFileReader(QtSupport::ProFileReader * reader)889 void QmakeBuildSystem::destroyProFileReader(QtSupport::ProFileReader *reader)
890 {
891 // The ProFileReader destructor is super expensive (but thread-safe).
892 const auto deleteFuture = runAsync(ProjectExplorerPlugin::sharedThreadPool(), QThread::LowestPriority,
893 [reader] { delete reader; });
894 onFinished(deleteFuture, this, [this](const QFuture<void> &) {
895 if (!--m_qmakeGlobalsRefCnt) {
896 deregisterFromCacheManager();
897 m_qmakeGlobals.reset();
898 }
899 });
900 }
901
deregisterFromCacheManager()902 void QmakeBuildSystem::deregisterFromCacheManager()
903 {
904 QString dir = projectFilePath().toString();
905 if (!dir.endsWith(QLatin1Char('/')))
906 dir += QLatin1Char('/');
907 QtSupport::ProFileCacheManager::instance()->discardFiles(dir, qmakeVfs());
908 QtSupport::ProFileCacheManager::instance()->decRefCount();
909 }
910
activeTargetWasChanged(Target * t)911 void QmakeBuildSystem::activeTargetWasChanged(Target *t)
912 {
913 // We are only interested in our own target.
914 if (t != target())
915 return;
916
917 m_invalidateQmakeVfsContents = true;
918 scheduleUpdateAll(QmakeProFile::ParseLater);
919 }
920
notifyChangedHelper(const FilePath & fileName,QmakeProFile * file)921 static void notifyChangedHelper(const FilePath &fileName, QmakeProFile *file)
922 {
923 if (file->filePath() == fileName) {
924 QtSupport::ProFileCacheManager::instance()->discardFile(
925 fileName.toString(), file->buildSystem()->qmakeVfs());
926 file->scheduleUpdate(QmakeProFile::ParseNow);
927 }
928
929 for (QmakePriFile *fn : file->children()) {
930 if (auto pro = dynamic_cast<QmakeProFile *>(fn))
931 notifyChangedHelper(fileName, pro);
932 }
933 }
934
notifyChanged(const FilePath & name)935 void QmakeBuildSystem::notifyChanged(const FilePath &name)
936 {
937 FilePaths files = project()->files([&name](const Node *n) {
938 return Project::SourceFiles(n) && n->filePath() == name;
939 });
940
941 if (files.isEmpty())
942 return;
943
944 notifyChangedHelper(name, m_rootProFile.get());
945 }
946
watchFolders(const QStringList & l,QmakePriFile * file)947 void QmakeBuildSystem::watchFolders(const QStringList &l, QmakePriFile *file)
948 {
949 if (l.isEmpty())
950 return;
951 if (!m_centralizedFolderWatcher)
952 m_centralizedFolderWatcher = new Internal::CentralizedFolderWatcher(this);
953 m_centralizedFolderWatcher->watchFolders(l, file);
954 }
955
unwatchFolders(const QStringList & l,QmakePriFile * file)956 void QmakeBuildSystem::unwatchFolders(const QStringList &l, QmakePriFile *file)
957 {
958 if (m_centralizedFolderWatcher && !l.isEmpty())
959 m_centralizedFolderWatcher->unwatchFolders(l, file);
960 }
961
962 /////////////
963 /// Centralized Folder Watcher
964 ////////////
965
966 // All the folder have a trailing slash!
CentralizedFolderWatcher(QmakeBuildSystem * parent)967 CentralizedFolderWatcher::CentralizedFolderWatcher(QmakeBuildSystem *parent)
968 : QObject(parent), m_buildSystem(parent)
969 {
970 m_compressTimer.setSingleShot(true);
971 m_compressTimer.setInterval(200);
972 connect(&m_compressTimer, &QTimer::timeout, this, &CentralizedFolderWatcher::onTimer);
973 connect(&m_watcher, &QFileSystemWatcher::directoryChanged,
974 this, &CentralizedFolderWatcher::folderChanged);
975 }
976
recursiveDirs(const QString & folder)977 QSet<QString> CentralizedFolderWatcher::recursiveDirs(const QString &folder)
978 {
979 QSet<QString> result;
980 QDir dir(folder);
981 QStringList list = dir.entryList(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
982 foreach (const QString &f, list) {
983 const QString a = folder + f + QLatin1Char('/');
984 result.insert(a);
985 result += recursiveDirs(a);
986 }
987 return result;
988 }
989
watchFolders(const QList<QString> & folders,QmakePriFile * file)990 void CentralizedFolderWatcher::watchFolders(const QList<QString> &folders, QmakePriFile *file)
991 {
992 m_watcher.addPaths(folders);
993
994 const QChar slash = QLatin1Char('/');
995 foreach (const QString &f, folders) {
996 QString folder = f;
997 if (!folder.endsWith(slash))
998 folder.append(slash);
999 m_map.insert(folder, file);
1000
1001 // Support for recursive watching
1002 // we add the recursive directories we find
1003 QSet<QString> tmp = recursiveDirs(folder);
1004 if (!tmp.isEmpty())
1005 m_watcher.addPaths(Utils::toList(tmp));
1006 m_recursiveWatchedFolders += tmp;
1007 }
1008 }
1009
unwatchFolders(const QList<QString> & folders,QmakePriFile * file)1010 void CentralizedFolderWatcher::unwatchFolders(const QList<QString> &folders, QmakePriFile *file)
1011 {
1012 const QChar slash = QLatin1Char('/');
1013 foreach (const QString &f, folders) {
1014 QString folder = f;
1015 if (!folder.endsWith(slash))
1016 folder.append(slash);
1017 m_map.remove(folder, file);
1018 if (!m_map.contains(folder))
1019 m_watcher.removePath(folder);
1020
1021 // Figure out which recursive directories we can remove
1022 // this might not scale. I'm pretty sure it doesn't
1023 // A scaling implementation would need to save more information
1024 // where a given directory watcher actual comes from...
1025
1026 QStringList toRemove;
1027 foreach (const QString &rwf, m_recursiveWatchedFolders) {
1028 if (rwf.startsWith(folder)) {
1029 // So the rwf is a subdirectory of a folder we aren't watching
1030 // but maybe someone else wants us to watch
1031 bool needToWatch = false;
1032 auto end = m_map.constEnd();
1033 for (auto it = m_map.constBegin(); it != end; ++it) {
1034 if (rwf.startsWith(it.key())) {
1035 needToWatch = true;
1036 break;
1037 }
1038 }
1039 if (!needToWatch) {
1040 m_watcher.removePath(rwf);
1041 toRemove << rwf;
1042 }
1043 }
1044 }
1045
1046 foreach (const QString &tr, toRemove)
1047 m_recursiveWatchedFolders.remove(tr);
1048 }
1049 }
1050
folderChanged(const QString & folder)1051 void CentralizedFolderWatcher::folderChanged(const QString &folder)
1052 {
1053 m_changedFolders.insert(folder);
1054 m_compressTimer.start();
1055 }
1056
onTimer()1057 void CentralizedFolderWatcher::onTimer()
1058 {
1059 foreach (const QString &folder, m_changedFolders)
1060 delayedFolderChanged(folder);
1061 m_changedFolders.clear();
1062 }
1063
delayedFolderChanged(const QString & folder)1064 void CentralizedFolderWatcher::delayedFolderChanged(const QString &folder)
1065 {
1066 // Figure out whom to inform
1067 QString dir = folder;
1068 const QChar slash = QLatin1Char('/');
1069 bool newOrRemovedFiles = false;
1070 while (true) {
1071 if (!dir.endsWith(slash))
1072 dir.append(slash);
1073 QList<QmakePriFile *> files = m_map.values(dir);
1074 if (!files.isEmpty()) {
1075 // Collect all the files
1076 QSet<FilePath> newFiles;
1077 newFiles += QmakePriFile::recursiveEnumerate(folder);
1078 foreach (QmakePriFile *file, files)
1079 newOrRemovedFiles = newOrRemovedFiles || file->folderChanged(folder, newFiles);
1080 }
1081
1082 // Chop off last part, and break if there's nothing to chop off
1083 //
1084 if (dir.length() < 2)
1085 break;
1086
1087 // We start before the last slash
1088 const int index = dir.lastIndexOf(slash, dir.length() - 2);
1089 if (index == -1)
1090 break;
1091 dir.truncate(index + 1);
1092 }
1093
1094 QString folderWithSlash = folder;
1095 if (!folder.endsWith(slash))
1096 folderWithSlash.append(slash);
1097
1098 // If a subdirectory was added, watch it too
1099 QSet<QString> tmp = recursiveDirs(folderWithSlash);
1100 if (!tmp.isEmpty()) {
1101 QSet<QString> alreadyAdded = Utils::toSet(m_watcher.directories());
1102 tmp.subtract(alreadyAdded);
1103 if (!tmp.isEmpty())
1104 m_watcher.addPaths(Utils::toList(tmp));
1105 m_recursiveWatchedFolders += tmp;
1106 }
1107
1108 if (newOrRemovedFiles)
1109 m_buildSystem->updateCodeModels();
1110 }
1111
configureAsExampleProject(Kit * kit)1112 void QmakeProject::configureAsExampleProject(Kit *kit)
1113 {
1114 QList<BuildInfo> infoList;
1115 const QList<Kit *> kits(kit != nullptr ? QList<Kit *>({kit}) : KitManager::kits());
1116 for (Kit *k : kits) {
1117 if (QtSupport::QtKitAspect::qtVersion(k) != nullptr) {
1118 if (auto factory = BuildConfigurationFactory::find(k, projectFilePath()))
1119 infoList << factory->allAvailableSetups(k, projectFilePath());
1120 }
1121 }
1122 setup(infoList);
1123 }
1124
updateBuildSystemData()1125 void QmakeBuildSystem::updateBuildSystemData()
1126 {
1127 const QmakeProFile *const file = rootProFile();
1128 if (!file || file->parseInProgress())
1129 return;
1130
1131 DeploymentData deploymentData;
1132 collectData(file, deploymentData);
1133 setDeploymentData(deploymentData);
1134
1135 QList<BuildTargetInfo> appTargetList;
1136
1137 project()->rootProjectNode()->forEachProjectNode([this, &appTargetList](const ProjectNode *pn) {
1138 auto node = dynamic_cast<const QmakeProFileNode *>(pn);
1139 if (!node || !node->includedInExactParse())
1140 return;
1141
1142 if (node->projectType() != ProjectType::ApplicationTemplate
1143 && node->projectType() != ProjectType::ScriptTemplate)
1144 return;
1145
1146 TargetInformation ti = node->targetInformation();
1147 if (!ti.valid)
1148 return;
1149
1150 const QStringList &config = node->variableValue(Variable::Config);
1151
1152 FilePath destDir = ti.destDir;
1153 FilePath workingDir;
1154 if (!destDir.isEmpty()) {
1155 bool workingDirIsBaseDir = false;
1156 if (destDir.path() == ti.buildTarget)
1157 workingDirIsBaseDir = true;
1158 if (QDir::isRelativePath(destDir.path()))
1159 destDir = ti.buildDir / destDir.path();
1160
1161 if (workingDirIsBaseDir)
1162 workingDir = ti.buildDir;
1163 else
1164 workingDir = destDir;
1165 } else {
1166 workingDir = ti.buildDir;
1167 }
1168
1169 if (HostOsInfo::isMacHost() && config.contains("app_bundle"))
1170 workingDir = workingDir / (ti.target + ".app/Contents/MacOS");
1171
1172 BuildTargetInfo bti;
1173 bti.targetFilePath = executableFor(node->proFile());
1174 bti.projectFilePath = node->filePath();
1175 bti.workingDirectory = workingDir;
1176 bti.displayName = bti.projectFilePath.completeBaseName();
1177 const FilePath relativePathInProject
1178 = bti.projectFilePath.relativeChildPath(projectDirectory());
1179 if (!relativePathInProject.isEmpty()) {
1180 bti.displayNameUniquifier = QString::fromLatin1(" (%1)")
1181 .arg(relativePathInProject.toUserOutput());
1182 }
1183 bti.buildKey = bti.projectFilePath.toString();
1184 bti.isQtcRunnable = config.contains("qtc_runnable");
1185
1186 if (config.contains("console") && !config.contains("testcase")) {
1187 const QStringList qt = node->variableValue(Variable::Qt);
1188 bti.usesTerminal = !qt.contains("testlib") && !qt.contains("qmltest");
1189 }
1190
1191 QStringList libraryPaths;
1192
1193 // The user could be linking to a library found via a -L/some/dir switch
1194 // to find those libraries while actually running we explicitly prepend those
1195 // dirs to the library search path
1196 const QStringList libDirectories = node->variableValue(Variable::LibDirectories);
1197 if (!libDirectories.isEmpty()) {
1198 QmakeProFile *proFile = node->proFile();
1199 QTC_ASSERT(proFile, return);
1200 const QString proDirectory = buildDir(proFile->filePath()).toString();
1201 for (QString dir : libDirectories) {
1202 // Fix up relative entries like "LIBS+=-L.."
1203 const QFileInfo fi(dir);
1204 if (!fi.isAbsolute())
1205 dir = QDir::cleanPath(proDirectory + '/' + dir);
1206 libraryPaths.append(dir);
1207 }
1208 }
1209 QtSupport::BaseQtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(kit());
1210 if (qtVersion)
1211 libraryPaths.append(qtVersion->librarySearchPath().toString());
1212
1213 bti.runEnvModifierHash = qHash(libraryPaths);
1214 bti.runEnvModifier = [libraryPaths](Environment &env, bool useLibrarySearchPath) {
1215 if (useLibrarySearchPath)
1216 env.prependOrSetLibrarySearchPaths(libraryPaths);
1217 };
1218
1219 appTargetList.append(bti);
1220 });
1221
1222 setApplicationTargets(appTargetList);
1223 }
1224
collectData(const QmakeProFile * file,DeploymentData & deploymentData)1225 void QmakeBuildSystem::collectData(const QmakeProFile *file, DeploymentData &deploymentData)
1226 {
1227 if (!file->isSubProjectDeployable(file->filePath()))
1228 return;
1229
1230 const InstallsList &installsList = file->installsList();
1231 for (const InstallsItem &item : installsList.items) {
1232 if (!item.active)
1233 continue;
1234 for (const auto &localFile : item.files) {
1235 deploymentData.addFile(localFile.fileName, item.path, item.executable
1236 ? DeployableFile::TypeExecutable : DeployableFile::TypeNormal);
1237 }
1238 }
1239
1240 switch (file->projectType()) {
1241 case ProjectType::ApplicationTemplate:
1242 if (!installsList.targetPath.isEmpty())
1243 collectApplicationData(file, deploymentData);
1244 break;
1245 case ProjectType::SharedLibraryTemplate:
1246 case ProjectType::StaticLibraryTemplate:
1247 collectLibraryData(file, deploymentData);
1248 break;
1249 case ProjectType::SubDirsTemplate:
1250 for (const QmakePriFile *const subPriFile : file->subPriFilesExact()) {
1251 auto subProFile = dynamic_cast<const QmakeProFile *>(subPriFile);
1252 if (subProFile)
1253 collectData(subProFile, deploymentData);
1254 }
1255 break;
1256 default:
1257 break;
1258 }
1259 }
1260
collectApplicationData(const QmakeProFile * file,DeploymentData & deploymentData)1261 void QmakeBuildSystem::collectApplicationData(const QmakeProFile *file, DeploymentData &deploymentData)
1262 {
1263 const FilePath executable = executableFor(file);
1264 if (!executable.isEmpty())
1265 deploymentData.addFile(executable.path(), file->installsList().targetPath,
1266 DeployableFile::TypeExecutable);
1267 }
1268
destDirFor(const TargetInformation & ti)1269 static FilePath destDirFor(const TargetInformation &ti)
1270 {
1271 if (ti.destDir.isEmpty())
1272 return ti.buildDir;
1273 if (QDir::isRelativePath(ti.destDir.path()))
1274 return ti.buildDir / ti.destDir.path();
1275 return ti.destDir;
1276 }
1277
collectLibraryData(const QmakeProFile * file,DeploymentData & deploymentData)1278 void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentData &deploymentData)
1279 {
1280 const QString targetPath = file->installsList().targetPath;
1281 if (targetPath.isEmpty())
1282 return;
1283 const ToolChain *const toolchain = ToolChainKitAspect::cxxToolChain(kit());
1284 if (!toolchain)
1285 return;
1286
1287 TargetInformation ti = file->targetInformation();
1288 QString targetFileName = ti.target;
1289 const QStringList config = file->variableValue(Variable::Config);
1290 const bool isStatic = config.contains(QLatin1String("static"));
1291 const bool isPlugin = config.contains(QLatin1String("plugin"));
1292 const bool nameIsVersioned = !isPlugin && !config.contains("unversioned_libname");
1293 switch (toolchain->targetAbi().os()) {
1294 case Abi::WindowsOS: {
1295 QString targetVersionExt = file->singleVariableValue(Variable::TargetVersionExt);
1296 if (targetVersionExt.isEmpty()) {
1297 const QString version = file->singleVariableValue(Variable::Version);
1298 if (!version.isEmpty()) {
1299 targetVersionExt = version.left(version.indexOf(QLatin1Char('.')));
1300 if (targetVersionExt == QLatin1String("0"))
1301 targetVersionExt.clear();
1302 }
1303 }
1304 targetFileName += targetVersionExt + QLatin1Char('.');
1305 targetFileName += QLatin1String(isStatic ? "lib" : "dll");
1306 deploymentData.addFile(destDirFor(ti).toString() + '/' + targetFileName, targetPath);
1307 break;
1308 }
1309 case Abi::DarwinOS: {
1310 FilePath destDir = destDirFor(ti);
1311 if (config.contains(QLatin1String("lib_bundle"))) {
1312 destDir = destDir.pathAppended(ti.target + ".framework");
1313 } else {
1314 if (!(isPlugin && config.contains(QLatin1String("no_plugin_name_prefix"))))
1315 targetFileName.prepend(QLatin1String("lib"));
1316
1317 if (nameIsVersioned) {
1318 targetFileName += QLatin1Char('.');
1319 const QString version = file->singleVariableValue(Variable::Version);
1320 QString majorVersion = version.left(version.indexOf(QLatin1Char('.')));
1321 if (majorVersion.isEmpty())
1322 majorVersion = QLatin1String("1");
1323 targetFileName += majorVersion;
1324 }
1325 targetFileName += QLatin1Char('.');
1326 targetFileName += file->singleVariableValue(isStatic
1327 ? Variable::StaticLibExtension : Variable::ShLibExtension);
1328 }
1329 deploymentData.addFile(destDir.toString() + '/' + targetFileName, targetPath);
1330 break;
1331 }
1332 case Abi::LinuxOS:
1333 case Abi::BsdOS:
1334 case Abi::QnxOS:
1335 case Abi::UnixOS:
1336 if (!(isPlugin && config.contains(QLatin1String("no_plugin_name_prefix"))))
1337 targetFileName.prepend(QLatin1String("lib"));
1338
1339 targetFileName += QLatin1Char('.');
1340 if (isStatic) {
1341 targetFileName += QLatin1Char('a');
1342 } else {
1343 targetFileName += QLatin1String("so");
1344 deploymentData.addFile(destDirFor(ti).toString() + '/' + targetFileName, targetPath);
1345 if (nameIsVersioned) {
1346 QString version = file->singleVariableValue(Variable::Version);
1347 if (version.isEmpty())
1348 version = QLatin1String("1.0.0");
1349 QStringList versionComponents = version.split('.');
1350 while (versionComponents.size() < 3)
1351 versionComponents << QLatin1String("0");
1352 targetFileName += QLatin1Char('.');
1353 while (!versionComponents.isEmpty()) {
1354 const QString versionString = versionComponents.join(QLatin1Char('.'));
1355 deploymentData.addFile(destDirFor(ti).toString() + '/'
1356 + targetFileName + versionString, targetPath);
1357 versionComponents.removeLast();
1358 }
1359 }
1360 }
1361 break;
1362 default:
1363 break;
1364 }
1365 }
1366
getFullPathOf(const QmakeProFile * pro,Variable variable,const BuildConfiguration * bc)1367 static Utils::FilePath getFullPathOf(const QmakeProFile *pro, Variable variable,
1368 const BuildConfiguration *bc)
1369 {
1370 // Take last non-flag value, to cover e.g. '@echo $< && $$QMAKE_CC' or 'ccache gcc'
1371 const QStringList values = Utils::filtered(pro->variableValue(variable),
1372 [](const QString &value) {
1373 return !value.startsWith('-');
1374 });
1375 if (values.isEmpty())
1376 return Utils::FilePath();
1377 const QString exe = values.last();
1378 QTC_ASSERT(bc, return Utils::FilePath::fromString(exe));
1379 QFileInfo fi(exe);
1380 if (fi.isAbsolute())
1381 return Utils::FilePath::fromString(exe);
1382
1383 return bc->environment().searchInPath(exe);
1384 }
1385
testToolChain(ToolChain * tc,const FilePath & path) const1386 void QmakeBuildSystem::testToolChain(ToolChain *tc, const FilePath &path) const
1387 {
1388 if (!tc || path.isEmpty())
1389 return;
1390
1391 const Utils::FilePath expected = tc->compilerCommand();
1392 Environment env = buildConfiguration()->environment();
1393
1394 if (env.isSameExecutable(path.toString(), expected.toString()))
1395 return;
1396 const QPair<Utils::FilePath, Utils::FilePath> pair = qMakePair(expected, path);
1397 if (m_toolChainWarnings.contains(pair))
1398 return;
1399 // Suppress warnings on Apple machines where compilers in /usr/bin point into Xcode.
1400 // This will suppress some valid warnings, but avoids annoying Apple users with
1401 // spurious warnings all the time!
1402 if (pair.first.path().startsWith("/usr/bin/")
1403 && pair.second.path().contains("/Contents/Developer/Toolchains/")) {
1404 return;
1405 }
1406 TaskHub::addTask(
1407 BuildSystemTask(Task::Warning,
1408 QCoreApplication::translate(
1409 "QmakeProjectManager",
1410 "\"%1\" is used by qmake, but \"%2\" is configured in the kit.\n"
1411 "Please update your kit (%3) or choose a mkspec for qmake that matches "
1412 "your target environment better.")
1413 .arg(path.toUserOutput())
1414 .arg(expected.toUserOutput())
1415 .arg(kit()->displayName())));
1416 m_toolChainWarnings.insert(pair);
1417 }
1418
warnOnToolChainMismatch(const QmakeProFile * pro) const1419 void QmakeBuildSystem::warnOnToolChainMismatch(const QmakeProFile *pro) const
1420 {
1421 const BuildConfiguration *bc = buildConfiguration();
1422 testToolChain(ToolChainKitAspect::cToolChain(kit()), getFullPathOf(pro, Variable::QmakeCc, bc));
1423 testToolChain(ToolChainKitAspect::cxxToolChain(kit()),
1424 getFullPathOf(pro, Variable::QmakeCxx, bc));
1425 }
1426
executableFor(const QmakeProFile * file)1427 FilePath QmakeBuildSystem::executableFor(const QmakeProFile *file)
1428 {
1429 const ToolChain *const tc = ToolChainKitAspect::cxxToolChain(kit());
1430 if (!tc)
1431 return {};
1432
1433 TargetInformation ti = file->targetInformation();
1434 QString target;
1435
1436 QTC_ASSERT(file, return {});
1437
1438 if (tc->targetAbi().os() == Abi::DarwinOS
1439 && file->variableValue(Variable::Config).contains("app_bundle")) {
1440 target = ti.target + ".app/Contents/MacOS/" + ti.target;
1441 } else {
1442 const QString extension = file->singleVariableValue(Variable::TargetExt);
1443 if (extension.isEmpty())
1444 target = OsSpecificAspects::withExecutableSuffix(Abi::abiOsToOsType(tc->targetAbi().os()), ti.target);
1445 else
1446 target = ti.target + extension;
1447 }
1448 return (destDirFor(ti) / target).absoluteFilePath();
1449 }
1450
projectImporter() const1451 ProjectImporter *QmakeProject::projectImporter() const
1452 {
1453 if (!m_projectImporter)
1454 m_projectImporter = new QmakeProjectImporter(projectFilePath());
1455 return m_projectImporter;
1456 }
1457
asyncUpdateState() const1458 QmakeBuildSystem::AsyncUpdateState QmakeBuildSystem::asyncUpdateState() const
1459 {
1460 return m_asyncUpdateState;
1461 }
1462
rootProFile() const1463 QmakeProFile *QmakeBuildSystem::rootProFile() const
1464 {
1465 return m_rootProFile.get();
1466 }
1467
triggerParsing()1468 void QmakeBuildSystem::triggerParsing()
1469 {
1470 asyncUpdate();
1471 }
1472
filesGeneratedFrom(const FilePath & input) const1473 FilePaths QmakeBuildSystem::filesGeneratedFrom(const FilePath &input) const
1474 {
1475 if (!project()->rootProjectNode())
1476 return {};
1477
1478 if (const FileNode *file = fileNodeOf(project()->rootProjectNode(), input)) {
1479 const QmakeProFileNode *pro = dynamic_cast<QmakeProFileNode *>(file->parentFolderNode());
1480 QTC_ASSERT(pro, return {});
1481 if (const QmakeProFile *proFile = pro->proFile())
1482 return proFile->generatedFiles(buildDir(pro->filePath()),
1483 file->filePath(), file->fileType());
1484 }
1485 return {};
1486 }
1487
additionalData(Utils::Id id) const1488 QVariant QmakeBuildSystem::additionalData(Utils::Id id) const
1489 {
1490 if (id == "QmlDesignerImportPath")
1491 return m_rootProFile->variableValue(Variable::QmlDesignerImportPath);
1492 return BuildSystem::additionalData(id);
1493 }
1494
buildHelper(Action action,bool isFileBuild,QmakeProFileNode * profile,FileNode * buildableFile)1495 void QmakeBuildSystem::buildHelper(Action action, bool isFileBuild, QmakeProFileNode *profile,
1496 FileNode *buildableFile)
1497 {
1498 auto bc = qmakeBuildConfiguration();
1499
1500 if (!profile || !buildableFile)
1501 isFileBuild = false;
1502
1503 if (profile) {
1504 if (profile != project()->rootProjectNode() || isFileBuild)
1505 bc->setSubNodeBuild(profile->proFileNode());
1506 }
1507
1508 if (isFileBuild)
1509 bc->setFileNodeBuild(buildableFile);
1510 if (ProjectExplorerPlugin::saveModifiedFiles()) {
1511 if (action == BUILD)
1512 BuildManager::buildList(bc->buildSteps());
1513 else if (action == CLEAN)
1514 BuildManager::buildList(bc->cleanSteps());
1515 else if (action == REBUILD)
1516 BuildManager::buildLists({bc->cleanSteps(), bc->buildSteps()});
1517 }
1518
1519 bc->setSubNodeBuild(nullptr);
1520 bc->setFileNodeBuild(nullptr);
1521 }
1522
1523 } // QmakeProjectManager
1524
1525 #include "qmakeproject.moc"
1526