diff options
-rw-r--r-- | src/libs/installer/componentmodel.cpp | 41 | ||||
-rw-r--r-- | src/libs/installer/componentmodel.h | 8 | ||||
-rw-r--r-- | src/libs/installer/componentselectionpage_p.cpp | 16 | ||||
-rw-r--r-- | src/libs/installer/installercalculator.cpp | 232 | ||||
-rw-r--r-- | src/libs/installer/installercalculator.h | 39 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercore.cpp | 87 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercore.h | 2 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercore_p.cpp | 73 | ||||
-rw-r--r-- | src/libs/installer/packagemanagercore_p.h | 19 | ||||
-rw-r--r-- | src/libs/installer/qinstallerglobal.cpp | 14 | ||||
-rw-r--r-- | src/libs/installer/qinstallerglobal.h | 5 | ||||
-rw-r--r-- | src/libs/installer/uninstallercalculator.cpp | 170 | ||||
-rw-r--r-- | src/libs/installer/uninstallercalculator.h | 19 | ||||
-rw-r--r-- | tests/auto/installer/solver/tst_solver.cpp | 31 |
14 files changed, 515 insertions, 241 deletions
diff --git a/src/libs/installer/componentmodel.cpp b/src/libs/installer/componentmodel.cpp index 88e502164..3bb9bc72e 100644 --- a/src/libs/installer/componentmodel.cpp +++ b/src/libs/installer/componentmodel.cpp @@ -58,14 +58,14 @@ namespace QInstaller { */ /*! - \fn void QInstaller::ComponentModel::checkStateChanged(const QModelIndex &index) + \fn void QInstaller::ComponentModel::componentsCheckStateChanged(const QList<QModelIndex> &indexes) - This signal is emitted whenever the checked state of a component is changed. The \a index value - indicates the QModelIndex representation of the component as seen from the model. + This signal is emitted whenever the checked state of components are changed. The \a indexes value + indicates the QModelIndexes representation of the components as seen from the model. */ /*! - \fn void QInstaller::ComponentModel::checkStateChanged(QInstaller::ComponentModel::ModelState state) + \fn void QInstaller::ComponentModel::modelCheckStateChanged(QInstaller::ComponentModel::ModelState state) This signal is emitted whenever the checked state of a model is changed after all state calculations have taken place. The \a state is a combination of \c ModelStateFlag values @@ -238,7 +238,7 @@ QVariant ComponentModel::data(const QModelIndex &index, int role) const /*! Sets the \a role data for the item at \a index to \a value. Returns true if successful; otherwise returns false. The dataChanged() signal is emitted if the data was successfully set. - The checkStateChanged() signals are emitted in addition if the checked state of the item is set. + The componentsCheckStateChanged() signal is emitted in addition if the checked state of the item is set. */ bool ComponentModel::setData(const QModelIndex &index, const QVariant &value, int role) { @@ -260,12 +260,13 @@ bool ComponentModel::setData(const QModelIndex &index, const QVariant &value, in const Qt::CheckState oldValue = component->checkState(); newValue = (oldValue == Qt::Checked) ? Qt::Unchecked : Qt::Checked; } - QSet<QModelIndex> changed = updateCheckedState(nodes << component, newValue); + const QList<QModelIndex> changed = updateCheckedState(nodes << component, newValue); foreach (const QModelIndex &changedIndex, changed) { emit dataChanged(changedIndex, changedIndex); - emit checkStateChanged(changedIndex); } - updateAndEmitModelState(); // update the internal state + updateModelState(); + if (changed.count() > 0) + emit componentsCheckStateChanged(changed); } else { component->setData(value, role); emit dataChanged(index, index); @@ -425,12 +426,12 @@ void ComponentModel::reset(QList<Component *> rootComponents) Sets the checked state of every component in the model to be \a state. The ComponentModel::PartiallyChecked flag is ignored by this function. Note that components - are not changed if they are not checkable. The dataChanged() and checkStateChanged() signals + are not changed if they are not checkable. The dataChanged() and componentsCheckStateChanged() signals are emitted. */ void ComponentModel::setCheckedState(QInstaller::ComponentModel::ModelStateFlag state) { - QSet<QModelIndex> changed; + QList<QModelIndex> changed; switch (state) { case AllChecked: changed = updateCheckedState(m_currentCheckedState[Qt::Unchecked], Qt::Checked); @@ -453,9 +454,9 @@ void ComponentModel::setCheckedState(QInstaller::ComponentModel::ModelStateFlag // notify about changes done to the model foreach (const QModelIndex &index, changed) { emit dataChanged(index, index); - emit checkStateChanged(index); } - updateAndEmitModelState(); // update the internal state + updateModelState(); + emit modelCheckStateChanged(m_modelState); } @@ -493,10 +494,11 @@ void ComponentModel::postModelReset() } m_currentCheckedState = m_initialCheckedState; - updateAndEmitModelState(); // update the internal state + updateModelState(); // update the internal state + emit modelCheckStateChanged(m_modelState); } -void ComponentModel::updateAndEmitModelState() +void ComponentModel::updateModelState() { m_modelState = ComponentModel::DefaultChecked; if (m_initialCheckedState != m_currentCheckedState) @@ -511,8 +513,6 @@ void ComponentModel::updateAndEmitModelState() m_modelState |= ComponentModel::AllChecked; m_modelState &= ~ComponentModel::PartiallyChecked; } - - emit checkStateChanged(m_modelState); } void ComponentModel::collectComponents(Component *const component, const QModelIndex &parent) const @@ -556,7 +556,7 @@ static Qt::CheckState verifyPartiallyChecked(Component *component) } // namespace ComponentModelPrivate -QSet<QModelIndex> ComponentModel::updateCheckedState(const ComponentSet &components, Qt::CheckState state) +QList<QModelIndex> ComponentModel::updateCheckedState(const ComponentSet &components, const Qt::CheckState state) { // get all parent nodes for the components we're going to update QMultiMap<QString, Component *> sortedNodesMap; @@ -567,7 +567,7 @@ QSet<QModelIndex> ComponentModel::updateCheckedState(const ComponentSet &compone } } - QSet<QModelIndex> changed; + QList<QModelIndex> changed; const ComponentList sortedNodes = sortedNodesMap.values(); // we can start in descending order to check node and tri-state nodes properly for (int i = sortedNodes.count(); i > 0; i--) { @@ -589,7 +589,10 @@ QSet<QModelIndex> ComponentModel::updateCheckedState(const ComponentSet &compone continue; node->setCheckState(newState); - changed.insert(indexFromComponentName(node->treeName())); + QModelIndex index = indexFromComponentName(node->treeName()); + //Prepend to the list so that install order is correct (parent first) + if (!changed.contains(index)) + changed.prepend(indexFromComponentName(node->treeName())); m_currentCheckedState[Qt::Checked].remove(node); m_currentCheckedState[Qt::Unchecked].remove(node); diff --git a/src/libs/installer/componentmodel.h b/src/libs/installer/componentmodel.h index 78d7e6401..f9fbae47a 100644 --- a/src/libs/installer/componentmodel.h +++ b/src/libs/installer/componentmodel.h @@ -90,17 +90,17 @@ public Q_SLOTS: void setCheckedState(QInstaller::ComponentModel::ModelStateFlag state); Q_SIGNALS: - void checkStateChanged(const QModelIndex &index); - void checkStateChanged(QInstaller::ComponentModel::ModelState state); + void componentsCheckStateChanged(const QList<QModelIndex> &indexes); + void modelCheckStateChanged(QInstaller::ComponentModel::ModelState state); private Q_SLOTS: void onVirtualStateChanged(); private: void postModelReset(); - void updateAndEmitModelState(); + void updateModelState(); void collectComponents(Component *const component, const QModelIndex &parent) const; - QSet<QModelIndex> updateCheckedState(const ComponentSet &components, Qt::CheckState state); + QList<QModelIndex> updateCheckedState(const ComponentSet &components, const Qt::CheckState state); private: PackageManagerCore *m_core; diff --git a/src/libs/installer/componentselectionpage_p.cpp b/src/libs/installer/componentselectionpage_p.cpp index e775c0cb4..5e44d153c 100644 --- a/src/libs/installer/componentselectionpage_p.cpp +++ b/src/libs/installer/componentselectionpage_p.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. @@ -185,10 +185,16 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP m_stackedLayout->addWidget(progressStackedWidget); m_stackedLayout->setCurrentIndex(0); - connect(m_allModel, SIGNAL(checkStateChanged(QInstaller::ComponentModel::ModelState)), this, - SLOT(onModelStateChanged(QInstaller::ComponentModel::ModelState))); - connect(m_updaterModel, SIGNAL(checkStateChanged(QInstaller::ComponentModel::ModelState)), - this, SLOT(onModelStateChanged(QInstaller::ComponentModel::ModelState))); + connect(m_allModel, &ComponentModel::modelCheckStateChanged, + this, &ComponentSelectionPagePrivate::onModelStateChanged); + connect(m_updaterModel, &ComponentModel::modelCheckStateChanged, + this, &ComponentSelectionPagePrivate::onModelStateChanged); + + connect(m_allModel, &ComponentModel::componentsCheckStateChanged, this, + [=]() { onModelStateChanged(m_allModel->checkedState());}); + + connect(m_updaterModel, &ComponentModel::componentsCheckStateChanged, this, + [=]() { onModelStateChanged(m_allModel->checkedState());}); connect(m_core, SIGNAL(metaJobProgress(int)), this, SLOT(onProgressChanged(int))); connect(m_core, SIGNAL(metaJobInfoMessage(QString)), this, SLOT(setMessage(QString))); diff --git a/src/libs/installer/installercalculator.cpp b/src/libs/installer/installercalculator.cpp index ba8bf9db3..307c0d2fb 100644 --- a/src/libs/installer/installercalculator.cpp +++ b/src/libs/installer/installercalculator.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 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. @@ -33,8 +33,6 @@ #include "settings.h" #include <globals.h> -#include <QDebug> - namespace QInstaller { /*! @@ -43,34 +41,18 @@ namespace QInstaller { \internal */ -InstallerCalculator::InstallerCalculator(const QList<Component *> &allComponents) +InstallerCalculator::InstallerCalculator(const QList<Component *> &allComponents, const QHash<QString, QStringList> &autoDependencyComponentHash) : m_allComponents(allComponents) + , m_autoDependencyComponentHash(autoDependencyComponentHash) { } -void InstallerCalculator::insertInstallReason(Component *component, - InstallReasonType installReason, const QString &referencedComponentName) +InstallerCalculator::InstallReasonType InstallerCalculator::installReasonType(const Component *c) const { - // keep the first reason - if (m_toInstallComponentIdReasonHash.contains(component->name())) - return; - m_toInstallComponentIdReasonHash.insert(component->name(), - qMakePair(installReason, referencedComponentName)); + return m_toInstallComponentIdReasonHash.value(c->name()).first; } -InstallerCalculator::InstallReasonType InstallerCalculator::installReasonType(Component *c) const -{ - return m_toInstallComponentIdReasonHash.value(c->name(), - qMakePair(InstallerCalculator::Selected, QString())).first; -} - -QString InstallerCalculator::installReasonReferencedComponent(Component *component) const -{ - return m_toInstallComponentIdReasonHash.value(component->name(), - qMakePair(InstallerCalculator::Selected, QString())).second; -} - -QString InstallerCalculator::installReason(Component *component) const +QString InstallerCalculator::installReason(const Component *component) const { InstallerCalculator::InstallReasonType reason = installReasonType(component); switch (reason) { @@ -100,69 +82,98 @@ QString InstallerCalculator::componentsToInstallError() const return m_componentsToInstallError; } -void InstallerCalculator::realAppendToInstallComponents(Component *component, const QString &version) -{ - if (!component->isInstalled(version) || component->updateRequested()) { - m_orderedComponentsToInstall.append(component); - m_toInstallComponentIds.insert(component->name()); - } -} - -QString InstallerCalculator::recursionError(Component *component) -{ - return QCoreApplication::translate("InstallerCalculator", "Recursion detected, component \"%1\" " - "already added with reason: \"%2\"").arg(component->name(), installReason(component)); -} - -bool InstallerCalculator::appendComponentsToInstall(const QList<Component *> &components) +bool InstallerCalculator::appendComponentsToInstall(const QList<Component *> &components, bool modelReset, const bool revertFromInstall) { if (components.isEmpty()) return true; QList<Component*> notAppendedComponents; // for example components with unresolved dependencies - foreach (Component *component, components){ + foreach (Component *component, components) { + if (!component) + continue; + // When model has been reseted, there should not be components with the same name if (m_toInstallComponentIds.contains(component->name())) { - const QString errorMessage = recursionError(component); - qCWarning(QInstaller::lcInstallerInstallLog).noquote() << errorMessage; - m_componentsToInstallError.append(errorMessage); - Q_ASSERT_X(!m_toInstallComponentIds.contains(component->name()), Q_FUNC_INFO, - qPrintable(errorMessage)); - return false; + if (modelReset) { + const QString errorMessage = recursionError(component); + qCWarning(QInstaller::lcInstallerInstallLog).noquote() << errorMessage; + m_componentsToInstallError.append(errorMessage); + Q_ASSERT_X(!m_toInstallComponentIds.contains(component->name()), Q_FUNC_INFO, + qPrintable(errorMessage)); + return false; + } + if (!revertFromInstall) { + // We can end up here if component is already added as dependency and + // user explicitly selects it to install + continue; + } } if (component->dependencies().isEmpty()) - realAppendToInstallComponents(component); + realAppendToInstallComponents(component, QString(), revertFromInstall); else notAppendedComponents.append(component); } foreach (Component *component, notAppendedComponents) { - if (!appendComponentToInstall(component)) + if (!appendComponentToInstall(component, QString(), revertFromInstall)) return false; } - QList<Component *> foundAutoDependOnList; // All regular dependencies are resolved. Now we are looking for auto depend on components. - foreach (Component *component, m_allComponents) { - // If a components is already installed or is scheduled for installation, no need to check - // for auto depend installation. - if ((!component->isInstalled() || component->updateRequested()) - && !m_toInstallComponentIds.contains(component->name())) { - // If we figure out a component requests auto installation, keep it to resolve - // their dependencies as well. - if (component->isAutoDependOn(m_toInstallComponentIds)) { - foundAutoDependOnList.append(component); - insertInstallReason(component, InstallerCalculator::Automatic); - } - } - } + QSet<Component *> foundAutoDependOnList = autodependencyComponents(revertFromInstall); if (!foundAutoDependOnList.isEmpty()) - return appendComponentsToInstall(foundAutoDependOnList); + return appendComponentsToInstall(foundAutoDependOnList.values(), modelReset, revertFromInstall); + return true; } -bool InstallerCalculator::appendComponentToInstall(Component *component, const QString &version) +bool InstallerCalculator::removeComponentsFromInstall(const QList<Component *> &components) +{ + return appendComponentsToInstall(components, false, true); +} + +QString InstallerCalculator::installReasonReferencedComponent(const Component *component) const +{ + const QString componentName = component->name(); + if (m_referenceCount.contains(componentName)) + return m_referenceCount.value(componentName).first(); + return m_toInstallComponentIdReasonHash.value(componentName, + qMakePair(InstallerCalculator::Selected, QString())).second; +} + +void InstallerCalculator::insertInstallReason(const Component *component, + const InstallReasonType installReason, const QString &referencedComponentName, const bool revertFromInstall) +{ + if (revertFromInstall && m_toInstallComponentIdReasonHash.contains(component->name())) { + m_toInstallComponentIdReasonHash.remove(component->name()); + } else if (!m_toInstallComponentIdReasonHash.contains(component->name())) { + m_toInstallComponentIdReasonHash.insert(component->name(), + qMakePair(installReason, referencedComponentName)); + } +} + +void InstallerCalculator::realAppendToInstallComponents(Component *component, const QString &version, const bool revertFromInstall) +{ + if (!m_componentsForAutodepencencyCheck.contains(component)) + m_componentsForAutodepencencyCheck.append(component); + if (revertFromInstall) { + if (m_toInstallComponentIds.contains(component->name())) { + // Component is no longer added as dependency and is not explicitly selected for install by user + if (m_referenceCount.value(component->name()).isEmpty() && !component->isSelected()) { + m_toInstallComponentIds.remove(component->name()); + m_orderedComponentsToInstall.removeAll(component); + } + } + } else { + if (!component->isInstalled(version) || component->updateRequested()) { + m_toInstallComponentIds.insert(component->name()); + m_orderedComponentsToInstall.append(component); + } + } +} + +bool InstallerCalculator::appendComponentToInstall(Component *component, const QString &version, bool revertFromInstall) { const QStringList dependenciesList = component->dependencies(); QSet<QString> allDependencies(dependenciesList.begin(), dependenciesList.end()); @@ -185,6 +196,8 @@ bool InstallerCalculator::appendComponentToInstall(Component *component, const Q return false; } } + if (revertFromInstall && dependencyComponent->forcedInstallation()) + continue; //Check if component requires higher version than what might be already installed bool isUpdateRequired = false; QString requiredName; @@ -203,11 +216,38 @@ bool InstallerCalculator::appendComponentToInstall(Component *component, const Q requiredDependencyVersion = requiredVersion; } } + + // Component can be requested for install by several component (as a dependency). + // Keep the reference count to a component, so we know when component needs to be + // removed from install. + if (!revertFromInstall && m_toInstallComponentIds.contains(dependencyComponentName)) { + QStringList value = m_referenceCount.value(dependencyComponentName, QStringList()); + if (!value.contains(component->name())) + value << component->name(); + m_referenceCount.insert(dependencyComponentName, value); + } + if (revertFromInstall) { + if (m_toInstallComponentIds.contains(dependencyComponentName) + && m_referenceCount.contains(dependencyComponentName)) { + QStringList value = m_referenceCount.value(dependencyComponentName); + if (value.contains(component->name())) + value.removeOne(component->name()); + if (value.isEmpty()) + m_referenceCount.remove(dependencyComponentName); + else + m_referenceCount.insert(dependencyComponentName, value); + } + insertInstallReason(dependencyComponent, InstallerCalculator::Dependent, component->name(), true); + if (!appendComponentToInstall(dependencyComponent, requiredDependencyVersion, revertFromInstall)) + return false; + m_visitedComponents.remove(component); + } + //Check dependencies only if //- Dependency is not installed or update requested, nor newer version of dependency component required //- And dependency component is not already added for install //- And component is not already added for install, then dependencies are already resolved - if (((!dependencyComponent->isInstalled() || dependencyComponent->updateRequested()) + if (!revertFromInstall && ((!dependencyComponent->isInstalled() || dependencyComponent->updateRequested()) || isUpdateRequired) && (!m_toInstallComponentIds.contains(dependencyComponent->name()) && !m_toInstallComponentIds.contains(component->name()))) { if (m_visitedComponents.value(component).contains(dependencyComponent)) { @@ -220,20 +260,66 @@ bool InstallerCalculator::appendComponentToInstall(Component *component, const Q } m_visitedComponents[component].insert(dependencyComponent); - // add needed dependency components to the next run - insertInstallReason(dependencyComponent, InstallerCalculator::Dependent, - component->name()); - - if (!appendComponentToInstall(dependencyComponent, requiredDependencyVersion)) + QStringList value = m_referenceCount.value(dependencyComponentName, QStringList()); + if (!value.contains(component->name())) + value << component->name(); + m_referenceCount.insert(dependencyComponentName, value); + insertInstallReason(dependencyComponent, InstallerCalculator::Dependent, component->name()); + if (!appendComponentToInstall(dependencyComponent, requiredDependencyVersion, revertFromInstall)) return false; } } - - if (!m_toInstallComponentIds.contains(component->name())) { - realAppendToInstallComponents(component, requiredDependencyVersion); + if (!revertFromInstall && !m_toInstallComponentIds.contains(component->name())) { + realAppendToInstallComponents(component, requiredDependencyVersion, revertFromInstall); insertInstallReason(component, InstallerCalculator::Resolved); } + if (revertFromInstall && m_toInstallComponentIds.contains(component->name())) { + realAppendToInstallComponents(component, requiredDependencyVersion, revertFromInstall); + } return true; } +QString InstallerCalculator::recursionError(const Component *component) const +{ + return QCoreApplication::translate("InstallerCalculator", "Recursion detected, component \"%1\" " + "already added with reason: \"%2\"").arg(component->name(), installReason(component)); +} + +QSet<Component *> InstallerCalculator::autodependencyComponents(const bool revertFromInstall) +{ + // All regular dependencies are resolved. Now we are looking for auto depend on components. + // m_componentsForAutodepencencyCheck is a list of recently calculated components to be installed + // (normal components and regular dependencies components), and we check possible installable auto + // dependency components based on that list. + QSet<Component *> foundAutoDependOnList; + for (const Component *component : qAsConst(m_componentsForAutodepencencyCheck)) { + if (!m_autoDependencyComponentHash.contains(component->name())) + continue; + for (const QString& autoDependency : m_autoDependencyComponentHash.value(component->name())) { + // If a components is already installed or is scheduled for installation, no need to check + // for auto depend installation. + if ((!revertFromInstall && m_toInstallComponentIds.contains(autoDependency)) + || (revertFromInstall && !m_toInstallComponentIds.contains(autoDependency))) { + continue; + } + Component *autoDependComponent = PackageManagerCore::componentByName(autoDependency, m_allComponents); + if (!autoDependComponent) + continue; + if ((!autoDependComponent->isInstalled() || autoDependComponent->updateRequested()) + && !m_toInstallComponentIds.contains(autoDependComponent->name())) { + // One of the components autodependons is requested for install, check if there + // are other autodependencies as well + if (autoDependComponent->isAutoDependOn(m_toInstallComponentIds)) { + foundAutoDependOnList.insert(autoDependComponent); + insertInstallReason(autoDependComponent, InstallerCalculator::Automatic); + } + } else if (revertFromInstall && m_toInstallComponentIds.contains(autoDependComponent->name())) { + foundAutoDependOnList.insert(autoDependComponent); + } + } + } + m_componentsForAutodepencencyCheck.clear(); + return foundAutoDependOnList; +} + } // namespace QInstaller diff --git a/src/libs/installer/installercalculator.h b/src/libs/installer/installercalculator.h index b2d05bdbe..2841d8671 100644 --- a/src/libs/installer/installercalculator.h +++ b/src/libs/installer/installercalculator.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 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. @@ -42,41 +42,50 @@ class Component; class INSTALLER_EXPORT InstallerCalculator { public: - InstallerCalculator(const QList<Component *> &allComponents); + InstallerCalculator(const QList<Component *> &allComponents, const QHash<QString, QStringList> &autoDependencyComponentHash); enum InstallReasonType { + Selected, // "Selected Component(s) without Dependencies" Automatic, // "Component(s) added as automatic dependencies" Dependent, // "Added as dependency for %1." - Resolved, // "Component(s) that have resolved Dependencies" - Selected // "Selected Component(s) without Dependencies" + Resolved // "Component(s) that have resolved Dependencies" }; - InstallReasonType installReasonType(Component *component) const; - QString installReasonReferencedComponent(Component *component) const; - QString installReason(Component *component) const; + InstallReasonType installReasonType(const Component *c) const; + QString installReason(const Component *component) const; QList<Component*> orderedComponentsToInstall() const; QString componentsToInstallError() const; - bool appendComponentsToInstall(const QList<Component*> &components); + bool appendComponentsToInstall(const QList<Component*> &components, bool modelReset = false, const bool revertFromInstall = false); + bool removeComponentsFromInstall(const QList<Component*> &components); private: - void insertInstallReason(Component *component, - InstallReasonType installReasonType, - const QString &referencedComponentName = QString()); - void realAppendToInstallComponents(Component *component, const QString &version = QString()); - bool appendComponentToInstall(Component *components, const QString &version = QString()); - QString recursionError(Component *component); + QString installReasonReferencedComponent(const Component *component) const; + void insertInstallReason(const Component *component, + const InstallReasonType installReason, + const QString &referencedComponentName = QString(), + const bool revertFromInstall = false); + void realAppendToInstallComponents(Component *component, const QString &version, const bool revertFromInstall); + bool appendComponentToInstall(Component *component, const QString &version, const bool revertFromInstall); + QString recursionError(const Component *component) const; + QSet<Component *> autodependencyComponents(const bool revertFromInstall); +private: QList<Component*> m_allComponents; QHash<Component*, QSet<Component*> > m_visitedComponents; - QSet<QString> m_toInstallComponentIds; //for faster lookups + QList<const Component*> m_componentsForAutodepencencyCheck; + //for faster lookups. + QSet<QString> m_toInstallComponentIds; + QHash<QString, QStringList> m_referenceCount; QString m_componentsToInstallError; //calculate installation order variables QList<Component*> m_orderedComponentsToInstall; //we can't use this reason hash as component id hash, because some reasons are ready before //the component is added QHash<QString, QPair<InstallReasonType, QString> > m_toInstallComponentIdReasonHash; + //Helper hash for quicker search for autodependency components + QHash<QString, QStringList> m_autoDependencyComponentHash; }; } diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp index b6f79a92b..041c0fd31 100644 --- a/src/libs/installer/packagemanagercore.cpp +++ b/src/libs/installer/packagemanagercore.cpp @@ -583,22 +583,10 @@ void PackageManagerCore::componentsToInstallNeedsRecalculation() QList<Component*> selectedComponentsToInstall = componentsMarkedForInstallation(); d->m_componentsToInstallCalculated = - d->installerCalculator()->appendComponentsToInstall(selectedComponentsToInstall); - - QList<Component *> componentsToInstall = d->installerCalculator()->orderedComponentsToInstall(); + d->installerCalculator()->appendComponentsToInstall(selectedComponentsToInstall, true); d->calculateUninstallComponents(); - - QSet<Component *> componentsToUninstall = d->uninstallerCalculator()->componentsToUninstall(); - - foreach (Component *component, components(ComponentType::All)) - component->setInstallAction(component->isInstalled() - ? ComponentModelHelper::KeepInstalled - : ComponentModelHelper::KeepUninstalled); - foreach (Component *component, componentsToUninstall) - component->setInstallAction(ComponentModelHelper::Uninstall); - foreach (Component *component, componentsToInstall) - component->setInstallAction(ComponentModelHelper::Install); + d->updateComponentCheckedState(); // update all nodes uncompressed size foreach (Component *const component, components(ComponentType::Root)) @@ -606,6 +594,59 @@ void PackageManagerCore::componentsToInstallNeedsRecalculation() } /*! + Calculates components to install based on user selection. \a indexes + contains list of model indexes user has selected for install, dependencies + and autodependencies are resolved later. + */ +void PackageManagerCore::calculateUserSelectedComponentsToInstall(const QList<QModelIndex> &indexes) +{ + QList<Component*> componentsToInstall; + QList<Component*> componentsToUnInstall; + ComponentModel *model = isUpdater() ? updaterComponentModel() : defaultComponentModel(); + for (QModelIndex index : indexes) { + Component *installComponent = model->componentFromIndex(index); + // 1. Component is selected for install + if (installComponent->isSelected() && !installComponent->isInstalled()) { + componentsToInstall.append(installComponent); + // Check if component has replacements that needs to be removed + const QList<Component*> replacedComponents = d->replacedComponentsByName(installComponent->name()); + for (Component *replacedComponent : replacedComponents) { + componentsToUnInstall.append(replacedComponent); + d->uninstallerCalculator()->insertUninstallReason(replacedComponent, + UninstallerCalculator::UninstallReasonType::Replaced); + } + } + // 2. Component is reseleted for install (tapping checkbox off/on) + else if (installComponent->isSelected() && installComponent->isInstalled() + && !d->installerCalculator()->orderedComponentsToInstall().contains(installComponent)) { + componentsToInstall.append(installComponent); + } + // 3. Component is selected for uninstall + else if (!isUpdater() && !installComponent->isSelected() && installComponent->isInstalled()) { + componentsToUnInstall.append(installComponent); + } + // 4. Component is reselected for uninstall (tapping checkbox on/off) + else if (!installComponent->isSelected() + && d->installerCalculator()->orderedComponentsToInstall().contains(installComponent)) { + componentsToUnInstall.append(installComponent); + // Check if component has replacements that needs to be readded + componentsToInstall.append(d->replacedComponentsByName(installComponent->name())); + } + } + + d->installerCalculator()->removeComponentsFromInstall(componentsToUnInstall); + d->m_componentsToInstallCalculated + = d->installerCalculator()->appendComponentsToInstall(componentsToInstall, false); + if (!isUpdater()) { + d->uninstallerCalculator()->appendComponentsToUninstall(componentsToUnInstall, false); + } + d->uninstallerCalculator()->removeComponentsFromUnInstall(componentsToInstall); + + d->updateComponentCheckedState(); +} + + +/*! Forces a recalculation of components to install. \sa {installer::clearComponentsToInstallCalculated}{installer.clearComponentsToInstallCalculated} */ @@ -2141,8 +2182,7 @@ QList<Component*> PackageManagerCore::orderedComponentsToInstall() const bool PackageManagerCore::calculateComponents(QString *displayString) { QString htmlOutput; - if (!calculateComponentsToUninstall() || - !calculateComponentsToInstall()) { + if (!calculateComponentsToInstall()) { htmlOutput.append(QString::fromLatin1("<h2><font color=\"red\">%1</font></h2><ul>") .arg(tr("Cannot resolve all dependencies."))); //if we have a missing dependency or a recursion we can display it @@ -3942,8 +3982,12 @@ void PackageManagerCore::storeReplacedComponents(QHash<QString, Component *> &co components.remove(key); d->m_deletedReplacedComponents.append(componentToReplace); } - d->componentsToReplace().insert(componentName, qMakePair(it.key(), componentToReplace)); d->replacementDependencyComponents().append(componentToReplace); + + //Following hashes are created for quicker search of components + d->componentsToReplace().insert(componentName, qMakePair(it.key(), componentToReplace)); + QStringList oldValue = d->componentReplaces().value(it.key()->name()); + d->componentReplaces().insert(it.key()->name(), oldValue << componentToReplace->name()); } } } @@ -4205,8 +4249,8 @@ bool PackageManagerCore::fetchUpdaterPackages(const PackagesList &remotes, const if (!component->isUnstable()) component->setCheckState(Qt::Checked); } + d->createDependencyHashes(component); } - if (foundEssentialUpdate()) { foreach (QInstaller::Component *component, components) { if (d->statusCanceledOrFailed()) @@ -4379,9 +4423,10 @@ ComponentModel *PackageManagerCore::componentModel(PackageManagerCore *core, con ComponentModel::tr("Release Date")); model->setHeaderData(ComponentModelHelper::UncompressedSizeColumn, Qt::Horizontal, ComponentModel::tr("Size")); - connect(model, SIGNAL(checkStateChanged(QInstaller::ComponentModel::ModelState)), this, - SLOT(componentsToInstallNeedsRecalculation())); - + connect(model, &ComponentModel::modelCheckStateChanged, + this, &PackageManagerCore::componentsToInstallNeedsRecalculation); + connect(model, &ComponentModel::componentsCheckStateChanged, + this, &PackageManagerCore::calculateUserSelectedComponentsToInstall); return model; } diff --git a/src/libs/installer/packagemanagercore.h b/src/libs/installer/packagemanagercore.h index 7cce3f7be..bebfc191d 100644 --- a/src/libs/installer/packagemanagercore.h +++ b/src/libs/installer/packagemanagercore.h @@ -352,6 +352,7 @@ public Q_SLOTS: void setCompleteUninstallation(bool complete); void cancelMetaInfoJob(); void componentsToInstallNeedsRecalculation(); + void calculateUserSelectedComponentsToInstall(const QList<QModelIndex> &indexes); void clearComponentsToInstallCalculated(); Q_SIGNALS: @@ -437,6 +438,7 @@ private: PackageManagerCorePrivate *const d; friend class PackageManagerCorePrivate; QHash<QString, QString> m_fileDialogAutomaticAnswers; + QHash<QString, QStringList> m_localVirtualWithDependants; private: // remove once we deprecate isSelected, setSelected etc... diff --git a/src/libs/installer/packagemanagercore_p.cpp b/src/libs/installer/packagemanagercore_p.cpp index 73738d1cb..fc7a8e2c0 100644 --- a/src/libs/installer/packagemanagercore_p.cpp +++ b/src/libs/installer/packagemanagercore_p.cpp @@ -430,12 +430,13 @@ bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &c m_core->appendRootComponent(component); } - // after everything is set up, load the scripts if needed - if (loadScript) { - foreach (QInstaller::Component *component, components) + // after everything is set up, load the scripts if needed and create helper hashes + // for autodependency and dependency components for quicker search later + foreach (QInstaller::Component *component, components) { + if (loadScript) component->loadComponentScript(); + createDependencyHashes(component); } - // now we can preselect components in the tree foreach (QInstaller::Component *component, components) { // set the checked state for all components without child (means without tristate) @@ -490,6 +491,10 @@ bool PackageManagerCorePrivate::buildComponentTree(QHash<QString, Component*> &c void PackageManagerCorePrivate::cleanUpComponentEnvironment() { + m_componentReplaces.clear(); + m_autoDependencyComponentHash.clear(); + m_dependencyComponentHash.clear(); + m_localVirtualComponents.clear(); // clean up registered (downloaded) data if (m_core->isMaintainer()) BinaryFormatEngineHandler::instance()->clear(); @@ -565,6 +570,26 @@ QHash<QString, QPair<Component*, Component*> > &PackageManagerCorePrivate::compo return (!isUpdater()) ? m_componentsToReplaceAllMode : m_componentsToReplaceUpdaterMode; } +QHash<QString, QStringList> &PackageManagerCorePrivate::componentReplaces() +{ + return m_componentReplaces; +} + +QList<Component*> PackageManagerCorePrivate::replacedComponentsByName(const QString &name) +{ + // Creates a list of components which are replaced by component 'name' + QList<Component*> replacedComponents; + if (m_componentReplaces.contains(name)) { + for (const QString &replacedComponentName : m_componentReplaces.value(name)) { + Component *replacedComponent = m_core->componentByName(replacedComponentName, + m_core->components(PackageManagerCore::ComponentType::All)); + if (replacedComponent) + replacedComponents.append(replacedComponent); + } + } + return replacedComponents; +} + void PackageManagerCorePrivate::clearInstallerCalculator() { delete m_installerCalculator; @@ -576,7 +601,7 @@ InstallerCalculator *PackageManagerCorePrivate::installerCalculator() const if (!m_installerCalculator) { PackageManagerCorePrivate *const pmcp = const_cast<PackageManagerCorePrivate *> (this); pmcp->m_installerCalculator = new InstallerCalculator( - m_core->components(PackageManagerCore::ComponentType::AllNoReplacements)); + m_core->components(PackageManagerCore::ComponentType::AllNoReplacements), pmcp->m_autoDependencyComponentHash); } return m_installerCalculator; } @@ -600,7 +625,8 @@ UninstallerCalculator *PackageManagerCorePrivate::uninstallerCalculator() const } } - pmcp->m_uninstallerCalculator = new UninstallerCalculator(installedComponents, m_core); + pmcp->m_uninstallerCalculator = new UninstallerCalculator(installedComponents, m_core, + pmcp->m_autoDependencyComponentHash, pmcp->m_dependencyComponentHash, pmcp->m_localVirtualComponents); } return m_uninstallerCalculator; } @@ -2685,8 +2711,11 @@ LocalPackagesHash PackageManagerCorePrivate::localInstalledPackages() if (statusCanceledOrFailed()) break; installedPackages.insert(package.name, package); + if (package.virtualComp && package.autoDependencies.isEmpty()) { + if (!m_localVirtualComponents.contains(package.name)) + m_localVirtualComponents.append(package.name); + } } - return installedPackages; } @@ -2823,6 +2852,19 @@ void PackageManagerCorePrivate::storeCheckState() m_coreCheckedHash.insert(component, component->checkState()); } +void PackageManagerCorePrivate::updateComponentCheckedState() +{ + for (Component *component : m_core->components(PackageManagerCore::ComponentType::All)) { + component->setInstallAction(component->isInstalled() + ? ComponentModelHelper::KeepInstalled + : ComponentModelHelper::KeepUninstalled); + } + for (Component *component : uninstallerCalculator()->componentsToUninstall()) + component->setInstallAction(ComponentModelHelper::Uninstall); + for (Component *component : installerCalculator()->orderedComponentsToInstall()) + component->setInstallAction(ComponentModelHelper::Install); +} + void PackageManagerCorePrivate::connectOperationCallMethodRequest(Operation *const operation) { QObject *const operationObject = dynamic_cast<QObject *> (operation); @@ -3093,4 +3135,21 @@ void PackageManagerCorePrivate::commitPendingUnstableComponents() m_pendingUnstableComponents.clear(); } +void PackageManagerCorePrivate::createDependencyHashes(const Component* component) +{ + for (const QString &autodepend : component->autoDependencies()) { + QStringList value = m_autoDependencyComponentHash.value(autodepend); + if (!value.contains(component->name())) + value.append(component->name()); + m_autoDependencyComponentHash.insert(autodepend, value); + } + + for (const QString &depend : component->dependencies()) { + QStringList value = m_dependencyComponentHash.value(depend); + if (!value.contains(component->name())) + value.append(component->name()); + m_dependencyComponentHash.insert(depend, value); + } +} + } // namespace QInstaller diff --git a/src/libs/installer/packagemanagercore_p.h b/src/libs/installer/packagemanagercore_p.h index 8c5191dea..8e1184c58 100644 --- a/src/libs/installer/packagemanagercore_p.h +++ b/src/libs/installer/packagemanagercore_p.h @@ -111,6 +111,8 @@ public: void clearUpdaterComponentLists(); QList<Component*> &replacementDependencyComponents(); QHash<QString, QPair<Component*, Component*> > &componentsToReplace(); + QHash<QString, QStringList > &componentReplaces(); + QList<Component*> replacedComponentsByName(const QString &name); void clearInstallerCalculator(); InstallerCalculator *installerCalculator() const; @@ -262,6 +264,12 @@ private: bool askUserConfirmCommand() const; bool packageNeedsUpdate(const LocalPackage &localPackage, const Package *update) const; void commitPendingUnstableComponents(); + void createDependencyHashes(const Component* component); + void updateComponentCheckedState(); + + // remove once we deprecate isSelected, setSelected etc... + void restoreCheckState(); + void storeCheckState(); private: PackageManagerCore *m_core; @@ -296,12 +304,15 @@ private: QScopedPointer<RemoteFileEngineHandler> m_remoteFileEngineHandler; QHash<QString, QVariantMap> m_licenseItems; -private: - // remove once we deprecate isSelected, setSelected etc... - void restoreCheckState(); - void storeCheckState(); QHash<Component*, Qt::CheckState> m_coreCheckedHash; QList<Component*> m_deletedReplacedComponents; + AutoDependencyHash m_autoDependencyComponentHash; + DependencyHash m_dependencyComponentHash; + + QStringList m_localVirtualComponents; + + // < name (component replacing others), components to replace> + QHash<QString, QStringList > m_componentReplaces; }; } // namespace QInstaller diff --git a/src/libs/installer/qinstallerglobal.cpp b/src/libs/installer/qinstallerglobal.cpp index 177a2595d..6a67e9e10 100644 --- a/src/libs/installer/qinstallerglobal.cpp +++ b/src/libs/installer/qinstallerglobal.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2020 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. @@ -70,3 +70,15 @@ Synonym for QHash<QString, KDUpdater::LocalPackage>. */ + +/*! + \typedef QInstaller::AutoDependencyHash + + Synonym for QHash<QString, QStringList>. +*/ + +/*! + \typedef QInstaller::DependencyHash + + Synonym for QHash<QString, QStringList>. +*/ diff --git a/src/libs/installer/qinstallerglobal.h b/src/libs/installer/qinstallerglobal.h index 884044db9..98a81f092 100644 --- a/src/libs/installer/qinstallerglobal.h +++ b/src/libs/installer/qinstallerglobal.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 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. @@ -57,6 +57,9 @@ typedef QList<QInstaller::Package*> PackagesList; typedef QHash<QString, KDUpdater::LocalPackage> LocalPackagesHash; +typedef QHash<QString, QStringList> AutoDependencyHash; +typedef QHash<QString, QStringList> DependencyHash; + } // namespace QInstaller #endif // QINSTALLER_GLOBAL_H diff --git a/src/libs/installer/uninstallercalculator.cpp b/src/libs/installer/uninstallercalculator.cpp index a8c47e523..9bcacb246 100644 --- a/src/libs/installer/uninstallercalculator.cpp +++ b/src/libs/installer/uninstallercalculator.cpp @@ -42,9 +42,16 @@ namespace QInstaller { \internal */ -UninstallerCalculator::UninstallerCalculator(const QList<Component *> &installedComponents, PackageManagerCore *core) +UninstallerCalculator::UninstallerCalculator(const QList<Component *> &installedComponents + , PackageManagerCore *core + , const QHash<QString, QStringList> &autoDependencyComponentHash + , const QHash<QString, QStringList> &dependencyComponentHash + , const QStringList &localVirtualComponents) : m_installedComponents(installedComponents) , m_core(core) + , m_autoDependencyComponentHash(autoDependencyComponentHash) + , m_dependencyComponentHash(dependencyComponentHash) + , m_localVirtualComponents(localVirtualComponents) { } @@ -53,7 +60,7 @@ QSet<Component *> UninstallerCalculator::componentsToUninstall() const return m_componentsToUninstall; } -void UninstallerCalculator::appendComponentToUninstall(Component *component) +void UninstallerCalculator::appendComponentToUninstall(Component *component, const bool reverse) { if (!component) return; @@ -61,79 +68,64 @@ void UninstallerCalculator::appendComponentToUninstall(Component *component) if (!component->isInstalled()) return; - // remove all already resolved dependees - const QList<Component *> dependeesList = m_core->dependees(component); - QSet<Component *> dependees = QSet<Component *>(dependeesList.begin(), - dependeesList.end()).subtract(m_componentsToUninstall); - - foreach (Component *dependee, dependees) { - appendComponentToUninstall(dependee); - insertUninstallReason(dependee, UninstallerCalculator::Dependent, component->name()); + if (m_dependencyComponentHash.contains(component->name())) { + const QStringList &dependencies = PackageManagerCore::parseNames(m_dependencyComponentHash.value(component->name())); + for (const QString &dependencyComponent : dependencies) { + Component *depComponent = m_core->componentByName(dependencyComponent); + if (depComponent && depComponent->isInstalled() && !m_componentsToUninstall.contains(depComponent)) { + appendComponentToUninstall(depComponent, reverse); + insertUninstallReason(depComponent, UninstallerCalculator::Dependent, component->name()); + } else if (reverse) { + appendComponentToUninstall(depComponent, true); + } + } + } + if (reverse) { + m_componentsToUninstall.remove(component); + } else { + m_componentsToUninstall.insert(component); } - - m_componentsToUninstall.insert(component); } -void UninstallerCalculator::appendComponentsToUninstall(const QList<Component*> &components) +void UninstallerCalculator::appendComponentsToUninstall(const QList<Component*> &components, const bool reverse) { + if (components.isEmpty()) + return; foreach (Component *component, components) - appendComponentToUninstall(component); - + appendComponentToUninstall(component, reverse); QList<Component*> autoDependOnList; // All regular dependees are resolved. Now we are looking for auto depend on components. - foreach (Component *component, m_installedComponents) { + for (Component *component : components) { // If a components is installed and not yet scheduled for un-installation, check for auto depend. - if (component->isInstalled() && !m_componentsToUninstall.contains(component)) { - QStringList autoDependencies = PackageManagerCore::parseNames(component->autoDependencies()); - if (autoDependencies.isEmpty()) - continue; - - // This code needs to be enabled once the scripts use isInstalled, installationRequested and - // uninstallationRequested... - if (autoDependencies.first().compare(scScript, Qt::CaseInsensitive) == 0) { - //QScriptValue valueFromScript; - //try { - // valueFromScript = callScriptMethod(QLatin1String("isAutoDependOn")); - //} catch (const Error &error) { - // // keep the component, should do no harm - // continue; - //} - - //if (valueFromScript.isValid() && !valueFromScript.toBool()) - // autoDependOnList.append(component); - continue; - } - - foreach (Component *c, m_installedComponents) { - const QString replaces = c->value(scReplaces); - const QStringList possibleNames = replaces.split(QInstaller::commaRegExp(), - Qt::SkipEmptyParts) << c->name(); - foreach (const QString &possibleName, possibleNames) { - - Component *cc = PackageManagerCore::componentByName(possibleName, m_installedComponents); - if (cc && (cc->installAction() != ComponentModelHelper::AutodependUninstallation)) { - autoDependencies.removeAll(possibleName); - - } - } - } - - // A component requested auto uninstallation, keep it to resolve their dependencies as well. - if (!autoDependencies.isEmpty()) { - autoDependOnList.append(component); - insertUninstallReason(component, UninstallerCalculator::AutoDependent, autoDependencies.join(QLatin1String(", "))); - component->setInstallAction(ComponentModelHelper::AutodependUninstallation); + if (!m_autoDependencyComponentHash.contains(component->name())) + continue; + const QStringList autoDependencies = PackageManagerCore::parseNames(m_autoDependencyComponentHash.value(component->name())); + for (const QString &autoDependencyComponent : autoDependencies) { + Component *autoDepComponent = m_core->componentByName(autoDependencyComponent); + if (autoDepComponent && autoDepComponent->isInstalled()) { + // A component requested auto uninstallation, keep it to resolve their dependencies as well. + if (reverse) { + autoDependOnList.append(autoDepComponent); + } else if (!m_componentsToUninstall.contains(autoDepComponent)) { + insertUninstallReason(autoDepComponent, UninstallerCalculator::AutoDependent, component->name()); + autoDepComponent->setInstallAction(ComponentModelHelper::AutodependUninstallation); + autoDependOnList.append(autoDepComponent); + } } } } - if (!autoDependOnList.isEmpty()) - appendComponentsToUninstall(autoDependOnList); + appendComponentsToUninstall(autoDependOnList, reverse); else - appendVirtualComponentsToUninstall(); + appendVirtualComponentsToUninstall(reverse); +} + +void UninstallerCalculator::removeComponentsFromUnInstall(const QList<Component*> &components) +{ + appendComponentsToUninstall(components, true); } -void UninstallerCalculator::insertUninstallReason(Component *component, UninstallReasonType uninstallReason, +void UninstallerCalculator::insertUninstallReason(Component *component, const UninstallReasonType uninstallReason, const QString &referencedComponentName) { // keep the first reason @@ -176,34 +168,56 @@ QString UninstallerCalculator::uninstallReasonReferencedComponent(Component *com return m_toUninstallComponentIdReasonHash.value(component->name()).second; } - -void UninstallerCalculator::appendVirtualComponentsToUninstall() +void UninstallerCalculator::appendVirtualComponentsToUninstall(const bool reverse) { QList<Component*> unneededVirtualList; + // Check for virtual components without dependees - for (Component *component : qAsConst(m_installedComponents)) { - if (component->isInstalled() && component->isVirtual() && !m_componentsToUninstall.contains(component)) { - // Components with auto dependencies were handled in the previous step - if (!component->autoDependencies().isEmpty() || component->forcedInstallation()) - continue; - - bool required = false; - // Check if installed or about to be updated -packages are dependant on the package - for (Component *dependant : m_core->installDependants(component)) { - if (dependant->isInstalled() && !m_componentsToUninstall.contains(dependant)) { - required = true; - break; + if (reverse) { + for (Component *reverseFromUninstall : qAsConst(m_virtualComponentsForReverse)) { + if (m_componentsToUninstall.contains(reverseFromUninstall)) { + bool required = false; + // Check if installed or about to be updated -packages are dependant on the package + const QList<Component*> installDependants = m_core->installDependants(reverseFromUninstall); + for (Component *dependant : installDependants) { + if (dependant->isInstalled() && !m_componentsToUninstall.contains(dependant)) { + required = true; + break; + } + } + if (required) { + unneededVirtualList.append(reverseFromUninstall); } } - if (!required) { - unneededVirtualList.append(component); - insertUninstallReason(component, UninstallerCalculator::VirtualDependent); + } + } else { + for (const QString &componentName : qAsConst(m_localVirtualComponents)) { + Component *virtualComponent = m_core->componentByName(componentName); + if (virtualComponent->isInstalled() && !m_componentsToUninstall.contains(virtualComponent)) { + // Components with auto dependencies were handled in the previous step + if (!virtualComponent->autoDependencies().isEmpty() || virtualComponent->forcedInstallation()) + continue; + + bool required = false; + // Check if installed or about to be updated -packages are dependant on the package + const QList<Component*> installDependants = m_core->installDependants(virtualComponent); + for (Component *dependant : installDependants) { + if (dependant->isInstalled() && !m_componentsToUninstall.contains(dependant)) { + required = true; + break; + } + } + if (!required) { + unneededVirtualList.append(virtualComponent); + m_virtualComponentsForReverse.append(virtualComponent); + insertUninstallReason(virtualComponent, UninstallerCalculator::VirtualDependent); + } } } } if (!unneededVirtualList.isEmpty()) - appendComponentsToUninstall(unneededVirtualList); + appendComponentsToUninstall(unneededVirtualList, reverse); } } // namespace QInstaller diff --git a/src/libs/installer/uninstallercalculator.h b/src/libs/installer/uninstallercalculator.h index c20616e93..8bb9eb814 100644 --- a/src/libs/installer/uninstallercalculator.h +++ b/src/libs/installer/uninstallercalculator.h @@ -52,27 +52,34 @@ public: AutoDependent // "Removed as autodependency component is removed" }; - UninstallerCalculator(const QList<Component *> &installedComponents, PackageManagerCore *core); + UninstallerCalculator(const QList<Component *> &installedComponents, PackageManagerCore *core, + const QHash<QString, QStringList> &autoDependencyComponentHash, + const QHash<QString, QStringList> &dependencyComponentHash, + const QStringList &localVirtualComponents); QSet<Component*> componentsToUninstall() const; - void appendComponentsToUninstall(const QList<Component*> &components); + void appendComponentsToUninstall(const QList<Component*> &components, const bool reverse = false); + void removeComponentsFromUnInstall(const QList<Component*> &components); void insertUninstallReason(Component *component, - UninstallReasonType installReasonType, + const UninstallReasonType uninstallReason, const QString &referencedComponentName = QString()); QString uninstallReason(Component *component) const; UninstallerCalculator::UninstallReasonType uninstallReasonType(Component *c) const; QString uninstallReasonReferencedComponent(Component *component) const; private: - - void appendComponentToUninstall(Component *component); - void appendVirtualComponentsToUninstall(); + void appendComponentToUninstall(Component *component, const bool reverse); + void appendVirtualComponentsToUninstall(const bool reverse); QList<Component *> m_installedComponents; QSet<Component *> m_componentsToUninstall; PackageManagerCore *m_core; QHash<QString, QPair<UninstallReasonType, QString> > m_toUninstallComponentIdReasonHash; + QHash<QString, QStringList> m_autoDependencyComponentHash; + QHash<QString, QStringList> m_dependencyComponentHash; + QStringList m_localVirtualComponents; + QList<Component *> m_virtualComponentsForReverse; }; } diff --git a/tests/auto/installer/solver/tst_solver.cpp b/tests/auto/installer/solver/tst_solver.cpp index 5e499da2d..f4e10f5e7 100644 --- a/tests/auto/installer/solver/tst_solver.cpp +++ b/tests/auto/installer/solver/tst_solver.cpp @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 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. @@ -150,6 +150,7 @@ private slots: QTest::addColumn<QList<Component *> >("selectedComponents"); QTest::addColumn<QList<Component *> >("expectedResult"); QTest::addColumn<QList<int> >("installReason"); + QTest::addColumn<AutoDependencyHash >("autodependencyHash"); PackageManagerCore *core = new PackageManagerCore(); core->setPackageManager(); @@ -169,6 +170,9 @@ private slots: core->appendRootComponent(componentB_NewVersion); core->appendRootComponent(componentB_Auto); + QHash<QString, QStringList> autodependencyHash; + autodependencyHash.insert(QLatin1String("B_version"), QStringList() << QLatin1String("B_auto")); + QTest::newRow("Installer resolved") << core << (QList<Component *>() << componentB) << (QList<Component *>() << componentB_NewVersion << componentAB << componentB << componentB_Auto) @@ -176,7 +180,8 @@ private slots: << InstallerCalculator::Dependent << InstallerCalculator::Dependent << InstallerCalculator::Resolved - << InstallerCalculator::Automatic); + << InstallerCalculator::Automatic) + << autodependencyHash; } void resolveInstaller() @@ -185,8 +190,9 @@ private slots: QFETCH(QList<Component *> , selectedComponents); QFETCH(QList<Component *> , expectedResult); QFETCH(QList<int>, installReason); + QFETCH(AutoDependencyHash, autodependencyHash); - InstallerCalculator calc(core->components(PackageManagerCore::ComponentType::AllNoReplacements)); + InstallerCalculator calc(core->components(PackageManagerCore::ComponentType::AllNoReplacements), autodependencyHash); calc.appendComponentsToInstall(selectedComponents); QList<Component *> result = calc.orderedComponentsToInstall(); @@ -229,7 +235,7 @@ private slots: QFETCH(QList<Component *> , selectedComponents); QFETCH(QList<Component *> , expectedResult); - InstallerCalculator calc(core->components(PackageManagerCore::ComponentType::AllNoReplacements)); + InstallerCalculator calc(core->components(PackageManagerCore::ComponentType::AllNoReplacements), QHash<QString, QStringList>()); QTest::ignoreMessage(QtWarningMsg, "Cannot find missing dependency \"B->=2.0.0\" for \"A\"."); calc.appendComponentsToInstall(selectedComponents); @@ -245,6 +251,7 @@ private slots: QTest::addColumn<QList<Component *> >("installedComponents"); QTest::addColumn<QSet<Component *> >("expectedResult"); QTest::addColumn<UninstallReasonList >("uninstallReasons"); + QTest::addColumn<DependencyHash >("dependencyHash"); UninstallReasonList uninstallReasonList; PackageManagerCore *core = new PackageManagerCore(); @@ -263,14 +270,19 @@ private slots: componentB->setInstalled(); componentAB->setInstalled(); + QHash<QString, QStringList> dependencyComponentHash; + dependencyComponentHash.insert(QLatin1String("A.B"), QStringList() << QLatin1String("B")); + uninstallReasonList.append(qMakePair(componentAB, UninstallerCalculator::Selected)); uninstallReasonList.append(qMakePair(componentB, UninstallerCalculator::Dependent)); QTest::newRow("Uninstaller resolved") << core << (QList<Component *>() << componentAB) << (QList<Component *>() << componentA << componentB) << (QSet<Component *>() << componentAB << componentB) - << (uninstallReasonList); + << uninstallReasonList + << dependencyComponentHash; + dependencyComponentHash.clear(); uninstallReasonList.clear(); core = new PackageManagerCore(); core->setPackageManager(); @@ -285,13 +297,16 @@ private slots: compA->setInstalled(); compB->setInstalled(); + dependencyComponentHash.insert(QLatin1String("A"), QStringList() << QLatin1String("B")); + uninstallReasonList.append(qMakePair(compA, UninstallerCalculator::Selected)); uninstallReasonList.append(qMakePair(compB, UninstallerCalculator::Dependent)); QTest::newRow("Cascade dependencies") << core << (QList<Component *>() << compA) << (QList<Component *>() << compB) << (QSet<Component *>() << compA << compB) - << (uninstallReasonList); + << (uninstallReasonList) + << dependencyComponentHash; } void resolveUninstaller() @@ -301,7 +316,9 @@ private slots: QFETCH(QList<Component *> , installedComponents); QFETCH(QSet<Component *> , expectedResult); QFETCH(UninstallReasonList, uninstallReasons); - UninstallerCalculator calc(installedComponents, core); + QFETCH(DependencyHash, dependencyHash); + + UninstallerCalculator calc(installedComponents, core, QHash<QString, QStringList>(), dependencyHash, QStringList()); calc.appendComponentsToUninstall(selectedToUninstall); QSet<Component *> result = calc.componentsToUninstall(); for (auto pair : uninstallReasons) { |