// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qtkitinformation.h" #include "qtparser.h" #include "qtsupportconstants.h" #include "qtsupporttr.h" #include "qttestparser.h" #include "qtversionmanager.h" #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace QtSupport { namespace Internal { class QtKitAspectWidget final : public KitAspectWidget { public: QtKitAspectWidget(Kit *k, const KitAspect *ki) : KitAspectWidget(k, ki) { m_combo = createSubWidget(); m_combo->setSizePolicy(QSizePolicy::Ignored, m_combo->sizePolicy().verticalPolicy()); m_combo->addItem(Tr::tr("None"), -1); QList versionIds = Utils::transform(QtVersionManager::versions(), &QtVersion::uniqueId); versionsChanged(versionIds, QList(), QList()); m_manageButton = createManageButton(Constants::QTVERSION_SETTINGS_PAGE_ID); refresh(); m_combo->setToolTip(ki->description()); connect(m_combo, &QComboBox::currentIndexChanged, this, &QtKitAspectWidget::currentWasChanged); connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged, this, &QtKitAspectWidget::versionsChanged); } ~QtKitAspectWidget() final { delete m_combo; delete m_manageButton; } private: void makeReadOnly() final { m_combo->setEnabled(false); } void addToLayout(Layouting::LayoutItem &parent) { addMutableAction(m_combo); parent.addItem(m_combo); parent.addItem(m_manageButton); } void refresh() final { m_combo->setCurrentIndex(findQtVersion(QtKitAspect::qtVersionId(m_kit))); } private: static QString itemNameFor(const QtVersion *v) { QTC_ASSERT(v, return QString()); QString name = v->displayName(); if (!v->isValid()) name = Tr::tr("%1 (invalid)").arg(v->displayName()); return name; } void versionsChanged(const QList &added, const QList &removed, const QList &changed) { for (const int id : added) { QtVersion *v = QtVersionManager::version(id); QTC_CHECK(v); QTC_CHECK(findQtVersion(id) < 0); m_combo->addItem(itemNameFor(v), id); } for (const int id : removed) { int pos = findQtVersion(id); if (pos >= 0) // We do not include invalid Qt versions, so do not try to remove those. m_combo->removeItem(pos); } for (const int id : changed) { QtVersion *v = QtVersionManager::version(id); int pos = findQtVersion(id); QTC_CHECK(pos >= 0); m_combo->setItemText(pos, itemNameFor(v)); } } void currentWasChanged(int idx) { QtKitAspect::setQtVersionId(m_kit, m_combo->itemData(idx).toInt()); } int findQtVersion(const int id) const { for (int i = 0; i < m_combo->count(); ++i) { if (id == m_combo->itemData(i).toInt()) return i; } return -1; } QComboBox *m_combo; QWidget *m_manageButton; }; } // namespace Internal QtKitAspect::QtKitAspect() { setObjectName(QLatin1String("QtKitAspect")); setId(QtKitAspect::id()); setDisplayName(Tr::tr("Qt version")); setDescription(Tr::tr("The Qt library to use for all projects using this kit.
" "A Qt version is required for qmake-based projects " "and optional when using other build systems.")); setPriority(26000); connect(KitManager::instance(), &KitManager::kitsLoaded, this, &QtKitAspect::kitsWereLoaded); } void QtKitAspect::setup(Kit *k) { if (!k || k->hasValue(id())) return; const Abi tcAbi = ToolChainKitAspect::targetAbi(k); const Id deviceType = DeviceTypeKitAspect::deviceTypeId(k); const QtVersions matches = QtVersionManager::versions([&tcAbi, &deviceType](const QtVersion *qt) { return qt->targetDeviceTypes().contains(deviceType) && Utils::contains(qt->qtAbis(), [&tcAbi](const Abi &qtAbi) { return qtAbi.isCompatibleWith(tcAbi); }); }); if (matches.empty()) return; // An MSVC 2015 toolchain is compatible with an MSVC 2017 Qt, but we prefer an // MSVC 2015 Qt if we find one. const QtVersions exactMatches = Utils::filtered(matches, [&tcAbi](const QtVersion *qt) { return qt->qtAbis().contains(tcAbi); }); const QtVersions &candidates = !exactMatches.empty() ? exactMatches : matches; QtVersion * const qtFromPath = QtVersionManager::version( equal(&QtVersion::detectionSource, QString("PATH"))); if (qtFromPath && candidates.contains(qtFromPath)) k->setValue(id(), qtFromPath->uniqueId()); else k->setValue(id(), candidates.first()->uniqueId()); } Tasks QtKitAspect::validate(const Kit *k) const { QTC_ASSERT(QtVersionManager::isLoaded(), return {}); QtVersion *version = qtVersion(k); if (!version) return {}; return version->validateKit(k); } void QtKitAspect::fix(Kit *k) { QTC_ASSERT(QtVersionManager::isLoaded(), return); QtVersion *version = qtVersion(k); if (!version) { if (qtVersionId(k) >= 0) { qWarning("Qt version is no longer known, removing from kit \"%s\".", qPrintable(k->displayName())); setQtVersionId(k, -1); } return; } // Set a matching toolchain if we don't have one. if (ToolChainKitAspect::cxxToolChain(k)) return; const QString spec = version->mkspec(); Toolchains possibleTcs = ToolChainManager::toolchains([version](const ToolChain *t) { if (!t->isValid() || t->language() != ProjectExplorer::Constants::CXX_LANGUAGE_ID) return false; return Utils::anyOf(version->qtAbis(), [t](const Abi &qtAbi) { return t->supportedAbis().contains(qtAbi) && t->targetAbi().wordWidth() == qtAbi.wordWidth() && t->targetAbi().architecture() == qtAbi.architecture(); }); }); if (!possibleTcs.isEmpty()) { // Prefer exact matches. // TODO: We should probably prefer the compiler with the highest version number instead, // but this information is currently not exposed by the ToolChain class. const FilePaths envPathVar = Environment::systemEnvironment().path(); sort(possibleTcs, [version, &envPathVar](const ToolChain *tc1, const ToolChain *tc2) { const QVector &qtAbis = version->qtAbis(); const bool tc1ExactMatch = qtAbis.contains(tc1->targetAbi()); const bool tc2ExactMatch = qtAbis.contains(tc2->targetAbi()); if (tc1ExactMatch && !tc2ExactMatch) return true; if (!tc1ExactMatch && tc2ExactMatch) return false; // For a multi-arch Qt that support the host ABI, prefer toolchains that match // the host ABI. if (qtAbis.size() > 1 && qtAbis.contains(Abi::hostAbi())) { const bool tc1HasHostAbi = tc1->targetAbi() == Abi::hostAbi(); const bool tc2HasHostAbi = tc2->targetAbi() == Abi::hostAbi(); if (tc1HasHostAbi && !tc2HasHostAbi) return true; if (!tc1HasHostAbi && tc2HasHostAbi) return false; } if (tc1->priority() > tc2->priority()) return true; if (tc1->priority() < tc2->priority()) return false; // Hack to prefer a tool chain from PATH (e.g. autodetected) over other matches. // This improves the situation a bit if a cross-compilation tool chain has the // same ABI as the host. const bool tc1IsInPath = envPathVar.contains(tc1->compilerCommand().parentDir()); const bool tc2IsInPath = envPathVar.contains(tc2->compilerCommand().parentDir()); return tc1IsInPath && !tc2IsInPath; }); // TODO: Why is this not done during sorting? const Toolchains goodTcs = Utils::filtered(possibleTcs, [&spec](const ToolChain *t) { return t->suggestedMkspecList().contains(spec); }); if (ToolChain * const bestTc = goodTcs.isEmpty() ? possibleTcs.first() : goodTcs.first()) ToolChainKitAspect::setAllToolChainsToMatch(k, bestTc); } } KitAspectWidget *QtKitAspect::createConfigWidget(Kit *k) const { QTC_ASSERT(k, return nullptr); return new Internal::QtKitAspectWidget(k, this); } QString QtKitAspect::displayNamePostfix(const Kit *k) const { QtVersion *version = qtVersion(k); return version ? version->displayName() : QString(); } KitAspect::ItemList QtKitAspect::toUserOutput(const Kit *k) const { QtVersion *version = qtVersion(k); return {{Tr::tr("Qt version"), version ? version->displayName() : Tr::tr("None")}}; } void QtKitAspect::addToBuildEnvironment(const Kit *k, Environment &env) const { QtVersion *version = qtVersion(k); if (version) version->addToEnvironment(k, env); } QList QtKitAspect::createOutputParsers(const Kit *k) const { if (qtVersion(k)) return {new Internal::QtTestParser, new QtParser}; return {}; } class QtMacroSubProvider { public: QtMacroSubProvider(Kit *kit) : expander(QtVersion::createMacroExpander( [kit] { return QtKitAspect::qtVersion(kit); })) {} MacroExpander *operator()() const { return expander.get(); } std::shared_ptr expander; }; void QtKitAspect::addToMacroExpander(Kit *kit, MacroExpander *expander) const { QTC_ASSERT(kit, return); expander->registerSubProvider(QtMacroSubProvider(kit)); expander->registerVariable("Qt:Name", Tr::tr("Name of Qt Version"), [kit]() -> QString { QtVersion *version = qtVersion(kit); return version ? version->displayName() : Tr::tr("unknown"); }); expander->registerVariable("Qt:qmakeExecutable", Tr::tr("Path to the qmake executable"), [kit]() -> QString { QtVersion *version = qtVersion(kit); return version ? version->qmakeFilePath().path() : QString(); }); } Id QtKitAspect::id() { return "QtSupport.QtInformation"; } int QtKitAspect::qtVersionId(const Kit *k) { if (!k) return -1; int id = -1; QVariant data = k->value(QtKitAspect::id(), -1); if (data.type() == QVariant::Int) { bool ok; id = data.toInt(&ok); if (!ok) id = -1; } else { QString source = data.toString(); QtVersion *v = QtVersionManager::version([source](const QtVersion *v) { return v->detectionSource() == source; }); if (v) id = v->uniqueId(); } return id; } void QtKitAspect::setQtVersionId(Kit *k, const int id) { QTC_ASSERT(k, return); k->setValue(QtKitAspect::id(), id); } QtVersion *QtKitAspect::qtVersion(const Kit *k) { return QtVersionManager::version(qtVersionId(k)); } void QtKitAspect::setQtVersion(Kit *k, const QtVersion *v) { if (!v) setQtVersionId(k, -1); else setQtVersionId(k, v->uniqueId()); } /*! * Helper function that prepends the directory containing the C++ toolchain and Qt * binaries to PATH. This is used to in build configurations targeting broken build * systems to provide hints about which binaries to use. */ void QtKitAspect::addHostBinariesToPath(const Kit *k, Environment &env) { if (const ToolChain *tc = ToolChainKitAspect::cxxToolChain(k)) env.prependOrSetPath(tc->compilerCommand().parentDir()); if (const QtVersion *qt = qtVersion(k)) env.prependOrSetPath(qt->hostBinPath()); } void QtKitAspect::qtVersionsChanged(const QList &addedIds, const QList &removedIds, const QList &changedIds) { Q_UNUSED(addedIds) Q_UNUSED(removedIds) for (Kit *k : KitManager::kits()) { if (changedIds.contains(qtVersionId(k))) { k->validate(); // Qt version may have become (in)valid notifyAboutUpdate(k); } } } void QtKitAspect::kitsWereLoaded() { for (Kit *k : KitManager::kits()) fix(k); connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged, this, &QtKitAspect::qtVersionsChanged); } Kit::Predicate QtKitAspect::platformPredicate(Id platform) { return [platform](const Kit *kit) -> bool { QtVersion *version = QtKitAspect::qtVersion(kit); return version && version->targetDeviceTypes().contains(platform); }; } Kit::Predicate QtKitAspect::qtVersionPredicate(const QSet &required, const QVersionNumber &min, const QVersionNumber &max) { return [required, min, max](const Kit *kit) -> bool { QtVersion *version = QtKitAspect::qtVersion(kit); if (!version) return false; const QVersionNumber current = version->qtVersion(); if (min.majorVersion() > -1 && current < min) return false; if (max.majorVersion() > -1 && current > max) return false; return version->features().contains(required); }; } QSet QtKitAspect::supportedPlatforms(const Kit *k) const { QtVersion *version = QtKitAspect::qtVersion(k); return version ? version->targetDeviceTypes() : QSet(); } QSet QtKitAspect::availableFeatures(const Kit *k) const { QtVersion *version = QtKitAspect::qtVersion(k); return version ? version->features() : QSet(); } int QtKitAspect::weight(const Kit *k) const { const QtVersion * const qt = qtVersion(k); if (!qt) return 0; if (!qt->targetDeviceTypes().contains(DeviceTypeKitAspect::deviceTypeId(k))) return 0; const Abi tcAbi = ToolChainKitAspect::targetAbi(k); if (qt->qtAbis().contains(tcAbi)) return 2; return Utils::contains(qt->qtAbis(), [&tcAbi](const Abi &qtAbi) { return qtAbi.isCompatibleWith(tcAbi); }) ? 1 : 0; } Id SuppliesQtQuickImportPath::id() { return QtSupport::Constants::FLAGS_SUPPLIES_QTQUICK_IMPORT_PATH; } Id KitQmlImportPath::id() { return QtSupport::Constants::KIT_QML_IMPORT_PATH; } Id KitHasMergedHeaderPathsWithQmlImportPaths::id() { return QtSupport::Constants::KIT_HAS_MERGED_HEADER_PATHS_WITH_QML_IMPORT_PATHS; } } // namespace QtSupport