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 QSet<Id> preferred
= evaluate(m_preferredFeatures, wiz->value(QLatin1String("PreferredFeatures")), wiz);
const QSet<Id> required
= evaluate(m_requiredFeatures, wiz->value(QLatin1String("RequiredFeatures")), wiz);
const QSet<Id> required = evaluate(m_requiredFeatures,
wiz->value(QLatin1String("RequiredFeatures")),
wiz);
setRequiredKitPredicate([required](const Kit *k) { return k->hasFeatures(required); });
setPreferredKitPredicate([platform, preferred](const Kit *k) {
return k->supportedPlatforms().contains(platform) && k->hasFeatures(preferred);
setTasksGenerator([required, preferred, platform](const Kit *k) -> Tasks {
if (!k->hasFeatures(required))
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())));

View File

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

View File

@@ -118,7 +118,7 @@ public:
void addToEnvironment(Utils::Environment &env) 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;
void copyFrom(const Kit *k);
@@ -173,6 +173,8 @@ private:
Kit *m_kit;
};
using TasksGenerator = std::function<Tasks(const Kit *)>;
} // namespace ProjectExplorer
Q_DECLARE_METATYPE(ProjectExplorer::Kit *)

View File

@@ -201,9 +201,6 @@ public:
QString m_displayName;
Kit::Predicate m_requiredKitPredicate;
Kit::Predicate m_preferredKitPredicate;
Utils::MacroExpander m_macroExpander;
Utils::FilePath m_rootProjectDirectory;
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!
d->m_containerNode = std::make_unique<ContainerNode>(this);
setRequiredKitPredicate([this](const Kit *k) {
return !containsType(projectIssues(k), Task::TaskType::Error);
});
}
Project::~Project()
@@ -974,31 +967,11 @@ ProjectImporter *Project::projectImporter() const
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()
{
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)
{
d->m_extraData.insert(key, data);

View File

@@ -144,9 +144,6 @@ public:
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
// of configuration.
bool knowsAllBuildExecutables() const;
@@ -208,12 +205,6 @@ protected:
void createTargetFromMap(const QVariantMap &map, int index);
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 setId(Core::Id id);

View File

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

View File

@@ -26,14 +26,15 @@
#include "targetsetuppage.h"
#include "buildconfiguration.h"
#include "buildinfo.h"
#include "importwidget.h"
#include "kit.h"
#include "kitmanager.h"
#include "importwidget.h"
#include "project.h"
#include "projectexplorerconstants.h"
#include "session.h"
#include "target.h"
#include "targetsetupwidget.h"
#include "task.h"
#include <coreplugin/icore.h>
@@ -159,13 +160,27 @@ public:
} // 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;
TargetSetupPage::TargetSetupPage(QWidget *parent) :
WizardPage(parent),
m_ui(new TargetSetupPageUi),
m_importWidget(new ImportWidget(this)),
m_spacer(new QSpacerItem(0,0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding))
TargetSetupPage::TargetSetupPage(QWidget *parent)
: WizardPage(parent)
, m_tasksGenerator(defaultTasksGenerator({}))
, m_ui(new TargetSetupPageUi)
, m_importWidget(new ImportWidget(this))
, m_spacer(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding))
{
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
@@ -232,11 +247,6 @@ QList<Core::Id> TargetSetupPage::selectedKits() const
return result;
}
void TargetSetupPage::setPreferredKitPredicate(const Kit::Predicate &predicate)
{
m_preferredPredicate = predicate;
}
TargetSetupPage::~TargetSetupPage()
{
disconnect();
@@ -396,8 +406,10 @@ void TargetSetupPage::selectAtLeastOneEnabledKit()
TargetSetupWidget *toCheckWidget = nullptr;
const Kit *defaultKit = KitManager::defaultKit();
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:
@@ -620,12 +632,12 @@ void TargetSetupPage::removeAdditionalWidgets(QLayout *layout)
void TargetSetupPage::updateWidget(TargetSetupWidget *widget)
{
QTC_ASSERT(widget, return );
widget->update(m_requiredPredicate);
widget->update(m_tasksGenerator);
}
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)

View File

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

View File

@@ -224,24 +224,25 @@ void TargetSetupWidget::expandWidget()
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,
// e.g. in case we misdetected an ABI mismatch.
// Kits that don't fulfill the project predicate are not selectable, because we cannot
m_detailsWidget->setSummaryText(kit()->displayName());
m_detailsWidget->setIcon(kit()->isValid() ? kit()->icon() : Icons::CRITICAL.icon());
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).
if (predicate && !predicate(kit())) {
if (!errorTask.isNull()) {
toggleEnabled(false);
m_detailsWidget->setToolTip(kit()->toHtml(tasks, ""));
m_infoStore.clear();
m_detailsWidget->setToolTip(tr("You cannot use this kit, because it does not fulfill "
"the project's prerequisites."));
return;
}
toggleEnabled(true);
m_detailsWidget->setIcon(kit()->isValid() ? kit()->icon() : Icons::CRITICAL.icon());
m_detailsWidget->setToolTip(m_kit->toHtml());
updateDefaultBuildDirectories();
}

View File

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

View File

@@ -117,16 +117,15 @@ private:
/*!
\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();
QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(kit);
return QtSupport::QtVersionManager::version([&filePath, version](const QtSupport::BaseQtVersion *v) {
return v->isValid() && v->isSubProject(filePath) && v == version;
return QtSupport::QtVersionManager::version([&filePath](const QtSupport::BaseQtVersion *v) {
return v->isValid() && v->isSubProject(filePath);
});
}
@@ -138,8 +137,6 @@ QmakeProject::QmakeProject(const FilePath &fileName) :
setDisplayName(fileName.toFileInfo().completeBaseName());
setCanBuildProducts();
setHasMakeInstallEquivalent(true);
setPreferredKitPredicate([this](const Kit *kit) -> bool { return matchesKit(this, kit); });
}
QmakeProject::~QmakeProject()
@@ -603,12 +600,21 @@ void QmakeBuildSystem::buildFinished(bool success)
Tasks QmakeProject::projectIssues(const Kit *k) const
{
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.")));
else if (!QtSupport::QtKitAspect::qtVersion(k)->isValid())
else if (!qtFromKit->isValid())
result.append(createProjectTask(Task::TaskType::Error, tr("Qt version is invalid.")));
if (!ToolChainKitAspect::toolChain(k, ProjectExplorer::Constants::CXX_LANGUAGE_ID))
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;
}

View File

@@ -37,6 +37,7 @@
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/targetsetuppage.h>
#include <projectexplorer/task.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtsupportconstants.h>
@@ -178,14 +179,23 @@ BaseQmakeProjectWizardDialog::~BaseQmakeProjectWizardDialog()
int BaseQmakeProjectWizardDialog::addTargetSetupPage(int id)
{
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);
if (id >= 0)