#!/usr/bin/python


import os
import sys
import shutil
import hashlib
import subprocess
import platform


#-------------------------------------------------------------------------------
# Helpaer functions
#-------------------------------------------------------------------------------

def split(ori, spl):
    l = ori.split(spl)
    return [x for x in l if x]

def get_subdir(path):
    for root, dirs, files in os.walk(path):
        return dirs

def get_files(path):
    for root, dirs, files in os.walk(path):
        return files
    
def hashfile(name):
    afile = open(name, 'rb')
    hasher = hashlib.sha256()
    blocksize = 32768
    
    buf = afile.read(blocksize)
    while len(buf) > 0:
        hasher.update(buf)
        buf = afile.read(blocksize)
     
    return hasher.digest()

def get_dir(name):
    return os.path.dirname(name) + '/'

def get_filename(name):
    head, tail = os.path.split(name)
    return tail

def relative_path(name):
    if project_dir in name:
        return name.replace(project_dir, '')
    else:
        return name

def get_ext(name):
    filename, file_extension = os.path.splitext(name)
    return file_extension

def exec_cmd(cmd):
    p = subprocess.Popen(cmd, shell = True)
    p.wait()
    return p.returncode

def get_all_files(path, ext):
    files = []
    
    all_files = get_files(path)
    if all_files != None:
        for f in all_files:
            if None == ext or get_ext(f) in ext:
                files.append(path + f)
    
    all_dirs = get_subdir(path)
    if all_dirs != None:
        for subdir in all_dirs:
            files += get_all_files(path + subdir + '/', ext)
    
    return files

def count_lines(file_name):
    f = open(file_name)
    c = 0
    
    for l in f:
        c += 1
    
    f.close()
    return c

def include(name):
    assert(os.path.exists(name))
    execfile(name)

def quit():
    sys.exit()

def panic(s):
    print('Fatal: ' + s)
    quit()

def warn(s):
    print('Warning: ' + s)
    quit()

#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Host
#-------------------------------------------------------------------------------

host_arch_alias = { \
    'i386' : 'ia32',
    'x86_64' : 'amd64',
    'arm32' : 'armv7',
}

host_arch = platform.machine().strip()
if host_arch in host_arch_alias:
    host_arch = host_arch_alias[host_arch]

host_info = platform.architecture()
host_os = platform.platform()

#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Target
#-------------------------------------------------------------------------------

target_arch = 'ia32'
target_mach = 'generic'
target_suffix = ''

# Parse the target cmd
for argv in sys.argv:
    if argv.startswith('target') and '=' in argv and '-' in argv:
        parts = split(argv, '=')
        parts = split(parts[1], '-')
        target_arch = parts[0]
        target_mach = parts[1]
        if len(parts) >= 3:
            target_suffix = parts[2]

target_name = target_arch + '_' + target_mach
if target_suffix != '':
    target_name += '_' + target_suffix

#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Path
#-------------------------------------------------------------------------------

def do_find_target_dirs(target, cur_path, dirs, names, dir_map):
    subdirs = get_subdir(cur_path)
    if not subdirs:
        return False
    
    for d in subdirs:
        next_path = cur_path + d + '/'
        dirs.append(next_path)
        names.append(d)
        dir_map[d] = next_path
        
        if d == target:
            return True
        else:
            found = do_find_target_dirs(target, next_path, dirs, names, dir_map)
            if found:
                return True
            else:
                dirs.remove(next_path)
                names.remove(d)
                del dir_map[d]
    
    return False

def find_target_dirs(target, start_path):
    dirs = []
    names = []
    dir_map = {}
    do_find_target_dirs(target, start_path, dirs, names, dir_map)
    return dirs, names, dir_map


# Top level
cur_dir         = os.path.dirname(os.path.realpath(__file__))
project_dir     = cur_dir + '/'

# Source
src_dir         = project_dir + 'src/'
arch_dirs, arch_names, arch_dir_map \
                = find_target_dirs(target_arch, src_dir + 'arch/')
