qmakenodes.cpp 95.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3
4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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
Eike Ziller's avatar
Eike Ziller committed
12
13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15
**
hjk's avatar
hjk committed
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25
26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
con's avatar
con committed
27
28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

31
32
33
34
35
#include "qmakenodes.h"
#include "qmakeproject.h"
#include "qmakeprojectmanager.h"
#include "qmakeprojectmanagerconstants.h"
#include "qmakebuildconfiguration.h"
Tobias Hunger's avatar
Tobias Hunger committed
36
#include "qmakerunconfigurationfactory.h"
con's avatar
con committed
37
38

#include <projectexplorer/nodesvisitor.h>
39
#include <projectexplorer/projectexplorer.h>
con's avatar
con committed
40
#include <coreplugin/editormanager/editormanager.h>
41
#include <coreplugin/editormanager/ieditor.h>
Jens Bache-Wiig's avatar
Jens Bache-Wiig committed
42
#include <coreplugin/fileiconprovider.h>
43
#include <coreplugin/documentmanager.h>
con's avatar
con committed
44
45
46
#include <coreplugin/icore.h>
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/vcsmanager.h>
47
#include <coreplugin/dialogs/readonlyfilesdialog.h>
con's avatar
con committed
48

49
#include <projectexplorer/buildmanager.h>
Tobias Hunger's avatar
Tobias Hunger committed
50
51
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/target.h>
52
#include <projectexplorer/projecttree.h>
53
#include <qtsupport/profilereader.h>
Tobias Hunger's avatar
Tobias Hunger committed
54
#include <qtsupport/qtkitinformation.h>
55
#include <qtsupport/uicodemodelsupport.h>
con's avatar
con committed
56

Daniel Teske's avatar
Daniel Teske committed
57
58
#include <resourceeditor/resourcenode.h>

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

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

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;
hjk's avatar
hjk committed
84

85
// Static cached data in struct QmakeNodeStaticData providing information and icons
86
87
88
89
// 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 {
90
    FileType type;
91
92
    const char *typeName;
    const char *icon;
Alessandro Portale's avatar
Alessandro Portale committed
93
    Theme::ImageFile themeImage;
94
95
96
};

static const FileTypeDataStorage fileTypeDataStorage[] = {
97
    { HeaderType, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Headers"),
Alessandro Portale's avatar
Alessandro Portale committed
98
      ":/qmakeprojectmanager/images/headers.png", Theme::ProjectExplorerHeader },
99
    { SourceType, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Sources"),
Alessandro Portale's avatar
Alessandro Portale committed
100
      ":/qmakeprojectmanager/images/sources.png", Theme::ProjectExplorerSource },
101
    { FormType, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Forms"),
Alessandro Portale's avatar
Alessandro Portale committed
102
      ":/qtsupport/images/forms.png", Theme::ProjectExplorerForm },
103
    { ResourceType, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Resources"),
Alessandro Portale's avatar
Alessandro Portale committed
104
      ":/qtsupport/images/qt_qrc.png", Theme::ProjectExplorerResource },
105
    { QMLType, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "QML"),
Alessandro Portale's avatar
Alessandro Portale committed
106
      ":/qtsupport/images/qml.png", Theme::ProjectExplorerQML },
107
    { UnknownFileType, QT_TRANSLATE_NOOP("QmakeProjectManager::QmakePriFileNode", "Other files"),
Alessandro Portale's avatar
Alessandro Portale committed
108
      ":/qmakeprojectmanager/images/unknown.png", Theme::ProjectExplorerOtherFiles }
109
110
};

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

125
class QmakeNodeStaticData {
126
127
128
public:
    class FileTypeData {
    public:
129
        FileTypeData(FileType t = UnknownFileType,
130
131
132
                     const QString &tN = QString(),
                     const QIcon &i = QIcon()) :
        type(t), typeName(tN), icon(i) { }
con's avatar
con committed
133

134
        FileType type;
135
136
137
138
        QString typeName;
        QIcon icon;
    };

139
    QmakeNodeStaticData();
140

141
142
143
144
    QVector<FileTypeData> fileTypeData;
    QIcon projectIcon;
};

