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