#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Script to checkout the ChromeOS source.

This script sets up the ChromeOS source in the given directory, matching a
particular release of ChromeOS.
"""


__author__ = (
    "asharif@google.com (Ahmad Sharif) "
    "llozano@google.com (Luis Lozano) "
    "raymes@google.com (Raymes Khoury) "
    "shenhan@google.com (Han Shen)"
)

import argparse
import os
import sys

from cros_utils import command_executer
from cros_utils import logger
from cros_utils import misc


def Usage(parser, message):
    print("ERROR: %s" % message)
    parser.print_help()
    sys.exit(0)


def Main(argv):
    """Build ChromeOS."""
    # Common initializations
    cmd_executer = command_executer.GetCommandExecuter()

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--chromeos_root",
        dest="chromeos_root",
        help="Target directory for ChromeOS installation.",
    )
    parser.add_argument(
        "--clobber_chroot",
        dest="clobber_chroot",
        action="store_true",
        help="Delete the chroot and start fresh",
        default=False,
    )
    parser.add_argument(
        "--clobber_board",
        dest="clobber_board",
        action="store_true",
        help="Delete the board and start fresh",
        default=False,
    )
    parser.add_argument(
        "--rebuild",
        dest="rebuild",
        action="store_true",
        help="Rebuild all board packages except the toolchain.",
        default=False,
    )
    parser.add_argument(
        "--cflags",
        dest="cflags",
        default="",
        help="CFLAGS for the ChromeOS packages",
    )
    parser.add_argument(
        "--cxxflags",
        dest="cxxflags",
        default="",
        help="CXXFLAGS for the ChromeOS packages",
    )
    parser.add_argument(
        "--ldflags",
        dest="ldflags",
        default="",
        help="LDFLAGS for the ChromeOS packages",
    )
    parser.add_argument(
        "--board", dest="board", help="ChromeOS target board, e.g. x86-generic"
    )
    parser.add_argument(
        "--package", dest="package", help="The package needs to be built"
    )
    parser.add_argument(
        "--label",
        dest="label",
        help="Optional label symlink to point to build dir.",
    )
    parser.add_argument(
        "--dev",
        dest="dev",
        default=False,
        action="store_true",
        help=(
            "Make the final image in dev mode (eg writable, "
            "more space on image). Defaults to False."
        ),
    )
    parser.add_argument(
        "--debug",
        dest="debug",
        default=False,
        action="store_true",
        help=(
            'Optional. Build chrome browser with "-g -O0". '
            "Notice, this also turns on '--dev'. "
            "Defaults to False."
        ),
    )
    parser.add_argument(
        "--env", dest="env", default="", help="Env to pass to build_packages."
    )
    parser.add_argument(
        "--vanilla",
        dest="vanilla",
        default=False,
        action="store_true",
        help="Use default ChromeOS toolchain.",
    )
    parser.add_argument(
        "--vanilla_image",
        dest="vanilla_image",
        default=False,
        action="store_true",
        help=(
            "Use prebuild packages for building the image. "
            "It also implies the --vanilla option is set."
        ),
    )

    options = parser.parse_args(argv[1:])

    if options.chromeos_root is None:
        Usage(parser, "--chromeos_root must be set")
    options.chromeos_root = os.path.expanduser(options.chromeos_root)
    scripts_dir = os.path.join(options.chromeos_root, "src", "scripts")
    if not os.path.isdir(scripts_dir):
        Usage(
            parser,
            "--chromeos_root must be set up first. Use setup_chromeos.py",
        )

    if options.board is None:
        Usage(parser, "--board must be set")

    if options.debug:
        options.dev = True

    build_packages_env = options.env
    if build_packages_env.find("EXTRA_BOARD_FLAGS=") != -1:
        logger.GetLogger().LogFatal(
            (
                'Passing "EXTRA_BOARD_FLAGS" in "--env" is not supported. '
                "This flags is used internally by this script. "
                "Contact the author for more detail."
            )
        )

    if options.rebuild:
        build_packages_env += " EXTRA_BOARD_FLAGS=-e"
        # EXTRA_BOARD_FLAGS=-e should clean up the object files for the chrome
        # browser but it doesn't. So do it here.
        misc.RemoveChromeBrowserObjectFiles(
            options.chromeos_root, options.board
        )

    # Build with afdo_use by default.
    # To change the default use --env="USE=-afdo_use".
    build_packages_env = misc.MergeEnvStringWithDict(
        build_packages_env, {"USE": "chrome_internal afdo_use -cros-debug"}
    )

    build_packages_command = misc.GetBuildPackagesCommand(
        board=options.board, usepkg=options.vanilla_image, debug=options.debug
    )

    if options.package:
        build_packages_command += " {0}".format(options.package)

    build_image_command = misc.GetBuildImageCommand(options.board, options.dev)

    if options.vanilla or options.vanilla_image:
        command = misc.GetSetupBoardCommand(
            options.board,
            usepkg=options.vanilla_image,
            force=options.clobber_board,
        )
        command += "; " + build_packages_env + " " + build_packages_command
        command += "&& " + build_packages_env + " " + build_image_command
        ret = cmd_executer.ChrootRunCommand(options.chromeos_root, command)
        return ret

    # Setup board
    if (
        not os.path.isdir(
            options.chromeos_root + "/chroot/build/" + options.board
        )
        or options.clobber_board
    ):
        # Run build_tc.py from binary package
        ret = cmd_executer.ChrootRunCommand(
            options.chromeos_root,
            misc.GetSetupBoardCommand(
                options.board, force=options.clobber_board
            ),
        )
        logger.GetLogger().LogFatalIf(ret, "setup_board failed")
    else:
        logger.GetLogger().LogOutput(
            "Did not setup_board " "because it already exists"
        )

    if options.debug:
        # Perform 2-step build_packages to build a debug chrome browser.

        # Firstly, build everything that chromeos-chrome depends on normally.
        if options.rebuild:
            # Give warning about "--rebuild" and "--debug". Under this combination,
            # only dependencies of "chromeos-chrome" get rebuilt.
            logger.GetLogger().LogWarning(
                '--rebuild" does not correctly re-build every package when '
                '"--debug" is enabled. '
            )

            # Replace EXTRA_BOARD_FLAGS=-e with "-e --onlydeps"
            build_packages_env = build_packages_env.replace(
                "EXTRA_BOARD_FLAGS=-e", 'EXTRA_BOARD_FLAGS="-e --onlydeps"'
            )
        else:
            build_packages_env += " EXTRA_BOARD_FLAGS=--onlydeps"

        ret = cmd_executer.ChrootRunCommand(
            options.chromeos_root,
            'CFLAGS="$(portageq-%s envvar CFLAGS) %s" '
            'CXXFLAGS="$(portageq-%s envvar CXXFLAGS) %s" '
            'LDFLAGS="$(portageq-%s envvar LDFLAGS) %s" '
            "CHROME_ORIGIN=SERVER_SOURCE "
            "%s "
            "%s --skip_chroot_upgrade"
            "chromeos-chrome"
            % (
                options.board,
                options.cflags,
                options.board,
                options.cxxflags,
                options.board,
                options.ldflags,
                build_packages_env,
                build_packages_command,
            ),
        )

        logger.GetLogger().LogFatalIf(
            ret,
            "build_packages failed while trying to build chromeos-chrome deps.",
        )

        # Secondly, build chromeos-chrome using debug mode.
        # Replace '--onlydeps' with '--nodeps'.
        if options.rebuild:
            build_packages_env = build_packages_env.replace(
                'EXTRA_BOARD_FLAGS="-e --onlydeps"',
                "EXTRA_BOARD_FLAGS=--nodeps",
            )
        else:
            build_packages_env = build_packages_env.replace(
                "EXTRA_BOARD_FLAGS=--onlydeps", "EXTRA_BOARD_FLAGS=--nodeps"
            )
        ret = cmd_executer.ChrootRunCommand(
            options.chromeos_root,
            'CFLAGS="$(portageq-%s envvar CFLAGS) %s" '
            'CXXFLAGS="$(portageq-%s envvar CXXFLAGS) %s" '
            'LDFLAGS="$(portageq-%s envvar LDFLAGS) %s" '
            "CHROME_ORIGIN=SERVER_SOURCE BUILDTYPE=Debug "
            "%s "
            "%s --skip_chroot_upgrade"
            "chromeos-chrome"
            % (
                options.board,
                options.cflags,
                options.board,
                options.cxxflags,
                options.board,
                options.ldflags,
                build_packages_env,
                build_packages_command,
            ),
        )
        logger.GetLogger().LogFatalIf(
            ret,
            "build_packages failed while trying to build debug chromeos-chrome.",
        )

        # Now, we have built chromeos-chrome and all dependencies.
        # Finally, remove '-e' from EXTRA_BOARD_FLAGS,
        # otherwise, chromeos-chrome gets rebuilt.
        build_packages_env = build_packages_env.replace(
            "EXTRA_BOARD_FLAGS=--nodeps", ""
        )

        # Up to now, we have a debug built chromos-chrome browser.
        # Fall through to build the rest of the world.

    # Build packages
    ret = cmd_executer.ChrootRunCommand(
        options.chromeos_root,
        'CFLAGS="$(portageq-%s envvar CFLAGS) %s" '
        'CXXFLAGS="$(portageq-%s envvar CXXFLAGS) %s" '
        'LDFLAGS="$(portageq-%s envvar LDFLAGS) %s" '
        "CHROME_ORIGIN=SERVER_SOURCE "
        "%s "
        "%s --skip_chroot_upgrade"
        % (
            options.board,
            options.cflags,
            options.board,
            options.cxxflags,
            options.board,
            options.ldflags,
            build_packages_env,
            build_packages_command,
        ),
    )

    logger.GetLogger().LogFatalIf(ret, "build_packages failed")
    if options.package:
        return 0
    # Build image
    ret = cmd_executer.ChrootRunCommand(
        options.chromeos_root, build_packages_env + " " + build_image_command
    )

    logger.GetLogger().LogFatalIf(ret, "build_image failed")

    flags_file_name = "flags.txt"
    flags_file_path = "%s/src/build/images/%s/latest/%s" % (
        options.chromeos_root,
        options.board,
        flags_file_name,
    )
    with open(flags_file_path, "w", encoding="utf-8") as flags_file:
        flags_file.write("CFLAGS=%s\n" % options.cflags)
        flags_file.write("CXXFLAGS=%s\n" % options.cxxflags)
        flags_file.write("LDFLAGS=%s\n" % options.ldflags)

    if options.label:
        image_dir_path = "%s/src/build/images/%s/latest" % (
            options.chromeos_root,
            options.board,
        )
        real_image_dir_path = os.path.realpath(image_dir_path)
        command = "ln -sf -T %s %s/%s" % (
            os.path.basename(real_image_dir_path),
            os.path.dirname(real_image_dir_path),
            options.label,
        )

        ret = cmd_executer.RunCommand(command)
        logger.GetLogger().LogFatalIf(
            ret, "Failed to apply symlink label %s" % options.label
        )

    return ret


if __name__ == "__main__":
    retval = Main(sys.argv)
    sys.exit(retval)