mach_dirs, mach_names, mach_dir_map \
                = find_target_dirs(target_mach, src_dir + 'mach/')

# See if the target arch and mach exists
if not len(arch_dirs):
    panic('Unable to find target architecture: {0}'.format(target_arch))

if not len(mach_dirs):
    panic('Unable to find target machine: {0}'.format(target_mach))

arch_dir        = arch_dirs[-1]
mach_dir        = mach_dirs[-1]

# Target
target_all_dir  = project_dir + 'target/'
target_dir      = target_all_dir + target_name + '/'
obj_dir         = target_dir + 'obj/'
bin_dir         = target_dir + 'bin/'
img_dir         = target_dir + 'img/'

# Tools
tools_src_dir   = project_dir + 'tools/'
tools_obj_dir   = obj_dir + 'tools/'
tools_bin_dir   = bin_dir + 'tools/'

# Doc
doc_dir         = project_dir + 'doc/'

# VM
vm_dir          = project_dir + 'vm/'

# Include
inc_dir         = [ src_dir ] + arch_dirs + mach_dirs

#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Printing functions
#-------------------------------------------------------------------------------

print_action_enabled = True
print_cmd_enabled = False

def str_red(s):
    return '\033[91m' + s + '\033[00m'

def str_blue(s):
    return '\033[94m' + s + '\033[00m'

def str_green(s):
    return '\033[92m' + s + '\033[00m'

def str_yellow(s):
    return '\033[93m' + s + '\033[00m'

def str_magenta(s):
    return '\033[95m' + s + '\033[00m'

def str_bold(s):
    return '\033[1m' + s + '\033[00m'

def print_action(atype, lead, msg):
    if lead.strip() != '':
        print(str_blue(lead) + ' ' + msg)
    else:
        print(lead + ' ' + msg)

def print_title(msg):
    print(str_bold(msg))
    
def print_info(lead, msg):
    print(str_blue('[' + lead + ']') + ' ' + msg)
    
def print_action(lead, msg):
    print(str_green('[' + lead + ']') + ' ' + msg)
    
def print_compile(lead, msg):
    print(str_yellow('[' + lead + ']') + ' ' + msg)
    
def print_link(lead, msg):
    print(str_magenta('[' + lead + ']') + ' ' + msg)

def print_archive(lead, msg):
    print(str_magenta('[' + lead + ']') + ' ' + msg)
    
def print_fail(msg):
    print(str_red('[fail]') + ' ' + msg)
    
def print_pad(count):
    print('\n' * (count - 1))
    
def print_cmd(msg):
    if print_cmd_enabled:
        print(msg)
        
def verify_cmd(code):
    if code != 0:
        print_fail('Error building the target')
        quit()

#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Build database
#-------------------------------------------------------------------------------

class bdb_entry:
    def __init__(self, name, checksum, mtime):
        self.name = name
        self.mtime = mtime if mtime != None else '{0}'.format(time.ctime(os.path.getmtime(name)))
        self.checksum = checksum if checksum != None else hashfile(name)
    
    def update(self):
        self.mtime = '{0}'.format(time.ctime(os.path.getmtime(self.name)))
        self.checksum = hashfile(self.name)
    
    def check(self):
        new_mtime = '{0}'.format(time.ctime(os.path.getmtime(self.name)))
        new_hash = hashfile(self.name)
        return new_mtime == self.mtime and new_hash == self.checksum
    
    def to_string(self):
        return self.name + ';' + self.checksum + ';' + self.mtime
        
bdb_name = target_dir + 'bdb'
bdb = {}

def parse_bdb_entry(s):
    parts = split(l.strip(), ';')
    name = parts[0].strip()
    checksum = parts[1].strip()
    mtime = parts[2].strip()
    
    return bdb_entry(name, checksum, mtime)

def load_bdb():
    global bdb_name
    global bdb
    
    bdb_file = open(bdb_name, 'r')
    for l in bdb_file:
        entry = parse_bdb_entry(l)
        bdb[entry.name] = entry
        
    bdb_file.close()
        
