[arch] improvement -> upgrade: checking specific version requirements and marking packages as 'cannot upgrade' when these requirements are not met

This commit is contained in:
Vinicius Moreira
2020-09-01 19:36:05 -03:00
parent d7225dab3b
commit feb061efa4
18 changed files with 286 additions and 43 deletions

View File

@@ -1,7 +1,8 @@
import logging
import time
from distutils.version import LooseVersion
from threading import Thread
from typing import Dict, Set, List, Tuple, Iterable
from typing import Dict, Set, List, Tuple, Iterable, Optional
from bauh.api.abstract.controller import UpgradeRequirements, UpgradeRequirement
from bauh.api.abstract.handler import ProcessWatcher
@@ -261,38 +262,53 @@ class UpdatesSummarizer:
context.pkgs_data.update(all_to_install_data)
self._fill_conflicts(context, context.to_remove.keys())
if context.to_install:
self.__fill_provided_map(context=context, pkgs=context.to_install, fill_installed=False)
tf = time.time()
self.logger.info("It took {0:.2f} seconds to retrieve required upgrade packages".format(tf - ti))
return True
def __fill_provided_map(self, context: UpdateRequirementsContext):
ti = time.time()
self.logger.info("Filling provided names")
context.installed_names = pacman.list_installed_names()
installed_to_ignore = set()
def __fill_provided_map(self, context: UpdateRequirementsContext, pkgs: Dict[str, ArchPackage], fill_installed: bool = True):
if pkgs:
ti = time.time()
self.logger.info("Filling provided names")
for pkgname in context.to_update:
pacman.fill_provided_map(pkgname, pkgname, context.provided_map)
installed_to_ignore.add(pkgname)
if not context.installed_names:
context.installed_names = pacman.list_installed_names()
pdata = context.pkgs_data.get(pkgname)
if pdata and pdata['p']:
pacman.fill_provided_map('{}={}'.format(pkgname, pdata['v']), pkgname, context.provided_map)
for p in pdata['p']:
pacman.fill_provided_map(p, pkgname, context.provided_map)
split_provided = p.split('=')
installed_to_ignore = set()
if len(split_provided) > 1 and split_provided[0] != p:
pacman.fill_provided_map(split_provided[0], pkgname, context.provided_map)
for pkgname in pkgs:
pacman.fill_provided_map(pkgname, pkgname, context.provided_map)
if installed_to_ignore: # filling the provided names of the installed
installed_to_query = context.installed_names.difference(installed_to_ignore)
if fill_installed:
installed_to_ignore.add(pkgname)
if installed_to_query:
context.provided_map.update(pacman.map_provided(remote=False, pkgs=installed_to_query))
pdata = context.pkgs_data.get(pkgname)
if pdata and pdata['p']:
pacman.fill_provided_map('{}={}'.format(pkgname, pdata['v']), pkgname, context.provided_map)
tf = time.time()
self.logger.info("Filling provided names took {0:.2f} seconds".format(tf - ti))
ver_split = pdata['v'].split('-')
if len(ver_split) > 1:
pacman.fill_provided_map('{}={}'.format(pkgname, '-'.join(ver_split[0:-1])), pkgname, context.provided_map)
for p in pdata['p']:
pacman.fill_provided_map(p, pkgname, context.provided_map)
split_provided = p.split('=')
if len(split_provided) > 1 and split_provided[0] != p:
pacman.fill_provided_map(split_provided[0], pkgname, context.provided_map)
if installed_to_ignore: # filling the provided names of the installed
installed_to_query = context.installed_names.difference(installed_to_ignore)
if installed_to_query:
context.provided_map.update(pacman.map_provided(remote=False, pkgs=installed_to_query))
tf = time.time()
self.logger.info("Filling provided names took {0:.2f} seconds".format(tf - ti))
def __fill_aur_index(self, context: UpdateRequirementsContext):
if context.arch_config['aur']:
@@ -381,7 +397,7 @@ class UpdatesSummarizer:
if aur_data:
context.pkgs_data.update(aur_data)
self.__fill_provided_map(context)
self.__fill_provided_map(context=context, pkgs=context.to_update)
if context.pkgs_data:
self._fill_conflicts(context)
@@ -394,6 +410,9 @@ class UpdatesSummarizer:
self.logger.error("Package '{}' not found".format(e.name))
return
if context.pkgs_data:
self._fill_dependency_breakage(context)
self.__update_context_based_on_to_remove(context)
if context.to_update:
@@ -558,3 +577,166 @@ class UpdatesSummarizer:
self._add_to_remove(pkgs_to_sync, {dep: {n} for dep in all_deps}, context, blacklist)
else:
self.logger.warning("Package '{}' could not be removed from the transaction context because its data was not loaded")
def _fill_dependency_breakage(self, context: UpdateRequirementsContext):
if bool(context.arch_config['check_dependency_breakage']) and (context.to_update or context.to_install):
ti = time.time()
self.logger.info("Begin: checking dependency breakage")
required_by = pacman.map_required_by(context.to_update.keys()) if context.to_update else {}
if context.to_install:
required_by.update(pacman.map_required_by(context.to_install.keys(), remote=True))
reqs_not_in_transaction = set()
reqs_in_transaction = set()
transaction_pkgs = {*context.to_update.keys(), *context.to_install.keys()}
for reqs in required_by.values():
for r in reqs:
if r in transaction_pkgs:
reqs_in_transaction.add(r)
elif r in context.installed_names:
reqs_not_in_transaction.add(r)
if not reqs_not_in_transaction and not reqs_in_transaction:
return
provided_versions = {}
for p in context.provided_map:
pkg_split = p.split('=')
if len(pkg_split) > 1:
versions = provided_versions.get(pkg_split[0])
if versions is None:
versions = set()
provided_versions[pkg_split[0]] = versions
versions.add(pkg_split[1])
if not provided_versions:
return
cannot_upgrade = set()
for pkg, deps in pacman.map_required_dependencies(*reqs_not_in_transaction).items():
self._add_dependency_breakage(pkgname=pkg,
pkgdeps=deps,
provided_versions=provided_versions,
cannot_upgrade=cannot_upgrade,
context=context)
for pkg in reqs_in_transaction:
data = context.pkgs_data[pkg]
if data and data['d']:
self._add_dependency_breakage(pkgname=pkg,
pkgdeps=data['d'],
provided_versions=provided_versions,
cannot_upgrade=cannot_upgrade,
context=context)
if cannot_upgrade:
cannot_upgrade.update(self._add_dependents_as_cannot_upgrade(context=context,
names=cannot_upgrade,
pkgs_available={*context.to_update.values(), *context.to_install.values()}))
for p in cannot_upgrade:
if p in context.to_update:
del context.to_update[p]
if p in context.repo_to_update:
del context.repo_to_update[p]
if p in context.aur_to_update:
del context.aur_to_update[p]
if p in context.pkgs_data:
del context.pkgs_data[p]
if p in context.to_install:
del context.to_install[p]
if p in context.repo_to_install:
del context.repo_to_install[p]
if p in context.aur_to_install:
del context.aur_to_install[p]
tf = time.time()
self.logger.info("End: checking dependency breakage. Time: {0:.2f} seconds".format(tf - ti))
def _add_dependents_as_cannot_upgrade(self, context: UpdateRequirementsContext, names: Iterable[str], pkgs_available: Set[ArchPackage], already_removed: Optional[Set[str]] = None, iteration_level: int = 0) -> Set[str]:
removed = set() if already_removed is None else already_removed
removed.update(names)
available = {p for p in pkgs_available if p.name not in removed}
to_remove = set()
if available:
for pkg in available:
if pkg.name not in removed:
data = context.pkgs_data.get(pkg.name)
if data and data['d']:
for dep in data['d']:
dep_providers = context.provided_map.get(dep)
if dep_providers:
for p in dep_providers:
if p in names:
to_remove.add(pkg.name)
if pkg.name not in context.cannot_upgrade:
reason = "{} {}".format(self.i18n['arch.info.depends on'].capitalize(), p)
context.cannot_upgrade[pkg.name] = UpgradeRequirement(pkg=pkg,
reason=reason,
sorting_priority=iteration_level - 1)
break
if to_remove:
removed.update(to_remove)
self._add_dependents_as_cannot_upgrade(context=context, names=to_remove, pkgs_available=available,
already_removed=to_remove, iteration_level=iteration_level-1)
return to_remove
def _add_dependency_breakage(self, pkgname: str, pkgdeps: Optional[Set[str]], provided_versions: Dict[str, Set[str]], cannot_upgrade: Set[str], context: UpdateRequirementsContext):
if pkgdeps:
for dep in pkgdeps:
dep_split = RE_DEP_OPERATORS.split(dep)
if len(dep_split) > 1 and dep_split[1]:
real_providers = context.provided_map.get(dep_split[0])
if real_providers:
versions = provided_versions.get(dep_split[0])
if versions:
op = ''.join(RE_DEP_OPERATORS.findall(dep))
if op == '=':
op = '=='
version_match = False
for v in versions:
provided_version, required_version = LooseVersion(v), LooseVersion(dep_split[1])
if eval('provided_version {} required_version'.format(op)):
version_match = True
break
if not version_match:
for pname in real_providers:
if pname not in cannot_upgrade:
provider = context.to_update.get(pname)
if provider:
cannot_upgrade.add(pname)
reason = self.i18n['arch.sync.dep_breakage.reason'].format(pkgname, dep)
context.cannot_upgrade[pname] = UpgradeRequirement(pkg=provider,
reason=reason)