From a0e7b41ec5d88a74e4d7575f2ce6c6a1e3db3420 Mon Sep 17 00:00:00 2001
From: Eike Ziller
Date: Mon, 17 Jan 2022 15:31:20 +0100
Subject: [PATCH] UpdateInfo: (Optionally) Inform user about new Qt versions
New Qt patch or minor releases are not considered "updates" by the
installer framework. The Qt packages are set up this way, to allow
installations of multiple patch versions of the same minor Qt versions.
Explicitly check if a newer Qt version than the latest installed one was
released since we last checked for updates, and inform the user with a
link to the Qt blog, and a button to start the package manager. We don't
show anything when checking for updates the very first time, since at
that point we must assume that the user installed the newest Qt version
they want.
Fixes: QTCREATORBUG-26708
Change-Id: I68374b3242b64d392a16de1abaac1292e0d534cf
Reviewed-by: Qt CI Bot
Reviewed-by: Christian Stenger
Reviewed-by:
---
src/plugins/updateinfo/settingspage.cpp | 4 +-
src/plugins/updateinfo/settingspage.ui | 41 ++--
src/plugins/updateinfo/updateinfoplugin.cpp | 240 +++++++++++++++++---
src/plugins/updateinfo/updateinfoplugin.h | 6 +
4 files changed, 240 insertions(+), 51 deletions(-)
diff --git a/src/plugins/updateinfo/settingspage.cpp b/src/plugins/updateinfo/settingspage.cpp
index ad12cd5528e..afc89a30739 100644
--- a/src/plugins/updateinfo/settingspage.cpp
+++ b/src/plugins/updateinfo/settingspage.cpp
@@ -37,8 +37,6 @@
namespace UpdateInfo {
namespace Internal {
-const char FILTER_OPTIONS_PAGE_ID[] = "Update";
-
class UpdateInfoSettingsPageWidget final : public Core::IOptionsPageWidget
{
Q_DECLARE_TR_FUNCTIONS(UpdateInfo::Internal::UpdateInfoSettingsPage)
@@ -60,6 +58,7 @@ public:
}
m_ui.m_updatesGroupBox->setChecked(m_plugin->isAutomaticCheck());
+ m_ui.m_checkForNewQtVersions->setChecked(m_plugin->isCheckingForQtVersions());
updateLastCheckDate();
checkRunningChanged(m_plugin->isCheckForUpdatesRunning());
@@ -153,6 +152,7 @@ void UpdateInfoSettingsPageWidget::apply()
{
m_plugin->setCheckUpdateInterval(currentCheckInterval());
m_plugin->setAutomaticCheck(m_ui.m_updatesGroupBox->isChecked());
+ m_plugin->setCheckingForQtVersions(m_ui.m_checkForNewQtVersions->isChecked());
}
// SettingsPage
diff --git a/src/plugins/updateinfo/settingspage.ui b/src/plugins/updateinfo/settingspage.ui
index 5efc6b1b88e..09d87019167 100644
--- a/src/plugins/updateinfo/settingspage.ui
+++ b/src/plugins/updateinfo/settingspage.ui
@@ -26,13 +26,6 @@
true
- -
-
-
- Check interval basis:
-
-
-
-
@@ -49,13 +42,6 @@
- -
-
-
- -1
-
-
-
-
@@ -76,6 +62,20 @@
+ -
+
+
+ Check interval basis:
+
+
+
+ -
+
+
+ -1
+
+
+
-
@@ -83,6 +83,13 @@
+ -
+
+
+ Check for new Qt versions
+
+
+
@@ -135,6 +142,12 @@
Qt::Vertical
+
+
+ 0
+ 0
+
+
diff --git a/src/plugins/updateinfo/updateinfoplugin.cpp b/src/plugins/updateinfo/updateinfoplugin.cpp
index edabb344064..353a5d703fe 100644
--- a/src/plugins/updateinfo/updateinfoplugin.cpp
+++ b/src/plugins/updateinfo/updateinfoplugin.cpp
@@ -43,22 +43,27 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
+#include
-namespace {
- static const char UpdaterGroup[] = "Updater";
- static const char MaintenanceToolKey[] = "MaintenanceTool";
- static const char AutomaticCheckKey[] = "AutomaticCheck";
- static const char CheckIntervalKey[] = "CheckUpdateInterval";
- static const char LastCheckDateKey[] = "LastCheckDate";
- static const quint32 OneMinute = 60000;
- static const quint32 OneHour = 3600000;
- static const char InstallUpdates[] = "UpdateInfo.InstallUpdates";
-}
+Q_LOGGING_CATEGORY(log, "qtc.updateinfo", QtWarningMsg)
+
+const char UpdaterGroup[] = "Updater";
+const char MaintenanceToolKey[] = "MaintenanceTool";
+const char AutomaticCheckKey[] = "AutomaticCheck";
+const char CheckForNewQtVersionsKey[] = "CheckForNewQtVersions";
+const char CheckIntervalKey[] = "CheckUpdateInterval";
+const char LastCheckDateKey[] = "LastCheckDate";
+const char LastMaxQtVersionKey[] = "LastMaxQtVersion";
+const quint32 OneMinute = 60000;
+const quint32 OneHour = 3600000;
+const char InstallUpdates[] = "UpdateInfo.InstallUpdates";
+const char InstallQtUpdates[] = "UpdateInfo.InstallQtUpdates";
using namespace Core;
@@ -78,9 +83,11 @@ public:
{
bool automaticCheck = true;
UpdateInfoPlugin::CheckUpdateInterval checkInterval = UpdateInfoPlugin::WeeklyCheck;
+ bool checkForQtVersions = true;
};
Settings m_settings;
QDate m_lastCheckDate;
+ QVersionNumber m_lastMaxQtVersion;
};
UpdateInfoPlugin::UpdateInfoPlugin()
@@ -140,6 +147,14 @@ void UpdateInfoPlugin::startCheckForUpdates()
[](int /*exitCode*/) {
return Utils::QtcProcess::FinishedWithSuccess;
});
+ if (d->m_settings.checkForQtVersions) {
+ d->m_checkUpdatesCommand
+ ->addJob({Utils::FilePath::fromString(d->m_maintenanceTool),
+ {"se", "qt[.]qt[0-9][.][0-9]+$", "-g", "*=false,ifw.package.*=true"}},
+ 60 * 3, // 3 minutes timeout
+ /*workingDirectory=*/{},
+ [](int /*exitCode*/) { return Utils::QtcProcess::FinishedWithSuccess; });
+ }
d->m_checkUpdatesCommand->execute();
d->m_progress = d->m_checkUpdatesCommand->futureProgress();
if (d->m_progress) {
@@ -189,44 +204,173 @@ static QList availableUpdates(const QDomDocument &document)
return result;
}
+struct QtPackage
+{
+ QString displayName;
+ QVersionNumber version;
+ bool installed;
+ bool isPrerelease;
+};
+
+static QList availableQtPackages(const QDomDocument &document)
+{
+ if (document.isNull() || !document.firstChildElement().hasChildNodes())
+ return {};
+ QList result;
+ const QDomNodeList packages = document.firstChildElement().elementsByTagName("package");
+ for (int i = 0; i < packages.size(); ++i) {
+ const QDomNode node = packages.item(i);
+ if (node.isElement()) {
+ const QDomElement element = node.toElement();
+ if (element.hasAttribute("displayname") && element.hasAttribute("name")
+ && element.hasAttribute("version")) {
+ QtPackage package{element.attribute("displayname"),
+ QVersionNumber::fromString(element.attribute("version")),
+ element.hasAttribute("installedVersion")};
+ // Heuristic: Prerelease if the name is not "Qt x.y.z"
+ // (prereleases are named "Qt x.y.z-alpha" etc)
+ package.isPrerelease = package.displayName
+ != QString("Qt %1").arg(package.version.toString());
+ result.append(package);
+ }
+ }
+ }
+ std::sort(result.begin(), result.end(), [](const QtPackage &p1, const QtPackage &p2) {
+ return p1.version > p2.version;
+ });
+ return result;
+}
+
+// Expects packages to be sorted, high version first.
+static Utils::optional highestInstalledQt(const QList &packages)
+{
+ const auto highestInstalledIt = std::find_if(packages.cbegin(),
+ packages.cend(),
+ [](const QtPackage &p) { return p.installed; });
+ if (highestInstalledIt == packages.cend()) // Qt not installed
+ return {};
+ return *highestInstalledIt;
+}
+
+// Expects packages to be sorted, high version first.
+static Utils::optional qtToNagAbout(const QList &allPackages,
+ QVersionNumber *highestSeen)
+{
+ // Filter out any Qt prereleases
+ const QList packages = Utils::filtered(allPackages, [](const QtPackage &p) {
+ return !p.isPrerelease;
+ });
+ if (packages.isEmpty())
+ return {};
+ const QtPackage highest = packages.constFirst();
+ qCDebug(log) << "Highest available (non-prerelease) Qt:" << highest.version;
+ qCDebug(log) << "Highest previously seen (non-prerelease) Qt:" << *highestSeen;
+ // if the highestSeen version is null, we don't know if the Qt version is new, and better don't nag
+ const bool isNew = !highestSeen->isNull() && highest.version > *highestSeen;
+ if (highestSeen->isNull() || isNew)
+ *highestSeen = highest.version;
+ if (!isNew)
+ return {};
+ const Utils::optional highestInstalled = highestInstalledQt(packages);
+ qCDebug(log) << "Highest installed Qt:"
+ << qPrintable(highestInstalled ? highestInstalled->version.toString()
+ : QString("none"));
+ if (!highestInstalled) // don't nag if no Qt is installed at all
+ return {};
+ if (highestInstalled->version == highest.version)
+ return {};
+ return highest;
+}
+
+static void showUpdateInfo(const QList &updates, const std::function &startUpdater)
+{
+ Utils::InfoBarEntry info(InstallUpdates,
+ UpdateInfoPlugin::tr("New updates are available. Start the update?"));
+ info.addCustomButton(UpdateInfoPlugin::tr("Start Update"), [startUpdater] {
+ Core::ICore::infoBar()->removeInfo(InstallUpdates);
+ startUpdater();
+ });
+ info.setDetailsWidgetCreator([updates]() -> QWidget * {
+ const QString updateText = Utils::transform(updates, [](const Update &u) {
+ return u.version.isEmpty()
+ ? u.name
+ : UpdateInfoPlugin::tr("%1 (%2)",
+ "Package name and version")
+ .arg(u.name, u.version);
+ }).join("");
+ auto label = new QLabel;
+ label->setText("" + UpdateInfoPlugin::tr("Available updates:") + "
");
+ label->setContentsMargins(0, 0, 0, 8);
+ return label;
+ });
+ Core::ICore::infoBar()->removeInfo(InstallUpdates); // remove any existing notifications
+ Core::ICore::infoBar()->unsuppressInfo(InstallUpdates);
+ Core::ICore::infoBar()->addInfo(info);
+}
+
+static void showQtUpdateInfo(const QtPackage &package,
+ const std::function &startPackageManager)
+{
+ Utils::InfoBarEntry info(InstallQtUpdates,
+ UpdateInfoPlugin::tr(
+ "%1 is available. Check the Qt blog for details.")
+ .arg(package.displayName));
+ info.addCustomButton(UpdateInfoPlugin::tr("Start Package Manager"), [startPackageManager] {
+ Core::ICore::infoBar()->removeInfo(InstallQtUpdates);
+ startPackageManager();
+ });
+ info.addCustomButton(UpdateInfoPlugin::tr("Open Settings"), [] {
+ Core::ICore::infoBar()->removeInfo(InstallQtUpdates);
+ Core::ICore::showOptionsDialog(FILTER_OPTIONS_PAGE_ID);
+ });
+ Core::ICore::infoBar()->removeInfo(InstallQtUpdates); // remove any existing notifications
+ Core::ICore::infoBar()->unsuppressInfo(InstallQtUpdates);
+ Core::ICore::infoBar()->addInfo(info);
+}
+
void UpdateInfoPlugin::checkForUpdatesFinished()
{
setLastCheckDate(QDate::currentDate());
QDomDocument document;
- document.setContent(d->m_collectedOutput);
+ // since the output can contain two toplevel items from the two separate MaintenanceTool runs,
+ // surround with a toplevel element
+ const QString xml = d->m_collectedOutput.isEmpty()
+ ? QString()
+ : ("" + d->m_collectedOutput + "");
+ qCDebug(log) << "--- MaintenanceTool output (combined):";
+ qCDebug(log) << qPrintable(xml);
+ document.setContent(xml);
stopCheckForUpdates();
- if (!document.isNull() && document.firstChildElement().hasChildNodes()) {
+ const QList updates = availableUpdates(document);
+ const QList qtPackages = availableQtPackages(document);
+ if (log().isDebugEnabled()) {
+ qCDebug(log) << "--- Available updates:";
+ for (const Update &u : updates)
+ qCDebug(log) << u.name << u.version;
+ qCDebug(log) << "--- Available Qt packages:";
+ for (const QtPackage &p : qtPackages) {
+ qCDebug(log) << p.displayName << p.version << "installed:" << p.installed
+ << "prerelease:" << p.isPrerelease;
+ }
+ }
+ Utils::optional qtToNag = qtToNagAbout(qtPackages, &d->m_lastMaxQtVersion);
+
+ if (!updates.isEmpty() || qtToNag) {
// progress details are shown until user interaction for the "no updates" case,
// so we can show the "No updates found" text, but if we have updates we don't
// want to keep it around
if (d->m_progress)
d->m_progress->setKeepOnFinish(FutureProgress::HideOnFinish);
emit newUpdatesAvailable(true);
- Utils::InfoBarEntry info(InstallUpdates, tr("New updates are available. Start the update?"));
- info.addCustomButton(tr("Start Update"), [this] {
- Core::ICore::infoBar()->removeInfo(InstallUpdates);
- startUpdater();
- });
- const QList updates = availableUpdates(document);
- info.setDetailsWidgetCreator([updates]() -> QWidget * {
- const QString updateText = Utils::transform(updates, [](const Update &u) {
- return u.version.isEmpty()
- ? u.name
- : tr("%1 (%2)", "Package name and version")
- .arg(u.name, u.version);
- }).join("");
- auto label = new QLabel;
- label->setText("" + tr("Available updates:") + "
");
- label->setContentsMargins(0, 0, 0, 8);
- return label;
- });
- Core::ICore::infoBar()->removeInfo(InstallUpdates); // remove any existing notifications
- Core::ICore::infoBar()->unsuppressInfo(InstallUpdates);
- Core::ICore::infoBar()->addInfo(info);
+ if (!updates.isEmpty())
+ showUpdateInfo(updates, [this] { startUpdater(); });
+ if (qtToNag)
+ showQtUpdateInfo(*qtToNag, [this] { startPackageManager(); });
} else {
emit newUpdatesAvailable(false);
if (d->m_progress)
@@ -298,6 +442,11 @@ void UpdateInfoPlugin::loadSettings() const
if (ok)
d->m_settings.checkInterval = static_cast(newValue);
}
+ const QString lastMaxQtVersionString = settings->value(updaterKey + LastMaxQtVersionKey)
+ .toString();
+ d->m_lastMaxQtVersion = QVersionNumber::fromString(lastMaxQtVersionString);
+ d->m_settings.checkForQtVersions
+ = settings->value(updaterKey + CheckForNewQtVersionsKey, def.checkForQtVersions).toBool();
}
void UpdateInfoPlugin::saveSettings()
@@ -318,6 +467,10 @@ void UpdateInfoPlugin::saveSettings()
settings->setValueWithDefault(CheckIntervalKey,
QString::fromUtf8(me.valueToKey(d->m_settings.checkInterval)),
QString::fromUtf8(me.valueToKey(def.checkInterval)));
+ settings->setValueWithDefault(LastMaxQtVersionKey, d->m_lastMaxQtVersion.toString());
+ settings->setValueWithDefault(CheckForNewQtVersionsKey,
+ d->m_settings.checkForQtVersions,
+ def.checkForQtVersions);
settings->endGroup();
}
@@ -351,6 +504,16 @@ void UpdateInfoPlugin::setCheckUpdateInterval(UpdateInfoPlugin::CheckUpdateInter
d->m_settings.checkInterval = interval;
}
+bool UpdateInfoPlugin::isCheckingForQtVersions() const
+{
+ return d->m_settings.checkForQtVersions;
+}
+
+void UpdateInfoPlugin::setCheckingForQtVersions(bool on)
+{
+ d->m_settings.checkForQtVersions = on;
+}
+
QDate UpdateInfoPlugin::lastCheckDate() const
{
return d->m_lastCheckDate;
@@ -384,7 +547,14 @@ QDate UpdateInfoPlugin::nextCheckDate(CheckUpdateInterval interval) const
void UpdateInfoPlugin::startUpdater()
{
- Utils::QtcProcess::startDetached({Utils::FilePath::fromString(d->m_maintenanceTool), {"--updater"}});
+ Utils::QtcProcess::startDetached(
+ {Utils::FilePath::fromString(d->m_maintenanceTool), {"--updater"}});
+}
+
+void UpdateInfoPlugin::startPackageManager()
+{
+ Utils::QtcProcess::startDetached(
+ {Utils::FilePath::fromString(d->m_maintenanceTool), {"--start-package-manager"}});
}
} //namespace Internal
diff --git a/src/plugins/updateinfo/updateinfoplugin.h b/src/plugins/updateinfo/updateinfoplugin.h
index 98c2b9904d6..78177fee919 100644
--- a/src/plugins/updateinfo/updateinfoplugin.h
+++ b/src/plugins/updateinfo/updateinfoplugin.h
@@ -35,6 +35,8 @@ namespace UpdateInfo {
namespace Internal {
+const char FILTER_OPTIONS_PAGE_ID[] = "Update";
+
class UpdateInfoPluginPrivate;
class UpdateInfoPlugin final : public ExtensionSystem::IPlugin
@@ -61,6 +63,9 @@ public:
CheckUpdateInterval checkUpdateInterval() const;
void setCheckUpdateInterval(CheckUpdateInterval interval);
+ bool isCheckingForQtVersions() const;
+ void setCheckingForQtVersions(bool on);
+
QDate lastCheckDate() const;
QDate nextCheckDate() const;
QDate nextCheckDate(CheckUpdateInterval interval) const;
@@ -81,6 +86,7 @@ private:
void doAutoCheckForUpdates();
void startUpdater();
+ void startPackageManager();
void stopCheckForUpdates();
void collectCheckForUpdatesOutput(const QString &contents);