settingsaccessor.cpp 115 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
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
****************************************************************************/
25

26
#include "settingsaccessor.h"
27

28
#include "abi.h"
29
#include "devicesupport/devicemanager.h"
30
#include "project.h"
31 32
#include "projectexplorer.h"
#include "projectexplorersettings.h"
33 34
#include "toolchain.h"
#include "toolchainmanager.h"
Tobias Hunger's avatar
Tobias Hunger committed
35 36
#include "kit.h"
#include "kitmanager.h"
37

dt's avatar
dt committed
38
#include <coreplugin/icore.h>
39
#include <utils/persistentsettings.h>
40
#include <utils/hostosinfo.h>
41
#include <utils/qtcassert.h>
42
#include <utils/qtcprocess.h>
43

44
#include <QApplication>
45
#include <QDir>
46

hjk's avatar
hjk committed
47 48
using namespace Utils;

49 50 51 52 53 54 55
namespace {
static QString generateSuffix(const QString &alt1, const QString &alt2)
{
    QString suffix = alt1;
    if (suffix.isEmpty())
        suffix = alt2;
    suffix.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_.-]")), QString(QLatin1Char('_'))); // replace fishy characters:
56
    if (!suffix.startsWith(QLatin1Char('.')))
57 58 59 60 61
        suffix.prepend(QLatin1Char('.'));
    return suffix;
}
} // end namespace

62 63 64
namespace ProjectExplorer {
namespace Internal {

65 66 67 68 69
// --------------------------------------------------------------------
// VersionUpgrader:
// --------------------------------------------------------------------
// Handles updating a QVariantMap from version() - 1 to version()
class VersionUpgrader
70 71
{
public:
72 73
    VersionUpgrader() { }
    virtual ~VersionUpgrader() { }
74

75 76
    virtual int version() const = 0;
    virtual QString backupExtension() const = 0;
77 78

    virtual QVariantMap upgrade(const QVariantMap &data) = 0;
79 80 81

protected:
    typedef QPair<QLatin1String,QLatin1String> Change;
82
    QVariantMap renameKeys(const QList<Change> &changes, QVariantMap map) const;
83 84
};

85
/*!
86 87
 * Performs a simple renaming of the listed keys in \a changes recursively on \a map.
 */
88
QVariantMap VersionUpgrader::renameKeys(const QList<Change> &changes, QVariantMap map) const
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
{
    foreach (const Change &change, changes) {
        QVariantMap::iterator oldSetting = map.find(change.first);
        if (oldSetting != map.end()) {
            map.insert(change.second, oldSetting.value());
            map.erase(oldSetting);
        }
    }

    QVariantMap::iterator i = map.begin();
    while (i != map.end()) {
        QVariant v = i.value();
        if (v.type() == QVariant::Map)
            i.value() = renameKeys(changes, v.toMap());

        ++i;
    }

    return map;
}

} // Internal
} // ProjectExplorer

113
using namespace ProjectExplorer;
hjk's avatar
hjk committed
114
using namespace ProjectExplorer::Internal;
115 116