145
static void clearQmakeNodeStaticData();
146

147
QmakeNodeStaticData::QmakeNodeStaticData()
148
{
149
150
    // File type data
    const unsigned count = sizeof(fileTypeDataStorage)/sizeof(FileTypeDataStorage);
151
    fileTypeData.reserve(count);
152
153
154
155

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

Friedemann Kleint's avatar
Friedemann Kleint committed
156
    for (unsigned i = 0 ; i < count; ++i) {
157
        QIcon overlayIcon;
Alessandro Portale's avatar
Alessandro Portale committed
158
159
        const QString iconFile = creatorTheme()->imageFile(fileTypeDataStorage[i].themeImage,
                                                           QString::fromLatin1(fileTypeDataStorage[i].icon));
160
        overlayIcon = QIcon(iconFile);
161
162
163
164
165
        const QPixmap folderPixmap =
                Core::FileIconProvider::overlayIcon(QStyle::SP_DirIcon,
                                                    overlayIcon, desiredSize);
        QIcon folderIcon;
        folderIcon.addPixmap(folderPixmap);
166
        const QString desc = QCoreApplication::translate("QmakeProjectManager::QmakePriFileNode", fileTypeDataStorage[i].typeName);
167
        fileTypeData.push_back(QmakeNodeStaticData::FileTypeData(fileTypeDataStorage[i].type,
168
                                                               desc, folderIcon));
169
170
    }
    // Project icon
Alessandro Portale's avatar
Alessandro Portale committed
171
172
173
    const QString fileName = creatorTheme()->imageFile(Theme::ProjectFileIcon,
                                                       QLatin1String(":/qtsupport/images/qt_project.png"));
    const QIcon projectBaseIcon(fileName);
174
175
176
    const QPixmap projectPixmap = Core::FileIconProvider::overlayIcon(QStyle::SP_DirIcon,
                                                                      projectBaseIcon,
                                                                      desiredSize);
177
    projectIcon.addPixmap(projectPixmap);
178

179
    qAddPostRoutine(clearQmakeNodeStaticData);
180
181
}

182
Q_GLOBAL_STATIC(QmakeNodeStaticData, qmakeNodeStaticData)
183

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

190
191
enum { debug = 0 };

192
193
using namespace QmakeProjectManager;
using namespace QmakeProjectManager::Internal;
194

195
196
197
198
199
200
namespace QmakeProjectManager {
namespace Internal {
class EvalInput
{
public:
    QString projectDir;
201
    FileName projectFilePath;
202
203
204
205
206
207
208
209
210
211
212
213
    QString buildDirectory;
    QtSupport::ProFileReader *readerExact;
    QtSupport::ProFileReader *readerCumulative;
    ProFileGlobals *qmakeGlobals;
    QMakeVfs *qmakeVfs;
    bool isQt5;
};

class PriFileEvalResult
{
public:
    QStringList folders;
214
215
    QSet<FileName> recursiveEnumerateFiles;
    QMap<FileType, QSet<FileName> > foundFiles;
216
217
};

218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
class IncludedPriFile
{
public:
    Utils::FileName name;
    PriFileEvalResult result;
    QMap<Utils::FileName, IncludedPriFile *> children;
    QVector<ProFile *> proFilesExact;
    QVector<ProFile *> proFilesCumulative;

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

233
234
235
236
237
238
239
240
class EvalResult
{
public:
    enum EvalResultState { EvalAbort, EvalFail, EvalPartial, EvalOk };
    EvalResultState state;
    QmakeProjectType projectType;

