#!/usr/bin/env python2.7

# Copyright (c) 2017-2018 Metempsy Technology Consulting
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met: redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer;
# redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution;
# neither the name of the copyright holders nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Authors: Pau Cabre

from optparse import OptionParser
from subprocess import call
from platform import machine
from distutils import spawn
from glob import glob

import sys
import os

def run_cmd(explanation, working_dir, cmd, stdout = None):
    print "Running phase '%s'" % explanation
    sys.stdout.flush()

    # some of the commands need $PWD to be properly set
    env = os.environ.copy()
    env['PWD'] = working_dir

    return_code = call(cmd, cwd = working_dir, stdout = stdout,
                       env = env)

    if return_code == 0:
        return

    print "Error running phase %s. Returncode: %d" % (explanation, return_code)
    sys.exit(1)

script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
gem5_dir = os.path.dirname(script_dir)

parser = OptionParser()

parser.add_option("--gem5-dir", default = gem5_dir,
    metavar = "GEM5_DIR",
    help = "gem5 root directory to be used for bootloader and "
           "VExpress_GEM5_V1 DTB sources. The default value is the gem5 root "
           "directory of the executed script (%default)")
parser.add_option("--dest-dir", default = "/tmp",
    metavar = "DEST_DIR",
    help = "Directory to use for checking out the different kernel "
           "repositories. Generated files will be copied to "
           "DEST_DIR/binaries (which must not exist). The default "
           "value is %default")
parser.add_option("--make-jobs", type = "int", default = 1,
    metavar = "MAKE_JOBS",
    help = "Number of jobs to use with the 'make' commands. Default value: "
           "%default")

(options, args) = parser.parse_args()

if args:
    print "Unrecognized argument(s) %s." % args
    sys.exit(1)

if not os.path.isdir(options.dest_dir):
    print "Error: %s is not a directory." % options.dest_dir
    sys.exit(1)

if not os.path.isdir(options.gem5_dir):
    print "Error: %s is not a directory." % options.gem5_dir
    sys.exit(1)

if machine() != "x86_64":
    print "Error: This script should run in a x86_64 machine"
    sys.exit(1)

binaries_dir = options.dest_dir + "/binaries"

if os.path.exists(binaries_dir):
    print "Error: %s already exists." % binaries_dir
    sys.exit(1)

revisions_dir = options.dest_dir + "/revisions"

if os.path.exists(revisions_dir):
    print "Error: %s already exists." %revisions_dir
    sys.exit(1)

# Some basic dependency checking
needed_programs = [
    "make",
    "aarch64-linux-gnu-gcc",
    "arm-linux-gnueabihf-gcc",
    "aarch64-linux-gnu-gcc-4.8",
    "arm-linux-gnueabihf-gcc-4.8",
    "gcc",
    "bc",
    "dtc",
    "arm-linux-gnueabi-gcc"
]

for program in needed_programs:
    if not spawn.find_executable(program):
        print "Error: command %s not found in $PATH" % program
        print ("If running on an Debian-based linux, please try the following "
               "cmd to get all the necessary packages: ")
        print ("sudo apt-get install -y make gcc bc gcc-aarch64-linux-gnu "
              "gcc-4.8-aarch64-linux-gnu gcc-4.8-arm-linux-gnueabihf "
              "gcc-arm-linux-gnueabihf device-tree-compiler "
              "gcc-arm-linux-gnueabi")
        sys.exit(1)

os.mkdir(binaries_dir);
os.mkdir(revisions_dir);

make_jobs_str = "-j" + str(options.make_jobs)

rev_file = open(revisions_dir + "/gem5", "w+")
run_cmd("write revision of gem5 repo",
    gem5_dir,
    ["git", "rev-parse", "--short", "HEAD"],
    rev_file)
rev_file.close()

# Checkout and build linux kernel for VExpress_GEM5_V1 (arm and arm64)
kernel_vexpress_gem5_dir = options.dest_dir + "/linux-kernel-vexpress_gem5"
run_cmd("clone linux kernel for VExpress_GEM5_V1 platform",
    options.dest_dir,
    ["git", "clone", "https://gem5.googlesource.com/arm/linux",
     kernel_vexpress_gem5_dir])
rev_file = open(revisions_dir + "/linux", "w+")
run_cmd("write revision of linux-kernel-vexpress_gem5 repo",
    kernel_vexpress_gem5_dir,
    ["git", "rev-parse", "--short", "HEAD"],
    rev_file)
rev_file.close()
run_cmd("configure kernel for arm64",
    kernel_vexpress_gem5_dir,
    ["make", "ARCH=arm64", "CROSS_COMPILE=aarch64-linux-gnu-",
     "gem5_defconfig", make_jobs_str])
run_cmd("compile kernel for arm64",
    kernel_vexpress_gem5_dir,
    ["make", "ARCH=arm64", "CROSS_COMPILE=aarch64-linux-gnu-", make_jobs_str])
run_cmd("copy arm64 vmlinux",
    kernel_vexpress_gem5_dir,
    ["cp", "vmlinux", binaries_dir + "/vmlinux.vexpress_gem5_v1_64"])
run_cmd("cleanup arm64 kernel compilation",
    kernel_vexpress_gem5_dir,
    ["make", "distclean"])
run_cmd("configure kernel for arm",
    kernel_vexpress_gem5_dir,
    ["make", "ARCH=arm", "CROSS_COMPILE=arm-linux-gnueabihf-",
     "gem5_defconfig"])
run_cmd("compile kernel for arm",
    kernel_vexpress_gem5_dir,
    ["make", "ARCH=arm", "CROSS_COMPILE=arm-linux-gnueabihf-", make_jobs_str])
