############################################
# Copyright (c) 2013 Microsoft Corporation
#
# Scripts for automatically generating
# Linux/OSX/BSD distribution zip files.
#
# Author: Leonardo de Moura (leonardo)
############################################
import os
import glob
import re
import getopt
import sys
import shutil
import subprocess
import zipfile
from mk_exception import *
from mk_project import *
import mk_util

BUILD_DIR='build-dist'
VERBOSE=True
DIST_DIR='dist'
FORCE_MK=False
DOTNET_CORE_ENABLED=True
DOTNET_KEY_FILE=None
JAVA_ENABLED=True
GIT_HASH=False
PYTHON_ENABLED=True
MAKEJOBS=getenv("MAKEJOBS", '8')
OS_NAME=None

def set_verbose(flag):
    global VERBOSE
    VERBOSE = flag

def is_verbose():
    return VERBOSE

def mk_dir(d):
    if not os.path.exists(d):
        os.makedirs(d)

def set_build_dir(path):
    global BUILD_DIR
    BUILD_DIR = mk_util.norm_path(path)
    mk_dir(BUILD_DIR)

def display_help():
    print("mk_unix_dist.py: Z3 Linux/OSX/BSD distribution generator\n")
    print("This script generates the zip files containing executables, shared objects, header files for Linux/OSX/BSD.")
    print("It must be executed from the Z3 root directory.")
    print("\nOptions:")
    print("  -h, --help                    display this message.")
    print("  -s, --silent                  do not print verbose messages.")
    print("  -b <sudir>, --build=<subdir>  subdirectory where x86 and x64 Z3 versions will be built (default: build-dist).")
    print("  -f, --force                   force script to regenerate Makefiles.")
    print("  --nodotnet                    do not include .NET bindings in the binary distribution files.")
    print("  --dotnet-key=<file>           sign the .NET assembly with the private key in <file>.")
    print("  --arch=<arch>                 set architecture (to arm64) to force arm64 build")
    print("  --nojava                      do not include Java bindings in the binary distribution files.")
    print("  --os=<os>                     set OS version.")
    print("  --nopython                    do not include Python bindings in the binary distribution files.")
    print("  --githash                     include git hash in the Zip file.")
    exit(0)

# Parse configuration option for mk_make script
def parse_options():
    global FORCE_MK, JAVA_ENABLED, GIT_HASH, DOTNET_CORE_ENABLED, DOTNET_KEY_FILE, PYTHON_ENABLED, OS_NAME
    path = BUILD_DIR
    options, remainder = getopt.gnu_getopt(sys.argv[1:], 'b:hsf', ['build=',
                                                                   'help',
                                                                   'silent',
                                                                   'force',
                                                                   'nojava',
                                                                   'nodotnet',
                                                                   'dotnet-key=',
                                                                   'arch=',
                                                                   'os=',
                                                                   'githash',
                                                                   'nopython'
                                                                   ])
    for opt, arg in options:
        if opt in ('-b', '--build'):
            if arg == 'src':
                raise MKException('The src directory should not be used to host the Makefile')
            path = arg
        elif opt in ('-s', '--silent'):
            set_verbose(False)
        elif opt in ('-h', '--help'):
            display_help()
        elif opt in ('-f', '--force'):
            FORCE_MK = True
        elif opt == '--nodotnet':
            DOTNET_CORE_ENABLED = False
        elif opt == '--nopython':
            PYTHON_ENABLED = False
        elif opt == '--dotnet-key':
            DOTNET_KEY_FILE = arg
        elif opt == '--nojava':
            JAVA_ENABLED = False
        elif opt == '--githash':
            GIT_HASH = True
        elif opt == '--arch':
            if arg == "arm64":
                mk_util.IS_ARCH_ARM64 = True
            else:
                raise MKException("Invalid architecture directive '%s'. Legal directives: arm64" % arg)
        elif opt == '--os':
            OS_NAME = arg
        else:
            raise MKException("Invalid command line option '%s'" % opt)
    set_build_dir(path)

# Check whether build directory already exists or not
def check_build_dir(path):
    return os.path.exists(path) and os.path.exists(os.path.join(path, 'Makefile'))

# Create a build directory using mk_make.py
def mk_build_dir(path):
    global LINUX_X64
    if not check_build_dir(path) or FORCE_MK:
        env = os.environ
        opts = [sys.executable, os.path.join('scripts', 'mk_make.py'), "-b", path, "--staticlib"]
        if DOTNET_CORE_ENABLED:
            opts.append('--dotnet')
            if not DOTNET_KEY_FILE is None:
                opts.append('--dotnet-key=' + DOTNET_KEY_FILE)            
        if JAVA_ENABLED:
            opts.append('--java')
        if GIT_HASH:
            opts.append('--githash=%s' % mk_util.git_hash())
            opts.append('--git-describe')
        if PYTHON_ENABLED:
            opts.append('--python')
        if mk_util.IS_ARCH_ARM64:
            opts.append('--arm64=true')
        if mk_util.IS_ARCH_ARM64 and LINUX_X64:
            # we are machine x64 but build against arm64
            # so we have to do cross compiling
            # the cross compiler is download from ARM GNU
            # toolchain
            myvar = {
                "CC":  "aarch64-none-linux-gnu-gcc",
                "CXX": "aarch64-none-linux-gnu-g++"
            }
            env.update(myvar)
        if subprocess.call(opts, env=env) != 0:
            raise MKException("Failed to generate build directory at '%s'" % path)

