TargetSetupPage: Improve reporting on invalid kits

Give the reason for a kit being disabled in the tooltip.

E.g. for a CMake project you will now get

"Kit is invalid: No CMake tool set."

instead of a generic error message about the kit being invalid.

Change-Id: Ic776dc24149d65ebf27163b605ec2e52a3a504a7
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
Tobias Hunger
2020-01-23 12:25:44 +01:00
parent ec4e60e691
commit e4738904d9
12 changed files with 104 additions and 100 deletions

View File

@@ -58,12 +58,19 @@ void JsonKitsPage::initializePage()
const Id platform = Id::fromString(wiz->stringValue(QLatin1String("Platform"))); const Id platform = Id::fromString(wiz->stringValue(QLatin1String("Platform")));
const QSet<Id> preferred const QSet<Id> preferred
= evaluate(m_preferredFeatures, wiz->value(QLatin1String("PreferredFeatures")), wiz); = evaluate(m_preferredFeatures, wiz->value(QLatin1String("PreferredFeatures")), wiz);
const QSet<Id> required const QSet<Id> required = evaluate(m_requiredFeatures,
= evaluate(m_requiredFeatures, wiz->value(QLatin1String("RequiredFeatures")), wiz); wiz->value(QLatin1String("RequiredFeatures")),
wiz);
setRequiredKitPredicate([required](const Kit *k) { return k->hasFeatures(required); }); setTasksGenerator([required, preferred, platform](const Kit *k) -> Tasks {
setPreferredKitPredicate([platform, preferred](const Kit *k) { if (!k->hasFeatures(required))
return k->supportedPlatforms().contains(platform) && k->hasFeatures(preferred); return {CompileTask(Task::Error, tr("At least one required feature is not present."))};
if (!k->supportedPlatforms().contains(platform))
return {CompileTask(Task::Unknown, tr("Platform is not supported."))};
if (!k->hasFeatures(preferred))
return {
CompileTask(Task::Unknown, tr("At least one preferred feature is not present."))};
return {};
}); });
setProjectPath(wiz->expander()->expand(Utils::FilePath::fromString(unexpandedProjectPath()))); setProjectPath(wiz->expander()->expand(Utils::FilePath::fromString(unexpandedProjectPath())));

View File

@@ -567,13 +567,16 @@ IOutputParser *Kit::createOutputParser() const
return first; return first;
} }
QString Kit::toHtml(const Tasks &additional) const QString Kit::toHtml(const Tasks &additional, const QString &extraText) const
{ {
QString result; QString result;
QTextStream str(&result); QTextStream str(&result);
str << "<html><body>"; str << "<html><body>";
str << "<h3>" << displayName() << "</h3>"; str << "<h3>" << displayName() << "</h3>";
if (!extraText.isEmpty())
str << "<p>" << extraText << "</p>";
if (!isValid() || hasWarning() || !additional.isEmpty()) if (!isValid() || hasWarning() || !additional.isEmpty())
str << "<p>" << ProjectExplorer::toHtml(additional + validate()) << "</p>"; str << "<p>" << ProjectExplorer::toHtml(additional + validate()) << "</p>";

View File

@@ -118,7 +118,7 @@ public:
void addToEnvironment(Utils::Environment &env) const; void addToEnvironment(Utils::Environment &env) const;
IOutputParser *createOutputParser() const; IOutputParser *createOutputParser() const;
QString toHtml(const Tasks &additional = Tasks()) const; QString toHtml(const Tasks &additional = Tasks(), const QString &extraText = QString()) const;
Kit *clone(bool keepName = false) const; Kit *clone(bool keepName = false) const;
void copyFrom(const Kit *k); void copyFrom(const Kit *k);
@@ -173,6 +173,8 @@ private:
Kit *m_kit; Kit *m_kit;
}; };
using TasksGenerator = std::function<Tasks(const Kit *)>;
} // namespace ProjectExplorer } // namespace ProjectExplorer
Q_DECLARE_METATYPE(ProjectExplorer::Kit *) Q_DECLARE_METATYPE(ProjectExplorer::Kit *)

