#!/usr/bin/env python3 # Adélie Linux architecture package tester # Ensure all packages depend on latest versions of each other # # Copyright © 2019-2020 Adélie Linux team. All rights reserved. # NCSA license. # import argparse import collections import functools import os import sys from apkkit.base.index import Index from version import is_older, is_same, verkey, ver_is, APK_OPS from output import FORMATTERS, No, Partial def atomize(spec): if not any(i in spec for i in APK_OPS): return spec, None for op in APK_OPS: try: name, ver = spec.split(op, maxsplit=1) except ValueError: continue return name, functools.partial(ver_is, op=op, b=ver) # Not reached assert False def analyze(url, repos, arch): pkgs = collections.defaultdict(dict) newest = {} providers = collections.defaultdict(list) print("Loading " + arch + "...", file=sys.stderr) index = [] for repo in repos: index.extend(Index(url=url + f"/{repo}/{arch}/APKINDEX.tar.gz").packages) for pkg in index: new = pkg.version pkgs[pkg.name][new] = pkg curr = newest.get(pkg.name, None) if curr is None: newest[pkg.name] = new elif is_older(curr, new): newest[pkg.name] = new providers[pkg.name].append((pkg.name, new)) for i in pkg.provides: i = i.split("=", maxsplit=1) if len(i) == 2: i, pver = i else: i, pver = i[0], new curr = newest.get(i, None) if curr is None: newest[i] = pver elif is_older(curr, pver): newest[i] = pver providers[i].append((pkg.name, pver)) yield ["arch", "package", "version", "issue"] for name in sorted(pkgs.keys()): # DON'T use newest[] here. It is possible that a package # provides= another package's name with a newer version ver = sorted(pkgs[name].keys(), key=verkey)[-1] pkg = pkgs[name][ver] for dep in pkg.depends: if dep.startswith("!"): continue spec = dep dep, constraint = atomize(dep) if dep in pkg.provides: continue if dep not in providers: yield [arch, name, ver, No(f"Missing {spec}")] continue if constraint: for provider, pver in providers[dep]: if constraint(pver): break else: yield [arch, name, ver, No(f"Missing {spec}")] for _, pver in providers[dep]: if is_same(pver, newest[dep]): break else: yield [arch, name, ver, Partial(f"Old {spec}")] if __name__ == "__main__": opts = argparse.ArgumentParser( description="""Scan the REPO/ARCH repositories under URL and look for outdated or missing dependencies as required by the most recent version of each package. This script examines both main packages and subpackages. Be sure to include any repositories on which the repository of interest depends, otherwise a lot of dependencies will be incorrectly marked as missing.""", ) opts.add_argument( "-f", "--format", choices=FORMATTERS.keys(), default="pretty" if os.isatty(sys.stdout.fileno()) else "tab", help="display format", ) opts.add_argument( "url", metavar="URL", help="base URL (no repository or arch)", ) opts.add_argument( "repos", metavar="REPOS", help="repositories (comma separated)", ) opts.add_argument( "arches", metavar="ARCHES", help="architectures (comma separated)", ) opts = opts.parse_args() opts.repos = opts.repos.split(",") opts.arches = opts.arches.split(",") for arch in opts.arches: # FIXME: with html, wrapping will repeat for > 1 arch FORMATTERS[opts.format]( opts, analyze(opts.url, opts.repos, arch), )