qmakenodes.cpp 93.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
12
13
14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16
17
18
19
20
21
22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
hjk's avatar
hjk committed
25

26
27
28
29
30
#include "qmakenodes.h"
#include "qmakeproject.h"
#include "qmakeprojectmanager.h"
#include "qmakeprojectmanagerconstants.h"
#include "qmakebuildconfiguration.h"
Tobias Hunger's avatar
Tobias Hunger committed
31
#include "qmakerunconfigurationfactory.h"
con's avatar
con committed
32
33

#include <projectexplorer/nodesvisitor.h>
34
#include <projectexplorer/projectexplorer.h>
Alessandro Portale's avatar
Alessandro Portale committed
35
#include <projectexplorer/projectexplorerconstants.h>
con's avatar
con committed
36
#include <coreplugin/editormanager/editormanager.h>
37
#include <coreplugin/editormanager/ieditor.h>
Jens Bache-Wiig's avatar
Jens Bache-Wiig committed
38
#include <coreplugin/fileiconprovider.h>
39
#include <coreplugin/documentmanager.h>
con's avatar
con committed
40
41
42
#include <coreplugin/icore.h>
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/vcsmanager.h>
43
#include <coreplugin/dialogs/readonlyfilesdialog.h>
con's avatar
con committed
44

45
46
#include <extensionsystem/pluginmanager.h>

47
#include <projectexplorer/buildmanager.h>
Tobias Hunger's avatar
Tobias Hunger committed
48
49
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/target.h>
50
#include <projectexplorer/projecttree.h>
51
#include <qtsupport/profilereader.h>
Tobias Hunger's avatar
Tobias Hunger committed
52
#include <qtsupport/qtkitinformation.h>
53
#include <cpptools/generatedcodemodelsupport.h>
con's avatar
con committed
54

Daniel Teske's avatar
Daniel Teske committed
55
56
#include <resourceeditor/resourcenode.h>

57
#include <cpptools/cppmodelmanager.h>
58
#include <cpptools/cpptoolsconstants.h>
Friedemann Kleint's avatar
Friedemann Kleint committed
59

60
#include <utils/algorithm.h>
61
#include <utils/fileutils.h>
62
#include <utils/hostosinfo.h>
63
#include <utils/qtcprocess.h>
Eike Ziller's avatar
Eike Ziller committed
64
#include <utils/mimetypes/mimedatabase.h>
65
#include <utils/stringutils.h>
66
#include <utils/theme/theme.h>
67
#include <proparser/prowriter.h>
68
#include <proparser/qmakevfs.h>
69
#include <proparser/ioutils.h>
hjk's avatar
hjk committed
70

71
#include <QApplication>
72
73
74
75
76
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QXmlStreamReader>
hjk's avatar
hjk committed
77

78
#include <QMessageBox>
79
#include <utils/QtConcurrentTools>
con's avatar
con committed
80

hjk's avatar
hjk committed
81
using namespace Core;
82
using namespace ProjectExplorer;
83
using namespace Utils;
84
using namespace QMakeInternal;
hjk's avatar
hjk committed
85

86
// Static cached data in struct QmakeNodeStaticData providing information and icons
87
88
89
90
// for file types and the project. Do some magic via qAddPostRoutine()
// to make sure the icons do not outlive QApplication, triggering warnings on X11.

struct FileTypeDataStorage {
91
    FileType type;
92
93
    const char *typeName;
    const char *icon;
94
    const char *addFileFilter;
95
96
97
};

static const FileTypeDataStorage fileTypeDataStorage[] = {
98
    { FileType::Header, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Headers"),
Alessandro Portale's avatar
Alessandro Portale committed
99
      ProjectExplorer::Constants::FILEOVERLAY_H, "*.h; *.hh; *.hpp; *.hxx;"},
100
    { FileType::Source, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Sources"),
Alessandro Portale's avatar
Alessandro Portale committed
101
      ProjectExplorer::Constants::FILEOVERLAY_CPP, "*.c; *.cc; *.cpp; *.cp; *.cxx; *.c++;" },
102
    { FileType::Form, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Forms"),
Alessandro Portale's avatar
Alessandro Portale committed
103
      Constants::FILEOVERLAY_UI, "*.ui;" },
104
    { FileType::StateChart, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "State charts"),
Alessandro Portale's avatar
Alessandro Portale committed
105
      ProjectExplorer::Constants::FILEOVERLAY_SCXML, "*.scxml;" },