namespace {
117

118
const char USER_STICKY_KEYS_KEY[] = "UserStickyKeys";
119
const char SHARED_SETTINGS[] = "SharedSettings";
120 121 122 123 124 125
const char ENVIRONMENT_ID_KEY[] = "EnvironmentId";
const char VERSION_KEY[] = "Version";
const char ORIGINAL_VERSION_KEY[] = "OriginalVersion";

// for compatibility with QtC 3.1 and older:
const char OBSOLETE_VERSION_KEY[] = "ProjectExplorer.Project.Updater.FileVersion";
126

Tobias Hunger's avatar
Tobias Hunger committed
127 128 129
// Version 1 is used in master post Qt Creator 1.3.x.
// It was never used in any official release but is required for the
// transition to later versions (which introduce support for targets).
130
class UserFileVersion1Upgrader : public VersionUpgrader
Tobias Hunger's avatar
Tobias Hunger committed
131 132
{
public:
133
    UserFileVersion1Upgrader(UserFileAccessor *a) : m_accessor(a) { }
134 135
    int version() const { return 1; }
    QString backupExtension() const { return QLatin1String("1.3+git"); }
136
    QVariantMap upgrade(const QVariantMap &map);
Tobias Hunger's avatar
Tobias Hunger committed
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155

private:
    struct TargetDescription
    {
        TargetDescription(QString tid, QString dn) :
            id(tid),
            displayName(dn)
        {
        }

        TargetDescription(const TargetDescription &td) :
            id(td.id),
            displayName(td.displayName)
        {
        }

        QString id;
        QString displayName;
    };
156

157
    UserFileAccessor *m_accessor;
Tobias Hunger's avatar
Tobias Hunger committed
158 159
};

160
// Version 2 is used in master post Qt Creator 2.0 alpha.
161
class UserFileVersion2Upgrader : public VersionUpgrader
162 163
{
public:
164 165
    int version() const { return 2; }
    QString backupExtension() const { return QLatin1String("2.0-alpha+git"); }
166
    QVariantMap upgrade(const QVariantMap &map);
167 168
};

169
// Version 3 reflect the move of symbian signing from run to build step.
170
class UserFileVersion3Upgrader : public VersionUpgrader
171 172
{
public:
173 174
    int version() const { return 3; }
    QString backupExtension() const { return QLatin1String("2.0-alpha2+git"); }
175
    QVariantMap upgrade(const QVariantMap &map);
176 177
};

Tobias Hunger's avatar
Tobias Hunger committed
178
// Version 4 reflects the introduction of deploy steps
179
class UserFileVersion4Upgrader : public VersionUpgrader
Tobias Hunger's avatar
Tobias Hunger committed
180 181
{
public:
182 183
    int version() const { return 4; }
    QString backupExtension() const { return QLatin1String("2.1pre1"); }
184
    QVariantMap upgrade(const QVariantMap &map);
Tobias Hunger's avatar
Tobias Hunger committed
185 186
};

187
// Version 5 reflects the introduction of new deploy steps for Symbian/Maemo
188
class UserFileVersion5Upgrader : public VersionUpgrader
189 190
{
public:
191 192
    int version() const { return 5; }
    QString backupExtension() const { return QLatin1String("2.1pre2"); }
193
    QVariantMap upgrade(const QVariantMap &map);
194 195
};

196
// Version 6 reflects the introduction of new deploy steps for Symbian/Maemo
197
class UserFileVersion6Upgrader : public VersionUpgrader
Tobias Hunger's avatar
Tobias Hunger committed
198 199
{
public:
200 201
    int version() const { return 6; }
    QString backupExtension() const { return QLatin1String("2.1pre3"); }
202
    QVariantMap upgrade(const QVariantMap &map);
Tobias Hunger's avatar
Tobias Hunger committed
203 204
};

205
// Version 7 reflects the introduction of new deploy configuration for Symbian
206
class UserFileVersion7Upgrader : public VersionUpgrader
207 208
{
public:
209 210
    int version() const { return 7; }
    QString backupExtension() const { return QLatin1String("2.1pre4"); }
211
    QVariantMap upgrade(const QVariantMap &map);
212 213
};

214 215 216
// Version 8 reflects the change of environment variable expansion rules,
// turning some env variables into expandos, the change of argument quoting rules,
// and the change of VariableManager's expansion syntax.
217
class UserFileVersion8Upgrader : public VersionUpgrader
218 219
{
public:
220 221
    int version() const { return 8; }
    QString backupExtension() const {
222 223 224
        // pre5 because we renamed 2.2 to 2.1 later, so people already have 2.2pre4 files
        return QLatin1String("2.2pre5");
    }
225
    QVariantMap upgrade(const QVariantMap &map);
226 227
};

228
// Version 9 reflects the refactoring of the Maemo deploy step.
229
class UserFileVersion9Upgrader : public VersionUpgrader
230 231
{
public:
232 233
    int version() const { return 9; }
    QString backupExtension() const { return QLatin1String("2.3pre1"); }
234
    QVariantMap upgrade(const QVariantMap &map);
235 236
};

Daniel Teske's avatar
Daniel Teske committed
237
// Version 10 introduces disabling buildsteps, and handles upgrading custom process steps
238
class UserFileVersion10Upgrader : public VersionUpgrader
Daniel Teske's avatar
Daniel Teske committed
239 240
{
public:
241 242
    int version() const { return 10; }
    QString backupExtension() const { return QLatin1String("2.5pre1"); }
243
    QVariantMap upgrade(const QVariantMap &map);
Daniel Teske's avatar
Daniel Teske committed
244
};
245

246
// Version 11 introduces kits
247
class UserFileVersion11Upgrader : public VersionUpgrader
Tobias Hunger's avatar
Tobias Hunger committed
248 249
{
public:
250
    UserFileVersion11Upgrader(UserFileAccessor *a) : m_accessor(a) { }
251
    ~UserFileVersion11Upgrader();
Tobias Hunger's avatar
Tobias Hunger committed
252

253 254
    int version() const { return 11; }
    QString backupExtension() const { return QLatin1String("2.6pre1"); }
255
    QVariantMap upgrade(const QVariantMap &map);
Tobias Hunger's avatar
Tobias Hunger committed
256 257

private:
258 259 260 261
    Kit *uniqueKit(Kit *k);
    void addBuildConfiguration(Kit *k, const QVariantMap &bc, int bcPos, int bcCount);
    void addDeployConfiguration(Kit *k, const QVariantMap &dc, int dcPos, int dcActive);
    void addRunConfigurations(Kit *k,
262
                              const QMap<int, QVariantMap> &rcs, int activeRc, const QString &projectDir);
Tobias Hunger's avatar
Tobias Hunger committed
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279

    void parseQtversionFile();
    void parseToolChainFile();

    class ToolChainExtraData {
    public:
        explicit ToolChainExtraData(const QString &mks = QString(), const QString &d = QString()) :
            m_mkspec(mks), m_debugger(d)
        { }

        QString m_mkspec;
        QString m_debugger;
    };

    QHash<QString, ToolChainExtraData> m_toolChainExtras;
    QHash<int, QString> m_qtVersionExtras;

Tobias Hunger's avatar
Tobias Hunger committed
280
    QHash<Kit *, QVariantMap> m_targets;
281
    UserFileAccessor *m_accessor;
Tobias Hunger's avatar
Tobias Hunger committed
282 283
};

284 285
// Version 12 reflects the move of environment settings from CMake/Qt4/Custom into
// LocalApplicationRunConfiguration
286
class UserFileVersion12Upgrader : public VersionUpgrader
287 288
{
public:
289 290
    int version() const { return 12; }
    QString backupExtension() const { return QLatin1String("2.7pre1"); }
291
    QVariantMap upgrade(const QVariantMap &map);
292 293
};

294 295
// Version 13 reflects the move of environment settings from LocalApplicationRunConfiguration
// into the EnvironmentAspect
296
class UserFileVersion13Upgrader : public VersionUpgrader
297 298
{
public:
299 300
    int version() const { return 13; }
    QString backupExtension() const { return QLatin1String("2.8"); }
301
    QVariantMap upgrade(const QVariantMap &map);
302 303
};

304
// Version 14 Move builddir into BuildConfiguration
305
class UserFileVersion14Upgrader : public VersionUpgrader
306 307
{
public:
308 309
    int version() const { return 14; }
    QString backupExtension() const { return QLatin1String("3.0-pre1"); }
310
    QVariantMap upgrade(const QVariantMap &map);
311 312
};

313 314 315 316 317 318 319 320
// Version 15 Use settingsaccessor based class for user file reading/writing
class UserFileVersion15Upgrader : public VersionUpgrader
{
public:
    int version() const { return 15; }
    QString backupExtension() const { return QLatin1String("3.2-pre1"); }
    QVariantMap upgrade(const QVariantMap &map);
};
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351

// Version 16 Changed android deployment
class UserFileVersion16Upgrader : public VersionUpgrader
{
public:
    int version() const { return 16; }
    QString backupExtension() const { return QLatin1String("3.3-pre1"); }
    QVariantMap upgrade(const QVariantMap &data);
private:
    class OldStepMaps
    {
    public:
        QString defaultDisplayName;
        QString displayName;
        QVariantMap androidPackageInstall;
        QVariantMap androidDeployQt;
        bool isEmpty()
        {
            return androidPackageInstall.isEmpty() || androidDeployQt.isEmpty();
        }
    };


