#! /usr/bin/python3

# Copyright 2015 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.

"""A Git command to construct trees based on recipes."""

import argparse
import datetime
import os.path
import shutil
import sys
import tempfile

from debian.changelog import Changelog

from gitbuildrecipe.deb_util import (
    NoSuchTag,
    add_autobuild_changelog_entry,
    build_source_package,
    debian_source_package_name,
    extract_upstream_tarball,
    force_native_format,
    get_source_format,
    )
from gitbuildrecipe.deb_version import (
    check_expanded_deb_version,
    substitute_branch_vars,
    substitute_time,
    )
from gitbuildrecipe.recipe import (
    build_tree,
    parse_recipe,
    resolve_revisions,
    )


def main():
    parser = argparse.ArgumentParser(
        description="Construct a Debian source tree based on a recipe.")
    parser.add_argument(
        "location", metavar="LOCATION", type=argparse.FileType("r"),
        help="The file system path to the recipe.")
    parser.add_argument(
        "working_basedir", metavar="WORKING-BASEDIR", nargs="?", help=(
            "The path to a working directory.  If not specified, use a "
            "temporary directory."))
    parser.add_argument(
        "--manifest", metavar="PATH", help="Path to write the manifest to.")
    parser.add_argument(
        "--if-changed-from", metavar="PATH", help=(
            "Only build if the outcome would be different to that "
            "specified in this manifest."))
    parser.add_argument(
        "--package", help=(
            "The package name to use in the changelog entry.  If not "
            "specified then the package from the previous changelog entry "
            "will be used, so it must be specified if there is no changelog."))
    parser.add_argument(
        "--distribution", help=(
            "The distribution to target.  If not specified then the same "
            "distribution as the last entry in debian/changelog will be "
            "used."))
    parser.add_argument(
        "--no-build", action="store_false", default=True, dest="build",
        help="Just ready the source package and don't actually build it.")
    parser.add_argument(
        "--append-version", help=(
            "Append the specified string to the end of the version used in "
            "debian/changelog."))
    parser.add_argument(
        "--safe", action="store_true", default=False,
        help="Error if the recipe would cause arbitrary code execution.")
    parser.add_argument(
        "--allow-fallback-to-native", action="store_true", default=False,
        help=(
            "Allow falling back to a native package if the upstream tarball "
            "cannot be found."))
    args = parser.parse_args()

    base_branch = parse_recipe(args.location, safe=args.safe)
    working_basedir = args.working_basedir
    if working_basedir is None:
        temp_dir = tempfile.mkdtemp(prefix="git-build-recipe-")
        working_basedir = temp_dir
    else:
        temp_dir = None
        if not os.path.exists(working_basedir):
            os.makedirs(working_basedir)
    try:
        package_name = args.package
        if package_name is None:
            package_name = os.path.basename(args.location.name)
            if package_name.endswith(".recipe"):
                package_name = package_name[:-len(".recipe")]
        working_directory = os.path.join(working_basedir, package_name)
        manifest_path = os.path.join(
            working_directory, "debian", "git-build-recipe.manifest")
        build_tree(base_branch, working_directory)
        old_recipe = None
        if args.if_changed_from is not None:
            try:
                if_changed_from = open(args.if_changed_from)
            except FileNotFoundError:
                if_changed_from = None
            if if_changed_from is not None:
                try:
                    old_recipe = parse_recipe(args.if_changed_from)
                finally:
                    if_changed_from.close()
        if base_branch.deb_version is not None:
            time = datetime.datetime.utcnow()
            substitute_time(base_branch, time)
            resolve_revisions(
                base_branch, if_changed_from=old_recipe,
                substitute_branch_vars=substitute_branch_vars)
            check_expanded_deb_version(base_branch)
        else:
            resolve_revisions(base_branch, if_changed_from=old_recipe)
        control_path = os.path.join(working_directory, "debian", "control")
        if not os.path.exists(control_path):
            if args.package is None:
                parser.error(
                    "No control file to take the package name from, and "
                    "--package not specified.")
            package = args.package
        else:
            package = debian_source_package_name(control_path)
        with open(manifest_path, "w") as manifest:
            manifest.write(str(base_branch))
        if base_branch.deb_version is not None:
            # add_autobuild_changelog_entry also substitutes {debupstream}.
            add_autobuild_changelog_entry(
                base_branch, working_directory, package,
                distribution=args.distribution,
                append_version=args.append_version)
        elif args.append_version:
            parser.error(
                "--append-version is only supported for autobuild recipes "
                "(with a 'deb-version' header).")
        changelog_path = os.path.join(working_directory, "debian", "changelog")
        with open(changelog_path) as changelog_file:
            cl = Changelog(file=changelog_file)
        package_name = cl.package
        package_version = cl.version
        current_format = get_source_format(working_directory)
        if (package_version.debian_revision is not None or
                current_format == "3.0 (quilt)"):
            # Non-native package
            try:
                extract_upstream_tarball(
                    base_branch.path, package_name,
                    package_version.upstream_version, working_basedir)
            except NoSuchTag as e:
                if not args.allow_fallback_to_native:
                    parser.error(
                        "Unable to find the upstream source.  Import it as "
                        "tag %s or build with --allow-fallback-to-native." %
                        e.tag_name)
                else:
                    force_native_format(working_directory, current_format)
        if args.build:
            build_source_package(
                working_directory, tgz_check=not args.allow_fallback_to_native)
        if args.manifest is not None:
            with open(args.manifest, "w") as manifest:
                manifest.write(str(base_branch))
    finally:
        if temp_dir is not None:
            shutil.rmtree(temp_dir)


if __name__ == "__main__":
    sys.exit(main())
