mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-06 22:54:16 +02:00
[arch] improvement -> upgrade: checking specific version requirements and marking packages as 'cannot upgrade' when these requirements are not met
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user