def check_bdb(file_name):
    if file_name in bdb:
        return bdb[file_name].check()
    return False
    
def update_bdb(file_name):
    if file_name in bdb:
        bdb[file_name].update()
    else:
        bdb[file_name] = bdb_entry(file_name)
    
def save_bdb():
    global bdb_name
    global bdb
    
    bdb_file = open(bdb_name, 'w')
    for e in sorted(bdb):
        f.write(e.to_string())
    
    bdb_file.close()

#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Build tools
#-------------------------------------------------------------------------------

def get_tool(src_name, tools):
    global arch_tools
    global_tools = arch_tools['global_tools']
    
    ext = get_ext(src_name)
    
    # Check tool ext
    if tools != None and ext in tools:
        return tools[ext]
    if global_tools != None and ext in global_tools:
        return global_tools[ext]
    
    # Check tool name
    if tools != None and src_name in tools:
        return tools[src_name]
    if global_tools != None and src_name in global_tools:
        return global_tools[src_name]
    
    return {}
#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Dependancy
#-------------------------------------------------------------------------------

# File modification time
class file_record:
    def __init__(self, name):
        self.name = name
        self.mtime = os.path.getmtime(name)
        
all_file_records = {}

def record_update(name):
    all_file_records[name] = file_record(name)
    
def record_remove(name):
    if name in all_file_records:
        del all_file_records[name]

def record_file(name):
    if name in all_file_records:
        return
    record_update(name)
    
def need_build(src_name, obj_name):
    if not src_name in all_file_records:
        record_file(src_name)
        
    if not os.path.exists(obj_name):
        return True
    if not obj_name in all_file_records:
        record_file(obj_name)
        
    src_mtime = all_file_records[src_name].mtime
    obj_mtime = all_file_records[obj_name].mtime
    
    result = src_mtime > obj_mtime
    return result

# File dependancy
class dep_record:
    def __init__(self, name, dep_list):
        self.name = name
        self.dep_list = dep_list

all_src_deps = {}

def get_src_dep_list(src_name, obj_name, tools):
    if print_cmd_enabled:
        lead = 'dep.' + get_filename(src_name)
        print_compile(lead, relative_path(src_name))
    
    # Get the tool
    tool = get_tool(src_name, tools)
    
    # Create dir
    obj_dir = get_dir(obj_name)
    if not os.path.exists(obj_dir):
        os.makedirs(obj_dir)
    
    # Compose the cmd line
    inc_cmd = ''
    for inc in inc_dir:
        inc_cmd += ' ' + tool['inc'].replace('__inc__', inc)
        
    dep_file = obj_name + '.d'
    dep_cmd = tool['dep'].replace('__src__', src_name).replace('__dep__', dep_file)
    
    cmd = tool['exec'] + ' ' + inc_cmd + ' ' + dep_cmd
    print_cmd(cmd)
    
    # Execute the cmd to get the dep file list
    code = exec_cmd(cmd)
    verify_cmd(code)
    assert(code == 0)
    
    # Read the dep file
    dep_list = []
    d = open(dep_file, 'r')
    for l in d:
        parts = split(l.strip(), ' ')
        for p in parts:
            if not ':' in p.strip() and '\\' != p.strip():
                dep_list.append(p.strip())
    
    d.close()
    
    return dep_list
    
def record_dep(src_name, obj_name, tools):
    dep_list = get_src_dep_list(src_name, obj_name, tools)
    all_src_deps[src_name] = dep_record(src_name, dep_list)

def get_dep_list(src_name, obj_name, ext_dep, tools):
    if not src_name in all_src_deps:
        record_dep(src_name, obj_name, tools)
        
    # Get src dep list
    dep_list = all_src_deps[src_name].dep_list
    
    # Append the external deps
    if ext_dep != None:
        dep_list += ext_dep
    
    return dep_list

