[arch] refactoring: conflict resolution checking (+ test cases)

This commit is contained in:
Vinicius Moreira
2022-11-20 11:13:54 -03:00
parent 53e3caba58
commit 69cf81dbe9
7 changed files with 1556 additions and 204 deletions

View File

@@ -8,7 +8,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixes ### Fixes
- Arch - Arch
- not properly mapping all provided packages by the system when checking for upgrade requirements - not properly checking all provided packages by the system when checking for conflicts (upgrade requirements)
### Improvements
- Arch
- added several test cases to ensure conflicting scenarios are properly covered when checking for upgrade requirements
## [0.10.4] 2022-11-05 ## [0.10.4] 2022-11-05

View File

@@ -58,7 +58,7 @@ class SearchResult:
class UpgradeRequirement: class UpgradeRequirement:
def __init__(self, pkg: SoftwarePackage, reason: str = None, required_size: float = None, extra_size: float = None, sorting_priority: int = 0): def __init__(self, pkg: SoftwarePackage, reason: Optional[str] = None, required_size: Optional[float] = None, extra_size: Optional[float] = None, sorting_priority: int = 0):
""" """
:param pkg: :param pkg:
@@ -77,6 +77,15 @@ class UpgradeRequirement:
def sort_by_priority(req: "UpgradeRequirement") -> Tuple[int, str]: def sort_by_priority(req: "UpgradeRequirement") -> Tuple[int, str]:
return -req.sorting_priority, req.pkg.name return -req.sorting_priority, req.pkg.name
def __eq__(self, other) -> bool:
if isinstance(other, UpgradeRequirement):
return self.__dict__ == other.__dict__
return False
def __hash__(self) -> int:
return sum((hash(k) + hash(v) for k, v in self.__dict__.items()))
class UpgradeRequirements: class UpgradeRequirements:

View File

