[arch] fix: some dependencies not being matched during comparisons between numeric and alphanumeric versions

This commit is contained in:
Vinicius Moreira
2021-06-17 17:03:34 -03:00
parent abaffcb850
commit db5eeabfb8
8 changed files with 104 additions and 53 deletions

View File

@@ -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/). 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 ## [0.9.17] 2021-06-16
### Improvements ### Improvements
- general: replacing subprocess commands to detect installed CLIs by Python faster calls (**shutil.which**) - general: replacing subprocess commands to detect installed CLIs by Python faster calls (**shutil.which**)

View File

@@ -1,4 +1,4 @@
__version__ = '0.9.17' __version__ = '0.9.18'
__app_name__ = 'bauh' __app_name__ = 'bauh'
import os import os

View File

@@ -3,13 +3,11 @@ import traceback
from threading import Thread from threading import Thread
from typing import Set, List, Tuple, Dict, Iterable, Optional 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.api.abstract.handler import ProcessWatcher
from bauh.gems.arch import pacman, message, sorting, confirmation from bauh.gems.arch import pacman, message, sorting, confirmation
from bauh.gems.arch.aur import AURClient from bauh.gems.arch.aur import AURClient
from bauh.gems.arch.exceptions import PackageNotFoundException 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 from bauh.view.util.translation import I18n
@@ -220,7 +218,7 @@ class DependenciesAnalyser:
matched_providers = set() matched_providers = set()
split_informed_dep = self.re_dep_operator.split(dep_exp) split_informed_dep = self.re_dep_operator.split(dep_exp)
try: 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 '==' exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '=='
for p in providers: for p in providers:
@@ -230,9 +228,9 @@ class DependenciesAnalyser:
split_dep = self.re_dep_operator.split(provided_exp) split_dep = self.re_dep_operator.split(provided_exp)
if len(split_dep) == 3 and split_dep[0] == dep_name: 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) matched_providers.add(p)
break break
@@ -270,17 +268,17 @@ class DependenciesAnalyser:
self.__raise_dependency_not_found(dep_exp, watcher) self.__raise_dependency_not_found(dep_exp, watcher)
else: else:
try: try:
dep_version = parse_version(clean_version(dep_info[0]['Version'])) dep_version = dep_info[0]['Version']
except: except:
traceback.print_exc() 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) split_informed_dep = self.re_dep_operator.split(dep_exp)
try: 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 '==' 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) aur_deps.add(dep_name)
missing_deps.add((dep_name, 'aur')) missing_deps.add((dep_name, 'aur'))
except: except:
@@ -334,20 +332,11 @@ class DependenciesAnalyser:
version_found = [p for p in provided_map if p.startswith(version_pattern)] version_found = [p for p in provided_map if p.startswith(version_pattern)]
if version_found: if version_found:
version_found = clean_version(version_found[0].split('=')[1]) version_found = version_found[0].split('=')[1]
version_required = clean_version(dep_split[2]) version_required = dep_split[2]
op = dep_split[1] if dep_split[1] != '=' else '=='
try: if not compare_versions(version_found, op, version_required):
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:
self._fill_missing_dep(dep_name=dep_name, dep_exp=dep, aur_index=aur_index, self._fill_missing_dep(dep_name=dep_name, dep_exp=dep, aur_index=aur_index,
missing_deps=missing_deps, missing_deps=missing_deps,
remote_provided_map=remote_provided_map, remote_provided_map=remote_provided_map,

View File

@@ -1,14 +1,11 @@
import logging import logging
import os import os
import traceback
from typing import Optional 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.abstract.model import PackageStatus
from bauh.api.http import HttpClient from bauh.api.http import HttpClient
from bauh.gems.arch.model import ArchPackage from bauh.gems.arch.model import ArchPackage
from bauh.gems.arch.version import compare_versions
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}' URL_PKG_DOWNLOAD = 'https://aur.archlinux.org/{}'
@@ -64,26 +61,7 @@ class AURDataMapper:
@staticmethod @staticmethod
def check_version_update(version: str, latest_version: str) -> bool: def check_version_update(version: str, latest_version: str) -> bool:
if version and latest_version and version != latest_version: if version and latest_version and version != latest_version:
try: return compare_versions(latest_version, '>', version)
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 False return False

View File

@@ -4,8 +4,6 @@ import traceback
from threading import Thread from threading import Thread
from typing import Dict, Set, List, Tuple, Iterable, Optional 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.controller import UpgradeRequirements, UpgradeRequirement
from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.handler import ProcessWatcher
from bauh.gems.arch import pacman, sorting 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.exceptions import PackageNotFoundException
from bauh.gems.arch.model import ArchPackage from bauh.gems.arch.model import ArchPackage
from bauh.gems.arch.pacman import RE_DEP_OPERATORS from bauh.gems.arch.pacman import RE_DEP_OPERATORS
from bauh.gems.arch.version import compare_versions
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
@@ -733,9 +732,7 @@ class UpdatesSummarizer:
for v in versions: for v in versions:
try: try:
provided_version, required_version = parse_version(v), parse_version(dep_split[1]) if compare_versions(v, op, dep_split[1]):
if eval('provided_version {} required_version'.format(op)):
version_match = True version_match = True
break break
except: except:

35
bauh/gems/arch/version.py Normal file
View File

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

0
tests/common/__init__.py Normal file
View File

View File

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