# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

'''Expandlibs is a system that allows to replace some libraries with a
descriptor file containing some linking information about them.

The descriptor file format is as follows:
---8<-----
OBJS = a.o b.o ...
LIBS = libfoo.a libbar.a ...
--->8-----

(In the example above, OBJ_SUFFIX is o and LIB_SUFFIX is a).

Expandlibs also canonicalizes how to pass libraries to the linker, such
that only the ${LIB_PREFIX}${ROOT}.${LIB_SUFFIX} form needs to be used:
given a list of files, expandlibs will replace items with the form
${LIB_PREFIX}${ROOT}.${LIB_SUFFIX} following these rules:

- If a ${DLL_PREFIX}${ROOT}.${DLL_SUFFIX} or
  ${DLL_PREFIX}${ROOT}.${IMPORT_LIB_SUFFIX} file exists, use that instead
- If the ${LIB_PREFIX}${ROOT}.${LIB_SUFFIX} file exists, use it
- If a ${LIB_PREFIX}${ROOT}.${LIB_SUFFIX}.${LIB_DESC_SUFFIX} file exists,
  replace ${LIB_PREFIX}${ROOT}.${LIB_SUFFIX} with the OBJS and LIBS the
  descriptor contains. And for each of these LIBS, also apply the same
  rules.
'''
from __future__ import with_statement
import sys, os, errno
import expandlibs_config as conf

def ensureParentDir(file):
    '''Ensures the directory parent to the given file exists'''
    dir = os.path.dirname(file)
    if dir and not os.path.exists(dir):
        try:
            os.makedirs(dir)
        except OSError, error:
            if error.errno != errno.EEXIST:
                raise

def relativize(path):
    '''Returns a path relative to the current working directory, if it is
    shorter than the given path'''
    def splitpath(path):
        dir, file = os.path.split(path)
        if os.path.splitdrive(dir)[1] == os.sep:
            return [file]
        return splitpath(dir) + [file]

    if not os.path.exists(path):
        return path
    curdir = splitpath(os.path.abspath(os.curdir))
    abspath = splitpath(os.path.abspath(path))
    while curdir and abspath and curdir[0] == abspath[0]:
        del curdir[0]
        del abspath[0]
    if not curdir and not abspath:
        return '.'
    relpath = os.path.join(*[os.pardir for i in curdir] + abspath)
    if len(path) > len(relpath):
        return relpath
    return path

def isObject(path):
    '''Returns whether the given path points to an object file, that is,
    ends with OBJ_SUFFIX or .i_o'''
    return os.path.splitext(path)[1] in [conf.OBJ_SUFFIX, '.i_o']

def isDynamicLib(path):
    '''Returns whether the given path points to a dynamic library, that is,
    ends with DLL_SUFFIX.'''
    # On mac, the xul library is named XUL, instead of libxul.dylib. Assume any
    # file by that name is a dynamic library.
    return os.path.splitext(path)[1] == conf.DLL_SUFFIX or os.path.basename(path) == 'XUL'

class LibDescriptor(dict):
    KEYS = ['OBJS', 'LIBS']

    def __init__(self, content=None):
        '''Creates an instance of a lib descriptor, initialized with contents
        from a list of strings when given. This is intended for use with
        file.readlines()'''
        if isinstance(content, list) and all([isinstance(item, str) for item in content]):
            pass
        elif content is not None:
            raise TypeError("LibDescriptor() arg 1 must be None or a list of strings")
        super(LibDescriptor, self).__init__()
        for key in self.KEYS:
            self[key] = []
        if not content:
            return
        for key, value in [(s.strip() for s in item.split('=', 2)) for item in content if item.find('=') >= 0]:
            if key in self.KEYS:
                self[key] = value.split()

    def __str__(self):
        '''Serializes the lib descriptor'''
        return '\n'.join('%s = %s' % (k, ' '.join(self[k])) for k in self.KEYS if len(self[k]))

class ExpandArgs(list):
    def __init__(self, args):
        '''Creates a clone of the |args| list and performs file expansion on
        each item it contains'''
        super(ExpandArgs, self).__init__()
        self._descs = set()
        for arg in args:
            self += self._expand(arg)

    def _expand(self, arg):
        '''Internal function doing the actual work'''
        (root, ext) = os.path.splitext(arg)
        if ext != conf.LIB_SUFFIX or not os.path.basename(root).startswith(conf.LIB_PREFIX):
            return [relativize(arg)]
        if conf.LIB_PREFIX:
            dll = root.replace(conf.LIB_PREFIX, conf.DLL_PREFIX, 1) + conf.DLL_SUFFIX
        else:
            dll = root + conf.DLL_SUFFIX
        if os.path.exists(dll):
            if conf.IMPORT_LIB_SUFFIX:
                return [relativize(root + conf.IMPORT_LIB_SUFFIX)]
            else:
                return [relativize(dll)]
        return self._expand_desc(arg)

    def _expand_desc(self, arg):
        '''Internal function taking care of lib descriptor expansion only'''
        desc = os.path.abspath(arg + conf.LIBS_DESC_SUFFIX)
        if os.path.exists(desc):
            if desc in self._descs:
                return []
            self._descs.add(desc)
            with open(desc, 'r') as f:
                desc = LibDescriptor(f.readlines())
            objs = [relativize(o) for o in desc['OBJS']]
            for lib in desc['LIBS']:
                objs += self._expand(lib)
            return objs
        return [relativize(arg)]

if __name__ == '__main__':
    print " ".join(ExpandArgs(sys.argv[1:]))