View File

@@ -201,9 +201,6 @@ public:
QString m_displayName; QString m_displayName;
Kit::Predicate m_requiredKitPredicate;
Kit::Predicate m_preferredKitPredicate;
Utils::MacroExpander m_macroExpander; Utils::MacroExpander m_macroExpander;
Utils::FilePath m_rootProjectDirectory; Utils::FilePath m_rootProjectDirectory;
mutable QVector<const Node *> m_sortedNodeList; mutable QVector<const Node *> m_sortedNodeList;
@@ -229,10 +226,6 @@ Project::Project(const QString &mimeType,
// Only set up containernode after d is set so that it will find the project directory! // Only set up containernode after d is set so that it will find the project directory!
d->m_containerNode = std::make_unique<ContainerNode>(this); d->m_containerNode = std::make_unique<ContainerNode>(this);
setRequiredKitPredicate([this](const Kit *k) {
return !containsType(projectIssues(k), Task::TaskType::Error);
});
} }
Project::~Project() Project::~Project()
@@ -974,31 +967,11 @@ ProjectImporter *Project::projectImporter() const
return nullptr; return nullptr;
} }
Kit::Predicate Project::requiredKitPredicate() const
{
return d->m_requiredKitPredicate;
}
void Project::setRequiredKitPredicate(const Kit::Predicate &predicate)
{
d->m_requiredKitPredicate = predicate;
}
void Project::setCanBuildProducts() void Project::setCanBuildProducts()
{ {
d->m_canBuildProducts = true; d->m_canBuildProducts = true;
} }
Kit::Predicate Project::preferredKitPredicate() const
{
return d->m_preferredKitPredicate;
}
void Project::setPreferredKitPredicate(const Kit::Predicate &predicate)
{
d->m_preferredKitPredicate = predicate;
}
void Project::setExtraData(const QString &key, const QVariant &data) void Project::setExtraData(const QString &key, const QVariant &data)
{ {
d->m_extraData.insert(key, data); d->m_extraData.insert(key, data);

View File

@@ -144,9 +144,6 @@ public:
virtual ProjectImporter *projectImporter() const; virtual ProjectImporter *projectImporter() const;
Kit::Predicate requiredKitPredicate() const;
Kit::Predicate preferredKitPredicate() const;
// The build system is able to report all executables that can be built, independent // The build system is able to report all executables that can be built, independent
// of configuration. // of configuration.
bool knowsAllBuildExecutables() const; bool knowsAllBuildExecutables() const;
@@ -208,12 +205,6 @@ protected:
void createTargetFromMap(const QVariantMap &map, int index); void createTargetFromMap(const QVariantMap &map, int index);
virtual bool setupTarget(Target *t); virtual bool setupTarget(Target *t);
// Used to pre-check kits in the TargetSetupPage. RequiredKitPredicate
// is used to select kits available in the TargetSetupPage
void setPreferredKitPredicate(const Kit::Predicate &predicate);
// The predicate used to select kits available in TargetSetupPage.
void setRequiredKitPredicate(const Kit::Predicate &predicate);
void setCanBuildProducts(); void setCanBuildProducts();
void setId(Core::Id id); void setId(Core::Id id);

View File

@@ -159,8 +159,8 @@ void TargetSetupPageWrapper::addTargetSetupPage()
m_targetSetupPage = new TargetSetupPage(this); m_targetSetupPage = new TargetSetupPage(this);
m_targetSetupPage->setUseScrollArea(false); m_targetSetupPage->setUseScrollArea(false);
m_targetSetupPage->setProjectPath(m_project->projectFilePath()); m_targetSetupPage->setProjectPath(m_project->projectFilePath());
m_targetSetupPage->setRequiredKitPredicate(m_project->requiredKitPredicate()); m_targetSetupPage->setTasksGenerator(
m_targetSetupPage->setPreferredKitPredicate(m_project->preferredKitPredicate()); [this](const Kit *k) { return m_project->projectIssues(k); });
m_targetSetupPage->setProjectImporter(m_project->projectImporter()); m_targetSetupPage->setProjectImporter(m_project->projectImporter());
m_targetSetupPage->initializePage(); m_targetSetupPage->initializePage();
m_targetSetupPage->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); m_targetSetupPage->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
@@ -322,14 +322,14 @@ public:
case Qt::ToolTipRole: { case Qt::ToolTipRole: {
Kit *k = KitManager::kit(m_kitId); Kit *k = KitManager::kit(m_kitId);
QTC_ASSERT(k, return QVariant()); QTC_ASSERT(k, return QVariant());
QString toolTip; const QString extraText = [this]() {
if (m_kitErrorsForProject) if (m_kitErrorsForProject)
toolTip = "<h3>" + tr("Kit is unsuited for project") + "</h3>"; return QString("<h3>" + tr("Kit is unsuited for project") + "</h3>");
else if (!isEnabled()) if (!isEnabled())
toolTip = "<h3>" + tr("Click to activate:") + "</h3>" + k->toHtml(); return QString("<h3>" + tr("Click to activate") + "</h3>");
if (!m_kitIssues.isEmpty()) return QString();
toolTip += toHtml(m_kitIssues); }();
return toolTip; return k->toHtml(m_kitIssues, extraText);
} }
case PanelWidgetRole: case PanelWidgetRole:

View File

@@ -26,14 +26,15 @@
#include "targetsetuppage.h" #include "targetsetuppage.h"
#include "buildconfiguration.h" #include "buildconfiguration.h"
#include "buildinfo.h" #include "buildinfo.h"
#include "importwidget.h"
#include "kit.h" #include "kit.h"
#include "kitmanager.h" #include "kitmanager.h"
#include "importwidget.h"
#include "project.h" #include "project.h"
#include "projectexplorerconstants.h" #include "projectexplorerconstants.h"
#include "session.h" #include "session.h"
#include "target.h" #include "target.h"
#include "targetsetupwidget.h" #include "targetsetupwidget.h"
#include "task.h"
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
@@ -159,13 +160,27 @@ public:
} // namespace Internal } // namespace Internal
static TasksGenerator defaultTasksGenerator(const TasksGenerator &childGenerator)
{
return [childGenerator](const Kit *k) -> Tasks {
if (!k->isValid())
return {
CompileTask(Task::Error,
QCoreApplication::translate("ProjectExplorer", "Kit is not valid."))};
if (childGenerator)
return childGenerator(k);
return {};
};
}
using namespace Internal; using namespace Internal;
TargetSetupPage::TargetSetupPage(QWidget *parent) : TargetSetupPage::TargetSetupPage(QWidget *parent)
WizardPage(parent), : WizardPage(parent)
m_ui(new TargetSetupPageUi), , m_tasksGenerator(defaultTasksGenerator({}))
m_importWidget(new ImportWidget(this)), , m_ui(new TargetSetupPageUi)
m_spacer(new QSpacerItem(0,0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding)) , m_importWidget(new ImportWidget(this))
, m_spacer(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding))
{ {
m_importWidget->setVisible(false); m_importWidget->setVisible(false);
@@ -217,9 +232,9 @@ void TargetSetupPage::initializePage()
} }
} }
void TargetSetupPage::setRequiredKitPredicate(const Kit::Predicate &predicate) void TargetSetupPage::setTasksGenerator(const TasksGenerator &tasksGenerator)
{ {
m_requiredPredicate = predicate; m_tasksGenerator = defaultTasksGenerator(tasksGenerator);
} }
QList<Core::Id> TargetSetupPage::selectedKits() const QList<Core::Id> TargetSetupPage::selectedKits() const
@@ -232,11 +247,6 @@ QList<Core::Id> TargetSetupPage::selectedKits() const
return result; return result;
} }
void TargetSetupPage::setPreferredKitPredicate(const Kit::Predicate &predicate)
{
m_preferredPredicate = predicate;
}
TargetSetupPage::~TargetSetupPage() TargetSetupPage::~TargetSetupPage()
{ {
disconnect(); disconnect();
@@ -396,8 +406,10 @@ void TargetSetupPage::selectAtLeastOneEnabledKit()
TargetSetupWidget *toCheckWidget = nullptr; TargetSetupWidget *toCheckWidget = nullptr;
const Kit *defaultKit = KitManager::defaultKit(); const Kit *defaultKit = KitManager::defaultKit();
auto isPreferred = [this](const TargetSetupWidget *w) { auto isPreferred = [this](const TargetSetupWidget *w) {
return w->isEnabled() && (!m_preferredPredicate || m_preferredPredicate(w->kit())); const Tasks tasks = m_tasksGenerator(w->kit());
return w->isEnabled() && tasks.isEmpty();
}; };
// Use default kit if that is preferred: // Use default kit if that is preferred:
@@ -620,12 +632,12 @@ void TargetSetupPage::removeAdditionalWidgets(QLayout *layout)
void TargetSetupPage::updateWidget(TargetSetupWidget *widget) void TargetSetupPage::updateWidget(TargetSetupWidget *widget)
{ {
QTC_ASSERT(widget, return ); QTC_ASSERT(widget, return );
widget->update(m_requiredPredicate); widget->update(m_tasksGenerator);
} }
bool TargetSetupPage::isUsable(const Kit *kit) const bool TargetSetupPage::isUsable(const Kit *kit) const
{ {
return kit->isValid() && (!m_requiredPredicate || m_requiredPredicate(kit)); return !containsType(m_tasksGenerator(kit), Task::Error);
} }
bool TargetSetupPage::setupProject(Project *project) bool TargetSetupPage::setupProject(Project *project)

View File

@@ -30,6 +30,7 @@
#include "kitinformation.h" #include "kitinformation.h"
#include "kitmanager.h" #include "kitmanager.h"
#include "projectimporter.h" #include "projectimporter.h"
#include "task.h"
#include <utils/wizardpage.h> #include <utils/wizardpage.h>
@@ -66,8 +67,7 @@ public:
void initializePage() override; void initializePage() override;
// Call these before initializePage! // Call these before initializePage!
void setRequiredKitPredicate(const Kit::Predicate &predicate); void setTasksGenerator(const TasksGenerator &tasksGenerator);
void setPreferredKitPredicate(const Kit::Predicate &predicate);
void setProjectPath(const Utils::FilePath &dir); void setProjectPath(const Utils::FilePath &dir);
void setProjectImporter(ProjectImporter *importer); void setProjectImporter(ProjectImporter *importer);
bool importLineEditHasFocus() const; bool importLineEditHasFocus() const;
@@ -125,8 +125,7 @@ private:
Internal::TargetSetupWidget *widget(const Core::Id kitId, Internal::TargetSetupWidget *widget(const Core::Id kitId,
Internal::TargetSetupWidget *fallback = nullptr) const; Internal::TargetSetupWidget *fallback = nullptr) const;
Kit::Predicate m_requiredPredicate; TasksGenerator m_tasksGenerator;
Kit::Predicate m_preferredPredicate;
QPointer<ProjectImporter> m_importer; QPointer<ProjectImporter> m_importer;
QLayout *m_baseLayout = nullptr; QLayout *m_baseLayout = nullptr;
Utils::FilePath m_projectPath; Utils::FilePath m_projectPath;

View File

@@ -224,24 +224,25 @@ void TargetSetupWidget::expandWidget()
m_detailsWidget->setState(Utils::DetailsWidget::Expanded); m_detailsWidget->setState(Utils::DetailsWidget::Expanded);
} }
void TargetSetupWidget::update(const Kit::Predicate &predicate) void TargetSetupWidget::update(const TasksGenerator &generator)
{ {
m_detailsWidget->setSummaryText(kit()->displayName()); const Tasks tasks = generator(kit());
// Kits that we deem invalid get a warning icon, but users can still select them, m_detailsWidget->setSummaryText(kit()->displayName());
// e.g. in case we misdetected an ABI mismatch. m_detailsWidget->setIcon(kit()->isValid() ? kit()->icon() : Icons::CRITICAL.icon());
// Kits that don't fulfill the project predicate are not selectable, because we cannot
const Task errorTask = Utils::findOrDefault(tasks, Utils::equal(&Task::type, Task::Error));
// Kits that where the taskGenarator reports an error are not selectable, because we cannot
// guarantee that we can handle the project sensibly (e.g. qmake project without Qt). // guarantee that we can handle the project sensibly (e.g. qmake project without Qt).
if (predicate && !predicate(kit())) { if (!errorTask.isNull()) {
toggleEnabled(false); toggleEnabled(false);
m_detailsWidget->setToolTip(kit()->toHtml(tasks, ""));
m_infoStore.clear(); m_infoStore.clear();
m_detailsWidget->setToolTip(tr("You cannot use this kit, because it does not fulfill "
"the project's prerequisites."));
return; return;
} }
toggleEnabled(true); toggleEnabled(true);
m_detailsWidget->setIcon(kit()->isValid() ? kit()->icon() : Icons::CRITICAL.icon());
m_detailsWidget->setToolTip(m_kit->toHtml());
updateDefaultBuildDirectories(); updateDefaultBuildDirectories();
} }

