From e01249f49c0d7e994da28441977455adc5015bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Moreira?= Date: Wed, 4 Dec 2019 12:47:10 -0300 Subject: [PATCH] [improvement][aur] retrieving and displaying all transitive required dependencies --- CHANGELOG.md | 6 +- README.md | 3 +- bauh/gems/arch/aur.py | 15 +++ bauh/gems/arch/confirmation.py | 14 +-- bauh/gems/arch/controller.py | 125 ++++++++++++++++-------- bauh/gems/arch/depedencies.py | 95 ++++++++++++++++++ bauh/gems/arch/exceptions.py | 5 + bauh/gems/arch/pacman.py | 148 ++++++++++++++++++++++++++++- bauh/gems/arch/resources/locale/ca | 8 +- bauh/gems/arch/resources/locale/de | 8 +- bauh/gems/arch/resources/locale/en | 8 +- bauh/gems/arch/resources/locale/es | 8 +- bauh/gems/arch/resources/locale/it | 8 +- bauh/gems/arch/resources/locale/pt | 8 +- bauh/gems/snap/controller.py | 5 +- bauh/gems/snap/resources/locale/ca | 3 +- bauh/gems/snap/resources/locale/de | 3 +- bauh/gems/snap/resources/locale/en | 3 +- bauh/gems/snap/resources/locale/es | 3 +- bauh/gems/snap/resources/locale/it | 3 +- bauh/gems/snap/resources/locale/pt | 3 +- bauh/view/qt/confirmation.py | 24 ++++- bauh/view/qt/window.py | 3 +- 23 files changed, 428 insertions(+), 81 deletions(-) create mode 100644 bauh/gems/arch/depedencies.py create mode 100644 bauh/gems/arch/exceptions.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 04de7d18..c4339b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [0.7.4] 2019-12 ### Improvements -- Some AUR labels have been changed to not confuse the user +- AUR: + - retrieving and displaying all transitive required dependencies ( it can be disabled via the new environment variable **BAUH_ARCH_CHECK_SUBDEPS=0** ) + - displaying **makedepends** and **checkdepends** in the info window + - Some AUR labels have been changed to not confuse the user +- Minor UI improvements ### Fixes - AUR: diff --git a/README.md b/README.md index 09dadc67..102ad4fe 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,8 @@ will be pre-downloaded faster ( it does **NOT** modify your **pacman** settings ( For more information about these optimizations, have a look at [Makepkg](https://wiki.archlinux.org/index.php/Makepkg) ) - Arch package memory-indexer running every 20 minutes. This memory index is used when AUR Api cannot handle the amount of results found for a given search. It can be disabled via the environment variable **BAUH_ARCH_AUR_INDEX_UPDATER=0**. - If some of your installed packages are not categorized, send an e-mail to **bauh4linux@gmail.com** informing their names and categories in the following format: ```name=category1[,category2,category3,...]``` - +- Transitive dependencies checking can be disabled through the environment variable **BAUH_ARCH_CHECK_SUBDEPS=0**. The dependency checking process will be +faster, but the application will ask for a new confirmation every time a not installed dependency is detected. ### General settings You can change some application settings via environment variables or arguments (type ```bauh --help``` to get more information). diff --git a/bauh/gems/arch/aur.py b/bauh/gems/arch/aur.py index 857c4be2..6c464ca7 100644 --- a/bauh/gems/arch/aur.py +++ b/bauh/gems/arch/aur.py @@ -4,6 +4,8 @@ from typing import Set, List from bauh.api.http import HttpClient import urllib.parse +from bauh.gems.arch.exceptions import PackageNotFoundException + URL_INFO = 'https://aur.archlinux.org/rpc/?v=5&type=info&' URL_SRC_INFO = 'https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h=' URL_SEARCH = 'https://aur.archlinux.org/rpc/?v=5&type=search&arg=' @@ -46,5 +48,18 @@ class AURClient: return info + def get_all_dependencies(self, name: str) -> Set[str]: + deps = set() + info = self.get_src_info(name) + + if not info: + raise PackageNotFoundException(name) + + for attr in ('makedepends', 'depends', 'checkdepends'): + if info.get(attr): + deps.update(info[attr]) + + return deps + def _map_names_as_queries(self, names) -> str: return '&'.join(['arg[{}]={}'.format(i, urllib.parse.quote(n)) for i, n in enumerate(names)]) diff --git a/bauh/gems/arch/confirmation.py b/bauh/gems/arch/confirmation.py index 15838e93..bec6e10a 100644 --- a/bauh/gems/arch/confirmation.py +++ b/bauh/gems/arch/confirmation.py @@ -1,4 +1,4 @@ -from typing import Set +from typing import Set, List, Tuple from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.view import MultipleSelectComponent, InputOption @@ -25,7 +25,7 @@ def request_optional_deps(pkgname: str, pkg_mirrors: dict, watcher: ProcessWatch default_options=None) install = watcher.request_confirmation(title=i18n['arch.install.optdeps.request.title'], - body='