    QStringList subProjectsNotToDeploy;
241
    QSet<FileName> exactSubdirs;
242
    IncludedPriFile includedFiles;
243
244
245
246
247
248
249
250
    TargetInformation targetInformation;
    InstallsList installsList;
    QHash<QmakeVariable, QStringList> newVarValues;
    bool isDeployable;
    QStringList errors;
};
}
}
251

252
QmakePriFile::QmakePriFile(QmakeProjectManager::QmakePriFileNode *qmakePriFile)
253
    : IDocument(0), m_priFile(qmakePriFile)
254
{
255
    setId("Qmake.PriFile");
hjk's avatar
hjk committed
256
    setMimeType(QLatin1String(QmakeProjectManager::Constants::PROFILE_MIMETYPE));
257
    setFilePath(m_priFile->path());
258
259
}

260
bool QmakePriFile::save(QString *errorString, const QString &fileName, bool autoSave)
261
{
262
    Q_UNUSED(errorString);
263
    Q_UNUSED(fileName);
264
    Q_UNUSED(autoSave);
265
266
267
    return false;
}

268
QString QmakePriFile::defaultPath() const
269
{
270
    return QString();
271
272
}

273
QString QmakePriFile::suggestedFileName() const
274
{
275
    return QString();
276
277
}

278
bool QmakePriFile::isModified() const
279
280
281
282
{
    return false;
}

283
bool QmakePriFile::isSaveAsAllowed() const
284
285
286
287
{
    return false;
}

288
Core::IDocument::ReloadBehavior QmakePriFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
289
{
290
291
292
    Q_UNUSED(state)
    Q_UNUSED(type)
    return BehaviorSilent;
293
294
}

295
bool QmakePriFile::reload(QString *errorString, ReloadFlag flag, ChangeType type)
296
{
297
    Q_UNUSED(errorString)
298
299
    Q_UNUSED(flag)
    if (type == TypePermissions)
300
        return true;
301
    m_priFile->scheduleUpdate();
302
    return true;
303
}
304

con's avatar
con committed
305
/*!
306
  \class QmakePriFileNode
con's avatar
con committed
307
308
309
  Implements abstract ProjectNode class
  */

310
namespace QmakeProjectManager {
311

312
313
QmakePriFileNode::QmakePriFileNode(QmakeProject *project, QmakeProFileNode *qmakeProFileNode,
                                   const FileName &filePath)
con's avatar
con committed
314
315
        : ProjectNode(filePath),
          m_project(project),
316
          m_qmakeProFileNode(qmakeProFileNode),
317
          m_projectFilePath(filePath),
318
          m_projectDir(filePath.toFileInfo().absolutePath())
con's avatar
con committed
319
{
dt's avatar
dt committed
320
    Q_ASSERT(project);
321
322
    m_qmakePriFile = new QmakePriFile(this);
    Core::DocumentManager::addDocument(m_qmakePriFile);
323

324
    setDisplayName(filePath.toFileInfo().completeBaseName());
325
    setIcon(qmakeNodeStaticData()->projectIcon);
326
327
}

328
QmakePriFileNode::~QmakePriFileNode()
329
330
{
    watchFolders(QSet<QString>());
331
    delete m_qmakePriFile;
332
333
}

334
void QmakePriFileNode::scheduleUpdate()
335
{
336
    QtSupport::ProFileCacheManager::instance()->discardFile(m_projectFilePath.toString());
337
    m_qmakeProFileNode->scheduleUpdate(QmakeProFileNode::ParseLater);
con's avatar
con committed
338
339
}

Daniel Teske's avatar
Daniel Teske committed
340
namespace Internal {
341
struct InternalNode
342
{
343
344
    QList<InternalNode *> virtualfolders;
    QMap<QString, InternalNode *> subnodes;
345
    FileNameList files;
346
347
    FileType type = UnknownFileType;
    int priority = 0;
348
    QString displayName;
349
    QString typeName;
350
351
    QString fullPath;
    QIcon icon;
352

353
354
    ~InternalNode()
    {
355
        qDeleteAll(virtualfolders);
356
357
        qDeleteAll(subnodes);
    }
358

359
    // Creates: a tree structure from a list of absolute file paths.
360
361
362
363
364
365
366
367
368
369
    // Empty directories are compressed into a single entry with a longer path.
    // * project
    //    * /absolute/path
    //       * file1
    //    * relative
    //       * path1
    //          * file1
    //          * file2
    //       * path2
    //          * file1
370
    // The function first creates a tree that looks like the directory structure, i.e.
371
372
373
374
375
376
    //    * /
    //       * absolute
    //          * path
    // ...
    // and afterwards calls compress() which merges directory nodes with single children, i.e. to
    //    * /absolute/path
377
    void create(const QString &projectDir, const QSet<FileName> &newFilePaths, FileType type)
378
    {
379
        static const QChar separator = QLatin1Char('/');
380
381
382
        const FileName projectDirFileName = FileName::fromString(projectDir);
        foreach (const FileName &file, newFilePaths) {
            FileName fileWithoutPrefix;
383
            bool isRelative;
384
            if (file.isChildOf(projectDirFileName)) {
385
                isRelative = true;
386
                fileWithoutPrefix = file.relativeChildPath(projectDirFileName);
387
388
389
390
            } else {
                isRelative = false;
                fileWithoutPrefix = file;
            }
391
            QStringList parts = fileWithoutPrefix.toString().split(separator, QString::SkipEmptyParts);
392
            if (!HostOsInfo::isWindowsHost() && !isRelative && parts.count() > 0)
393
394
395
                parts[0].prepend(separator);
            QStringListIterator it(parts);
            InternalNode *currentNode = this;
396
            QString path = (isRelative ? (projectDirFileName.toString() + QLatin1Char('/')) : QString());
397
398
399
400
            while (it.hasNext()) {
                const QString &key = it.next();
                if (it.hasNext()) { // key is directory
                    path += key;
401
                    if (!currentNode->subnodes.contains(path)) {
402
403
404
                        InternalNode *val = new InternalNode;
                        val->type = type;
                        val->fullPath = path;
405
406
                        val->displayName = key;
                        currentNode->subnodes.insert(path, val);
407
408
                        currentNode = val;
                    } else {
409
                        currentNode = currentNode->subnodes.value(path);
410
                    }
411
412
                    path += separator;
                } else { // key is filename
413
                    currentNode->files.append(file);
414
415
416
                }
            }
        }
417
418
        this->compress();
    }
419

420
421
422
423
424
425
426
427
428
    // 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) {
429
                // replace i.value() by i.value()->subnodes.begin()
430
                QString key = i.value()->subnodes.begin().key();
431
                InternalNode *keep = i.value()->subnodes.value(key);
432
                keep->displayName = i.value()->displayName + QDir::separator() + keep->displayName;
433
                newSubnodes.insert(key, keep);
434
435
436
437
                i.value()->subnodes.clear();
                delete i.value();
            } else {
                newSubnodes.insert(i.key(), i.value());
438
439
            }
        }
440
441
        subnodes = newSubnodes;
    }
442

443
444
445
    FolderNode *createFolderNode(InternalNode *node)
    {
        FolderNode *newNode = 0;
446
447
448
449
450
451
        if (node->typeName.isEmpty()) {
            newNode = new FolderNode(FileName::fromString(node->fullPath));
        } else {
            newNode = new ProVirtualFolderNode(FileName::fromString(node->fullPath),
                                               node->priority, node->typeName);
        }
452
453
454
455
456
457
458

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

459
    // Makes the projectNode's subtree below the given folder match this internal node's subtree
460
    void updateSubFolders(FolderNode *folder)
461
    {
462
        if (type == ResourceType)
Daniel Teske's avatar
Daniel Teske committed
463
464
465
            updateResourceFiles(folder);
        else
            updateFiles(folder, type);
466

467
468
469
        // updateFolders
        QMultiMap<QString, FolderNode *> existingFolderNodes;
        foreach (FolderNode *node, folder->subFolderNodes())
470
            if (node->nodeType() != ProjectNodeType && !dynamic_cast<ResourceEditor::ResourceTopLevelNode *>(node))
471
                existingFolderNodes.insert(node->path().toString(), node);
472

473
474
475
476
477
        QList<FolderNode *> foldersToRemove;
        QList<FolderNode *> foldersToAdd;
        typedef QPair<InternalNode *, FolderNode *> NodePair;
        QList<NodePair> nodesToUpdate;

478
479
480
481
482
483
484
485
486
        // 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);
487
                while (oldit != existingFolderNodes.constEnd() && oldit.key() == path) {
488
489
                    if (oldit.value()->nodeType() == VirtualFolderNodeType) {
                        VirtualFolderNode *vfn = dynamic_cast<VirtualFolderNode *>(oldit.value());
490
491
492
493
494
495
496
497
498
499
500
501
502
503
                        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);
                }
504
            }
505
        }
506
507
508
509
510
511
512
513
514
515
        // 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);
516
                while (oldit != existingFolderNodes.constEnd() && oldit.key() == path) {
517
                    if (oldit.value()->nodeType() == FolderNodeType) {
518
519
520
521
522
523
524
525
526
527
528
529
530
                        found = true;
                        break;
                    }
                    ++oldit;
                }
                if (found) {
                    nodesToUpdate << NodePair(it.value(), *oldit);
                } else {
                    FolderNode *newNode = createFolderNode(it.value());
                    foldersToAdd << newNode;
                    nodesToUpdate << NodePair(it.value(), newNode);
                }
            }
