diff --git a/CHANGELOG.md b/CHANGELOG.md index 92d8c90f..38d11772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.9.18] +### Fixes +- Arch + - install/upgrade/downgrade: some dependencies not being matched during comparisons between numeric and alphanumeric versions + + ## [0.9.17] 2021-06-16 ### Improvements - general: replacing subprocess commands to detect installed CLIs by Python faster calls (**shutil.which**) diff --git a/bauh/__init__.py b/bauh/__init__.py index 5b6bb829..1cf69e8d 100644 --- a/bauh/__init__.py +++ b/bauh/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.9.17' +__version__ = '0.9.18' __app_name__ = 'bauh' import os diff --git a/bauh/gems/arch/dependencies.py b/bauh/gems/arch/dependencies.py index 66ebe8ad..53289679 100644 --- a/bauh/gems/arch/dependencies.py +++ b/bauh/gems/arch/dependencies.py @@ -3,13 +3,11 @@ import traceback from threading import Thread from typing import Set, List, Tuple, Dict, Iterable, Optional -from packaging.version import parse as parse_version - from bauh.api.abstract.handler import ProcessWatcher from bauh.gems.arch import pacman, message, sorting, confirmation from bauh.gems.arch.aur import AURClient from bauh.gems.arch.exceptions import PackageNotFoundException -from bauh.gems.arch.util import clean_version +from bauh.gems.arch.version import compare_versions from bauh.view.util.translation import I18n @@ -220,7 +218,7 @@ class DependenciesAnalyser: matched_providers = set() split_informed_dep = self.re_dep_operator.split(dep_exp) try: - version_required = parse_version(clean_version(split_informed_dep[2])) + version_required = split_informed_dep[2] exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '==' for p in providers: @@ -230,9 +228,9 @@ class DependenciesAnalyser: split_dep = self.re_dep_operator.split(provided_exp) if len(split_dep) == 3 and split_dep[0] == dep_name: - version_provided = parse_version(clean_version(split_dep[2])) + version_provided = split_dep[2] - if eval('version_provided {} version_required'.format(exp_op)): + if compare_versions(version_provided, exp_op, version_required): matched_providers.add(p) break @@ -270,17 +268,17 @@ class DependenciesAnalyser: self.__raise_dependency_not_found(dep_exp, watcher) else: try: - dep_version = parse_version(clean_version(dep_info[0]['Version'])) + dep_version = dep_info[0]['Version'] except: traceback.print_exc() - self.__raise_dependency_not_found(dep_exp, watcher) + return self.__raise_dependency_not_found(dep_exp, watcher) split_informed_dep = self.re_dep_operator.split(dep_exp) try: - version_required = parse_version(clean_version(split_informed_dep[2])) + version_required = split_informed_dep[2] exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '==' - if eval('dep_version {} version_required'.format(exp_op)): + if compare_versions(dep_version, exp_op, version_required): aur_deps.add(dep_name) missing_deps.add((dep_name, 'aur')) except: @@ -334,20 +332,11 @@ class DependenciesAnalyser: version_found = [p for p in provided_map if p.startswith(version_pattern)] if version_found: - version_found = clean_version(version_found[0].split('=')[1]) - version_required = clean_version(dep_split[2]) + version_found = version_found[0].split('=')[1] + version_required = dep_split[2] + op = dep_split[1] if dep_split[1] != '=' else '==' - try: - version_found = parse_version(version_found) - version_required = parse_version(version_required) - - op = dep_split[1] if dep_split[1] != '=' else '==' - match = eval('version_found {} version_required'.format(op)) - except: - match = False - traceback.print_exc() - - if not match: + if not compare_versions(version_found, op, version_required): self._fill_missing_dep(dep_name=dep_name, dep_exp=dep, aur_index=aur_index, missing_deps=missing_deps, remote_provided_map=remote_provided_map, diff --git a/bauh/gems/arch/mapper.py b/bauh/gems/arch/mapper.py index 3d363a73..30b11425 100644 --- a/bauh/gems/arch/mapper.py +++ b/bauh/gems/arch/mapper.py @@ -1,14 +1,11 @@ import logging import os -import traceback from typing import Optional -from colorama import Fore -from packaging.version import parse as parse_version - from bauh.api.abstract.model import PackageStatus from bauh.api.http import HttpClient from bauh.gems.arch.model import ArchPackage +from bauh.gems.arch.version import compare_versions from bauh.view.util.translation import I18n URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}' @@ -64,26 +61,7 @@ class AURDataMapper: @staticmethod def check_version_update(version: str, latest_version: str) -> bool: if version and latest_version and version != latest_version: - try: - ver_epoch, latest_epoch = version.split(':'), latest_version.split(':') - - if len(ver_epoch) > 1 and len(latest_epoch) > 1: - parsed_ver_epoch, parsed_latest_epoch = parse_version(ver_epoch[0]), parse_version(latest_epoch[0]) - - if parsed_ver_epoch == parsed_latest_epoch: - return parse_version(''.join(ver_epoch[1:])) < parse_version(''.join(latest_epoch[1:])) - else: - return parsed_ver_epoch < parsed_latest_epoch - elif len(ver_epoch) > 1 and len(latest_epoch) == 1: - return False - elif len(ver_epoch) == 1 and len(latest_epoch) > 1: - return True - else: - return parse_version(version) < parse_version(latest_version) - except: - print('{}Version: {}. Latest version: {}{}'.format(Fore.RED, version, latest_version, Fore.RESET)) - traceback.print_exc() - return False + return compare_versions(latest_version, '>', version) return False diff --git a/bauh/gems/arch/updates.py b/bauh/gems/arch/updates.py index 77db6c80..347b6714 100644 --- a/bauh/gems/arch/updates.py +++ b/bauh/gems/arch/updates.py @@ -4,8 +4,6 @@ import traceback from threading import Thread from typing import Dict, Set, List, Tuple, Iterable, Optional -from packaging.version import parse as parse_version - from bauh.api.abstract.controller import UpgradeRequirements, UpgradeRequirement from bauh.api.abstract.handler import ProcessWatcher from bauh.gems.arch import pacman, sorting @@ -14,6 +12,7 @@ from bauh.gems.arch.dependencies import DependenciesAnalyser from bauh.gems.arch.exceptions import PackageNotFoundException from bauh.gems.arch.model import ArchPackage from bauh.gems.arch.pacman import RE_DEP_OPERATORS +from bauh.gems.arch.version import compare_versions from bauh.view.util.translation import I18n @@ -733,9 +732,7 @@ class UpdatesSummarizer: for v in versions: try: - provided_version, required_version = parse_version(v), parse_version(dep_split[1]) - - if eval('provided_version {} required_version'.format(op)): + if compare_versions(v, op, dep_split[1]): version_match = True break except: diff --git a/bauh/gems/arch/version.py b/bauh/gems/arch/version.py new file mode 100644 index 00000000..cf4574d4 --- /dev/null +++ b/bauh/gems/arch/version.py @@ -0,0 +1,35 @@ +from typing import Optional + +from packaging.version import parse + + +def normalize_version(version: str) -> Optional[str]: + if version: + final_version = version.strip() + + if final_version: + if ':' not in final_version: + final_version = '0:{}'.format(final_version) + + if '-' not in final_version: + final_version = '{}-1'.format(final_version) + + return final_version + + +def compare_versions(version_a: str, operator: str, version_b: str) -> bool: + if operator: + a_obj, b_obj = parse(normalize_version(version_a)), parse(normalize_version(version_b)) + + if operator == '>': + return a_obj > b_obj + elif operator == '>=': + return a_obj >= b_obj + elif operator == '<': + return a_obj < b_obj + elif operator == '<=': + return a_obj <= b_obj + elif operator == '==': + return a_obj == b_obj + + raise Exception("compare: invalid string operator {}".format(operator)) diff --git a/tests/common/__init__.py b/tests/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/gems/arch/test_version.py b/tests/gems/arch/test_version.py new file mode 100644 index 00000000..7a8e2017 --- /dev/null +++ b/tests/gems/arch/test_version.py @@ -0,0 +1,46 @@ +from unittest import TestCase + +from bauh.gems.arch import version + + +class CompareVersionsTest(TestCase): + + def test_both_versions_only_filled_with_numbers_and_same_number_of_divisions(self): + self.assertFalse(version.compare_versions('1', '>', '2')) + self.assertTrue(version.compare_versions('1', '<', '2')) + self.assertTrue(version.compare_versions('1', '<=', '2')) + self.assertTrue(version.compare_versions('2', '>=', '1')) + self.assertTrue(version.compare_versions('1', '==', '1')) + self.assertFalse(version.compare_versions('1.1', '>', '2.0')) + self.assertTrue(version.compare_versions('1.1', '<', '2.0')) + self.assertTrue(version.compare_versions('1.1', '<=', '2.0')) + self.assertTrue(version.compare_versions('2.1', '>', '2.0')) + self.assertTrue(version.compare_versions('2.1', '>=', '2.0')) + self.assertTrue(version.compare_versions('2.0', '==', '2.0')) + + def test_versions_only_filled_with_numbers_and_different_number_of_divisons(self): + self.assertFalse(version.compare_versions('1.0', '>', '2')) + self.assertTrue(version.compare_versions('1', '<', '2.0')) + self.assertTrue(version.compare_versions('1.1.1', '<=', '2')) + self.assertTrue(version.compare_versions('2', '>=', '1.0')) + self.assertTrue(version.compare_versions('1.0', '==', '1')) + self.assertTrue(version.compare_versions('1.1', '>', '1')) + self.assertFalse(version.compare_versions('1.1', '<', '1')) + + def test_one_version_contain_mixed_letters_and_symbols(self): + self.assertTrue(version.compare_versions('2.3.3op2', '>=', '2.2.6')) + self.assertTrue(version.compare_versions('2.2a', '<', '2.2b')) + self.assertTrue(version.compare_versions('2.2a', '<', '2.3a')) + + def test_versions_with_epochs(self): + self.assertTrue(version.compare_versions('1:1', '==', '1:1.0')) + self.assertTrue(version.compare_versions('1:1', '<', '1:1.1')) + self.assertTrue(version.compare_versions('1:1', '<=', '1:1.1')) + self.assertTrue(version.compare_versions('2:0', '>', '1:1.1')) + self.assertTrue(version.compare_versions('1:0', '>', '2.0-1')) + + def test_versions_with_release_number(self): + self.assertTrue(version.compare_versions('1.0-1', '<', '1.0-2')) + self.assertTrue(version.compare_versions('1.0-2', '>', '1.0-1')) + self.assertTrue(version.compare_versions('1.0-1', '<', '1.0-10')) + self.assertTrue(version.compare_versions('1.0-10', '>', '1.0-1'))