    QVariantMap removeAndroidPackageStep(QVariantMap deployMap);
    OldStepMaps extractStepMaps(const QVariantMap &deployMap);
    enum NamePolicy { KeepName, RenameBuildConfiguration };
    QVariantMap insertSteps(QVariantMap buildConfigurationMap,
                            const OldStepMaps &oldStepMap,
                            NamePolicy policy);
};

352 353 354 355 356 357 358 359 360 361 362 363 364 365
// Version 17 Apply user sticky keys per map
class UserFileVersion17Upgrader : public VersionUpgrader
{
public:
    int version() const { return 17; }
    QString backupExtension() const { return QLatin1String("3.3-pre2"); }
    QVariantMap upgrade(const QVariantMap &map);

    QVariant process(const QVariant &entry);

private:
    QVariantList m_sticky;
};

366 367
} // namespace

Tobias Hunger's avatar
Tobias Hunger committed
368 369 370 371
//
// Helper functions:
//

372 373
QT_BEGIN_NAMESPACE

hjk's avatar
hjk committed
374 375
class HandlerNode
{
376 377 378 379
public:
    QSet<QString> strings;
    QHash<QString, HandlerNode> children;
};
hjk's avatar
hjk committed
380

381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
Q_DECLARE_TYPEINFO(HandlerNode, Q_MOVABLE_TYPE);

QT_END_NAMESPACE

static HandlerNode buildHandlerNodes(const char * const **keys)
{
    HandlerNode ret;
    while (const char *rname = *(*keys)++) {
        QString name = QLatin1String(rname);
        if (name.endsWith(QLatin1Char('.'))) {
            HandlerNode sub = buildHandlerNodes(keys);
            foreach (const QString &key, name.split(QLatin1Char('|')))
                ret.children.insert(key, sub);
        } else {
            ret.strings.insert(name);
        }
    }
    return ret;
}

static QVariantMap processHandlerNodes(const HandlerNode &node, const QVariantMap &map,
                                       QVariant (*handler)(const QVariant &var))
{
    QVariantMap result;
    QMapIterator<QString, QVariant> it(map);
    while (it.hasNext()) {
        it.next();
        const QString &key = it.key();
        if (node.strings.contains(key)) {
            result.insert(key, handler(it.value()));
            goto handled;
        }
        if (it.value().type() == QVariant::Map)
            for (QHash<QString, HandlerNode>::ConstIterator subit = node.children.constBegin();
                 subit != node.children.constEnd(); ++subit)
                if (key.startsWith(subit.key())) {
                    result.insert(key, processHandlerNodes(subit.value(), it.value().toMap(), handler));
                    goto handled;
                }
        result.insert(key, it.value());
      handled: ;
    }
    return result;
}
425

426 427 428 429 430
// --------------------------------------------------------------------
// UserFileAccessor:
// --------------------------------------------------------------------
UserFileAccessor::UserFileAccessor(Project *project)
    : SettingsAccessor(project)
431
{
432
    // Register Upgraders:
433
    addVersionUpgrader(new UserFileVersion1Upgrader(this));
434 435 436 437 438 439 440 441 442
    addVersionUpgrader(new UserFileVersion2Upgrader);
    addVersionUpgrader(new UserFileVersion3Upgrader);
    addVersionUpgrader(new UserFileVersion4Upgrader);
    addVersionUpgrader(new UserFileVersion5Upgrader);
    addVersionUpgrader(new UserFileVersion6Upgrader);
    addVersionUpgrader(new UserFileVersion7Upgrader);
    addVersionUpgrader(new UserFileVersion8Upgrader);
    addVersionUpgrader(new UserFileVersion9Upgrader);
    addVersionUpgrader(new UserFileVersion10Upgrader);
443
    addVersionUpgrader(new UserFileVersion11Upgrader(this));
444 445 446
    addVersionUpgrader(new UserFileVersion12Upgrader);
    addVersionUpgrader(new UserFileVersion13Upgrader);
    addVersionUpgrader(new UserFileVersion14Upgrader);
447
    addVersionUpgrader(new UserFileVersion15Upgrader);
448
    addVersionUpgrader(new UserFileVersion16Upgrader);
449
    addVersionUpgrader(new UserFileVersion17Upgrader);
450 451 452 453 454 455 456 457
}