531
        }
532

533
534
535
536
537
538
539
540
541
542
        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();

543
        if (!foldersToRemove.isEmpty())
544
            folder->removeFolderNodes(foldersToRemove);
545
        if (!foldersToAdd.isEmpty())
546
            folder->addFolderNodes(foldersToAdd);
547

548
        foreach (const NodePair &np, nodesToUpdate)
549
            np.first->updateSubFolders(np.second);
550
551
552
    }

    // Makes the folder's files match this internal node's file list
553
    void updateFiles(FolderNode *folder, FileType type)
554
555
556
557
558
    {
        QList<FileNode*> existingFileNodes;
        foreach (FileNode *fileNode, folder->fileNodes()) {
            if (fileNode->fileType() == type && !fileNode->isGenerated())
                existingFileNodes << fileNode;
559
560
        }

561
        QList<FileNode*> filesToRemove;
562
        FileNameList filesToAdd;
563
564

        SortByPath sortByPath;
565
566
        Utils::sort(files, sortByPath);
        Utils::sort(existingFileNodes, sortByPath);
567
568
569
570

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

        QList<FileNode *> nodesToAdd;
571
        foreach (const FileName &file, filesToAdd)
572
            nodesToAdd << new FileNode(file, type, false);
573

574
575
        folder->removeFileNodes(filesToRemove);
        folder->addFileNodes(nodesToAdd);
576
    }