@@ -464,9 +464,9 @@ class DependenciesAnalyser:
if missing_deps: if missing_deps:
self._fill_single_providers_data(missing_deps, repo_missing, aur_missing, deps_data) self._fill_single_providers_data(missing_deps, repo_missing, aur_missing, deps_data)
missing_subdeps = self.map_missing_deps(pkgs_data={**deps_data}, provided_map=provided_map, aur_index=aur_index, missing_subdeps = self.map_missing_deps(pkgs_data={**deps_data}, provided_map=provided_map,
deps_checked=deps_checked, sort=False, deps_data=deps_data, aur_index=aur_index, deps_checked=deps_checked, sort=False,
watcher=watcher, deps_data=deps_data, watcher=watcher,
remote_provided_map=remote_provided_map, remote_provided_map=remote_provided_map,
remote_repo_map=remote_repo_map, remote_repo_map=remote_repo_map,
automatch_providers=automatch_providers, automatch_providers=automatch_providers,
@@ -650,7 +650,7 @@ class DependenciesAnalyser:
if pkgnames: if pkgnames:
to_ignore.update(pkgnames) to_ignore.update(pkgnames)
all_requirements = {req for reqs in pacman.map_required_by(pkgnames).values() for req in reqs if req not in to_ignore} all_requirements = {r for reqs in pacman.map_required_by(pkgnames).values() for r in reqs if r not in to_ignore}
if all_requirements: if all_requirements:
sub_requirements = self.map_all_required_by(all_requirements, to_ignore) sub_requirements = self.map_all_required_by(all_requirements, to_ignore)

View File

@@ -396,7 +396,7 @@ def get_version_for_not_installed(pkgname: str) -> str:
def map_repositories(pkgnames: Iterable[str] = None) -> Dict[str, str]: def map_repositories(pkgnames: Iterable[str] = None) -> Dict[str, str]:
info = run_cmd('pacman -Si {}'.format(' '.join(pkgnames) if pkgnames else ''), print_error=False, ignore_return_code=True) info = run_cmd(f"pacman -Si {' '.join(pkgnames) if pkgnames else ''}", print_error=False, ignore_return_code=True)
if info: if info:
repos = re.findall(r'(Name|Repository)\s*:\s*(.+)', info) repos = re.findall(r'(Name|Repository)\s*:\s*(.+)', info)
@@ -529,7 +529,7 @@ def upgrade_system(root_password: Optional[str]) -> SimpleProcess:
return SimpleProcess(cmd=['pacman', '-Syyu', '--noconfirm'], root_password=root_password) return SimpleProcess(cmd=['pacman', '-Syyu', '--noconfirm'], root_password=root_password)
def fill_provided_map(key: str, val: str, output: dict): def _fill_provided_map(key: str, val: str, output: Dict[str, Set[str]]):
current_val = output.get(key) current_val = output.get(key)
if current_val is None: if current_val is None:
@@ -558,19 +558,19 @@ def map_provided(remote: bool = False, pkgs: Iterable[str] = None) -> Optional[D
elif field == 'Version': elif field == 'Version':
latest_version = val.split('=')[0] latest_version = val.split('=')[0]
elif field == 'Provides': elif field == 'Provides':
fill_provided_map(latest_name, latest_name, provided_map) _fill_provided_map(latest_name, latest_name, provided_map)
fill_provided_map(f'{latest_name}={latest_version}', latest_name, provided_map) _fill_provided_map(f'{latest_name}={latest_version}', latest_name, provided_map)
if val != 'None': if val != 'None':
for w in val.split(' '): for w in val.split(' '):
if w: if w:
word = w.strip() word = w.strip()
fill_provided_map(word, latest_name, provided_map) _fill_provided_map(word, latest_name, provided_map)
word_split = word.split('=') word_split = word.split('=')
if word_split[0] != word: if word_split[0] != word:
fill_provided_map(word_split[0], latest_name, provided_map) _fill_provided_map(word_split[0], latest_name, provided_map)
else: else:
provided = True provided = True
@@ -583,12 +583,12 @@ def map_provided(remote: bool = False, pkgs: Iterable[str] = None) -> Optional[D
for w in l.split(' '): for w in l.split(' '):
if w: if w:
word = w.strip() word = w.strip()
fill_provided_map(word, latest_name, provided_map) _fill_provided_map(word, latest_name, provided_map)
word_split = word.split('=') word_split = word.split('=')
if word_split[0] != word: if word_split[0] != word:
fill_provided_map(word_split[0], latest_name, provided_map) _fill_provided_map(word_split[0], latest_name, provided_map)
return provided_map return provided_map
@@ -857,7 +857,8 @@ def get_cache_dir() -> str:
def map_required_by(names: Iterable[str] = None, remote: bool = False) -> Dict[str, Set[str]]: def map_required_by(names: Iterable[str] = None, remote: bool = False) -> Dict[str, Set[str]]:
output = run_cmd('pacman -{} {}'.format('Sii' if remote else 'Qi', ' '.join(names) if names else ''), print_error=False) output = run_cmd(f"pacman -{'Sii' if remote else 'Qi'} {' '.join(names) if names else ''}".strip(),
print_error=False)
if output: if output:
latest_name, required = None, None latest_name, required = None, None

View File

@@ -2,7 +2,7 @@ import logging
import time import time
import traceback import traceback
from threading import Thread from threading import Thread
from typing import Dict, Set, List, Tuple, Iterable, Optional from typing import Dict, Set, List, Tuple, Iterable, Optional, Any
from bauh.api.abstract.controller import UpgradeRequirements, UpgradeRequirement from bauh.api.abstract.controller import UpgradeRequirements, UpgradeRequirement
from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.handler import ProcessWatcher
@@ -47,12 +47,26 @@ class UpdateRequirementsContext:
if self.provided_map is None: if self.provided_map is None:
self.provided_map = {**update} self.provided_map = {**update}
else: else:
for key, val in update.items(): for provider, provided in update.items():
if key not in self.provided_map: provided_set = self.provided_map.get(provider)
self.provided_map[key] = val
elif val: if provided_set is None:
current_val = self.provided_map[key] provided_set = set()
current_val.update(val) self.provided_map[provider] = provided_set
provided_set.update(provided)
def add_to_provided_map(self, provider: str, provided: str):
if self.provided_map is None:
self.provided_map = dict()
provided_set = self.provided_map.get(provider)
if provided_set is None:
provided_set = set()
self.provided_map[provider] = provided_set
provided_set.add(provided)
class UpdatesSummarizer: class UpdatesSummarizer:
@@ -72,8 +86,7 @@ class UpdatesSummarizer:
for src_pkg in {p for p, data in context.pkgs_data.items() if for src_pkg in {p for p, data in context.pkgs_data.items() if
data['d'] and pkg1 in data['d'] or pkg2 in data['d']}: data['d'] and pkg1 in data['d'] or pkg2 in data['d']}:
if src_pkg not in context.cannot_upgrade: if src_pkg not in context.cannot_upgrade:
reason = self.i18n['arch.update_summary.to_install.dep_conflict'].format("'{}'".format(pkg1), reason = self.i18n['arch.update_summary.to_install.dep_conflict'].format(f"'{pkg1}'", f"'{pkg2}'")
"'{}'".format(pkg2))
context.cannot_upgrade[src_pkg] = UpgradeRequirement(context.to_update[src_pkg], reason) context.cannot_upgrade[src_pkg] = UpgradeRequirement(context.to_update[src_pkg], reason)
del context.to_update[src_pkg] del context.to_update[src_pkg]
@@ -137,11 +150,11 @@ class UpdatesSummarizer:
def _handle_conflict_both_to_update(self, pkg1: str, pkg2: str, context: UpdateRequirementsContext): def _handle_conflict_both_to_update(self, pkg1: str, pkg2: str, context: UpdateRequirementsContext):
if pkg1 not in context.cannot_upgrade: if pkg1 not in context.cannot_upgrade:
reason = "{} '{}'".format(self.i18n['arch.info.conflicts with'].capitalize(), pkg2) reason = f"{self.i18n['arch.info.conflicts with'].capitalize()} '{pkg2}'"
context.cannot_upgrade[pkg1] = UpgradeRequirement(pkg=context.to_update[pkg1], reason=reason) context.cannot_upgrade[pkg1] = UpgradeRequirement(pkg=context.to_update[pkg1], reason=reason)
if pkg2 not in context.cannot_upgrade: if pkg2 not in context.cannot_upgrade:
reason = "{} '{}'".format(self.i18n['arch.info.conflicts with'].capitalize(), pkg1) reason = f"{self.i18n['arch.info.conflicts with'].capitalize()} '{pkg1}'"
context.cannot_upgrade[pkg2] = UpgradeRequirement(pkg=context.to_update[pkg2], reason=reason) context.cannot_upgrade[pkg2] = UpgradeRequirement(pkg=context.to_update[pkg2], reason=reason)
for p in (pkg1, pkg2): for p in (pkg1, pkg2):
@@ -153,65 +166,101 @@ class UpdatesSummarizer:
else: else:
del context.aur_to_update[p] del context.aur_to_update[p]
def _filter_and_map_conflicts(self, context: UpdateRequirementsContext) -> Dict[str, str]: def _map_conflicts(self, data: Dict[str, Dict[str, Any]], providers: Dict[str, Set[str]],
versions: Dict[str, str]) -> Tuple[Dict[str, str], Dict[str, str]]:
"""
Parameters
pkgs_data: a dict mapping the packages whose conflicts need to be analyzed to their data
providers: a dict mapping the available providers on the context (installed and to be installed) to their
respective package
versions: a dict mapping the package name to it's version (the updated or to be installed version)
Return
a tuple with two dictionaries:
- first: containing all conflicts
- second: containing mutual conflicts
"""
root_conflict = {} root_conflict = {}
mutual_conflicts = {} mutual_conflicts = {}
for p, data in context.pkgs_data.items(): for pkg_name, data in data.items():
if data['c']: if data['c']:
for c in data['c']: for c in data['c']:
if c: if c:
name_op_exp = DependenciesAnalyser.re_dep_operator().split(c) name_op_exp = DependenciesAnalyser.re_dep_operator().split(c)
conflict_name = name_op_exp[0] conflict_name = name_op_exp[0]
if conflict_name != p: if conflict_name != pkg_name:
conflict_version = context.installed.get(conflict_name) conflict_providers = providers.get(conflict_name)
if conflict_version: if conflict_providers:
if len(name_op_exp) == 1 or match_required_version(conflict_version, checked_conflicts = []
name_op_exp[1], for provider in conflict_providers:
name_op_exp[2]): if provider != pkg_name:
root_conflict[conflict_name] = p if len(name_op_exp) == 1:
checked_conflicts.append(provider)
else:
provider_version = versions.get(provider)
if (p, conflict_name) in root_conflict.items(): if match_required_version(provider_version,
mutual_conflicts[conflict_name] = p name_op_exp[1],
name_op_exp[2]):
checked_conflicts.append(provider)
if mutual_conflicts: for provider in checked_conflicts:
for pkg1, pkg2 in mutual_conflicts.items(): root_conflict[provider] = pkg_name
pkg1_to_install = pkg1 in context.to_install
pkg2_to_install = pkg2 in context.to_install
if pkg1_to_install and pkg2_to_install: # remove both from to install and mark their source packages as 'cannot_update' if (pkg_name, provider) in root_conflict.items():
self._handle_conflict_both_to_install(pkg1, pkg2, context) mutual_conflicts[provider] = pkg_name
elif (pkg1_to_install and not pkg2_to_install) or (not pkg1_to_install and pkg2_to_install):
self._handle_conflict_to_update_and_to_install(pkg1, pkg2, pkg1_to_install, context)
else:
self._handle_conflict_both_to_update(pkg1, pkg2, context) # adding both to the 'cannot update' list
for pkg1, pkg2 in mutual_conflicts.items(): # removing conflicting packages from the packages selected to upgrade return root_conflict, mutual_conflicts
for p in (pkg1, pkg2):
if p in context.pkgs_data:
if context.pkgs_data[p].get('c'):
for c in context.pkgs_data[p]['c']:
# source = provided_map[c]
if c in root_conflict:
del root_conflict[c]
del context.pkgs_data[p] def _handle_mutual_conflicts(self, mutual_conflicts: Dict[str, str], all_conflicts: Dict[str, str],
context: UpdateRequirementsContext):
for pkg1, pkg2 in mutual_conflicts.items():
pkg1_to_install = pkg1 in context.to_install
pkg2_to_install = pkg2 in context.to_install
return root_conflict if pkg1_to_install and pkg2_to_install:
# remove both from to install and mark their source packages as 'cannot_update'
self._handle_conflict_both_to_install(pkg1, pkg2, context)
elif (pkg1_to_install and not pkg2_to_install) or (not pkg1_to_install and pkg2_to_install):
self._handle_conflict_to_update_and_to_install(pkg1, pkg2, pkg1_to_install, context)
else:
# adding both to the 'cannot update' list
self._handle_conflict_both_to_update(pkg1, pkg2, context)
def _fill_conflicts(self, context: UpdateRequirementsContext, blacklist: Iterable[str] = None): # removing conflicting packages from the packages selected to upgrade
for pkg1, pkg2 in mutual_conflicts.items():
for pkg_name in (pkg1, pkg2):
if pkg_name in context.pkgs_data:
if context.pkgs_data[pkg_name].get('c'):
for c in context.pkgs_data[pkg_name]['c']:
# source = provided_map[c]
if c in all_conflicts:
del all_conflicts[c]
del context.pkgs_data[pkg_name]
def _fill_conflicts(self, context: UpdateRequirementsContext, blacklist: Optional[Iterable[str]] = None):
self.logger.info("Checking conflicts") self.logger.info("Checking conflicts")
root_conflict = self._filter_and_map_conflicts(context) conflicts, mutual_conflicts = self._map_conflicts(data=context.pkgs_data,
providers=context.provided_map,
versions=context.installed)
if root_conflict: if mutual_conflicts:
for dep, source in root_conflict.items(): self._handle_mutual_conflicts(mutual_conflicts, conflicts, context)
if dep not in context.to_remove and (not blacklist or dep not in blacklist):
req = ArchPackage(name=dep, installed=True, i18n=self.i18n) if conflicts:
reason = "{} '{}'".format(self.i18n['arch.info.conflicts with'].capitalize(), source) for conflict_name, source_name in conflicts.items():
context.to_remove[dep] = UpgradeRequirement(req, reason) if conflict_name not in context.to_remove and (not blacklist or conflict_name not in blacklist):
if conflict_name in context.to_update:
conflict = context.to_update[conflict_name]
else:
conflict = ArchPackage(name=conflict_name, installed=True, i18n=self.i18n)
reason = f"{self.i18n['arch.info.conflicts with'].capitalize()} '{source_name}'"
context.to_remove[conflict_name] = UpgradeRequirement(conflict, reason)
def _map_and_add_package(self, pkg_data: Tuple[str, str], idx: int, output: dict): def _map_and_add_package(self, pkg_data: Tuple[str, str], idx: int, output: dict):
version = None version = None
@@ -234,6 +283,31 @@ class UpdatesSummarizer:
output[idx] = ArchPackage(name=pkg_data[0], version=version, latest_version=version, repository=pkg_data[1], i18n=self.i18n) output[idx] = ArchPackage(name=pkg_data[0], version=version, latest_version=version, repository=pkg_data[1], i18n=self.i18n)
def _fill_conflicts_to_install(self, context: UpdateRequirementsContext, install_data: Dict[str, Dict[str, Any]]):
"""
Parameters
context: update context
install_data: a dict mapping the packages to be installed names by their data
"""
# to properly fill conflicts considering new packages to be installed:
# - the 'context.provided_map' should contain the providers of these new packages
# - the 'context.installed' should contain the versions of these new packages
provided_map_bkp = {**context.provided_map}
self.__fill_provided_map(context=context, pkgs=context.to_install, fill_installed=False)
# adding the new packages to install as 'installed'
for pkg, data in install_data.items():
context.installed[pkg] = data["v"]
self._fill_conflicts(context, context.to_remove.keys())
# restoring the original data structures
context.provided_map = provided_map_bkp
for pkg in install_data:
if pkg in context.installed:
del context.installed[pkg]
def _fill_to_install(self, context: UpdateRequirementsContext) -> bool: def _fill_to_install(self, context: UpdateRequirementsContext) -> bool:
ti = time.time() ti = time.time()
self.logger.info("Discovering updates missing packages") self.logger.info("Discovering updates missing packages")
@@ -266,7 +340,8 @@ class UpdatesSummarizer:
for idx, dep in enumerate(deps): for idx, dep in enumerate(deps):
data = deps_data[dep[0]] data = deps_data[dep[0]]
pkg = ArchPackage(name=dep[0], version=data['v'], latest_version=data['v'], repository=dep[1], i18n=self.i18n, package_base=data.get('b', dep[0])) pkg = ArchPackage(name=dep[0], version=data['v'], latest_version=data['v'], repository=dep[1],
i18n=self.i18n, package_base=data.get('b', dep[0]))
sorted_pkgs[idx] = pkg sorted_pkgs[idx] = pkg
context.to_install[dep[0]] = pkg context.to_install[dep[0]] = pkg
@@ -284,16 +359,17 @@ class UpdatesSummarizer:
if all_to_install_data: if all_to_install_data:
context.pkgs_data.update(all_to_install_data) context.pkgs_data.update(all_to_install_data)
self._fill_conflicts(context, context.to_remove.keys()) self._fill_conflicts_to_install(context, all_to_install_data)
if context.to_install: if context.to_install:
self.__fill_provided_map(context=context, pkgs=context.to_install, fill_installed=False) self.__fill_provided_map(context=context, pkgs=context.to_install, fill_installed=False)
tf = time.time() tf = time.time()
self.logger.info("It took {0:.2f} seconds to retrieve required upgrade packages".format(tf - ti)) self.logger.info(f"It took {tf - ti:.2f} seconds to retrieve required upgrade packages")
return True return True
def __fill_provided_map(self, context: UpdateRequirementsContext, pkgs: Dict[str, ArchPackage], fill_installed: bool = True): def __fill_provided_map(self, context: UpdateRequirementsContext, pkgs: Dict[str, ArchPackage],
fill_installed: bool = True):
if pkgs: if pkgs:
ti = time.time() ti = time.time()
self.logger.info("Filling provided names") self.logger.info("Filling provided names")
@@ -304,26 +380,26 @@ class UpdatesSummarizer:
installed_to_ignore = set() installed_to_ignore = set()
for pkgname in pkgs: for pkgname in pkgs:
pacman.fill_provided_map(pkgname, pkgname, context.provided_map) context.add_to_provided_map(pkgname, pkgname)
if fill_installed: if fill_installed:
installed_to_ignore.add(pkgname) installed_to_ignore.add(pkgname)
pdata = context.pkgs_data.get(pkgname) pdata = context.pkgs_data.get(pkgname)
if pdata and pdata['p']: if pdata and pdata['p']:
pacman.fill_provided_map('{}={}'.format(pkgname, pdata['v']), pkgname, context.provided_map) context.add_to_provided_map(f"{pkgname}={pdata['v']}", pkgname)
ver_split = pdata['v'].split('-') ver_split = pdata['v'].split('-')
if len(ver_split) > 1: if len(ver_split) > 1:
pacman.fill_provided_map('{}={}'.format(pkgname, '-'.join(ver_split[0:-1])), pkgname, context.provided_map) context.add_to_provided_map(f"{pkgname}={'-'.join(ver_split[0:-1])}", pkgname)
for p in pdata['p']: for p in pdata['p']:
pacman.fill_provided_map(p, pkgname, context.provided_map) context.add_to_provided_map(p, pkgname)
split_provided = p.split('=') split_provided = p.split('=')
if len(split_provided) > 1 and split_provided[0] != p: if len(split_provided) > 1 and split_provided[0] != p:
pacman.fill_provided_map(split_provided[0], pkgname, context.provided_map) context.add_to_provided_map(split_provided[0], pkgname)
if context.installed and installed_to_ignore: # filling the provided names of the installed if context.installed and installed_to_ignore: # filling the provided names of the installed
installed_to_query = {*context.installed}.difference(installed_to_ignore) installed_to_query = {*context.installed}.difference(installed_to_ignore)
@@ -343,7 +419,10 @@ class UpdatesSummarizer:
context.aur_index.update(names) context.aur_index.update(names)
self.logger.info("AUR index loaded on the context") self.logger.info("AUR index loaded on the context")
def _map_requirement(self, pkg: ArchPackage, context: UpdateRequirementsContext, installed_sizes: Dict[str, int] = None, to_install: bool = False, to_sync: Set[str] = None) -> UpgradeRequirement: def _map_requirement(self, pkg: ArchPackage, context: UpdateRequirementsContext,
installed_sizes: Optional[Dict[str, float]] = None, to_install: bool = False,
to_sync: Set[str] = None) -> UpgradeRequirement:
requirement = UpgradeRequirement(pkg) requirement = UpgradeRequirement(pkg)
if pkg.repository != 'aur': if pkg.repository != 'aur':
@@ -383,13 +462,15 @@ class UpdatesSummarizer:
required_by.add(p) required_by.add(p)
break break
requirement.reason = '{}: {}'.format(self.i18n['arch.info.required by'].capitalize(), ','.join(required_by) if required_by else '?') requirement.reason = f"{self.i18n['arch.info.required by'].capitalize()}: " \
f"{','.join(required_by) if required_by else '?'}"
return requirement return requirement
def summarize(self, pkgs: List[ArchPackage], root_password: Optional[str], arch_config: dict) -> Optional[UpgradeRequirements]: def summarize(self, pkgs: List[ArchPackage], root_password: Optional[str], arch_config: dict) \
res = UpgradeRequirements([], [], [], []) -> Optional[UpgradeRequirements]:
res = UpgradeRequirements([], [], [], [])
remote_provided_map = pacman.map_provided(remote=True) remote_provided_map = pacman.map_provided(remote=True)
remote_repo_map = pacman.map_repositories() remote_repo_map = pacman.map_repositories()
context = UpdateRequirementsContext(to_update={}, repo_to_update={}, aur_to_update={}, repo_to_install={}, context = UpdateRequirementsContext(to_update={}, repo_to_update={}, aur_to_update={}, repo_to_install={},
@@ -434,20 +515,22 @@ class UpdatesSummarizer:
self.logger.info("The operation was cancelled by the user") self.logger.info("The operation was cancelled by the user")
return return
except PackageNotFoundException as e: except PackageNotFoundException as e:
self.logger.error("Package '{}' not found".format(e.name)) self.logger.error(f"Package '{e.name}' not found")
return return
if context.pkgs_data: if context.pkgs_data:
self._fill_dependency_breakage(context) self._fill_dependency_breakage(context)
self.__update_context_based_on_to_remove(context) if context.to_remove:
self.__update_context_based_on_to_remove(context)
if context.to_update: if context.to_update:
installed_sizes = pacman.get_installed_size(list(context.to_update.keys())) installed_sizes = pacman.get_installed_size(list(context.to_update.keys()))
sorted_pkgs = [] sorted_pkgs = []
if context.repo_to_update: # only sorting by name ( pacman already knows the best order to perform the upgrade ) if context.repo_to_update:
# only sorting by name (pacman already knows the best order to perform the upgrade)
sorted_pkgs.extend(context.repo_to_update.values()) sorted_pkgs.extend(context.repo_to_update.values())
sorted_pkgs.sort(key=lambda pkg: pkg.name) sorted_pkgs.sort(key=lambda pkg: pkg.name)
@@ -468,101 +551,121 @@ class UpdatesSummarizer:
if context.to_install: if context.to_install:
to_sync = {r.pkg.name for r in res.to_upgrade} if res.to_upgrade else {} to_sync = {r.pkg.name for r in res.to_upgrade} if res.to_upgrade else {}
to_sync.update(context.to_install.keys()) to_sync.update(context.to_install.keys())
res.to_install = [self._map_requirement(p, context, to_install=True, to_sync=to_sync) for p in context.to_install.values()] res.to_install = [self._map_requirement(p, context, to_install=True, to_sync=to_sync)
for p in context.to_install.values()]
res.context['data'] = context.pkgs_data res.context['data'] = context.pkgs_data
return res return res
def __update_context_based_on_to_remove(self, context: UpdateRequirementsContext): def __update_context_based_on_to_remove(self, context: UpdateRequirementsContext):
if context.to_remove: # filtering all package to synchronization from the transaction context
to_remove_provided = {} to_sync = set()
# filtering all package to synchronization from the transaction context
to_sync = {*(context.to_update.keys() if context.to_update else set()), *(context.to_install.keys() if context.to_install else set())}
if to_sync: # checking if any packages to sync on the context rely on the 'to remove' ones
to_remove_provided.update(pacman.map_provided(remote=False, pkgs=context.to_remove.keys()))
to_remove_from_sync = {} # will store all packages that should be removed
for pname in to_sync: if context.to_update:
if pname in context.pkgs_data: to_sync.update(context.to_update.keys())
deps = context.pkgs_data[pname].get('d')
if deps: if context.to_install:
required = set() to_sync.update(context.to_install.keys())
for pkg in context.to_remove: to_remove_provided = {}
for provided in to_remove_provided[pkg]: if to_sync: # checking if any packages to sync on the context rely on the 'to remove' ones
if provided in deps: to_remove_provided.update(pacman.map_provided(remote=False, pkgs=context.to_remove.keys()))
required.add(pkg) to_remove_from_sync = {} # will store all packages that should be removed
break
if required: for pname in to_sync:
to_remove_from_sync[pname] = required if pname in context.pkgs_data:
deps = context.pkgs_data[pname].get('d')
if deps:
required = set()
for pkg in context.to_remove:
for provided in to_remove_provided[pkg]:
if provided in deps:
required.add(pkg)
break
if required:
to_remove_from_sync[pname] = required
else:
self.logger.warning(f"Conflict resolution: package '{pname}' marked to synchronization "
f"has no data loaded")
if to_remove_from_sync: # removing all these packages and their dependents from the context
self._add_to_remove(to_sync, to_remove_from_sync, context)
# checking if the installed packages that are not in the transaction context rely on the current
# packages to be removed:
current_to_remove = {*context.to_remove.keys()}
required_by_to_remove = self.deps_analyser.map_all_required_by(current_to_remove, {*to_sync})
if required_by_to_remove:
# updating provided context:
provided_not_mapped = set()
for pkg in current_to_remove.difference({*to_remove_provided.keys()}):
if pkg not in context.pkgs_data:
provided_not_mapped.add(pkg)
else:
provided = context.pkgs_data[pkg].get('p')
if provided:
to_remove_provided[pkg] = provided
else: else:
self.logger.warning("Conflict resolution: package '{}' marked to synchronization has no data loaded")
if to_remove_from_sync: # removing all these packages and their dependents from the context
self._add_to_remove(to_sync, to_remove_from_sync, context)
# checking if the installed packages that are not in the transaction context rely on the current packages to be removed:
current_to_remove = {*context.to_remove.keys()}
required_by_installed = self.deps_analyser.map_all_required_by(current_to_remove, {*to_sync})
if required_by_installed:
# updating provided context:
provided_not_mapped = set()
for pkg in current_to_remove.difference({*to_remove_provided.keys()}):
if pkg not in context.pkgs_data:
provided_not_mapped.add(pkg) provided_not_mapped.add(pkg)
if provided_not_mapped:
to_remove_provided.update(pacman.map_provided(remote=False, pkgs=provided_not_mapped))
deps_no_data = {dep for dep in required_by_to_remove if dep not in context.pkgs_data}
deps_nodata_deps = pacman.map_required_dependencies(*deps_no_data) if deps_no_data else {}
reverse_to_remove_provided = {p: name for name, provided in to_remove_provided.items() for p in provided}
for pkg in required_by_to_remove:
if pkg not in context.to_remove:
if pkg in context.pkgs_data:
dep_deps = context.pkgs_data[pkg].get('d')
else: else:
provided = context.pkgs_data[pkg].get('p') dep_deps = deps_nodata_deps.get(pkg)
if provided:
to_remove_provided[pkg] = provided
else:
provided_not_mapped.add(pkg)
if provided_not_mapped: if dep_deps:
to_remove_provided.update(pacman.map_provided(remote=False, pkgs=provided_not_mapped)) source = ', '.join(
(reverse_to_remove_provided[d] for d in dep_deps if d in reverse_to_remove_provided))
reason = f"{self.i18n['arch.info.depends on'].capitalize()} '{source if source else '?'}'"
else:
reason = '?'
deps_no_data = {dep for dep in required_by_installed if dep in context.pkgs_data} pkg_repo = context.remote_repo_map.get(pkg)
deps_nodata_deps = pacman.map_required_dependencies(*deps_no_data) if deps_no_data else {} pkg_version = context.installed.get(pkg)
context.to_remove[pkg] = UpgradeRequirement(pkg=ArchPackage(name=pkg,
installed=True,
i18n=self.i18n,
version=pkg_version,
latest_version=pkg_version,
repository=pkg_repo),
reason=reason)
reverse_to_remove_provided = {p: name for name, provided in to_remove_provided.items() for p in provided} for name in context.to_remove: # upgrading lists
if name in context.pkgs_data:
del context.pkgs_data[name]
for pkg in required_by_installed: if name in context.aur_to_update:
if pkg not in context.to_remove: del context.aur_to_update[name]
if pkg in context.pkgs_data:
dep_deps = context.pkgs_data[pkg].get('d')
else:
dep_deps = deps_nodata_deps.get(pkg)
if dep_deps: if name in context.repo_to_update:
source = ', '.join((reverse_to_remove_provided[d] for d in dep_deps if d in reverse_to_remove_provided)) del context.repo_to_update[name]
reason = "{} '{}'".format(self.i18n['arch.info.depends on'].capitalize(), source if source else '?')
context.to_remove[pkg] = UpgradeRequirement(pkg=ArchPackage(name=pkg,
installed=True,
i18n=self.i18n),
reason=reason)
for name in context.to_remove: # upgrading lists removed_size = pacman.get_installed_size([*context.to_remove.keys()])
if name in context.pkgs_data:
del context.pkgs_data[name]
if name in context.aur_to_update: if removed_size:
del context.aur_to_update[name] for name, size in removed_size.items():
if size is not None:
req = context.to_remove.get(name)
if req:
req.extra_size = size
if name in context.repo_to_update: def _add_to_remove(self, pkgs_to_sync: Set[str], names: Dict[str, Set[str]], context: UpdateRequirementsContext,
del context.repo_to_update[name] to_ignore: Set[str] = None):
removed_size = pacman.get_installed_size([*context.to_remove.keys()])
if removed_size:
for name, size in removed_size.items():
if size is not None:
req = context.to_remove.get(name)
if req:
req.extra_size = size
def _add_to_remove(self, pkgs_to_sync: Set[str], names: Dict[str, Set[str]], context: UpdateRequirementsContext, to_ignore: Set[str] = None):
blacklist = to_ignore if to_ignore else set() blacklist = to_ignore if to_ignore else set()
blacklist.update(names) blacklist.update(names)
@@ -582,20 +685,19 @@ class UpdatesSummarizer:
dependents[n] = all_deps dependents[n] = all_deps
else: else:
self.logger.warning("Package '{}' to sync could not be removed from the transaction context because its data was not loaded") self.logger.warning(f"Package '{pname}' to sync could not be removed from the transaction context "
f"because its data was not loaded")
for n in names: for n in names:
if n in context.pkgs_data: if n in context.pkgs_data:
if n not in context.to_remove: if n not in context.to_remove:
depends_on = names.get(n) depends_on = names.get(n)
if depends_on: if depends_on:
reason = "{} '{}'".format(self.i18n['arch.info.depends on'].capitalize(), ', '.join(depends_on)) reason = f"{self.i18n['arch.info.depends on'].capitalize()} '{', '.join(depends_on)}'"
else: else:
reason = '?' reason = '?'
context.to_remove[n] = UpgradeRequirement(pkg=ArchPackage(name=n, context.to_remove[n] = UpgradeRequirement(pkg=ArchPackage(name=n, installed=True, i18n=self.i18n),
installed=True,
i18n=self.i18n),
reason=reason) reason=reason)
all_deps = dependents.get(n) all_deps = dependents.get(n)
@@ -603,7 +705,8 @@ class UpdatesSummarizer:
if all_deps: if all_deps:
self._add_to_remove(pkgs_to_sync, {dep: {n} for dep in all_deps}, context, blacklist) self._add_to_remove(pkgs_to_sync, {dep: {n} for dep in all_deps}, context, blacklist)
else: else:
self.logger.warning("Package '{}' could not be removed from the transaction context because its data was not loaded") self.logger.warning(f"Package '{n}' could not be removed from the transaction context because its "
f"data was not loaded")
def _fill_dependency_breakage(self, context: UpdateRequirementsContext): def _fill_dependency_breakage(self, context: UpdateRequirementsContext):
if bool(context.arch_config['check_dependency_breakage']) and (context.to_update or context.to_install): if bool(context.arch_config['check_dependency_breakage']) and (context.to_update or context.to_install):
@@ -667,9 +770,10 @@ class UpdatesSummarizer:
context=context) context=context)
if cannot_upgrade: if cannot_upgrade:
pkgs_available = {*context.to_update.values(), *context.to_install.values()}
cannot_upgrade.update(self._add_dependents_as_cannot_upgrade(context=context, cannot_upgrade.update(self._add_dependents_as_cannot_upgrade(context=context,
names=cannot_upgrade, names=cannot_upgrade,
pkgs_available={*context.to_update.values(), *context.to_install.values()})) pkgs_available=pkgs_available))
for p in cannot_upgrade: for p in cannot_upgrade:
if p in context.to_update: if p in context.to_update:
@@ -696,7 +800,9 @@ class UpdatesSummarizer:
tf = time.time() tf = time.time()
self.logger.info("End: checking dependency breakage. Time: {0:.2f} seconds".format(tf - ti)) 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]: 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 = set() if already_removed is None else already_removed
removed.update(names) removed.update(names)
@@ -718,11 +824,10 @@ class UpdatesSummarizer:
to_remove.add(pkg.name) to_remove.add(pkg.name)
if pkg.name not in context.cannot_upgrade: if pkg.name not in context.cannot_upgrade:
reason = "{} {}".format(self.i18n['arch.info.depends on'].capitalize(), p) reason = f"{self.i18n['arch.info.depends on'].capitalize()} {p}"
context.cannot_upgrade[pkg.name] = UpgradeRequirement(pkg=pkg, req = UpgradeRequirement(pkg=pkg, reason=reason,
reason=reason, sorting_priority=iteration_level - 1)
sorting_priority=iteration_level - 1) context.cannot_upgrade[pkg.name] = req
break break
if to_remove: if to_remove:
@@ -732,7 +837,9 @@ class UpdatesSummarizer:
return to_remove 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): 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: if pkgdeps:
for dep in pkgdeps: for dep in pkgdeps:
dep_split = RE_DEP_OPERATORS.split(dep) dep_split = RE_DEP_OPERATORS.split(dep)
@@ -750,11 +857,14 @@ class UpdatesSummarizer:
for v in versions: for v in versions:
try: try:
if match_required_version(current_version=v, operator=op, required_version=dep_split[1]): if match_required_version(current_version=v,
operator=op,
required_version=dep_split[1]):
version_match = True version_match = True
break break
except: except:
self.logger.error("Error when comparing versions {} (provided) and {} (required)".format(v, dep_split[1])) self.logger.error(f"Error when comparing versions {v} (provided) and "
f"{dep_split[1]} (required)")
traceback.print_exc() traceback.print_exc()
if not version_match: if not version_match:

View File

@@ -1,52 +1,52 @@
Australia=Australia Australia=Australia
Austria=Austria Austria=Austria
Bangladesh=Bangladesh Bangladesh=Bangladesh
Belarus=Belarus Belarus=Biellorussia
Belgium=Belgium Belgium=Belgio
Brazil=Brazil Brazil=Brasile
Bulgaria=Bulgaria Bulgaria=Bulgaria
Canada=Canada Canada=Canada
Chile=Chile Chile=Cile
China=China China=Cina
Costa Rica=Costa Rica Costa Rica=Costa Rica
Czech=Czech Czech=Cecoslovacchia
Denmark=Denmark Denmark=Danimarca
Ecuador=Ecuador Ecuador=Ecuador
France=France France=Francia
Georgia=Georgia Georgia=Georgia
Germany=Germany Germany=Germania
Greece=Greece Greece=Grecia
Hong Kong=Hong Kong Hong Kong=Hong Kong
Hungary=Hungary Hungary=Ungheria
Iceland=Iceland Iceland=Islanda
India=India India=India
Indonesia=Indonesia Indonesia=Indonesia
Iran=Iran Iran=Iran
Italy=Italy Italy=Italia
Japan=Japan Japan=Giappone
Kenya=Kenya Kenya=Kenya
Netherlands=Netherlands Netherlands=Olanda
New Zealand=New Zealand New Zealand=Nuova Zelanda
Norway=Norway Norway=Norvegia
Philippines=Philippines Philippines=Filippine
Poland=Poland Poland=Polonia
Portugal=Portugal Portugal=Portogallo
Russia=Russia Russia=Russia
Singapore=Singapore Singapore=Singapore
South Africa=South Africa South Africa=South Africa
South Korea=South Korea South Korea=Korea del Sud
Spain=Spain Spain=Spagna
Sweden=Sweden Sweden=Svezia
Switzerland=Switzerland Switzerland=Svizzera
Taiwan=Taiwan Taiwan=Taiwan
Thailand=Thailand Thailand=Tailandia
Turkey=Turkey Turkey=Turchia
Ukraine=Ukraine Ukraine=Ucraina
United Kingdom=United Kingdom United Kingdom=Regno Unito
United States=United States United States=Stati Uniti
action.backup.error.create=It was not possible to generate a new system copy action.backup.error.create=Non è stato possibile generare una nuova copia di sistema
action.backup.error.delete=It was not possible to delete all the old system copies action.backup.error.delete=Non è stato possibile eliminare tutte le vecchie copie di sistema
action.backup.error.proceed=Proceed anyway ? action.backup.error.proceed=Procedere comunque ?
action.backup.invalid_mode=Invalid backup mode action.backup.invalid_mode=Invalid backup mode
action.backup.msg=Do you want to generate a system copy before proceeding ? action.backup.msg=Do you want to generate a system copy before proceeding ?
action.backup.substatus.create=Generating a new system backup action.backup.substatus.create=Generating a new system backup
@@ -461,4 +461,4 @@ welcome=benvenuto
window_manage.search_bar.button_tooltip=Click here to search for applications window_manage.search_bar.button_tooltip=Click here to search for applications
window_manage.search_bar.placeholder=Cerca window_manage.search_bar.placeholder=Cerca
window_manage.search_bar.tooltip=Digitare e premere INVIO per cercare applicazioni window_manage.search_bar.tooltip=Digitare e premere INVIO per cercare applicazioni
yes=si yes=si

File diff suppressed because it is too large Load Diff