#!/usr/bin/python3
#
# Check components of build dependencies and warn about universe/multiverse
# ones, for a package destined for main/restricted
#
# Copyright (C) 2011 Canonical
#
# Authors:
#  Martin Pitt
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

# pylint: disable=invalid-name
# pylint: enable=invalid-name

"""Check if any of a package's build or binary dependencies are in universe or multiverse.

Run this inside an unpacked source package
"""

import argparse
import os.path
import sys

import apt


def check_support(apt_cache, pkgname, alt=False):
    """Check if pkgname is in main or restricted.

    This prints messages if a package is not in main/restricted, or only
    partially (i. e. source in main, but binary in universe).
    """
    if alt:
        prefix = "  ... alternative " + pkgname
    else:
        prefix = " * " + pkgname

    prov_packages = apt_cache.get_providing_packages(pkgname)
    if pkgname in apt_cache:
        pkg = apt_cache[pkgname]

    # If this is a virtual package, iterate through the binary packages that
    # provide this, and ensure they are all in Main. Source packages in and of
    # themselves cannot provide virtual packages, only binary packages can.
    elif len(prov_packages) > 0:
        supported, unsupported = [], []
        for pkg in prov_packages:
            candidate = pkg.candidate
            if candidate:
                section = candidate.section
                if section.startswith("universe") or section.startswith("multiverse"):
                    unsupported.append(pkg.name)
                else:
                    supported.append(pkg.name)

        if len(supported) > 0:
            msg = "is a virtual package, which is provided by the following "
            msg += "candidates in Main: " + " ".join(supported)
            print(prefix, msg)
        elif len(unsupported) > 0:
            msg = "is a virtual package, but is only provided by the "
            msg += "following non-Main candidates: " + " ".join(unsupported)
            print(prefix, msg, file=sys.stderr)
            return False
        else:
            msg = "is a virtual package that exists but is not provided by "
            msg += "package currently in the archive. Proceed with caution."
            print(prefix, msg, file=sys.stderr)
            return False

    else:
        print(prefix, "does not exist", file=sys.stderr)
        return False

    section = pkg.candidate.section
    if section.startswith("universe") or section.startswith("multiverse"):
        # check if the source package is in main and thus will only need binary
        # promotion
        source_records = apt.apt_pkg.SourceRecords()
        if not source_records.lookup(pkg.candidate.source_name):
            print("ERROR: Cannot lookup source package for", pkg.name, file=sys.stderr)
            print(prefix, "package is in", section.split("/")[0])
            return False
        src = apt.apt_pkg.TagSection(source_records.record)
        if src["Section"].startswith("universe") or src["Section"].startswith("multiverse"):
            print(prefix, "binary and source package is in", section.split("/")[0])
            return False

        print(
            prefix,
            "is in",
            section.split("/")[0] + ", but its source",
            pkg.candidate.source_name,
            "is already in main; file an ubuntu-archive bug for "
            "promoting the current preferred alternative",
        )
        return True

    if alt:
        print(prefix, "is already in main; consider preferring it")

    return True


def check_build_dependencies(apt_cache, control):
    print("Checking support status of build dependencies...")

    any_unsupported = False

    for field in ("Build-Depends", "Build-Depends-Indep"):
        if field not in control.section:
            continue
        for or_group in apt.apt_pkg.parse_src_depends(control.section[field]):
            pkgname = or_group[0][0]

            # debhelper-compat is expected to be a build dependency of every
            # package, so it is a red herring to display it in this report.
            # (src:debhelper is in Ubuntu Main anyway)
            if pkgname == "debhelper-compat":
                continue

            if not check_support(apt_cache, pkgname):
                # check non-preferred alternatives
                for altpkg in or_group[1:]:
                    if check_support(apt_cache, altpkg[0], alt=True):
                        break
                else:
                    any_unsupported = True

    return any_unsupported


def check_binary_dependencies(apt_cache, control):
    any_unsupported = False

    print("\nChecking support status of binary dependencies...")
    while True:
        try:
            next(control)
        except StopIteration:
            break

        for field in ("Depends", "Pre-Depends", "Recommends"):
            if field not in control.section:
                continue
            for or_group in apt.apt_pkg.parse_src_depends(control.section[field]):
                pkgname = or_group[0][0]
                if pkgname.startswith("$"):
                    continue
                if not check_support(apt_cache, pkgname):
                    # check non-preferred alternatives
                    for altpkg in or_group[1:]:
                        if check_support(apt_cache, altpkg[0], alt=True):
                            break
                    else:
                        any_unsupported = True

    return any_unsupported


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.parse_args()
    apt_cache = apt.Cache()

    if not os.path.exists("debian/control"):
        print(
            "debian/control not found. You need to run this tool in a source package directory",
            file=sys.stderr,
        )
        sys.exit(1)

    # get build dependencies from debian/control
    control = apt.apt_pkg.TagFile(open("debian/control", encoding="utf-8"))
    next(control)

    unsupported_build_deps = check_build_dependencies(apt_cache, control)
    unsupported_binary_deps = check_binary_dependencies(apt_cache, control)

    if unsupported_build_deps or unsupported_binary_deps:
        print(
            "\nPlease check https://wiki.ubuntu.com/MainInclusionProcess if "
            "this source package needs to get into in main/restricted, or "
            "reconsider if the package really needs above dependencies."
        )
    else:
        print("All dependencies are supported in main or restricted.")


if __name__ == "__main__":
    main()