QVariantMap UserFileAccessor::prepareSettings(const QVariantMap &data) const
{
    // Move from old Version field to new one:
    // This can not be done in a normal upgrader since the version information is needed
    // to decide which upgraders to run
    QVariantMap result = SettingsAccessor::prepareSettings(data);
Daniel Teske's avatar
Daniel Teske committed
458 459 460 461
    const QString obsoleteKey = QLatin1String(OBSOLETE_VERSION_KEY);
    if (data.contains(obsoleteKey)) {
        result = setVersionInMap(result, data.value(obsoleteKey, versionFromMap(data)).toInt());
        result.remove(obsoleteKey);
462 463
    }
    return result;
464 465
}

466 467 468 469 470 471 472 473 474
QVariantMap UserFileAccessor::prepareToSaveSettings(const QVariantMap &data) const
{
    QVariantMap tmp = SettingsAccessor::prepareToSaveSettings(data);

    // for compatibility with QtC 3.1 and older:
    tmp.insert(QLatin1String(OBSOLETE_VERSION_KEY), currentVersion());
    return tmp;
}

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
namespace ProjectExplorer {
// --------------------------------------------------------------------
// SettingsAccessorPrivate:
// --------------------------------------------------------------------
class SettingsAccessorPrivate
{
public:
    SettingsAccessorPrivate() :
        m_writer(0)
    { }

    ~SettingsAccessorPrivate()
    {
        qDeleteAll(m_upgraders);
        delete m_writer;
    }

492 493 494 495 496 497
    // The relevant data from the settings currently in use.
    class Settings
    {
    public:
        bool isValid() const;

498
        QVariantMap map;
499
        FileName path;
500 501
    };

502 503 504 505 506 507 508 509 510 511
    int firstVersion() const { return m_upgraders.isEmpty() ? -1 : m_upgraders.first()->version(); }
    int lastVersion() const { return m_upgraders.isEmpty() ? -1 : m_upgraders.last()->version(); }
    int currentVersion() const { return lastVersion() + 1; }
    VersionUpgrader *upgrader(const int version) const
    {
        int pos = version - firstVersion();
        if (pos >= 0 && pos < m_upgraders.count())
            return m_upgraders.at(pos);
        return 0;
    }
512
    Settings bestSettings(const SettingsAccessor *accessor, const FileNameList &pathList);
513

514
    QList<VersionUpgrader *> m_upgraders;
515
    PersistentSettingsWriter *m_writer;
516
};
517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574

// Return path to shared directory for .user files, create if necessary.
static inline QString determineSharedUserFileDir()
{
    const char userFilePathVariable[] = "QTC_USER_FILE_PATH";
    if (!qEnvironmentVariableIsSet(userFilePathVariable))
        return QString();
    const QFileInfo fi(QFile::decodeName(qgetenv(userFilePathVariable)));
    const QString path = fi.absoluteFilePath();
    if (fi.isDir() || fi.isSymLink())
        return path;
    if (fi.exists()) {
        qWarning() << userFilePathVariable << '=' << QDir::toNativeSeparators(path)
            << " points to an existing file";
        return QString();
    }
    QDir dir;
    if (!dir.mkpath(path)) {
        qWarning() << "Cannot create: " << QDir::toNativeSeparators(path);
        return QString();
    }
    return path;
}

static QString sharedUserFileDir()
{
    static const QString sharedDir = determineSharedUserFileDir();
    return sharedDir;
}

// Return a suitable relative path to be created under the shared .user directory.
static QString makeRelative(QString path)
{
    const QChar slash(QLatin1Char('/'));
    // Windows network shares: "//server.domain-a.com/foo' -> 'serverdomainacom/foo'
    if (path.startsWith(QLatin1String("//"))) {
        path.remove(0, 2);
        const int nextSlash = path.indexOf(slash);
        if (nextSlash > 0) {
            for (int p = nextSlash; p >= 0; --p) {
                if (!path.at(p).isLetterOrNumber())
                    path.remove(p, 1);
            }
        }
        return path;
    }
    // Windows drives: "C:/foo' -> 'c/foo'
    if (path.size() > 3 && path.at(1) == QLatin1Char(':')) {
        path.remove(1, 1);
        path[0] = path.at(0).toLower();
        return path;
    }
    if (path.startsWith(slash)) // Standard UNIX paths: '/foo' -> 'foo'
        path.remove(0, 1);
    return path;
}

// Return complete file path of the .user file.
575
static FileName userFilePath(const Project *project, const QString &suffix)
576
{
577 578
    FileName result;
    const FileName projectFilePath = project->projectFilePath();
579 580 581 582 583
    if (sharedUserFileDir().isEmpty()) {
        result = projectFilePath;
    } else {
        // Recreate the relative project file hierarchy under the shared directory.
        // PersistentSettingsWriter::write() takes care of creating the path.
584
        result = FileName::fromString(sharedUserFileDir());
585 586 587 588 589 590
        result.appendString(QLatin1Char('/') + makeRelative(projectFilePath.toString()));
    }
    result.appendString(suffix);
    return result;
}

591 592
} // end namespace

593
SettingsAccessor::SettingsAccessor(Project *project) :
594 595
    m_project(project),
    d(new SettingsAccessorPrivate)
