Files
bearhub/bauh/gems/arch/sorting.py

151 lines
4.9 KiB
Python

from typing import Dict, Set, Tuple, List, Collection, Optional
def __add_dep_to_sort(pkgname: str, pkgs_data: Dict[str, dict], sorted_names: dict, not_sorted: Set[str],
provided_map: Dict[str, str]):
idx = sorted_names.get(pkgname)
if idx is not None:
return idx
else:
idx = len(sorted_names)
sorted_names[pkgname] = idx
for dep in pkgs_data[pkgname]['d']:
dep_idx = sorted_names.get(dep)
if dep_idx is not None and dep_idx + 1 > idx:
idx = dep_idx + 1
else:
real_dep = provided_map.get(dep) # gets the real package name instead of the provided one
if not real_dep or real_dep not in pkgs_data:
continue # it means this depends does not belong to the sorting context
else:
dep_idx = sorted_names.get(real_dep)
if dep_idx is not None and dep_idx + 1 > idx:
idx = dep_idx + 1
else:
dep_idx = __add_dep_to_sort(real_dep, pkgs_data, sorted_names, not_sorted, provided_map)
if dep_idx + 1 > idx:
idx = dep_idx + 1
sorted_names[pkgname] = idx
return idx
def sort(pkgs: Collection[str], pkgs_data: Dict[str, dict], provided_map: Optional[Dict[str, Set[str]]] = None) -> List[Tuple[str, str]]:
sorted_list, sorted_names, not_sorted = [], set(), set()
all_provided = {**provided_map} if provided_map else {}
# adding all packages with no dependencies first
for pkgname in pkgs:
data = pkgs_data[pkgname]
if data['p']: # adding providers not mapped to the sorting context
for p in data['p']:
if p not in all_provided:
all_provided[p] = {pkgname}
if not data['d']:
sorted_list.append(pkgname)
sorted_names.add(pkgname)
else:
not_sorted.add(pkgname)
deps_map, not_deps_available = {}, set()
for pkg in not_sorted: # generating a dependency map with only the dependencies among the informed packages
pkgsdeps = set()
data = pkgs_data[pkg]
for dep in data['d']:
providers = all_provided.get(dep)
if providers:
for p in providers:
if p in pkgs:
pkgsdeps.add(p)
if pkgsdeps:
deps_map[pkg] = pkgsdeps
else:
not_deps_available.add(pkg)
sorted_list.append(pkg)
sorted_names.add(pkg)
for pkg in not_deps_available: # removing from not_sorted
not_sorted.remove(pkg)
while not_sorted:
sorted_in_round = set()
for pkg in not_sorted:
idx = _index_pkg(pkg, sorted_list, sorted_names, deps_map, ignore_not_sorted=False)
if idx >= 0:
sorted_in_round.add(pkg)
sorted_names.add(pkg)
sorted_list.insert(idx, pkg)
for pkg in sorted_in_round:
not_sorted.remove(pkg)
if not_sorted and not sorted_in_round: # it means there are cyclic deps
break
if not_sorted: # it means there are cyclic deps
# filtering deps already mapped
for pkg in not_sorted:
deps_map[pkg] = deps_map[pkg].difference(sorted_names)
dep_lvl_map = {} # holds the diff between the number of dependents per package and its dependencies
for pkg in not_sorted:
dependents = 0
for pkg2 in not_sorted:
if pkg != pkg2 and pkg in deps_map[pkg2]:
dependents += 1
dep_lvl_map[pkg] = dependents - len(deps_map[pkg])
sorted_by_less_deps = [*not_sorted]
sorted_by_less_deps.sort(key=lambda o: dep_lvl_map[o], reverse=True) # sorting by higher dep level
for pkg in sorted_by_less_deps:
idx = _index_pkg(pkg, sorted_list, sorted_names, deps_map, ignore_not_sorted=True)
sorted_names.add(pkg)
sorted_list.insert(idx, pkg)
# putting arch pkgs in the end:
aur_pkgs = None
res = []
for name in sorted_list:
repo = pkgs_data[name]['r']
if repo == 'aur':
if not aur_pkgs:
aur_pkgs = []
aur_pkgs.append((name, 'aur'))
else:
res.append((name, repo))
if aur_pkgs:
res.extend(aur_pkgs)
return res
def _index_pkg(name: str, sorted_list: List[str], sorted_names: Set[str], deps_map: Dict[str, Set[str]], ignore_not_sorted: bool) -> int:
deps_to_check_idx = set()
for dep in deps_map[name]:
if dep in sorted_names:
deps_to_check_idx.add(dep)
elif not ignore_not_sorted:
return -1
if not deps_to_check_idx:
return len(sorted_list)
else:
idxs = {sorted_list.index(dep) for dep in deps_to_check_idx}
return max(idxs) + 1