# Create build directories
def mk_build_dirs():
    mk_build_dir(BUILD_DIR)

class cd:
    def __init__(self, newPath):
        self.newPath = newPath

    def __enter__(self):
        self.savedPath = os.getcwd()
        os.chdir(self.newPath)

    def __exit__(self, etype, value, traceback):
        os.chdir(self.savedPath)

def mk_z3():
    with cd(BUILD_DIR):
        try:
            return subprocess.call(['make', '-j', MAKEJOBS])
        except:
            return 1

def get_os_name():
    global LINUX_X64
    if OS_NAME is not None:
        return OS_NAME
    import platform
    basic = os.uname()[0].lower()
    if basic == 'linux':
        if mk_util.IS_ARCH_ARM64 and LINUX_X64:
            # handle cross compiling
            # example: 'ldd (GNU) 2.34'
            lines = subprocess.check_output(["ldd", "--version"]).decode('ascii')
            first_line = lines.split("\n")[0]
            ldd_version = first_line.split()[-1]
            # coerce the format to platform.libc_ver() return type
            dist = ('glibc', ldd_version)
        else:
            dist = platform.libc_ver()
        if len(dist) == 2 and len(dist[0]) > 0 and len(dist[1]) > 0:
            return '%s-%s' % (dist[0].lower(), dist[1].lower())
        else:
            return basic
    elif basic == 'darwin':
        ver = platform.mac_ver()
        if len(ver) == 3 and len(ver[0]) > 0:
            return 'osx-%s' % ver[0]
        else:
            return 'osx'
    elif basic == 'freebsd':
        ver = platform.version()
        idx1 = ver.find(' ')
        idx2 = ver.find('-')
        if idx1 < 0 or idx2 < 0 or idx1 >= idx2:
            return basic
        else:
            return 'freebsd-%s' % ver[(idx1+1):idx2]
    else:
        return basic

def get_z3_name():
    import platform as platform_module
    # Note that the platform name this function return
    # has to work together with setup.py
    # It's not the typical output from platform.machine()
    major, minor, build, revision = get_version()
    if mk_util.IS_ARCH_ARM64 or platform_module.machine() == "aarch64":
        # the second case handle native build on aarch64
        # TODO: we don't handle cross compile on host aarch64 to target x64
        platform = "arm64"    
    elif sys.maxsize >= 2**32:
        platform = "x64"
    else:
        platform = "x86"
    osname = get_os_name()
    if GIT_HASH:
        return 'z3-%s.%s.%s.%s-%s-%s' % (major, minor, build, mk_util.git_hash(), platform, osname)
    else:
        return 'z3-%s.%s.%s-%s-%s' % (major, minor, build, platform, osname)

def mk_dist_dir():
    build_path = BUILD_DIR
    dist_path = os.path.join(DIST_DIR, get_z3_name())
    mk_dir(dist_path)
    mk_util.DOTNET_CORE_ENABLED = DOTNET_CORE_ENABLED
    mk_util.DOTNET_ENABLED = False
    mk_util.DOTNET_KEY_FILE = DOTNET_KEY_FILE
    mk_util.JAVA_ENABLED = JAVA_ENABLED
    mk_util.PYTHON_ENABLED = PYTHON_ENABLED
    mk_unix_dist(build_path, dist_path)
    if is_verbose():
        print("Generated distribution folder at '%s'" % dist_path)

def get_dist_path():
    return get_z3_name()

def mk_zip():
    dist_path = get_dist_path()
    old = os.getcwd()
    try:
        os.chdir(DIST_DIR)
        zfname = '%s.zip' % dist_path
        zipout = zipfile.ZipFile(zfname, 'w', zipfile.ZIP_DEFLATED)
        for root, dirs, files in os.walk(dist_path):
            for f in files:
                zipout.write(os.path.join(root, f))
        if is_verbose():
            print("Generated '%s'" % zfname)
    except:
        pass
    os.chdir(old)

def cp_license():
    shutil.copy("LICENSE.txt", os.path.join(DIST_DIR, get_dist_path()))

# Entry point
def main():
    parse_options()
    mk_build_dirs()
    mk_z3()
    init_project_def()
    mk_dist_dir()
    cp_license()
    mk_zip()

main()