106
    { FileType::Resource, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Resources"),
Alessandro Portale's avatar
Alessandro Portale committed
107
      ProjectExplorer::Constants::FILEOVERLAY_QRC, "*.qrc;" },
108
    { FileType::QML, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "QML"),
Alessandro Portale's avatar
Alessandro Portale committed
109
      ProjectExplorer::Constants::FILEOVERLAY_QML, "*.qml;" },
110
    { FileType::Unknown, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Other files"),
Alessandro Portale's avatar
Alessandro Portale committed
111
      ProjectExplorer::Constants::FILEOVERLAY_UNKNOWN, "*;" }
112
113
};

114
class SortByPath
115
{
116
public:
117
    bool operator()(Node *a, Node *b)
118
    { return operator()(a->filePath(), b->filePath()); }
119
    bool operator()(Node *a, const FileName &b)
120
    { return operator()(a->filePath(), b); }
121
    bool operator()(const FileName &a, Node *b)
122
    { return operator()(a, b->filePath()); }
123
    // Compare as strings to correctly detect case-only file rename
124
    bool operator()(const FileName &a, const FileName &b)
125
    { return a.toString() < b.toString(); }
126
};
127

128
class QmakeNodeStaticData {
129
130
131
public:
    class FileTypeData {
    public:
132
        FileTypeData(FileType t = FileType::Unknown,
133
                     const QString &tN = QString(),
134
                     const QString &aff = QString(),
135
                     const QIcon &i = QIcon()) :
136
        type(t), typeName(tN), addFileFilter(aff), icon(i) { }
con's avatar
con committed
137

138
        FileType type;
139
        QString typeName;
140
        QString addFileFilter;
141
142
143
        QIcon icon;
    };

144
    QmakeNodeStaticData();
145

146
147
148
149
    QVector<FileTypeData> fileTypeData;
    QIcon projectIcon;
};

150
static void clearQmakeNodeStaticData();
151

152
QmakeNodeStaticData::QmakeNodeStaticData()
153
{
154
155
    // File type data
    const unsigned count = sizeof(fileTypeDataStorage)/sizeof(FileTypeDataStorage);
156
    fileTypeData.reserve(count);
157
158
159
160

    // Overlay the SP_DirIcon with the custom icons
    const QSize desiredSize = QSize(16, 16);

161
    const QPixmap dirPixmap = qApp->style()->standardIcon(QStyle::SP_DirIcon).pixmap(desiredSize);
Friedemann Kleint's avatar
Friedemann Kleint committed
162
    for (unsigned i = 0 ; i < count; ++i) {
163
        const QIcon overlayIcon(QLatin1String(fileTypeDataStorage[i].icon));
164
        QIcon folderIcon;
165
        folderIcon.addPixmap(FileIconProvider::overlayIcon(dirPixmap, overlayIcon));
166
        const QString desc = QCoreApplication::translate("QmakeProjectManager::QmakePriFileNode", fileTypeDataStorage[i].typeName);
167
        const QString filter = QString::fromUtf8(fileTypeDataStorage[i].addFileFilter);
168
        fileTypeData.push_back(QmakeNodeStaticData::FileTypeData(fileTypeDataStorage[i].type,
169
                                                                 desc, filter, folderIcon));
170
171
    }
    // Project icon
172
    const QIcon projectBaseIcon(ProjectExplorer::Constants::FILEOVERLAY_QT);
173
    const QPixmap projectPixmap = FileIconProvider::overlayIcon(dirPixmap, projectBaseIcon);
174
    projectIcon.addPixmap(projectPixmap);
175

176
    qAddPostRoutine(clearQmakeNodeStaticData);
177
178
}

179
Q_GLOBAL_STATIC(QmakeNodeStaticData, qmakeNodeStaticData)
180

181
static void clearQmakeNodeStaticData()
182
{
183
184
    qmakeNodeStaticData()->fileTypeData.clear();
    qmakeNodeStaticData()->projectIcon = QIcon();
con's avatar
con committed
185
186
}

187
188
enum { debug = 0 };

189
190
using namespace QmakeProjectManager;
using namespace QmakeProjectManager::Internal;
191

