[arch] improvement: allowing AUR packages to be installed as dependencies of a repository package | fix: not displaying all possible AUR providers for a given dependency

This commit is contained in:
Vinicius Moreira
2022-01-28 12:05:58 -03:00
parent f69ed7aa4a
commit ca9b3d0e51
14 changed files with 312 additions and 168 deletions

View File

@@ -1,7 +1,8 @@
import re
import traceback
from logging import Logger
from threading import Thread
from typing import Set, List, Tuple, Dict, Iterable, Optional
from typing import Set, List, Tuple, Dict, Iterable, Optional, Generator
from bauh.api.abstract.handler import ProcessWatcher
from bauh.gems.arch import pacman, message, sorting, confirmation
@@ -13,10 +14,11 @@ from bauh.view.util.translation import I18n
class DependenciesAnalyser:
def __init__(self, aur_client: AURClient, i18n: I18n):
def __init__(self, aur_client: AURClient, i18n: I18n, logger: Logger):
self.aur_client = aur_client
self.i18n = i18n
self.re_dep_operator = re.compile(r'([<>=]+)')
self._log = logger
def _fill_repository(self, name: str, output: List[Tuple[str, str]]):
@@ -40,8 +42,8 @@ class DependenciesAnalyser:
output.append((name, ''))
def get_missing_packages(self, names: Set[str], repository: str = None, in_analysis: Set[str] = None) -> List[
Tuple[str, str]]:
def get_missing_packages(self, names: Set[str], repository: Optional[str] = None, in_analysis: Optional[Set[str]] = None) -> \
List[Tuple[str, str]]:
"""
:param names:
:param repository:
@@ -157,7 +159,8 @@ class DependenciesAnalyser:
if missing_subdeps:
for dep in missing_subdeps:
if not dep[1]:
message.show_dep_not_found(dep[0], self.i18n, watcher)
dependents = ', '.join(deps[0]) # it is not possible to know which is the exact dependent
message.show_dep_not_found(dep[0], self.i18n, watcher, dependent=dependents)
return
for dep in missing_subdeps:
@@ -175,25 +178,28 @@ class DependenciesAnalyser:
return sorted_deps
def _fill_missing_dep(self, dep_name: str, dep_exp: str, aur_index: Iterable[str],
missing_deps: Set[Tuple[str, str]],
remote_provided_map: Dict[str, Set[str]], remote_repo_map: Dict[str, str],
repo_deps: Set[str], aur_deps: Set[str], deps_data: Dict[str, dict], watcher: ProcessWatcher,
automatch_providers: bool):
def _find_repo_providers(self, dep_name: str, dep_exp: str, remote_provided_map: Dict[str, Set[str]],
deps_data: Dict[str, dict], remote_repo_map: Dict[str, str]) -> Generator[Tuple[str, str, Optional[dict]], None, None]:
if dep_name == dep_exp:
providers = remote_provided_map.get(dep_name)
if not providers: # try to find the package through the pacman's search mechanism
if providers:
for pkgname in providers:
yield pkgname, remote_repo_map.get(pkgname), None
else: # try to find the package through the pacman's search mechanism
match = pacman.find_one_match(dep_name)
if match:
providers = {match}
yield match, remote_repo_map.get(match), None
else: # handling cases when the dep has an expression ( e.g: xpto>=0.12 )
providers = remote_provided_map.get(dep_exp)
exact_exp_providers = remote_provided_map.get(dep_exp)
if providers is None:
if exact_exp_providers:
for p in exact_exp_providers:
yield p, remote_repo_map.get(p), None
else:
providers = remote_provided_map.get(dep_name)
if not providers: # try to find the package through the pacman's search mechanism
@@ -203,103 +209,177 @@ class DependenciesAnalyser:
providers = {match}
if providers:
no_mapped_data = {p for p in providers if
p not in deps_data} # checking providers with no mapped data
providers_no_provided_data = {p for p in providers if p not in deps_data}
missing_providers_data = None
if no_mapped_data:
providers_data = pacman.map_updates_data(no_mapped_data)
if providers_no_provided_data:
missing_providers_data = pacman.map_updates_data(providers_no_provided_data)
if not providers_data:
raise Exception("Could not retrieve the info from providers: {}".format(no_mapped_data))
if not missing_providers_data:
raise Exception(f"Could not retrieve information from providers: "
f"{', '.join(providers_no_provided_data)}")
deps_data.update(providers_data) # adding missing providers data
data_not_found = {p for p in providers if p not in missing_providers_data}
if data_not_found:
raise Exception(f"Could not retrieve information from providers: "
f"{', '.join(data_not_found)}")
matched_providers = set()
split_informed_dep = self.re_dep_operator.split(dep_exp)
try:
version_required = split_informed_dep[2]
exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '=='
for p in providers:
provided = deps_data[p]['p']
version_required = split_informed_dep[2]
exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '=='
for provided_exp in provided:
split_dep = self.re_dep_operator.split(provided_exp)
for p in providers:
info = deps_data.get(p)
if len(split_dep) == 3 and split_dep[0] == dep_name:
version_provided = split_dep[2]
if not info and missing_providers_data:
info = missing_providers_data[p]
if match_required_version(version_provided, exp_op, version_required):
matched_providers.add(p)
break
for provided_exp in info['p']:
split_dep = self.re_dep_operator.split(provided_exp)
providers = matched_providers
except:
traceback.print_exc()
if len(split_dep) == 3 and split_dep[0] == dep_name:
version_provided = split_dep[2]
if providers:
if len(providers) > 1:
dep_data = None
if match_required_version(version_provided, exp_op, version_required):
yield p, remote_repo_map.get(p), info
break
if automatch_providers:
exact_matches = [p for p in providers if p == dep_name]
if exact_matches:
dep_data = (exact_matches[0], remote_repo_map.get(exact_matches[0]))
if not dep_data:
dep_data = (dep_name, '__several__')
else:
real_name = providers.pop()
dep_data = (real_name, remote_repo_map.get(real_name))
repo_deps.add(dep_data[0])
missing_deps.add(dep_data)
elif aur_index and dep_name in aur_index:
def _find_aur_providers(self, dep_name: str, dep_exp: str, aur_index: Iterable[str], exact_match: bool) -> Generator[Tuple[str, dict], None, None]:
if exact_match and dep_name in aur_index:
if dep_name == dep_exp:
aur_deps.add(dep_name)
missing_deps.add((dep_name, 'aur'))
yield from self.aur_client.gen_updates_data((dep_name,))
return
else:
dep_info = self.aur_client.get_info({dep_name})
if not dep_info:
self.__raise_dependency_not_found(dep_exp, watcher)
else:
try:
dep_version = dep_info[0]['Version']
except:
traceback.print_exc()
return self.__raise_dependency_not_found(dep_exp, watcher)
for _, dep_data in self.aur_client.gen_updates_data((dep_name,)):
split_informed_dep = self.re_dep_operator.split(dep_exp)
version_required = split_informed_dep[2]
exp_op = split_informed_dep[1].strip()
if match_required_version(dep_data['v'], exp_op, version_required):
yield dep_name, dep_data
return
aur_search = self.aur_client.search(dep_name)
if aur_search:
if dep_name == dep_exp:
version_required, exp_op = None, None
else:
split_informed_dep = self.re_dep_operator.split(dep_exp)
version_required = split_informed_dep[2]
exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '=='
for pkgname, pkgdata in self.aur_client.gen_updates_data(
(aur_res['Name'] for aur_res in aur_search['results'])):
if pkgname == dep_name or (dep_name in pkgdata['p']):
try:
version_required = split_informed_dep[2]
exp_op = split_informed_dep[1].strip()
if match_required_version(dep_version, exp_op, version_required):
aur_deps.add(dep_name)
missing_deps.add((dep_name, 'aur'))
if not version_required or match_required_version(pkgdata['v'], exp_op,
version_required):
yield pkgname, pkgdata
except:
self.__raise_dependency_not_found(dep_exp, watcher)
else:
self.__raise_dependency_not_found(dep_exp, watcher)
self._log.warning(f"Could not compare AUR package '{pkgname}' version '{pkgdata['v']}' "
f"with the dependency expression '{dep_exp}'")
traceback.print_exc()
def __raise_dependency_not_found(self, dep_exp: str, watcher: Optional[ProcessWatcher]):
def _fill_missing_dep(self, dep_name: str, dep_exp: str, aur_index: Iterable[str],
missing_deps: Set[Tuple[str, str]],
remote_provided_map: Dict[str, Set[str]], remote_repo_map: Dict[str, str],
repo_deps: Set[str], aur_deps: Set[str], deps_data: Dict[str, dict], watcher: ProcessWatcher,
automatch_providers: bool, dependent: Optional[str] = None):
repo_matches = None
for pkgname, repo, data in self._find_repo_providers(dep_name=dep_name, dep_exp=dep_exp,
remote_repo_map=remote_repo_map,
remote_provided_map=remote_provided_map,
deps_data=deps_data):
if automatch_providers and pkgname == dep_name:
missing_deps.add((pkgname, repo))
repo_deps.add(pkgname)
if data:
deps_data[pkgname] = data
return
if repo_matches is None:
repo_matches = []
repo_matches.append((pkgname, repo, data))
aur_matches = None
if aur_index:
for pkgname, pkgdata in self._find_aur_providers(dep_name=dep_name, dep_exp=dep_exp, aur_index=aur_index,
exact_match=automatch_providers):
if automatch_providers and pkgname == dep_name:
missing_deps.add((pkgname, 'aur'))
aur_deps.add(pkgname)
deps_data[pkgname] = pkgdata
return
if aur_matches is None:
aur_matches = []
aur_matches.append((pkgname, pkgdata))
total_matches = (len(repo_matches) if repo_matches else 0) + (len(aur_matches) if aur_matches else 0)
if total_matches == 0:
self.__raise_dependency_not_found(dep_exp, watcher, dependent)
elif total_matches == 1:
if repo_matches:
repo_pkg = [*repo_matches][0]
if repo_pkg[2]:
deps_data[repo_pkg[0]] = repo_pkg[2] # already updating the deps data (if available)
match = (repo_pkg[0], repo_pkg[1])
repo_deps.add(repo_pkg[0])
else:
aur_pkg = aur_matches[0]
deps_data[aur_pkg[0]] = aur_pkg[1] # already updating the deps data (if available)
aur_deps.add(aur_pkg[0])
match = aur_pkg[0], 'aur'
missing_deps.add(match)
elif total_matches > 1:
missing_deps.add((dep_name, '__several__'))
if repo_matches:
repo_deps.add(dep_name)
if aur_matches:
aur_deps.add(dep_name)
for pkgname, _ in aur_matches:
for key in (dep_name, dep_exp):
key_provided = remote_provided_map.get(key, set())
remote_provided_map[key] = key_provided
key_provided.add(pkgname)
if pkgname not in remote_repo_map:
remote_repo_map[pkgname] = 'aur'
def __raise_dependency_not_found(self, dep_exp: str, watcher: Optional[ProcessWatcher], dependent: Optional[str] = None):
if watcher:
message.show_dep_not_found(dep_exp, self.i18n, watcher)
message.show_dep_not_found(depname=dep_exp, i18n=self.i18n, watcher=watcher, dependent=dependent)
raise PackageNotFoundException(dep_exp)
else:
raise PackageNotFoundException(dep_exp)
def __fill_aur_update_data(self, pkgname: str, output: dict):
output[pkgname] = self.aur_client.map_update_data(pkgname, None)
def _fill_aur_updates_data(self, pkgnames: Iterable[str], output_data: dict):
for pkgname, pkgdata in self.aur_client.gen_updates_data(pkgnames):
output_data[pkgname] = pkgdata
def map_missing_deps(self, pkgs_data: Dict[str, dict], provided_map: Dict[str, Set[str]],
remote_provided_map: Dict[str, Set[str]], remote_repo_map: Dict[str, str],
aur_index: Iterable[str], deps_checked: Set[str], deps_data: Dict[str, dict],
sort: bool, watcher: ProcessWatcher, choose_providers: bool = True,
automatch_providers: bool = False) -> List[Tuple[str, str]]:
automatch_providers: bool = False) -> Optional[List[Tuple[str, str]]]:
sorted_deps = [] # it will hold the proper order to install the missing dependencies
missing_deps, repo_missing, aur_missing = set(), set(), set()
@@ -325,7 +405,8 @@ class DependenciesAnalyser:
remote_repo_map=remote_repo_map,
repo_deps=repo_missing, aur_deps=aur_missing, watcher=watcher,
deps_data=deps_data,
automatch_providers=automatch_providers)
automatch_providers=automatch_providers,
dependent=p)
else:
version_pattern = '{}='.format(dep_name)
version_found = [p for p in provided_map if p.startswith(version_pattern)]
@@ -343,7 +424,8 @@ class DependenciesAnalyser:
repo_deps=repo_missing, aur_deps=aur_missing,
watcher=watcher,
deps_data=deps_data,
automatch_providers=automatch_providers)
automatch_providers=automatch_providers,
dependent=p)
else:
self._fill_missing_dep(dep_name=dep_name, dep_exp=dep, aur_index=aur_index,
missing_deps=missing_deps,
@@ -352,34 +434,11 @@ class DependenciesAnalyser:
repo_deps=repo_missing, aur_deps=aur_missing,
watcher=watcher,
deps_data=deps_data,
automatch_providers=automatch_providers)
automatch_providers=automatch_providers,
dependent=p)
if missing_deps:
if repo_missing:
with_single_providers = []
for d in missing_deps:
if d[0] in repo_missing and d[0] not in deps_data:
if d[1] == '__several__':
deps_data[d[0]] = {'d': None, 'p': d[0], 'r': d[1]}
else:
with_single_providers.append(d[0])
if with_single_providers:
data = pacman.map_updates_data(with_single_providers)
if data:
deps_data.update(data)
if aur_missing:
aur_threads = []
for pkgname in aur_missing:
t = Thread(target=self.__fill_aur_update_data, args=(pkgname, deps_data), daemon=True)
t.start()
aur_threads.append(t)
for t in aur_threads:
t.join()
self._fill_single_providers_data(missing_deps, repo_missing, aur_missing, deps_data)
missing_subdeps = self.map_missing_deps(pkgs_data={**deps_data}, provided_map=provided_map, aur_index=aur_index,
deps_checked=deps_checked, sort=False, deps_data=deps_data,
@@ -406,6 +465,51 @@ class DependenciesAnalyser:
return sorted_deps
def _fill_single_providers_data(self, all_missing_deps: Iterable[Tuple[str, str]], repo_missing_deps: Iterable[str], aur_missing_deps: Iterable[str], deps_data: Dict[str, dict]):
"""
fills the missing data of the single dependency providers since they are already considered dependencies
(when several providers are available for given a dependency, the user must choose first)
"""
repo_providers_no_data, aur_providers_no_data = None, None
for dep_name, dep_repo in all_missing_deps:
if dep_repo == '__several__':
deps_data[dep_name] = {'d': None, 'p': {dep_name}, 'r': dep_repo}
elif dep_name not in deps_data:
if repo_missing_deps and dep_name in repo_missing_deps:
if repo_providers_no_data is None:
repo_providers_no_data = set()
repo_providers_no_data.add(dep_name)
elif aur_missing_deps and dep_name in aur_missing_deps:
if aur_providers_no_data is None:
aur_providers_no_data = set()
aur_providers_no_data.add(dep_name)
aur_data_filler, aur_providers_data = None, None
if aur_providers_no_data:
aur_providers_data = dict()
aur_data_filler = Thread(target=self._fill_aur_updates_data,
args=(aur_providers_no_data, aur_providers_data))
aur_data_filler.start()
if repo_providers_no_data:
repo_providers_data = pacman.map_updates_data(repo_providers_no_data)
if repo_providers_data:
deps_data.update(repo_providers_data)
for pkgname, pkgdata in self.aur_client.gen_updates_data(aur_providers_no_data):
deps_data[pkgname] = pkgdata
if aur_data_filler:
aur_data_filler.join()
if aur_providers_data:
deps_data.update(aur_providers_data)
def fill_providers_deps(self, missing_deps: List[Tuple[str, str]],
provided_map: Dict[str, Set[str]], remote_repo_map: Dict[str, str],
already_checked: Set[str], remote_provided_map: Dict[str, Set[str]],
@@ -429,21 +533,45 @@ class DependenciesAnalyser:
remote_provided_map)
if deps_providers:
all_providers = set()
providers_repos = {}
repos_providers = set()
for providers in deps_providers.values():
all_providers.update(providers)
for provider in providers:
if remote_repo_map.get(provider) == 'aur':
providers_repos[provider] = 'aur'
else:
repos_providers.add(provider)
providers_repos = pacman.map_repositories(all_providers)
providers_repos.update(pacman.map_repositories(repos_providers))
selected_providers = confirmation.request_providers(deps_providers, providers_repos, watcher, self.i18n)
if not selected_providers:
return
else:
providers_data = pacman.map_updates_data(
selected_providers) # adding the chosen providers to re-check the missing deps
provided_map.update(pacman.map_provided(remote=True,
pkgs=selected_providers)) # adding the providers as "installed" packages
# adding the chosen providers for re-checking the missing dependencies
repo_selected, aur_selected = set(), set()
for provider in selected_providers:
if provider in repos_providers:
repo_selected.add(provider)
else:
aur_selected.add(provider)
providers_data = dict()
if repo_selected:
providers_data.update(pacman.map_updates_data(repo_selected))
# adding the providers as "installed" packages
provided_map.update(pacman.map_provided(remote=True, pkgs=repo_selected))
if aur_selected:
for pkgname, pkgdata in self.aur_client.gen_updates_data(aur_selected):
providers_data[pkgname] = pkgdata
for provider in pkgdata['p']: # adding the providers as "installed" packages
currently_provided = provided_map.get(provider, set())
provided_map[provider] = currently_provided
currently_provided.add(pkgname)
providers_deps = self.map_missing_deps(pkgs_data=providers_data,
provided_map=provided_map,
@@ -457,6 +585,9 @@ class DependenciesAnalyser:
choose_providers=True,
automatch_providers=automatch_providers)
if providers_deps is None: # it means the user called off the installation process
return
# cleaning the already mapped providers deps:
to_remove = []