mirror of
https://github.com/spalencsar/bearhub.git
synced 2026-07-07 19:44:15 +02:00
[fix][arch] not retrieving all unnecessary packages when uninstalling a package
This commit is contained in:
@@ -27,8 +27,7 @@ from bauh.commons import user
|
||||
from bauh.commons.category import CategoriesDownloader
|
||||
from bauh.commons.config import save_config
|
||||
from bauh.commons.html import bold
|
||||
from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, new_root_subprocess, \
|
||||
SimpleProcess
|
||||
from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, run_cmd, SimpleProcess
|
||||
from bauh.gems.arch import BUILD_DIR, aur, pacman, makepkg, message, confirmation, disk, git, \
|
||||
gpg, URL_CATEGORIES_FILE, CATEGORIES_FILE_PATH, CUSTOM_MAKEPKG_FILE, SUGGESTIONS_FILE, \
|
||||
CONFIG_FILE, get_icon_path, database, mirrors, sorting, cpu_manager, ARCH_CACHE_PATH
|
||||
@@ -786,7 +785,9 @@ class ArchManager(SoftwareManager):
|
||||
return True
|
||||
|
||||
def _uninstall_pkgs(self, pkgs: Iterable[str], root_password: str, handler: ProcessHandler) -> bool:
|
||||
res = handler.handle(SystemProcess(new_root_subprocess(['pacman', '-R', *pkgs, '--noconfirm'], root_password), wrong_error_phrase='warning:'))
|
||||
all_uninstalled, _ = handler.handle_simple(SimpleProcess(cmd=['pacman', '-R', *pkgs, '--noconfirm'],
|
||||
root_password=root_password,
|
||||
error_phrases={'error: failed to prepare transaction'}))
|
||||
|
||||
installed = pacman.list_installed_names()
|
||||
|
||||
@@ -796,7 +797,7 @@ class ArchManager(SoftwareManager):
|
||||
if os.path.exists(cache_path):
|
||||
shutil.rmtree(cache_path)
|
||||
|
||||
return res
|
||||
return all_uninstalled
|
||||
|
||||
def _request_uninstall_confirmation(self, pkgs: Iterable[str], context: TransactionContext) -> bool:
|
||||
reqs = [InputOption(label=p, value=p, icon_path=get_icon_path(), read_only=True) for p in pkgs]
|
||||
@@ -809,7 +810,8 @@ class ArchManager(SoftwareManager):
|
||||
body=msg,
|
||||
components=[reqs_select],
|
||||
confirmation_label=self.i18n['proceed'].capitalize(),
|
||||
deny_label=self.i18n['cancel'].capitalize()):
|
||||
deny_label=self.i18n['cancel'].capitalize(),
|
||||
window_cancel=False):
|
||||
context.watcher.print("Aborted")
|
||||
return False
|
||||
|
||||
@@ -834,13 +836,14 @@ class ArchManager(SoftwareManager):
|
||||
|
||||
def _request_all_unncessary_uninstall_confirmation(self, pkgs: Iterable[str], context: TransactionContext):
|
||||
reqs = [InputOption(label=p, value=p, icon_path=get_icon_path(), read_only=True) for p in pkgs]
|
||||
reqs_select = MultipleSelectComponent(options=reqs, default_options=set(reqs), label="", max_per_line=3)
|
||||
reqs_select = MultipleSelectComponent(options=reqs, default_options=set(reqs), label="", max_per_line=1)
|
||||
|
||||
if not context.watcher.request_confirmation(title=self.i18n['confirmation'].capitalize(),
|
||||
body=self.i18n['arch.uninstall.unnecessary.all'].format(len(pkgs)),
|
||||
body=self.i18n['arch.uninstall.unnecessary.all'].format(bold(str(len(pkgs)))),
|
||||
components=[reqs_select],
|
||||
confirmation_label=self.i18n['proceed'].capitalize(),
|
||||
deny_label=self.i18n['cancel'].capitalize()):
|
||||
deny_label=self.i18n['cancel'].capitalize(),
|
||||
window_cancel=False):
|
||||
context.watcher.print("Aborted")
|
||||
return False
|
||||
|
||||
@@ -886,27 +889,27 @@ class ArchManager(SoftwareManager):
|
||||
|
||||
if all_deps:
|
||||
self.logger.info("Mapping dependencies required packages of uninstalled packages")
|
||||
optdeps_reqs = pacman.map_required_by(all_deps)
|
||||
alldeps_reqs = pacman.map_required_by(all_deps)
|
||||
|
||||
no_longer_needed = set()
|
||||
if optdeps_reqs:
|
||||
for dep, reqs in optdeps_reqs.items():
|
||||
if alldeps_reqs:
|
||||
for dep, reqs in alldeps_reqs.items():
|
||||
if not reqs:
|
||||
no_longer_needed.add(dep)
|
||||
|
||||
if no_longer_needed:
|
||||
self.logger.info("{} packages no longer needed found".format(len(no_longer_needed)))
|
||||
opt_to_uninstall = self._request_unncessary_uninstall_confirmation(no_longer_needed, context)
|
||||
unnecessary_to_uninstall = self._request_unncessary_uninstall_confirmation(no_longer_needed, context)
|
||||
|
||||
if opt_to_uninstall:
|
||||
opt_required = self.deps_analyser.map_all_required_by(opt_to_uninstall, set())
|
||||
unnecessary_to_uninstall = {*opt_to_uninstall, *opt_required}
|
||||
if unnecessary_to_uninstall:
|
||||
unnecessary_to_uninstall_deps = pacman.list_unnecessary_deps(unnecessary_to_uninstall, all_provided)
|
||||
all_unnecessary_to_uninstall = {*unnecessary_to_uninstall, *unnecessary_to_uninstall_deps}
|
||||
|
||||
if len(opt_to_uninstall) == len(unnecessary_to_uninstall) or self._request_all_unncessary_uninstall_confirmation(unnecessary_to_uninstall, context):
|
||||
unneded_uninstalled = self._uninstall_pkgs(unnecessary_to_uninstall, context.root_password, context.handler)
|
||||
if not unnecessary_to_uninstall_deps or self._request_all_unncessary_uninstall_confirmation(all_unnecessary_to_uninstall, context):
|
||||
unneded_uninstalled = self._uninstall_pkgs(all_unnecessary_to_uninstall, context.root_password, context.handler)
|
||||
|
||||
if unneded_uninstalled:
|
||||
to_uninstall.update(unnecessary_to_uninstall)
|
||||
to_uninstall.update(all_unnecessary_to_uninstall)
|
||||
else:
|
||||
self.logger.error("Could not uninstall some unnecessary packages")
|
||||
context.watcher.print("Could not uninstall some unnecessary packages")
|
||||
|
||||
@@ -901,3 +901,111 @@ def map_conflicts_with(names: Iterable[str], remote: bool) -> Dict[str, Set[str]
|
||||
conflicts.update(conflicts.update((d for d in l.strip().split(' ') if d)))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _list_unnecessary_deps(pkgs: Iterable[str], already_checked: Set[str], all_provided: Dict[str, Set[str]], recursive: bool = False) -> Set[str]:
|
||||
output = run_cmd('pacman -Qi {}'.format(' '.join(pkgs)))
|
||||
|
||||
if output:
|
||||
res = set()
|
||||
deps_field = False
|
||||
|
||||
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 == 'Depends On':
|
||||
deps_field = True
|
||||
val = line[field_sep_idx + 1:].strip()
|
||||
|
||||
if val != 'None':
|
||||
if ':' in val:
|
||||
dep_info = val.split(':')
|
||||
|
||||
real_deps = all_provided.get(dep_info[0].strip())
|
||||
|
||||
if real_deps:
|
||||
res.update(real_deps)
|
||||
else:
|
||||
for dep in val.split(' '):
|
||||
if dep:
|
||||
real_deps = all_provided.get(dep.strip())
|
||||
|
||||
if real_deps:
|
||||
res.update(real_deps)
|
||||
|
||||
elif deps_field:
|
||||
latest_field = False
|
||||
|
||||
elif deps_field:
|
||||
if ':' in l:
|
||||
dep_info = l.split(':')
|
||||
|
||||
real_deps = all_provided.get(dep_info[0].strip())
|
||||
|
||||
if real_deps:
|
||||
res.update(real_deps)
|
||||
else:
|
||||
for dep in l.split(' '):
|
||||
if dep:
|
||||
real_deps = all_provided.get(dep.strip())
|
||||
|
||||
if real_deps:
|
||||
res.update(real_deps)
|
||||
|
||||
if res:
|
||||
res = {dep for dep in res if dep not in already_checked}
|
||||
already_checked.update(res)
|
||||
|
||||
if recursive and res:
|
||||
subdeps = _list_unnecessary_deps(res, already_checked, all_provided)
|
||||
|
||||
if subdeps:
|
||||
res.update(subdeps)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def list_unnecessary_deps(pkgs: Iterable[str], all_provided: Dict[str, Set[str]] = None) -> Set[str]:
|
||||
all_checked = set(pkgs)
|
||||
all_deps = _list_unnecessary_deps(pkgs, all_checked, map_provided(remote=False) if not all_provided else all_provided, recursive=True)
|
||||
|
||||
unnecessary = set(pkgs)
|
||||
if all_deps:
|
||||
requirements_map = map_required_by(all_deps)
|
||||
|
||||
to_clean = set()
|
||||
for dep, required_by in requirements_map.items():
|
||||
if not required_by or not required_by.difference(unnecessary):
|
||||
unnecessary.add(dep)
|
||||
to_clean.add(dep)
|
||||
elif required_by.difference(all_checked): # checking if there are requirements outside the context
|
||||
to_clean.add(dep)
|
||||
|
||||
if to_clean:
|
||||
for dep in to_clean:
|
||||
del requirements_map[dep]
|
||||
|
||||
if requirements_map:
|
||||
while True:
|
||||
to_clean = set()
|
||||
for dep, required_by in requirements_map.items():
|
||||
if not required_by.difference(unnecessary):
|
||||
unnecessary.add(dep)
|
||||
to_clean.add(dep)
|
||||
|
||||
if to_clean:
|
||||
for dep in to_clean:
|
||||
del requirements_map[dep]
|
||||
else:
|
||||
break
|
||||
|
||||
if requirements_map: # if it reaches this points it is possible to exist mutual dependent packages
|
||||
for dep, required_by in requirements_map.items():
|
||||
if not required_by.difference({*requirements_map.keys(), *unnecessary}):
|
||||
unnecessary.add(dep)
|
||||
|
||||
return unnecessary.difference(pkgs)
|
||||
|
||||
Reference in New Issue
Block a user