# High level interface
def get_obj_name(src_name):
    obj_name = ''
    
    replace_paths = [ src_dir, tools_src_dir ] + arch_dirs + mach_dirs + \
        [ d + m + '/' for d in arch_dirs for m in mach_names ]
    replace_paths = sorted(replace_paths, key=len, reverse=True)
    
    found = False
    for path in replace_paths:
        if path in src_name:
            obj_name = src_name.replace(path, obj_dir)
            found = True
            break
    
    if not found:
        panic('Unsupported src file path: ' + src_name)
    
    obj_name += '.o'
    return obj_name

def check_dep_changed(files, target_name, ext_dep, tools):
    for f in files:
        src_name = f
        obj_name = get_obj_name(f)
        
        dep_list = get_dep_list(src_name, obj_name, ext_dep, tools)
        
        for d in dep_list:
            if need_build(d, target_name):
                return True
            
    return False

#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Internal build
#-------------------------------------------------------------------------------

#
# Compile
#
def compile_file(src_name, obj_name, dep_list, ext_flags, tools):
    global inc_dir
    
    # Print the action
    lead = get_filename(src_name)
    print_compile(lead, 'Compile: ' + relative_path(src_name) + ' -> ' + relative_path(obj_name))
    
    # Get the tool
    ext = get_ext(src_name)
    tool = get_tool(src_name, tools)
    
    # Compose the cmd line
    inc_cmd = ''
    for inc in inc_dir:
        inc_cmd += ' ' + tool['inc'].replace('__inc__', inc)
        
    obj_cmd = tool['obj'].replace('__src__', src_name).replace('__obj__', obj_name)
    
    ext_cmd = ''
    if ext_flags != None and ext + '/ext' in ext_flags:
        ext_cmd = ext_flags[ext + '/ext']
    
    cmd = tool['exec'] + ' ' + tool['flags'] + ' ' + inc_cmd + ' ' + ext_cmd + ' ' + obj_cmd
    print_cmd(cmd)
    
    # Execute the cmd to compile
    code = exec_cmd(cmd)
    verify_cmd(code)
    assert(code == 0)
    
    # Update record
    record_update(obj_name)
    
def auto_compile_file(src_name, obj_name, ext_dep, ext_flags, tools):
    obj_dir = get_dir(obj_name)
    if not os.path.exists(obj_dir):
        os.makedirs(obj_dir)
    
    dep_list = get_dep_list(src_name, obj_name, ext_dep, tools)
    check_files = [ src_name ] + dep_list
    
    recompile = False
    for f in check_files:
        if need_build(f, obj_name):
            recompile = True
            break
    
    if recompile:
        compile_file(src_name, obj_name, dep_list, ext_flags, tools)
    
def auto_compile(files, ext_dep, ext_flags, tools):
    objs = []
    
    for f in files:
        src_name = f
        obj_name = get_obj_name(f)
        
        objs.append(obj_name)
        auto_compile_file(src_name, obj_name, ext_dep, ext_flags, tools)
    
    return objs

#
# Link and Build
#
def auto_link_target(obj_files, target_name, ext_libs, ext_flags, tools):
    lead = get_filename(target_name)
    print_link(lead, 'Link: ' + relative_path(target_name))
    
    # Get the tool
    tool = get_tool('ld', tools)
    
    # Compose the cmd line
    obj_cmd = ''
    if ext_libs != None:
        for obj in ext_libs:
            obj_cmd += ' ' + obj
    for obj in obj_files:
        obj_cmd += ' ' + obj
    if ext_libs != None:
        for obj in ext_libs:
            obj_cmd += ' ' + obj
    obj_cmd = tool['obj'].replace('__target__', target_name).replace('__obj__', obj_cmd)
    
    script_cmd = ''
    if ext_flags != None and 'ld/script' in ext_flags:
        script_cmd = tool['script'].replace('__script__', ext_flags['ld/script'])
        
    ext_cmd = ''
    if ext_flags != None and 'ld/ext' in ext_flags:
        ext_cmd = ext_flags['ld/ext']
    
    cmd = tool['exec'] + ' ' + tool['flags'] + ' ' + ext_cmd + ' ' + script_cmd + ' ' + obj_cmd
    print_cmd(cmd)
    
    # Execute the cmd to compile
    code = exec_cmd(cmd)
    verify_cmd(code)
    assert(code == 0)
    
    # Update record
    record_update(target_name)
    
def auto_link(obj_files, target_name, ext_dep, ext_libs, ext_flags, tools):
    dep_list = combine_lists([obj_files, ext_dep, ext_libs])
        
    relink = False
    for obj in dep_list:
        if need_build(obj, target_name):
            relink = True
            break
    
    if relink:
        auto_link_target(obj_files, target_name, ext_libs, ext_flags, tools)

def auto_build(files, target_name, ext_dep, ext_libs, ext_flags, tools):
    # Compile every file
    objs = auto_compile(files, ext_dep, ext_flags, tools)
    
    # Link the obj files
    auto_link(objs, target_name, ext_dep, ext_libs, ext_flags, tools)

#
# Archive and Lib
#
def auto_archive_target(obj_files, target_name, ext_libs, ext_flags, tools):
    lead = get_filename(target_name)
    print_archive(lead, 'Archive: ' + relative_path(target_name))
    
    # Get the tool
    tool = get_tool('ar', tools)
    
    # Compose the cmd line
    obj_cmd = ''
    for obj in obj_files:
        obj_cmd += ' ' + obj
    if ext_libs != None:
        for obj in ext_libs:
            obj_cmd += ' ' + obj
    obj_cmd = tool['obj'].replace('__target__', target_name).replace('__obj__', obj_cmd)
        
    ext_cmd = ''
    if ext_flags != None and 'ar/ext' in ext_flags:
        ext_cmd = ext_flags['ar/ext']
    
    cmd = tool['exec'] + ' ' + tool['flags'] + ' ' + ext_cmd + ' ' + obj_cmd
    print_cmd(cmd)
    
    # Execute the cmd to compile
    code = exec_cmd(cmd)
    verify_cmd(code)
    assert(code == 0)
    
    # Update record
    record_update(target_name)

def auto_archive(obj_files, target_name, ext_dep, ext_libs, ext_flags, tools):
    dep_list = combine_lists([obj_files, ext_dep, ext_libs])
    
    rearchive = False
    for obj in dep_list:
        if need_build(obj, target_name):
            rearchive = True
            break
    
    if rearchive:
        auto_archive_target(obj_files, target_name, ext_libs, ext_flags, tools)

def auto_lib(files, target_name, ext_dep, ext_libs, ext_flags, tools):
    # Compile every file
    objs = auto_compile(files, ext_dep, ext_flags, tools)
    
    # Archive the obj files
    auto_archive(objs, target_name, ext_dep, ext_libs, ext_flags, tools)

#
# Direct build
#
def direct_build(files, target_name, ext_libs, tools):
    lead = get_filename(target_name)
    print_compile(lead, 'Direct Build: ' + relative_path(target_name))
    
    ext = get_ext(files[0])
    
    # Get the tool
    tool = get_tool(files[0], tools)
    
    # Compose the cmd line
    inc_cmd = ''
    for inc in inc_dir:
        inc_cmd += ' ' + tool['inc'].replace('__inc__', inc)
        
    src_name = ''
    for f in files:
        assert(ext == get_ext(f))
        src_name += ' ' + tool['direct'].replace('__src__', f)
    if ext_libs != None:
        for f in ext_libs:
            src_name += ' ' + tool['direct'].replace('__src__', f)
        
    obj_cmd = tool['obj'].replace('__src__', src_name).replace('__obj__', target_name)
    
    cmd = tool['exec'] + ' ' + tool['flags'] + ' ' + inc_cmd + ' ' + obj_cmd
    print_cmd(cmd)
    
    # Execute the cmd to compile
    code = exec_cmd(cmd)
    verify_cmd(code)
    assert(code == 0)
    
    # Update record
    record_update(target_name)
    