596 597
{
    QTC_CHECK(m_project);
598 599
    m_userSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_EXTENSION")), QLatin1String(".user"));
    m_sharedSuffix = generateSuffix(QString::fromLocal8Bit(qgetenv("QTC_SHARED_EXTENSION")), QLatin1String(".shared"));
600 601
}

602
SettingsAccessor::~SettingsAccessor()
603
{
604
    delete d;
605 606
}

Tobias Hunger's avatar
Tobias Hunger committed
607 608
Project *SettingsAccessor::project() const
{ return m_project; }
609

610 611
namespace {

612 613 614 615 616 617 618 619 620 621 622 623 624
class Operation {
public:
    virtual ~Operation() { }

    virtual void apply(QVariantMap &userMap, const QString &key, const QVariant &sharedValue) = 0;

    void synchronize(QVariantMap &userMap, const QVariantMap &sharedMap)
    {
        QVariantMap::const_iterator it = sharedMap.begin();
        QVariantMap::const_iterator eit = sharedMap.end();

        for (; it != eit; ++it) {
            const QString &key = it.key();
625
            if (key == QLatin1String(VERSION_KEY) || key == QLatin1String(ENVIRONMENT_ID_KEY))
626
                continue;
627 628 629 630 631 632 633 634 635 636 637 638 639 640
            const QVariant &sharedValue = it.value();
            const QVariant &userValue = userMap.value(key);
            if (sharedValue.type() == QVariant::Map) {
                if (userValue.type() != QVariant::Map) {
                    // This should happen only if the user manually changed the file in such a way.
                    continue;
                }
                QVariantMap nestedUserMap = userValue.toMap();
                synchronize(nestedUserMap, sharedValue.toMap());
                userMap.insert(key, nestedUserMap);
                continue;
            }
            if (userMap.contains(key) && userValue != sharedValue) {
                apply(userMap, key, sharedValue);
641 642 643 644
                continue;
            }
        }
    }
645
};
646

647
class MergeSettingsOperation : public Operation
648
{
649 650
public:
    void apply(QVariantMap &userMap, const QString &key, const QVariant &sharedValue)
651
    {
652 653 654 655
        // Do not override bookkeeping settings:
        if (key == QLatin1String(ORIGINAL_VERSION_KEY) || key == QLatin1String(VERSION_KEY))
            return;
        if (!userMap.value(QLatin1String(USER_STICKY_KEYS_KEY)).toList().contains(key))
656
            userMap.insert(key, sharedValue);
657 658 659
    }
};

660 661 662 663 664 665

class TrackStickyness : public Operation
{
public:
    void apply(QVariantMap &userMap, const QString &key, const QVariant &)
    {
666 667 668 669
        const QString stickyKey = QLatin1String(USER_STICKY_KEYS_KEY);
        QVariantList sticky = userMap.value(stickyKey).toList();
        sticky.append(key);
        userMap.insert(stickyKey, sticky);
670 671 672 673 674
    }
};

} // namespace

675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698
int SettingsAccessor::versionFromMap(const QVariantMap &data)
{
    return data.value(QLatin1String(VERSION_KEY), -1).toInt();
}

int SettingsAccessor::originalVersionFromMap(const QVariantMap &data)
{
    return data.value(QLatin1String(ORIGINAL_VERSION_KEY), versionFromMap(data)).toInt();
}

QVariantMap SettingsAccessor::setOriginalVersionInMap(const QVariantMap &data, int version)
{
    QVariantMap result = data;
    result.insert(QLatin1String(ORIGINAL_VERSION_KEY), version);
    return result;
}

QVariantMap SettingsAccessor::setVersionInMap(const QVariantMap &data, int version)
{
    QVariantMap result = data;
    result.insert(QLatin1String(VERSION_KEY), version);
    return result;
}

699 700
/*!
 * Run directly after reading the \a data.
701 702 703 704
 *
 * This method is called right after reading the data before any attempt at interpreting the data
 * is made.
 *
705
 * Returns the prepared data.
706 707 708 709 710 711
 */
QVariantMap SettingsAccessor::prepareSettings(const QVariantMap &data) const
{
    return data;
}

712 713
/*!
 * Check which of two sets of data are a better match to load.
714 715 716 717 718
 *
 * This method is used to compare data extracted from two XML settings files.
 * It will never be called with a version too old or too new to be read by
 * the current instance of Qt Creator.
 *
719 720 721
 * Compares \a newData against \a origData.
 *
 * Returns \c true if \a newData is a better match than \a origData and \c false otherwise.
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
 */
bool SettingsAccessor::isBetterMatch(const QVariantMap &origData, const QVariantMap &newData) const
{
    if (origData.isEmpty())
        return true;

    int origVersion = versionFromMap(origData);
    QByteArray origEnv = environmentIdFromMap(origData);

    int newVersion = versionFromMap(newData);
    QByteArray newEnv = environmentIdFromMap(newData);

    if (origEnv != newEnv) {
        if (origEnv == creatorId())
            return false;
        if (newEnv == creatorId())
            return true;
    }

    return newVersion > origVersion;
}

744 745 746 747
/*!
 * Upgrade the settings in \a data to the version \a toVersion.
 *
 * Returns settings of the requested version.
748
 */
