[fix][arch] not retrieving all unnecessary packages when uninstalling a package

This commit is contained in:
Vinicius Moreira
2020-04-23 20:07:56 -03:00
parent 9ec031b770
commit 2da65dd803
2 changed files with 129 additions and 18 deletions

View File

@@ -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")

View File

@@ -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)