Daniel Teske's avatar
Daniel Teske committed
577
578
579
580

    // Makes the folder's files match this internal node's file list
    void updateResourceFiles(FolderNode *folder)
    {
581
        QList<FolderNode *> existingResourceNodes; // for resource special handling
Daniel Teske's avatar
Daniel Teske committed
582
        foreach (FolderNode *folderNode, folder->subFolderNodes()) {
583
            if (ResourceEditor::ResourceTopLevelNode *rn = dynamic_cast<ResourceEditor::ResourceTopLevelNode *>(folderNode))
Daniel Teske's avatar
Daniel Teske committed
584
585
586
                existingResourceNodes << rn;
        }

587
        QList<FolderNode *> resourcesToRemove;
588
        FileNameList resourcesToAdd;
Daniel Teske's avatar
Daniel Teske committed
589
590

        SortByPath sortByPath;
591
592
        Utils::sort(files, sortByPath);
        Utils::sort(existingResourceNodes, sortByPath);
Daniel Teske's avatar
Daniel Teske committed
593
594
595
596
597
598

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

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

599
        foreach (const FileName &file, resourcesToAdd)
Daniel Teske's avatar
Daniel Teske committed
600
601
602
603
604
605
            nodesToAdd.append(new ResourceEditor::ResourceTopLevelNode(file, folder));

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

        foreach (FolderNode *fn, nodesToAdd)
606
            dynamic_cast<ResourceEditor::ResourceTopLevelNode *>(fn)->update();
Daniel Teske's avatar
Daniel Teske committed
607
    }
