// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "toolchainsettingsaccessor.h" #include "devicesupport/devicemanager.h" #include "projectexplorerconstants.h" #include "projectexplorertr.h" #include "toolchain.h" #include #include #include #include #include #include using namespace Utils; namespace ProjectExplorer { namespace Internal { static Q_LOGGING_CATEGORY(Log, "qtc.projectexplorer.toolchain.autodetection", QtWarningMsg) // -------------------------------------------------------------------- // ToolChainSettingsUpgraders: // -------------------------------------------------------------------- class ToolChainSettingsUpgraderV0 : public Utils::VersionUpgrader { // Necessary to make Version 1 supported. public: ToolChainSettingsUpgraderV0() : Utils::VersionUpgrader(0, "4.6") { } // NOOP Store upgrade(const Store &data) final { return data; } }; // -------------------------------------------------------------------- // Helpers: // -------------------------------------------------------------------- const char TOOLCHAIN_DATA_KEY[] = "ToolChain."; const char TOOLCHAIN_COUNT_KEY[] = "ToolChain.Count"; const char TOOLCHAIN_FILENAME[] = "toolchains.xml"; struct ToolChainOperations { Toolchains toDemote; Toolchains toRegister; Toolchains toDelete; }; static Toolchains autoDetectToolchains(const ToolchainDetector &detector) { Toolchains result; for (ToolchainFactory *f : ToolchainFactory::allToolchainFactories()) { NANOTRACE_SCOPE_ARGS("ProjectExplorer", "ToolchainSettingsAccessor::autoDetectToolchains", {"factory", f->displayName().toStdString()}); QElapsedTimer et; et.start(); if (std::optional asyncDetector = f->asyncAutoDetector(detector)) { Toolchains known = Utils::filtered(detector.alreadyKnown, [supportedType = f->supportedToolchainType()]( const Toolchain *tc) { return tc->typeId() == supportedType && tc->isValid(); }); result.append(known); asyncDetector->run(); } else { result.append(f->autoDetect(detector)); } qCDebug(Log) << f->displayName() << "auto detection took: " << et.elapsed() << "ms"; } // Remove invalid toolchains that might have sneaked in. return Utils::filtered(result, [](const Toolchain *tc) { return tc->isValid(); }); } static Toolchains makeUniqueByEqual(const Toolchains &a) { Toolchains result; for (Toolchain *tc : a) { if (!Utils::contains(result, [tc](Toolchain *rtc) { return *tc == *rtc; })) result.append(tc); } return result; } static Toolchains makeUniqueByPointerEqual(const Toolchains &a) { return Utils::toList(Utils::toSet(a)); } static Toolchains subtractById(const Toolchains &a, const Toolchains &b) { return Utils::filtered(a, [&b](Toolchain *atc) { return !Utils::anyOf(b, Utils::equal(&Toolchain::id, atc->id())); }); } static bool containsByEqual(const Toolchains &a, const Toolchain *atc) { return Utils::anyOf(a, [atc](Toolchain *btc) { return *atc == *btc; }); } static Toolchains subtractByEqual(const Toolchains &a, const Toolchains &b) { return Utils::filtered(a, [&b](Toolchain *atc) { return !Utils::anyOf(b, [atc](Toolchain *btc) { return *atc == *btc; }); }); } static Toolchains subtractByPointerEqual(const Toolchains &a, const Toolchains &b) { return Utils::filtered(a, [&b](Toolchain *atc) { return !b.contains(atc); }); } static Toolchains stabilizeOrder(const Toolchains &toRegister, const Toolchains &userFileTcs) { // Keep the toolchains in their position in the user toolchain file to minimize diff! Toolchains result; result.reserve(toRegister.size()); Toolchains 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 Toolchains &systemFileTcs, const Toolchains &userFileTcs, const Toolchains &autodetectedTcs) { const Toolchains uniqueUserFileTcs = makeUniqueByEqual(userFileTcs); Toolchains manualUserFileTcs; Toolchains autodetectedUserFileTcs; std::tie(autodetectedUserFileTcs, manualUserFileTcs) = Utils::partition(uniqueUserFileTcs, &Toolchain::isAutoDetected); const Toolchains 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. Toolchains redetectedUserTcs; Toolchains notRedetectedUserTcs; std::tie(redetectedUserTcs, notRedetectedUserTcs) = Utils::partition(autodetectedUserTcs, [&autodetectedTcs](Toolchain *tc) { return containsByEqual(autodetectedTcs, tc); }); // Remove redetected tcs from autodetectedTcs: const Toolchains newlyAutodetectedTcs = subtractByEqual(autodetectedTcs, redetectedUserTcs); const Toolchains notRedetectedButValidUserTcs = Utils::filtered(notRedetectedUserTcs, &Toolchain::isValid); ToolChainOperations result; result.toDemote = notRedetectedButValidUserTcs; result.toRegister = stabilizeOrder(systemFileTcs + manualUserFileTcs + result.toDemote // manual TCs + redetectedUserTcs + newlyAutodetectedTcs, // auto TCs userFileTcs); result.toDelete = makeUniqueByPointerEqual(subtractByPointerEqual(systemFileTcs + userFileTcs + autodetectedTcs, result.toRegister)); return result; } // -------------------------------------------------------------------- // ToolChainSettingsAccessor: // -------------------------------------------------------------------- ToolchainSettingsAccessor::ToolchainSettingsAccessor() { setDocType("QtCreatorToolChains"); setApplicationDisplayName(QGuiApplication::applicationDisplayName()); setBaseFilePath(Core::ICore::userResourcePath(TOOLCHAIN_FILENAME)); addVersionUpgrader(std::make_unique()); } Toolchains ToolchainSettingsAccessor::restoreToolchains(QWidget *parent) const { NANOTRACE_SCOPE("ProjectExplorer", "ToolChainSettingsAccessor::restoreToolChains"); // read all tool chains from SDK const Toolchains systemFileTcs = toolChains( restoreSettings(Core::ICore::installerResourcePath(TOOLCHAIN_FILENAME), parent)); for (Toolchain * const systemTc : systemFileTcs) systemTc->setDetection(Toolchain::AutoDetectionFromSdk); // read all tool chains from user file. const Toolchains userFileTcs = toolChains(restoreSettings(parent)); // Autodetect: Pass autodetected toolchains from user file so the information can be reused: const Toolchains autodetectedUserFileTcs = Utils::filtered(userFileTcs, &Toolchain::isAutoDetected); // Autodect from system paths on the desktop device. // The restriction is intentional to keep startup and automatic validation a limited effort ToolchainDetector detector(autodetectedUserFileTcs, DeviceManager::defaultDesktopDevice(), {}); const Toolchains autodetectedTcs = autoDetectToolchains(detector); // 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) { // FIXME: We currently only demote local toolchains, as they are not redetected. if (tc->detectionSource().isEmpty()) tc->setDetection(Toolchain::ManualDetection); } qDeleteAll(ops.toDelete); return ops.toRegister; } void ToolchainSettingsAccessor::saveToolchains(const Toolchains &toolchains, QWidget *parent) { Store data; int count = 0; for (const Toolchain *tc : toolchains) { if (!tc || (!tc->isValid() && tc->isAutoDetected())) continue; Store tmp; tc->toMap(tmp); if (tmp.isEmpty()) continue; data.insert(numberedKey(TOOLCHAIN_DATA_KEY, count), variantFromStore(tmp)); ++count; } data.insert(TOOLCHAIN_COUNT_KEY, count); // Do not save default debuggers! Those are set by the SDK! saveSettings(data, parent); } Toolchains ToolchainSettingsAccessor::toolChains(const Store &data) const { Toolchains result; const QList factories = ToolchainFactory::allToolchainFactories(); const int count = data.value(TOOLCHAIN_COUNT_KEY, 0).toInt(); for (int i = 0; i < count; ++i) { const Key key = numberedKey(TOOLCHAIN_DATA_KEY, i); if (!data.contains(key)) break; const Store tcMap = storeFromVariant(data.value(key)); bool restored = false; const Utils::Id tcType = ToolchainFactory::typeIdFromMap(tcMap); if (tcType.isValid()) { for (ToolchainFactory *f : factories) { if (f->supportedToolchainType() == tcType) { 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(tcType.toString()), qPrintable(QString::fromUtf8(ToolchainFactory::idFromMap(tcMap)))); } return result; } } // namespace Internal } // namespace ProjectExplorer #ifdef WITH_TESTS #include "projectexplorer_test.h" #include "abi.h" #include "toolchainconfigwidget.h" #include #include namespace ProjectExplorer { const char TestTokenKey[] = "TestTokenKey"; const char TestToolChainType[] = "TestToolChainType"; class TTC : public Toolchain { public: TTC(const QByteArray &t = {}, bool v = true) : Toolchain(TestToolChainType), token(t), m_valid(v) { m_toolChains.append(this); setLanguage(Constants::CXX_LANGUAGE_ID); setTypeDisplayName("Test Tool Chain"); setTargetAbiNoSignal(Abi::hostAbi()); setCompilerCommand("/tmp/test/gcc"); } static QList toolChains() { return m_toolChains; } static bool hasToolChains() { return !m_toolChains.isEmpty(); } bool isValid() const override { return m_valid; } MacroInspectionRunner createMacroInspectionRunner() const override { return {}; } LanguageExtensions languageExtensions(const QStringList &cxxflags) const override { Q_UNUSED(cxxflags) return LanguageExtension::None; } WarningFlags warningFlags(const QStringList &cflags) const override { Q_UNUSED(cflags) return WarningFlags::NoWarnings; } BuiltInHeaderPathsRunner createBuiltInHeaderPathsRunner(const Utils::Environment &) const override { return {}; } void addToEnvironment(Environment &env) const override { Q_UNUSED(env) } FilePath makeCommand(const Environment &) const override { return "make"; } QList createOutputParsers() const override { return {}; } std::unique_ptr createConfigurationWidget() override { return nullptr; } bool operator ==(const Toolchain &other) const override { if (!Toolchain::operator==(other)) return false; return static_cast(&other)->token == token; } void fromMap(const Store &data) final { Toolchain::fromMap(data); token = data.value(TestTokenKey).toByteArray(); } void toMap(Store &data) const final { Toolchain::toMap(data); data[TestTokenKey] = token; } QByteArray token; private: bool m_valid = false; static QList m_toolChains; }; QList TTC::m_toolChains; } // namespace ProjectExplorer Q_DECLARE_METATYPE(ProjectExplorer::Toolchain *) namespace ProjectExplorer::Internal { void ProjectExplorerTest::testToolChainMerging_data() { class TestToolchainFactory : ToolchainFactory { public: TestToolchainFactory() { setSupportedToolchainType(TestToolChainType); setToolchainConstructor([] { return new TTC; }); } }; TestToolchainFactory factory; QTest::addColumn("system"); QTest::addColumn("user"); QTest::addColumn("autodetect"); QTest::addColumn("toDemote"); QTest::addColumn("toRegister"); TTC *system1 = nullptr; Toolchain *system1c = nullptr; TTC *system2 = nullptr; TTC *system3i = nullptr; TTC *user1 = nullptr; Toolchain *user1c = nullptr; TTC *user3i = nullptr; TTC *user2 = nullptr; TTC *auto1 = nullptr; Toolchain *auto1c = nullptr; TTC *auto1_2 = nullptr; TTC *auto2 = nullptr; TTC *auto3i = nullptr; if (!TTC::hasToolChains()) { system1 = new TTC("system1"); system1->setDetection(Toolchain::AutoDetection); system1c = system1->clone(); Q_UNUSED(system1c) system2 = new TTC("system2"); system2->setDetection(Toolchain::AutoDetection); system3i = new TTC("system3", false); system3i->setDetection(Toolchain::AutoDetection); user1 = new TTC("user1"); user1->setDetection(Toolchain::ManualDetection); user1c = user1->clone(); Q_UNUSED(user1c) user2 = new TTC("user2"); user2->setDetection(Toolchain::ManualDetection); user3i = new TTC("user3", false); user3i->setDetection(Toolchain::ManualDetection); auto1 = new TTC("auto1"); auto1->setDetection(Toolchain::AutoDetection); auto1c = auto1->clone(); auto1_2 = new TTC("auto1"); auto1_2->setDetection(Toolchain::AutoDetection); auto2 = new TTC("auto2"); auto2->setDetection(Toolchain::AutoDetection); auto3i = new TTC("auto3", false); auto3i->setDetection(Toolchain::AutoDetection); } QTest::newRow("no toolchains") << (Toolchains()) << (Toolchains()) << (Toolchains()) << (Toolchains()) << (Toolchains()); QTest::newRow("System: system, no user") << (Toolchains() << system1) << (Toolchains()) << (Toolchains()) << (Toolchains()) << (Toolchains() << system1); QTest::newRow("System: system, user") << (Toolchains() << system1) << (Toolchains() << system1) << (Toolchains()) << (Toolchains()) << (Toolchains() << system1); QTest::newRow("System: no system, user") // keep, the user tool chain as it is still found << (Toolchains()) << (Toolchains() << system1) << (Toolchains()) << (Toolchains() << system1) << (Toolchains() << system1); QTest::newRow("System: no system, invalid user") << (Toolchains()) << (Toolchains() << system3i) << (Toolchains()) << (Toolchains()) << (Toolchains()); QTest::newRow("Auto: no auto, user") << (Toolchains()) << (Toolchains() << auto1) << (Toolchains()) << (Toolchains() << auto1) << (Toolchains() << auto1); QTest::newRow("Auto: auto, no user") << (Toolchains()) << (Toolchains()) << (Toolchains() << auto1) << (Toolchains()) << (Toolchains() << auto1); QTest::newRow("Auto: auto, user") << (Toolchains()) << (Toolchains() << auto1) << (Toolchains() << auto1) << (Toolchains()) << (Toolchains() << auto1); QTest::newRow("Auto: auto-redetect, user") << (Toolchains()) << (Toolchains() << auto1) << (Toolchains() << auto1_2) << (Toolchains()) << (Toolchains() << auto1); QTest::newRow("Auto: auto-redetect, duplicate users") << (Toolchains()) << (Toolchains() << auto1 << auto1c) << (Toolchains() << auto1_2) << (Toolchains()) << (Toolchains() << auto1); QTest::newRow("Auto: (no) auto, invalid user") << (Toolchains()) << (Toolchains() << auto3i) << (Toolchains()) << (Toolchains()) << (Toolchains()); QTest::newRow("invalid user") << (Toolchains()) << (Toolchains() << user3i) << (Toolchains()) << (Toolchains()) << (Toolchains{user3i}); QTest::newRow("one of everything") << (Toolchains() << system1) << (Toolchains() << user1) << (Toolchains() << auto1) << (Toolchains()) << (Toolchains() << system1 << user1 << auto1); } void ProjectExplorerTest::testToolChainMerging() { QFETCH(Toolchains, system); QFETCH(Toolchains, user); QFETCH(Toolchains, autodetect); QFETCH(Toolchains, toRegister); QFETCH(Toolchains, toDemote); Internal::ToolChainOperations ops = Internal::mergeToolChainLists(system, user, autodetect); QSet expToRegister = Utils::toSet(toRegister); QSet expToDemote = Utils::toSet(toDemote); QSet actToRegister = Utils::toSet(ops.toRegister); QSet actToDemote = Utils::toSet(ops.toDemote); QSet actToDelete = Utils::toSet(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, Utils::toSet(system + user + autodetect)); // All input is accounted for QCOMPARE(expToRegister, actToRegister); QCOMPARE(expToDemote, actToDemote); QCOMPARE(Utils::toSet(system + user + autodetect), Utils::toSet(ops.toRegister + ops.toDemote + ops.toDelete)); } void ProjectExplorerTest::deleteTestToolchains() { qDeleteAll(TTC::toolChains()); } } // ProjectExplorer::Internal #endif // WITH_TESTS