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 ¤tFiles = 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 ¤tLines,
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