608
};
Daniel Teske's avatar
Daniel Teske committed
609
}
dt's avatar
dt committed
610

611
QStringList QmakePriFileNode::baseVPaths(QtSupport::ProFileReader *reader, const QString &projectDir, const QString &buildDir)
con's avatar
con committed
612
{
dt's avatar
dt committed
613
614
615
    QStringList result;
    if (!reader)
        return result;
616
    result += reader->absolutePathValues(QLatin1String("VPATH"), projectDir);
dt's avatar
dt committed
617
    result << projectDir; // QMAKE_ABSOLUTE_SOURCE_PATH
618
    result << buildDir;
dt's avatar
dt committed
619
620
621
    result.removeDuplicates();
    return result;
}
con's avatar
con committed
622

623
QStringList QmakePriFileNode::fullVPaths(const QStringList &baseVPaths, QtSupport::ProFileReader *reader,
624
                                       const QString &qmakeVariable, const QString &projectDir)
dt's avatar
dt committed
625
626
627
628
{
    QStringList vPaths;
    if (!reader)
        return vPaths;
629
    vPaths = reader->absolutePathValues(QLatin1String("VPATH_") + qmakeVariable, projectDir);
dt's avatar
dt committed
630
631
632
633
634
    vPaths += baseVPaths;
    vPaths.removeDuplicates();
    return vPaths;
}

635
QSet<FileName> QmakePriFileNode::recursiveEnumerate(const QString &folder)
636
{
637
    QSet<FileName> result;
638
639
640
641
642
643
    QFileInfo fi(folder);
    if (fi.isDir()) {
        QDir dir(folder);
        dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);

        foreach (const QFileInfo &file, dir.entryInfoList()) {
644
            if (file.isDir() && !file.isSymLink())
645
                result += recursiveEnumerate(file.absoluteFilePath());
646
            else if (!Core::EditorManager::isAutoSaveFile(file.fileName()))
647
                result += FileName(file);
648
649
        }
    } else if (fi.exists()) {
650
        result << FileName(fi);
651
652
653
    }
    return result;
}
dt's avatar
dt committed
654

655
656
657
PriFileEvalResult QmakePriFileNode::extractValues(const EvalInput &input,
                                                  QVector<ProFile *> includeFilesExact,
                                                  QVector<ProFile *> includeFilesCumlative,
658
                                                  const QList<QList<VariableAndVPathInformation>> &variableAndVPathInformation)
