#!/usr/bin/env python

# This is a helper script for importing the things we use from LLVM.
#
# NOTE: We have slightly modified copies of some of the files, to reduce the
# dependency surface area, so if you run this to update the sources, please
# examine the diffs to remove things that don't make sense.

from __future__ import print_function
import errno
import optparse
import os
import shutil
import sys

# ADT types.
ADT_imports = ['iterator', 'iterator_range', 'APFloat', 'APInt', 'APSInt',
               'AllocatorList', 'EpochTracker', 'Hashing', 'None', 'Optional',
               'StringExtras', 'STLExtras']

# ADT basic data structures.
ADT_imports += ['ArrayRef', 'PointerIntPair', 'PointerUnion', 'StringRef',
                'StringSwitch', 'Triple', 'Twine', 'IntrusiveRefCntPtr',
                'ilist', 'ilist_base', 'ilist_node', 'ilist_node_base',
                'ilist_node_options', 'simple_ilist', 'ilist_iterator']

# ADT Mapping structures.
ADT_imports += ['DenseSet', 'DenseMap', 'DenseMapInfo', 'FoldingSet', 'StringMap']

# ADT "Small" structures.
ADT_imports += ['SmallPtrSet', 'SmallSet', 'SmallString', 'SmallVector',]

# ADT Algorithms.
ADT_imports += ['edit_distance']


Demangle_imports = ['Demangle']

# Support types and infrastructure.
Support_imports = [ 'AlignOf', 'Allocator', 'Atomic', 'CBindingWrapping',
                    'Casting', 'Compiler', 'CommandLine', 'ConvertUTF',
                    'ConvertUTFWrapper', 'DJB', 'Endian', 'Errno', 'Error',
                    'ErrorHandling', 'Errc', 'ErrorOr', 'Format',
                    'ManagedStatic', 'MathExtras', 'MD5', 'Mutex',
                    'MutexGuard', 'Memory', 'MemoryBuffer',
                    'PointerLikeTypeTraits', 'Recycler', 'ReverseIteration',
                    'SmallVectorMemoryBuffer', 'StringSaver', 'SwapByteOrder',
                    'TimeValue', 'Threading', 'Unicode', 'UniqueLock', 'Unix',
                    'WindowsError', 'WindowsSupport', 'Valgrind',
                    'circular_raw_ostream', 'raw_ostream', 'type_traits']

# Stuff we don't want, but have to pull in.
Support_imports += [ 'APFloat', 'ARMTargetParser', 'ARMBuildAttributes',
                     'AArch64TargetParser', 'Chrono', 'DataTypes', 'Debug',
                     'FileSystem', 'FileUtilities', 'FormatCommon',
                     'FormatProviders',
                     'FormatVariadic','FormatVariadicDetails', 'Host',
                     'Locale', 'MemAlloc', 'NativeFormatting', 'Options',
                     'Path', 'Process', 'Program', 'SMLoc', 'SourceMgr',
                     'Signals', 'Support', 'TargetParser', 'Types', 'Unicode',
                     'UnicodeCaseFold', 'UnicodeCharRanges', 'X86TargetParser']

# Support data structures.
Support_imports += ['YAMLParser']

# Source files to exclude.
Support_source_excludes = set()

llvm_srcroot = None
llbuild_srcroot = None

def note(msg):
    msg = msg.replace(llvm_srcroot, "<LLVM>")
    msg = msg.replace(llbuild_srcroot, "<LLBUILD>")
    print("note: %s" % (msg,), file=sys.stderr)

def mkdir_p(path):
    try:
        os.makedirs(path)
        note("mkdir -p %r" % (path,))
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise

def copyfile(src, dst):
    #note("cp %r %r" % (src, dst))
    shutil.copyfile(src, dst)

def main():
    parser = optparse.OptionParser("usage: %prog <llvm-source-path>")
    (opts, args) = parser.parse_args()

    if len(args) != 1:
        parser.error("unexpected number of arguments")

    global llvm_srcroot, llbuild_srcroot
    llvm_srcroot, = args
    llbuild_srcroot = os.path.dirname(
        os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

    # Remove the old llvm import
    llbuild_llvm_include = os.path.join(llbuild_srcroot, 'include', 'llvm')
    llbuild_llvm_lib = os.path.join(llbuild_srcroot, 'lib', 'llvm')
    shutil.rmtree(llbuild_llvm_include)
    shutil.rmtree(llbuild_llvm_lib)

    # Add the platform specific config files
    #
    # NOTE: If you're updating to a newer llvm, you may have to manually merge
    # these with the newer version produced by the llvm build
    llbuild_llvm_config = os.path.join(llbuild_srcroot, 'utils', 'import-llvm', 'include', 'llvm', 'Config')
    shutil.copytree(llbuild_llvm_config, os.path.join(llbuild_llvm_include, 'Config'))

    def import_header(dir, name):
        src = os.path.join(llvm_srcroot, 'include', 'llvm', dir, name)
        c_src = os.path.join(llvm_srcroot, 'include', 'llvm-c', name)
        if os.path.exists(src):
            dst = os.path.join(llbuild_srcroot, 'include', 'llvm', dir, name)
            mkdir_p(os.path.dirname(dst))
            copyfile(src, dst)
        if os.path.exists(c_src):
            dst = os.path.join(llbuild_srcroot, 'include', 'llvm-c', name)
            mkdir_p(os.path.dirname(dst))
            copyfile(c_src, dst)

    def import_source(dir, name):
        src = os.path.join(llvm_srcroot, 'lib', dir, name)
        if os.path.exists(src):
            dst = os.path.join(llbuild_srcroot, 'lib', 'llvm', dir, name)
            mkdir_p(os.path.dirname(dst))
            copyfile(src, dst)

    print("note: importing from %r to %r" % (llvm_srcroot, llbuild_srcroot))

    for name in ADT_imports:
        import_header('ADT', name+'.h')
        if name not in Support_source_excludes:
            import_source('Support', name+'.c')
            import_source('Support', name+'.cpp')
    for name in Support_imports:
        import_header('Support', name+'.h')
        import_header('Support', name+'.def')
        if name not in Support_source_excludes:
            import_source('Support', name+'.c')
            import_source('Support', name+'.cpp')
            import_source('Support', os.path.join('Unix', name+'.h'))
            import_source('Support', os.path.join('Unix', name+'.inc'))
            import_source('Support', os.path.join('Windows', name+'.h'))
            import_source('Support', os.path.join('Windows', name+'.inc'))
    for name in Demangle_imports:
        import_header('Demangle', name+'.h')
        import_source('Demangle', name+'.cpp')

    # Copy over all of demangle
    demangle_src = os.path.join(llvm_srcroot, 'lib', 'Demangle')
    demangle_dest = os.path.join(llbuild_srcroot, 'lib', 'llvm', 'Demangle')
    shutil.copytree(demangle_src, demangle_dest)

    # Add in the CMakeLists
    llbuild_cmake_root = os.path.join(llbuild_srcroot, 'utils', 'import-llvm', 'cmake')
    shutil.copy(os.path.join(llbuild_cmake_root, 'llvmCMakeLists.txt'),
                os.path.join(llbuild_llvm_lib, 'CMakeLists.txt'))
    shutil.copy(os.path.join(llbuild_cmake_root, 'demangleCMakeLists.txt'),
                os.path.join(llbuild_llvm_lib, 'Demangle', 'CMakeLists.txt'))
    shutil.copy(os.path.join(llbuild_cmake_root, 'supportCMakeLists.txt'),
                os.path.join(llbuild_llvm_lib, 'Support', 'CMakeLists.txt'))

    # Create Symlinks for SwiftPM include directory resolution
    os.makedirs(os.path.join(llbuild_llvm_lib, 'Support', 'include', 'llvm'))
    os.makedirs(os.path.join(llbuild_llvm_lib, 'Demangle', 'include'))
    os.makedirs(os.path.join(llbuild_llvm_include, 'Demangle', 'include'))
    os.symlink(os.path.join('..', '..', '..', '..', '..', 'include', 'llvm', 'ADT'),
               os.path.join(llbuild_llvm_lib, 'Support', 'include', 'llvm', 'ADT'))
    os.symlink(os.path.join('..', '..', '..', '..', '..', 'include', 'llvm', 'Config'),
               os.path.join(llbuild_llvm_lib, 'Support', 'include', 'llvm', 'Config'))
    os.symlink(os.path.join('..', '..', '..', '..', '..', 'include', 'llvm', 'Support'),
               os.path.join(llbuild_llvm_lib, 'Support', 'include', 'llvm', 'Support'))
    os.symlink(os.path.join('..', '..', '..', '..', 'include', 'llvm-c'),
               os.path.join(llbuild_llvm_lib, 'Support', 'include', 'llvm-c'))
    os.symlink(os.path.join('..', '..', '..', '..', 'include', 'llvm'),
               os.path.join(llbuild_llvm_lib, 'Demangle', 'include', 'llvm'))
    os.symlink(os.path.join('..', '..', '..', 'llvm'),
               os.path.join(llbuild_llvm_include, 'Demangle', 'include', 'llvm'))

if __name__ == '__main__':
    main()
