#! /usr/bin/python -u
# Copyright 2015 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""A script that builds a snap."""

from __future__ import print_function

__metaclass__ = type

from optparse import OptionParser
import os
import subprocess
import sys
import traceback

from lpbuildd.util import (
    set_personality,
    shell_escape,
    )


RETCODE_SUCCESS = 0
RETCODE_FAILURE_INSTALL = 200
RETCODE_FAILURE_BUILD = 201


def get_build_path(build_id, *extra):
    """Generate a path within the build directory.

    :param build_id: the build id to use.
    :param extra: the extra path segments within the build directory.
    :return: the generated path.
    """
    return os.path.join(os.environ["HOME"], "build-" + build_id, *extra)


class SnapBuilder:
    """Builds a snap."""

    def __init__(self, options, name):
        self.options = options
        self.name = name
        self.chroot_path = get_build_path(
            self.options.build_id, 'chroot-autobuild')
        # Set to False for local testing if your chroot doesn't have an
        # appropriate certificate for your codehosting system.
        self.ssl_verify = True

    def chroot(self, args, echo=False):
        """Run a command in the chroot.

        :param args: the command and arguments to run.
        :param echo: if True, print the command before executing it.
        """
        args = set_personality(self.options.arch, args)
        if echo:
            print(
                "Running in chroot: %s" % ' '.join(
                "'%s'" % arg for arg in args))
            sys.stdout.flush()
        subprocess.check_call([
            "/usr/bin/sudo", "/usr/sbin/chroot", self.chroot_path] + args)

    def run_build_command(self, args, path="/build", env=None, echo=False):
        """Run a build command in the chroot.

        This is unpleasant because we need to run it in /build under sudo
        chroot, and there's no way to do this without either a helper
        program in the chroot or unpleasant quoting.  We go for the
        unpleasant quoting.

        :param args: the command and arguments to run.
        :param path: the working directory to use in the chroot.
        :param env: dictionary of additional environment variables to set.
        :param echo: if True, print the command before executing it.
        """
        args = [shell_escape(arg) for arg in args]
        path = shell_escape(path)
        if env:
            args = ["env"] + [
                "%s=%s" % (key, shell_escape(value))
                for key, value in env.items()] + args
        command = "cd %s && %s" % (path, " ".join(args))
        self.chroot(["/bin/sh", "-c", command], echo=echo)

    def install(self):
        deps = ["snapcraft"]
        if self.options.branch is not None:
            deps.append("bzr")
        else:
            deps.append("git")
        self.chroot(["apt-get", "-y", "install"] + deps)

    def build(self):
        if self.options.branch is not None:
            self.run_build_command([
                "bzr", "branch", self.options.branch, self.name])
        else:
            assert self.options.git_repository is not None
            assert self.options.git_path is not None
            if not self.ssl_verify:
                env = {"GIT_SSL_NO_VERIFY": "1"}
            else:
                env = None
            self.run_build_command([
                "git", "clone", "-b", self.options.git_path,
                self.options.git_repository, self.name],
                env=env)
        self.run_build_command(
            ["snapcraft", "assemble"], path=os.path.join("/build", self.name),
            env={"SNAPCRAFT_LOCAL_SOURCES": "1"})


def main():
    parser = OptionParser("%prog [options] NAME")
    parser.add_option("--build-id", help="build identifier")
    parser.add_option(
        "--arch", metavar="ARCH", help="build for architecture ARCH")
    parser.add_option(
        "--branch", metavar="BRANCH", help="build from this Bazaar branch")
    parser.add_option(
        "--git-repository", metavar="REPOSITORY",
        help="build from this Git repository")
    parser.add_option(
        "--git-path", metavar="REF-PATH",
        help="build from this ref path in REPOSITORY")
    options, args = parser.parse_args()
    if (options.git_repository is None) != (options.git_path is None):
        parser.error(
            "must provide both --git-repository and --git-path or neither")
    if (options.branch is None) == (options.git_repository is None):
        parser.error(
            "must provide exactly one of --branch and --git-repository")
    if len(args) != 1:
        parser.error(
            "must provide a package name and no other positional arguments")
    [name] = args
    builder = SnapBuilder(options, name)
    try:
        builder.install()
    except Exception:
        traceback.print_exc()
        return RETCODE_FAILURE_INSTALL
    try:
        builder.build()
    except Exception:
        traceback.print_exc()
        return RETCODE_FAILURE_BUILD
    return RETCODE_SUCCESS


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