[arch] fix -> not able to replace an installed package for a new one that replaces it during conflict resolutions

This commit is contained in:
Vinicius Moreira
2020-11-13 12:00:08 -03:00
parent f6b0679fd7
commit 9356c6519d
3 changed files with 104 additions and 33 deletions

View File

@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixes
- Arch:
- search: not able to find installed packages that were renamed on the repositories (e.g: xapps -> xapp)
- not able to replace an installed package for a new one that replaces it during conflict resolutions (e.g: xapp replaces xapps)
- AUR: not able to find some repository dependencies when their names are not an exact match (e.g: sc-controller [0.4.7-1] relies on "pylibacl". This dependency now is called "python-pylibacl")
## [0.9.8] 2020-10-02

View File

@@ -1095,15 +1095,20 @@ class ArchManager(SoftwareManager):
watcher.change_substatus('')
return True
def _uninstall_pkgs(self, pkgs: Iterable[str], root_password: str, handler: ProcessHandler) -> bool:
def _uninstall_pkgs(self, pkgs: Iterable[str], root_password: str, handler: ProcessHandler, ignore_dependencies: bool = False) -> bool:
status_handler = TransactionStatusHandler(watcher=handler.watcher,
i18n=self.i18n,
names={*pkgs},
logger=self.logger,
pkgs_to_remove=len(pkgs))
cmd = ['pacman', '-R', *pkgs, '--noconfirm']
if ignore_dependencies:
cmd.append('-dd')
status_handler.start()
all_uninstalled, _ = handler.handle_simple(SimpleProcess(cmd=['pacman', '-R', *pkgs, '--noconfirm'],
all_uninstalled, _ = handler.handle_simple(SimpleProcess(cmd=cmd,
root_password=root_password,
error_phrases={'error: failed to prepare transaction',
'error: failed to commit transaction'},
@@ -1167,24 +1172,25 @@ class ArchManager(SoftwareManager):
return True
def _uninstall(self, context: TransactionContext, names: Set[str], remove_unneeded: bool = False, disk_loader: DiskCacheLoader = None):
def _uninstall(self, context: TransactionContext, names: Set[str], remove_unneeded: bool = False, disk_loader: Optional[DiskCacheLoader] = None, skip_requirements: bool = False):
self._update_progress(context, 10)
net_available = internet.is_available() if disk_loader else True
hard_requirements = set()
for n in names:
try:
pkg_reqs = pacman.list_hard_requirements(n, self.logger)
if not skip_requirements:
for n in names:
try:
pkg_reqs = pacman.list_hard_requirements(n, self.logger)
if pkg_reqs:
hard_requirements.update(pkg_reqs)
except PackageInHoldException:
context.watcher.show_message(title=self.i18n['error'].capitalize(),
body=self.i18n['arch.uninstall.error.hard_dep_in_hold'].format(bold(n)),
type_=MessageType.ERROR)
return False
if pkg_reqs:
hard_requirements.update(pkg_reqs)
except PackageInHoldException:
context.watcher.show_message(title=self.i18n['error'].capitalize(),
body=self.i18n['arch.uninstall.error.hard_dep_in_hold'].format(bold(n)),
type_=MessageType.ERROR)
return False
self._update_progress(context, 25)
@@ -1199,7 +1205,7 @@ class ArchManager(SoftwareManager):
watcher=context.watcher):
return False
if remove_unneeded:
if not skip_requirements and remove_unneeded:
unnecessary_packages = pacman.list_post_uninstall_unneeded_packages(to_uninstall)
self.logger.info("Checking unnecessary optdeps")
@@ -1222,7 +1228,7 @@ class ArchManager(SoftwareManager):
else:
instances = None
uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler)
uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler, ignore_dependencies=skip_requirements)
if uninstalled:
if disk_loader: # loading package instances in case the uninstall succeeds
@@ -1521,7 +1527,7 @@ class ArchManager(SoftwareManager):
else:
return self._get_history_repo_pkg(pkg)
def _request_conflict_resolution(self, pkg: str, conflicting_pkg: str, context: TransactionContext) -> bool:
def _request_conflict_resolution(self, pkg: str, conflicting_pkg: str, context: TransactionContext, skip_requirements: bool = False) -> bool:
conflict_msg = '{} {} {}'.format(bold(pkg), self.i18n['and'], bold(conflicting_pkg))
if not context.watcher.request_confirmation(title=self.i18n['arch.install.conflict.popup.title'],
body=self.i18n['arch.install.conflict.popup.body'].format(conflict_msg)):
@@ -1534,7 +1540,8 @@ class ArchManager(SoftwareManager):
if context.removed is None:
context.removed = {}
res = self._uninstall(context=context, names={conflicting_pkg}, disk_loader=context.disk_loader, remove_unneeded=False)
res = self._uninstall(context=context, names={conflicting_pkg}, disk_loader=context.disk_loader,
remove_unneeded=False, skip_requirements=skip_requirements)
context.restabilish_progress()
return res
@@ -1572,16 +1579,17 @@ class ArchManager(SoftwareManager):
all_provided = context.get_provided_map()
for dep, conflicts in pacman.map_conflicts_with(repo_dep_names, remote=True).items():
if conflicts:
for c in conflicts:
for dep, data in pacman.map_conflicts_with(repo_dep_names, remote=True).items():
if data and data['c']:
for c in data['c']:
source_conflict = all_provided.get(c)
if source_conflict:
conflict_pkg = [*source_conflict][0]
if dep != conflict_pkg:
if not self._request_conflict_resolution(dep, conflict_pkg , context):
if not self._request_conflict_resolution(dep, conflict_pkg, context,
skip_requirements=data['r'] and conflict_pkg in data['r']):
return {dep}
downloaded = 0
@@ -1592,7 +1600,7 @@ class ArchManager(SoftwareManager):
except ArchDownloadException:
return False
status_handler = TransactionStatusHandler(watcher=context.watcher, i18n=self.i18n, names=repo_dep_names,
status_handler = TransactionStatusHandler(watcher=context.watcher, i18n=self.i18n, names={*repo_dep_names},
logger=self.logger, percentage=len(repo_deps) > 1, downloading=downloaded)
status_handler.start()
installed, _ = context.handler.handle_simple(pacman.install_as_process(pkgpaths=repo_dep_names,
@@ -2106,8 +2114,25 @@ class ArchManager(SoftwareManager):
if context.removed is None:
context.removed = {}
to_install_replacements = pacman.map_replaces(names_to_install)
skip_requirement_checking = False
if to_install_replacements: # checking if the packages to be installed replace the installed packages
all_replacements = set()
for replacements in to_install_replacements:
all_replacements.update(replacements)
if all_replacements:
for pkg in to_uninstall:
if pkg not in all_replacements:
break
skip_requirement_checking = True
context.disable_progress_if_changing()
if not self._uninstall(names=to_uninstall, context=context, remove_unneeded=False, disk_loader=context.disk_loader):
if not self._uninstall(names=to_uninstall, context=context, remove_unneeded=False,
disk_loader=context.disk_loader, skip_requirements=skip_requirement_checking):
context.watcher.show_message(title=self.i18n['error'],
body=self.i18n['arch.uninstalling.conflict.fail'].format(', '.join((bold(p) for p in to_uninstall))),
type_=MessageType.ERROR)

View File

@@ -960,12 +960,57 @@ def map_required_by(names: Iterable[str] = None, remote: bool = False) -> Dict[s
return {}
def map_conflicts_with(names: Iterable[str], remote: bool) -> Dict[str, Set[str]]:
def map_conflicts_with(names: Iterable[str], remote: bool) -> Dict[str, Dict[str, Set[str]]]:
output = run_cmd('pacman -{}i {}'.format('S' if remote else 'Q', ' '.join(names)))
if output:
res = {}
latest_name, conflicts = None, None
latest_name, conflicts, replaces, field = None, None, None, None
for l in output.split('\n'):
if l:
if l[0] != ' ':
line = l.strip()
field_sep_idx = line.index(':')
field = line[0:field_sep_idx].strip()
if field == 'Name':
field = 'n'
val = line[field_sep_idx + 1:].strip()
latest_name = val
elif field == 'Conflicts With':
field = 'c'
val = line[field_sep_idx + 1:].strip()
conflicts = set()
if val != 'None':
conflicts.update((d for d in val.split(' ') if d))
elif field == 'Replaces':
field = 'r'
val = line[field_sep_idx + 1:].strip()
replaces = set()
if val != 'None':
replaces.update((d for d in val.split(' ') if d))
elif latest_name and conflicts is not None and replaces is not None:
field = None
res[latest_name] = {'c': conflicts, 'r': replaces}
latest_name, conflicts, replaces = None, None, None
elif latest_name and field:
if field == 'c':
conflicts.update((d for d in l.strip().split(' ') if d))
elif field == 'r':
replaces.update((d for d in l.strip().split(' ') if d))
return res
def map_replaces(names: Iterable[str], remote: bool = False) -> Dict[str, Set[str]]:
output = run_cmd('pacman -{}i {}'.format('S' if remote else 'Q', ' '.join(names)))
if output:
res = {}
latest_name, replaces = None, None
for l in output.split('\n'):
if l:
@@ -977,18 +1022,18 @@ def map_conflicts_with(names: Iterable[str], remote: bool) -> Dict[str, Set[str]
if field == 'Name':
val = line[field_sep_idx + 1:].strip()
latest_name = val
elif field == 'Conflicts With':
elif field == 'Replaces':
val = line[field_sep_idx + 1:].strip()
conflicts = set()
replaces = set()
if val != 'None':
conflicts.update((d for d in val.split(' ') if d))
replaces.update((d for d in val.split(' ') if d))
elif latest_name and conflicts is not None:
res[latest_name] = conflicts
latest_name, conflicts = None, None
elif latest_name and replaces is not None:
res[latest_name] = replaces
latest_name, replaces = None, None, None
elif latest_name and conflicts is not None:
conflicts.update(conflicts.update((d for d in l.strip().split(' ') if d)))
elif latest_name and replaces is not None:
replaces.update((d for d in l.strip().split(' ') if d))
return res