diff --git a/bauh/gems/arch/aur.py b/bauh/gems/arch/aur.py index 6c464ca7..defb1e71 100644 --- a/bauh/gems/arch/aur.py +++ b/bauh/gems/arch/aur.py @@ -4,6 +4,7 @@ from typing import Set, List from bauh.api.http import HttpClient import urllib.parse +from bauh.gems.arch import pacman from bauh.gems.arch.exceptions import PackageNotFoundException URL_INFO = 'https://aur.archlinux.org/rpc/?v=5&type=info&' @@ -57,7 +58,7 @@ class AURClient: for attr in ('makedepends', 'depends', 'checkdepends'): if info.get(attr): - deps.update(info[attr]) + deps.update([pacman.RE_DEP_OPERATORS.split(dep)[0] for dep in info[attr]]) return deps diff --git a/bauh/gems/arch/confirmation.py b/bauh/gems/arch/confirmation.py index bec6e10a..544c9ef3 100644 --- a/bauh/gems/arch/confirmation.py +++ b/bauh/gems/arch/confirmation.py @@ -34,11 +34,15 @@ def request_optional_deps(pkgname: str, pkg_mirrors: dict, watcher: ProcessWatch return {o.value for o in view_opts.values} -def request_install_missing_deps(pkgname: str, deps: List[Tuple[str,str]], watcher: ProcessWatcher, i18n: I18n) -> bool: - msg = '
{}
'.format(i18n['arch.missing_deps.body'].format(name=bold(pkgname), deps=bold(len(deps)))) +def request_install_missing_deps(pkgname: str, deps: List[Tuple[str, str]], watcher: ProcessWatcher, i18n: I18n) -> bool: + msg = '{}
'.format(i18n['arch.missing_deps.body'].format(name=bold(pkgname) if pkgname else '', deps=bold(str(len(deps))))) opts = [] - for dep in deps: + + sorted_deps = [*deps] + sorted_deps.sort(key=lambda e: e[0]) + + for dep in sorted_deps: op = InputOption('{} ( {}: {} )'.format(dep[0], i18n['repository'], dep[1].upper()), dep[0]) op.read_only = True op.icon_path = _get_mirror_icon(dep[1]) diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 9bfe23a9..87ce01a8 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -6,7 +6,7 @@ import subprocess import time from pathlib import Path from threading import Thread -from typing import List, Set, Type, Tuple +from typing import List, Set, Type, Tuple, Dict import requests @@ -431,14 +431,16 @@ class ArchManager(SoftwareManager): return True + def _should_check_subdeps(self): + return bool(int(os.getenv('BAUH_ARCH_CHECK_SUBDEPS', 1))) + def _build(self, pkgname: str, maintainer: str, root_password: str, handler: ProcessHandler, build_dir: str, project_dir: str, dependency: bool, skip_optdeps: bool = False, change_progress: bool = True) -> bool: self._pre_download_source(pkgname, project_dir, handler.watcher) self._update_progress(handler.watcher, 50, change_progress) - check_subdeps = bool(int(os.getenv('BAUH_ARCH_CHECK_SUBDEPS', 1))) - if not self._handle_deps_and_keys(pkgname, root_password, handler, project_dir, check_subdeps=check_subdeps): + if not self._handle_deps_and_keys(pkgname, root_password, handler, project_dir, check_subdeps=self._should_check_subdeps()): return False # building main package @@ -468,21 +470,12 @@ class ArchManager(SoftwareManager): return False - def _map_missing_deps(self, deps: List[str], watcher: ProcessWatcher, check_subdeps: bool = True) -> List[Tuple[str, str]]: - depnames = {RE_SPLIT_VERSION.split(dep)[0] for dep in deps} - dep_repos = self._map_repos(depnames) - - if len(depnames) != len(dep_repos): # checking if a dependency could not be found in any mirror - for dep in depnames: - if dep not in dep_repos: - message.show_dep_not_found(dep, self.i18n, watcher) - return - + def _map_known_missing_deps(self, known_deps: Dict[str, str], watcher: ProcessWatcher, check_subdeps: bool = True) -> List[Tuple[str, str]]: sorted_deps = [] # it will hold the proper order to install the missing dependencies repo_deps, aur_deps = set(), set() - for dep, repo in dep_repos.items(): + for dep, repo in known_deps.items(): if repo == 'aur': aur_deps.add(dep) else: @@ -491,7 +484,7 @@ class ArchManager(SoftwareManager): if check_subdeps: for deps in ((repo_deps, 'repo'), (aur_deps, 'aur')): if deps[0]: - missing_subdeps = self.deps_analyser.get_missing_dependencies_from(deps[0], deps[1]) + missing_subdeps = self.deps_analyser.get_missing_subdeps_of(deps[0], deps[1]) if missing_subdeps: for dep in missing_subdeps: @@ -502,7 +495,7 @@ class ArchManager(SoftwareManager): for dep in missing_subdeps: sorted_deps.append(dep) - for dep, repo in dep_repos.items(): + for dep, repo in known_deps.items(): if repo != 'aur': sorted_deps.append((dep, repo)) @@ -511,13 +504,26 @@ class ArchManager(SoftwareManager): return sorted_deps + def _map_unknown_missing_deps(self, deps: List[str], watcher: ProcessWatcher, check_subdeps: bool = True) -> List[Tuple[str, str]]: + depnames = {RE_SPLIT_VERSION.split(dep)[0] for dep in deps} + dep_repos = self._map_repos(depnames) + + if len(depnames) != len(dep_repos): # checking if a dependency could not be found in any mirror + for dep in depnames: + if dep not in dep_repos: + message.show_dep_not_found(dep, self.i18n, watcher) + return + + return self._map_known_missing_deps(dep_repos, watcher, check_subdeps) + def _handle_deps_and_keys(self, pkgname: str, root_password: str, handler: ProcessHandler, pkgdir: str, check_subdeps: bool = True) -> bool: handler.watcher.change_substatus(self.i18n['arch.checking.deps'].format(bold(pkgname))) check_res = makepkg.check(pkgdir, handler) if check_res: if check_res.get('missing_deps'): - sorted_deps = self._map_missing_deps(check_res['missing_deps'], handler.watcher, check_subdeps=check_subdeps) + handler.watcher.change_substatus(self.i18n['arch.checking.missing_deps'].format(bold(pkgname))) + sorted_deps = self._map_unknown_missing_deps(check_res['missing_deps'], handler.watcher, check_subdeps=check_subdeps) if sorted_deps is None: return False @@ -579,17 +585,37 @@ class ArchManager(SoftwareManager): if not deps_to_install: return True else: - aur_deps, repo_deps = [], [] + sorted_deps = [] - for dep in deps_to_install: - mirror = pkg_mirrors[dep] + if self._should_check_subdeps(): + missing_deps = self._map_known_missing_deps({d: pkg_mirrors[d] for d in deps_to_install}, handler.watcher) - if mirror == 'aur': - aur_deps.append((dep, mirror)) - else: - repo_deps.append((dep, mirror)) + if missing_deps is None: + return True # because the main package installation was successful - dep_not_installed = self._install_deps([*repo_deps, *aur_deps], root_password, handler, change_progress=True) + if missing_deps: + same_as_selected = len(deps_to_install) == len(missing_deps) and deps_to_install == {d[0] for d in missing_deps} + + if not same_as_selected and not confirmation.request_install_missing_deps(None, missing_deps, handler.watcher, self.i18n): + handler.watcher.print(self.i18n['action.cancelled']) + return True # because the main package installation was successful + + sorted_deps.extend(missing_deps) + else: + aur_deps, repo_deps = [], [] + + for dep in deps_to_install: + mirror = pkg_mirrors[dep] + + if mirror == 'aur': + aur_deps.append((dep, mirror)) + else: + repo_deps.append((dep, mirror)) + + sorted_deps.extend(repo_deps) + sorted_deps.extend(aur_deps) + + dep_not_installed = self._install_deps(sorted_deps, root_password, handler, change_progress=True) if dep_not_installed: message.show_optdep_not_installed(dep_not_installed, handler.watcher, self.i18n) diff --git a/bauh/gems/arch/depedencies.py b/bauh/gems/arch/depedencies.py index 5d3774bd..5d67a7c2 100644 --- a/bauh/gems/arch/depedencies.py +++ b/bauh/gems/arch/depedencies.py @@ -32,9 +32,16 @@ class DependenciesAnalyser: output.append((name, '')) - def get_missing_dependencies(self, names: Set[str], mirror: str = None) -> List[Tuple[str, str]]: + def get_missing_packages(self, names: Set[str], mirror: str = None, in_analysis: Set[str] = None) -> List[Tuple[str, str]]: + """ + :param names: + :param mirror: + :param in_analysis: global set storing all names in analysis to avoid repeated recursion + :return: + """ + global_in_analysis = in_analysis if in_analysis else set() - missing_names = pacman.check_missing(names) + missing_names = pacman.check_missing({n for n in names if n not in in_analysis}) if missing_names: missing_root = [] @@ -52,44 +59,50 @@ class DependenciesAnalyser: threads.clear() # checking if there is any unknown dependency: - for dep in missing_root: - if not dep[1]: + for rdep in missing_root: + if not rdep[1]: return missing_root + else: + global_in_analysis.add(rdep[0]) else: for missing in missing_names: missing_root.append((missing, mirror)) + global_in_analysis.add(missing) missing_sub = [] - for dep in missing_root: - subdeps = self.aur_client.get_all_dependencies(dep[0]) if dep[1] == 'aur' else pacman.read_dependencies(dep[0]) + for rdep in missing_root: + subdeps = self.aur_client.get_all_dependencies(rdep[0]) if rdep[1] == 'aur' else pacman.read_dependencies(rdep[0]) + subdeps_not_analysis = {sd for sd in subdeps if sd not in global_in_analysis} - if subdeps: - missing_subdeps = self.get_missing_dependencies(subdeps) + if subdeps_not_analysis: + missing_subdeps = self.get_missing_packages(subdeps_not_analysis, in_analysis=global_in_analysis) # checking if there is any unknown: if missing_subdeps: for subdep in missing_subdeps: if not subdep[0]: - missing_sub.extend(missing_subdeps) - break - - missing_sub.extend(missing_subdeps) + return [*missing_subdeps, *missing_root] + if subdep[0] not in missing_names: + missing_sub.append(subdep) return [*missing_sub, *missing_root] - def get_missing_dependencies_from(self, names: Set[str], mirror: str) -> List[Tuple[str, str]]: + def get_missing_subdeps_of(self, names: Set[str], mirror: str) -> List[Tuple[str, str]]: missing = [] - for dep in names: - subdeps = self.aur_client.get_all_dependencies(dep) if mirror == 'aur' else pacman.read_dependencies(dep) + already_added = {*names} + in_analyses = {*names} + + for name in names: + subdeps = self.aur_client.get_all_dependencies(name) if mirror == 'aur' else pacman.read_dependencies(name) if subdeps: - missing_subdeps = self.get_missing_dependencies(subdeps) + missing_subdeps = self.get_missing_packages(subdeps, in_analysis=in_analyses) if missing_subdeps: - missing.extend(missing_subdeps) - for subdep in missing_subdeps: # checking if there is any unknown: + if subdep[0] not in already_added: + missing.append(subdep) + if not subdep[0]: return missing - return missing diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py index 5c9381ae..93dbb75c 100644 --- a/bauh/gems/arch/pacman.py +++ b/bauh/gems/arch/pacman.py @@ -8,6 +8,7 @@ from bauh.gems.arch.exceptions import PackageNotFoundException RE_DEPS = re.compile(r'[\w\-_]+:[\s\w_\-\.]+\s+\[\w+\]') RE_OPTDEPS = re.compile(r'[\w\._\-]+\s*:') RE_DEP_NOTFOUND = re.compile(r'error:.+\'(.+)\'') +RE_DEP_OPERATORS = re.compile(r'[<>=]') def is_enabled() -> bool: @@ -15,17 +16,6 @@ def is_enabled() -> bool: return res and not res.strip().startswith('which ') -def get_configured_repository(pkg: str) -> Tuple[str, str]: - res = run_cmd('pacman -Ss {}'.format(pkg)) - - if res: - lines = res.split('\n') - - if lines: - data = lines[0].split('/') - return data[1].split(' ')[0], data[0] - - def get_repositories(pkgs: Set[str]) -> dict: pkgre = '|'.join(pkgs).replace('+', r'\+').replace('.', r'\.') @@ -39,11 +29,11 @@ def get_repositories(pkgs: Set[str]) -> dict: if p in match: mirrors[p] = match.split('/')[0] - not_found = {pkg for pkg in pkgs if pkg not in mirrors} + not_found = {pkg for pkg in pkgs if pkg and pkg not in mirrors} if not_found: # if there are some packages not found, try to find via the single method: for dep in not_found: - mirror_data = get_configured_repository(dep) + mirror_data = guess_repository(dep) if mirror_data: mirrors[mirror_data[0]] = mirror_data[1] @@ -250,7 +240,7 @@ def check_missing(names: Set[str]) -> Set[str]: err_line = o.decode() if err_line: - not_found = RE_DEP_NOTFOUND.findall(err_line) + not_found = [n for n in RE_DEP_NOTFOUND.findall(err_line) if n] if not_found: not_installed.update(not_found) @@ -284,14 +274,59 @@ def read_repository_from_info(name: str) -> str: def guess_repository(name: str) -> Tuple[str, str]: - res = run_cmd('pacman -Ss {}'.format(name.split('=')[0] if '=' in name else name)) + + if not name: + raise Exception("'name' cannot be None or blank") + + only_name = RE_DEP_OPERATORS.split(name)[0] + res = run_cmd('pacman -Ss {}'.format(only_name)) if res: lines = res.split('\n') if lines: - data = lines[0].split('/') - return data[1].split(' ')[0], data[0] + for line in lines: + if line and not line.startswith(' '): + data = line.split('/') + line_name, line_repo = data[1].split(' ')[0], data[0] + + provided = read_provides(line_name) + + if provided: + found = {p for p in provided if only_name == RE_DEP_OPERATORS.split(p)[0]} + + if found: + return line_name, line_repo + + +def read_provides(name: str) -> Set[str]: + dep_info = new_subprocess(['pacman', '-Si', name]) + + not_found = False + + for o in dep_info.stderr: + if o: + err_line = o.decode() + + if err_line: + if RE_DEP_NOTFOUND.findall(err_line): + not_found = True + + if not_found: + raise PackageNotFoundException(name) + + provides = None + + for out in new_subprocess(['grep', '-Po', 'Provides\s+:\s\K(.+)'], stdin=dep_info.stdout).stdout: + if out: + provided_names = [p.strip() for p in out.decode().strip().split(' ') if p] + + if provided_names[0].lower() == 'none': + provides = {name} + else: + provides = set(provided_names) + + return provides def read_dependencies(name: str) -> Set[str]: @@ -316,63 +351,6 @@ def read_dependencies(name: str) -> Set[str]: line = out.decode().strip() if line: - depends_on.update(line.split(' ')) + depends_on.update([d for d in line.split(' ') if d and d.lower() != 'none']) return depends_on - -""" -def check_uninstalled(names: List[str], stop_recursion_if_no_mirror: bool = True) -> Dict[str, str]: - missing = {} - installed = new_subprocess(['pacman', '-Qq', *names]) - - not_installed = [] - - for o in installed.stderr: - if o: - err_line = o.decode() - - if err_line: - not_found = RE_DEP_NOTFOUND.findall(err_line) - - if not_found: - not_installed.extend(not_found) - - if not_installed: - missing_mirrors = False - - for dep in not_installed: - pkgname = dep - not_installed_info = new_subprocess(['pacman', '-Si', dep]) - - mirror = None - for o in new_subprocess(['grep', '-Po', "Repository\s+:\s+\K.+"], stdin=not_installed_info.stdout).stdout: - if o: - line = o.decode().strip() - - if line: - mirror = line - - if not mirror: - mirror_data = get_configured_repository(dep) - - if mirror_data: - pkgname = mirror_data[0] - mirror = mirror_data[1] - - missing[pkgname] = mirror - - if mirror is None: - missing_mirrors = True - - if stop_recursion_if_no_mirror and missing_mirrors: - return missing - - subdeps_mirrors = dict() - - for pkg in missing: - subdeps_mirrors.update(check_uninstalled(pkg)) - - missing.update(subdeps_mirrors, stop_recursion_if_no_mirror=stop_recursion_if_no_mirror) - - return missing -""" diff --git a/bauh/gems/arch/resources/locale/ca b/bauh/gems/arch/resources/locale/ca index 1d419dc5..f71d1b72 100644 --- a/bauh/gems/arch/resources/locale/ca +++ b/bauh/gems/arch/resources/locale/ca @@ -45,6 +45,7 @@ aur.history.3_date=data arch.downloading.package=S’està baixant el paquet arch.uncompressing.package=S’està descomprimint el paquet arch.checking.deps=S’estan comprovant les dependències de {} +arch.checking.missing_deps=Verificació de les dependències que falten de {} arch.missing_deps_found=Dependències mancants per a {} arch.building.package=S’està compilant el paquet {} arch.checking.conflicts=S’està comprovant si hi ha conflictes amb {} diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de index deb95eee..adf008ca 100644 --- a/bauh/gems/arch/resources/locale/de +++ b/bauh/gems/arch/resources/locale/de @@ -13,6 +13,7 @@ aur.history.3_date=Datum arch.downloading.package=Paket herunterladen arch.uncompressing.package=Paket entpacken arch.checking.deps={} Abhängigkeiten überprüfen +arch.checking.missing_deps=Überprüfen der fehlenden Abhängigkeiten von {} arch.missing_deps_found=Fehlende Abhängigkeiten für {} arch.building.package=Paket {} erstellen arch.checking.conflicts=Konflikte mit {} überprüfen diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en index ad58825d..f2a295de 100644 --- a/bauh/gems/arch/resources/locale/en +++ b/bauh/gems/arch/resources/locale/en @@ -4,7 +4,7 @@ gem.aur.install.warning=AUR packages are maintained by an independent user commu arch.install.conflict.popup.title=Conflict detected arch.install.conflict.popup.body=The applications {} are in conflict. You must uninstall one to install the other. Continue ? arch.missing_deps.title=Missing dependencies -arch.missing_deps.body=The following {deps} dependencies must be installed before the {name} installation continues +arch.missing_deps.body=The following {deps} dependencies must be installed so the {name} installation can continue arch.downgrade.error=Error arch.downgrade.impossible=It is not possible to downgrade {} aur.history.1_version=version @@ -14,6 +14,7 @@ arch.downloading.package=Downloading the package arch.uncompressing.package=Uncompressing the package arch.checking.deps=Checking {} dependencies arch.missing_deps_found=Missing dependencies for {} +arch.checking.missing_deps=Verifying missing dependencies of {} arch.building.package=Building package {} arch.checking.conflicts=Checking any conflicts with {} arch.installing.package=Installing {} package diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es index e71632e3..711e5c48 100644 --- a/bauh/gems/arch/resources/locale/es +++ b/bauh/gems/arch/resources/locale/es @@ -36,7 +36,7 @@ aur.info.conflicts with=conflicta arch.install.conflict.popup.title=Conflicto detectado arch.install.conflict.popup.body=Los aplicativos {} estan en conflicto. Debe desinstalar uno para instalar el otro. ¿Continuar? arch.missing_deps.title=Dependencias faltantes -arch.missing_deps.body=Deben instalarse las siguientes {deps} dependencias antes de continuar la instalación de {name} +arch.missing_deps.body=Deben instalarse las siguientes {deps} dependencias para que la instalación de {name} pueda continuar arch.downgrade.error=Error arch.downgrade.impossible=No es posible revertir la versión de {} aur.history.1_version=versión @@ -45,6 +45,7 @@ aur.history.3_date=fecha arch.downloading.package=Descargando el paquete arch.uncompressing.package=Descomprimindo el paquete arch.checking.deps=Verificando las dependencias de {} +arch.checking.missing_deps=Verificando las dependencias faltantes de {} arch.missing_deps_found=Dependencias faltantes para {} arch.building.package=Construyendo el paquete {} arch.checking.conflicts=Verificando se hay conflictos con {} diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it index 45ae9bce..23fd2b3e 100644 --- a/bauh/gems/arch/resources/locale/it +++ b/bauh/gems/arch/resources/locale/it @@ -13,6 +13,7 @@ aur.history.3_date=data arch.downloading.package=Download del pacchetto arch.uncompressing.package=Non comprimere il pacchetto arch.checking.deps=Verifica di {} dipendenze +arch.checking.missing_deps=Verifica delle dipendenze mancanti di {} arch.missing_deps_found=Dipendenze mancanti per {} arch.building.package=Pacchetto costruito {} arch.checking.conflicts=Verifica di eventuali conflitti con {} diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt index 266da512..b38003fa 100644 --- a/bauh/gems/arch/resources/locale/pt +++ b/bauh/gems/arch/resources/locale/pt @@ -36,7 +36,7 @@ aur.info.conflicts with=conflita arch.install.conflict.popup.title=Conflito detectado arch.install.conflict.popup.body=Os aplicativos {} estão em conflito. Você precisa desinstalar um para instalar o outro. Continuar ? arch.missing_deps.title=Dependências ausentes -arch.missing_deps.body=As seguintes {deps} dependências devem ser instaladas antes de continuar com a instalação de {name} +arch.missing_deps.body=As seguintes {deps} dependências devem ser instaladas para que a instalação de {name} continue arch.downgrade.error=Erro arch.downgrade.impossible=Não é possível reverter a versão de {} aur.history.1_version=versão @@ -44,7 +44,8 @@ aur.history.2_release=lançamento aur.history.3_date=data arch.downloading.package=Baixando o pacote arch.uncompressing.package=Descompactando o pacote -arch.checking.deps=Checando as dependências de {} +arch.checking.deps=Verificando as dependências de {} +arch.checking.missing_deps=Verificando dependências ausentes de {} arch.missing_deps_found=Dependencias ausentes para {} arch.building.package=Construindo o pacote {} arch.checking.conflicts=Verificando se há conflitos com {} diff --git a/bauh/view/qt/confirmation.py b/bauh/view/qt/confirmation.py index b169f68d..11b2f501 100644 --- a/bauh/view/qt/confirmation.py +++ b/bauh/view/qt/confirmation.py @@ -49,10 +49,7 @@ class ConfirmationDialog(QMessageBox): height += inst.sizeHint().height() comps_container.layout().addWidget(inst) - height = height if height < screen_size.height() / 2 else height / 2 - - if height < 100: - height = 100 + height = height if height < int(screen_size.height() / 2.5) else int(screen_size.height() / 2.5) scroll.setFixedHeight(height) self.layout().addWidget(scroll, 1, 1)