192
193
194
195
196
197
namespace QmakeProjectManager {
namespace Internal {
class EvalInput
{
public:
    QString projectDir;
198
    FileName projectFilePath;
199
    QString buildDirectory;
200
    QString sysroot;
201
202
    QtSupport::ProFileReader *readerExact;
    QtSupport::ProFileReader *readerCumulative;
203
    QMakeGlobals *qmakeGlobals;
204
205
206
207
208
209
210
    QMakeVfs *qmakeVfs;
};

class PriFileEvalResult
{
public:
    QStringList folders;
211
212
    QSet<FileName> recursiveEnumerateFiles;
    QMap<FileType, QSet<FileName> > foundFiles;
213
214
};

215
216
217
class IncludedPriFile
{
public:
218
    ProFile *proFile;
219
220
221
222
223
224
225
226
227
228
    Utils::FileName name;
    PriFileEvalResult result;
    QMap<Utils::FileName, IncludedPriFile *> children;

    ~IncludedPriFile()
    {
        qDeleteAll(children);
    }
};

229
230
231
232
233
234
235
236
class EvalResult
{
public:
    enum EvalResultState { EvalAbort, EvalFail, EvalPartial, EvalOk };
    EvalResultState state;
    QmakeProjectType projectType;

    QStringList subProjectsNotToDeploy;
237
    QSet<FileName> exactSubdirs;
238
    IncludedPriFile includedFiles;
239
240
241
242
243
    TargetInformation targetInformation;
    InstallsList installsList;
    QHash<QmakeVariable, QStringList> newVarValues;
    QStringList errors;
};
244
245
246
247

QString ProVirtualFolderNode::displayName() const
{
    return m_typeName;
248
}
249
250
251
252

QString ProVirtualFolderNode::addFileFilter() const
{
    return m_addFileFilter;
253
}
254

255
256
257
} // namespace Internal
} // namespace QMakeProjectManager

258
QmakePriFile::QmakePriFile(QmakeProjectManager::QmakePriFileNode *qmakePriFile)
259
    : IDocument(0), m_priFile(qmakePriFile)
260
{
261
    setId("Qmake.PriFile");
hjk's avatar
hjk committed
262
    setMimeType(QLatin1String(QmakeProjectManager::Constants::PROFILE_MIMETYPE));
263
    setFilePath(m_priFile->filePath());
264
265
}

266
Core::IDocument::ReloadBehavior QmakePriFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
267
{
268
269
270
    Q_UNUSED(state)
    Q_UNUSED(type)
    return BehaviorSilent;
271
272
}

273
bool QmakePriFile::reload(QString *errorString, ReloadFlag flag, ChangeType type)
274
{
275
    Q_UNUSED(errorString)
276
277
    Q_UNUSED(flag)
    if (type == TypePermissions)
278
        return true;
279
    m_priFile->scheduleUpdate();
280
    return true;
281
}
282

con's avatar
con committed
283
/*!
284
  \class QmakePriFileNode
con's avatar
con committed
285
286
287
  Implements abstract ProjectNode class
  */

