mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 00:04:15 +02:00
[arch] fix: conflict resolution: removing hard dependencies that would be satisfied with the inclusion of the new package
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user