[arch] fix: conflict resolution: removing hard dependencies that would be satisfied with the inclusion of the new package

This commit is contained in:
Vinicius Moreira
2022-05-19 16:15:34 -03:00
parent 667bca3748
commit a191748124
4 changed files with 92 additions and 38 deletions

View File

@@ -23,9 +23,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- version: the limit for displaying both the installed and latest versions is 22% (otherwise just the latest version will be displayed) - version: the limit for displaying both the installed and latest versions is 22% (otherwise just the latest version will be displayed)
### Fixes ### Fixes
- Arch
- conflict resolution: removing hard dependencies that would be satisfied with the inclusion of the new package [#268](https://github.com/vinifmor/bauh/issues/268)
- e.g: `pipewire-pulse` conflicts with `pulseaudio`. `pulseaudio-alsa` (a dependency of pulseaudio) should not be removed, since `pipewire-pulse` provides `pulseaudio`
- Flatpak - Flatpak
- not all selected runtime partials to upgrade are actually requested to be upgraded - not all selected runtime partials to upgrade are actually requested to be upgraded
## [0.10.2] 2022-04-16 ## [0.10.2] 2022-04-16
### Improvements ### Improvements
- Arch - Arch

View File

@@ -294,7 +294,7 @@ class AURClient:
provided.add(pkgname) provided.add(pkgname)
if info: if info:
provided.add('{}={}'.format(pkgname, info['pkgver'])) provided.add(f"{pkgname}={info['pkgver']}")
if info.get('provides'): if info.get('provides'):
provided.update(info.get('provides')) provided.update(info.get('provides'))
@@ -303,7 +303,7 @@ class AURClient:
'b': info.get('pkgbase', pkgname)} 'b': info.get('pkgbase', pkgname)}
else: else:
if latest_version: if latest_version:
provided.add('{}={}'.format(pkgname, latest_version)) provided.add(f'{pkgname}={latest_version}')
return {'c': None, 's': None, 'p': provided, 'r': 'aur', 'v': latest_version, 'd': set(), 'b': pkgname} return {'c': None, 's': None, 'p': provided, 'r': 'aur', 'v': latest_version, 'd': set(), 'b': pkgname}

View File