def auto_direct_build(files, target_name, ext_dep, ext_libs, tools):
    rebuild = False
    for f in files:
        obj_name = get_obj_name(f)
        target_dir = get_dir(obj_name)
        if not os.path.exists(obj_name):
            os.makedirs(obj_name)
            
        ext_list = combine_lists([ext_dep, ext_libs])
        dep_list = [ f ] + get_dep_list(f, obj_name, ext_list, tools)
        for d in dep_list:
            if need_build(d, target_name):
                rebuild = True
                break
        if rebuild:
            break
        
    if rebuild:
        direct_build(files, target_name, ext_libs, tools)
    
#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Public interface for build
#-------------------------------------------------------------------------------

def combine_lists(lists):
    r = []
    for l in lists:
        if l == None:
            continue
        for e in l:
            if not e in r:
                r.append(e)
    return r

def build_files(files, target_name, ext_dep = None, ext_libs = None, ext_flags = None, tools = None):
    if not check_dep_changed(files, target_name, combine_lists([ext_dep, ext_libs]), tools):
        return
    
    target_dir = get_dir(target_name)
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
    
    auto_build(files, target_name, ext_dep, ext_libs, ext_flags, tools)

def build_dir(dir_name, ext, target_name, ext_dep = None, ext_libs = None, ext_flags = None, tools = None):
    files = get_all_files(dir_name, ext)
    build_files(files, target_name, ext_dep, ext_libs, ext_flags, tools)

def direct_build_files(files, target_name, ext_dep = None, ext_libs = None, tools = None):
    target_dir = get_dir(target_name)
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
        
    auto_direct_build(files, target_name, ext_dep, ext_libs, tools)

def direct_build_dir(dir_name, ext, target_name, ext_dep = None, tools = None):
    files = get_all_files(dir_name, ext)
    direct_build_files(files, target_name, ext_dep, tools)

def lib_files(files, target_name, ext_dep = None, ext_libs = None, ext_flags = None, tools = None):
    if not check_dep_changed(files, target_name, combine_lists([ext_dep, ext_libs]), tools):
        return
    
    target_dir = get_dir(target_name)
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
    
    auto_lib(files, target_name, ext_dep, ext_libs, ext_flags, tools)
    
def lib_dir(dir_name, ext, target_name, ext_dep = None, ext_libs = None, ext_flags = None, tools = None):
    files = get_all_files(dir_name, ext)
    lib_files(files, target_name, ext_dep, ext_libs, ext_flags, tools)
    
def compile_dir(dir_name, ext = None, ext_dep = None, tools = None):
    files = get_all_files(dir_name, ext)
    objs = auto_compile(files, tools)
    return objs

def compile_files(files, ext_dep = None, tools = None):
    objs = auto_compile(files, tools)
    return objs

#def link_files(files, target_name, ext_dep = None, ext_libs = None, ext_flags = None, tools = None):
    #auto_link_target(files, target_name, tools)
    
#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Public interface for other supportive operations
#-------------------------------------------------------------------------------

def get_all_arch_and_mach_files(subdir, ext):
    files = []
    
    # Standalone arch files
    for d in arch_dirs:
        files += get_all_files(d + subdir, ext)
        
        # Arch-specific machine files
        for m in mach_names:
            files += get_all_files(d + m + '/' + subdir, ext)
    
    # Standalone machine files
    for d in mach_dirs:
        files += get_all_files(d + subdir, ext)
    
    return files

def find_deepest_arch_file(match):
    for d in arch_dirs:
        for m in mach_names:
            fname = d + m + '/' + match
            if os.path.exists(fname):
                return fname
        
        fname = d + match
        if os.path.exists(fname):
            return fname
    
    return None

def gen_bin(src_name, target_name, tools):
    if not need_build(src_name, target_name):
        return
    
    lead = get_filename(target_name)
    print_action(lead, 'Generate binary: ' + relative_path(src_name) + ' -> ' + relative_path(target_name))
    
    target_dir = get_dir(target_name)
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
    
    # Get the tool
    tool = get_tool('gen_bin', tools)
    
    # Compose the cmd
    obj_cmd = tool['obj'].replace('__src__', src_name).replace('__target__', target_name)
    cmd = tool['exec'] + ' ' + tool['flags'] + ' ' + obj_cmd
    print_cmd(cmd)
    
    # Execute the cmd
    code = exec_cmd(cmd)
    verify_cmd(code)
    assert(code == 0)
    
    # Update record
    record_update(target_name)
    
def strip_target(target_name, tools):
    lead = get_filename(target_name)
    print_action(lead, 'Strip: ' + relative_path(target_name))
    
    # Get the tool
    tool = get_tool('strip', tools)
    
    # Compose the cmd
    cmd = tool['exec'] + ' ' + tool['flags'] + ' ' + tool['obj'].replace('__target__', target_name)
    print_cmd(cmd)
    
    # Execute the cmd
    code = exec_cmd(cmd)
    verify_cmd(code)
    assert(code == 0)
    
    # Update record
    record_update(target_name)

def cat_files(files, target_name, tools = None):
    recat = False
    for f in files:
        if need_build(f, target_name):
            recat = True
            break
    if not recat:
        return
    
    lead = get_filename(target_name)
    print_action(lead, 'Concatinate: ' + relative_path(target_name))
    
    target_dir = get_dir(target_name)
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
    
    # Compose the cmd
    cmd = 'cat'
    for f in files:
        cmd += ' ' + f
    cmd += ' > ' + target_name
    print_cmd(cmd)
    
    # Execute the cmd
    code = exec_cmd(cmd)
    verify_cmd(code)
    assert(code == 0)
    
    # Update record
    record_update(target_name)
        
def remove_files(files, tools = None):
    rel_files = []
    for f in files:
        rel_files.append(relative_path(f))
    print_action('remove', str(rel_files))
    
    # Compose the cmd
    cmd = 'rm'
    for f in files:
        cmd += ' ' + f
    print_cmd(cmd)
    
    # Execute thte cmd
    code = exec_cmd(cmd)
    verify_cmd(code)
    assert(code == 0)
    
    # Update record
    for f in files:
        record_remove(f)

def stats_dir(dir_name, ext):
    files = get_all_files(dir_name, ext)
    
    ext_list = []
    stats_files = {}
    stats_lines = {}

    for f in files:
        fext = get_ext(f)
        if 'tmake' in f:
            fext = 'tmake'
        if fext == '':
            fext = 'empty'
        
        if not fext in ext_list:
            ext_list.append(fext)
            stats_files[fext] = 0
            stats_lines[fext] = 0
        lines = count_lines(f)
        
        stats_files[fext] += 1
        stats_lines[fext] += lines
    
    total_files = 0
    total_lines = 0
    
    print_action('stats', relative_path(dir_name))
    for fext in sorted(ext_list):
        print_info(fext, 'File count: {0}, line count: {1}'.format(stats_files[fext], stats_lines[fext]))
        
        total_files += stats_files[fext]
        total_lines += stats_lines[fext]
        
    print_info('total', 'File count: {0}, line count: {1}'.format(total_files, total_lines))
    
    return [ ext_list, stats_files, stats_lines ]

#-------------------------------------------------------------------------------


#-------------------------------------------------------------------------------
# Main
#-------------------------------------------------------------------------------

print_title('Toddler building system started')

# Parse arch command line
for argv in sys.argv:
    if 'arch' in argv:
        parts = split(argv, '=')
        target_arch = parts[1]
    elif 'machine' in argv:
        parts = split(argv, '=')
        target_mach = parts[1]
    elif 'suffix' in argv:
        parts = split(argv, '=')
        target_suffix = parts[1]

# Host and target info
print_info('host', 'Architecture: {0}, OS: {1}'.format(host_arch, host_os))
print_info('target', 'Architecture: {0}, machine: {1}, suffix: {2}'.format(
    target_arch, target_mach,
    'N/A' if target_suffix == '' else target_suffix)
)

# Include arch specific make file
arch_funcs = {}
arch_vars = {}
arch_tools = {}
include(project_dir + 'tmake.main')
include(tools_src_dir + 'tmake.tools')
include(arch_dir + 'tmake.arch')


# Parse action cmd line
supported_actions = [ 'all', 'build', 'emu', 'stats', 'clean', 'clean_all' ]
actions = []

for argv in sys.argv:
    if 'action' in argv:
        parts = split(argv, '=')
        actions = split(parts[1], ',')
    elif not '=' in argv:
        if ',' in argv:
            parts = split(argv, ',')
        else:
            parts = [ argv ]
        for p in parts:
            if p in supported_actions or p in arch_funcs:
                if p in actions:
                    actions.remove(p)
                actions.append(p)

if not len(actions):
    actions = [ 'all' ]

if 'all' in actions and 'build' in actions and 'emu' in actions:
    actions.remove('build')
    actions.remove('emu')

# Build
if 'build' in actions or 'all' in actions:
    print_title('Start building Toddler')

    # Build arch specific targets
    if 'build_arch' in arch_funcs:
        arch_funcs['build_arch']()

    # Build main components
    if 'build_main' in arch_funcs:
        arch_funcs['build_main']()

    # Build tools
    if 'build_tools' in arch_funcs:
        arch_funcs['build_tools']()

    # Build core image
    if 'build_coreimg' in arch_funcs:
        arch_funcs['build_coreimg']()

    # Build disk image
    if 'build_disk' in arch_funcs:
        arch_funcs['build_disk']()

    # Remove action
    if 'build' in actions:
        actions.remove('build')

# Emulator
if 'emu' in actions or 'all' in actions:
    print_title('Start emulator')

    # Start emulator
    if 'start_emu' in arch_funcs:
        arch_funcs['start_emu']()
        
    # Remove action
    if 'emu' in actions:
        actions.remove('emu')
        
# Remove 'all' action
if 'all' in actions:
    actions.remove('all')
    
# Lines of code
if 'stats' in actions:
    print_title('Collect source code statistics')
    
    global_ext_list = []
    global_files = {}
    global_lines = {}
    
    dir_list = [ doc_dir, tools_src_dir, src_dir ]
    
    for pdir in dir_list:
        subdirs = get_subdir(pdir)
        for i in range(len(subdirs)):
            subdirs[i] = pdir + subdirs[i] + '/'
        subdirs.append(pdir)
        
        for dir in subdirs:
            ret = stats_dir(dir, None)
            
            if dir != pdir:
                continue;
            
            ext_list = ret[0]
            stats_files = ret[1]
            stats_lines = ret[2]
            
            for fext in ext_list:
                if not fext in global_ext_list:
                    global_ext_list.append(fext)
                    global_files[fext] = 0
                    global_lines[fext] = 0
                    
                global_files[fext] += stats_files[fext]
                global_lines[fext] += stats_lines[fext]
    
    total_files = 0
    total_lines = 0
    
    print_action('stats', 'Aggregate')
    for fext in sorted(ext_list):
        print_info(fext, 'File count: {0}, line count: {1}'.format(global_files[fext], global_lines[fext]))
        
        total_files += global_files[fext]
        total_lines += global_lines[fext]
        
    print_info('total', 'File count: {0}, line count: {1}'.format(total_files, total_lines))
    
    actions.remove('stats')
    
# Clean
if 'clean' in actions:
    print_title('Cleaning target: ' + target_name)
    
    print_info('remove', target_dir)
    shutil.rmtree(target_dir, ignore_errors = True)
    actions.remove('clean')

# Clean all
if 'clean_all' in actions:
    print_title('Cleaning all')
    
    print_info('remove', target_all_dir)
    shutil.rmtree(target_all_dir, ignore_errors = True)
    actions.remove('clean_all')

# Other actions
for act in actions:
    if act in arch_funcs:
        print_title('Run action: ' + act)
        arch_funcs[act]()

print_title('Toddler building system completed')

#-------------------------------------------------------------------------------
