diff --git a/CHANGELOG.md b/CHANGELOG.md index 008f4cbf..9c8681f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,9 +37,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - only removing packages after downloading the required ones - summary: displaying the reason a given package must be installed

- +

- + + - checking specific version requirements and marking packages as "cannot upgrade" when these requirements are not met (e.g: package A depends on version 1.0 of B. If A and B were selected to upgrade, and B would be upgrade to 2.0, then B would be excluded from the transaction. This new checking behavior can be disabled through the property (**check_dependency_breakage**): +

+ +

+ - AUR - caching the PKGBUILD file used for the package installation/upgrade/downgrade (**~/.cache/bauh/arch/installed/$pkgname/PKGBUILD**) - new settings property **aur_build_dir** -> it allows to define a custom build dir. @@ -77,6 +82,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - upgrade summary - not displaying all packages that must be uninstalled - displaying "required size" for packages that must be uninstalled + - not displaying packages that cannot upgrade due to specific version requirements (e.g: package A requires version 1.0 of package B, however package B will be upgrade to version 2.0) +

+ +

+ - some conflict resolution scenarios when upgrading several packages - not handling conflicting files errors during the installation process - AUR diff --git a/README.md b/README.md index a7ac525d..cfa0039c 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ edit_aur_pkgbuild: false # if the AUR PKGBUILD file should be displayed for edi aur_build_dir: null # defines a custom build directory for AUR packages (a null value will point to /tmp/bauh/arch (non-root user) or /tmp/bauh_root/arch (root user)). Default: null. aur_remove_build_dir: true # it defines if a package's generated build directory should be removed after the operation is finished (installation, upgrading, ...). Options: true, false (default: true). aur_build_only_chosen : true # some AUR packages have a common file definition declaring several packages to be built. When this property is 'true' only the package the user select to install will be built (unless its name is different from those declared in the PKGBUILD base). With a 'null' value a popup asking if the user wants to build all of them will be displayed. 'false' will build and install all packages. Default: true. +check_dependency_breakage: true # If, during the verification of the update requirements, specific versions of dependencies must also be checked. Example: package A depends on version 1.0 of B. If A and B were selected to upgrade, and B would be upgrade to 2.0, then B would be excluded from the transaction. Default: true. ``` - Required dependencies: - **pacman** diff --git a/bauh/api/abstract/controller.py b/bauh/api/abstract/controller.py index 8ce3f477..cf496949 100644 --- a/bauh/api/abstract/controller.py +++ b/bauh/api/abstract/controller.py @@ -30,18 +30,24 @@ class SearchResult: class UpgradeRequirement: - def __init__(self, pkg: SoftwarePackage, reason: str = None, required_size: int = None, extra_size: int = None): + def __init__(self, pkg: SoftwarePackage, reason: str = None, required_size: int = None, extra_size: int = None, sorting_priority: int = 0): """ :param pkg: :param reason: :param required_size: size in BYTES required to upgrade the package :param extra_size: the extra size IN BYTES the upgrade will allocate in relation to the already allocated + :param sorting_priority: an int representing the sorting priority (higher numbers = higher priority) """ self.pkg = pkg self.reason = reason self.required_size = required_size self.extra_size = extra_size + self.sorting_priority = sorting_priority + + @staticmethod + def sort_by_priority(req: "UpgradeRequirement") -> Tuple[int, str]: + return -req.sorting_priority, req.pkg.name class UpgradeRequirements: diff --git a/bauh/gems/arch/config.py b/bauh/gems/arch/config.py index 80f412d8..402c0ce2 100644 --- a/bauh/gems/arch/config.py +++ b/bauh/gems/arch/config.py @@ -18,7 +18,8 @@ def read_config(update_file: bool = False) -> dict: 'edit_aur_pkgbuild': False, 'aur_build_dir': None, 'aur_remove_build_dir': True, - 'aur_build_only_chosen': True} + 'aur_build_only_chosen': True, + 'check_dependency_breakage': True} return read(CONFIG_FILE, template, update_file=update_file) diff --git a/bauh/gems/arch/controller.py b/bauh/gems/arch/controller.py index 83ec271f..b6511bc2 100644 --- a/bauh/gems/arch/controller.py +++ b/bauh/gems/arch/controller.py @@ -2554,6 +2554,11 @@ class ArchManager(SoftwareManager): tooltip_key='arch.config.automatch_providers.tip', value=bool(local_config['automatch_providers']), max_width=max_width), + self._gen_bool_selector(id_='check_dependency_breakage', + label_key='arch.config.check_dependency_breakage', + tooltip_key='arch.config.check_dependency_breakage.tip', + value=bool(local_config['check_dependency_breakage']), + max_width=max_width), self._gen_bool_selector(id_='mthread_download', label_key='arch.config.pacman_mthread_download', tooltip_key='arch.config.pacman_mthread_download.tip', @@ -2639,6 +2644,7 @@ class ArchManager(SoftwareManager): config['aur_remove_build_dir'] = form_install.get_component('aur_remove_build_dir').get_selected() config['aur_build_dir'] = form_install.get_component('aur_build_dir').file_path config['aur_build_only_chosen'] = form_install.get_component('aur_build_only_chosen').get_selected() + config['check_dependency_breakage'] = form_install.get_component('check_dependency_breakage').get_selected() if not config['aur_build_dir']: config['aur_build_dir'] = None diff --git a/bauh/gems/arch/model.py b/bauh/gems/arch/model.py index d72c256d..a78f4287 100644 --- a/bauh/gems/arch/model.py +++ b/bauh/gems/arch/model.py @@ -180,3 +180,8 @@ class ArchPackage(SoftwarePackage): else: return ACTIONS_AUR_ENABLE_PKGBUILD_EDITION + def __hash__(self): + if self.view_name is not None: + return hash((self.view_name, self.repository)) + else: + return hash((self.name, self.repository)) diff --git a/bauh/gems/arch/pacman.py b/bauh/gems/arch/pacman.py index e2e9d0b5..fab93ed9 100644 --- a/bauh/gems/arch/pacman.py +++ b/bauh/gems/arch/pacman.py @@ -914,8 +914,8 @@ def get_cache_dir() -> str: return '/var/cache/pacman/pkg' -def map_required_by(names: Iterable[str] = None) -> Dict[str, Set[str]]: - output = run_cmd('pacman -Qi {}'.format(' '.join(names) if names else '')) +def map_required_by(names: Iterable[str] = None, remote: bool = False) -> Dict[str, Set[str]]: + output = run_cmd('pacman -{} {}'.format('Sii' if remote else 'Qi', ' '.join(names) if names else ''), print_error=False) if output: res = {} diff --git a/bauh/gems/arch/resources/locale/ca b/bauh/gems/arch/resources/locale/ca index 0fd756c4..4350d047 100644 --- a/bauh/gems/arch/resources/locale/ca +++ b/bauh/gems/arch/resources/locale/ca @@ -42,6 +42,8 @@ arch.config.aur_remove_build_dir=Remove build directory (AUR) arch.config.aur_remove_build_dir.tip=If a package's generated build directory should be removed after the operations is finished. arch.config.clean_cache=Elimina les versions antigues arch.config.clean_cache.tip=Si cal eliminar les versions antigues d'un paquet emmagatzemat al disc durant la desinstal·lació +arch.config.check_dependency_breakage=Check dependency version breakage +arch.config.check_dependency_breakage.tip=If, during the verification of upgrade requirements, specific versions of dependencies must also be checked. Example: package A depends on version 1.0 of B. arch.config.edit_aur_pkgbuild=Edit PKGBUILD (AUR) arch.config.edit_aur_pkgbuild.tip=If the PKGBUILD file of an AUR package should be displayed for editing before its installation/upgrade/downgrade arch.config.mirrors_sort_limit=Mirrors sort limit @@ -196,6 +198,7 @@ arch.substatus.integrity=Checking packages integrity arch.substatus.keyring=Checking keyring arch.substatus.loading_files=Loading package files arch.substatus.pre_hooks=Running pre-transaction hooks +arch.sync.dep_breakage.reason={} requires {} arch.sync_databases.substatus=Synchronizing package databases arch.sync_databases.substatus.error=It was not possible to synchronize the package database arch.task.disk_cache=Indexing packages data diff --git a/bauh/gems/arch/resources/locale/de b/bauh/gems/arch/resources/locale/de index 342e8775..9e9ce49b 100644 --- a/bauh/gems/arch/resources/locale/de +++ b/bauh/gems/arch/resources/locale/de @@ -42,6 +42,8 @@ arch.config.aur_remove_build_dir=Remove build directory (AUR) arch.config.aur_remove_build_dir.tip=If a package's generated build directory should be removed after the operations is finished. arch.config.clean_cache=Remove old versions arch.config.clean_cache.tip=Whether old versions of a package stored on disk should be removed during uninstall +arch.config.check_dependency_breakage=Check dependency version breakage +arch.config.check_dependency_breakage.tip=If, during the verification of upgrade requirements, specific versions of dependencies must also be checked. Example: package A depends on version 1.0 of B. arch.config.edit_aur_pkgbuild=Edit PKGBUILD (AUR) arch.config.edit_aur_pkgbuild.tip=If the PKGBUILD file of an AUR package should be displayed for editing before its installation/upgrade/downgrade arch.config.mirrors_sort_limit=Mirrors sort limit @@ -196,6 +198,7 @@ arch.substatus.integrity=Checking packages integrity arch.substatus.keyring=Checking keyring arch.substatus.loading_files=Loading package files arch.substatus.pre_hooks=Running pre-transaction hooks +arch.sync.dep_breakage.reason={} requires {} arch.sync_databases.substatus=Synchronizing package databases arch.sync_databases.substatus.error=It was not possible to synchronize the package database arch.task.disk_cache=Indexing packages data diff --git a/bauh/gems/arch/resources/locale/en b/bauh/gems/arch/resources/locale/en index 74add205..13bb8423 100644 --- a/bauh/gems/arch/resources/locale/en +++ b/bauh/gems/arch/resources/locale/en @@ -43,6 +43,8 @@ arch.config.aur_remove_build_dir=Remove build directory (AUR) arch.config.aur_remove_build_dir.tip=If a package's generated build directory should be removed after the operations is finished. arch.config.clean_cache=Remove old versions arch.config.clean_cache.tip=Whether old versions of a package stored on disk should be removed during uninstall +arch.config.check_dependency_breakage=Check dependency version breakage +arch.config.check_dependency_breakage.tip=If, during the verification of upgrade requirements, specific versions of dependencies must also be checked. Example: package A depends on version 1.0 of B. arch.config.edit_aur_pkgbuild=Edit PKGBUILD (AUR) arch.config.edit_aur_pkgbuild.tip=If the PKGBUILD file of an AUR package should be displayed for editing before its installation/upgrade/downgrade arch.config.mirrors_sort_limit=Mirrors sort limit @@ -197,6 +199,7 @@ arch.substatus.integrity=Checking packages integrity arch.substatus.keyring=Checking keyring arch.substatus.loading_files=Loading package files arch.substatus.pre_hooks=Running pre-transaction hooks +arch.sync.dep_breakage.reason={} requires {} arch.sync_databases.substatus=Synchronizing package databases arch.sync_databases.substatus.error=It was not possible to synchronize the package database arch.task.disk_cache=Indexing packages data diff --git a/bauh/gems/arch/resources/locale/es b/bauh/gems/arch/resources/locale/es index 87feee95..bcf416a5 100644 --- a/bauh/gems/arch/resources/locale/es +++ b/bauh/gems/arch/resources/locale/es @@ -42,6 +42,8 @@ arch.config.aur_remove_build_dir=Eliminar directorio de compilación (AUR) arch.config.aur_remove_build_dir.tip=Si el directorio de compilación generado para un paquete debe ser eliminado una vez finalizada la operación. arch.config.clean_cache=Eliminar versiones antiguas arch.config.clean_cache.tip=Si las versiones antiguas de un paquete almacenado en el disco deben ser eliminadas durante la desinstalación +arch.config.check_dependency_breakage=Verificar rotura de versión de dependencia +arch.config.check_dependency_breakage.tip=Si, durante la verificación de los requisitos de actualización, también se deben verificar versiones específicas de las dependencias. Ejemplo: el paquete A depende de la versión 1.0 de B. arch.config.edit_aur_pkgbuild=Editar PKGBUILD (AUR) arch.config.edit_aur_pkgbuild.tip=Si el archivo PKGBUILD de un paquete AUR debe ser exhibido para edición antes de su instalación/actualización/degradación arch.config.mirrors_sort_limit=Límite de ordenación de espejos @@ -196,6 +198,7 @@ arch.substatus.integrity=Verificando la integridad de los paquetes arch.substatus.keyring=Verificando keyring arch.substatus.loading_files=Cargando archivos de los paquetes arch.substatus.pre_hooks=Ejecutando ganchos pre-transacción +arch.sync.dep_breakage.reason={} necesita de {} arch.sync_databases.substatus=Sincronizando bases de paquetes arch.sync_databases.substatus.error=No fue posible sincronizar la base de paquetes arch.task.disk_cache=Indexando datos de paquetes diff --git a/bauh/gems/arch/resources/locale/it b/bauh/gems/arch/resources/locale/it index bfe1eff2..ddad0842 100644 --- a/bauh/gems/arch/resources/locale/it +++ b/bauh/gems/arch/resources/locale/it @@ -42,6 +42,8 @@ arch.config.aur_remove_build_dir=Remove build directory (AUR) arch.config.aur_remove_build_dir.tip=If a package's generated build directory should be removed after the operations is finished. arch.config.clean_cache=Rimuovi le vecchie versioni arch.config.clean_cache.tip=Se le vecchie versioni di un pacchetto memorizzate sul disco devono essere rimosse durante la disinstallazione +arch.config.check_dependency_breakage=Check dependency version breakage +arch.config.check_dependency_breakage.tip=If, during the verification of upgrade requirements, specific versions of dependencies must also be checked. Example: package A depends on version 1.0 of B. arch.config.edit_aur_pkgbuild=Edit PKGBUILD (AUR) arch.config.edit_aur_pkgbuild.tip=If the PKGBUILD file of an AUR package should be displayed for editing before its installation/upgrade/downgrade arch.config.mirrors_sort_limit=Mirrors sort limit @@ -196,6 +198,7 @@ arch.substatus.integrity=Checking packages integrity arch.substatus.keyring=Checking keyring arch.substatus.loading_files=Loading package files arch.substatus.pre_hooks=Running pre-transaction hooks +arch.sync.dep_breakage.reason={} requires {} arch.sync_databases.substatus=Synchronizing package databases arch.sync_databases.substatus.error=It was not possible to synchronize the package database arch.task.disk_cache=Indexing packages data diff --git a/bauh/gems/arch/resources/locale/pt b/bauh/gems/arch/resources/locale/pt index fe526426..21d92811 100644 --- a/bauh/gems/arch/resources/locale/pt +++ b/bauh/gems/arch/resources/locale/pt @@ -43,6 +43,8 @@ arch.config.aur_remove_build_dir=Remover diretório de construção (AUR) arch.config.aur_remove_build_dir.tip=Se o diretório gerado para a construção de um pacote do AUR deve ser removido após a operação ser finalizada. arch.config.clean_cache=Remover versões antigas arch.config.clean_cache.tip=Se versões antigas de um pacote armazenadas em disco devem ser removidas durante a desinstalação +arch.config.check_dependency_breakage=Verificar quebra de dependência de versão +arch.config.check_dependency_breakage.tip=Se durante a verificação dos requisitos de atualização deve-se verificar também versões específicas de dependências. Exemplo: pacote A depende da versão 1.0 de B. arch.config.edit_aur_pkgbuild=Editar PKGBUILD (AUR) arch.config.edit_aur_pkgbuild.tip=Se o arquivo PKGBUILD de um pacote do AUR deve ser exibido para edição antes da instalação/atualização/reversão arch.config.mirrors_sort_limit=Limite de ordenação de espelhos @@ -196,6 +198,7 @@ arch.substatus.integrity=Verificando a integridade dos pacotes arch.substatus.keyring=Verificando o keyring arch.substatus.loading_files=Carregando os arquivos dos pacotes arch.substatus.pre_hooks=Executando ganchos pré-transação +arch.sync.dep_breakage.reason={} precisa de {} arch.sync_databases.substatus=Sincronizando bases de pacotes arch.sync_databases.substatus.error=Não foi possível sincronizar as bases de pacotes arch.task.disk_cache=Indexando dados de pacotes diff --git a/bauh/gems/arch/resources/locale/ru b/bauh/gems/arch/resources/locale/ru index 02329c12..51fe3fa1 100644 --- a/bauh/gems/arch/resources/locale/ru +++ b/bauh/gems/arch/resources/locale/ru @@ -42,6 +42,8 @@ arch.config.aur_remove_build_dir=Remove build directory (AUR) arch.config.aur_remove_build_dir.tip=If a package's generated build directory should be removed after the operations is finished. arch.config.clean_cache=Remove old versions arch.config.clean_cache.tip=Whether old versions of a package stored on disk should be removed during uninstall +arch.config.check_dependency_breakage=Check dependency version breakage +arch.config.check_dependency_breakage.tip=If, during the verification of upgrade requirements, specific versions of dependencies must also be checked. Example: package A depends on version 1.0 of B. arch.config.edit_aur_pkgbuild=Edit PKGBUILD (AUR) arch.config.edit_aur_pkgbuild.tip=If the PKGBUILD file of an AUR package should be displayed for editing before its installation/upgrade/downgrade arch.config.mirrors_sort_limit=Ограничение сортировки зеркал @@ -196,6 +198,7 @@ arch.substatus.integrity=Checking packages integrity arch.substatus.keyring=Checking keyring arch.substatus.loading_files=Loading package files arch.substatus.pre_hooks=Running pre-transaction hooks +arch.sync.dep_breakage.reason={} requires {} arch.sync_databases.substatus=Синхронизация баз данных пакетов arch.sync_databases.substatus.error=Синхронизировать базу данных пакета не удалось arch.task.disk_cache=Indexing packages data diff --git a/bauh/gems/arch/resources/locale/tr b/bauh/gems/arch/resources/locale/tr index 74efb19d..0ae6eb28 100644 --- a/bauh/gems/arch/resources/locale/tr +++ b/bauh/gems/arch/resources/locale/tr @@ -42,6 +42,8 @@ arch.config.aur_remove_build_dir=Remove build directory (AUR) arch.config.aur_remove_build_dir.tip=If a package's generated build directory should be removed after the operations is finished. arch.config.clean_cache=Önbelleği temizle arch.config.clean_cache.tip=Disk üzerinde kurulu bir paketin eski sürümlerinin kaldırma sırasında kaldırılıp kaldırılmayacağı +arch.config.check_dependency_breakage=Check dependency version breakage +arch.config.check_dependency_breakage.tip=If, during the verification of upgrade requirements, specific versions of dependencies must also be checked. Example: package A depends on version 1.0 of B. arch.config.edit_aur_pkgbuild=Edit PKGBUILD (AUR) arch.config.edit_aur_pkgbuild.tip=If the PKGBUILD file of an AUR package should be displayed for editing before its installation/upgrade/downgrade arch.config.mirrors_sort_limit=Yansı sıralama sınırı @@ -196,6 +198,7 @@ arch.substatus.integrity=Checking packages integrity arch.substatus.keyring=Checking keyring arch.substatus.loading_files=Loading package files arch.substatus.pre_hooks=Running pre-transaction hooks +arch.sync.dep_breakage.reason={} requires {} arch.sync_databases.substatus=Paket veritabanı eşitleniyor arch.sync_databases.substatus.error=Paket veritabanı eşitlenemedi arch.task.disk_cache=Indexing packages data diff --git a/bauh/gems/arch/updates.py b/bauh/gems/arch/updates.py index b00bc55b..be96550a 100644 --- a/bauh/gems/arch/updates.py +++ b/bauh/gems/arch/updates.py @@ -1,7 +1,8 @@ import logging import time +from distutils.version import LooseVersion from threading import Thread -from typing import Dict, Set, List, Tuple, Iterable +from typing import Dict, Set, List, Tuple, Iterable, Optional from bauh.api.abstract.controller import UpgradeRequirements, UpgradeRequirement from bauh.api.abstract.handler import ProcessWatcher @@ -261,38 +262,53 @@ class UpdatesSummarizer: context.pkgs_data.update(all_to_install_data) self._fill_conflicts(context, context.to_remove.keys()) + if context.to_install: + self.__fill_provided_map(context=context, pkgs=context.to_install, fill_installed=False) + tf = time.time() self.logger.info("It took {0:.2f} seconds to retrieve required upgrade packages".format(tf - ti)) return True - def __fill_provided_map(self, context: UpdateRequirementsContext): - ti = time.time() - self.logger.info("Filling provided names") - context.installed_names = pacman.list_installed_names() - installed_to_ignore = set() + def __fill_provided_map(self, context: UpdateRequirementsContext, pkgs: Dict[str, ArchPackage], fill_installed: bool = True): + if pkgs: + ti = time.time() + self.logger.info("Filling provided names") - for pkgname in context.to_update: - pacman.fill_provided_map(pkgname, pkgname, context.provided_map) - installed_to_ignore.add(pkgname) + if not context.installed_names: + context.installed_names = pacman.list_installed_names() - pdata = context.pkgs_data.get(pkgname) - if pdata and pdata['p']: - pacman.fill_provided_map('{}={}'.format(pkgname, pdata['v']), pkgname, context.provided_map) - for p in pdata['p']: - pacman.fill_provided_map(p, pkgname, context.provided_map) - split_provided = p.split('=') + installed_to_ignore = set() - if len(split_provided) > 1 and split_provided[0] != p: - pacman.fill_provided_map(split_provided[0], pkgname, context.provided_map) + for pkgname in pkgs: + pacman.fill_provided_map(pkgname, pkgname, context.provided_map) - if installed_to_ignore: # filling the provided names of the installed - installed_to_query = context.installed_names.difference(installed_to_ignore) + if fill_installed: + installed_to_ignore.add(pkgname) - if installed_to_query: - context.provided_map.update(pacman.map_provided(remote=False, pkgs=installed_to_query)) + pdata = context.pkgs_data.get(pkgname) + if pdata and pdata['p']: + pacman.fill_provided_map('{}={}'.format(pkgname, pdata['v']), pkgname, context.provided_map) - tf = time.time() - self.logger.info("Filling provided names took {0:.2f} seconds".format(tf - ti)) + ver_split = pdata['v'].split('-') + + if len(ver_split) > 1: + pacman.fill_provided_map('{}={}'.format(pkgname, '-'.join(ver_split[0:-1])), pkgname, context.provided_map) + + for p in pdata['p']: + pacman.fill_provided_map(p, pkgname, context.provided_map) + split_provided = p.split('=') + + if len(split_provided) > 1 and split_provided[0] != p: + pacman.fill_provided_map(split_provided[0], pkgname, context.provided_map) + + if installed_to_ignore: # filling the provided names of the installed + installed_to_query = context.installed_names.difference(installed_to_ignore) + + if installed_to_query: + context.provided_map.update(pacman.map_provided(remote=False, pkgs=installed_to_query)) + + tf = time.time() + self.logger.info("Filling provided names took {0:.2f} seconds".format(tf - ti)) def __fill_aur_index(self, context: UpdateRequirementsContext): if context.arch_config['aur']: @@ -381,7 +397,7 @@ class UpdatesSummarizer: if aur_data: context.pkgs_data.update(aur_data) - self.__fill_provided_map(context) + self.__fill_provided_map(context=context, pkgs=context.to_update) if context.pkgs_data: self._fill_conflicts(context) @@ -394,6 +410,9 @@ class UpdatesSummarizer: self.logger.error("Package '{}' not found".format(e.name)) return + if context.pkgs_data: + self._fill_dependency_breakage(context) + self.__update_context_based_on_to_remove(context) if context.to_update: @@ -558,3 +577,166 @@ class UpdatesSummarizer: self._add_to_remove(pkgs_to_sync, {dep: {n} for dep in all_deps}, context, blacklist) else: self.logger.warning("Package '{}' could not be removed from the transaction context because its data was not loaded") + + def _fill_dependency_breakage(self, context: UpdateRequirementsContext): + if bool(context.arch_config['check_dependency_breakage']) and (context.to_update or context.to_install): + ti = time.time() + self.logger.info("Begin: checking dependency breakage") + + required_by = pacman.map_required_by(context.to_update.keys()) if context.to_update else {} + + if context.to_install: + required_by.update(pacman.map_required_by(context.to_install.keys(), remote=True)) + + reqs_not_in_transaction = set() + reqs_in_transaction = set() + + transaction_pkgs = {*context.to_update.keys(), *context.to_install.keys()} + + for reqs in required_by.values(): + for r in reqs: + if r in transaction_pkgs: + reqs_in_transaction.add(r) + elif r in context.installed_names: + reqs_not_in_transaction.add(r) + + if not reqs_not_in_transaction and not reqs_in_transaction: + return + + provided_versions = {} + + for p in context.provided_map: + pkg_split = p.split('=') + + if len(pkg_split) > 1: + versions = provided_versions.get(pkg_split[0]) + + if versions is None: + versions = set() + provided_versions[pkg_split[0]] = versions + + versions.add(pkg_split[1]) + + if not provided_versions: + return + + cannot_upgrade = set() + + for pkg, deps in pacman.map_required_dependencies(*reqs_not_in_transaction).items(): + self._add_dependency_breakage(pkgname=pkg, + pkgdeps=deps, + provided_versions=provided_versions, + cannot_upgrade=cannot_upgrade, + context=context) + + for pkg in reqs_in_transaction: + data = context.pkgs_data[pkg] + + if data and data['d']: + self._add_dependency_breakage(pkgname=pkg, + pkgdeps=data['d'], + provided_versions=provided_versions, + cannot_upgrade=cannot_upgrade, + context=context) + + if cannot_upgrade: + cannot_upgrade.update(self._add_dependents_as_cannot_upgrade(context=context, + names=cannot_upgrade, + pkgs_available={*context.to_update.values(), *context.to_install.values()})) + + for p in cannot_upgrade: + if p in context.to_update: + del context.to_update[p] + + if p in context.repo_to_update: + del context.repo_to_update[p] + + if p in context.aur_to_update: + del context.aur_to_update[p] + + if p in context.pkgs_data: + del context.pkgs_data[p] + + if p in context.to_install: + del context.to_install[p] + + if p in context.repo_to_install: + del context.repo_to_install[p] + + if p in context.aur_to_install: + del context.aur_to_install[p] + + tf = time.time() + self.logger.info("End: checking dependency breakage. Time: {0:.2f} seconds".format(tf - ti)) + + def _add_dependents_as_cannot_upgrade(self, context: UpdateRequirementsContext, names: Iterable[str], pkgs_available: Set[ArchPackage], already_removed: Optional[Set[str]] = None, iteration_level: int = 0) -> Set[str]: + removed = set() if already_removed is None else already_removed + removed.update(names) + + available = {p for p in pkgs_available if p.name not in removed} + to_remove = set() + + if available: + for pkg in available: + if pkg.name not in removed: + data = context.pkgs_data.get(pkg.name) + + if data and data['d']: + for dep in data['d']: + dep_providers = context.provided_map.get(dep) + + if dep_providers: + for p in dep_providers: + if p in names: + to_remove.add(pkg.name) + + if pkg.name not in context.cannot_upgrade: + reason = "{} {}".format(self.i18n['arch.info.depends on'].capitalize(), p) + context.cannot_upgrade[pkg.name] = UpgradeRequirement(pkg=pkg, + reason=reason, + sorting_priority=iteration_level - 1) + + break + + if to_remove: + removed.update(to_remove) + self._add_dependents_as_cannot_upgrade(context=context, names=to_remove, pkgs_available=available, + already_removed=to_remove, iteration_level=iteration_level-1) + + return to_remove + + def _add_dependency_breakage(self, pkgname: str, pkgdeps: Optional[Set[str]], provided_versions: Dict[str, Set[str]], cannot_upgrade: Set[str], context: UpdateRequirementsContext): + if pkgdeps: + for dep in pkgdeps: + dep_split = RE_DEP_OPERATORS.split(dep) + + if len(dep_split) > 1 and dep_split[1]: + real_providers = context.provided_map.get(dep_split[0]) + + if real_providers: + versions = provided_versions.get(dep_split[0]) + + if versions: + op = ''.join(RE_DEP_OPERATORS.findall(dep)) + + if op == '=': + op = '==' + + version_match = False + + for v in versions: + provided_version, required_version = LooseVersion(v), LooseVersion(dep_split[1]) + + if eval('provided_version {} required_version'.format(op)): + version_match = True + break + + if not version_match: + for pname in real_providers: + if pname not in cannot_upgrade: + provider = context.to_update.get(pname) + if provider: + cannot_upgrade.add(pname) + reason = self.i18n['arch.sync.dep_breakage.reason'].format(pkgname, dep) + context.cannot_upgrade[pname] = UpgradeRequirement(pkg=provider, + reason=reason) diff --git a/bauh/view/qt/components.py b/bauh/view/qt/components.py index ab219268..41ed76e0 100644 --- a/bauh/view/qt/components.py +++ b/bauh/view/qt/components.py @@ -619,6 +619,7 @@ class FormMultipleSelectQt(QWidget): help_icon = QLabel() help_icon.setPixmap(pixmap_help) help_icon.setToolTip(op.tooltip) + help_icon.setCursor(QCursor(Qt.PointingHandCursor)) widget.layout().addWidget(help_icon) self._layout.addWidget(widget, line, col) diff --git a/bauh/view/qt/thread.py b/bauh/view/qt/thread.py index 818d74b8..cec0122d 100644 --- a/bauh/view/qt/thread.py +++ b/bauh/view/qt/thread.py @@ -193,7 +193,7 @@ class UpgradeSelected(AsyncAction): self.manager = manager self.i18n = i18n - def _req_as_option(self, req: UpgradeRequirement, tooltip: bool = True, custom_tooltip: str = None, required_size: bool = True) -> InputOption: + def _req_as_option(self, req: UpgradeRequirement, tooltip: bool = True, custom_tooltip: str = None, required_size: bool = True, display_sizes: bool = True) -> InputOption: if req.pkg.installed: icon_path = req.pkg.get_disk_icon_path() @@ -205,15 +205,19 @@ class UpgradeSelected(AsyncAction): else: icon_path = req.pkg.get_type_icon_path() - size_str = '{}: {}'.format(self.i18n['size'].capitalize(), - '?' if req.extra_size is None else get_human_size_str(req.extra_size)) - if required_size and req.extra_size != req.required_size: - size_str += ' ( {}: {} )'.format(self.i18n['action.update.pkg.required_size'].capitalize(), - '?' if req.required_size is None else get_human_size_str(req.required_size)) + size_str = None + if display_sizes: + size_str = '{}: {}'.format(self.i18n['size'].capitalize(), + '?' if req.extra_size is None else get_human_size_str(req.extra_size)) + if required_size and req.extra_size != req.required_size: + size_str += ' ( {}: {} )'.format(self.i18n['action.update.pkg.required_size'].capitalize(), + '?' if req.required_size is None else get_human_size_str(req.required_size)) - label = '{}{} - {}'.format(req.pkg.name, - ' ( {} )'.format(req.pkg.latest_version) if req.pkg.latest_version else '', - size_str) + label = '{}{}'.format(req.pkg.name, + ' ( {} )'.format(req.pkg.latest_version) if req.pkg.latest_version else '') + + if size_str: + label += ' - {}'.format(size_str) return InputOption(label=label, value=None, @@ -232,11 +236,14 @@ class UpgradeSelected(AsyncAction): return required, extra - def _gen_cannot_update_form(self, reqs: List[UpgradeRequirement]) -> FormComponent: - opts = [self._req_as_option(r, False, r.reason) for r in reqs] + def _gen_cannot_upgrade_form(self, reqs: List[UpgradeRequirement]) -> FormComponent: + reqs.sort(key=UpgradeRequirement.sort_by_priority) + opts = [self._req_as_option(req=r, tooltip=False, custom_tooltip=r.reason, display_sizes=False) for r in reqs] comps = [MultipleSelectComponent(label='', options=opts, default_options=set(opts))] - return FormComponent(label=self.i18n['action.update.cannot_update_label'], components=comps) + return FormComponent(label='{} ( {}: {} )'.format(self.i18n['action.update.cannot_update_label'], + self.i18n['amount'].capitalize(), len(opts)), + components=comps) def _gen_to_install_form(self, reqs: List[UpgradeRequirement]) -> Tuple[FormComponent, Tuple[int, int]]: opts = [self._req_as_option(r, custom_tooltip=r.reason) for r in reqs] @@ -396,7 +403,7 @@ class UpgradeSelected(AsyncAction): comps, required_size, extra_size = [], 0, 0 if requirements.cannot_upgrade: - comps.append(self._gen_cannot_update_form(requirements.cannot_upgrade)) + comps.append(self._gen_cannot_upgrade_form(requirements.cannot_upgrade)) if requirements.to_install: req_form, reqs_size = self._gen_to_install_form(requirements.to_install)