View File

@@ -70,7 +70,7 @@ public:
const QList<BuildInfo> selectedBuildInfoList() const; const QList<BuildInfo> selectedBuildInfoList() const;
void setProjectPath(const Utils::FilePath &projectPath); void setProjectPath(const Utils::FilePath &projectPath);
void expandWidget(); void expandWidget();
void update(const Kit::Predicate &predicate); void update(const TasksGenerator &generator);
signals: signals:
void selectedToggled() const; void selectedToggled() const;

View File

@@ -117,16 +117,15 @@ private:
/*! /*!
\class QmakeProject \class QmakeProject
QmakeProject manages information about an individual Qt 4 (.pro) project file. QmakeProject manages information about an individual qmake project file (.pro).
*/ */
static bool matchesKit(const Project *p, const Kit *kit) static QtSupport::BaseQtVersion *projectIsPartOfQt(const Project *p)
{ {
FilePath filePath = p->projectFilePath(); FilePath filePath = p->projectFilePath();
QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(kit);
return QtSupport::QtVersionManager::version([&filePath, version](const QtSupport::BaseQtVersion *v) { return QtSupport::QtVersionManager::version([&filePath](const QtSupport::BaseQtVersion *v) {
return v->isValid() && v->isSubProject(filePath) && v == version; return v->isValid() && v->isSubProject(filePath);
}); });
} }
@@ -138,8 +137,6 @@ QmakeProject::QmakeProject(const FilePath &fileName) :
setDisplayName(fileName.toFileInfo().completeBaseName()); setDisplayName(fileName.toFileInfo().completeBaseName());
setCanBuildProducts(); setCanBuildProducts();
setHasMakeInstallEquivalent(true); setHasMakeInstallEquivalent(true);
setPreferredKitPredicate([this](const Kit *kit) -> bool { return matchesKit(this, kit); });
} }
QmakeProject::~QmakeProject() QmakeProject::~QmakeProject()
@@ -603,12 +600,21 @@ void QmakeBuildSystem::buildFinished(bool success)
Tasks QmakeProject::projectIssues(const Kit *k) const Tasks QmakeProject::projectIssues(const Kit *k) const
{ {
Tasks result = Project::projectIssues(k); Tasks result = Project::projectIssues(k);
if (!QtSupport::QtKitAspect::qtVersion(k)) const QtSupport::BaseQtVersion *const qtFromKit = QtSupport::QtKitAspect::qtVersion(k);
if (!qtFromKit)
result.append(createProjectTask(Task::TaskType::Error, tr("No Qt version set in kit."))); result.append(createProjectTask(Task::TaskType::Error, tr("No Qt version set in kit.")));
else if (!QtSupport::QtKitAspect::qtVersion(k)->isValid()) else if (!qtFromKit->isValid())
result.append(createProjectTask(Task::TaskType::Error, tr("Qt version is invalid."))); result.append(createProjectTask(Task::TaskType::Error, tr("Qt version is invalid.")));
if (!ToolChainKitAspect::toolChain(k, ProjectExplorer::Constants::CXX_LANGUAGE_ID)) if (!ToolChainKitAspect::toolChain(k, ProjectExplorer::Constants::CXX_LANGUAGE_ID))
result.append(createProjectTask(Task::TaskType::Error, tr("No C++ compiler set in kit."))); result.append(createProjectTask(Task::TaskType::Error, tr("No C++ compiler set in kit.")));
const QtSupport::BaseQtVersion *const qtThatContainsProject = projectIsPartOfQt(this);
if (qtThatContainsProject && qtThatContainsProject != qtFromKit) {
result.append(CompileTask(Task::Warning,
tr("Project is part of Qt sources that do not match "
"the Qt defined in the Kit")));
}
return result; return result;
} }

