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