#!/usr/bin/env python3
#
# Copyright 2014 Helmut Grohne <helmut@subdivi.de>
#
# Dual licensed under MIT/Expat and LGPL3+ with ocaml linking exception
#
# the regular use of the botch tools is to analyze either the native bootstrap
# of an architecture that exists or the cross bootstrap from one existing
# architecture to another existing architecture. This is not sufficient for the
# bootstrap of an unknown architecture where the following limitations exist:
#
#    - it is not known which binary packages of the new arch are Essential:yes
#    - providers of virtual packages are not known because there exists no
#      Packages file
#    - it is not known which source package builds which binary package for
#      source packages without a Packages-List field
#    - it is not known which binary package version is built from which source
#      package even with a Packages-List field (some source packages build a
#      binary package of different version)
#
# this script makes the following assumptions:
#
#    - versions do not exist
#           * no versioned build dependencies and conflicts
#           * no versioned runtime dependencies and conflicts
#           * which binary package version is built from which source package
#             version is ignored
#    - architecture restrictions do not exist
#           * all source packages build for all architectures
#           * no build dependency architecture restrictions exist
#    - some provides are hardcoded because they are not known beforehand
#    - conflicts do not exist
#           * no build-conflicts
#           * no runtime conflicts
#    - alternatives (disjunctions) do not exist
#           * the first alternative will always be chosen for build as well as
#             for runtime dependencies
#    - if a binary package is not declared to be built by a source package
#      through a Packages-List field (maybe the source package that builds it
#      does not have a Packages-List field) then fall back to using the Source
#      field of the build architecture package of the same name and assume they
#      build from the same source package
#    - the build architecture essential:yes packages are the same for the host
#      architecture

from debian import deb822

build_arch = "amd64"
host_arch = "x32"


def one(iterable):
    return next(iter(iterable))


provides = {
    "libz-dev": "zlib1g-dev",
    "libpng-dev": "libpng12-dev",
    "libgpmg1-dev": "libgpm-dev",
    "libselinux-dev": "libselinux1-dev",
    "libgmp10-dev": "libgmp-dev",
    "libltdl3-dev": "libltdl-dev",
    "libltdl7-dev": "libltdl-dev",
    "libncurses-dev": "libncurses5-dev",
    "libexpat-dev": "libexpat1-dev",
    "libtiff-dev": "libtiff5-dev",
}


def main(packages_f, sources_f):
    pkgs = dict()
    print("reading Packages")
    with open(packages_f) as f:
        for pkg in deb822.Packages.iter_paragraphs(f):
            archdict = pkgs.setdefault(pkg["Package"], dict())
            assert pkg["Architecture"] not in archdict
            retain = dict()
            for key in ("Essential", "Multi-Arch"):
                if key in pkg:
                    retain[key] = pkg[key]
            retain["Depends"] = pkg.relations["Depends"]
            if "Source" in pkg:
                retain["Source"] = pkg["Source"].split()[0]  # drop version
            archdict[pkg["Architecture"]] = retain
    print("reading Sources")
    srcs = dict()
    builds = dict()
    with open(sources_f) as f:
        for src in deb822.Sources.iter_paragraphs(f):
            # assert src["Package"] not in srcs
            retain = dict()
            retain["Build-Depends"] = src.relations["Build-Depends"]
            pkglist = set()
            if "Package-List" in src:
                for line in src["Package-List"].splitlines():
                    if not line.strip():
                        continue
                    parts = line.split()
                    if parts[1] == "deb":
                        builds[parts[0]] = src["Package"]
                        pkglist.add(parts[0])
            retain["Package-List"] = pkglist
            srcs[src["Package"]] = retain
    print("computing binary essential")
    binneeded = set(("build-essential", "debhelper"))
    for name, archdict in pkgs.items():
        for arch, pkg in archdict.items():
            if pkg.get("Essential", "no") == "yes":
                binneeded.add(name)
    print("computing source essential")
    binhave = set()
    depsneeded = set()
    bdsneeded = set()
    srcneeded = set()
    while True:
        if depsneeded:
            name = depsneeded.pop()
            archdict = pkgs[name]
            pkg = archdict.get(host_arch)
            if pkg is None:
                pkg = archdict.get("all")
            assert pkg is not None
            print("considering dependencies for %s" % name)
            for dep in pkg["Depends"]:
                # assumption: satisfiable using first alternative
                dep = dep[0]
                # assumption: version constraint always satisfiable
                # assumption: non-virtual package
                dep = dep["name"]
                if dep in provides:
                    print(
                        "dependency %s is virtual. provided by %s"
                        % (dep, provides[dep])
                    )
                    dep = provides[dep]
                if dep in binhave:
                    print("dependency %s already scheduled for building" % dep)
                elif dep in pkgs:
                    print("dependency %s needs to be built." % dep)
                    binneeded.add(dep)
                else:
                    print("dependency %s not found. virtual?" % dep)
        elif bdsneeded:
            name = bdsneeded.pop()
            src = srcs[name]
            print("considering build-dependencies for %s" % name)
            for dep in src["Build-Depends"]:
                # assumption: satifiable using first alternative
                dep = dep[0]
                # assumption: version constraint always satisfiable
                # assumption: non-virtual package
                dep = dep["name"]
                if dep.split()[0].endswith(":any"):
                    print("build-dependency %s satisfied by multi-arch" % dep)
                    continue
                if dep in provides:
                    print(
                        "build-dependency %s is virtual. provided by %s"
                        % (dep, provides[dep])
                    )
                    dep = provides[dep]
                try:
                    archdict = pkgs[dep]
                except KeyError:
                    print("build-dependency %s not found. virtual?" % dep)
                    continue
                arch, pkg = one(archdict.items())
                if pkg.get("Multi-Arch") == "foreign":
                    print("build-dependency %s satisfied by M-A:foreign" % dep)
                    continue
                if arch == "all":
                    print(
                        "build-dependency %s unsatisfiable due to "
                        + "arch:all M-A:no" % dep
                    )
                    continue
                if dep in binhave or dep in binneeded:
                    print("build-dependency %s already scheduled for building" % dep)
                    continue
                print("build-dependency %s needs to be built." % dep)
                binneeded.add(dep)
        elif binneeded:
            name = binneeded.pop()
            archdict = pkgs[name]
            if host_arch in archdict:
                print("binary %s already built. considering dependencies" % name)
                depsneeded.add(name)
                binhave.add(name)
            elif "all" in archdict:
                print("binary %s is arch:all. considering dependencies" % name)
                depsneeded.add(name)
                binhave.add(name)
            else:
                if name in builds:
                    src = builds[name]
                    binhave.update(srcs[src]["Package-List"])
                else:
                    print("binary %s not declared using Package-List" % name)
                    # assumption: same source for all architectures
                    src = one(archdict.values()).get("Source", name)
                    binhave.add(name)
                if src in srcneeded:
                    print(
                        "binary %s needs to be built from %s. already "
                        "scheduled" % (name, src)
                    )
                else:
                    print("binary %s needs to be built from %s." % (name, src))
                    srcneeded.add(src)
                    bdsneeded.add(src)
        else:
            break
    print("")
    for pkg in srcneeded:
        print(pkg)


if __name__ == "__main__":
    import sys

    if len(sys.argv) != 3:
        print("usage: %s Packages Sources" % sys.argv[0], file=sys.stderr)
        exit(1)
    main(sys.argv[1], sys.argv[2])