749
QVariantMap SettingsAccessor::upgradeSettings(const QVariantMap &data) const
750
{
751 752 753 754
    const int version = versionFromMap(data);

    if (data.isEmpty())
        return data;
755

756 757 758 759 760
    QVariantMap result;
    if (!data.contains(QLatin1String(ORIGINAL_VERSION_KEY)))
        result = setOriginalVersionInMap(data, version);
    else
        result = data;
761

762 763
    const int toVersion = currentVersion();
    if (version >= toVersion || version < d->firstVersion())
764
        return result;
765

766
    for (int i = version; i < toVersion; ++i) {
767 768
        VersionUpgrader *upgrader = d->upgrader(i);
        QTC_CHECK(upgrader && upgrader->version() == i);
769 770
        result = upgrader->upgrade(result);
        result = setVersionInMap(result, i + 1);
771 772
    }

773
    return result;
774 775
}

776
/*!
777 778 779 780 781 782 783 784 785
 * Find issues with the settings file and warn the user about them.
 *
 * \a data is the data from the settings file.
 * \a path is the full path to the settings used.
 * \a parent is the widget to be set as parent of any dialogs that are opened.
 *
 * Returns \c true if the settings are not usable anymore and \c false otherwise.
 */
SettingsAccessor::ProceedInfo SettingsAccessor::reportIssues(const QVariantMap &data,
786
                                                             const FileName &path,
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811
                                                             QWidget *parent) const
{
    IssueInfo issue = findIssues(data, path);
    QMessageBox::Icon icon = QMessageBox::Information;

    if (issue.buttons.count() > 1)
        icon = QMessageBox::Question;

    QMessageBox::StandardButtons buttons = QMessageBox::NoButton;
    foreach (QMessageBox::StandardButton b, issue.buttons.keys())
        buttons |= b;

    if (buttons == QMessageBox::NoButton)
        return Continue;

    QMessageBox msgBox(icon, issue.title, issue.message, buttons, parent);
    if (issue.defaultButton != QMessageBox::NoButton)
        msgBox.setDefaultButton(issue.defaultButton);
    if (issue.escapeButton != QMessageBox::NoButton)
        msgBox.setEscapeButton(issue.escapeButton);

    int boxAction = msgBox.exec();
    return issue.buttons.value(static_cast<QMessageBox::StandardButton>(boxAction));
}

812
/*!
813 814 815 816
 * Checks \a data located at \a path for issues to be displayed with reportIssues.
 *
 * Returns a IssueInfo object which is then used by reportIssues.
 */
817
SettingsAccessor::IssueInfo SettingsAccessor::findIssues(const QVariantMap &data, const FileName &path) const
818 819 820
{
    SettingsAccessor::IssueInfo result;

821
    const FileName defaultSettingsPath = userFilePath(project(), m_userSuffix);
822 823

    int version = versionFromMap(data);
hjk's avatar
hjk committed
824
    if (!path.exists()) {
825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
        return result;
    } else if (data.isEmpty() || version < firstSupportedVersion() || version > currentVersion()) {
        result.title = QApplication::translate("Utils::SettingsAccessor", "No Valid Settings Found");
        result.message = QApplication::translate("Utils::SettingsAccessor",
                                                 "<p>No valid settings file could be found.</p>"
                                                 "<p>All settings files found in directory \"%1\" "
                                                 "were either too new or too old to be read.</p>")
                .arg(path.toUserOutput());
        result.buttons.insert(QMessageBox::Ok, DiscardAndContinue);
    } else if ((path != defaultSettingsPath) && (version < currentVersion())) {
        result.title = QApplication::translate("Utils::SettingsAccessor", "Using Old Settings");
        result.message = QApplication::translate("Utils::SettingsAccessor",
                                                 "<p>The versioned backup \"%1\" of the settings "
                                                 "file is used, because the non-versioned file was "
                                                 "created by an incompatible version of Qt Creator.</p>"
                                                 "<p>Settings changes made since the last time this "
                                                 "version of Qt Creator was used are ignored, and "
                                                 "changes made now will <b>not</b> be propagated to "
                                                 "the newer version.</p>").arg(path.toUserOutput());
        result.buttons.insert(QMessageBox::Ok, Continue);
    }

    if (!result.buttons.isEmpty())
        return result;

    QByteArray readId = environmentIdFromMap(data);
    if (!readId.isEmpty() && readId != creatorId()) {
        result.title = differentEnvironmentMsg(project()->displayName());
        result.message = QApplication::translate("ProjectExplorer::EnvironmentIdAccessor",
                                                 "<p>No .user settings file created by this instance "
                                                 "of Qt Creator was found.</p>"
                                                 "<p>Did you work with this project on another machine or "
                                                 "using a different settings path before?</p>"
                                                 "<p>Do you still want to load the settings file \"%1\"?</p>")
                .arg(path.toUserOutput());
        result.defaultButton = QMessageBox::No;
        result.escapeButton = QMessageBox::No;
        result.buttons.insert(QMessageBox::Yes, SettingsAccessor::Continue);
        result.buttons.insert(QMessageBox::No, SettingsAccessor::DiscardAndContinue);
    }
    return result;
}

QString SettingsAccessor::differentEnvironmentMsg(const QString &projectName)
{
    return QApplication::translate("ProjectExplorer::EnvironmentIdAccessor",
                                   "Settings File for \"%1\" from a different Environment?")
            .arg(projectName);
}