{}.

{}.

'.format(i18n['arch.install.optdeps.request.body'].format(bold(pkgname)), i18n['arch.install.optdeps.request.help']), + body='

{}.

{}:

'.format(i18n['arch.install.optdeps.request.body'].format(bold(pkgname)), i18n['arch.install.optdeps.request.help']), components=[view_opts], confirmation_label=i18n['install'].capitalize(), deny_label=i18n['do_not.install'].capitalize()) @@ -34,14 +34,14 @@ 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, pkg_mirrors: dict, watcher: ProcessWatcher, i18n: I18n) -> bool: - msg = '

{}

'.format(i18n['arch.missing_deps.body'].format(bold(pkgname))) +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)))) opts = [] - for p, m in pkg_mirrors.items(): - op = InputOption('{} ( {}: {} )'.format(p, i18n['repository'], m.upper()), p) + for dep in deps: + op = InputOption('{} ( {}: {} )'.format(dep[0], i18n['repository'], dep[1].upper()), dep[0]) op.read_only = True - op.icon_path = _get_mirror_icon(m) + op.icon_path = _get_mirror_icon(dep[1]) opts.append(op) comp = MultipleSelectComponent(label='', options=opts, default_options=set(opts)) diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 0612fc57..9dc54b0d 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 +from typing import List, Set, Type, Tuple import requests @@ -22,6 +22,7 @@ from bauh.commons.system import SystemProcess, ProcessHandler, new_subprocess, r from bauh.gems.arch import BUILD_DIR, aur, pacman, makepkg, pkgbuild, message, confirmation, disk, git, suggestions, \ gpg, URL_CATEGORIES_FILE, CATEGORIES_CACHE_DIR, CATEGORIES_FILE_PATH from bauh.gems.arch.aur import AURClient +from bauh.gems.arch.depedencies import DependenciesAnalyser from bauh.gems.arch.mapper import ArchDataMapper from bauh.gems.arch.model import ArchPackage from bauh.gems.arch.worker import AURIndexUpdater, ArchDiskCacheUpdater, ArchCompilationOptimizer @@ -62,6 +63,7 @@ class ArchManager(SoftwareManager): self.categories_mapper = CategoriesDownloader('AUR', context.http_client, context.logger, self, self.context.disk_cache, URL_CATEGORIES_FILE, CATEGORIES_CACHE_DIR, CATEGORIES_FILE_PATH) self.categories = {} + self.deps_analyser = DependenciesAnalyser(self.aur_client) def _upgrade_search_result(self, apidata: dict, installed_pkgs: dict, downgrade_enabled: bool, res: SearchResult, disk_loader: DiskCacheLoader): app = self.mapper.map_api_data(apidata, installed_pkgs['not_signed'], self.categories) @@ -219,7 +221,7 @@ class ArchManager(SoftwareManager): break watcher.change_substatus(self.i18n['arch.downgrade.install_older']) - return self._make_pkg(pkg.name, pkg.maintainer, root_password, handler, app_build_dir, clone_path, dependency=False, skip_optdeps=True) + return self._build(pkg.name, pkg.maintainer, root_password, handler, app_build_dir, clone_path, dependency=False, skip_optdeps=True) else: watcher.show_message(title=self.i18n['arch.downgrade.error'], body=self.i18n['arch.downgrade.impossible'].format(pkg.name), @@ -308,11 +310,17 @@ class ArchManager(SoftwareManager): srcinfo = self.aur_client.get_src_info(pkg.name) if srcinfo: + if srcinfo.get('makedepends'): + info['12_makedepends'] = srcinfo['makedepends'] + if srcinfo.get('depends'): - info['11_dependson'] = srcinfo['depends'] + info['13_dependson'] = srcinfo['depends'] if srcinfo.get('optdepends'): - info['12_optdepends'] = srcinfo['optdepends'] + info['14_optdepends'] = srcinfo['optdepends'] + + if srcinfo.get('checkdepends'): + info['15_checkdepends'] = srcinfo['checkdepends'] if pkg.pkgbuild: info['00_pkg_build'] = pkg.pkgbuild @@ -355,34 +363,34 @@ class ArchManager(SoftwareManager): if os.path.exists(temp_dir): shutil.rmtree(temp_dir) - def _install_deps(self, pkg_mirrors: dict, root_password: str, handler: ProcessHandler, change_progress: bool = False) -> str: + def _install_deps(self, deps: List[Tuple[str, str]], root_password: str, handler: ProcessHandler, change_progress: bool = False) -> str: """ - :param pkg_mirrors: + :param pkgs_repos: :param root_password: :param handler: :return: not installed dependency """ - progress_increment = int(100 / len(pkg_mirrors)) + progress_increment = int(100 / len(deps)) progress = 0 self._update_progress(handler.watcher, 1, change_progress) - for pkgname, mirror in pkg_mirrors.items(): - handler.watcher.change_substatus(self.i18n['arch.install.dependency.install'].format(bold('{} ()'.format(pkgname, mirror)))) - if mirror == 'aur': - installed = self._install_from_aur(pkgname=pkgname, maintainer=None, root_password=root_password, handler=handler, dependency=True, change_progress=False) + for dep in deps: + handler.watcher.change_substatus(self.i18n['arch.install.dependency.install'].format(bold('{} ()'.format(dep[0], dep[1])))) + if dep[1] == 'aur': + installed = self._install_from_aur(pkgname=dep[0], maintainer=None, root_password=root_password, handler=handler, dependency=True, change_progress=False) else: - installed = self._install(pkgname=pkgname, maintainer=None, root_password=root_password, handler=handler, install_file=None, mirror=mirror, change_progress=False) + installed = self._install(pkgname=dep[0], maintainer=None, root_password=root_password, handler=handler, install_file=None, mirror=dep[1], change_progress=False) if not installed: - return pkgname + return dep[0] progress += progress_increment self._update_progress(handler.watcher, progress, change_progress) self._update_progress(handler.watcher, 100, change_progress) - def _map_mirrors(self, pkgnames: Set[str]) -> dict: - pkg_mirrors = pacman.get_mirrors(pkgnames) # getting mirrors set + def _map_repos(self, pkgnames: Set[str]) -> dict: + pkg_mirrors = pacman.get_repositories(pkgnames) # getting mirrors set if len(pkgnames) != len(pkg_mirrors): # checking if any dep not found in the distro mirrors are from AUR nomirrors = {p for p in pkgnames if p not in pkg_mirrors} @@ -423,12 +431,14 @@ class ArchManager(SoftwareManager): return True - def _make_pkg(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: + 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) - if not self._check_deps(pkgname, root_password, handler, project_dir): + + 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): return False # building main package @@ -458,35 +468,74 @@ class ArchManager(SoftwareManager): return False - def _check_deps(self, pkgname: str, root_password: str, handler: ProcessHandler, pkgdir: str) -> bool: + 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 + + 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(): + if repo == 'aur': + aur_deps.add(dep) + else: + repo_deps.add(dep) + + 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]) + + if missing_subdeps: + for dep in missing_subdeps: + if not dep[1]: + message.show_dep_not_found(dep[0], self.i18n, watcher) + return + + for dep in missing_subdeps: + sorted_deps.append(dep) + + for dep, repo in dep_repos.items(): + if repo != 'aur': + sorted_deps.append((dep, repo)) + + for dep in aur_deps: + sorted_deps.append((dep, 'aur')) + + return sorted_deps + + 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'): - depnames = {RE_SPLIT_VERSION.split(dep)[0] for dep in check_res['missing_deps']} - dep_mirrors = self._map_mirrors(depnames) + sorted_deps = self._map_missing_deps(check_res['missing_deps'], handler.watcher, check_subdeps=check_subdeps) - if len(depnames) != len(dep_mirrors): # checking if a dependency could not be found in any mirror - for dep in depnames: - if dep not in dep_mirrors: - message.show_dep_not_found(dep, self.i18n, handler.watcher) - return False + if sorted_deps is None: + return False handler.watcher.change_substatus(self.i18n['arch.missing_deps_found'].format(bold(pkgname))) - if not confirmation.request_install_missing_deps(pkgname, dep_mirrors, handler.watcher, self.i18n): + if not confirmation.request_install_missing_deps(pkgname, sorted_deps, handler.watcher, self.i18n): handler.watcher.print(self.i18n['action.cancelled']) return False - dep_not_installed = self._install_deps(dep_mirrors, root_password, handler, change_progress=False) + dep_not_installed = self._install_deps(sorted_deps, root_password, handler, change_progress=False) if dep_not_installed: message.show_dep_not_installed(handler.watcher, pkgname, dep_not_installed, self.i18n) return False # it is necessary to re-check because missing PGP keys are only notified when there are none missing - return self._check_deps(pkgname, root_password, handler, pkgdir) + return self._handle_deps_and_keys(pkgname, root_password, handler, pkgdir, check_subdeps=False) if check_res.get('gpg_key'): if handler.watcher.request_confirmation(title=self.i18n['arch.aur.install.unknown_key.title'], @@ -520,7 +569,7 @@ class ArchManager(SoftwareManager): if not to_install: return True - pkg_mirrors = self._map_mirrors(to_install) + pkg_mirrors = self._map_repos(to_install) if pkg_mirrors: final_optdeps = {dep: {'desc': odeps.get(dep), 'mirror': pkg_mirrors.get(dep)} for dep, mirror in pkg_mirrors.items()} @@ -642,15 +691,15 @@ class ArchManager(SoftwareManager): if uncompress: uncompress_dir = '{}/{}'.format(app_build_dir, pkgname) - return self._make_pkg(pkgname=pkgname, - maintainer=maintainer, - root_password=root_password, - handler=handler, - build_dir=app_build_dir, - project_dir=uncompress_dir, - dependency=dependency, - skip_optdeps=skip_optdeps, - change_progress=change_progress) + return self._build(pkgname=pkgname, + maintainer=maintainer, + root_password=root_password, + handler=handler, + build_dir=app_build_dir, + project_dir=uncompress_dir, + dependency=dependency, + skip_optdeps=skip_optdeps, + change_progress=change_progress) finally: if os.path.exists(app_build_dir): handler.handle(SystemProcess(new_subprocess(['rm', '-rf', app_build_dir]))) diff --git a/bauh/gems/arch/depedencies.py b/bauh/gems/arch/depedencies.py new file mode 100644 index 00000000..5d3774bd --- /dev/null +++ b/bauh/gems/arch/depedencies.py @@ -0,0 +1,95 @@ +from threading import Thread +from typing import Set, List, Tuple + +from bauh.gems.arch import pacman +from bauh.gems.arch.aur import AURClient + + +class DependenciesAnalyser: + + def __init__(self, aur_client: AURClient): + self.aur_client = aur_client + + def _fill_mirror(self, name: str, output: List[Tuple[str, str]]): + + mirror = pacman.read_repository_from_info(name) + + if mirror: + output.append((name, mirror)) + return + + guess = pacman.guess_repository(name) + + if guess: + output.append(guess) + return + + aur_info = self.aur_client.get_src_info(name) + + if aur_info: + output.append((name, 'aur')) + return + + output.append((name, '')) + + def get_missing_dependencies(self, names: Set[str], mirror: str = None) -> List[Tuple[str, str]]: + + missing_names = pacman.check_missing(names) + + if missing_names: + missing_root = [] + threads = [] + + if not mirror: + for name in missing_names: + t = Thread(target=self._fill_mirror, args=(name, missing_root)) + t.start() + threads.append(t) + + for t in threads: + t.join() + + threads.clear() + + # checking if there is any unknown dependency: + for dep in missing_root: + if not dep[1]: + return missing_root + else: + for missing in missing_names: + missing_root.append((missing, mirror)) + + 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]) + + if subdeps: + missing_subdeps = self.get_missing_dependencies(subdeps) + + # 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_sub, *missing_root] + + def get_missing_dependencies_from(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) + + if subdeps: + missing_subdeps = self.get_missing_dependencies(subdeps) + + if missing_subdeps: + missing.extend(missing_subdeps) + + for subdep in missing_subdeps: # checking if there is any unknown: + if not subdep[0]: + return missing + + return missing diff --git a/bauh/gems/arch/exceptions.py b/bauh/gems/arch/exceptions.py new file mode 100644 index 00000000..d868cb15 --- /dev/null +++ b/bauh/gems/arch/exceptions.py @@ -0,0 +1,5 @@ + +class PackageNotFoundException(Exception): + + def __init__(self, name: str): + self.name = name diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py index 06fe978f..5c9381ae 100644 --- a/bauh/gems/arch/pacman.py +++ b/bauh/gems/arch/pacman.py @@ -3,9 +3,11 @@ from threading import Thread from typing import List, Set, Tuple from bauh.commons.system import run_cmd, new_subprocess, new_root_subprocess, SystemProcess +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:.+\'(.+)\'') def is_enabled() -> bool: @@ -13,7 +15,7 @@ def is_enabled() -> bool: return res and not res.strip().startswith('which ') -def get_mirror(pkg: str) -> Tuple[str, str]: +def get_configured_repository(pkg: str) -> Tuple[str, str]: res = run_cmd('pacman -Ss {}'.format(pkg)) if res: @@ -24,7 +26,7 @@ def get_mirror(pkg: str) -> Tuple[str, str]: return data[1].split(' ')[0], data[0] -def get_mirrors(pkgs: Set[str]) -> dict: +def get_repositories(pkgs: Set[str]) -> dict: pkgre = '|'.join(pkgs).replace('+', r'\+').replace('.', r'\.') searchres = new_subprocess(['pacman', '-Ss', pkgre]).stdout @@ -41,7 +43,7 @@ def get_mirrors(pkgs: Set[str]) -> dict: 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_mirror(dep) + mirror_data = get_configured_repository(dep) if mirror_data: mirrors[mirror_data[0]] = mirror_data[1] @@ -49,7 +51,7 @@ def get_mirrors(pkgs: Set[str]) -> dict: return mirrors -def is_available_from_mirrors(pkg_name: str) -> bool: +def is_available_in_repositories(pkg_name: str) -> bool: return bool(run_cmd('pacman -Ss ' + pkg_name)) @@ -236,3 +238,141 @@ def list_ignored_packages(config_path: str = '/etc/pacman.conf') -> Set[str]: pacman_conf.terminate() grep.terminate() return ignored + + +def check_missing(names: Set[str]) -> Set[str]: + installed = new_subprocess(['pacman', '-Qq', *names]) + + not_installed = set() + + 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.update(not_found) + + return not_installed + + +def read_repository_from_info(name: str) -> str: + info = new_subprocess(['pacman', '-Si', name]) + + not_found = False + for o in info.stderr: + if o: + err_line = o.decode() + if RE_DEP_NOTFOUND.findall(err_line): + not_found = True + + if not_found: + return + + mirror = None + + for o in new_subprocess(['grep', '-Po', "Repository\s+:\s+\K.+"], stdin=info.stdout).stdout: + if o: + line = o.decode().strip() + + if line: + mirror = line + + return mirror + + +def guess_repository(name: str) -> Tuple[str, str]: + res = run_cmd('pacman -Ss {}'.format(name.split('=')[0] if '=' in name else name)) + + if res: + lines = res.split('\n') + + if lines: + data = lines[0].split('/') + return data[1].split(' ')[0], data[0] + + +def read_dependencies(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) + + depends_on = set() + for out in new_subprocess(['grep', '-Po', 'Depends\s+On\s+:\s\K(.+)'], stdin=dep_info.stdout).stdout: + if out: + line = out.decode().strip() + + if line: + depends_on.update(line.split(' ')) + + 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 4ccf68ca..1d419dc5 100644 --- a/bauh/gems/arch/resources/locale/ca +++ b/bauh/gems/arch/resources/locale/ca @@ -36,7 +36,7 @@ aur.info.conflicts with=té un conflicte amb arch.install.conflict.popup.title=S’ha detectat un conflicte arch.install.conflict.popup.body=Les aplicacions {} estan en conflicte. Heu de desinstal·lar una per a instal·lar l’altra. Voleu continuar? arch.missing_deps.title=Dependències mancants -arch.missing_deps.body=S’han d’instal·lar les dependències següents abans de continuar amb la instal·lació de {} +arch.missing_deps.body=S’han d’instal·lar les {deps} dependències següents abans de continuar amb la instal·lació de {name} arch.downgrade.error=Error arch.downgrade.impossible=No s’ha pogut revertir la versió de {} aur.history.1_version=versió @@ -69,8 +69,10 @@ aur.info.09_last_modified=darrera modificació aur.info.10_url=url de baixada aur.info.11_pkg_build_url=url pkgbuild aur.info.13_pkg_build=pkgbuild -aur.info.11_dependson=dependències -aur.info.12_optdepends=dependències opcionals +aur.info.12_makedepends=dependències de compilació +aur.info.13_dependson=dependències d’instal·lació +aur.info.14_optdepends=dependències opcionals +aur.info.15_checkdepends=dependències de verificació aur.info.14_installed_files=Fitxers instal·lats arch.install.dep_not_found.title=No s’ha trobat la dependència arch.install.dep_not_found.body=No s’ha trobat la dependència requerida {} a l’AUR ni als servidors rèplica per defecte. S’ha cancel·lat la instal·lació. diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de index 5315ead8..deb95eee 100644 --- a/bauh/gems/arch/resources/locale/de +++ b/bauh/gems/arch/resources/locale/de @@ -4,7 +4,7 @@ gem.aur.install.warning=AUR Pakete werden von einer unabhängigen Nutzergemeinsc arch.install.conflict.popup.title=Konflikt entdeckt arch.install.conflict.popup.body=Die Anwendungen {} können nicht gleichzeitig installiert sein. Du musst eine deinstallieren um die andere zu installieren. Fortfahren? arch.missing_deps.title=Fehlende Abhängigkeiten -arch.missing_deps.body=Die folgenden Abhängigkeiten müssten installiert sein, bevor mit der {} Installation fortgefahren werden kann +arch.missing_deps.body=Die folgenden {deps} Abhängigkeiten müssten installiert sein, bevor mit der {name} Installation fortgefahren werden kann arch.downgrade.error=Fehler arch.downgrade.impossible=Downgrade von {} ist nicht möglich aur.history.1_version=Version @@ -37,8 +37,10 @@ aur.info.09_last_modified=Zuletzt verändert aur.info.10_url=url zum Herunterladen aur.info.11_pkg_build_url=url pkgbuild aur.info.13_pkg_build=pkgbuild -aur.info.11_dependson=Abhängigkeiten -aur.info.12_optdepends=optionale Abhängigkeiten +aur.info.12_makedepends=Kompilierungsabhängigkeiten +aur.info.13_dependson=Installationsabhängigkeiten +aur.info.14_optdepends=optionale Abhängigkeiten +aur.info.15_checkdepends=Überprüfungsabhängigkeiten aur.info.14_installed_files=Installationsdateien arch.install.dep_not_found.title=Abhängigkeit nicht gefunden arch.install.dep_not_found.body=Nötige Abhängigkeit {} wurde weder im AUR noch in den standard Spiegelservern gefunden. Installation abgebrochen diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en index 9dfa9425..ad58825d 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 dependencies must be installed before the {} installation continues +arch.missing_deps.body=The following {deps} dependencies must be installed before the {name} installation continues arch.downgrade.error=Error arch.downgrade.impossible=It is not possible to downgrade {} aur.history.1_version=version @@ -37,8 +37,10 @@ aur.info.09_last_modified=last modified aur.info.10_url=url download aur.info.11_pkg_build_url=url pkgbuild aur.info.13_pkg_build=pkgbuild -aur.info.11_dependson=dependencies -aur.info.12_optdepends=optional dependencies +aur.info.12_makedepends=compilation dependencies +aur.info.13_dependson=installation dependencies +aur.info.14_optdepends=optional dependencies +aur.info.15_checkdepends=checking dependencies aur.info.14_installed_files=Installed files arch.install.dep_not_found.title=Dependency not found arch.install.dep_not_found.body=Required dependency {} was not found in AUR nor in default mirrors. Installation cancelled. diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es index 4a14dfc6..e71632e3 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 dependencias antes de continuar la instalación de {} +arch.missing_deps.body=Deben instalarse las siguientes {deps} dependencias antes de continuar la instalación de {name} arch.downgrade.error=Error arch.downgrade.impossible=No es posible revertir la versión de {} aur.history.1_version=versión @@ -69,8 +69,10 @@ aur.info.09_last_modified=última modificación aur.info.10_url=url download aur.info.11_pkg_build_url=url pkgbuild aur.info.13_pkg_build=pkgbuild -aur.info.11_dependson=dependencias -aur.info.12_optdepends=dependencias opcionales +aur.info.12_makedepends=dependencias para compilacion +aur.info.13_dependson=dependencias para instalación +aur.info.14_optdepends=dependencias opcionales +aur.info.15_checkdepends=dependencias para verificación aur.info.14_installed_files=Archivos instalados arch.install.dep_not_found.title=Dependencia no encontrada arch.install.dep_not_found.body=No se encontró la dependencia requerida {} en AUR ni en los espejos predeterminados. Instalación cancelada. diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it index 483d7e53..45ae9bce 100644 --- a/bauh/gems/arch/resources/locale/it +++ b/bauh/gems/arch/resources/locale/it @@ -4,7 +4,7 @@ gem.aur.install.warning=I pacchetti AUR sono gestiti da una comunità di utenti arch.install.conflict.popup.title=Conflitto rilevato arch.install.conflict.popup.body=Le applicazioni {} sono in conflitto. È necessario disinstallarne una per installare l'altra. Continua ? arch.missing_deps.title=Dipendenze mancanti -arch.missing_deps.body=Le seguenti dipendenze devono essere installate prima che l'installazione di {} continui +arch.missing_deps.body=Le seguenti {deps} dipendenze devono essere installate prima che l'installazione di {name} continui arch.downgrade.error=Errore arch.downgrade.impossible=Non è possibile effettuare il downgrade {} aur.history.1_version=versione @@ -37,8 +37,10 @@ aur.info.09_last_modified=ultima modifica aur.info.10_url=url di download aur.info.11_pkg_build_url=url pkgbuild aur.info.13_pkg_build=pkgbuild -aur.info.11_dependson=dipendenze -aur.info.12_optdepends=dipendenze opzionali +aur.info.12_makedepends=dipendenze di compilazione +aur.info.13_dependson=dipendenze di installazione +aur.info.14_optdepends=dipendenze opzionali +aur.info.15_checkdepends=dipendenze di verifica aur.info.14_installed_files=File installati arch.install.dep_not_found.title=Dipendenza non trovata arch.install.dep_not_found.body=La dipendenza richiesta {} non è stata trovata in AUR né nei mirror predefiniti. Installazione annullata. diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt index cc952077..266da512 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 dependências devem ser instaladas antes de continuar com a instalação de {} +arch.missing_deps.body=As seguintes {deps} dependências devem ser instaladas antes de continuar com a instalação de {name} arch.downgrade.error=Erro arch.downgrade.impossible=Não é possível reverter a versão de {} aur.history.1_version=versão @@ -69,8 +69,10 @@ aur.info.09_last_modified=última modificação aur.info.10_url=url download aur.info.11_pkg_build_url=url pkgbuild aur.info.13_pkg_build=pkgbuild -aur.info.11_dependson=dependências -aur.info.12_optdepends=dependências opcionais +aur.info.12_makedepends=dependências para compilação +aur.info.13_dependson=dependências para instalação +aur.info.14_optdepends=dependências opcionais +aur.info.15_checkdepends=dependências para verificação aur.info.14_installed_files=Arquivos instalados arch.install.dep_not_found.title=Dependência não encontrada arch.install.dep_not_found.body=A dependência {} não foi encontrado no AUR nem nos espelhos padrões. Instalação cancelada. diff --git a/bauh/gems/snap/controller.py b/bauh/gems/snap/controller.py index cc4a5ae3..37489422 100644 --- a/bauh/gems/snap/controller.py +++ b/bauh/gems/snap/controller.py @@ -161,8 +161,11 @@ class SnapManager(SoftwareManager): if channels: opts = [InputOption(label=c[0], value=c[1]) for c in channels] channel_select = SingleSelectComponent(type_=SelectViewType.RADIO, label='', options=opts, default_option=opts[0]) + body = '

{}.

'.format(self.i18n['snap.install.available_channels.message'].format(bold(self.i18n['stable']), bold(pkg.name))) + body += '

{}:

'.format(self.i18n['snap.install.available_channels.help']) + if watcher.request_confirmation(title=self.i18n['snap.install.available_channels.title'], - body=self.i18n['snap.install.available_channels.body'].format(bold(self.i18n['stable']), bold(pkg.name)) + ':', + body=body, components=[channel_select], confirmation_label=self.i18n['continue'], deny_label=self.i18n['cancel']): diff --git a/bauh/gems/snap/resources/locale/ca b/bauh/gems/snap/resources/locale/ca index 7d06be86..68bc77d6 100644 --- a/bauh/gems/snap/resources/locale/ca +++ b/bauh/gems/snap/resources/locale/ca @@ -15,4 +15,5 @@ snap.notifications.api.unavailable=Sembla que l’API de {} no està disponible snap.action.refresh.status=S’està actualitzant snap.action.refresh.label=Actualitza snap.install.available_channels.title=Canals disponibles -snap.install.available_channels.body=No hi ha un canal {} (stable) disponible per a {}. Però més avall n’hi ha els següents (seleccioneu-ne un si voleu continuar) +snap.install.available_channels.message=No hi ha un canal {} (stable) disponible per a {}. Però més avall n’hi ha els següents +snap.install.available_channels.help=Seleccioneu-ne un si voleu continuar diff --git a/bauh/gems/snap/resources/locale/de b/bauh/gems/snap/resources/locale/de index c71ba525..445b779e 100644 --- a/bauh/gems/snap/resources/locale/de +++ b/bauh/gems/snap/resources/locale/de @@ -5,7 +5,8 @@ snap.notifications.api.unavailable=Es scheint als ob die {} API aktuell nicht ve snap.action.refresh.status=Erneuern snap.action.refresh.label=Erneuern snap.install.available_channels.title=Verfügbare Channels -snap.install.available_channels.body=Es ist kein {} Channel verfügbar für {}. Es gibt jedoch folgende (wähle einen aus um fortzufahren) +snap.install.available_channels.message=Es ist kein {} Channel verfügbar für {}. Es gibt jedoch folgende +snap.install.available_channels.help=Wähle einen aus um fortzufahren snap.info.commands=Kommandos snap.info.contact=Kontakt snap.info.description=Beschreibung diff --git a/bauh/gems/snap/resources/locale/en b/bauh/gems/snap/resources/locale/en index 2155e858..a6cd99a6 100644 --- a/bauh/gems/snap/resources/locale/en +++ b/bauh/gems/snap/resources/locale/en @@ -5,7 +5,8 @@ snap.notifications.api.unavailable=It seems the {} API is unavailable at the mom snap.action.refresh.status=Refreshing snap.action.refresh.label=Refresh snap.install.available_channels.title=Available channels -snap.install.available_channels.body=There is no {} channel available for {}. But there are these below ( select one if you want to continue ) +snap.install.available_channels.message=There is no {} channel available for {}. But there are these below +snap.install.available_channels.help=Select one if you want to continue snap.info.commands=commands snap.info.contact=contact snap.info.description=description diff --git a/bauh/gems/snap/resources/locale/es b/bauh/gems/snap/resources/locale/es index 07e0501d..544e969b 100644 --- a/bauh/gems/snap/resources/locale/es +++ b/bauh/gems/snap/resources/locale/es @@ -15,4 +15,5 @@ snap.notifications.api.unavailable=Parece que la API de {} no está disponible e snap.action.refresh.status=Actualizando snap.action.refresh.label=Actualizar snap.install.available_channels.title=Canales disponibles -snap.install.available_channels.body=No hay un canal {} ( stable ) disponible para {}. Pero hay estos otros abajo ( seleccione uno si desea continuar ) \ No newline at end of file +snap.install.available_channels.message=No hay un canal {} ( stable ) disponible para {}. Pero hay estos otros abajo +snap.install.available_channels.help=Seleccione uno si desea continuar \ No newline at end of file diff --git a/bauh/gems/snap/resources/locale/it b/bauh/gems/snap/resources/locale/it index 57cf629d..86b2d0dd 100644 --- a/bauh/gems/snap/resources/locale/it +++ b/bauh/gems/snap/resources/locale/it @@ -5,4 +5,5 @@ snap.notifications.api.unavailable=Sembra che l'API {} non sia al momento dispon snap.action.refresh.status=Ripristinare snap.action.refresh.label=Ripristina snap.install.available_channels.title=Canali disponibili -snap.install.available_channels.body=Non esiste un {} canale disponibile per {}. Ma ci sono questi sotto (selezionane uno se vuoi continuare) +snap.install.available_channels.message=Non esiste un {} canale disponibile per {}. Ma ci sono questi sotto +snap.install.available_channels.help=Selezionane uno se vuoi continuare diff --git a/bauh/gems/snap/resources/locale/pt b/bauh/gems/snap/resources/locale/pt index 4960ddc5..bf2f66e2 100644 --- a/bauh/gems/snap/resources/locale/pt +++ b/bauh/gems/snap/resources/locale/pt @@ -15,4 +15,5 @@ snap.notifications.api.unavailable=Parece que a API de {} está indisponível no snap.action.refresh.status=Atualizando snap.action.refresh.label=Atualizar snap.install.available_channels.title=Canais disponíveis -snap.install.available_channels.body=Não há um canal {} ( stable ) disponível para {}. Porém existem estes outros abaixo ( selecione algum se quiser continuar ) \ No newline at end of file +snap.install.available_channels.message=Não há um canal {} ( stable ) disponível para {}. Porém existem estes outros abaixo +snap.install.available_channels.help=Selecione algum se quiser continuar \ No newline at end of file diff --git a/bauh/view/qt/confirmation.py b/bauh/view/qt/confirmation.py index ceeee6ab..b169f68d 100644 --- a/bauh/view/qt/confirmation.py +++ b/bauh/view/qt/confirmation.py @@ -1,6 +1,7 @@ from typing import List -from PyQt5.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QWidget +from PyQt5.QtCore import QSize +from PyQt5.QtWidgets import QMessageBox, QVBoxLayout, QLabel, QWidget, QScrollArea, QFrame from bauh.api.abstract.view import ViewComponent, SingleSelectComponent, MultipleSelectComponent from bauh.view.qt import css @@ -10,7 +11,8 @@ from bauh.view.util.translation import I18n class ConfirmationDialog(QMessageBox): - def __init__(self, title: str, body: str, i18n: I18n, components: List[ViewComponent] = None, confirmation_label: str = None, deny_label: str = None): + def __init__(self, title: str, body: str, i18n: I18n, screen_size: QSize, components: List[ViewComponent] = None, + confirmation_label: str = None, deny_label: str = None): super(ConfirmationDialog, self).__init__() self.setWindowTitle(title) self.setStyleSheet('QLabel { margin-right: 25px; }') @@ -26,8 +28,15 @@ class ConfirmationDialog(QMessageBox): self.layout().addWidget(QLabel(body), 0, 1) if components: - comps_container = QWidget(parent=self) + scroll = QScrollArea(self) + scroll.setFrameShape(QFrame.NoFrame) + scroll.setWidgetResizable(True) + + comps_container = QWidget() comps_container.setLayout(QVBoxLayout()) + scroll.setWidget(comps_container) + + height = 0 for idx, comp in enumerate(components): if isinstance(comp, SingleSelectComponent): @@ -37,9 +46,16 @@ class ConfirmationDialog(QMessageBox): else: raise Exception("Cannot render instances of " + comp.__class__.__name__) + height += inst.sizeHint().height() comps_container.layout().addWidget(inst) - self.layout().addWidget(comps_container, 1, 1) + height = height if height < screen_size.height() / 2 else height / 2 + + if height < 100: + height = 100 + + scroll.setFixedHeight(height) + self.layout().addWidget(scroll, 1, 1) self.exec_() diff --git a/bauh/view/qt/window.py b/bauh/view/qt/window.py index 33092eb9..c7a449d2 100755 --- a/bauh/view/qt/window.py +++ b/bauh/view/qt/window.py @@ -379,7 +379,8 @@ class ManageWindow(QWidget): i18n=self.i18n, components=msg['components'], confirmation_label=msg['confirmation_label'], - deny_label=msg['deny_label']) + deny_label=msg['deny_label'], + screen_size=self.screen_size) res = diag.is_confirmed() self.thread_animate_progress.animate() self.signal_user_res.emit(res)