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 "qmakeparsernodes.h"
27 
28 #include "qmakeproject.h"
29 #include "qmakeprojectmanagerconstants.h"
30 #include "qmakebuildconfiguration.h"
31 
32 #include <android/androidconstants.h>
33 #include <coreplugin/documentmanager.h>
34 #include <coreplugin/editormanager/editormanager.h>
35 #include <coreplugin/icore.h>
36 #include <coreplugin/iversioncontrol.h>
37 #include <coreplugin/vcsmanager.h>
38 #include <cpptools/cpptoolsconstants.h>
39 #include <projectexplorer/editorconfiguration.h>
40 #include <projectexplorer/projectexplorer.h>
41 #include <projectexplorer/projectexplorerconstants.h>
42 #include <projectexplorer/target.h>
43 #include <projectexplorer/taskhub.h>
44 #include <qtsupport/profilereader.h>
45 #include <texteditor/icodestylepreferences.h>
46 #include <texteditor/tabsettings.h>
47 #include <texteditor/texteditorsettings.h>
48 
49 #include <utils/algorithm.h>
50 #include <utils/filesystemwatcher.h>
51 #include <utils/qtcprocess.h>
52 #include <utils/mimetypes/mimedatabase.h>
53 #include <utils/stringutils.h>
54 #include <utils/temporarydirectory.h>
55 #include <utils/QtConcurrentTools>
56 
57 #include <QLoggingCategory>
58 #include <QMessageBox>
59 #include <QTextCodec>
60 
61 using namespace Core;
62 using namespace ProjectExplorer;
63 using namespace QmakeProjectManager;
64 using namespace QmakeProjectManager::Internal;
65 using namespace QMakeInternal;
66 using namespace Utils;
67 
68 namespace QmakeProjectManager {
69 
70 static Q_LOGGING_CATEGORY(qmakeParse, "qtc.qmake.parsing", QtWarningMsg);
71 
qHash(Variable key,uint seed)72 uint qHash(Variable key, uint seed) { return ::qHash(static_cast<int>(key), seed); }
qHash(FileOrigin fo)73 uint qHash(FileOrigin fo) { return ::qHash(int(fo)); }
74 
75 namespace Internal {
76 
77 Q_LOGGING_CATEGORY(qmakeNodesLog, "qtc.qmake.nodes", QtWarningMsg)
78 
79 class QmakeEvalInput
80 {
81 public:
82     QString projectDir;
83     FilePath projectFilePath;
84     FilePath buildDirectory;
85     FilePath sysroot;
86     QtSupport::ProFileReader *readerExact;
87     QtSupport::ProFileReader *readerCumulative;
88     QMakeGlobals *qmakeGlobals;
89     QMakeVfs *qmakeVfs;
90     QSet<FilePath> parentFilePaths;
91     bool includedInExcactParse;
92 };
93 
94 class QmakePriFileEvalResult
95 {
96 public:
97     QSet<FilePath> folders;
98     QSet<FilePath> recursiveEnumerateFiles;
99     QMap<FileType, QSet<FilePath>> foundFilesExact;
100     QMap<FileType, QSet<FilePath>> foundFilesCumulative;
101 };
102 
103 class QmakeIncludedPriFile
104 {
105 public:
106     ProFile *proFile;
107     Utils::FilePath name;
108     QmakePriFileEvalResult result;
109     QMap<Utils::FilePath, QmakeIncludedPriFile *> children;
110 
~QmakeIncludedPriFile()111     ~QmakeIncludedPriFile()
112     {
113         qDeleteAll(children);
114     }
115 };
116 
117 class QmakeEvalResult
118 {
119 public:
~QmakeEvalResult()120     ~QmakeEvalResult() { qDeleteAll(directChildren); }
121 
122     enum EvalResultState { EvalAbort, EvalFail, EvalPartial, EvalOk };
123     EvalResultState state;
124     ProjectType projectType;
125 
126     QStringList subProjectsNotToDeploy;
127     QSet<FilePath> exactSubdirs;
128     QmakeIncludedPriFile includedFiles;
129     TargetInformation targetInformation;
130     InstallsList installsList;
131     QHash<Variable, QStringList> newVarValues;
132     QStringList errors;
133     QSet<QString> directoriesWithWildcards;
134     QList<QmakePriFile *> directChildren;
135     QList<QPair<QmakePriFile *, QmakePriFileEvalResult>> priFiles;
136     QList<QmakeProFile *> proFiles;
137 };
138 
139 } // namespace Internal
140 
QmakePriFile(QmakeBuildSystem * buildSystem,QmakeProFile * qmakeProFile,const FilePath & filePath)141 QmakePriFile::QmakePriFile(QmakeBuildSystem *buildSystem, QmakeProFile *qmakeProFile,
142                            const FilePath &filePath) : m_filePath(filePath)
143 {
144     finishInitialization(buildSystem, qmakeProFile);
145 }
146 
QmakePriFile(const FilePath & filePath)147 QmakePriFile::QmakePriFile(const FilePath &filePath) : m_filePath(filePath) { }
148 
finishInitialization(QmakeBuildSystem * buildSystem,QmakeProFile * qmakeProFile)149 void QmakePriFile::finishInitialization(QmakeBuildSystem *buildSystem, QmakeProFile *qmakeProFile)
150 {
151     QTC_ASSERT(buildSystem, return);
152     m_buildSystem = buildSystem;
153     m_qmakeProFile = qmakeProFile;
154 }
155 
filePath() const156 FilePath QmakePriFile::filePath() const
157 {
158     return m_filePath;
159 }
160 
directoryPath() const161 FilePath QmakePriFile::directoryPath() const
162 {
163     return filePath().parentDir();
164 }
165 
displayName() const166 QString QmakePriFile::displayName() const
167 {
168     return filePath().completeBaseName();
169 }
170 
parent() const171 QmakePriFile *QmakePriFile::parent() const
172 {
173     return m_parent;
174 }
175 
project() const176 QmakeProject *QmakePriFile::project() const
177 {
178     return static_cast<QmakeProject *>(m_buildSystem->project());
179 }
180 
children() const181 QVector<QmakePriFile *> QmakePriFile::children() const
182 {
183     return m_children;
184 }
185 
findPriFile(const FilePath & fileName)186 QmakePriFile *QmakePriFile::findPriFile(const FilePath &fileName)
187 {
188     if (fileName == filePath())
189         return this;
190     for (QmakePriFile *n : qAsConst(m_children)) {
191         if (QmakePriFile *result = n->findPriFile(fileName))
192             return result;
193     }
194     return nullptr;
195 }
196 
findPriFile(const FilePath & fileName) const197 const QmakePriFile *QmakePriFile::findPriFile(const FilePath &fileName) const
198 {
199     if (fileName == filePath())
200         return this;
201     for (const QmakePriFile *n : qAsConst(m_children)) {
202         if (const QmakePriFile *result = n->findPriFile(fileName))
203             return result;
204     }
205     return nullptr;
206 }
207 
makeEmpty()208 void QmakePriFile::makeEmpty()
209 {
210     qDeleteAll(m_children);
211     m_children.clear();
212 }
213 
files(const FileType & type) const214 SourceFiles QmakePriFile::files(const FileType &type) const
215 {
216     return m_files.value(type);
217 }
218 
collectFiles(const FileType & type) const219 const QSet<FilePath> QmakePriFile::collectFiles(const FileType &type) const
220 {
221     QSet<FilePath> allFiles = transform(files(type),
222                                         [](const SourceFile &sf) { return sf.first; });
223     for (const QmakePriFile * const priFile : qAsConst(m_children)) {
224         if (!dynamic_cast<const QmakeProFile *>(priFile))
225             allFiles.unite(priFile->collectFiles(type));
226     }
227     return allFiles;
228 }
229 
~QmakePriFile()230 QmakePriFile::~QmakePriFile()
231 {
232     watchFolders( {} );
233     qDeleteAll(m_children);
234 }
235 
scheduleUpdate()236 void QmakePriFile::scheduleUpdate()
237 {
238     QTC_ASSERT(m_buildSystem, return);
239     QtSupport::ProFileCacheManager::instance()->discardFile(
240                 filePath().toString(), m_buildSystem->qmakeVfs());
241     m_qmakeProFile->scheduleUpdate(QmakeProFile::ParseLater);
242 }
243 
baseVPaths(QtSupport::ProFileReader * reader,const QString & projectDir,const QString & buildDir)244 QStringList QmakePriFile::baseVPaths(QtSupport::ProFileReader *reader, const QString &projectDir, const QString &buildDir)
245 {
246     QStringList result;
247     if (!reader)
248         return result;
249     result += reader->absolutePathValues(QLatin1String("VPATH"), projectDir);
250     result << projectDir; // QMAKE_ABSOLUTE_SOURCE_PATH
251     result << buildDir;
252     result.removeDuplicates();
253     return result;
254 }
255 
fullVPaths(const QStringList & baseVPaths,QtSupport::ProFileReader * reader,const QString & qmakeVariable,const QString & projectDir)256 QStringList QmakePriFile::fullVPaths(const QStringList &baseVPaths, QtSupport::ProFileReader *reader,
257                                                const QString &qmakeVariable, const QString &projectDir)
258 {
259     QStringList vPaths;
260     if (!reader)
261         return vPaths;
262     vPaths = reader->absolutePathValues(QLatin1String("VPATH_") + qmakeVariable, projectDir);
263     vPaths += baseVPaths;
264     vPaths.removeDuplicates();
265     return vPaths;
266 }
267 
recursiveEnumerate(const QString & folder)268 QSet<FilePath> QmakePriFile::recursiveEnumerate(const QString &folder)
269 {
270     QSet<FilePath> result;
271     QDir dir(folder);
272     dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
273     foreach (const QFileInfo &file, dir.entryInfoList()) {
274         if (file.isDir() && !file.isSymLink())
275             result += recursiveEnumerate(file.absoluteFilePath());
276         else if (!Core::EditorManager::isAutoSaveFile(file.fileName()))
277             result += FilePath::fromFileInfo(file);
278     }
279     return result;
280 }
281 
fileListForVar(const QHash<QString,QVector<ProFileEvaluator::SourceFile>> & sourceFiles,const QString & varName)282 static QStringList fileListForVar(
283         const QHash<QString, QVector<ProFileEvaluator::SourceFile>> &sourceFiles,
284         const QString &varName)
285 {
286     const QVector<ProFileEvaluator::SourceFile> &sources = sourceFiles[varName];
287     QStringList result;
288     result.reserve(sources.size());
289     foreach (const ProFileEvaluator::SourceFile &sf, sources)
290         result << sf.fileName;
291     return result;
292 }
293 
extractSources(QHash<int,QmakePriFileEvalResult * > proToResult,QmakePriFileEvalResult * fallback,QVector<ProFileEvaluator::SourceFile> sourceFiles,FileType type,bool cumulative)294 void QmakePriFile::extractSources(
295         QHash<int, QmakePriFileEvalResult *> proToResult, QmakePriFileEvalResult *fallback,
296         QVector<ProFileEvaluator::SourceFile> sourceFiles, FileType type, bool cumulative)
297 {
298     foreach (const ProFileEvaluator::SourceFile &source, sourceFiles) {
299         auto *result = proToResult.value(source.proFileId);
300         if (!result)
301             result = fallback;
302         auto &foundFiles = cumulative ? result->foundFilesCumulative : result->foundFilesExact;
303         foundFiles[type].insert(FilePath::fromString(source.fileName));
304     }
305 }
306 
extractInstalls(QHash<int,QmakePriFileEvalResult * > proToResult,QmakePriFileEvalResult * fallback,const InstallsList & installList)307 void QmakePriFile::extractInstalls(
308         QHash<int, QmakePriFileEvalResult *> proToResult, QmakePriFileEvalResult *fallback,
309         const InstallsList &installList)
310 {
311     for (const InstallsItem &item : installList.items) {
312         for (const ProFileEvaluator::SourceFile &source : item.files) {
313             auto *result = proToResult.value(source.proFileId);
314             if (!result)
315                 result = fallback;
316             result->folders.insert(FilePath::fromString(source.fileName));
317         }
318     }
319 }
320 
processValues(QmakePriFileEvalResult & result)321 void QmakePriFile::processValues(QmakePriFileEvalResult &result)
322 {
323     // Remove non existing items and non folders
324     auto it = result.folders.begin();
325     while (it != result.folders.end()) {
326         QFileInfo fi((*it).toFileInfo());
327         if (fi.exists()) {
328             if (fi.isDir()) {
329                 result.recursiveEnumerateFiles += recursiveEnumerate((*it).toString());
330                 // keep directories
331                 ++it;
332             } else {
333                 // move files directly to recursiveEnumerateFiles
334                 result.recursiveEnumerateFiles += (*it);
335                 it = result.folders.erase(it);
336             }
337         } else {
338             // do remove non exsting stuff
339             it = result.folders.erase(it);
340         }
341     }
342 
343     for (int i = 0; i < static_cast<int>(FileType::FileTypeSize); ++i) {
344         auto type = static_cast<FileType>(i);
345         for (QSet<FilePath> * const foundFiles
346              : {&result.foundFilesExact[type], &result.foundFilesCumulative[type]}) {
347             result.recursiveEnumerateFiles.subtract(*foundFiles);
348             QSet<FilePath> newFilePaths = filterFilesProVariables(type, *foundFiles);
349             newFilePaths += filterFilesRecursiveEnumerata(type, result.recursiveEnumerateFiles);
350             *foundFiles = newFilePaths;
351         }
352     }
353 }
354 
update(const Internal::QmakePriFileEvalResult & result)355 void QmakePriFile::update(const Internal::QmakePriFileEvalResult &result)
356 {
357     m_recursiveEnumerateFiles = result.recursiveEnumerateFiles;
358     watchFolders(result.folders);
359 
360     for (int i = 0; i < static_cast<int>(FileType::FileTypeSize); ++i) {
361         const auto type = static_cast<FileType>(i);
362         SourceFiles &files = m_files[type];
363         files.clear();
364         const QSet<FilePath> exactFps = result.foundFilesExact.value(type);
365         for (const FilePath &exactFp : exactFps)
366             files << qMakePair(exactFp, FileOrigin::ExactParse);
367         for (const FilePath &cumulativeFp : result.foundFilesCumulative.value(type)) {
368             if (!exactFps.contains(cumulativeFp))
369                 files << qMakePair(cumulativeFp, FileOrigin::CumulativeParse);
370         }
371     }
372 }
373 
watchFolders(const QSet<FilePath> & folders)374 void QmakePriFile::watchFolders(const QSet<FilePath> &folders)
375 {
376     const QSet<QString> folderStrings =
377             Utils::transform(folders, &FilePath::toString);
378     QSet<QString> toUnwatch = m_watchedFolders;
379     toUnwatch.subtract(folderStrings);
380 
381     QSet<QString> toWatch = folderStrings;
382     toWatch.subtract(m_watchedFolders);
383 
384     if (m_buildSystem) {
385         // Check needed on early exit of QmakeProFile::applyEvaluate?
386         m_buildSystem->unwatchFolders(Utils::toList(toUnwatch), this);
387         m_buildSystem->watchFolders(Utils::toList(toWatch), this);
388     }
389 
390     m_watchedFolders = folderStrings;
391 }
392 
continuationIndent() const393 QString QmakePriFile::continuationIndent() const
394 {
395     const EditorConfiguration *editorConf = project()->editorConfiguration();
396     const TextEditor::TabSettings &tabSettings = editorConf->useGlobalSettings()
397             ? TextEditor::TextEditorSettings::codeStyle()->tabSettings()
398             : editorConf->codeStyle()->tabSettings();
399     if (tabSettings.m_continuationAlignBehavior == TextEditor::TabSettings::ContinuationAlignWithIndent
400             && tabSettings.m_tabPolicy == TextEditor::TabSettings::TabsOnlyTabPolicy) {
401         return QString("\t");
402     }
403     return QString(tabSettings.m_indentSize, ' ');
404 }
405 
buildSystem() const406 QmakeBuildSystem *QmakePriFile::buildSystem() const
407 {
408     return m_buildSystem;
409 }
410 
knowsFile(const FilePath & filePath) const411 bool QmakePriFile::knowsFile(const FilePath &filePath) const
412 {
413     return m_recursiveEnumerateFiles.contains(filePath);
414 }
415 
folderChanged(const QString & changedFolder,const QSet<FilePath> & newFiles)416 bool QmakePriFile::folderChanged(const QString &changedFolder, const QSet<FilePath> &newFiles)
417 {
418     qCDebug(qmakeParse()) << "QmakePriFile::folderChanged";
419 
420     QSet<FilePath> addedFiles = newFiles;
421     addedFiles.subtract(m_recursiveEnumerateFiles);
422 
423     QSet<FilePath> removedFiles = m_recursiveEnumerateFiles;
424     removedFiles.subtract(newFiles);
425 
426     foreach (const FilePath &file, removedFiles) {
427         if (!file.isChildOf(FilePath::fromString(changedFolder)))
428             removedFiles.remove(file);
429     }
430 
431     if (addedFiles.isEmpty() && removedFiles.isEmpty())
432         return false;
433 
434     m_recursiveEnumerateFiles = newFiles;
435 
436     // Apply the differences per file type
437     for (int i = 0; i < static_cast<int>(FileType::FileTypeSize); ++i) {
438         auto type = static_cast<FileType>(i);
439         QSet<FilePath> add = filterFilesRecursiveEnumerata(type, addedFiles);
440         QSet<FilePath> remove = filterFilesRecursiveEnumerata(type, removedFiles);
441 
442         if (!add.isEmpty() || !remove.isEmpty()) {
443             qCDebug(qmakeParse()) << "For type" << static_cast<int>(type) <<"\n"
444                                   << "added files"  <<  add << "\n"
445                                   << "removed files" << remove;
446             SourceFiles &currentFiles = m_files[type];
447             for (const FilePath &fp : add) {
448                 if (!contains(currentFiles, [&fp](const SourceFile &sf) { return sf.first == fp; }))
449                     currentFiles.insert(qMakePair(fp, FileOrigin::ExactParse));
450             }
451             for (const FilePath &fp : remove) {
452                 const auto it = std::find_if(currentFiles.begin(), currentFiles.end(),
453                                              [&fp](const SourceFile &sf) {
454                     return sf.first == fp; });
455                 if (it != currentFiles.end())
456                     currentFiles.erase(it);
457             }
458         }
459     }
460     return true;
461 }
462 
deploysFolder(const QString & folder) const463 bool QmakePriFile::deploysFolder(const QString &folder) const
464 {
465     QString f = folder;
466     const QChar slash = QLatin1Char('/');
467     if (!f.endsWith(slash))
468         f.append(slash);
469 
470     foreach (const QString &wf, m_watchedFolders) {
471         if (f.startsWith(wf)
472             && (wf.endsWith(slash)
473                 || (wf.length() < f.length() && f.at(wf.length()) == slash)))
474             return true;
475     }
476     return false;
477 }
478 
subPriFilesExact() const479 QVector<QmakePriFile *> QmakePriFile::subPriFilesExact() const
480 {
481     return Utils::filtered(m_children, &QmakePriFile::includedInExactParse);
482 }
483 
proFile() const484 QmakeProFile *QmakePriFile::proFile() const
485 {
486     return m_qmakeProFile;
487 }
488 
includedInExactParse() const489 bool QmakePriFile::includedInExactParse() const
490 {
491     return m_includedInExactParse;
492 }
493 
setIncludedInExactParse(bool b)494 void QmakePriFile::setIncludedInExactParse(bool b)
495 {
496     m_includedInExactParse = b;
497 }
498 
canAddSubProject(const QString & proFilePath) const499 bool QmakePriFile::canAddSubProject(const QString &proFilePath) const
500 {
501     QFileInfo fi(proFilePath);
502     if (fi.suffix() == QLatin1String("pro")
503         || fi.suffix() == QLatin1String("pri"))
504         return true;
505     return false;
506 }
507 
simplifyProFilePath(const FilePath & proFilePath)508 static FilePath simplifyProFilePath(const FilePath &proFilePath)
509 {
510     // if proFilePath is like: _path_/projectName/projectName.pro
511     // we simplify it to: _path_/projectName
512     QFileInfo fi = proFilePath.toFileInfo(); // FIXME
513     const QString parentPath = fi.absolutePath();
514     QFileInfo parentFi(parentPath);
515     if (parentFi.fileName() == fi.completeBaseName())
516         return FilePath::fromString(parentPath);
517     return proFilePath;
518 }
519 
addSubProject(const QString & proFile)520 bool QmakePriFile::addSubProject(const QString &proFile)
521 {
522     FilePaths uniqueProFilePaths;
523     if (!m_recursiveEnumerateFiles.contains(FilePath::fromString(proFile)))
524         uniqueProFilePaths.append(simplifyProFilePath(FilePath::fromString(proFile)));
525 
526     FilePaths failedFiles;
527     changeFiles(QLatin1String(Constants::PROFILE_MIMETYPE), uniqueProFilePaths, &failedFiles, AddToProFile);
528 
529     return failedFiles.isEmpty();
530 }
531 
removeSubProjects(const QString & proFilePath)532 bool QmakePriFile::removeSubProjects(const QString &proFilePath)
533 {
534     FilePaths failedOriginalFiles;
535     changeFiles(QLatin1String(Constants::PROFILE_MIMETYPE), {FilePath::fromString(proFilePath)}, &failedOriginalFiles, RemoveFromProFile);
536 
537     FilePaths simplifiedProFiles = Utils::transform(failedOriginalFiles, &simplifyProFilePath);
538 
539     FilePaths failedSimplifiedFiles;
540     changeFiles(QLatin1String(Constants::PROFILE_MIMETYPE), simplifiedProFiles, &failedSimplifiedFiles, RemoveFromProFile);
541 
542     return failedSimplifiedFiles.isEmpty();
543 }
544 
addFiles(const FilePaths & filePaths,FilePaths * notAdded)545 bool QmakePriFile::addFiles(const FilePaths &filePaths, FilePaths *notAdded)
546 {
547     // If a file is already referenced in the .pro file then we don't add them.
548     // That ignores scopes and which variable was used to reference the file
549     // So it's obviously a bit limited, but in those cases you need to edit the
550     // project files manually anyway.
551 
552     using TypeFileMap = QMap<QString, FilePaths>;
553     // Split into lists by file type and bulk-add them.
554     TypeFileMap typeFileMap;
555     for (const FilePath &file : filePaths) {
556         const Utils::MimeType mt = Utils::mimeTypeForFile(file);
557         typeFileMap[mt.name()] << file;
558     }
559 
560     FilePaths failedFiles;
561     foreach (const QString &type, typeFileMap.keys()) {
562         const FilePaths typeFiles = typeFileMap.value(type);
563         FilePaths qrcFiles; // the list of qrc files referenced from ui files
564         if (type == QLatin1String(ProjectExplorer::Constants::RESOURCE_MIMETYPE)) {
565             for (const FilePath &formFile : typeFiles) {
566                 const FilePaths resourceFiles = formResources(formFile);
567                 for (const FilePath &resourceFile : resourceFiles)
568                     if (!qrcFiles.contains(resourceFile))
569                         qrcFiles.append(resourceFile);
570             }
571         }
572 
573         FilePaths uniqueQrcFiles;
574         for (const FilePath &file : qAsConst(qrcFiles)) {
575             if (!m_recursiveEnumerateFiles.contains(file))
576                 uniqueQrcFiles.append(file);
577         }
578 
579         FilePaths uniqueFilePaths;
580         for (const FilePath &file : typeFiles) {
581             if (!m_recursiveEnumerateFiles.contains(file))
582                 uniqueFilePaths.append(file);
583         }
584         FilePath::sort(uniqueFilePaths);
585 
586         changeFiles(type, uniqueFilePaths, &failedFiles, AddToProFile);
587         if (notAdded)
588             *notAdded += failedFiles;
589         changeFiles(QLatin1String(ProjectExplorer::Constants::RESOURCE_MIMETYPE), uniqueQrcFiles, &failedFiles, AddToProFile);
590         if (notAdded)
591             *notAdded += failedFiles;
592     }
593     return failedFiles.isEmpty();
594 }
595 
removeFiles(const FilePaths & filePaths,FilePaths * notRemoved)596 bool QmakePriFile::removeFiles(const FilePaths &filePaths, FilePaths *notRemoved)
597 {
598     FilePaths failedFiles;
599     using TypeFileMap = QMap<QString, FilePaths>;
600     // Split into lists by file type and bulk-add them.
601     TypeFileMap typeFileMap;
602     for (const FilePath &file : filePaths) {
603         const Utils::MimeType mt = Utils::mimeTypeForFile(file);
604         typeFileMap[mt.name()] << file;
605     }
606     foreach (const QString &type, typeFileMap.keys()) {
607         const FilePaths typeFiles = typeFileMap.value(type);
608         changeFiles(type, typeFiles, &failedFiles, RemoveFromProFile);
609         if (notRemoved)
610             *notRemoved = failedFiles;
611     }
612     return failedFiles.isEmpty();
613 }
614 
deleteFiles(const FilePaths & filePaths)615 bool QmakePriFile::deleteFiles(const FilePaths &filePaths)
616 {
617     removeFiles(filePaths);
618     return true;
619 }
620 
canRenameFile(const FilePath & oldFilePath,const FilePath & newFilePath)621 bool QmakePriFile::canRenameFile(const FilePath &oldFilePath, const FilePath &newFilePath)
622 {
623     if (newFilePath.isEmpty())
624         return false;
625 
626     bool changeProFileOptional = deploysFolder(oldFilePath.absolutePath().toString());
627     if (changeProFileOptional)
628         return true;
629 
630     return renameFile(oldFilePath, newFilePath, Change::TestOnly);
631 }
632 
renameFile(const FilePath & oldFilePath,const FilePath & newFilePath)633 bool QmakePriFile::renameFile(const FilePath &oldFilePath, const FilePath &newFilePath)
634 {
635     if (newFilePath.isEmpty())
636         return false;
637 
638     bool changeProFileOptional = deploysFolder(oldFilePath.absolutePath().toString());
639     if (renameFile(oldFilePath, newFilePath, Change::Save))
640         return true;
641     return changeProFileOptional;
642 }
643 
addDependencies(const QStringList & dependencies)644 bool QmakePriFile::addDependencies(const QStringList &dependencies)
645 {
646     if (dependencies.isEmpty())
647         return true;
648     if (!prepareForChange())
649         return false;
650 
651     QStringList qtDependencies = filtered(dependencies, [](const QString &dep) {
652         return dep.length() > 3 && dep.startsWith("Qt.");
653     });
654     qtDependencies = transform(qtDependencies, [](const QString &dep) {
655         return dep.mid(3);
656     });
657     qtDependencies.removeOne("core");
658     if (qtDependencies.isEmpty())
659         return true;
660 
661     const QPair<ProFile *, QStringList> pair = readProFile();
662     ProFile * const includeFile = pair.first;
663     if (!includeFile)
664         return false;
665     QStringList lines = pair.second;
666 
667     const QString indent = continuationIndent();
668     const ProWriter::PutFlags appendFlags(ProWriter::AppendValues | ProWriter::AppendOperator);
669     if (!proFile()->variableValue(Variable::Config).contains("qt")) {
670         if (lines.removeAll("CONFIG -= qt") == 0) {
671             ProWriter::putVarValues(includeFile, &lines, {"qt"}, "CONFIG", appendFlags,
672                                     QString(), indent);
673         }
674     }
675 
676     const QStringList currentQtDependencies = proFile()->variableValue(Variable::Qt);
677     qtDependencies = filtered(qtDependencies, [currentQtDependencies](const QString &dep) {
678         return !currentQtDependencies.contains(dep);
679     });
680     if (!qtDependencies.isEmpty()) {
681         ProWriter::putVarValues(includeFile, &lines, qtDependencies,  "QT", appendFlags,
682                                 QString(), indent);
683     }
684 
685     save(lines);
686     includeFile->deref();
687     return true;
688 }
689 
saveModifiedEditors()690 bool QmakePriFile::saveModifiedEditors()
691 {
692     Core::IDocument *document = Core::DocumentModel::documentForFilePath(filePath());
693     if (!document || !document->isModified())
694         return true;
695 
696     if (!Core::DocumentManager::saveDocument(document))
697         return false;
698 
699     // force instant reload of ourselves
700     QtSupport::ProFileCacheManager::instance()->discardFile(
701                 filePath().toString(), m_buildSystem->qmakeVfs());
702 
703     m_buildSystem->notifyChanged(filePath());
704     return true;
705 }
706 
formResources(const FilePath & formFile) const707 FilePaths QmakePriFile::formResources(const FilePath &formFile) const
708 {
709     QStringList resourceFiles;
710     QFile file(formFile.toString());
711     if (!file.open(QIODevice::ReadOnly))
712         return {};
713 
714     QXmlStreamReader reader(&file);
715 
716     QFileInfo fi(formFile.toString());
717     QDir formDir = fi.absoluteDir();
718     while (!reader.atEnd()) {
719         reader.readNext();
720         if (reader.isStartElement()) {
721             if (reader.name() == QLatin1String("iconset")) {
722                 const QXmlStreamAttributes attributes = reader.attributes();
723                 if (attributes.hasAttribute(QLatin1String("resource")))
724                     resourceFiles.append(QDir::cleanPath(formDir.absoluteFilePath(
725                                   attributes.value(QLatin1String("resource")).toString())));
726             } else if (reader.name() == QLatin1String("include")) {
727                 const QXmlStreamAttributes attributes = reader.attributes();
728                 if (attributes.hasAttribute(QLatin1String("location")))
729                     resourceFiles.append(QDir::cleanPath(formDir.absoluteFilePath(
730                                   attributes.value(QLatin1String("location")).toString())));
731 
732             }
733         }
734     }
735 
736     if (reader.hasError())
737         qWarning() << "Could not read form file:" << formFile;
738 
739     return Utils::transform(resourceFiles, &FilePath::fromString);
740 }
741 
ensureWriteableProFile(const QString & file)742 bool QmakePriFile::ensureWriteableProFile(const QString &file)
743 {
744     // Ensure that the file is not read only
745     QFileInfo fi(file);
746     if (!fi.isWritable()) {
747         // Try via vcs manager
748         Core::IVersionControl *versionControl = Core::VcsManager::findVersionControlForDirectory(fi.absolutePath());
749         if (!versionControl || !versionControl->vcsOpen(file)) {
750             bool makeWritable = QFile::setPermissions(file, fi.permissions() | QFile::WriteUser);
751             if (!makeWritable) {
752                 QMessageBox::warning(Core::ICore::dialogParent(),
753                                      QCoreApplication::translate("QmakePriFile", "Failed"),
754                                      QCoreApplication::translate("QmakePriFile",
755                                                                  "Could not write project file %1.")
756                                          .arg(file));
757                 return false;
758             }
759         }
760     }
761     return true;
762 }
763 
readProFile()764 QPair<ProFile *, QStringList> QmakePriFile::readProFile()
765 {
766     QStringList lines;
767     ProFile *includeFile = nullptr;
768     {
769         QString contents;
770         {
771             QString errorMsg;
772             if (TextFileFormat::readFile(filePath(),
773                                          Core::EditorManager::defaultTextCodec(),
774                                          &contents,
775                                          &m_textFormat,
776                                          &errorMsg)
777                 != TextFileFormat::ReadSuccess) {
778                 QmakeBuildSystem::proFileParseError(errorMsg, filePath());
779                 return qMakePair(includeFile, lines);
780             }
781             lines = contents.split('\n');
782         }
783 
784         QMakeVfs vfs;
785         QtSupport::ProMessageHandler handler;
786         QMakeParser parser(nullptr, &vfs, &handler);
787         includeFile = parser.parsedProBlock(Utils::make_stringview(contents),
788                                             0,
789                                             filePath().toString(),
790                                             1);
791     }
792     return qMakePair(includeFile, lines);
793 }
794 
prepareForChange()795 bool QmakePriFile::prepareForChange()
796 {
797     return saveModifiedEditors() && ensureWriteableProFile(filePath().toString());
798 }
799 
renameFile(const FilePath & oldFilePath,const FilePath & newFilePath,Change mode)800 bool QmakePriFile::renameFile(const FilePath &oldFilePath, const FilePath &newFilePath, Change mode)
801 {
802     if (!prepareForChange())
803         return false;
804 
805     QPair<ProFile *, QStringList> pair = readProFile();
806     ProFile *includeFile = pair.first;
807     QStringList lines = pair.second;
808 
809     if (!includeFile)
810         return false;
811 
812     QDir priFileDir = QDir(m_qmakeProFile->directoryPath().toString());
813     ProWriter::VarLocations removedLocations;
814     const QStringList notChanged = ProWriter::removeFiles(includeFile,
815                                                           &lines,
816                                                           priFileDir,
817                                                           {oldFilePath.toString()},
818                                                           varNamesForRemoving(),
819                                                           &removedLocations);
820 
821     includeFile->deref();
822     if (!notChanged.isEmpty())
823         return false;
824     QTC_ASSERT(!removedLocations.isEmpty(), return false);
825 
826     int endLine = lines.count();
827     reverseForeach(removedLocations,
828                    [this, &newFilePath, &lines, &endLine](const ProWriter::VarLocation &loc) {
829         QStringList currentLines = lines.mid(loc.second, endLine - loc.second);
830         const QString currentContents = currentLines.join('\n');
831 
832         // Reparse necessary due to changed contents.
833         QMakeParser parser(nullptr, nullptr, nullptr);
834         ProFile *const proFile = parser.parsedProBlock(Utils::make_stringview(currentContents),
835                                                        0,
836                                                        filePath().toString(),
837                                                        1,
838                                                        QMakeParser::FullGrammar);
839         QTC_ASSERT(proFile, return); // The file should still be valid after what we did.
840 
841         ProWriter::addFiles(proFile,
842                             &currentLines,
843                             {newFilePath.toString()},
844                             loc.first,
845                             continuationIndent());
846         lines = lines.mid(0, loc.second) + currentLines + lines.mid(endLine);
847         endLine = loc.second;
848         proFile->deref();
849     });
850 
851     if (mode == Change::Save)
852         save(lines);
853     return true;
854 }
855 
changeFiles(const QString & mimeType,const FilePaths & filePaths,FilePaths * notChanged,ChangeType change,Change mode)856 void QmakePriFile::changeFiles(const QString &mimeType,
857                                const FilePaths &filePaths,
858                                FilePaths *notChanged,
859                                ChangeType change, Change mode)
860 {
861     if (filePaths.isEmpty())
862         return;
863 
864     *notChanged = filePaths;
865 
866     // Check for modified editors
867     if (!prepareForChange())
868         return;
869 
870     QPair<ProFile *, QStringList> pair = readProFile();
871     ProFile *includeFile = pair.first;
872     QStringList lines = pair.second;
873 
874     if (!includeFile)
875         return;
876 
877     qCDebug(qmakeNodesLog) << Q_FUNC_INFO << "mime type:" << mimeType << "file paths:"
878                            << filePaths << "change type:" << int(change) << "mode:" << int(mode);
879     if (change == AddToProFile) {
880         // Use the first variable for adding.
881         ProWriter::addFiles(includeFile, &lines,
882                             Utils::transform(filePaths, &FilePath::toString),
883                             varNameForAdding(mimeType),
884                             continuationIndent());
885         notChanged->clear();
886     } else { // RemoveFromProFile
887         QDir priFileDir = QDir(m_qmakeProFile->directoryPath().toString());
888         *notChanged = Utils::transform(
889                     ProWriter::removeFiles(includeFile, &lines, priFileDir,
890                                            Utils::transform(filePaths, &FilePath::toString),
891                                            varNamesForRemoving()),
892                     &FilePath::fromString);
893     }
894 
895     // save file
896     if (mode == Change::Save)
897         save(lines);
898     includeFile->deref();
899 }
900 
addChild(QmakePriFile * pf)901 void QmakePriFile::addChild(QmakePriFile *pf)
902 {
903     QTC_ASSERT(!m_children.contains(pf), return);
904     QTC_ASSERT(!pf->parent(), return);
905     m_children.append(pf);
906     pf->setParent(this);
907 }
908 
setParent(QmakePriFile * p)909 void QmakePriFile::setParent(QmakePriFile *p)
910 {
911     QTC_ASSERT(!m_parent, return);
912     m_parent = p;
913 }
914 
setProVariable(const QString & var,const QStringList & values,const QString & scope,int flags)915 bool QmakePriFile::setProVariable(const QString &var, const QStringList &values, const QString &scope, int flags)
916 {
917     if (!prepareForChange())
918         return false;
919 
920     QPair<ProFile *, QStringList> pair = readProFile();
921     ProFile *includeFile = pair.first;
922     QStringList lines = pair.second;
923 
924     if (!includeFile)
925         return false;
926 
927     ProWriter::putVarValues(includeFile, &lines, values, var,
928                             ProWriter::PutFlags(flags),
929                             scope, continuationIndent());
930 
931     save(lines);
932     includeFile->deref();
933     return true;
934 }
935 
save(const QStringList & lines)936 void QmakePriFile::save(const QStringList &lines)
937 {
938     {
939         QTC_ASSERT(m_textFormat.codec, return);
940         FileChangeBlocker changeGuard(filePath());
941         QString errorMsg;
942         if (!m_textFormat.writeFile(filePath(), lines.join('\n'), &errorMsg)) {
943             QMessageBox::critical(Core::ICore::dialogParent(), QCoreApplication::translate(
944                                       "QmakePriFile", "File Error"), errorMsg);
945         }
946     }
947 
948     // This is a hack.
949     // We are saving twice in a very short timeframe, once the editor and once the ProFile.
950     // So the modification time might not change between those two saves.
951     // We manually tell each editor to reload it's file.
952     // (The .pro files are notified by the file system watcher.)
953     QStringList errorStrings;
954     Core::IDocument *document = Core::DocumentModel::documentForFilePath(filePath());
955     if (document) {
956         QString errorString;
957         if (!document->reload(&errorString, Core::IDocument::FlagReload, Core::IDocument::TypeContents))
958             errorStrings << errorString;
959     }
960     if (!errorStrings.isEmpty())
961         QMessageBox::warning(Core::ICore::dialogParent(),
962                              QCoreApplication::translate("QmakePriFile", "File Error"),
963                              errorStrings.join(QLatin1Char('\n')));
964 }
965 
varNames(FileType type,QtSupport::ProFileReader * readerExact)966 QStringList QmakePriFile::varNames(FileType type, QtSupport::ProFileReader *readerExact)
967 {
968     QStringList vars;
969     switch (type) {
970     case FileType::Header:
971         vars << "HEADERS" << "OBJECTIVE_HEADERS" << "PRECOMPILED_HEADER";
972         break;
973     case FileType::Source: {
974         vars << QLatin1String("SOURCES");
975         QStringList listOfExtraCompilers = readerExact->values(QLatin1String("QMAKE_EXTRA_COMPILERS"));
976         foreach (const QString &var, listOfExtraCompilers) {
977             QStringList inputs = readerExact->values(var + QLatin1String(".input"));
978             foreach (const QString &input, inputs)
979                 // FORMS, RESOURCES, and STATECHARTS are handled below, HEADERS and SOURCES above
980                 if (input != "FORMS"
981                         && input != "STATECHARTS"
982                         && input != "RESOURCES"
983                         && input != "SOURCES"
984                         && input != "HEADERS"
985                         && input != "OBJECTIVE_HEADERS"
986                         && input != "PRECOMPILED_HEADER") {
987                     vars << input;
988                 }
989         }
990         break;
991     }
992     case FileType::Resource:
993         vars << QLatin1String("RESOURCES");
994         break;
995     case FileType::Form:
996         vars << QLatin1String("FORMS");
997         break;
998     case FileType::StateChart:
999         vars << QLatin1String("STATECHARTS");
1000         break;
1001     case FileType::Project:
1002         vars << QLatin1String("SUBDIRS");
1003         break;
1004     case FileType::QML:
1005         vars << QLatin1String("OTHER_FILES");
1006         vars << QLatin1String("DISTFILES");
1007         break;
1008     default:
1009         vars << "DISTFILES" << "ICON" << "OTHER_FILES" << "QMAKE_INFO_PLIST" << "TRANSLATIONS";
1010         break;
1011     }
1012     return vars;
1013 }
1014 
1015 //!
1016 //! \brief QmakePriFile::varNames
1017 //! \param mimeType
1018 //! \return the qmake variable name for the mime type
1019 //! Note: Only used for adding.
1020 //!
varNameForAdding(const QString & mimeType)1021 QString QmakePriFile::varNameForAdding(const QString &mimeType)
1022 {
1023     if (mimeType == QLatin1String(ProjectExplorer::Constants::CPP_HEADER_MIMETYPE)
1024             || mimeType == QLatin1String(ProjectExplorer::Constants::C_HEADER_MIMETYPE)) {
1025         return QLatin1String("HEADERS");
1026     }
1027 
1028     if (mimeType == QLatin1String(ProjectExplorer::Constants::CPP_SOURCE_MIMETYPE)
1029                || mimeType == QLatin1String(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE)
1030                || mimeType == QLatin1String(ProjectExplorer::Constants::C_SOURCE_MIMETYPE)) {
1031         return QLatin1String("SOURCES");
1032     }
1033 
1034     if (mimeType == QLatin1String(ProjectExplorer::Constants::RESOURCE_MIMETYPE))
1035         return QLatin1String("RESOURCES");
1036 
1037     if (mimeType == QLatin1String(ProjectExplorer::Constants::FORM_MIMETYPE))
1038         return QLatin1String("FORMS");
1039 
1040     if (mimeType == QLatin1String(ProjectExplorer::Constants::QML_MIMETYPE)
1041             || mimeType == QLatin1String(ProjectExplorer::Constants::QMLUI_MIMETYPE)) {
1042         return QLatin1String("DISTFILES");
1043     }
1044 
1045     if (mimeType == QLatin1String(ProjectExplorer::Constants::SCXML_MIMETYPE))
1046         return QLatin1String("STATECHARTS");
1047 
1048     if (mimeType == QLatin1String(Constants::PROFILE_MIMETYPE))
1049         return QLatin1String("SUBDIRS");
1050 
1051     return QLatin1String("DISTFILES");
1052 }
1053 
1054 //!
1055 //! \brief QmakePriFile::varNamesForRemoving
1056 //! \return all qmake variables which are displayed in the project tree
1057 //! Note: Only used for removing.
1058 //!
varNamesForRemoving()1059 QStringList QmakePriFile::varNamesForRemoving()
1060 {
1061     QStringList vars;
1062     vars << QLatin1String("HEADERS");
1063     vars << QLatin1String("OBJECTIVE_HEADERS");
1064     vars << QLatin1String("PRECOMPILED_HEADER");
1065     vars << QLatin1String("SOURCES");
1066     vars << QLatin1String("OBJECTIVE_SOURCES");
1067     vars << QLatin1String("RESOURCES");
1068     vars << QLatin1String("FORMS");
1069     vars << QLatin1String("OTHER_FILES");
1070     vars << QLatin1String("SUBDIRS");
1071     vars << QLatin1String("DISTFILES");
1072     vars << QLatin1String("ICON");
1073     vars << QLatin1String("QMAKE_INFO_PLIST");
1074     vars << QLatin1String("STATECHARTS");
1075     return vars;
1076 }
1077 
filterFilesProVariables(FileType fileType,const QSet<FilePath> & files)1078 QSet<FilePath> QmakePriFile::filterFilesProVariables(FileType fileType, const QSet<FilePath> &files)
1079 {
1080     if (fileType != FileType::QML && fileType != FileType::Unknown)
1081         return files;
1082     QSet<FilePath> result;
1083     if (fileType == FileType::QML) {
1084         foreach (const FilePath &file, files)
1085             if (file.toString().endsWith(QLatin1String(".qml")))
1086                 result << file;
1087     } else {
1088         foreach (const FilePath &file, files)
1089             if (!file.toString().endsWith(QLatin1String(".qml")))
1090                 result << file;
1091     }
1092     return result;
1093 }
1094 
filterFilesRecursiveEnumerata(FileType fileType,const QSet<FilePath> & files)1095 QSet<FilePath> QmakePriFile::filterFilesRecursiveEnumerata(FileType fileType, const QSet<FilePath> &files)
1096 {
1097     QSet<FilePath> result;
1098     if (fileType != FileType::QML && fileType != FileType::Unknown)
1099         return result;
1100     if (fileType == FileType::QML) {
1101         foreach (const FilePath &file, files)
1102             if (file.toString().endsWith(QLatin1String(".qml")))
1103                 result << file;
1104     } else {
1105         foreach (const FilePath &file, files)
1106             if (!file.toString().endsWith(QLatin1String(".qml")))
1107                 result << file;
1108     }
1109     return result;
1110 }
1111 
1112 } // namespace QmakeProjectManager
1113 
proFileTemplateTypeToProjectType(ProFileEvaluator::TemplateType type)1114 static ProjectType proFileTemplateTypeToProjectType(ProFileEvaluator::TemplateType type)
1115 {
1116     switch (type) {
1117     case ProFileEvaluator::TT_Unknown:
1118     case ProFileEvaluator::TT_Application:
1119         return ProjectType::ApplicationTemplate;
1120     case ProFileEvaluator::TT_StaticLibrary:
1121         return ProjectType::StaticLibraryTemplate;
1122     case ProFileEvaluator::TT_SharedLibrary:
1123         return ProjectType::SharedLibraryTemplate;
1124     case ProFileEvaluator::TT_Script:
1125         return ProjectType::ScriptTemplate;
1126     case ProFileEvaluator::TT_Aux:
1127         return ProjectType::AuxTemplate;
1128     case ProFileEvaluator::TT_Subdirs:
1129         return ProjectType::SubDirsTemplate;
1130     default:
1131         return ProjectType::Invalid;
1132     }
1133 }
1134 
findProFile(const FilePath & fileName)1135 QmakeProFile *QmakeProFile::findProFile(const FilePath &fileName)
1136 {
1137     return static_cast<QmakeProFile *>(findPriFile(fileName));
1138 }
1139 
findProFile(const FilePath & fileName) const1140 const QmakeProFile *QmakeProFile::findProFile(const FilePath &fileName) const
1141 {
1142     return static_cast<const QmakeProFile *>(findPriFile(fileName));
1143 }
1144 
cxxDefines() const1145 QByteArray QmakeProFile::cxxDefines() const
1146 {
1147     QByteArray result;
1148     foreach (const QString &def, variableValue(Variable::Defines)) {
1149         // 'def' is shell input, so interpret it.
1150         ProcessArgs::SplitError error = ProcessArgs::SplitOk;
1151         const QStringList args = ProcessArgs::splitArgs(def, HostOsInfo::hostOs(), false, &error);
1152         if (error != ProcessArgs::SplitOk || args.size() == 0)
1153             continue;
1154 
1155         result += "#define ";
1156         const QString defInterpreted = args.first();
1157         const int index = defInterpreted.indexOf(QLatin1Char('='));
1158         if (index == -1) {
1159             result += defInterpreted.toLatin1();
1160             result += " 1\n";
1161         } else {
1162             const QString name = defInterpreted.left(index);
1163             const QString value = defInterpreted.mid(index + 1);
1164             result += name.toLatin1();
1165             result += ' ';
1166             result += value.toLocal8Bit();
1167             result += '\n';
1168         }
1169     }
1170     return result;
1171 }
1172 
1173 /*!
1174   \class QmakeProFile
1175   Implements abstract ProjectNode class
1176   */
QmakeProFile(QmakeBuildSystem * buildSystem,const FilePath & filePath)1177 QmakeProFile::QmakeProFile(QmakeBuildSystem *buildSystem, const FilePath &filePath) :
1178     QmakePriFile(buildSystem, this, filePath)
1179 {
1180     setupFutureWatcher();
1181 }
1182 
QmakeProFile(const FilePath & filePath)1183 QmakeProFile::QmakeProFile(const FilePath &filePath) : QmakePriFile(filePath) { }
1184 
~QmakeProFile()1185 QmakeProFile::~QmakeProFile()
1186 {
1187     qDeleteAll(m_extraCompilers);
1188     if (m_parseFutureWatcher) {
1189         m_parseFutureWatcher->cancel();
1190         m_parseFutureWatcher->waitForFinished();
1191         if (m_readerExact)
1192             applyAsyncEvaluate(false);
1193         delete m_parseFutureWatcher;
1194     }
1195     cleanupProFileReaders();
1196 }
1197 
setupFutureWatcher()1198 void QmakeProFile::setupFutureWatcher()
1199 {
1200     m_parseFutureWatcher = new QFutureWatcher<Internal::QmakeEvalResult *>;
1201     QObject::connect(m_parseFutureWatcher, &QFutureWatcherBase::finished, [this]() {
1202         applyAsyncEvaluate(true);
1203     });
1204 }
1205 
isParent(QmakeProFile * node)1206 bool QmakeProFile::isParent(QmakeProFile *node)
1207 {
1208     while ((node = dynamic_cast<QmakeProFile *>(node->parent()))) {
1209         if (node == this)
1210             return true;
1211     }
1212     return false;
1213 }
1214 
displayName() const1215 QString QmakeProFile::displayName() const
1216 {
1217     if (!m_displayName.isEmpty())
1218         return m_displayName;
1219     return QmakePriFile::displayName();
1220 }
1221 
allProFiles()1222 QList<QmakeProFile *> QmakeProFile::allProFiles()
1223 {
1224     QList<QmakeProFile *> result = { this };
1225     for (QmakePriFile *c : qAsConst(m_children)) {
1226         auto proC = dynamic_cast<QmakeProFile *>(c);
1227         if (proC)
1228             result.append(proC->allProFiles());
1229     }
1230     return result;
1231 }
1232 
projectType() const1233 ProjectType QmakeProFile::projectType() const
1234 {
1235     return m_projectType;
1236 }
1237 
variableValue(const Variable var) const1238 QStringList QmakeProFile::variableValue(const Variable var) const
1239 {
1240     return m_varValues.value(var);
1241 }
1242 
singleVariableValue(const Variable var) const1243 QString QmakeProFile::singleVariableValue(const Variable var) const
1244 {
1245     const QStringList &values = variableValue(var);
1246     return values.isEmpty() ? QString() : values.first();
1247 }
1248 
setParseInProgressRecursive(bool b)1249 void QmakeProFile::setParseInProgressRecursive(bool b)
1250 {
1251     setParseInProgress(b);
1252     foreach (QmakePriFile *c, children()) {
1253         if (auto node = dynamic_cast<QmakeProFile *>(c))
1254             node->setParseInProgressRecursive(b);
1255     }
1256 }
1257 
setParseInProgress(bool b)1258 void QmakeProFile::setParseInProgress(bool b)
1259 {
1260     m_parseInProgress = b;
1261 }
1262 
1263 // Do note the absence of signal emission, always set validParse
1264 // before setParseInProgress, as that will emit the signals
setValidParseRecursive(bool b)1265 void QmakeProFile::setValidParseRecursive(bool b)
1266 {
1267     m_validParse = b;
1268     foreach (QmakePriFile *c, children()) {
1269         if (auto *node = dynamic_cast<QmakeProFile *>(c))
1270             node->setValidParseRecursive(b);
1271     }
1272 }
1273 
validParse() const1274 bool QmakeProFile::validParse() const
1275 {
1276     return m_validParse;
1277 }
1278 
parseInProgress() const1279 bool QmakeProFile::parseInProgress() const
1280 {
1281     return m_parseInProgress;
1282 }
1283 
scheduleUpdate(QmakeProFile::AsyncUpdateDelay delay)1284 void QmakeProFile::scheduleUpdate(QmakeProFile::AsyncUpdateDelay delay)
1285 {
1286     setParseInProgressRecursive(true);
1287     m_buildSystem->scheduleAsyncUpdateFile(this, delay);
1288 }
1289 
asyncUpdate()1290 void QmakeProFile::asyncUpdate()
1291 {
1292     m_buildSystem->incrementPendingEvaluateFutures();
1293     setupReader();
1294     if (!includedInExactParse())
1295         m_readerExact->setExact(false);
1296     m_parseFutureWatcher->waitForFinished();
1297     QmakeEvalInput input = evalInput();
1298     QFuture<QmakeEvalResult *> future = Utils::runAsync(ProjectExplorerPlugin::sharedThreadPool(),
1299                                                         QThread::LowestPriority,
1300                                                         &QmakeProFile::asyncEvaluate,
1301                                                         this, input);
1302     m_parseFutureWatcher->setFuture(future);
1303 }
1304 
isFileFromWildcard(const QString & filePath) const1305 bool QmakeProFile::isFileFromWildcard(const QString &filePath) const
1306 {
1307     const QFileInfo fileInfo(filePath);
1308     const auto directoryIterator = m_wildcardDirectoryContents.constFind(fileInfo.path());
1309     return (directoryIterator != m_wildcardDirectoryContents.end()
1310             && directoryIterator.value().contains(fileInfo.fileName()));
1311 }
1312 
evalInput() const1313 QmakeEvalInput QmakeProFile::evalInput() const
1314 {
1315     QmakeEvalInput input;
1316     input.projectDir = directoryPath().toString();
1317     input.projectFilePath = filePath();
1318     input.buildDirectory = m_buildSystem->buildDir(m_filePath);
1319     input.sysroot = FilePath::fromString(m_buildSystem->qmakeSysroot());
1320     input.readerExact = m_readerExact;
1321     input.readerCumulative = m_readerCumulative;
1322     input.qmakeGlobals = m_buildSystem->qmakeGlobals();
1323     input.qmakeVfs = m_buildSystem->qmakeVfs();
1324     input.includedInExcactParse = includedInExactParse();
1325     for (const QmakePriFile *pri = this; pri; pri = pri->parent())
1326         input.parentFilePaths.insert(pri->filePath());
1327     return input;
1328 }
1329 
setupReader()1330 void QmakeProFile::setupReader()
1331 {
1332     Q_ASSERT(!m_readerExact);
1333     Q_ASSERT(!m_readerCumulative);
1334 
1335     m_readerExact = m_buildSystem->createProFileReader(this);
1336 
1337     m_readerCumulative = m_buildSystem->createProFileReader(this);
1338     m_readerCumulative->setCumulative(true);
1339 }
1340 
evaluateOne(const QmakeEvalInput & input,ProFile * pro,QtSupport::ProFileReader * reader,bool cumulative,QtSupport::ProFileReader ** buildPassReader)1341 static bool evaluateOne(const QmakeEvalInput &input, ProFile *pro,
1342                         QtSupport::ProFileReader *reader, bool cumulative,
1343                         QtSupport::ProFileReader **buildPassReader)
1344 {
1345     if (!reader->accept(pro, QMakeEvaluator::LoadAll))
1346         return false;
1347 
1348     QStringList builds = reader->values(QLatin1String("BUILDS"));
1349     if (builds.isEmpty()) {
1350         *buildPassReader = reader;
1351     } else {
1352         QString build = builds.first();
1353         QHash<QString, QStringList> basevars;
1354         QStringList basecfgs = reader->values(build + QLatin1String(".CONFIG"));
1355         basecfgs += build;
1356         basecfgs += QLatin1String("build_pass");
1357         basecfgs += "qtc_run";
1358         basevars[QLatin1String("BUILD_PASS")] = QStringList(build);
1359         QStringList buildname = reader->values(build + QLatin1String(".name"));
1360         basevars[QLatin1String("BUILD_NAME")] = (buildname.isEmpty() ? QStringList(build) : buildname);
1361 
1362         // We don't increase/decrease m_qmakeGlobalsRefCnt here, because the outer profilereaders keep m_qmakeGlobals alive anyway
1363         auto bpReader = new QtSupport::ProFileReader(input.qmakeGlobals, input.qmakeVfs); // needs to access m_qmakeGlobals, m_qmakeVfs
1364 
1365         // FIXME: Currently intentional.
1366         // Core parts of the ProParser hard-assert on non-local items.
1367         bpReader->setOutputDir(input.buildDirectory.path());
1368         bpReader->setCumulative(cumulative);
1369         bpReader->setExtraVars(basevars);
1370         bpReader->setExtraConfigs(basecfgs);
1371 
1372         if (bpReader->accept(pro, QMakeEvaluator::LoadAll))
1373             *buildPassReader = bpReader;
1374         else
1375             delete bpReader;
1376     }
1377 
1378     return true;
1379 }
1380 
evaluate(const QmakeEvalInput & input)1381 QmakeEvalResult *QmakeProFile::evaluate(const QmakeEvalInput &input)
1382 {
1383     auto *result = new QmakeEvalResult;
1384     QtSupport::ProFileReader *exactBuildPassReader = nullptr;
1385     QtSupport::ProFileReader *cumulativeBuildPassReader = nullptr;
1386     ProFile *pro;
1387     if ((pro = input.readerExact->parsedProFile(input.projectFilePath.toString()))) {
1388         bool exactOk = evaluateOne(input, pro, input.readerExact, false, &exactBuildPassReader);
1389         bool cumulOk = evaluateOne(input, pro, input.readerCumulative, true, &cumulativeBuildPassReader);
1390         pro->deref();
1391         result->state = exactOk ? QmakeEvalResult::EvalOk
1392                                 : cumulOk ? QmakeEvalResult::EvalPartial : QmakeEvalResult::EvalFail;
1393     } else {
1394         result->state = QmakeEvalResult::EvalFail;
1395     }
1396 
1397     if (result->state == QmakeEvalResult::EvalFail)
1398         return result;
1399 
1400     result->includedFiles.proFile = pro;
1401     result->includedFiles.name = input.projectFilePath;
1402 
1403     QHash<int, QmakePriFileEvalResult *> proToResult;
1404 
1405     result->projectType
1406             = proFileTemplateTypeToProjectType(
1407                 (result->state == QmakeEvalResult::EvalOk ? input.readerExact
1408                                                           : input.readerCumulative)->templateType());
1409     if (result->state == QmakeEvalResult::EvalOk) {
1410         if (result->projectType == ProjectType::SubDirsTemplate) {
1411             QStringList errors;
1412             FilePaths subDirs = subDirsPaths(input.readerExact, input.projectDir, &result->subProjectsNotToDeploy, &errors);
1413             result->errors.append(errors);
1414 
1415             foreach (const Utils::FilePath &subDirName, subDirs) {
1416                 auto subDir = new QmakeIncludedPriFile;
1417                 subDir->proFile = nullptr;
1418                 subDir->name = subDirName;
1419                 result->includedFiles.children.insert(subDirName, subDir);
1420             }
1421 
1422             result->exactSubdirs = Utils::toSet(subDirs);
1423         }
1424 
1425         // Convert ProFileReader::includeFiles to IncludedPriFile structure
1426         QHash<ProFile *, QVector<ProFile *>> includeFiles = input.readerExact->includeFiles();
1427         QList<QmakeIncludedPriFile *> toBuild = {&result->includedFiles};
1428         while (!toBuild.isEmpty()) {
1429             QmakeIncludedPriFile *current = toBuild.takeFirst();
1430             if (!current->proFile)
1431                 continue;  // Don't attempt to map subdirs here
1432             QVector<ProFile *> children = includeFiles.value(current->proFile);
1433             foreach (ProFile *child, children) {
1434                 const Utils::FilePath childName = Utils::FilePath::fromString(child->fileName());
1435                 auto it = current->children.find(childName);
1436                 if (it == current->children.end()) {
1437                     auto childTree = new QmakeIncludedPriFile;
1438                     childTree->proFile = child;
1439                     childTree->name = childName;
1440                     current->children.insert(childName, childTree);
1441                     proToResult[child->id()] = &childTree->result;
1442                 }
1443             }
1444             toBuild.append(current->children.values());
1445         }
1446     }
1447 
1448     if (result->projectType == ProjectType::SubDirsTemplate) {
1449         FilePaths subDirs = subDirsPaths(input.readerCumulative, input.projectDir, nullptr, nullptr);
1450         foreach (const Utils::FilePath &subDirName, subDirs) {
1451             auto it = result->includedFiles.children.find(subDirName);
1452             if (it == result->includedFiles.children.end()) {
1453                 auto subDir = new QmakeIncludedPriFile;
1454                 subDir->proFile = nullptr;
1455                 subDir->name = subDirName;
1456                 result->includedFiles.children.insert(subDirName, subDir);
1457             }
1458         }
1459     }
1460 
1461     // Add ProFileReader::includeFiles information from cumulative parse to IncludedPriFile structure
1462     QHash<ProFile *, QVector<ProFile *>> includeFiles = input.readerCumulative->includeFiles();
1463     QList<QmakeIncludedPriFile *> toBuild = {&result->includedFiles};
1464     while (!toBuild.isEmpty()) {
1465         QmakeIncludedPriFile *current = toBuild.takeFirst();
1466         if (!current->proFile)
1467             continue;  // Don't attempt to map subdirs here
1468         QVector<ProFile *> children = includeFiles.value(current->proFile);
1469         foreach (ProFile *child, children) {
1470             const Utils::FilePath childName = Utils::FilePath::fromString(child->fileName());
1471             auto it = current->children.find(childName);
1472             if (it == current->children.end()) {
1473                 auto childTree = new QmakeIncludedPriFile;
1474                 childTree->proFile = child;
1475                 childTree->name = childName;
1476                 current->children.insert(childName, childTree);
1477                 proToResult[child->id()] = &childTree->result;
1478             }
1479         }
1480         toBuild.append(current->children.values());
1481     }
1482 
1483     auto exactReader = exactBuildPassReader ? exactBuildPassReader : input.readerExact;
1484     auto cumulativeReader = cumulativeBuildPassReader ? cumulativeBuildPassReader : input.readerCumulative;
1485 
1486     QHash<QString, QVector<ProFileEvaluator::SourceFile>> exactSourceFiles;
1487     QHash<QString, QVector<ProFileEvaluator::SourceFile>> cumulativeSourceFiles;
1488 
1489     const QStringList baseVPathsExact
1490             = baseVPaths(exactReader, input.projectDir, input.buildDirectory.toString());
1491     const QStringList baseVPathsCumulative
1492             = baseVPaths(cumulativeReader, input.projectDir, input.buildDirectory.toString());
1493 
1494     for (int i = 0; i < static_cast<int>(FileType::FileTypeSize); ++i) {
1495         const auto type = static_cast<FileType>(i);
1496         const QStringList qmakeVariables = varNames(type, exactReader);
1497         foreach (const QString &qmakeVariable, qmakeVariables) {
1498             QHash<ProString, bool> handled;
1499             if (result->state == QmakeEvalResult::EvalOk) {
1500                 const QStringList vPathsExact = fullVPaths(
1501                             baseVPathsExact, exactReader, qmakeVariable, input.projectDir);
1502                 auto sourceFiles = exactReader->absoluteFileValues(
1503                             qmakeVariable, input.projectDir, vPathsExact, &handled, result->directoriesWithWildcards);
1504                 exactSourceFiles[qmakeVariable] = sourceFiles;
1505                 extractSources(proToResult, &result->includedFiles.result, sourceFiles, type, false);
1506             }
1507             const QStringList vPathsCumulative = fullVPaths(
1508                         baseVPathsCumulative, cumulativeReader, qmakeVariable, input.projectDir);
1509             auto sourceFiles = cumulativeReader->absoluteFileValues(
1510                         qmakeVariable, input.projectDir, vPathsCumulative, &handled, result->directoriesWithWildcards);
1511             cumulativeSourceFiles[qmakeVariable] = sourceFiles;
1512             extractSources(proToResult, &result->includedFiles.result, sourceFiles, type, true);
1513         }
1514     }
1515 
1516     // This is used for two things:
1517     // - Actual deployment, in which case we need exact values.
1518     // - The project tree, in which case we also want exact values to avoid recursively
1519     //   watching bogus paths. However, we accept the values even if the evaluation
1520     //   failed, to at least have a best-effort result.
1521     result->installsList = installsList(exactBuildPassReader, input.projectFilePath.toString(),
1522                                         input.projectDir, input.buildDirectory.toString());
1523     extractInstalls(proToResult, &result->includedFiles.result, result->installsList);
1524 
1525     if (result->state == QmakeEvalResult::EvalOk) {
1526         result->targetInformation = targetInformation(input.readerExact, exactBuildPassReader,
1527                                                       input.buildDirectory, input.projectFilePath);
1528 
1529         // update other variables
1530         result->newVarValues[Variable::Defines] = exactReader->values(QLatin1String("DEFINES"));
1531         result->newVarValues[Variable::IncludePath] = includePaths(exactReader, input.sysroot,
1532                                                             input.buildDirectory, input.projectDir);
1533         result->newVarValues[Variable::CppFlags] = exactReader->values(QLatin1String("QMAKE_CXXFLAGS"));
1534         result->newVarValues[Variable::CFlags] = exactReader->values(QLatin1String("QMAKE_CFLAGS"));
1535         result->newVarValues[Variable::ExactSource] =
1536                 fileListForVar(exactSourceFiles, QLatin1String("SOURCES")) +
1537                 fileListForVar(exactSourceFiles, QLatin1String("HEADERS")) +
1538                 fileListForVar(exactSourceFiles, QLatin1String("OBJECTIVE_HEADERS"));
1539         result->newVarValues[Variable::CumulativeSource] =
1540                 fileListForVar(cumulativeSourceFiles, QLatin1String("SOURCES")) +
1541                 fileListForVar(cumulativeSourceFiles, QLatin1String("HEADERS")) +
1542                 fileListForVar(cumulativeSourceFiles, QLatin1String("OBJECTIVE_HEADERS"));
1543         result->newVarValues[Variable::UiDir] = QStringList() << uiDirPath(exactReader, input.buildDirectory);
1544         result->newVarValues[Variable::HeaderExtension] = QStringList() << exactReader->value(QLatin1String("QMAKE_EXT_H"));
1545         result->newVarValues[Variable::CppExtension] = QStringList() << exactReader->value(QLatin1String("QMAKE_EXT_CPP"));
1546         result->newVarValues[Variable::MocDir] = QStringList() << mocDirPath(exactReader, input.buildDirectory);
1547         result->newVarValues[Variable::ExactResource] = fileListForVar(exactSourceFiles, QLatin1String("RESOURCES"));
1548         result->newVarValues[Variable::CumulativeResource] = fileListForVar(cumulativeSourceFiles, QLatin1String("RESOURCES"));
1549         result->newVarValues[Variable::PkgConfig] = exactReader->values(QLatin1String("PKGCONFIG"));
1550         result->newVarValues[Variable::PrecompiledHeader] = ProFileEvaluator::sourcesToFiles(exactReader->fixifiedValues(
1551                     QLatin1String("PRECOMPILED_HEADER"), input.projectDir, input.buildDirectory.toString(), false));
1552         result->newVarValues[Variable::LibDirectories] = libDirectories(exactReader);
1553         result->newVarValues[Variable::Config] = exactReader->values(QLatin1String("CONFIG"));
1554         result->newVarValues[Variable::QmlImportPath] = exactReader->absolutePathValues(
1555                     QLatin1String("QML_IMPORT_PATH"), input.projectDir);
1556         result->newVarValues[Variable::QmlDesignerImportPath] = exactReader->absolutePathValues(
1557                     QLatin1String("QML_DESIGNER_IMPORT_PATH"), input.projectDir);
1558         result->newVarValues[Variable::Makefile] = exactReader->values(QLatin1String("MAKEFILE"));
1559         result->newVarValues[Variable::Qt] = exactReader->values(QLatin1String("QT"));
1560         result->newVarValues[Variable::ObjectExt] = exactReader->values(QLatin1String("QMAKE_EXT_OBJ"));
1561         result->newVarValues[Variable::ObjectsDir] = exactReader->values(QLatin1String("OBJECTS_DIR"));
1562         result->newVarValues[Variable::Version] = exactReader->values(QLatin1String("VERSION"));
1563         result->newVarValues[Variable::TargetExt] = exactReader->values(QLatin1String("TARGET_EXT"));
1564         result->newVarValues[Variable::TargetVersionExt]
1565                 = exactReader->values(QLatin1String("TARGET_VERSION_EXT"));
1566         result->newVarValues[Variable::StaticLibExtension] = exactReader->values(QLatin1String("QMAKE_EXTENSION_STATICLIB"));
1567         result->newVarValues[Variable::ShLibExtension] = exactReader->values(QLatin1String("QMAKE_EXTENSION_SHLIB"));
1568         result->newVarValues[Variable::AndroidArch] = exactReader->values(QLatin1String("ANDROID_TARGET_ARCH"));
1569         result->newVarValues[Variable::AndroidDeploySettingsFile] = exactReader->values(QLatin1String(Android::Constants::ANDROID_DEPLOYMENT_SETTINGS_FILE));
1570         result->newVarValues[Variable::AndroidPackageSourceDir] = exactReader->values(QLatin1String(Android::Constants::ANDROID_PACKAGE_SOURCE_DIR));
1571         result->newVarValues[Variable::AndroidAbis] = exactReader->values(QLatin1String("ANDROID_ABIS"));
1572         result->newVarValues[Variable::AndroidApplicationArguments] = exactReader->values(QLatin1String(Android::Constants::ANDROID_APPLICATION_ARGUMENTS));
1573         result->newVarValues[Variable::AndroidExtraLibs] = exactReader->values(QLatin1String(Android::Constants::ANDROID_EXTRA_LIBS));
1574         result->newVarValues[Variable::AppmanPackageDir] = exactReader->values(QLatin1String("AM_PACKAGE_DIR"));
1575         result->newVarValues[Variable::AppmanManifest] = exactReader->values(QLatin1String("AM_MANIFEST"));
1576         result->newVarValues[Variable::IsoIcons] = exactReader->values(QLatin1String("ISO_ICONS"));
1577         result->newVarValues[Variable::QmakeProjectName] = exactReader->values(QLatin1String("QMAKE_PROJECT_NAME"));
1578         result->newVarValues[Variable::QmakeCc] = exactReader->values("QMAKE_CC");
1579         result->newVarValues[Variable::QmakeCxx] = exactReader->values("QMAKE_CXX");
1580     }
1581 
1582     if (result->state == QmakeEvalResult::EvalOk || result->state == QmakeEvalResult::EvalPartial) {
1583 
1584         QList<QmakeIncludedPriFile *> toExtract = {&result->includedFiles};
1585         while (!toExtract.isEmpty()) {
1586             QmakeIncludedPriFile *current = toExtract.takeFirst();
1587             processValues(current->result);
1588             toExtract.append(current->children.values());
1589         }
1590     }
1591 
1592     if (exactBuildPassReader && exactBuildPassReader != input.readerExact)
1593         delete exactBuildPassReader;
1594     if (cumulativeBuildPassReader && cumulativeBuildPassReader != input.readerCumulative)
1595         delete cumulativeBuildPassReader;
1596 
1597     QList<QPair<QmakePriFile *, QmakeIncludedPriFile *>> toCompare;
1598     toCompare.append(qMakePair(nullptr, &result->includedFiles));
1599     while (!toCompare.isEmpty()) {
1600         QmakePriFile *pn = toCompare.first().first;
1601         QmakeIncludedPriFile *tree = toCompare.first().second;
1602         toCompare.pop_front();
1603 
1604         // Loop prevention: Make sure that exact same node is not in our parent chain
1605         for (QmakeIncludedPriFile *priFile : qAsConst(tree->children)) {
1606             bool loop = input.parentFilePaths.contains(priFile->name);
1607             for (const QmakePriFile *n = pn; n && !loop; n = n->parent()) {
1608                 if (n->filePath() == priFile->name)
1609                     loop = true;
1610             }
1611             if (loop)
1612                 continue; // Do nothing
1613 
1614             if (priFile->proFile) {
1615                 auto *qmakePriFileNode = new QmakePriFile(priFile->name);
1616                 if (pn)
1617                     pn->addChild(qmakePriFileNode);
1618                 else
1619                     result->directChildren << qmakePriFileNode;
1620                 qmakePriFileNode->setIncludedInExactParse(input.includedInExcactParse
1621                         && result->state == QmakeEvalResult::EvalOk);
1622                 result->priFiles.append(qMakePair(qmakePriFileNode, priFile->result));
1623                 toCompare.append(qMakePair(qmakePriFileNode, priFile));
1624             } else {
1625                 auto *qmakeProFileNode = new QmakeProFile(priFile->name);
1626                 if (pn)
1627                     pn->addChild(qmakeProFileNode);
1628                 else
1629                     result->directChildren << qmakeProFileNode;
1630                 qmakeProFileNode->setIncludedInExactParse(input.includedInExcactParse
1631                             && result->exactSubdirs.contains(qmakeProFileNode->filePath()));
1632                 qmakeProFileNode->setParseInProgress(true);
1633                 result->proFiles << qmakeProFileNode;
1634             }
1635         }
1636     }
1637 
1638     return result;
1639 }
1640 
asyncEvaluate(QFutureInterface<QmakeEvalResult * > & fi,QmakeEvalInput input)1641 void QmakeProFile::asyncEvaluate(QFutureInterface<QmakeEvalResult *> &fi, QmakeEvalInput input)
1642 {
1643     QmakeEvalResult *evalResult = evaluate(input);
1644     fi.reportResult(evalResult);
1645 }
1646 
applyAsyncEvaluate(bool apply)1647 void QmakeProFile::applyAsyncEvaluate(bool apply)
1648 {
1649     if (apply)
1650         applyEvaluate(m_parseFutureWatcher->result());
1651     m_buildSystem->decrementPendingEvaluateFutures();
1652 }
1653 
sortByParserNodes(Node * a,Node * b)1654 bool sortByParserNodes(Node *a, Node *b)
1655 {
1656     return a->filePath() < b->filePath();
1657 }
1658 
applyEvaluate(QmakeEvalResult * evalResult)1659 void QmakeProFile::applyEvaluate(QmakeEvalResult *evalResult)
1660 {
1661     QScopedPointer<QmakeEvalResult> result(evalResult);
1662     if (!m_readerExact)
1663         return;
1664 
1665     if (m_buildSystem->asyncUpdateState() == QmakeBuildSystem::ShuttingDown) {
1666         cleanupProFileReaders();
1667         return;
1668     }
1669 
1670     foreach (const QString &error, evalResult->errors)
1671         QmakeBuildSystem::proFileParseError(error, filePath());
1672 
1673     // we are changing what is executed in that case
1674     if (result->state == QmakeEvalResult::EvalFail || m_buildSystem->wasEvaluateCanceled()) {
1675         m_validParse = false;
1676         cleanupProFileReaders();
1677         setValidParseRecursive(false);
1678         setParseInProgressRecursive(false);
1679 
1680         if (result->state == QmakeEvalResult::EvalFail) {
1681             QmakeBuildSystem::proFileParseError(
1682                 QCoreApplication::translate("QmakeProFile",
1683                                             "Error while parsing file %1. Giving up.")
1684                     .arg(filePath().toUserOutput()),
1685                 filePath());
1686             if (m_projectType == ProjectType::Invalid)
1687                 return;
1688 
1689             makeEmpty();
1690 
1691             m_projectType = ProjectType::Invalid;
1692         }
1693         return;
1694     }
1695 
1696     qCDebug(qmakeParse()) << "QmakeProFile - updating files for file " << filePath();
1697 
1698     if (result->projectType != m_projectType) {
1699         // probably all subfiles/projects have changed anyway
1700         // delete files && folders && projects
1701         foreach (QmakePriFile *c, children()) {
1702             if (auto qmakeProFile = dynamic_cast<QmakeProFile *>(c)) {
1703                 qmakeProFile->setValidParseRecursive(false);
1704                 qmakeProFile->setParseInProgressRecursive(false);
1705             }
1706         }
1707 
1708         makeEmpty();
1709         m_projectType = result->projectType;
1710     }
1711 
1712     //
1713     // Add/Remove pri files, sub projects
1714     //
1715     FilePath buildDirectory = m_buildSystem->buildDir(m_filePath);
1716     makeEmpty();
1717     for (QmakePriFile * const toAdd : qAsConst(result->directChildren))
1718         addChild(toAdd);
1719     result->directChildren.clear();
1720 
1721     for (const auto &priFiles : qAsConst(result->priFiles)) {
1722         priFiles.first->finishInitialization(m_buildSystem, this);
1723         priFiles.first->update(priFiles.second);
1724     }
1725 
1726     for (QmakeProFile * const proFile : qAsConst(result->proFiles)) {
1727         proFile->finishInitialization(m_buildSystem, proFile);
1728         proFile->setupFutureWatcher();
1729         proFile->asyncUpdate();
1730     }
1731     QmakePriFile::update(result->includedFiles.result);
1732 
1733     m_validParse = (result->state == QmakeEvalResult::EvalOk);
1734     if (m_validParse) {
1735         // update TargetInformation
1736         m_qmakeTargetInformation = result->targetInformation;
1737 
1738         m_subProjectsNotToDeploy
1739                 = Utils::transform(result->subProjectsNotToDeploy,
1740                                    [](const QString &s) { return FilePath::fromString(s); });
1741         m_installsList = result->installsList;
1742 
1743         if (m_varValues != result->newVarValues)
1744             m_varValues = result->newVarValues;
1745 
1746         m_displayName = singleVariableValue(Variable::QmakeProjectName);
1747         m_featureRoots = m_readerExact->featureRoots();
1748     } // result == EvalOk
1749 
1750     if (!result->directoriesWithWildcards.isEmpty()) {
1751         if (!m_wildcardWatcher) {
1752             m_wildcardWatcher = std::make_unique<Utils::FileSystemWatcher>();
1753             QObject::connect(
1754                 m_wildcardWatcher.get(), &Utils::FileSystemWatcher::directoryChanged,
1755                 [this](QString path) {
1756                     QStringList directoryContents = QDir(path).entryList();
1757                     if (m_wildcardDirectoryContents.value(path) != directoryContents) {
1758                         m_wildcardDirectoryContents.insert(path, directoryContents);
1759                         scheduleUpdate();
1760                     }
1761                 });
1762         }
1763         const QStringList directoriesToAdd = Utils::filtered<QStringList>(
1764             Utils::toList(result->directoriesWithWildcards),
1765             [this](const QString &path) {
1766                 return !m_wildcardWatcher->watchesDirectory(path);
1767             });
1768         for (const QString &path : directoriesToAdd)
1769             m_wildcardDirectoryContents.insert(path, QDir(path).entryList());
1770         m_wildcardWatcher->addDirectories(directoriesToAdd,
1771                                           Utils::FileSystemWatcher::WatchModifiedDate);
1772     }
1773     if (m_wildcardWatcher) {
1774         if (result->directoriesWithWildcards.isEmpty()) {
1775             m_wildcardWatcher.reset();
1776             m_wildcardDirectoryContents.clear();
1777         } else {
1778             const QStringList directoriesToRemove =
1779                 Utils::filtered<QStringList>(
1780                     m_wildcardWatcher->directories(),
1781                     [&result](const QString &path) {
1782                         return !result->directoriesWithWildcards.contains(path);
1783                     });
1784             m_wildcardWatcher->removeDirectories(directoriesToRemove);
1785             for (const QString &path : directoriesToRemove)
1786                 m_wildcardDirectoryContents.remove(path);
1787         }
1788     }
1789 
1790     setParseInProgress(false);
1791 
1792     updateGeneratedFiles(buildDirectory);
1793 
1794     cleanupProFileReaders();
1795 }
1796 
cleanupProFileReaders()1797 void QmakeProFile::cleanupProFileReaders()
1798 {
1799     if (m_readerExact)
1800         m_buildSystem->destroyProFileReader(m_readerExact);
1801     if (m_readerCumulative)
1802         m_buildSystem->destroyProFileReader(m_readerCumulative);
1803 
1804     m_readerExact = nullptr;
1805     m_readerCumulative = nullptr;
1806 }
1807 
uiDirPath(QtSupport::ProFileReader * reader,const FilePath & buildDir)1808 QString QmakeProFile::uiDirPath(QtSupport::ProFileReader *reader, const FilePath &buildDir)
1809 {
1810     QString path = reader->value(QLatin1String("UI_DIR"));
1811     if (QFileInfo(path).isRelative())
1812         path = QDir::cleanPath(buildDir.toString() + QLatin1Char('/') + path);
1813     return path;
1814 }
1815 
mocDirPath(QtSupport::ProFileReader * reader,const FilePath & buildDir)1816 QString QmakeProFile::mocDirPath(QtSupport::ProFileReader *reader, const FilePath &buildDir)
1817 {
1818     QString path = reader->value(QLatin1String("MOC_DIR"));
1819     if (QFileInfo(path).isRelative())
1820         path = QDir::cleanPath(buildDir.toString() + QLatin1Char('/') + path);
1821     return path;
1822 }
1823 
sysrootify(const QString & path,const QString & sysroot,const QString & baseDir,const QString & outputDir)1824 QString QmakeProFile::sysrootify(const QString &path, const QString &sysroot,
1825                                      const QString &baseDir, const QString &outputDir)
1826 {
1827 #ifdef Q_OS_WIN
1828     Qt::CaseSensitivity cs = Qt::CaseInsensitive;
1829 #else
1830     Qt::CaseSensitivity cs = Qt::CaseSensitive;
1831 #endif
1832     if (sysroot.isEmpty() || path.startsWith(sysroot, cs)
1833         || path.startsWith(baseDir, cs) || path.startsWith(outputDir, cs)) {
1834         return path;
1835     }
1836     QString sysrooted = QDir::cleanPath(sysroot + path);
1837     return !IoUtils::exists(sysrooted) ? path : sysrooted;
1838 }
1839 
includePaths(QtSupport::ProFileReader * reader,const FilePath & sysroot,const FilePath & buildDir,const QString & projectDir)1840 QStringList QmakeProFile::includePaths(QtSupport::ProFileReader *reader, const FilePath &sysroot,
1841                                            const FilePath &buildDir, const QString &projectDir)
1842 {
1843     QStringList paths;
1844     bool nextIsAnIncludePath = false;
1845     foreach (const QString &cxxflags, reader->values(QLatin1String("QMAKE_CXXFLAGS"))) {
1846         if (nextIsAnIncludePath) {
1847             nextIsAnIncludePath = false;
1848             paths.append(cxxflags);
1849         } else if (cxxflags.startsWith(QLatin1String("-I"))) {
1850             paths.append(cxxflags.mid(2));
1851         } else if (cxxflags.startsWith(QLatin1String("-isystem"))) {
1852             nextIsAnIncludePath = true;
1853         }
1854     }
1855 
1856     bool tryUnfixified = false;
1857 
1858     // These paths should not be checked for existence, to ensure consistent include path lists
1859     // before and after building.
1860     const QString mocDir = mocDirPath(reader, buildDir);
1861     const QString uiDir = uiDirPath(reader, buildDir);
1862 
1863     foreach (const ProFileEvaluator::SourceFile &el,
1864              reader->fixifiedValues(QLatin1String("INCLUDEPATH"), projectDir, buildDir.toString(),
1865                                     false)) {
1866         const QString sysrootifiedPath = sysrootify(el.fileName, sysroot.toString(), projectDir,
1867                                                     buildDir.toString());
1868         if (IoUtils::isAbsolutePath(sysrootifiedPath)
1869                 && (IoUtils::exists(sysrootifiedPath) || sysrootifiedPath == mocDir
1870                     || sysrootifiedPath == uiDir)) {
1871             paths << sysrootifiedPath;
1872         } else {
1873             tryUnfixified = true;
1874         }
1875     }
1876 
1877     // If sysrootifying a fixified path does not yield a valid path, try again with the
1878     // unfixified value. This can be necessary for cross-building; see QTCREATORBUG-21164.
1879     if (tryUnfixified) {
1880         const QStringList rawValues = reader->values("INCLUDEPATH");
1881         for (const QString &p : rawValues) {
1882             const QString sysrootifiedPath = sysrootify(QDir::cleanPath(p), sysroot.toString(),
1883                                                         projectDir, buildDir.toString());
1884             if (IoUtils::isAbsolutePath(sysrootifiedPath) && IoUtils::exists(sysrootifiedPath))
1885                 paths << sysrootifiedPath;
1886         }
1887     }
1888 
1889     paths.removeDuplicates();
1890     return paths;
1891 }
1892 
libDirectories(QtSupport::ProFileReader * reader)1893 QStringList QmakeProFile::libDirectories(QtSupport::ProFileReader *reader)
1894 {
1895     QStringList result;
1896     foreach (const QString &str, reader->values(QLatin1String("LIBS"))) {
1897         if (str.startsWith(QLatin1String("-L")))
1898             result.append(str.mid(2));
1899     }
1900     return result;
1901 }
1902 
subDirsPaths(QtSupport::ProFileReader * reader,const QString & projectDir,QStringList * subProjectsNotToDeploy,QStringList * errors)1903 FilePaths QmakeProFile::subDirsPaths(QtSupport::ProFileReader *reader,
1904                                             const QString &projectDir,
1905                                             QStringList *subProjectsNotToDeploy,
1906                                             QStringList *errors)
1907 {
1908     FilePaths subProjectPaths;
1909 
1910     const QStringList subDirVars = reader->values(QLatin1String("SUBDIRS"));
1911 
1912     foreach (const QString &subDirVar, subDirVars) {
1913         // Special case were subdir is just an identifier:
1914         //   "SUBDIR = subid
1915         //    subid.subdir = realdir"
1916         // or
1917         //   "SUBDIR = subid
1918         //    subid.file = realdir/realfile.pro"
1919 
1920         QString realDir;
1921         const QString subDirKey = subDirVar + QLatin1String(".subdir");
1922         const QString subDirFileKey = subDirVar + QLatin1String(".file");
1923         if (reader->contains(subDirKey))
1924             realDir = reader->value(subDirKey);
1925         else if (reader->contains(subDirFileKey))
1926             realDir = reader->value(subDirFileKey);
1927         else
1928             realDir = subDirVar;
1929         QFileInfo info(realDir);
1930         if (!info.isAbsolute())
1931             info.setFile(projectDir + QLatin1Char('/') + realDir);
1932         realDir = info.filePath();
1933 
1934         QString realFile;
1935         if (info.isDir())
1936             realFile = QString::fromLatin1("%1/%2.pro").arg(realDir, info.fileName());
1937         else
1938             realFile = realDir;
1939 
1940         if (QFile::exists(realFile)) {
1941             realFile = QDir::cleanPath(realFile);
1942             subProjectPaths << FilePath::fromString(realFile);
1943             if (subProjectsNotToDeploy && !subProjectsNotToDeploy->contains(realFile)
1944                     && reader->values(subDirVar + QLatin1String(".CONFIG"))
1945                         .contains(QLatin1String("no_default_target"))) {
1946                 subProjectsNotToDeploy->append(realFile);
1947             }
1948         } else {
1949             if (errors)
1950                 errors->append(QCoreApplication::translate("QmakeProFile", "Could not find .pro file for subdirectory \"%1\" in \"%2\".")
1951                                .arg(subDirVar).arg(realDir));
1952         }
1953     }
1954 
1955     return Utils::filteredUnique(subProjectPaths);
1956 }
1957 
targetInformation(QtSupport::ProFileReader * reader,QtSupport::ProFileReader * readerBuildPass,const FilePath & buildDir,const FilePath & projectFilePath)1958 TargetInformation QmakeProFile::targetInformation(QtSupport::ProFileReader *reader,
1959                                                   QtSupport::ProFileReader *readerBuildPass,
1960                                                   const FilePath &buildDir,
1961                                                   const FilePath &projectFilePath)
1962 {
1963     TargetInformation result;
1964     if (!reader || !readerBuildPass)
1965         return result;
1966 
1967     QStringList builds = reader->values(QLatin1String("BUILDS"));
1968     if (!builds.isEmpty()) {
1969         QString build = builds.first();
1970         result.buildTarget = reader->value(build + QLatin1String(".target"));
1971     }
1972 
1973     // BUILD DIR
1974     result.buildDir = buildDir;
1975 
1976     if (readerBuildPass->contains(QLatin1String("DESTDIR")))
1977         result.destDir = FilePath::fromString(readerBuildPass->value(QLatin1String("DESTDIR")));
1978 
1979     // Target
1980     result.target = readerBuildPass->value(QLatin1String("TARGET"));
1981     if (result.target.isEmpty())
1982         result.target = projectFilePath.baseName();
1983 
1984     result.valid = true;
1985 
1986     return result;
1987 }
1988 
targetInformation() const1989 TargetInformation QmakeProFile::targetInformation() const
1990 {
1991     return m_qmakeTargetInformation;
1992 }
1993 
installsList(const QtSupport::ProFileReader * reader,const QString & projectFilePath,const QString & projectDir,const QString & buildDir)1994 InstallsList QmakeProFile::installsList(const QtSupport::ProFileReader *reader, const QString &projectFilePath,
1995                                         const QString &projectDir, const QString &buildDir)
1996 {
1997     InstallsList result;
1998     if (!reader)
1999         return result;
2000     const QStringList &itemList = reader->values(QLatin1String("INSTALLS"));
2001     if (itemList.isEmpty())
2002         return result;
2003 
2004     const QStringList installPrefixVars{"QT_INSTALL_PREFIX", "QT_INSTALL_EXAMPLES"};
2005     QList<QPair<QString, QString>> installPrefixValues;
2006     for (const QString &installPrefix : installPrefixVars) {
2007         installPrefixValues << qMakePair(reader->propertyValue(installPrefix),
2008                                          reader->propertyValue(installPrefix + "/dev"));
2009     }
2010 
2011     foreach (const QString &item, itemList) {
2012         const QStringList config = reader->values(item + ".CONFIG");
2013         const bool active = !config.contains("no_default_install");
2014         const bool executable = config.contains("executable");
2015         const QString pathVar = item + QLatin1String(".path");
2016         const QStringList &itemPaths = reader->values(pathVar);
2017         if (itemPaths.count() != 1) {
2018             qDebug("Invalid RHS: Variable '%s' has %d values.",
2019                 qPrintable(pathVar), int(itemPaths.count()));
2020             if (itemPaths.isEmpty()) {
2021                 qDebug("%s: Ignoring INSTALLS item '%s', because it has no path.",
2022                     qPrintable(projectFilePath), qPrintable(item));
2023                 continue;
2024             }
2025         }
2026 
2027         QString itemPath = itemPaths.last();
2028         for (const auto &prefixValuePair : qAsConst(installPrefixValues)) {
2029             if (prefixValuePair.first == prefixValuePair.second
2030                     || !itemPath.startsWith(prefixValuePair.first)) {
2031                 continue;
2032             }
2033             // This is a hack for projects which install into $$[QT_INSTALL_*],
2034             // in particular Qt itself, examples being most relevant.
2035             // Projects which implement their own install path policy must
2036             // parametrize their INSTALLS themselves depending on the intended
2037             // installation/deployment mode.
2038             itemPath.replace(0, prefixValuePair.first.length(), prefixValuePair.second);
2039             break;
2040         }
2041         if (item == QLatin1String("target")) {
2042             if (active)
2043                 result.targetPath = itemPath;
2044         } else {
2045             const auto &itemFiles = reader->fixifiedValues(
2046                         item + QLatin1String(".files"), projectDir, buildDir, true);
2047             result.items << InstallsItem(itemPath, itemFiles, active, executable);
2048         }
2049     }
2050     return result;
2051 }
2052 
installsList() const2053 InstallsList QmakeProFile::installsList() const
2054 {
2055     return m_installsList;
2056 }
2057 
sourceDir() const2058 FilePath QmakeProFile::sourceDir() const
2059 {
2060     return directoryPath();
2061 }
2062 
generatedFiles(const FilePath & buildDir,const FilePath & sourceFile,const FileType & sourceFileType) const2063 FilePaths QmakeProFile::generatedFiles(const FilePath &buildDir,
2064                                           const FilePath &sourceFile,
2065                                           const FileType &sourceFileType) const
2066 {
2067     // The mechanism for finding the file names is rather crude, but as we
2068     // cannot parse QMAKE_EXTRA_COMPILERS and qmake has facilities to put
2069     // ui_*.h files into a special directory, or even change the .h suffix, we
2070     // cannot help doing this here.
2071 
2072     if (sourceFileType == FileType::Form) {
2073         FilePath location;
2074         auto it = m_varValues.constFind(Variable::UiDir);
2075         if (it != m_varValues.constEnd() && !it.value().isEmpty())
2076             location = FilePath::fromString(it.value().front());
2077         else
2078             location = buildDir;
2079         if (location.isEmpty())
2080             return { };
2081         location = location.pathAppended("ui_"
2082                                          + sourceFile.completeBaseName()
2083                                          + singleVariableValue(Variable::HeaderExtension));
2084         return { Utils::FilePath::fromString(QDir::cleanPath(location.toString())) };
2085     } else if (sourceFileType == FileType::StateChart) {
2086         if (buildDir.isEmpty())
2087             return { };
2088         const FilePath location = buildDir.pathAppended(sourceFile.completeBaseName());
2089         return {
2090             location.stringAppended(singleVariableValue(Variable::HeaderExtension)),
2091             location.stringAppended(singleVariableValue(Variable::CppExtension))
2092         };
2093     }
2094     return { };
2095 }
2096 
extraCompilers() const2097 QList<ExtraCompiler *> QmakeProFile::extraCompilers() const
2098 {
2099     return m_extraCompilers;
2100 }
2101 
setupExtraCompiler(const FilePath & buildDir,const FileType & fileType,ExtraCompilerFactory * factory)2102 void QmakeProFile::setupExtraCompiler(const FilePath &buildDir,
2103                                        const FileType &fileType, ExtraCompilerFactory *factory)
2104 {
2105     for (const FilePath &fn : collectFiles(fileType)) {
2106         const FilePaths generated = generatedFiles(buildDir, fn, fileType);
2107         if (!generated.isEmpty())
2108             m_extraCompilers.append(factory->create(m_buildSystem->project(), fn, generated));
2109     }
2110 }
2111 
updateGeneratedFiles(const FilePath & buildDir)2112 void QmakeProFile::updateGeneratedFiles(const FilePath &buildDir)
2113 {
2114     // We can do this because other plugins are not supposed to keep the compilers around.
2115     qDeleteAll(m_extraCompilers);
2116     m_extraCompilers.clear();
2117 
2118     // Only those project types can have generated files for us
2119     if (m_projectType != ProjectType::ApplicationTemplate
2120             && m_projectType != ProjectType::SharedLibraryTemplate
2121             && m_projectType != ProjectType::StaticLibraryTemplate) {
2122         return;
2123     }
2124 
2125     const QList<ExtraCompilerFactory *> factories =
2126             ProjectExplorer::ExtraCompilerFactory::extraCompilerFactories();
2127 
2128     ExtraCompilerFactory *formFactory
2129             = Utils::findOrDefault(factories, Utils::equal(&ExtraCompilerFactory::sourceType, FileType::Form));
2130     if (formFactory)
2131         setupExtraCompiler(buildDir, FileType::Form, formFactory);
2132     ExtraCompilerFactory *scxmlFactory
2133             = Utils::findOrDefault(factories, Utils::equal(&ExtraCompilerFactory::sourceType, FileType::StateChart));
2134     if (scxmlFactory)
2135         setupExtraCompiler(buildDir, FileType::StateChart, scxmlFactory);
2136 }
2137