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

"""A script that builds a live file system."""

__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 LiveFSBuilder:
    """Builds a live file system."""

    def __init__(self, options):
        self.options = options
        self.chroot_path = get_build_path(
            self.options.build_id, 'chroot-autobuild')

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

        :param args: the command and arguments to run.
        """
        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, 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 env: dictionary of additional environment variables to set.
        """
        args = [shell_escape(arg) for arg in args]
        if env:
            args = ["env"] + [
                "%s=%s" % (key, shell_escape(value))
                for key, value in env.items()] + args
        command = "cd /build && %s" % " ".join(args)
        self.chroot(["/bin/sh", "-c", command], echo=echo)

    def install(self):
        self.chroot(["apt-get", "-y", "install", "livecd-rootfs"])
        if self.options.arch == "i386":
            self.chroot([
                "apt-get", "-y", "--no-install-recommends", "install",
                "ltsp-server",
                ])
        if self.options.locale is not None:
            self.chroot([
                "apt-get", "-y", "--install-recommends", "install",
                "ubuntu-defaults-builder",
                ])

    def build(self):
        if self.options.locale is not None:
            self.run_build_command([
                "ubuntu-defaults-image",
                "--locale", self.options.locale,
                "--arch", self.options.arch,
                "--release", self.options.series,
                ])
        else:
            self.run_build_command(["rm", "-rf", "auto"])
            self.run_build_command(["mkdir", "-p", "auto"])
            for lb_script in ("config", "build", "clean"):
                lb_script_path = os.path.join(
                    "/usr/share/livecd-rootfs/live-build/auto", lb_script)
                self.run_build_command(["ln", "-s", lb_script_path, "auto/"])
            self.run_build_command(["lb", "clean", "--purge"])

            base_lb_env = {
                "PROJECT": self.options.project,
                "ARCH": self.options.arch,
                }
            if self.options.subproject is not None:
                base_lb_env["SUBPROJECT"] = self.options.subproject
            if self.options.subarch is not None:
                base_lb_env["SUBARCH"] = self.options.subarch
            lb_env = dict(base_lb_env)
            lb_env["SUITE"] = self.options.series
            if self.options.datestamp is not None:
                lb_env["NOW"] = self.options.datestamp
            if self.options.image_format is not None:
                lb_env["IMAGEFORMAT"] = self.options.image_format
            if self.options.proposed:
                lb_env["PROPOSED"] = "1"
            if self.options.extra_ppas:
                lb_env["EXTRA_PPAS"] = " ".join(self.options.extra_ppas)
            self.run_build_command(["lb", "config"], env=lb_env)
            self.run_build_command(["lb", "build"], env=base_lb_env)


def main():
    parser = OptionParser()
    parser.add_option("--build-id", help="build identifier")
    parser.add_option(
        "--arch", metavar="ARCH", help="build for architecture ARCH")
    parser.add_option(
        "--subarch", metavar="SUBARCH",
        help="build for subarchitecture SUBARCH")
    parser.add_option(
        "--project", metavar="PROJECT", help="build for project PROJECT")
    parser.add_option(
        "--subproject", metavar="SUBPROJECT",
        help="build for subproject SUBPROJECT")
    parser.add_option(
        "--series", metavar="SERIES", help="build for series SERIES")
    parser.add_option("--datestamp", help="date stamp")
    parser.add_option(
        "--image-format", metavar="FORMAT", help="produce an image in FORMAT")
    parser.add_option(
        "--proposed", default=False, action="store_true",
        help="enable use of -proposed pocket")
    parser.add_option(
        "--locale", metavar="LOCALE",
        help="use ubuntu-defaults-image to build an image for LOCALE")
    parser.add_option(
        "--extra-ppa", dest="extra_ppas", default=[], action="append",
        help="use this additional PPA")
    options, _ = parser.parse_args()

    builder = LiveFSBuilder(options)
    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())