@@ -8,7 +8,6 @@ import tarfile
import time import time
import traceback import traceback
from datetime import datetime from datetime import datetime
from math import floor
from pathlib import Path from pathlib import Path
from pwd import getpwnam from pwd import getpwnam
from threading import Thread from threading import Thread
@@ -21,8 +20,8 @@ from bauh.api.abstract.controller import SearchResult, SoftwareManager, Applicat
TransactionResult, SoftwareAction, SettingsView, SettingsController TransactionResult, SoftwareAction, SettingsView, SettingsController
from bauh.api.abstract.disk import DiskCacheLoader from bauh.api.abstract.disk import DiskCacheLoader
from bauh.api.abstract.handler import ProcessWatcher, TaskManager from bauh.api.abstract.handler import ProcessWatcher, TaskManager
from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageSuggestion, PackageStatus, \ from bauh.api.abstract.model import PackageUpdate, PackageHistory, SoftwarePackage, PackageStatus, \
SuggestionPriority, CustomSoftwareAction CustomSoftwareAction
from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \ from bauh.api.abstract.view import MessageType, FormComponent, InputOption, SingleSelectComponent, SelectViewType, \
ViewComponent, PanelComponent, MultipleSelectComponent, TextInputComponent, TextInputType, \ ViewComponent, PanelComponent, MultipleSelectComponent, TextInputComponent, TextInputType, \
FileChooserComponent, TextComponent FileChooserComponent, TextComponent
@@ -1194,7 +1193,10 @@ class ArchManager(SoftwareManager, SettingsController):
watcher.change_substatus('') watcher.change_substatus('')
return True return True
def _uninstall_pkgs(self, pkgs: Iterable[str], root_password: Optional[str], handler: ProcessHandler, ignore_dependencies: bool = False) -> bool: def _uninstall_pkgs(self, pkgs: Collection[str], root_password: Optional[str],
handler: ProcessHandler, ignore_dependencies: bool = False,
replacers: Optional[Set[str]] = None) -> bool:
status_handler = TransactionStatusHandler(watcher=handler.watcher, status_handler = TransactionStatusHandler(watcher=handler.watcher,
i18n=self.i18n, i18n=self.i18n,
names={*pkgs}, names={*pkgs},
@@ -1206,6 +1208,9 @@ class ArchManager(SoftwareManager, SettingsController):
if ignore_dependencies: if ignore_dependencies:
cmd.append('-dd') cmd.append('-dd')
if replacers:
cmd.extend(f'--assume-installed={r}' for r in replacers)
status_handler.start() status_handler.start()
all_uninstalled, _ = handler.handle_simple(SimpleProcess(cmd=cmd, all_uninstalled, _ = handler.handle_simple(SimpleProcess(cmd=cmd,
root_password=root_password, root_password=root_password,
@@ -1299,9 +1304,53 @@ class ArchManager(SoftwareManager, SettingsController):
return True return True
def _uninstall(self, context: TransactionContext, names: Set[str], remove_unneeded: bool = False, disk_loader: Optional[DiskCacheLoader] = None, skip_requirements: bool = False): def _fill_aur_providers(self, names: str, output: Set[str]):
for _, data in self.aur_client.gen_updates_data(names):
providers = data.get('p')
if providers:
output.update(providers)
def _map_actual_replacers(self, names: Set[str], context: TransactionContext) -> Optional[Set[str]]:
if not names:
return
repo_replacers, aur_replacers = set(), set()
for r in names:
repo = context.remote_repo_map.get(r)
if repo and repo != 'aur':
repo_replacers.add(r)
elif repo == 'aur' or (context.aur_idx and r in context.aur_idx):
aur_replacers.add(r)
actual_replacers = set()
thread_fill_aur = None
if aur_replacers:
thread_fill_aur = Thread(target=self._fill_aur_providers, args=(aur_replacers, actual_replacers))
thread_fill_aur.start()
if repo_replacers:
repo_replace_providers = pacman.map_provided(remote=True, pkgs=repo_replacers)
if repo_replace_providers:
actual_replacers.update(repo_replace_providers)
if thread_fill_aur:
thread_fill_aur.join()
return actual_replacers
def _uninstall(self, context: TransactionContext, names: Set[str], remove_unneeded: bool = False,
disk_loader: Optional[DiskCacheLoader] = None, skip_requirements: bool = False,
replacers: Optional[Set[str]] = None):
self._update_progress(context, 10) self._update_progress(context, 10)
actual_replacers = self._map_actual_replacers(replacers, context) if replacers else None
net_available = self.context.internet_checker.is_available() if disk_loader else True net_available = self.context.internet_checker.is_available() if disk_loader else True
hard_requirements = set() hard_requirements = set()
@@ -1309,13 +1358,14 @@ class ArchManager(SoftwareManager, SettingsController):
if not skip_requirements: if not skip_requirements:
for n in names: for n in names:
try: try:
pkg_reqs = pacman.list_hard_requirements(n, self.logger) pkg_reqs = pacman.list_hard_requirements(name=n, logger=self.logger, assume_installed=actual_replacers)
if pkg_reqs: if pkg_reqs:
hard_requirements.update(pkg_reqs) hard_requirements.update(pkg_reqs)
except PackageInHoldException: except PackageInHoldException:
context.watcher.show_message(title=self.i18n['error'].capitalize(), error_msg = self.i18n['arch.uninstall.error.hard_dep_in_hold'].format(bold(n))
body=self.i18n['arch.uninstall.error.hard_dep_in_hold'].format(bold(n)), context.watcher.show_message(title=self.i18n['error'].capitalize(), body=error_msg,
type_=MessageType.ERROR) type_=MessageType.ERROR)
return False return False
@@ -1355,7 +1405,8 @@ class ArchManager(SoftwareManager, SettingsController):
provided_by_uninstalled = pacman.map_provided(pkgs=to_uninstall) provided_by_uninstalled = pacman.map_provided(pkgs=to_uninstall)
uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler, ignore_dependencies=skip_requirements) uninstalled = self._uninstall_pkgs(to_uninstall, context.root_password, context.handler,
ignore_dependencies=skip_requirements, replacers=actual_replacers)
if uninstalled: if uninstalled:
self._remove_uninstalled_from_context(provided_by_uninstalled, context) self._remove_uninstalled_from_context(provided_by_uninstalled, context)
@@ -1398,7 +1449,8 @@ class ArchManager(SoftwareManager, SettingsController):
else: else:
unnecessary_instances = None unnecessary_instances = None
unneded_uninstalled = self._uninstall_pkgs(all_unnecessary_to_uninstall, context.root_password, context.handler) unneded_uninstalled = self._uninstall_pkgs(all_unnecessary_to_uninstall, context.root_password,
context.handler, replacers=actual_replacers)
if unneded_uninstalled: if unneded_uninstalled:
to_uninstall.update(all_unnecessary_to_uninstall) to_uninstall.update(all_unnecessary_to_uninstall)
@@ -1769,10 +1821,13 @@ class ArchManager(SoftwareManager, SettingsController):
else: else:
return self._get_history_repo_pkg(pkg) return self._get_history_repo_pkg(pkg)
def _request_conflict_resolution(self, pkg: str, conflicting_pkg: str, context: TransactionContext, skip_requirements: bool = False) -> bool: def _request_conflict_resolution(self, pkg: str, conflicting_pkg: str, context: TransactionContext,
conflict_msg = '{} {} {}'.format(bold(pkg), self.i18n['and'], bold(conflicting_pkg)) skip_requirements: bool = False) -> bool:
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)): conflict_msg = f"{bold(pkg)} {self.i18n['and']} {bold(conflicting_pkg)}"
msg_body = self.i18n['arch.install.conflict.popup.body'].format(conflict_msg)
if not context.watcher.request_confirmation(title=self.i18n['arch.install.conflict.popup.title'], body=msg_body):
context.watcher.print(self.i18n['action.cancelled']) context.watcher.print(self.i18n['action.cancelled'])
return False return False
else: else:
@@ -1783,8 +1838,7 @@ class ArchManager(SoftwareManager, SettingsController):
context.removed = {} context.removed = {}
res = self._uninstall(context=context, names={conflicting_pkg}, disk_loader=context.disk_loader, res = self._uninstall(context=context, names={conflicting_pkg}, disk_loader=context.disk_loader,
remove_unneeded=False, skip_requirements=skip_requirements) remove_unneeded=False, skip_requirements=skip_requirements, replacers={pkg})
context.restabilish_progress() context.restabilish_progress()
return res return res
@@ -2389,25 +2443,12 @@ class ArchManager(SoftwareManager, SettingsController):
if context.removed is None: if context.removed is None:
context.removed = {} 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() context.disable_progress_if_changing()
if not self._uninstall(names=to_uninstall, context=context, remove_unneeded=False, if not self._uninstall(names=to_uninstall, context=context, remove_unneeded=False,
disk_loader=context.disk_loader, skip_requirements=skip_requirement_checking): disk_loader=context.disk_loader,
replacers=names_to_install):
context.watcher.show_message(title=self.i18n['error'], context.watcher.show_message(title=self.i18n['error'],
body=self.i18n['arch.uninstalling.conflict.fail'].format(', '.join((bold(p) for p in to_uninstall))), body=self.i18n['arch.uninstalling.conflict.fail'].format(', '.join((bold(p) for p in to_uninstall))),
type_=MessageType.ERROR) type_=MessageType.ERROR)

View File

@@ -2,6 +2,7 @@ import logging
import os import os
import re import re
import shutil import shutil
from io import StringIO
from threading import Thread from threading import Thread
from typing import List, Set, Tuple, Dict, Iterable, Optional from typing import List, Set, Tuple, Dict, Iterable, Optional
@@ -514,7 +515,7 @@ def fill_provided_map(key: str, val: str, output: dict):
current_val.add(val) current_val.add(val)
def map_provided(remote: bool = False, pkgs: Iterable[str] = None) -> Dict[str, Set[str]]: def map_provided(remote: bool = False, pkgs: Iterable[str] = None) -> Optional[Dict[str, Set[str]]]:
output = run_cmd('pacman -{}i {}'.format('S' if remote else 'Q', ' '.join(pkgs) if pkgs else '')) output = run_cmd('pacman -{}i {}'.format('S' if remote else 'Q', ' '.join(pkgs) if pkgs else ''))
if output: if output:
@@ -982,8 +983,15 @@ def is_snapd_installed() -> bool:
return bool(run_cmd('pacman -Qq snapd', print_error=False)) return bool(run_cmd('pacman -Qq snapd', print_error=False))
def list_hard_requirements(name: str, logger: Optional[logging.Logger] = None) -> Optional[Set[str]]: def list_hard_requirements(name: str, logger: Optional[logging.Logger] = None,
code, output = system.execute('pacman -Rc {} --print-format=%n'.format(name), shell=True) assume_installed: Optional[Set[str]] = None) -> Optional[Set[str]]:
cmd = StringIO()
cmd.write(f'pacman -Rc {name} --print-format=%n ')
if assume_installed:
cmd.write(' '.join(f'--assume-installed={provider}' for provider in assume_installed))
code, output = system.execute(cmd.getvalue(), shell=True)
if code != 0: if code != 0:
if 'HoldPkg' in output: if 'HoldPkg' in output: