diff --git a/src/libs/utils/settingsaccessor.cpp b/src/libs/utils/settingsaccessor.cpp index 0c41e20cbe1..ae828aff694 100644 --- a/src/libs/utils/settingsaccessor.cpp +++ b/src/libs/utils/settingsaccessor.cpp @@ -81,10 +81,7 @@ QVariantMap SettingsAccessor::restoreSettings(QWidget *parent) const { QTC_ASSERT(!m_baseFilePath.isEmpty(), return QVariantMap()); - const RestoreData result = readData(m_baseFilePath, parent); - - const ProceedInfo pi = result.hasIssue() ? reportIssues(result.issue.value(), result.path, parent) : ProceedInfo::Continue; - return pi == ProceedInfo::DiscardAndContinue ? QVariantMap() : result.data; + return restoreSettings(m_baseFilePath, parent); } /*! @@ -120,6 +117,15 @@ SettingsAccessor::writeData(const FileName &path, const QVariantMap &data, QWidg return writeFile(path, prepareToWriteSettings(data)); } +QVariantMap SettingsAccessor::restoreSettings(const FileName &settingsPath, QWidget *parent) const +{ + const RestoreData result = readData(settingsPath, parent); + + const ProceedInfo pi = result.hasIssue() ? reportIssues(result.issue.value(), result.path, parent) + : ProceedInfo::Continue; + return pi == ProceedInfo::DiscardAndContinue ? QVariantMap() : result.data; +} + /*! * Read a file at \a path from disk and extract the data into a RestoreData set. * @@ -422,7 +428,8 @@ QVariantMap VersionUpgrader::renameKeys(const QList &changes, QVariantMa * The UpgradingSettingsAccessor keeps version information in the settings file and will * upgrade the settings on load to the latest supported version (if possible). */ -UpgradingSettingsAccessor::UpgradingSettingsAccessor(const QString &displayName, +UpgradingSettingsAccessor::UpgradingSettingsAccessor(const QString &docType, + const QString &displayName, const QString &applicationDisplayName) : UpgradingSettingsAccessor(std::make_unique(this), docType, displayName, applicationDisplayName) diff --git a/src/libs/utils/settingsaccessor.h b/src/libs/utils/settingsaccessor.h index be64438ec95..36b2a257ae5 100644 --- a/src/libs/utils/settingsaccessor.h +++ b/src/libs/utils/settingsaccessor.h @@ -129,6 +129,7 @@ public: protected: // Report errors: + QVariantMap restoreSettings(const Utils::FileName &settingsPath, QWidget *parent) const; ProceedInfo reportIssues(const Issue &issue, const FileName &path, QWidget *parent) const; virtual QVariantMap preprocessReadSettings(const QVariantMap &data) const; @@ -234,7 +235,8 @@ class MergingSettingsAccessor; class QTCREATOR_UTILS_EXPORT UpgradingSettingsAccessor : public BackingUpSettingsAccessor { public: - UpgradingSettingsAccessor(const QString &displayName, const QString &applicationDisplayName); + UpgradingSettingsAccessor(const QString &docType, + const QString &displayName, const QString &applicationDisplayName); UpgradingSettingsAccessor(std::unique_ptr &&strategy, const QString &docType, const QString &displayName, const QString &appDisplayName); diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h index 5b7af57b5a9..b4d71451d09 100644 --- a/src/plugins/projectexplorer/projectexplorer.h +++ b/src/plugins/projectexplorer/projectexplorer.h @@ -231,8 +231,8 @@ private slots: void testDeviceManager(); - void testToolChainManager_data(); - void testToolChainManager(); + void testToolChainMerging_data(); + void testToolChainMerging(); void testUserFileAccessor_prepareToReadSettings(); void testUserFileAccessor_prepareToReadSettingsObsoleteVersion(); diff --git a/src/plugins/projectexplorer/projectexplorer.pro b/src/plugins/projectexplorer/projectexplorer.pro index 5c91002043a..8f6ddfcddf4 100644 --- a/src/plugins/projectexplorer/projectexplorer.pro +++ b/src/plugins/projectexplorer/projectexplorer.pro @@ -88,6 +88,7 @@ HEADERS += projectexplorer.h \ toolchainconfigwidget.h \ toolchainmanager.h \ toolchainoptionspage.h \ + toolchainsettingsaccessor.h \ gccparser.h \ projectexplorersettingspage.h \ baseprojectwizarddialog.h \ @@ -232,6 +233,7 @@ SOURCES += projectexplorer.cpp \ toolchainconfigwidget.cpp \ toolchainmanager.cpp \ toolchainoptionspage.cpp \ + toolchainsettingsaccessor.cpp \ gccparser.cpp \ projectexplorersettingspage.cpp \ baseprojectwizarddialog.cpp \ diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs index 884444a52e7..046d42de8ab 100644 --- a/src/plugins/projectexplorer/projectexplorer.qbs +++ b/src/plugins/projectexplorer/projectexplorer.qbs @@ -144,6 +144,7 @@ Project { "toolchainconfigwidget.cpp", "toolchainconfigwidget.h", "toolchainmanager.cpp", "toolchainmanager.h", "toolchainoptionspage.cpp", "toolchainoptionspage.h", + "toolchainsettingsaccessor.cpp", "toolchainsettingsaccessor.h", "userfileaccessor.cpp", "userfileaccessor.h", "vcsannotatetaskhandler.cpp", "vcsannotatetaskhandler.h", "waitforstopdialog.cpp", "waitforstopdialog.h", diff --git a/src/plugins/projectexplorer/toolchain.h b/src/plugins/projectexplorer/toolchain.h index af8241b953d..2b5e4bec97e 100644 --- a/src/plugins/projectexplorer/toolchain.h +++ b/src/plugins/projectexplorer/toolchain.h @@ -63,10 +63,11 @@ class HeaderPath; class IOutputParser; class ToolChainConfigWidget; class ToolChainFactory; -class ToolChainManager; class Task; class Kit; +namespace Internal { class ToolChainSettingsAccessor; } + // -------------------------------------------------------------------------- // ToolChain (documentation inside) // -------------------------------------------------------------------------- @@ -169,7 +170,7 @@ private: Internal::ToolChainPrivate *const d; - friend class ToolChainManager; + friend class Internal::ToolChainSettingsAccessor; friend class ToolChainFactory; }; diff --git a/src/plugins/projectexplorer/toolchainmanager.cpp b/src/plugins/projectexplorer/toolchainmanager.cpp index 47c9aae4134..7bc3274f211 100644 --- a/src/plugins/projectexplorer/toolchainmanager.cpp +++ b/src/plugins/projectexplorer/toolchainmanager.cpp @@ -28,6 +28,7 @@ #include "abi.h" #include "kitinformation.h" #include "toolchain.h" +#include "toolchainsettingsaccessor.h" #include @@ -41,18 +42,8 @@ #include -static const char TOOLCHAIN_DATA_KEY[] = "ToolChain."; -static const char TOOLCHAIN_COUNT_KEY[] = "ToolChain.Count"; -static const char TOOLCHAIN_FILE_VERSION_KEY[] = "Version"; -static const char TOOLCHAIN_FILENAME[] = "/toolchains.xml"; - using namespace Utils; -static FileName settingsFileName(const QString &path) -{ - return FileName::fromString(Core::ICore::userResourcePath() + path); -} - namespace ProjectExplorer { namespace Internal { @@ -72,7 +63,7 @@ public: ~ToolChainManagerPrivate(); QMap m_abiToDebugger; - PersistentSettingsWriter *m_writer = nullptr; + std::unique_ptr m_accessor; QList m_toolChains; // prioritized List QVector m_languages; @@ -82,7 +73,6 @@ ToolChainManagerPrivate::~ToolChainManagerPrivate() { qDeleteAll(m_toolChains); m_toolChains.clear(); - delete m_writer; } static ToolChainManager *m_instance = nullptr; @@ -122,215 +112,22 @@ ToolChainManager *ToolChainManager::instance() return m_instance; } -static QList restoreFromFile(const FileName &fileName) -{ - QList result; - - PersistentSettingsReader reader; - if (!reader.load(fileName)) - return result; - QVariantMap data = reader.restoreValues(); - - // Check version: - int version = data.value(QLatin1String(TOOLCHAIN_FILE_VERSION_KEY), 0).toInt(); - if (version < 1) - return result; - - const QList factories = ToolChainFactory::allToolChainFactories(); - - int count = data.value(QLatin1String(TOOLCHAIN_COUNT_KEY), 0).toInt(); - for (int i = 0; i < count; ++i) { - const QString key = QString::fromLatin1(TOOLCHAIN_DATA_KEY) + QString::number(i); - if (!data.contains(key)) - break; - - const QVariantMap tcMap = data.value(key).toMap(); - - bool restored = false; - for (ToolChainFactory *f : factories) { - if (f->canRestore(tcMap)) { - if (ToolChain *tc = f->restore(tcMap)) { - result.append(tc); - restored = true; - break; - } - } - } - if (!restored) - qWarning("Warning: '%s': Unable to restore compiler type '%s' for tool chain %s.", - qPrintable(fileName.toUserOutput()), - qPrintable(ToolChainFactory::typeIdFromMap(tcMap).toString()), - qPrintable(QString::fromUtf8(ToolChainFactory::idFromMap(tcMap)))); - } - - return result; -} - -static QList autoDetectToolChains(const QList alreadyKnownTcs) -{ - QList result; - for (ToolChainFactory *f : ToolChainFactory::allToolChainFactories()) - result.append(f->autoDetect(alreadyKnownTcs)); - - // Remove invalid toolchains that might have sneaked in. - result = Utils::filtered(result, [](const ToolChain *tc) { return tc->isValid(); }); - - return result; -} - -static QList subtractByEqual(const QList &a, const QList &b) -{ - return Utils::filtered(a, [&b](ToolChain *atc) { - return !Utils::anyOf(b, [atc](ToolChain *btc) { return *atc == *btc; }); - }); -} - -static QList subtractByPointerEqual(const QList &a, const QList &b) -{ - return Utils::filtered(a, [&b](ToolChain *atc) { return !b.contains(atc); }); -} - -static QList subtractById(const QList &a, const QList &b) -{ - return Utils::filtered(a, [&b](ToolChain *atc) { - return !Utils::anyOf(b, Utils::equal(&ToolChain::id, atc->id())); - }); -} - -static bool containsByEqual(const QList &a, const ToolChain *atc) -{ - return Utils::anyOf(a, [atc](ToolChain *btc) { return *atc == *btc; }); -} - -static QList makeUniqueByPointerEqual(const QList &a) -{ - return QSet::fromList(a).toList(); -} - -static QList makeUniqueByEqual(const QList &a) -{ - QList result; - foreach (ToolChain *tc, a) { - if (!Utils::contains(result, [tc](ToolChain *rtc) { return *tc == *rtc; })) - result.append(tc); - } - return result; -} - -namespace { - -struct ToolChainOperations -{ - QList toDemote; - QList toRegister; - QList toDelete; -}; - -} // namespace - -static ToolChainOperations mergeToolChainLists(const QList &systemFileTcs, - const QList &userFileTcs, - const QList &autodetectedTcs) -{ - const QList uniqueUserFileTcs = makeUniqueByEqual(userFileTcs); - QList manualUserFileTcs; - QList autodetectedUserFileTcs; - std::tie(autodetectedUserFileTcs, manualUserFileTcs) - = Utils::partition(uniqueUserFileTcs, &ToolChain::isAutoDetected); - const QList autodetectedUserTcs = subtractById(autodetectedUserFileTcs, systemFileTcs); - - // Calculate a set of Tcs that were detected before (and saved to userFile) and that - // got re-detected again. Take the userTcs (to keep Ids) over the same in autodetectedTcs. - QList redetectedUserTcs; - QList notRedetectedUserTcs; - std::tie(redetectedUserTcs, notRedetectedUserTcs) - = Utils::partition(autodetectedUserTcs, - [&autodetectedTcs](ToolChain *tc) { return containsByEqual(autodetectedTcs, tc); }); - - // Remove redetected tcs from autodetectedTcs: - const QList newlyAutodetectedTcs - = subtractByEqual(autodetectedTcs, redetectedUserTcs); - - const QList notRedetectedButValidUserTcs - = Utils::filtered(notRedetectedUserTcs, &ToolChain::isValid); - - const QList validManualUserTcs - = Utils::filtered(manualUserFileTcs, &ToolChain::isValid); - - ToolChainOperations result; - result.toDemote = notRedetectedButValidUserTcs; - result.toRegister = systemFileTcs + validManualUserTcs + result.toDemote // manual TCs - + redetectedUserTcs + newlyAutodetectedTcs; // auto TCs - - result.toDelete = makeUniqueByPointerEqual(subtractByPointerEqual(systemFileTcs + userFileTcs + autodetectedTcs, - result.toRegister)); - return result; -} - void ToolChainManager::restoreToolChains() { - QTC_ASSERT(!d->m_writer, return); - d->m_writer = - new PersistentSettingsWriter(settingsFileName(QLatin1String(TOOLCHAIN_FILENAME)), - QLatin1String("QtCreatorToolChains")); + QTC_ASSERT(!d->m_accessor, return); + d->m_accessor = std::make_unique(); - // read all tool chains from SDK - const QList systemFileTcs = readSystemFileToolChains(); - - // read all tool chains from user file. - const QList userFileTcs - = restoreFromFile(settingsFileName(QLatin1String(TOOLCHAIN_FILENAME))); - - // Autodetect: Pass autodetected toolchains from user file so the information can be reused: - const QList autodetectedUserFileTcs - = Utils::filtered(userFileTcs, &ToolChain::isAutoDetected); - const QList autodetectedTcs = autoDetectToolChains(autodetectedUserFileTcs); - - // merge tool chains and register those that we need to keep: - ToolChainOperations ops = mergeToolChainLists(systemFileTcs, userFileTcs, autodetectedTcs); - - // Process ops: - foreach (ToolChain *tc, ops.toDemote) - tc->setDetection(ToolChain::ManualDetection); - - foreach (ToolChain *tc, ops.toRegister) + for (ToolChain *tc : d->m_accessor->restoreToolChains(Core::ICore::dialogParent())) registerToolChain(tc); - qDeleteAll(ops.toDelete); - emit m_instance->toolChainsLoaded(); } -QList ToolChainManager::readSystemFileToolChains() -{ - QList systemTcs = restoreFromFile( - FileName::fromString(Core::ICore::installerResourcePath() + TOOLCHAIN_FILENAME)); - - foreach (ToolChain *tc, systemTcs) - tc->setDetection(ToolChain::AutoDetection); - - return systemTcs; -} - void ToolChainManager::saveToolChains() { - QVariantMap data; - data.insert(QLatin1String(TOOLCHAIN_FILE_VERSION_KEY), 1); + QTC_ASSERT(d->m_accessor, return); - int count = 0; - foreach (ToolChain *tc, d->m_toolChains) { - if (tc->isValid()) { - QVariantMap tmp = tc->toMap(); - if (tmp.isEmpty()) - continue; - data.insert(QString::fromLatin1(TOOLCHAIN_DATA_KEY) + QString::number(count), tmp); - ++count; - } - } - data.insert(QLatin1String(TOOLCHAIN_COUNT_KEY), count); - d->m_writer->save(data, Core::ICore::mainWindow()); - - // Do not save default debuggers! Those are set by the SDK! + d->m_accessor->saveToolChains(d->m_toolChains, Core::ICore::dialogParent()); } QList ToolChainManager::toolChains(const ToolChain::Predicate &predicate) @@ -386,7 +183,7 @@ FileName ToolChainManager::defaultDebugger(const Abi &abi) bool ToolChainManager::isLoaded() { - return d->m_writer; + return bool(d->m_accessor); } void ToolChainManager::notifyAboutUpdate(ToolChain *tc) @@ -400,7 +197,7 @@ bool ToolChainManager::registerToolChain(ToolChain *tc) { QTC_ASSERT(tc, return false); QTC_ASSERT(isLanguageSupported(tc->language()), return false); - QTC_ASSERT(d->m_writer, return false); + QTC_ASSERT(d->m_accessor, return false); if (d->m_toolChains.contains(tc)) return true; @@ -454,202 +251,3 @@ bool ToolChainManager::isLanguageSupported(const Core::Id &id) } } // namespace ProjectExplorer - -#ifdef WITH_TESTS -#include "projectexplorer.h" - -#include "headerpath.h" - -#include -#include - -namespace ProjectExplorer { - -typedef QList TCList; - -class TTC : public ToolChain -{ -public: - TTC(ToolChain::Detection d, const QByteArray &t, bool v = true) : - ToolChain("TestToolChainType", d), - token(t), - m_valid(v) - { - m_toolChains.append(this); - setLanguage(Constants::CXX_LANGUAGE_ID); - } - - static QList toolChains(); - static bool hasToolChains() { return !m_toolChains.isEmpty(); } - - QString typeDisplayName() const override { return QLatin1String("Test Tool Chain"); } - Abi targetAbi() const override { return Abi::hostAbi(); } - bool isValid() const override { return m_valid; } - PredefinedMacrosRunner createPredefinedMacrosRunner() const override { return PredefinedMacrosRunner(); } - Macros predefinedMacros(const QStringList &cxxflags) const override { Q_UNUSED(cxxflags); return Macros(); } - CompilerFlags compilerFlags(const QStringList &cxxflags) const override { Q_UNUSED(cxxflags); return NoFlags; } - WarningFlags warningFlags(const QStringList &cflags) const override { Q_UNUSED(cflags); return WarningFlags::NoWarnings; } - SystemHeaderPathsRunner createSystemHeaderPathsRunner() const override { return SystemHeaderPathsRunner(); } - QList systemHeaderPaths(const QStringList &cxxflags, const FileName &sysRoot) const override - { Q_UNUSED(cxxflags); Q_UNUSED(sysRoot); return QList(); } - void addToEnvironment(Environment &env) const override { Q_UNUSED(env); } - QString makeCommand(const Environment &env) const override { Q_UNUSED(env); return QLatin1String("make"); } - FileName compilerCommand() const override { return Utils::FileName::fromString(QLatin1String("/tmp/test/gcc")); } - IOutputParser *outputParser() const override { return nullptr; } - ToolChainConfigWidget *configurationWidget() override { return nullptr; } - TTC *clone() const override { return new TTC(*this); } - bool operator ==(const ToolChain &other) const override { - if (!ToolChain::operator==(other)) - return false; - return static_cast(&other)->token == token; - } - - QByteArray token; - -private: - TTC(const TTC &other) : - ToolChain(other.typeId(), other.detection()), - token(other.token) - {} - - bool m_valid = false; - - static QList m_toolChains; - - // ToolChain interface -public: -}; - -QList TTC::m_toolChains; - -} // namespace ProjectExplorer - -Q_DECLARE_METATYPE(ProjectExplorer::ToolChain *) - -namespace ProjectExplorer { - -void ProjectExplorerPlugin::testToolChainManager_data() -{ - QTest::addColumn("system"); - QTest::addColumn("user"); - QTest::addColumn("autodetect"); - QTest::addColumn("toDemote"); - QTest::addColumn("toRegister"); - - TTC *system1 = nullptr; - TTC *system1c = nullptr; - TTC *system2 = nullptr; - TTC *system3i = nullptr; - TTC *user1 = nullptr; - TTC *user1c = nullptr; - TTC *user3i = nullptr; - TTC *user2 = nullptr; - TTC *auto1 = nullptr; - TTC *auto1c = nullptr; - TTC *auto1_2 = nullptr; - TTC *auto2 = nullptr; - TTC *auto3i = nullptr; - - if (!TTC::hasToolChains()) { - system1 = new TTC(ToolChain::AutoDetection, "system1"); Q_UNUSED(system1); - system1c = system1->clone(); Q_UNUSED(system1c); - system2 = new TTC(ToolChain::AutoDetection, "system2"); Q_UNUSED(system2); - system3i = new TTC(ToolChain::AutoDetection, "system3", false); Q_UNUSED(system3i); - user1 = new TTC(ToolChain::ManualDetection, "user1"); Q_UNUSED(user1); - user1c = user1->clone(); Q_UNUSED(user1c); - user2 = new TTC(ToolChain::ManualDetection, "user2"); Q_UNUSED(user2); - user3i = new TTC(ToolChain::ManualDetection, "user3", false); Q_UNUSED(user3i); - auto1 = new TTC(ToolChain::AutoDetectionFromSettings, "auto1"); Q_UNUSED(auto1); - auto1c = auto1->clone(); Q_UNUSED(auto1c); - auto1_2 = new TTC(ToolChain::AutoDetectionFromSettings, "auto1"); Q_UNUSED(auto1_2); - auto2 = new TTC(ToolChain::AutoDetectionFromSettings, "auto2"); Q_UNUSED(auto2); - auto3i = new TTC(ToolChain::AutoDetectionFromSettings, "auto3", false); Q_UNUSED(auto3i); - } - - QTest::newRow("no toolchains") - << (TCList()) << (TCList()) << (TCList()) - << (TCList()) << (TCList()); - - QTest::newRow("System: system, no user") - << (TCList() << system1) << (TCList()) << (TCList()) - << (TCList()) << (TCList() << system1); - QTest::newRow("System: system, user") - << (TCList() << system1) << (TCList() << system1) << (TCList()) - << (TCList()) << (TCList() << system1); - QTest::newRow("System: no system, user") // keep, the user tool chain as it is still found - << (TCList()) << (TCList() << system1) << (TCList()) - << (TCList() << system1) << (TCList() << system1); - QTest::newRow("System: no system, invalid user") - << (TCList()) << (TCList() << system3i) << (TCList()) - << (TCList()) << (TCList()); - - QTest::newRow("Auto: no auto, user") - << (TCList()) << (TCList() << auto1) << (TCList()) - << (TCList() << auto1) << (TCList() << auto1); - QTest::newRow("Auto: auto, no user") - << (TCList()) << (TCList()) << (TCList() << auto1) - << (TCList()) << (TCList() << auto1); - QTest::newRow("Auto: auto, user") - << (TCList()) << (TCList() << auto1) << (TCList() << auto1) - << (TCList()) << (TCList() << auto1); - QTest::newRow("Auto: auto-redetect, user") - << (TCList()) << (TCList() << auto1) << (TCList() << auto1_2) - << (TCList()) << (TCList() << auto1); - QTest::newRow("Auto: auto-redetect, duplicate users") - << (TCList()) << (TCList() << auto1 << auto1c) << (TCList() << auto1_2) - << (TCList()) << (TCList() << auto1); - QTest::newRow("Auto: (no) auto, invalid user") - << (TCList()) << (TCList() << auto3i) << (TCList()) - << (TCList()) << (TCList()); - - QTest::newRow("Delete invalid user") - << (TCList()) << (TCList() << user3i) << (TCList()) - << (TCList()) << (TCList()); - - QTest::newRow("one of everything") - << (TCList() << system1) << (TCList() << user1) << (TCList() << auto1) - << (TCList()) << (TCList() << system1 << user1 << auto1); -} - -void ProjectExplorerPlugin::testToolChainManager() -{ - QFETCH(TCList, system); - QFETCH(TCList, user); - QFETCH(TCList, autodetect); - QFETCH(TCList, toRegister); - QFETCH(TCList, toDemote); - - ToolChainOperations ops = mergeToolChainLists(system, user, autodetect); - - QSet expToRegister = QSet::fromList(toRegister); - QSet expToDemote = QSet::fromList(toDemote); - - QSet actToRegister = QSet::fromList(ops.toRegister); - QSet actToDemote = QSet::fromList(ops.toDemote); - QSet actToDelete = QSet::fromList(ops.toDelete); - - QCOMPARE(actToRegister.count(), ops.toRegister.count()); // no dups! - QCOMPARE(actToDemote.count(), ops.toDemote.count()); // no dups! - QCOMPARE(actToDelete.count(), ops.toDelete.count()); // no dups! - - QSet tmp = actToRegister; - tmp.intersect(actToDemote); - QCOMPARE(tmp, actToDemote); // all toDemote are in toRegister - - tmp = actToRegister; - tmp.intersect(actToDelete); - QVERIFY(tmp.isEmpty()); // Nothing that needs to be registered is to be deleted - - tmp = actToRegister; - tmp.unite(actToDelete); - QCOMPARE(tmp, QSet::fromList(system + user + autodetect)); // All input is accounted for - - QCOMPARE(expToRegister, actToRegister); - QCOMPARE(expToDemote, actToDemote); - QCOMPARE(QSet::fromList(system + user + autodetect), - QSet::fromList(ops.toRegister + ops.toDemote + ops.toDelete)); -} - -} // namespace ProjectExplorer - -#endif // WITH_TESTS diff --git a/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp b/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp new file mode 100644 index 00000000000..dba11138a87 --- /dev/null +++ b/src/plugins/projectexplorer/toolchainsettingsaccessor.cpp @@ -0,0 +1,477 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 +** 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. +** +** 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. +** +****************************************************************************/ + +#include "toolchainsettingsaccessor.h" + +#include "toolchain.h" + +#include + +#include + +#include + +using namespace Utils; + +namespace ProjectExplorer { +namespace Internal { + +// -------------------------------------------------------------------- +// ToolChainSettingsUpgraders: +// -------------------------------------------------------------------- + +class ToolChainSettingsUpgraderV0 : public Utils::VersionUpgrader +{ + // Necessary to make Version 1 supported. +public: + ToolChainSettingsUpgraderV0() : Utils::VersionUpgrader(0, "4.6") { } + + // NOOP + QVariantMap upgrade(const QVariantMap &data) final { return data; } +}; + +// -------------------------------------------------------------------- +// Helpers: +// -------------------------------------------------------------------- + +static const char TOOLCHAIN_DATA_KEY[] = "ToolChain."; +static const char TOOLCHAIN_COUNT_KEY[] = "ToolChain.Count"; +static const char TOOLCHAIN_FILENAME[] = "/toolchains.xml"; + +struct ToolChainOperations +{ + QList toDemote; + QList toRegister; + QList toDelete; +}; + +static QList autoDetectToolChains(const QList alreadyKnownTcs) +{ + QList result; + for (ToolChainFactory *f : ToolChainFactory::allToolChainFactories()) + result.append(f->autoDetect(alreadyKnownTcs)); + + // Remove invalid toolchains that might have sneaked in. + return Utils::filtered(result, [](const ToolChain *tc) { return tc->isValid(); }); +} + +static QList makeUniqueByEqual(const QList &a) +{ + QList result; + foreach (ToolChain *tc, a) { + if (!Utils::contains(result, [tc](ToolChain *rtc) { return *tc == *rtc; })) + result.append(tc); + } + return result; +} + +static QList makeUniqueByPointerEqual(const QList &a) +{ + return QSet::fromList(a).toList(); +} + +static QList subtractById(const QList &a, const QList &b) +{ + return Utils::filtered(a, [&b](ToolChain *atc) { + return !Utils::anyOf(b, Utils::equal(&ToolChain::id, atc->id())); + }); +} + +static bool containsByEqual(const QList &a, const ToolChain *atc) +{ + return Utils::anyOf(a, [atc](ToolChain *btc) { return *atc == *btc; }); +} + +static QList subtractByEqual(const QList &a, const QList &b) +{ + return Utils::filtered(a, [&b](ToolChain *atc) { + return !Utils::anyOf(b, [atc](ToolChain *btc) { return *atc == *btc; }); + }); +} + +static QList subtractByPointerEqual(const QList &a, const QList &b) +{ + return Utils::filtered(a, [&b](ToolChain *atc) { return !b.contains(atc); }); +} + +static QList stabilizeOrder(const QList &toRegister, + const QList &userFileTcs) +{ + // Keep the toolchains in their position in the user toolchain file to minimize diff! + QList result; + result.reserve(toRegister.size()); + QList toHandle = toRegister; + + for (int i = 0; i < userFileTcs.count(); ++i) { + const QByteArray userId = userFileTcs.at(i)->id(); + const int handlePos = Utils::indexOf(toHandle, + [&userId](const ToolChain *htc) { return htc->id() == userId; }); + if (handlePos < 0) + continue; + + result.append(toHandle.at(handlePos)); + toHandle.removeAt(handlePos); + } + result.append(toHandle); + return result; +} + +static ToolChainOperations mergeToolChainLists(const QList &systemFileTcs, + const QList &userFileTcs, + const QList &autodetectedTcs) +{ + const QList uniqueUserFileTcs = makeUniqueByEqual(userFileTcs); + QList manualUserFileTcs; + QList autodetectedUserFileTcs; + std::tie(autodetectedUserFileTcs, manualUserFileTcs) + = Utils::partition(uniqueUserFileTcs, &ToolChain::isAutoDetected); + const QList autodetectedUserTcs = subtractById(autodetectedUserFileTcs, systemFileTcs); + + // Calculate a set of Tcs that were detected before (and saved to userFile) and that + // got re-detected again. Take the userTcs (to keep Ids) over the same in autodetectedTcs. + QList redetectedUserTcs; + QList notRedetectedUserTcs; + std::tie(redetectedUserTcs, notRedetectedUserTcs) + = Utils::partition(autodetectedUserTcs, + [&autodetectedTcs](ToolChain *tc) { return containsByEqual(autodetectedTcs, tc); }); + + // Remove redetected tcs from autodetectedTcs: + const QList newlyAutodetectedTcs + = subtractByEqual(autodetectedTcs, redetectedUserTcs); + + const QList notRedetectedButValidUserTcs + = Utils::filtered(notRedetectedUserTcs, &ToolChain::isValid); + + const QList validManualUserTcs + = Utils::filtered(manualUserFileTcs, &ToolChain::isValid); + + ToolChainOperations result; + result.toDemote = notRedetectedButValidUserTcs; + result.toRegister = stabilizeOrder(systemFileTcs + validManualUserTcs + result.toDemote // manual TCs + + redetectedUserTcs + newlyAutodetectedTcs, // auto TCs + userFileTcs); + + result.toDelete = makeUniqueByPointerEqual(subtractByPointerEqual(systemFileTcs + userFileTcs + autodetectedTcs, + result.toRegister)); + return result; +} + +// -------------------------------------------------------------------- +// ToolChainSettingsAccessor: +// -------------------------------------------------------------------- + +ToolChainSettingsAccessor::ToolChainSettingsAccessor() : + UpgradingSettingsAccessor("QtCreatorToolChains", + QCoreApplication::translate("ProjectExplorer::ToolChainManager", "ToolChains"), + Core::Constants::IDE_DISPLAY_NAME) +{ + setBaseFilePath(FileName::fromString(Core::ICore::userResourcePath() + TOOLCHAIN_FILENAME)); + + addVersionUpgrader(std::make_unique()); +} + +QList ToolChainSettingsAccessor::restoreToolChains(QWidget *parent) const +{ + // read all tool chains from SDK + const QList systemFileTcs + = toolChains(restoreSettings(FileName::fromString(Core::ICore::installerResourcePath() + TOOLCHAIN_FILENAME), + parent)); + + // read all tool chains from user file. + const QList userFileTcs = toolChains(restoreSettings(parent)); + + // Autodetect: Pass autodetected toolchains from user file so the information can be reused: + const QList autodetectedUserFileTcs + = Utils::filtered(userFileTcs, &ToolChain::isAutoDetected); + const QList autodetectedTcs = autoDetectToolChains(autodetectedUserFileTcs); + + // merge tool chains and register those that we need to keep: + const ToolChainOperations ops = mergeToolChainLists(systemFileTcs, userFileTcs, autodetectedTcs); + + // Process ops: + for (ToolChain *tc : ops.toDemote) + tc->setDetection(ToolChain::ManualDetection); + + qDeleteAll(ops.toDelete); + + return ops.toRegister; +} + +void ToolChainSettingsAccessor::saveToolChains(const QList &toolchains, QWidget *parent) +{ + QVariantMap data; + + int count = 0; + for (const ToolChain *tc : toolchains) { + if (!tc || !tc->isValid()) + continue; + const QVariantMap tmp = tc->toMap(); + if (tmp.isEmpty()) + continue; + data.insert(QString::fromLatin1(TOOLCHAIN_DATA_KEY) + QString::number(count), tmp); + ++count; + } + data.insert(TOOLCHAIN_COUNT_KEY, count); + + // Do not save default debuggers! Those are set by the SDK! + + saveSettings(data, parent); +} + +QList ToolChainSettingsAccessor::toolChains(const QVariantMap &data) const +{ + QList result; + const QList factories = ToolChainFactory::allToolChainFactories(); + + const int count = data.value(TOOLCHAIN_COUNT_KEY, 0).toInt(); + for (int i = 0; i < count; ++i) { + const QString key = QString::fromLatin1(TOOLCHAIN_DATA_KEY) + QString::number(i); + if (!data.contains(key)) + break; + + const QVariantMap tcMap = data.value(key).toMap(); + + bool restored = false; + for (ToolChainFactory *f : factories) { + if (f->canRestore(tcMap)) { + if (ToolChain *tc = f->restore(tcMap)) { + result.append(tc); + restored = true; + break; + } + } + } + if (!restored) + qWarning("Warning: Unable to restore compiler type '%s' for tool chain %s.", + qPrintable(ToolChainFactory::typeIdFromMap(tcMap).toString()), + qPrintable(QString::fromUtf8(ToolChainFactory::idFromMap(tcMap)))); + } + + return result; +} + +} // namespace Internal +} // namespace ProjectExplorer + +#ifdef WITH_TESTS +#include "projectexplorer.h" + +#include "headerpath.h" + +#include "abi.h" + +#include +#include + +namespace ProjectExplorer { + +typedef QList TCList; + +class TTC : public ToolChain +{ +public: + TTC(ToolChain::Detection d, const QByteArray &t, bool v = true) : + ToolChain("TestToolChainType", d), + token(t), + m_valid(v) + { + m_toolChains.append(this); + setLanguage(Constants::CXX_LANGUAGE_ID); + } + + static QList toolChains(); + static bool hasToolChains() { return !m_toolChains.isEmpty(); } + + QString typeDisplayName() const override { return "Test Tool Chain"; } + Abi targetAbi() const override { return Abi::hostAbi(); } + bool isValid() const override { return m_valid; } + PredefinedMacrosRunner createPredefinedMacrosRunner() const override { return PredefinedMacrosRunner(); } + Macros predefinedMacros(const QStringList &cxxflags) const override { Q_UNUSED(cxxflags); return Macros(); } + CompilerFlags compilerFlags(const QStringList &cxxflags) const override { Q_UNUSED(cxxflags); return NoFlags; } + WarningFlags warningFlags(const QStringList &cflags) const override { Q_UNUSED(cflags); return WarningFlags::NoWarnings; } + SystemHeaderPathsRunner createSystemHeaderPathsRunner() const override { return SystemHeaderPathsRunner(); } + QList systemHeaderPaths(const QStringList &cxxflags, const FileName &sysRoot) const override + { Q_UNUSED(cxxflags); Q_UNUSED(sysRoot); return QList(); } + void addToEnvironment(Environment &env) const override { Q_UNUSED(env); } + QString makeCommand(const Environment &env) const override { Q_UNUSED(env); return "make"; } + FileName compilerCommand() const override { return Utils::FileName::fromString("/tmp/test/gcc"); } + IOutputParser *outputParser() const override { return nullptr; } + ToolChainConfigWidget *configurationWidget() override { return nullptr; } + TTC *clone() const override { return new TTC(*this); } + bool operator ==(const ToolChain &other) const override { + if (!ToolChain::operator==(other)) + return false; + return static_cast(&other)->token == token; + } + + QByteArray token; + +private: + TTC(const TTC &other) : + ToolChain(other.typeId(), other.detection()), + token(other.token) + {} + + bool m_valid = false; + + static QList m_toolChains; + +public: +}; + +QList TTC::m_toolChains; + +} // namespace ProjectExplorer + +Q_DECLARE_METATYPE(ProjectExplorer::ToolChain *) + +namespace ProjectExplorer { + +void ProjectExplorerPlugin::testToolChainMerging_data() +{ + QTest::addColumn("system"); + QTest::addColumn("user"); + QTest::addColumn("autodetect"); + QTest::addColumn("toDemote"); + QTest::addColumn("toRegister"); + + TTC *system1 = nullptr; + TTC *system1c = nullptr; + TTC *system2 = nullptr; + TTC *system3i = nullptr; + TTC *user1 = nullptr; + TTC *user1c = nullptr; + TTC *user3i = nullptr; + TTC *user2 = nullptr; + TTC *auto1 = nullptr; + TTC *auto1c = nullptr; + TTC *auto1_2 = nullptr; + TTC *auto2 = nullptr; + TTC *auto3i = nullptr; + + if (!TTC::hasToolChains()) { + system1 = new TTC(ToolChain::AutoDetection, "system1"); Q_UNUSED(system1); + system1c = system1->clone(); Q_UNUSED(system1c); + system2 = new TTC(ToolChain::AutoDetection, "system2"); Q_UNUSED(system2); + system3i = new TTC(ToolChain::AutoDetection, "system3", false); Q_UNUSED(system3i); + user1 = new TTC(ToolChain::ManualDetection, "user1"); Q_UNUSED(user1); + user1c = user1->clone(); Q_UNUSED(user1c); + user2 = new TTC(ToolChain::ManualDetection, "user2"); Q_UNUSED(user2); + user3i = new TTC(ToolChain::ManualDetection, "user3", false); Q_UNUSED(user3i); + auto1 = new TTC(ToolChain::AutoDetectionFromSettings, "auto1"); Q_UNUSED(auto1); + auto1c = auto1->clone(); Q_UNUSED(auto1c); + auto1_2 = new TTC(ToolChain::AutoDetectionFromSettings, "auto1"); Q_UNUSED(auto1_2); + auto2 = new TTC(ToolChain::AutoDetectionFromSettings, "auto2"); Q_UNUSED(auto2); + auto3i = new TTC(ToolChain::AutoDetectionFromSettings, "auto3", false); Q_UNUSED(auto3i); + } + + QTest::newRow("no toolchains") + << (TCList()) << (TCList()) << (TCList()) + << (TCList()) << (TCList()); + + QTest::newRow("System: system, no user") + << (TCList() << system1) << (TCList()) << (TCList()) + << (TCList()) << (TCList() << system1); + QTest::newRow("System: system, user") + << (TCList() << system1) << (TCList() << system1) << (TCList()) + << (TCList()) << (TCList() << system1); + QTest::newRow("System: no system, user") // keep, the user tool chain as it is still found + << (TCList()) << (TCList() << system1) << (TCList()) + << (TCList() << system1) << (TCList() << system1); + QTest::newRow("System: no system, invalid user") + << (TCList()) << (TCList() << system3i) << (TCList()) + << (TCList()) << (TCList()); + + QTest::newRow("Auto: no auto, user") + << (TCList()) << (TCList() << auto1) << (TCList()) + << (TCList() << auto1) << (TCList() << auto1); + QTest::newRow("Auto: auto, no user") + << (TCList()) << (TCList()) << (TCList() << auto1) + << (TCList()) << (TCList() << auto1); + QTest::newRow("Auto: auto, user") + << (TCList()) << (TCList() << auto1) << (TCList() << auto1) + << (TCList()) << (TCList() << auto1); + QTest::newRow("Auto: auto-redetect, user") + << (TCList()) << (TCList() << auto1) << (TCList() << auto1_2) + << (TCList()) << (TCList() << auto1); + QTest::newRow("Auto: auto-redetect, duplicate users") + << (TCList()) << (TCList() << auto1 << auto1c) << (TCList() << auto1_2) + << (TCList()) << (TCList() << auto1); + QTest::newRow("Auto: (no) auto, invalid user") + << (TCList()) << (TCList() << auto3i) << (TCList()) + << (TCList()) << (TCList()); + + QTest::newRow("Delete invalid user") + << (TCList()) << (TCList() << user3i) << (TCList()) + << (TCList()) << (TCList()); + + QTest::newRow("one of everything") + << (TCList() << system1) << (TCList() << user1) << (TCList() << auto1) + << (TCList()) << (TCList() << system1 << user1 << auto1); +} + +void ProjectExplorerPlugin::testToolChainMerging() +{ + QFETCH(TCList, system); + QFETCH(TCList, user); + QFETCH(TCList, autodetect); + QFETCH(TCList, toRegister); + QFETCH(TCList, toDemote); + + Internal::ToolChainOperations ops = Internal::mergeToolChainLists(system, user, autodetect); + + QSet expToRegister = QSet::fromList(toRegister); + QSet expToDemote = QSet::fromList(toDemote); + + QSet actToRegister = QSet::fromList(ops.toRegister); + QSet actToDemote = QSet::fromList(ops.toDemote); + QSet actToDelete = QSet::fromList(ops.toDelete); + + QCOMPARE(actToRegister.count(), ops.toRegister.count()); // no dups! + QCOMPARE(actToDemote.count(), ops.toDemote.count()); // no dups! + QCOMPARE(actToDelete.count(), ops.toDelete.count()); // no dups! + + QSet tmp = actToRegister; + tmp.intersect(actToDemote); + QCOMPARE(tmp, actToDemote); // all toDemote are in toRegister + + tmp = actToRegister; + tmp.intersect(actToDelete); + QVERIFY(tmp.isEmpty()); // Nothing that needs to be registered is to be deleted + + tmp = actToRegister; + tmp.unite(actToDelete); + QCOMPARE(tmp, QSet::fromList(system + user + autodetect)); // All input is accounted for + + QCOMPARE(expToRegister, actToRegister); + QCOMPARE(expToDemote, actToDemote); + QCOMPARE(QSet::fromList(system + user + autodetect), + QSet::fromList(ops.toRegister + ops.toDemote + ops.toDelete)); +} + +} // namespace ProjectExplorer + +#endif // WITH_TESTS + diff --git a/src/plugins/projectexplorer/toolchainsettingsaccessor.h b/src/plugins/projectexplorer/toolchainsettingsaccessor.h new file mode 100644 index 00000000000..80d3f90fd6d --- /dev/null +++ b/src/plugins/projectexplorer/toolchainsettingsaccessor.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** 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 +** 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. +** +** 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. +** +****************************************************************************/ + +#pragma once + +#include + +#include + +#include + +namespace ProjectExplorer { + +class ToolChain; + +namespace Internal { + +class ToolChainSettingsAccessor : public Utils::UpgradingSettingsAccessor +{ +public: + ToolChainSettingsAccessor(); + + QList restoreToolChains(QWidget *parent) const; + + void saveToolChains(const QList &toolchains, QWidget *parent); + +private: + QList toolChains(const QVariantMap &data) const; +}; + +} // namespace Internal +} // namespace ProjectExplorer