288
namespace QmakeProjectManager {
289

290
291
QmakePriFileNode::QmakePriFileNode(QmakeProject *project, QmakeProFileNode *qmakeProFileNode,
                                   const FileName &filePath)
con's avatar
con committed
292
293
        : ProjectNode(filePath),
          m_project(project),
294
          m_qmakeProFileNode(qmakeProFileNode),
295
          m_projectFilePath(filePath),
296
          m_projectDir(filePath.toFileInfo().absolutePath())
con's avatar
con committed
297
{
dt's avatar
dt committed
298
    Q_ASSERT(project);
299
300
    m_qmakePriFile = new QmakePriFile(this);
    Core::DocumentManager::addDocument(m_qmakePriFile);
301

302
    setDisplayName(filePath.toFileInfo().completeBaseName());
303
    setIcon(qmakeNodeStaticData()->projectIcon);
304
305
}

306
QmakePriFileNode::~QmakePriFileNode()
307
308
{
    watchFolders(QSet<QString>());
309
    delete m_qmakePriFile;
310
311
}

312
void QmakePriFileNode::scheduleUpdate()
313
{
314
    QtSupport::ProFileCacheManager::instance()->discardFile(m_projectFilePath.toString());
315
    m_qmakeProFileNode->scheduleUpdate(QmakeProFileNode::ParseLater);
con's avatar
con committed
316
317
}

Daniel Teske's avatar
Daniel Teske committed
318
namespace Internal {
319
struct InternalNode
320
{
321
322
    QList<InternalNode *> virtualfolders;
    QMap<QString, InternalNode *> subnodes;
323
    FileNameList files;
324
    FileType type = FileType::Unknown;
325
    int priority = Node::DefaultVirtualFolderPriority;
326
    QString displayName;
327
    QString typeName;
328
    QString addFileFilter;
329
330
    QString fullPath;
    QIcon icon;
331

332
333
    ~InternalNode()
    {
334
        qDeleteAll(virtualfolders);
335
336
        qDeleteAll(subnodes);
    }
337

338
    // Creates: a tree structure from a list of absolute file paths.
339
340
341
342
343
344
345
346
347
348
    // Empty directories are compressed into a single entry with a longer path.
    // * project
    //    * /absolute/path
    //       * file1
    //    * relative
    //       * path1
    //          * file1
    //          * file2
    //       * path2
    //          * file1
349
    // The function first creates a tree that looks like the directory structure, i.e.
350
351
352
353
354
355
    //    * /
    //       * absolute
    //          * path
    // ...
    // and afterwards calls compress() which merges directory nodes with single children, i.e. to
    //    * /absolute/path
356
    void create(const QString &projectDir, const QSet<FileName> &newFilePaths, FileType type)
357
    {
358
        static const QChar separator = QLatin1Char('/');
359
360
361
        const FileName projectDirFileName = FileName::fromString(projectDir);
        foreach (const FileName &file, newFilePaths) {
            FileName fileWithoutPrefix;
362
            bool isRelative;
363
            if (file.isChildOf(projectDirFileName)) {
364
                isRelative = true;
365
                fileWithoutPrefix = file.relativeChildPath(projectDirFileName);
366
367
368
369
            } else {
                isRelative = false;
                fileWithoutPrefix = file;
            }
370
            QStringList parts = fileWithoutPrefix.toString().split(separator, QString::SkipEmptyParts);
371
            if (!HostOsInfo::isWindowsHost() && !isRelative && parts.count() > 0)
372
373
374
                parts[0].prepend(separator);
            QStringListIterator it(parts);
            InternalNode *currentNode = this;
375
            QString path = (isRelative ? (projectDirFileName.toString() + QLatin1Char('/')) : QString());
376
377
378
379
            while (it.hasNext()) {
                const QString &key = it.next();
                if (it.hasNext()) { // key is directory
                    path += key;
380
                    if (!currentNode->subnodes.contains(path)) {
381
382
383
                        InternalNode *val = new InternalNode;
                        val->type = type;
                        val->fullPath = path;
384
385
                        val->displayName = key;
                        currentNode->subnodes.insert(path, val);
386
387
                        currentNode = val;
                    } else {
388
                        currentNode = currentNode->subnodes.value(path);
389
                    }
390
391
                    path += separator;
                } else { // key is filename
392
                    currentNode->files.append(file);
393
394
395
                }
            }
        }
396
397
        this->compress();
    }
398

399
400
401
402
403
404
405
406
407
    // Removes folder nodes with only a single sub folder in it
    void compress()
    {
        QMap<QString, InternalNode*> newSubnodes;
        QMapIterator<QString, InternalNode*> i(subnodes);
        while (i.hasNext()) {
            i.next();
            i.value()->compress();
            if (i.value()->files.isEmpty() && i.value()->subnodes.size() == 1) {
408
                // replace i.value() by i.value()->subnodes.begin()
409
                QString key = i.value()->subnodes.begin().key();
410
                InternalNode *keep = i.value()->subnodes.value(key);
411
                keep->displayName = i.value()->displayName + QDir::separator() + keep->displayName;
412
                newSubnodes.insert(key, keep);
413
414
415
416
                i.value()->subnodes.clear();
                delete i.value();
            } else {
                newSubnodes.insert(i.key(), i.value());
417
418
            }
        }
419
420
        subnodes = newSubnodes;
    }
421

422
423
424
    FolderNode *createFolderNode(InternalNode *node)
    {
        FolderNode *newNode = 0;
425
426
427
        if (node->typeName.isEmpty()) {
            newNode = new FolderNode(FileName::fromString(node->fullPath));
        } else {
428
429
430
431
            auto n = new ProVirtualFolderNode(FileName::fromString(node->fullPath),
                                                 node->priority, node->typeName);
            n->setAddFileFilter(node->addFileFilter);
            newNode = n;
432
        }
433
434
435
436
437
438
439

        newNode->setDisplayName(node->displayName);
        if (!node->icon.isNull())
            newNode->setIcon(node->icon);
        return newNode;
    }

440
    // Makes the projectNode's subtree below the given folder match this internal node's subtree
441
    void updateSubFolders(FolderNode *folder)
442
    {
443
        if (type == FileType::Resource)
Daniel Teske's avatar
Daniel Teske committed
444
445
446
            updateResourceFiles(folder);
        else
            updateFiles(folder, type);
447

448
449
        // updateFolders
        QMultiMap<QString, FolderNode *> existingFolderNodes;
450
        foreach (FolderNode *node, folder->folderNodes())
451
            if (node->nodeType() != NodeType::Project && !dynamic_cast<ResourceEditor::ResourceTopLevelNode *>(node))
452
                existingFolderNodes.insert(node->filePath().toString(), node);
453

454
455
456
457
458
        QList<FolderNode *> foldersToRemove;
        QList<FolderNode *> foldersToAdd;
        typedef QPair<InternalNode *, FolderNode *> NodePair;
        QList<NodePair> nodesToUpdate;

459
460
461
462
463
464
465
466
467
        // Check virtual
        {
            QList<InternalNode *>::const_iterator it = virtualfolders.constBegin();
            QList<InternalNode *>::const_iterator end = virtualfolders.constEnd();
            for ( ; it != end; ++it) {
                bool found = false;
                QString path = (*it)->fullPath;
                QMultiMap<QString, FolderNode *>::const_iterator oldit
                        = existingFolderNodes.constFind(path);
468
                while (oldit != existingFolderNodes.constEnd() && oldit.key() == path) {
469
                    if (oldit.value()->nodeType() == NodeType::VirtualFolder) {
470
                        VirtualFolderNode *vfn = dynamic_cast<VirtualFolderNode *>(oldit.value());
471
472
473
474
475
476
477
478
479
480
481
482
483
484
                        if (vfn->priority() == (*it)->priority) {
                            found = true;
                            break;
                        }
                    }
                    ++oldit;
                }
                if (found) {
                    nodesToUpdate << NodePair(*it, *oldit);
                } else {
                    FolderNode *newNode = createFolderNode(*it);
                    foldersToAdd << newNode;
                    nodesToUpdate << NodePair(*it, newNode);
                }
485
            }
486
        }
487
488
489
490
491
492
493
494
495
496
        // Check subnodes
        {
            QMap<QString, InternalNode *>::const_iterator it = subnodes.constBegin();
            QMap<QString, InternalNode *>::const_iterator end = subnodes.constEnd();

            for ( ; it != end; ++it) {
                bool found = false;
                QString path = it.value()->fullPath;
                QMultiMap<QString, FolderNode *>::const_iterator oldit
                        = existingFolderNodes.constFind(path);
497
                while (oldit != existingFolderNodes.constEnd() && oldit.key() == path) {
498
                    if (oldit.value()->nodeType() == NodeType::Folder) {
499
500
501
502
503
504
505
506
507
508
509
510
511
                        found = true;
                        break;
                    }
                    ++oldit;
                }
                if (found) {
                    nodesToUpdate << NodePair(it.value(), *oldit);
                } else {
                    FolderNode *newNode = createFolderNode(it.value());
                    foldersToAdd << newNode;
                    nodesToUpdate << NodePair(it.value(), newNode);
                }
            }
512
        }
513

514
515
516
517
518
519
520
521
522
523
        QSet<FolderNode *> toKeep;
        foreach (const NodePair &np, nodesToUpdate)
            toKeep << np.second;

        QMultiMap<QString, FolderNode *>::const_iterator jit = existingFolderNodes.constBegin();
        QMultiMap<QString, FolderNode *>::const_iterator jend = existingFolderNodes.constEnd();
        for ( ; jit != jend; ++jit)
            if (!toKeep.contains(jit.value()))
                foldersToRemove << jit.value();

524
        if (!foldersToRemove.isEmpty())
525
            folder->removeFolderNodes(foldersToRemove);
526
        if (!foldersToAdd.isEmpty())
527
            folder->addFolderNodes(foldersToAdd);
528

529
        foreach (const NodePair &np, nodesToUpdate)
530
            np.first->updateSubFolders(np.second);
531
532
533
    }

    // Makes the folder's files match this internal node's file list
534
    void updateFiles(FolderNode *folder, FileType type)
535
536
537
538
539
    {
        QList<FileNode*> existingFileNodes;
        foreach (FileNode *fileNode, folder->fileNodes()) {
            if (fileNode->fileType() == type && !fileNode->isGenerated())
                existingFileNodes << fileNode;
540
541
        }

542
        QList<FileNode*> filesToRemove;
543
        FileNameList filesToAdd;
544
545

        SortByPath sortByPath;
546
547
        Utils::sort(files, sortByPath);
        Utils::sort(existingFileNodes, sortByPath);
548
549
550
551

        ProjectExplorer::compareSortedLists(existingFileNodes, files, filesToRemove, filesToAdd, sortByPath);

        QList<FileNode *> nodesToAdd;
552
        foreach (const FileName &file, filesToAdd)
553
            nodesToAdd << new FileNode(file, type, false);
554

555
556
        folder->removeFileNodes(filesToRemove);
        folder->addFileNodes(nodesToAdd);
557
    }
Daniel Teske's avatar
Daniel Teske committed
558
559
560
561

    // Makes the folder's files match this internal node's file list
    void updateResourceFiles(FolderNode *folder)
    {
562
        QList<FolderNode *> existingResourceNodes; // for resource special handling
563
        foreach (FolderNode *folderNode, folder->folderNodes()) {
564
            if (ResourceEditor::ResourceTopLevelNode *rn = dynamic_cast<ResourceEditor::ResourceTopLevelNode *>(folderNode))
Daniel Teske's avatar
Daniel Teske committed
565
566
567
                existingResourceNodes << rn;
        }

568
        QList<FolderNode *> resourcesToRemove;
569
        FileNameList resourcesToAdd;
Daniel Teske's avatar
Daniel Teske committed
570
571

        SortByPath sortByPath;
572
573
        Utils::sort(files, sortByPath);
        Utils::sort(existingResourceNodes, sortByPath);
Daniel Teske's avatar
Daniel Teske committed
574
575
576
577
578
579

        ProjectExplorer::compareSortedLists(existingResourceNodes, files, resourcesToRemove, resourcesToAdd, sortByPath);

        QList<FolderNode *> nodesToAdd;
        nodesToAdd.reserve(resourcesToAdd.size());

580
        foreach (const FileName &file, resourcesToAdd) {
581
            auto vfs = static_cast<QmakePriFileNode *>(folder->parentProjectNode())->m_project->qmakeVfs();
582
            QString contents;
583
584
585
586
587
588
            // Prefer the cumulative file if it's non-empty, based on the assumption
            // that it contains more "stuff".
            vfs->readVirtualFile(file.toString(), QMakeVfs::VfsCumulative, &contents);
            // If the cumulative evaluation botched the file too much, try the exact one.
            if (contents.isEmpty())
                vfs->readVirtualFile(file.toString(), QMakeVfs::VfsExact, &contents);
589
590
            nodesToAdd.append(new ResourceEditor::ResourceTopLevelNode(file, contents, folder));
        }
Daniel Teske's avatar
Daniel Teske committed
591
592
593
594
595

        folder->removeFolderNodes(resourcesToRemove);
        folder->addFolderNodes(nodesToAdd);

        foreach (FolderNode *fn, nodesToAdd)
596
            dynamic_cast<ResourceEditor::ResourceTopLevelNode *>(fn)->update();
Daniel Teske's avatar
Daniel Teske committed
597
    }
598
};
Daniel Teske's avatar
Daniel Teske committed
599
}
dt's avatar
dt committed
600

601
QStringList QmakePriFileNode::baseVPaths(QtSupport::ProFileReader *reader, const QString &projectDir, const QString &buildDir)
con's avatar
con committed
602
{
dt's avatar
dt committed
603
604
605
    QStringList result;
    if (!reader)
        return result;
606
    result += reader->absolutePathValues(QLatin1String("VPATH"), projectDir);
dt's avatar
dt committed
607
    result << projectDir; // QMAKE_ABSOLUTE_SOURCE_PATH
608
    result << buildDir;
dt's avatar
dt committed
609
610
611
    result.removeDuplicates();
    return result;
}
con's avatar
con committed
612

613
QStringList QmakePriFileNode::fullVPaths(const QStringList &baseVPaths, QtSupport::ProFileReader *reader,
614
                                       const QString &qmakeVariable, const QString &projectDir)
dt's avatar
dt committed
615
616
617
618
{
    QStringList vPaths;
    if (!reader)
        return vPaths;
619
    vPaths = reader->absolutePathValues(QLatin1String("VPATH_") + qmakeVariable, projectDir);
dt's avatar
dt committed
620
621
622
623
624
    vPaths += baseVPaths;
    vPaths.removeDuplicates();
    return vPaths;
}

625
QSet<FileName> QmakePriFileNode::recursiveEnumerate(const QString &folder)
626
{
627
    QSet<FileName> result;
628
629
630
631
632
633
634
    QDir dir(folder);
    dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
    foreach (const QFileInfo &file, dir.entryInfoList()) {
        if (file.isDir() && !file.isSymLink())
            result += recursiveEnumerate(file.absoluteFilePath());
        else if (!Core::EditorManager::isAutoSaveFile(file.fileName()))
            result += FileName(file);
635
636
637
    }
    return result;
}
dt's avatar
dt committed
638

639
640
641
QStringList QmakeProFileNode::fileListForVar(
        const QHash<QString, QVector<ProFileEvaluator::SourceFile> > &sourceFiles,
        const QString &varName)
dt's avatar
dt committed
642
{
643
644
645
646
647
648
649
    const QVector<ProFileEvaluator::SourceFile> &sources = sourceFiles[varName];
    QStringList result;
    result.reserve(sources.size());
    foreach (const ProFileEvaluator::SourceFile &sf, sources)
        result << sf.fileName;
    return result;
}
650

651
652
653
654
655
656
657
658
659
660
661
662
void QmakePriFileNode::extractSources(
        QHash<const ProFile *, PriFileEvalResult *> proToResult, PriFileEvalResult *fallback,
        QVector<ProFileEvaluator::SourceFile> sourceFiles, FileType type)
{
    foreach (const ProFileEvaluator::SourceFile &source, sourceFiles) {
        PriFileEvalResult *result = proToResult.value(source.proFile);
        if (!result)
            result = fallback;
        result->foundFiles[type].insert(FileName::fromString(source.fileName));
    }
}

663
664
665
666
667
668
669
670
671
672
673
void QmakePriFileNode::extractInstalls(
        QHash<const ProFile *, PriFileEvalResult *> proToResult, PriFileEvalResult *fallback,
        const InstallsList &installList)
{
    for (const InstallsItem &item : installList.items) {
        for (const ProFileEvaluator::SourceFile &source : item.files) {
            auto *result = proToResult.value(source.proFile);
            if (!result)
                result = fallback;
            result->folders << source.fileName;
        }
674
    }
675
}
676

677
678
void QmakePriFileNode::processValues(PriFileEvalResult &result)
{
679
    result.folders.removeDuplicates();
dt's avatar
dt committed
680

681
    // Remove non existing items and non folders
682
683
    QStringList::iterator it = result.folders.begin();
    while (it != result.folders.end()) {
dt's avatar
dt committed
684
        QFileInfo fi(*it);
dt's avatar
dt committed
685
686
687
688
689
        if (fi.exists()) {
            if (fi.isDir()) {
                // keep directories
                ++it;
            } else {
690
                // move files directly to recursiveEnumerateFiles
691
                result.recursiveEnumerateFiles << FileName::fromString(*it);
692
                it = result.folders.erase(it);
dt's avatar
dt committed
693
694
695
            }
        } else {
            // do remove non exsting stuff
696
            it = result.folders.erase(it);
dt's avatar
dt committed
697
        }
dt's avatar
dt committed
698
699
    }

700
701
    foreach (const QString &folder, result.folders)
        result.recursiveEnumerateFiles += recursiveEnumerate(folder);
702

703
    const QVector<QmakeNodeStaticData::FileTypeData> &fileTypes = qmakeNodeStaticData()->fileTypeData;
dt's avatar
dt committed
704
705
    for (int i = 0; i < fileTypes.size(); ++i) {
        FileType type = fileTypes.at(i).type;
706
707
708
        QSet<FileName> &foundFiles = result.foundFiles[type];
        result.recursiveEnumerateFiles.subtract(foundFiles);
        QSet<FileName> newFilePaths = filterFilesProVariables(type, foundFiles);
709
        newFilePaths += filterFilesRecursiveEnumerata(type, result.recursiveEnumerateFiles);
710
        foundFiles = newFilePaths;
711
712
713
714
715
716
717
    }
}

void QmakePriFileNode::update(const Internal::PriFileEvalResult &result)
{
    // add project file node
    if (m_fileNodes.isEmpty())
718
        addFileNodes(QList<FileNode *>() << new FileNode(m_projectFilePath, FileType::Project, false));
719

720
721
722
723
724
725
726
    m_recursiveEnumerateFiles = result.recursiveEnumerateFiles;
    watchFolders(result.folders.toSet());

    InternalNode contents;
    const QVector<QmakeNodeStaticData::FileTypeData> &fileTypes = qmakeNodeStaticData()->fileTypeData;
    for (int i = 0; i < fileTypes.size(); ++i) {
        FileType type = fileTypes.at(i).type;
727
        const QSet<FileName> &newFilePaths = result.foundFiles.value(type);
728
729
        // We only need to save this information if
        // we are watching folders
730
        if (!result.folders.isEmpty())
731
732
733
734
            m_files[type] = newFilePaths;
        else
            m_files[type].clear();

735
736
737
        if (!newFilePaths.isEmpty()) {
            InternalNode *subfolder = new InternalNode;
            subfolder->type = type;
738
            subfolder->icon = fileTypes.at(i).icon;
739
740
            subfolder->fullPath = m_projectDir;
            subfolder->typeName = fileTypes.at(i).typeName;
741
            subfolder->addFileFilter = fileTypes.at(i).addFileFilter;
742
            subfolder->priority = Node::DefaultVirtualFolderPriority - i;
743
            subfolder->displayName = fileTypes.at(i).typeName;
744
            contents.virtualfolders.append(subfolder);
745
            // create the hierarchy with subdirectories
746
            subfolder->create(m_projectDir, newFilePaths, type);
con's avatar
con committed
747
748
        }
    }
749

750
    contents.updateSubFolders(this);
751
752
}

753
void QmakePriFileNode::watchFolders(const QSet<QString> &folders)
754
755
756
757
758
759
760
761
{
    QSet<QString> toUnwatch = m_watchedFolders;
    toUnwatch.subtract(folders);

    QSet<QString> toWatch = folders;
    toWatch.subtract(m_watchedFolders);

    if (!toUnwatch.isEmpty())
762
        m_project->unwatchFolders(toUnwatch.toList(), this);
763
    if (!toWatch.isEmpty())
764
        m_project->watchFolders(toWatch.toList(), this);
765
766
767
768

    m_watchedFolders = folders;
}

769
bool QmakePriFileNode::folderChanged(const QString &changedFolder, const QSet<FileName> &newFiles)
770
{
771
    //qDebug()<<"########## QmakePriFileNode::folderChanged";
772
773
    // So, we need to figure out which files changed.

774
    QSet<FileName> addedFiles = newFiles;
775
776
    addedFiles.subtract(m_recursiveEnumerateFiles);

777
    QSet<FileName> removedFiles = m_recursiveEnumerateFiles;
778
779
    removedFiles.subtract(newFiles);

780
781
    foreach (const FileName &file, removedFiles) {
        if (!file.isChildOf(FileName::fromString(changedFolder)))
782
783
784
785
786
787
788
789
790
791
            removedFiles.remove(file);
    }

    if (addedFiles.isEmpty() && removedFiles.isEmpty())
        return false;

    m_recursiveEnumerateFiles = newFiles;

    // Apply the differences
    // per file type
792
    const QVector<QmakeNodeStaticData::FileTypeData> &fileTypes = qmakeNodeStaticData()->fileTypeData;
793
794
    for (int i = 0; i < fileTypes.size(); ++i) {
        FileType type = fileTypes.at(i).type;
795
796
        QSet<FileName> add = filterFilesRecursiveEnumerata(type, addedFiles);
        QSet<FileName> remove = filterFilesRecursiveEnumerata(type, removedFiles);
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818

        if (!add.isEmpty() || !remove.isEmpty()) {
            // Scream :)
//            qDebug()<<"For type"<<fileTypes.at(i).typeName<<"\n"
//                    <<"added files"<<add<<"\n"
//                    <<"removed files"<<remove;

            m_files[type].unite(add);
            m_files[type].subtract(remove);
        }
    }

    // Now apply stuff
    InternalNode contents;
    for (int i = 0; i < fileTypes.size(); ++i) {
        FileType type = fileTypes.at(i).type;
        if (!m_files[type].isEmpty()) {
            InternalNode *subfolder = new InternalNode;
            subfolder->type