dt's avatar
dt committed
659
{
660
    PriFileEvalResult result;
661

662
    // Figure out DEPLOYMENT and INSTALL folders
663
    QStringList dynamicVariables = dynamicVarNames(input.readerExact, input.readerCumulative, input.isQt5);
664
    foreach (ProFile *includeFileExact, includeFilesExact)
665
        foreach (const QString &dynamicVar, dynamicVariables) {
666
            result.folders += input.readerExact->values(dynamicVar, includeFileExact);
667
668
            // Ignore stuff from cumulative parse
            // we are recursively enumerating all the files from those folders
669
            // and add watchers for them, that's too dangerous if we get the folders
670
671
            // wrong and enumerate the whole project tree multiple times
        }
672
673


674
675
    for (int i=0; i < result.folders.size(); ++i) {
        const QFileInfo fi(result.folders.at(i));
676
        if (fi.isRelative())
677
            result.folders[i] = QDir::cleanPath(input.projectDir + QLatin1Char('/') + result.folders.at(i));
678
679
    }

680
    result.folders.removeDuplicates();
dt's avatar
dt committed
681

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

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

704
    const QVector<QmakeNodeStaticData::FileTypeData> &fileTypes = qmakeNodeStaticData()->fileTypeData;
con's avatar
con committed
705
    // update files
706
    QFileInfo tmpFi;
707
    for (int i = 0; i < fileTypes.size(); ++i) {
708
        FileType type = fileTypes.at(i).type;
709
        const QList<VariableAndVPathInformation> &qmakeVariables = variableAndVPathInformation.at(i);
710
        QSet<FileName> newFilePaths;
711
        foreach (const VariableAndVPathInformation &qmakeVariable, qmakeVariables) {
712
            foreach (ProFile *includeFileExact, includeFilesExact) {
713
                QStringList tmp = input.readerExact->absoluteFileValues(qmakeVariable.variable, input.projectDir, qmakeVariable.vPathsExact, includeFileExact);
714
715
716
                foreach (const QString &t, tmp) {
                    tmpFi.setFile(t);
                    if (tmpFi.isFile())
717
                        newFilePaths += FileName::fromString(t);
718
                }
719
            }
720
            foreach (ProFile *includeFileCumlative, includeFilesCumlative) {
721
                QStringList tmp = input.readerCumulative->absoluteFileValues(qmakeVariable.variable, input.projectDir, qmakeVariable.vPathsCumulative, includeFileCumlative);
722
723
724
                foreach (const QString &t, tmp) {
                    tmpFi.setFile(t);
                    if (tmpFi.isFile())
725
                        newFilePaths += FileName::fromString(t);
726
                }
727
            }
728
        }
con's avatar
con committed
729

730
731
        result.foundFiles[type] = newFilePaths;
        result.recursiveEnumerateFiles.subtract(newFilePaths);
dt's avatar
dt committed
732
733
    }

734

dt's avatar
dt committed
735
736
    for (int i = 0; i < fileTypes.size(); ++i) {
        FileType type = fileTypes.at(i).type;
737
        QSet<FileName> newFilePaths = filterFilesProVariables(type, result.foundFiles[type]);
738
739
740
741
742
743
744
745
746
747
748
749
        newFilePaths += filterFilesRecursiveEnumerata(type, result.recursiveEnumerateFiles);
        result.foundFiles[type] = newFilePaths;
    }


    return result;
}

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

752
753
754
755
756
757
758
    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;
759
        const QSet<FileName> &newFilePaths = result.foundFiles.value(type);
760
761
        // We only need to save this information if
        // we are watching folders
762
        if (!result.folders.isEmpty())
763
764
765
766
            m_files[type] = newFilePaths;
        else
            m_files[type].clear();

767
768
769
        if (!newFilePaths.isEmpty()) {
            InternalNode *subfolder = new InternalNode;
            subfolder->type = type;
770
            subfolder->icon = fileTypes.at(i).icon;
771
772
773
            subfolder->fullPath = m_projectDir;
            subfolder->typeName = fileTypes.at(i).typeName;
            subfolder->priority = -i;
774
            subfolder->displayName = fileTypes.at(i).typeName;
775
            contents.virtualfolders.append(subfolder);
776
            // create the hierarchy with subdirectories
777
            subfolder->create(m_projectDir, newFilePaths, type);
con's avatar
con committed
778
779
        }
    }
780

781
    contents.updateSubFolders(this);
782
783
}

784
void QmakePriFileNode::watchFolders(const QSet<QString> &folders)
785
786
787
788
789
790
791