run_cmd("copy arm vmlinux",
    kernel_vexpress_gem5_dir,
    ["cp", "vmlinux", binaries_dir + "/vmlinux.vexpress_gem5_v1"])

# Checkout and build linux kernel and DTB for VExpress_EMM64
kernel_vexpress_emm64_dir = options.dest_dir + "/linux-kernel-vexpress_emm64"
run_cmd("clone linux kernel for VExpress_EMM64 platform",
    options.dest_dir,
    ["git", "clone", "https://gem5.googlesource.com/arm/linux-arm64-legacy",
     kernel_vexpress_emm64_dir])
rev_file = open(revisions_dir + "/linux-arm64-legacy", "w+")
run_cmd("write revision of linux-kernel-vexpress_emm64 repo",
    kernel_vexpress_emm64_dir,
    ["git", "rev-parse", "--short", "HEAD"],
    rev_file)
rev_file.close()
run_cmd("configure kernel",
    kernel_vexpress_emm64_dir,
    ["make", "ARCH=arm64", "CROSS_COMPILE=aarch64-linux-gnu-",
     "CC=aarch64-linux-gnu-gcc-4.8", "gem5_defconfig"])
run_cmd("compile kernel",
    kernel_vexpress_emm64_dir,
    ["make", "ARCH=arm64", "CROSS_COMPILE=aarch64-linux-gnu-",
     "CC=aarch64-linux-gnu-gcc-4.8", make_jobs_str])
run_cmd("copy vmlinux",
    kernel_vexpress_emm64_dir,
    ["cp", "vmlinux", binaries_dir + "/vmlinux.vexpress_emm64"])
run_cmd("copy DTB",
    kernel_vexpress_emm64_dir,
    ["cp", "arch/arm64/boot/dts/aarch64_gem5_server.dtb", binaries_dir])

# Checkout and build linux kernel and DTBs for VExpress_EMM
kernel_vexpress_emm_dir = options.dest_dir + "/linux-kernel-vexpress_emm"
run_cmd("clone linux kernel for VExpress_EMM platform",
    options.dest_dir,
    ["git", "clone", "https://gem5.googlesource.com/arm/linux-arm-legacy",
     kernel_vexpress_emm_dir])
rev_file = open(revisions_dir + "/linux-arm-legacy", "w+")
run_cmd("write revision of linux-kernel-vexpress_emm64 repo",
    kernel_vexpress_emm_dir,
    ["git", "rev-parse", "--short", "HEAD"],
    rev_file)
rev_file.close()
run_cmd("configure kernel",
    kernel_vexpress_emm_dir,
    ["make", "ARCH=arm", "CROSS_COMPILE=arm-linux-gnueabihf-",
     "CC=arm-linux-gnueabihf-gcc-4.8", "vexpress_gem5_server_defconfig"])
run_cmd("compile kernel",
    kernel_vexpress_emm_dir,
    ["make", "ARCH=arm", "CROSS_COMPILE=arm-linux-gnueabihf-",
     "CC=arm-linux-gnueabihf-gcc-4.8", make_jobs_str])
run_cmd("copy vmlinux",
    kernel_vexpress_emm_dir,
    ["cp", "vmlinux", binaries_dir + "/vmlinux.vexpress_emm"])
run_cmd("rename DTB for 1 CPU",
    kernel_vexpress_emm_dir,
    ["cp", "arch/arm/boot/dts/vexpress-v2p-ca15-tc1-gem5.dtb",
     binaries_dir + "/vexpress-v2p-ca15-tc1-gem5_1cpus.dtb"])
run_cmd("copy DTBs",
    kernel_vexpress_emm_dir,
    ["cp"] + glob(kernel_vexpress_emm_dir + "/arch/arm/boot/dts/*gem5_*dtb") +
    [binaries_dir])

# Build DTBs for VExpress_GEM5_V1
dt_dir = gem5_dir + "/system/arm/dt"
run_cmd("compile DTBs for VExpress_GEM5_V1 platform",
    dt_dir,
    ["make", make_jobs_str])
run_cmd("copy DTBs",
    dt_dir,
    ["cp"] + glob(dt_dir + "/*dtb") + [binaries_dir])

# Build bootloaders arm64
bootloader_arm64_dir = gem5_dir + "/system/arm/aarch64_bootloader"
run_cmd("compile arm64 bootloader",
    bootloader_arm64_dir,
    ["make"])
run_cmd("copy arm64 bootloader",
    bootloader_arm64_dir,
    ["cp", "boot_emm.arm64", binaries_dir])

# Build bootloaders arm
bootloader_arm_dir = gem5_dir + "/system/arm/simple_bootloader"
run_cmd("compile arm bootloader",
    bootloader_arm_dir,
    ["make"])
run_cmd("copy arm bootloaders",
    bootloader_arm_dir,
    ["cp", "boot.arm", "boot_emm.arm", binaries_dir])

# Build m5 binaries
m5_dir = gem5_dir + "/util/m5"
run_cmd("compile arm64 m5",
    m5_dir,
    ["make", "-f", "Makefile.aarch64"])
run_cmd("copy arm64 m5",
    m5_dir,
    ["cp", "m5", binaries_dir + "/m5.aarch64"])
run_cmd("clean arm64 m5",
    m5_dir,
    ["make", "clean", "-f", "Makefile.aarch64"])
run_cmd("compile arm m5",
    m5_dir,
    ["make", "-f", "Makefile.arm"])
run_cmd("copy arm m5",
    m5_dir,
    ["cp", "m5", binaries_dir + "/m5.aarch32"])

print "Done! All the generated files can be found in %s" % binaries_dir

sys.exit(0)