875 876
namespace {

877 878 879 880 881
// When restoring settings...
//   We check whether a .shared file exists. If so, we compare the settings in this file with
//   corresponding ones in the .user file. Whenever we identify a corresponding setting which
//   has a different value and which is not marked as sticky, we merge the .shared value into
//   the .user value.
882
QVariantMap mergeSharedSettings(const QVariantMap &userMap, const QVariantMap &sharedMap)
883
{
884
    QVariantMap result = userMap;
885
    if (sharedMap.isEmpty())
886
        return result;
887 888
    if (userMap.isEmpty())
        return sharedMap;
889

890
    MergeSettingsOperation op;
891
    op.synchronize(result, sharedMap);
892
    return result;
893 894 895 896 897 898 899 900 901 902
}

// When saving settings...
//   If a .shared file was considered in the previous restoring step, we check whether for
//   any of the current .shared settings there's a .user one which is different. If so, this
//   means the user explicitly changed it and we mark this setting as sticky.
//   Note that settings are considered sticky only when they differ from the .shared ones.
//   Although this approach is more flexible than permanent/forever sticky settings, it has
//   the side-effect that if a particular value unintentionally becomes the same in both
//   the .user and .shared files, this setting will "unstick".
903
void trackUserStickySettings(QVariantMap &userMap, const QVariantMap &sharedMap)
904 905 906 907
{
    if (sharedMap.isEmpty())
        return;

908 909
    TrackStickyness op;
    op.synchronize(userMap, sharedMap);
910 911 912 913
}

} // Anonymous

914
QByteArray SettingsAccessor::environmentIdFromMap(const QVariantMap &data)
915 916 917
{
    return data.value(QLatin1String(ENVIRONMENT_ID_KEY)).toByteArray();
}
918

919
QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const
920
{
921
    if (d->lastVersion() < 0)
922 923
        return QVariantMap();

924 925 926
    QVariantMap userSettings = readUserSettings(parent);
    QVariantMap sharedSettings = readSharedSettings(parent);
    return mergeSettings(userSettings, sharedSettings);
927 928
}

929 930 931 932 933 934 935 936 937 938 939 940 941
QVariantMap SettingsAccessor::prepareToSaveSettings(const QVariantMap &data) const
{
    QVariantMap tmp = data;
    const QVariant &shared = m_project->property(SHARED_SETTINGS);
    if (shared.isValid())
        trackUserStickySettings(tmp, shared.toMap());

    tmp.insert(QLatin1String(VERSION_KEY), d->currentVersion());
    tmp.insert(QLatin1String(ENVIRONMENT_ID_KEY), SettingsAccessor::creatorId());

    return tmp;
}

942
bool SettingsAccessor::saveSettings(const QVariantMap &map, QWidget *parent) const
943
{
Tobias Hunger's avatar
Tobias Hunger committed
944
    if (map.isEmpty())
945 946
        return false;

947 948
    backupUserFile();

949
    QVariantMap data = prepareToSaveSettings(map);
950

951
    FileName path = FileName::fromString(defaultFileName(m_userSuffix));
952
    if (!d->m_writer || d->m_writer->fileName() != path) {
953
        delete d->m_writer;
954
        d->m_writer = new PersistentSettingsWriter(path, QLatin1String("QtCreatorProject"));
955 956 957
    }

    return d->m_writer->save(data, parent);
958 959
}

960 961 962 963 964
bool SettingsAccessor::addVersionUpgrader(VersionUpgrader *upgrader)
{
    QTC_ASSERT(upgrader, return false);
    int version = upgrader->version();
    QTC_ASSERT(version >= 0, return false);
965

966 967 968 969 970 971
    if (d->m_upgraders.isEmpty() || d->currentVersion() == version)
        d->m_upgraders.append(upgrader);
    else if (d->firstVersion() - 1 == version)
        d->m_upgraders.prepend(upgrader);
    else
        QTC_ASSERT(false, return false); // Upgrader was added out of sequence or twice
972

973
    return true;
974 975
}

976
/* Will always return the default name first (if applicable) */
977
FileNameList SettingsAccessor::settingsFiles(const QString &suffix) const
978
{
979
    FileNameList result;
980

981 982 983
    QFileInfoList list;
    const QFileInfo pfi = project()->projectFilePath().toFileInfo();
    const QStringList filter(pfi.fileName() + suffix + QLatin1Char('*'));
984

985 986 987
    if (!sharedUserFileDir().isEmpty()) {
        const QString sharedPath = sharedUserFileDir() + QLatin1Char('/')
            + makeRelative(pfi.absolutePath());
988
        list.append(QDir(sharedPath).entryInfoList(filter, QDir::Files | QDir::Hidden | QDir::System));
989
    }
990
    list.append(QDir(pfi.dir()).entryInfoList(filter, QDir::Files | QDir::Hidden | QDir::System));
991

992
    foreach (const QFileInfo &fi, list) {
993
        const FileName path = FileName::fromString(fi.absoluteFilePath());
994 995 996 997 998 999
        if (!result.contains(path)) {
            if (path.endsWith(suffix))
                result.prepend(path);
            else
                result.append(path);
        }
1000
    }
1001

1002 1003 1004
    return result;
}

1005
QByteArray SettingsAccessor::creatorId()
1006
{
1007
    return ProjectExplorerPlugin::projectExplorerSettings().environmentId.toByteArray();
1008 1009 1010 1011
}

QString SettingsAccessor::defaultFileName(const QString &suffix) const
{
1012
    return userFilePath(project(), suffix).toString();
1013 1014 1015 1016
}

int SettingsAccessor::currentVersion() const
{
1017 1018 1019 1020 1021 1022
    return d->currentVersion();
}

int SettingsAccessor::firstSupportedVersion() const
{
    return d->firstVersion();
1023 1024
}

