summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArttu Tarkiainen <arttu.tarkiainen@qt.io>2023-01-10 14:17:21 +0200
committerArttu Tarkiainen <arttu.tarkiainen@qt.io>2023-01-26 15:25:41 +0200
commit50575212455fbd3109adbe92d8509fca3c51850a (patch)
treec098c162795ec071653ffe082dda782b61edb813
parentf5750078a7a75e7ad7b0260b54236ee87f3900f9 (diff)
Support detecting invalid dependency resolutions on component selection
IFW could already previously detect circular and missing dependencies by running a precheck calculation when building the component tree. However, there can be also cases of where the user selection results in unsolvable changes to components. One such case was the erroneous possibility to uninstall a component marked with ForcedInstallation, if any of its dependencies was checkable and the user unselected it for uninstallation. Add support for reacting to such errors in the component tree view, displaying a message box about the invalid dependency resolvation error, and blocking further navigation until the component selection passes the dependency calculations. Also attempt some refactoring the related code to simplify the different call sequences resulting in recalculation of the components. Change-Id: I9dc78f858bd4be7932f89f8e14bbfd97fbd3a0f6 Reviewed-by: Katja Marttila <katja.marttila@qt.io>
-rw-r--r--doc/scripting-api/packagemanagercore.qdoc14
-rw-r--r--src/libs/installer/component.cpp11
-rw-r--r--src/libs/installer/component.h3
-rw-r--r--src/libs/installer/componentselectionpage_p.cpp20
-rw-r--r--src/libs/installer/componentselectionpage_p.h4
-rw-r--r--src/libs/installer/packagemanagercore.cpp140
-rw-r--r--src/libs/installer/packagemanagercore.h11
-rw-r--r--src/libs/installer/packagemanagercore_p.cpp37
-rw-r--r--src/libs/installer/packagemanagercore_p.h4
-rw-r--r--src/libs/installer/packagemanagergui.cpp20
-rw-r--r--src/libs/installer/uninstallercalculator.cpp52
-rw-r--r--src/libs/installer/uninstallercalculator.h8
-rw-r--r--tests/auto/installer/brokeninstaller/tst_brokeninstaller.cpp4
-rw-r--r--tests/auto/installer/packagemanagercore/tst_packagemanagercore.cpp3
14 files changed, 205 insertions, 126 deletions
diff --git a/doc/scripting-api/packagemanagercore.qdoc b/doc/scripting-api/packagemanagercore.qdoc
index b14fd957f..ac6830d33 100644
--- a/doc/scripting-api/packagemanagercore.qdoc
+++ b/doc/scripting-api/packagemanagercore.qdoc
@@ -325,15 +325,25 @@
*/
/*!
+ \qmlmethod boolean installer::recalculateAllComponents()
+
+ Recalculates all components to install and uninstall. Returns \c true
+ on success, \c false otherwise.
+*/
+
+/*!
\qmlmethod void installer::componentsToInstallNeedsRecalculation()
+ \deprecated [4.5] Use recalculateAllComponents() instead.
Ensures that component dependencies are re-calculated.
*/
/*!
\qmlmethod void installer::clearComponentsToInstallCalculated()
-
- Forces a recalculation of components to install.
+ \deprecated [4.5] Installer framework recalculates components each time the calculation
+ of components to install is requested, so there is no need to call this anymore, and the
+ method does nothing. On previous versions calling this forced a recalculation of
+ components to install.
*/
/*!
diff --git a/src/libs/installer/component.cpp b/src/libs/installer/component.cpp
index 36d207af7..77a4775c4 100644
--- a/src/libs/installer/component.cpp
+++ b/src/libs/installer/component.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -1332,6 +1332,15 @@ bool Component::forcedInstallation() const
}
/*!
+ Returns whether this component is essential. Essential components
+ are always installed, and updated before other components.
+*/
+bool Component::isEssential() const
+{
+ return d->m_vars.value(scEssential, scFalse).toLower() == scTrue;
+}
+
+/*!
Sets the validator callback name to \a name.
*/
void Component::setValidatorCallbackName(const QString &name)
diff --git a/src/libs/installer/component.h b/src/libs/installer/component.h
index e5f1b38da..35b0ea0b2 100644
--- a/src/libs/installer/component.h
+++ b/src/libs/installer/component.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -208,6 +208,7 @@ public:
bool isVirtual() const;
bool isSelected() const;
bool forcedInstallation() const;
+ bool isEssential() const;
void setValidatorCallbackName(const QString &name);
diff --git a/src/libs/installer/componentselectionpage_p.cpp b/src/libs/installer/componentselectionpage_p.cpp
index 5f58ae5ba..1cb10336c 100644
--- a/src/libs/installer/componentselectionpage_p.cpp
+++ b/src/libs/installer/componentselectionpage_p.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -72,6 +72,7 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP
, m_categoryWidget(Q_NULLPTR)
, m_categoryLayoutVisible(false)
, m_proxyModel(new ComponentSortFilterProxyModel(q))
+ , m_componentsResolved(false)
, m_headerStretchLastSection(false)
{
m_treeView->setObjectName(QLatin1String("ComponentsTreeView"));
@@ -390,6 +391,15 @@ void ComponentSelectionPagePrivate::expandSearchResults()
restoreHeaderResizeModes();
}
+/*!
+ Returns \c true if the components to install and uninstall are calculated
+ successfully, \c false otherwise.
+*/
+bool ComponentSelectionPagePrivate::componentsResolved() const
+{
+ return m_componentsResolved;
+}
+
void ComponentSelectionPagePrivate::currentSelectedChanged(const QModelIndex &current)
{
if (!current.isValid())
@@ -536,6 +546,14 @@ void ComponentSelectionPagePrivate::selectDefault()
void ComponentSelectionPagePrivate::onModelStateChanged(QInstaller::ComponentModel::ModelState state)
{
+ m_componentsResolved = m_core->recalculateAllComponents();
+ if (!m_componentsResolved) {
+ const QString error = !m_core->componentsToInstallError().isEmpty()
+ ? m_core->componentsToInstallError() : m_core->componentsToUninstallError();
+ MessageBoxHandler::critical(MessageBoxHandler::currentBestSuitParent(),
+ QLatin1String("CalculateComponentsError"), tr("Error"), error);
+ }
+
q->setModified(state.testFlag(ComponentModel::DefaultChecked) == false);
// If all components in the checked list are only checkable when run without forced
// installation, set ComponentModel::AllUnchecked as well, as we cannot uncheck anything.
diff --git a/src/libs/installer/componentselectionpage_p.h b/src/libs/installer/componentselectionpage_p.h
index 7c2f6a38e..f06d376f4 100644
--- a/src/libs/installer/componentselectionpage_p.h
+++ b/src/libs/installer/componentselectionpage_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -74,6 +74,7 @@ public:
void updateTreeView();
void expandDefault();
void expandSearchResults();
+ bool componentsResolved() const;
public slots:
void currentSelectedChanged(const QModelIndex &current);
@@ -118,6 +119,7 @@ private:
QStackedLayout *m_stackedLayout;
ComponentSortFilterProxyModel *m_proxyModel;
QLineEdit *m_searchLineEdit;
+ bool m_componentsResolved;
bool m_headerStretchLastSection;
QHash<int, QHeaderView::ResizeMode> m_headerResizeModes;
diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp
index c0902569d..db6c692e2 100644
--- a/src/libs/installer/packagemanagercore.cpp
+++ b/src/libs/installer/packagemanagercore.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -532,7 +532,6 @@ void PackageManagerCore::reset()
d->m_status = PackageManagerCore::Unfinished;
d->m_installerBaseBinaryUnreplaced.clear();
d->m_coreCheckedHash.clear();
- d->m_componentsToInstallCalculated = false;
}
/*!
@@ -602,32 +601,45 @@ bool PackageManagerCore::clearLocalCache(QString *error)
}
/*!
+ \deprecated [4.5] Use recalculateAllComponents() instead.
+
\sa {installer::componentsToInstallNeedsRecalculation}{installer.componentsToInstallNeedsRecalculation}
*/
void PackageManagerCore::componentsToInstallNeedsRecalculation()
{
- d->clearInstallerCalculator();
+ recalculateAllComponents();
+}
- QList<Component*> selectedComponentsToInstall = componentsMarkedForInstallation();
+/*!
+ \fn QInstaller::PackageManagerCore::clearComponentsToInstallCalculated()
- d->m_componentsToInstallCalculated =
- d->installerCalculator()->appendComponentsToInstall(selectedComponentsToInstall);
+ \deprecated [4.5] Installer framework recalculates components each time the calculation
+ of components to install is requested, so there is no need to call this anymore, and the
+ method does nothing. On previous versions calling this forced a recalculation of
+ components to install.
- d->calculateUninstallComponents();
- d->updateComponentCheckedState();
+ \sa {installer::clearComponentsToInstallCalculated}{installer.clearComponentsToInstallCalculated}
+ */
+/*!
+ Recalculates all components to install and uninstall. Returns \c true
+ on success, \c false otherwise. Detailed error messages can be retrieved
+ with {installer::componentsToInstallError} and {installer::componentsToUninstallError}.
+ */
+bool PackageManagerCore::recalculateAllComponents()
+{
+ if (!calculateComponentsToInstall())
+ return false;
+ if (!isInstaller() && !calculateComponentsToUninstall())
+ return false;
+
+ // update install actions
+ d->updateComponentCheckedState();
// update all nodes uncompressed size
foreach (Component *const component, components(ComponentType::Root))
component->updateUncompressedSize(); // this is a recursive call
-}
-/*!
- Forces a recalculation of components to install.
- \sa {installer::clearComponentsToInstallCalculated}{installer.clearComponentsToInstallCalculated}
- */
-void PackageManagerCore::clearComponentsToInstallCalculated()
-{
- d->m_componentsToInstallCalculated = false;
+ return true;
}
/*!
@@ -2154,8 +2166,9 @@ QList<Component *> PackageManagerCore::componentsMarkedForInstallation() const
}
/*!
- Determines which components to install based on the current run mode and returns an
- ordered list of components to install. Also auto installed dependencies are resolved.
+ Determines which components to install based on the current run mode, including dependencies
+ and automatic dependencies. Returns \c true on success, \c false otherwise.
+
The aboutCalculateComponentsToInstall() signal is emitted
before the calculation starts, the finishedCalculateComponentsToInstall()
signal once all calculations are done.
@@ -2166,16 +2179,15 @@ QList<Component *> PackageManagerCore::componentsMarkedForInstallation() const
bool PackageManagerCore::calculateComponentsToInstall() const
{
emit aboutCalculateComponentsToInstall();
- if (!d->m_componentsToInstallCalculated) {
- d->clearInstallerCalculator();
- QList<Component*> selectedComponentsToInstall = componentsMarkedForInstallation();
- d->storeCheckState();
- d->m_componentsToInstallCalculated =
- d->installerCalculator()->appendComponentsToInstall(selectedComponentsToInstall);
- }
+ d->clearInstallerCalculator();
+ const QList<Component*> selectedComponentsToInstall = componentsMarkedForInstallation();
+
+ const bool componentsToInstallCalculated =
+ d->installerCalculator()->appendComponentsToInstall(selectedComponentsToInstall);
+
emit finishedCalculateComponentsToInstall();
- return d->m_componentsToInstallCalculated;
+ return componentsToInstallCalculated;
}
/*!
@@ -2187,25 +2199,30 @@ QList<Component*> PackageManagerCore::orderedComponentsToInstall() const
}
/*!
- Calculates components to install and uninstall. In case of an error, returns \c false
- and and sets the \a displayString for error detail.
+ Returns a HTML-formatted description of the reasons each component is about
+ to be installed or uninstalled, or a description of the error occurred while
+ calculating components to install and uninstall.
*/
-
-bool PackageManagerCore::calculateComponents(QString *displayString)
+QString PackageManagerCore::componentResolveReasons() const
{
QString htmlOutput;
- if (!calculateComponentsToInstall()) {
+ if (!componentsToInstallError().isEmpty()) {
htmlOutput.append(QString::fromLatin1("<h2><font color=\"red\">%1</font></h2><ul>")
- .arg(tr("Cannot resolve all dependencies.")));
+ .arg(tr("Cannot resolve all dependencies.")));
//if we have a missing dependency or a recursion we can display it
- if (!componentsToInstallError().isEmpty()) {
- htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg(
- componentsToInstallError()));
- }
+ htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg(
+ componentsToInstallError()));
htmlOutput.append(QLatin1String("</ul>"));
- if (displayString)
- *displayString = htmlOutput;
- return false;
+ return htmlOutput;
+ }
+
+ if (!componentsToUninstallError().isEmpty()) {
+ htmlOutput.append(QString::fromLatin1("<h2><font color=\"red\">%1</font></h2><ul>")
+ .arg(tr("Cannot resolve components to uninstall.")));
+ htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg(
+ componentsToUninstallError()));
+ htmlOutput.append(QLatin1String("</ul>"));
+ return htmlOutput;
}
QList<Component*> componentsToRemove = componentsToUninstall();
@@ -2238,28 +2255,44 @@ bool PackageManagerCore::calculateComponents(QString *displayString)
}
htmlOutput.append(QString::fromLatin1("<li> %1 </li>").arg(component->name()));
}
- if (displayString)
- *displayString = htmlOutput;
- return true;
+ return htmlOutput;
}
/*!
- Calculates a list of components to uninstall based on the current run mode.
+ Calculates a list of components to uninstall.
+
The aboutCalculateComponentsToUninstall() signal is emitted
before the calculation starts, the finishedCalculateComponentsToUninstall() signal once all
- calculations are done. Always returns \c true.
+ calculations are done. Returns \c true on success, \c false otherwise.
\sa {installer::calculateComponentsToUninstall}{installer.calculateComponentsToUninstall}
*/
bool PackageManagerCore::calculateComponentsToUninstall() const
{
emit aboutCalculateComponentsToUninstall();
- if (!isUpdater()) {
- d->calculateUninstallComponents();
- d->storeCheckState();
+
+ d->clearUninstallerCalculator();
+ const QList<Component *> componentsToInstallList = d->installerCalculator()->orderedComponentsToInstall();
+
+ QList<Component *> selectedComponentsToUninstall;
+ foreach (Component* component, components(PackageManagerCore::ComponentType::Replacements)) {
+ // Uninstall the component if replacement is selected for install or update
+ QPair<Component*, Component*> comp = d->componentsToReplace().value(component->name());
+ if (comp.first && d->m_installerCalculator->orderedComponentsToInstall().contains(comp.first)) {
+ d->uninstallerCalculator()->insertUninstallReason(component,
+ UninstallerCalculator::Replaced, comp.first->name());
+ selectedComponentsToUninstall.append(comp.second);
+ }
+ }
+ foreach (Component *component, components(PackageManagerCore::ComponentType::AllNoReplacements)) {
+ if (component->uninstallationRequested() && !componentsToInstallList.contains(component))
+ selectedComponentsToUninstall.append(component);
}
+ const bool componentsToUninstallCalculated =
+ d->uninstallerCalculator()->appendComponentsToUninstall(selectedComponentsToUninstall);
+
emit finishedCalculateComponentsToUninstall();
- return true;
+ return componentsToUninstallCalculated;
}
/*!
@@ -2283,6 +2316,14 @@ QString PackageManagerCore::componentsToInstallError() const
}
/*!
+ Returns errors found in the components that are marked for uninstallation.
+*/
+QString PackageManagerCore::componentsToUninstallError() const
+{
+ return d->uninstallerCalculator()->componentsToUninstallError();
+}
+
+/*!
Returns the reason why \a component needs to be installed:
\list
@@ -4516,8 +4557,7 @@ ComponentModel *PackageManagerCore::componentModel(PackageManagerCore *core, con
ComponentModel::tr("Release Date"));
model->setHeaderData(ComponentModelHelper::UncompressedSizeColumn, Qt::Horizontal,
ComponentModel::tr("Size"));
- connect(model, &ComponentModel::checkStateChanged,
- this, &PackageManagerCore::componentsToInstallNeedsRecalculation);
+
return model;
}
diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h
index 26fa8e086..fb1eee160 100644
--- a/src/libs/installer/packagemanagercore.h
+++ b/src/libs/installer/packagemanagercore.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -239,12 +239,15 @@ public:
Q_INVOKABLE bool calculateComponentsToInstall() const;
QList<Component*> orderedComponentsToInstall() const;
- bool calculateComponents(QString *displayString);
+
+ Q_INVOKABLE bool recalculateAllComponents();
+ QString componentResolveReasons() const;
Q_INVOKABLE bool calculateComponentsToUninstall() const;
QList<Component*> componentsToUninstall() const;
QString componentsToInstallError() const;
+ QString componentsToUninstallError() const;
QString installReason(Component *component) const;
QString uninstallReason(Component *component) const;
@@ -360,8 +363,8 @@ public Q_SLOTS:
void languageChanged();
void setCompleteUninstallation(bool complete);
void cancelMetaInfoJob();
- void componentsToInstallNeedsRecalculation();
- void clearComponentsToInstallCalculated();
+ void componentsToInstallNeedsRecalculation(); // TODO: deprecated, remove
+ void clearComponentsToInstallCalculated() {} // TODO: deprecated, remove
Q_SIGNALS:
void aboutCalculateComponentsToInstall() const;
diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp
index d9a35bcb1..6d56791a0 100644
--- a/src/libs/installer/packagemanagercore_p.cpp
+++ b/src/libs/installer/packagemanagercore_p.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -170,7 +170,6 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core)
, m_updateSourcesAdded(false)
, m_magicBinaryMarker(0) // initialize with pseudo marker
, m_magicMarkerSupplement(BinaryContent::Default)
- , m_componentsToInstallCalculated(false)
, m_componentScriptEngine(nullptr)
, m_controlScriptEngine(nullptr)
, m_installerCalculator(nullptr)
@@ -209,7 +208,6 @@ PackageManagerCorePrivate::PackageManagerCorePrivate(PackageManagerCore *core, q
, m_updateSourcesAdded(false)
, m_magicBinaryMarker(magicInstallerMaker)
, m_magicMarkerSupplement(BinaryContent::Default)
- , m_componentsToInstallCalculated(false)
, m_componentScriptEngine(nullptr)
, m_controlScriptEngine(nullptr)
, m_installerCalculator(nullptr)
@@ -496,7 +494,6 @@ void PackageManagerCorePrivate::clearAllComponentLists()
m_deletedReplacedComponents.clear();
m_componentsToReplaceAllMode.clear();
- m_componentsToInstallCalculated = false;
qDeleteAll(toDelete);
cleanUpComponentEnvironment();
@@ -521,7 +518,6 @@ void PackageManagerCorePrivate::clearUpdaterComponentLists()
m_updaterDependencyReplacements.clear();
m_componentsToReplaceUpdaterMode.clear();
- m_componentsToInstallCalculated = false;
qDeleteAll(usedComponents);
cleanUpComponentEnvironment();
@@ -586,7 +582,6 @@ void PackageManagerCorePrivate::initialize(const QHash<QString, QString> &params
{
m_coreCheckedHash.clear();
m_data = PackageManagerCoreData(params, isInstaller());
- m_componentsToInstallCalculated = false;
#ifdef Q_OS_LINUX
if (m_launchedAsRoot && isInstaller())
@@ -2915,7 +2910,6 @@ void PackageManagerCorePrivate::restoreCheckState()
}
m_coreCheckedHash.clear();
- m_componentsToInstallCalculated = false;
}
void PackageManagerCorePrivate::storeCheckState()
@@ -3056,12 +3050,12 @@ QStringList PackageManagerCorePrivate::runningInstallerProcesses(const QStringLi
bool PackageManagerCorePrivate::calculateComponentsAndRun()
{
- QString htmlOutput;
- bool componentsOk = m_core->calculateComponents(&htmlOutput);
+ bool componentsOk = m_core->recalculateAllComponents();
if (statusCanceledOrFailed()) {
qCDebug(QInstaller::lcInstallerInstallLog) << "Installation canceled.";
} else if (componentsOk && acceptLicenseAgreements()) {
- qCDebug(QInstaller::lcInstallerInstallLog).noquote() << htmlToString(htmlOutput);
+ qCDebug(QInstaller::lcInstallerInstallLog).noquote()
+ << htmlToString(m_core->componentResolveReasons());
QString spaceInfo;
const bool spaceOk = m_core->checkAvailableSpace(spaceInfo);
@@ -3078,29 +3072,6 @@ bool PackageManagerCorePrivate::calculateComponentsAndRun()
return false;
}
-void PackageManagerCorePrivate::calculateUninstallComponents()
-{
- clearUninstallerCalculator();
- const QList<Component *> componentsToInstallList = installerCalculator()->orderedComponentsToInstall();
- QSet<Component*> componentsToInstall(componentsToInstallList.begin(), componentsToInstallList.end());
-
- QList<Component *> selectedComponentsToUninstall;
- foreach (Component* component, m_core->components(PackageManagerCore::ComponentType::Replacements)) {
- // Uninstall the component if replacement is selected for install or update
- QPair<Component*, Component*> comp = componentsToReplace().value(component->name());
- if (comp.first && m_installerCalculator->orderedComponentsToInstall().contains(comp.first)) {
- uninstallerCalculator()->insertUninstallReason(component,
- UninstallerCalculator::Replaced, comp.first->name());
- selectedComponentsToUninstall.append(comp.second);
- }
- }
- foreach (Component *component, m_core->components(PackageManagerCore::ComponentType::AllNoReplacements)) {
- if (component->uninstallationRequested() && !componentsToInstallList.contains(component))
- selectedComponentsToUninstall.append(component);
- }
- uninstallerCalculator()->appendComponentsToUninstall(selectedComponentsToUninstall);
-}
-
bool PackageManagerCorePrivate::acceptLicenseAgreements() const
{
// Always skip for uninstaller
diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h
index 56cf48d25..aaf9e27be 100644
--- a/src/libs/installer/packagemanagercore_p.h
+++ b/src/libs/installer/packagemanagercore_p.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -267,7 +267,6 @@ private:
void findExecutablesRecursive(const QString &path, const QStringList &excludeFiles, QStringList *result);
QStringList runningInstallerProcesses(const QStringList &exludeFiles);
bool calculateComponentsAndRun();
- void calculateUninstallComponents();
bool acceptLicenseAgreements() const;
bool askUserAcceptLicense(const QString &name, const QString &content) const;
bool askUserConfirmCommand() const;
@@ -292,7 +291,6 @@ private:
qint64 m_magicBinaryMarker;
int m_magicMarkerSupplement;
- bool m_componentsToInstallCalculated;
bool m_foundEssentialUpdate;;
mutable ScriptEngine *m_componentScriptEngine;
diff --git a/src/libs/installer/packagemanagergui.cpp b/src/libs/installer/packagemanagergui.cpp
index bec77b886..9cdea0356 100644
--- a/src/libs/installer/packagemanagergui.cpp
+++ b/src/libs/installer/packagemanagergui.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -1432,7 +1432,6 @@ int PackageManagerPage::nextId() const
if (core->isUninstaller())
return nextNextId; // forcibly hide the license page if we run as uninstaller
- core->calculateComponentsToInstall();
foreach (Component* component, core->orderedComponentsToInstall()) {
if (core->isMaintainer() && component->isInstalled())
continue; // package manager or updater, hide as long as the component is installed
@@ -1935,9 +1934,6 @@ void IntroductionPage::entering()
*/
void IntroductionPage::leaving()
{
- // force a recalculation of components to install to keep the state correct
- if (!packageManagerCore()->isUninstaller())
- packageManagerCore()->componentsToInstallNeedsRecalculation();
m_progressBar->setValue(0);
m_progressBar->setRange(0, 0);
setButtonText(QWizard::CancelButton, gui()->defaultButtonText(QWizard::CancelButton));
@@ -2067,7 +2063,6 @@ void LicenseAgreementPage::entering()
m_textBrowser->setHtml(QString());
m_licenseListWidget->setVisible(false);
- packageManagerCore()->calculateComponentsToInstall();
foreach (QInstaller::Component *component, packageManagerCore()->orderedComponentsToInstall())
packageManagerCore()->addLicenseItem(component->licenses());
@@ -2198,8 +2193,7 @@ void ComponentSelectionPage::entering()
d->updateTreeView();
// check component model state so we can enable needed component selection buttons
- if (core->isUpdater())
- d->onModelStateChanged(d->m_currentModel->checkedState());
+ d->onModelStateChanged(d->m_currentModel->checkedState());
setModified(isComplete());
if (core->settings().repositoryCategories().count() > 0 && !core->isOfflineOnly()
@@ -2301,7 +2295,7 @@ bool ComponentSelectionPage::addVirtualComponentToUninstall(const QString &name)
name, allComponents);
if (component && component->isInstalled() && component->isVirtual()) {
component->setCheckState(Qt::Unchecked);
- core->componentsToInstallNeedsRecalculation();
+ core->recalculateAllComponents();
qCDebug(QInstaller::lcDeveloperBuild) << "Virtual component " << name << " was selected for uninstall by script.";
return true;
}
@@ -2318,6 +2312,9 @@ void ComponentSelectionPage::setModified(bool modified)
*/
bool ComponentSelectionPage::isComplete() const
{
+ if (!d->componentsResolved())
+ return false;
+
if (packageManagerCore()->isInstaller() || packageManagerCore()->isUpdater())
return d->m_currentModel->checked().count();
@@ -2675,8 +2672,9 @@ void ReadyForInstallationPage::entering()
.arg(productName()));
}
- QString htmlOutput;
- bool componentsOk = packageManagerCore()->calculateComponents(&htmlOutput);
+ bool componentsOk = packageManagerCore()->recalculateAllComponents();
+ const QString htmlOutput = packageManagerCore()->componentResolveReasons();
+
qCDebug(QInstaller::lcInstallerInstallLog).noquote() << htmlToString(htmlOutput);
m_taskDetailsBrowser->setHtml(htmlOutput);
m_taskDetailsBrowser->setVisible(!componentsOk || LoggingHandler::instance().isVerbose());
diff --git a/src/libs/installer/uninstallercalculator.cpp b/src/libs/installer/uninstallercalculator.cpp
index 2f029630f..214e1124a 100644
--- a/src/libs/installer/uninstallercalculator.cpp
+++ b/src/libs/installer/uninstallercalculator.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -30,6 +30,7 @@
#include "component.h"
#include "packagemanagercore.h"
+#include "globals.h"
namespace QInstaller {
@@ -55,35 +56,60 @@ QSet<Component *> UninstallerCalculator::componentsToUninstall() const
return m_componentsToUninstall;
}
-void UninstallerCalculator::appendComponentToUninstall(Component *component)
+QString UninstallerCalculator::componentsToUninstallError() const
+{
+ return m_componentsToUninstallError;
+}
+
+bool UninstallerCalculator::appendComponentToUninstall(Component *component)
{
if (!component)
- return;
+ return true;
if (!component->isInstalled())
- return;
+ return true;
if (m_localDependencyComponentHash.contains(component->name())) {
const QStringList &dependencies = PackageManagerCore::parseNames(m_localDependencyComponentHash.value(component->name()));
for (const QString &dependencyComponent : dependencies) {
Component *depComponent = m_core->componentByName(dependencyComponent);
- if (!depComponent)
+ if (!depComponent || !depComponent->isInstalled())
+ continue;
+
+ if (m_componentsToUninstall.contains(depComponent)
+ || m_core->orderedComponentsToInstall().contains(depComponent)) {
+ // Component is already selected for uninstall or update
continue;
- if (depComponent->isInstalled() && !m_componentsToUninstall.contains(depComponent)
- && !m_core->orderedComponentsToInstall().contains(depComponent)) {
- appendComponentToUninstall(depComponent);
- insertUninstallReason(depComponent, UninstallerCalculator::Dependent, component->name());
}
+
+ if (depComponent->isEssential() || depComponent->forcedInstallation()) {
+ const QString errorMessage = QCoreApplication::translate("InstallerCalculator",
+ "Impossible dependency resolution detected. Forced install component \"%1\" would be uninstalled "
+ "because its dependency \"%2\" is marked for uninstallation with reason: \"%3\".")
+ .arg(depComponent->name(), component->name(), uninstallReason(component));
+
+ qCWarning(QInstaller::lcInstallerInstallLog).noquote() << errorMessage;
+ m_componentsToUninstallError.append(errorMessage);
+ return false;
+ }
+ // Resolve also all cascading dependencies
+ if (!appendComponentToUninstall(depComponent))
+ return false;
+
+ insertUninstallReason(depComponent, UninstallerCalculator::Dependent, component->name());
}
}
m_componentsToUninstall.insert(component);
+ return true;
}
-void UninstallerCalculator::appendComponentsToUninstall(const QList<Component*> &components)
+bool UninstallerCalculator::appendComponentsToUninstall(const QList<Component*> &components)
{
- foreach (Component *component, components)
- appendComponentToUninstall(component);
+ foreach (Component *component, components) {
+ if (!appendComponentToUninstall(component))
+ return false;
+ }
QList<Component*> autoDependOnList;
// All regular dependees are resolved. Now we are looking for auto depend on components.
@@ -109,6 +135,8 @@ void UninstallerCalculator::appendComponentsToUninstall(const QList<Component*>
appendComponentsToUninstall(autoDependOnList);
else
appendVirtualComponentsToUninstall();
+
+ return true;
}
void UninstallerCalculator::insertUninstallReason(Component *component, const UninstallReasonType uninstallReason,
diff --git a/src/libs/installer/uninstallercalculator.h b/src/libs/installer/uninstallercalculator.h
index 74207e005..6bdd342d2 100644
--- a/src/libs/installer/uninstallercalculator.h
+++ b/src/libs/installer/uninstallercalculator.h
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2022 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -59,8 +59,9 @@ public:
const QStringList &localVirtualComponents);
QSet<Component*> componentsToUninstall() const;
+ QString componentsToUninstallError() const;
- void appendComponentsToUninstall(const QList<Component*> &components);
+ bool appendComponentsToUninstall(const QList<Component*> &components);
void insertUninstallReason(Component *component,
const UninstallReasonType uninstallReason,
const QString &referencedComponentName = QString());
@@ -70,8 +71,9 @@ public:
private:
QString uninstallReasonReferencedComponent(Component *component) const;
bool isRequiredVirtualPackage(Component *component);
- void appendComponentToUninstall(Component *component);
+ bool appendComponentToUninstall(Component *component);
void appendVirtualComponentsToUninstall();
+ QString m_componentsToUninstallError;
QSet<Component *> m_componentsToUninstall;
PackageManagerCore *m_core;
diff --git a/tests/auto/installer/brokeninstaller/tst_brokeninstaller.cpp b/tests/auto/installer/brokeninstaller/tst_brokeninstaller.cpp
index 43bd15288..afa11d707 100644
--- a/tests/auto/installer/brokeninstaller/tst_brokeninstaller.cpp
+++ b/tests/auto/installer/brokeninstaller/tst_brokeninstaller.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2018 The Qt Company Ltd.
+** Copyright (C) 2023 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -145,7 +145,7 @@ private slots:
Component *componentDependingOnMissingDependency = core.componentByName("componentd");
componentDependingOnMissingDependency->addDependency("componentmissingdependency");
- core.componentsToInstallNeedsRecalculation();
+ core.recalculateAllComponents();
model->reset(components);
testModelState(model, m_checkedComponentsWithMissingDependency, m_partiallyCheckedComponentsWithBrokenScript, m_uncheckedComponentsWithMissingDependency);
diff --git a/tests/auto/installer/packagemanagercore/tst_packagemanagercore.cpp b/tests/auto/installer/packagemanagercore/tst_packagemanagercore.cpp
index 44dc65960..cc2e069ea 100644
--- a/tests/auto/installer/packagemanagercore/tst_packagemanagercore.cpp
+++ b/tests/auto/installer/packagemanagercore/tst_packagemanagercore.cpp
@@ -1,6 +1,6 @@
/**************************************************************************
**
-** Copyright (C) 2021 The Qt Company Ltd.
+** Copyright (C) 2022 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Installer Framework.
@@ -260,7 +260,6 @@ private slots:
root->setInstalled();
child1->setInstalled();
child2->setUninstalled();
- core.componentsToInstallNeedsRecalculation();
core.calculateComponentsToInstall();
QCOMPARE(core.requiredDiskSpace(), 250ULL);
}