View File

@@ -37,6 +37,7 @@
#include <projectexplorer/projectexplorer.h> #include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h> #include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/targetsetuppage.h> #include <projectexplorer/targetsetuppage.h>
#include <projectexplorer/task.h>
#include <qtsupport/qtkitinformation.h> #include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtsupportconstants.h> #include <qtsupport/qtsupportconstants.h>
@@ -178,14 +179,23 @@ BaseQmakeProjectWizardDialog::~BaseQmakeProjectWizardDialog()
int BaseQmakeProjectWizardDialog::addTargetSetupPage(int id) int BaseQmakeProjectWizardDialog::addTargetSetupPage(int id)
{ {
m_targetSetupPage = new ProjectExplorer::TargetSetupPage; m_targetSetupPage = new ProjectExplorer::TargetSetupPage;
const Core::Id platform = selectedPlatform();
QSet<Core::Id> features = {QtSupport::Constants::FEATURE_DESKTOP};
if (!platform.isValid())
m_targetSetupPage->setPreferredKitPredicate(QtKitAspect::qtVersionPredicate(features));
else
m_targetSetupPage->setPreferredKitPredicate(QtKitAspect::platformPredicate(platform));
m_targetSetupPage->setRequiredKitPredicate(QtKitAspect::qtVersionPredicate(requiredFeatures())); m_targetSetupPage->setTasksGenerator([this](const Kit *k) -> Tasks {
if (!QtKitAspect::qtVersionPredicate(requiredFeatures())(k))
return {
ProjectExplorer::CompileTask(Task::Error, tr("Required Qt features not present."))};
const Core::Id platform = selectedPlatform();
if (platform.isValid() && !QtKitAspect::platformPredicate(platform)(k))
return {ProjectExplorer::CompileTask(
ProjectExplorer::Task::Warning,
tr("Qt version does not target the expected platform."))};
QSet<Core::Id> features = {QtSupport::Constants::FEATURE_DESKTOP};
if (!QtKitAspect::qtVersionPredicate(features)(k))
return {ProjectExplorer::CompileTask(ProjectExplorer::Task::Unknown,
tr("Qt version does not provide all features."))};
return {};
});
resize(900, 450); resize(900, 450);
if (id >= 0) if (id >= 0)