1025
FileName SettingsAccessor::backupName(const QVariantMap &data) const
1026
{
1027 1028
    QString backupName = defaultFileName(m_userSuffix);
    const QByteArray oldEnvironmentId = environmentIdFromMap(data);
1029
    if (!oldEnvironmentId.isEmpty() && oldEnvironmentId != creatorId())
1030
        backupName += QLatin1Char('.') + QString::fromLatin1(oldEnvironmentId).mid(1, 7);
1031
    const int oldVersion = versionFromMap(data);
1032
    if (oldVersion != currentVersion()) {
1033 1034
        VersionUpgrader *upgrader = d->upgrader(oldVersion);
        if (upgrader)
1035
            backupName += QLatin1Char('.') + upgrader->backupExtension();
1036
        else
1037
            backupName += QLatin1Char('.') + QString::number(oldVersion);
1038
    }
1039
    return FileName::fromString(backupName);
1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
}

void SettingsAccessor::backupUserFile() const
{
    SettingsAccessorPrivate::Settings oldSettings;
    oldSettings.path = FileName::fromString(defaultFileName(m_userSuffix));
    oldSettings.map = readFile(oldSettings.path);
    if (oldSettings.map.isEmpty())
        return;

    // Do we need to do a backup?
    const QString origName = oldSettings.path.toString();
    QString backupFileName = backupName(oldSettings.map).toString();
    if (backupFileName != origName)
        QFile::copy(origName, backupFileName);
1055 1056
}

1057
QVariantMap SettingsAccessor::readUserSettings(QWidget *parent) const
1058
{
1059
    SettingsAccessorPrivate::Settings result;
1060
    FileNameList fileList = settingsFiles(m_userSuffix);
1061
    if (fileList.isEmpty()) // No settings found at all.
1062
        return result.map;
1063

1064
    result = d->bestSettings(this, fileList);
1065 1066 1067 1068 1069 1070
    if (result.path.isEmpty())
        result.path = project()->projectDirectory();

    ProceedInfo proceed = reportIssues(result.map, result.path, parent);
    if (proceed == DiscardAndContinue)
        return QVariantMap();
1071

1072
    return result.map;
1073 1074
}

1075
QVariantMap SettingsAccessor::readSharedSettings(QWidget *parent) const
1076
{
1077
    SettingsAccessorPrivate::Settings sharedSettings;
1078
    QString fn = project()->projectFilePath().toString() + m_sharedSuffix;
1079 1080
    sharedSettings.path = FileName::fromString(fn);
    sharedSettings.map = readFile(sharedSettings.path);
1081

1082
    if (versionFromMap(sharedSettings.map) > currentVersion()) {
1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097
        // The shared file version is newer than Creator... If we have valid user
        // settings we prompt the user whether we could try an *unsupported* update.
        // This makes sense since the merging operation will only replace shared settings
        // that perfectly match corresponding user ones. If we don't have valid user
        // settings to compare against, there's nothing we can do.

        QMessageBox msgBox(
                    QMessageBox::Question,
                    QApplication::translate("ProjectExplorer::SettingsAccessor",
                                            "Unsupported Shared Settings File"),
                    QApplication::translate("ProjectExplorer::SettingsAccessor",
                                            "The version of your .shared file is not "
                                            "supported by Qt Creator. "
                                            "Do you want to try loading it anyway?"),
                    QMessageBox::Yes | QMessageBox::No,
1098
                    parent);
1099 1100 1101
        msgBox.setDefaultButton(QMessageBox::No);
        msgBox.setEscapeButton(QMessageBox::No);
        if (msgBox.exec() == QMessageBox::No)
1102
            sharedSettings.map.clear();
1103
        else
1104
            sharedSettings.map = setVersionInMap(sharedSettings.map, currentVersion());
1105
    }
1106
    return sharedSettings.map;
1107 1108
}

1109
SettingsAccessorPrivate::Settings SettingsAccessorPrivate::bestSettings(const SettingsAccessor *accessor,
1110
                                                                        const FileNameList &pathList)
1111
{
1112
    Settings bestMatch;
1113
    foreach (const FileName &path, pathList) {
1114
        QVariantMap tmp = accessor->readFile(path);
1115

1116 1117
        int version = SettingsAccessor::versionFromMap(tmp);
        if (version < firstVersion() || version > currentVersion())
1118 1119
            continue;

1120 1121 1122
        if (accessor->isBetterMatch(bestMatch.map, tmp)) {
            bestMatch.path = path;
            bestMatch.map = tmp;
1123
        }
1124
    }
1125
    return bestMatch;
1126 1127
}

1128 1129
QVariantMap SettingsAccessor::mergeSettings(const QVariantMap &userMap,
                                            const QVariantMap &sharedMap) const
1130
{
1131 1132 1133 1134
    QVariantMap newUser = userMap;
    QVariantMap newShared = sharedMap;
    QVariantMap result;
    if (!newUser.isEmpty() && !newShared.isEmpty()) {
1135 1136
        newUser = upgradeSettings(newUser);
        newShared = upgradeSettings(newShared);
1137 1138 1139 1140 1141
        result = mergeSharedSettings(newUser, newShared);
    } else if (!sharedMap.isEmpty()) {
        result = sharedMap;
    } else if (!userMap.isEmpty()) {
        result = userMap;
1142 1143
    }

1144
    m_project->setProperty(SHARED_SETTINGS, newShared);
1145 1146

    // Update from the base version to Creator's version.
1147
    return upgradeSettings(result);
1148 1149
}

1150 1151 1152
// -------------------------------------------------------------------------
// SettingsData
// -------------------------------------------------------------------------
1153
bool SettingsAccessorPrivate::Settings::isValid() const
1154
{
1155
    return SettingsAccessor::versionFromMap(map) > -1 && !path.isEmpty();
1156 1157
}