#!/usr/bin/env python
#
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
Test that we can build shared libraries and link against shared
libraries that have non-standard library prefixes and suffixes.
"""

import re
import sys
import TestSCons

test = TestSCons.TestSCons()

test.write('SConstruct', """
import sys
isCygwin = sys.platform == 'cygwin'
isWindows = sys.platform == 'win32'
isMingw = False
if isWindows:
    import SCons.Tool.MSCommon as msc
    if not msc.msvc_exists():
        # We can't seem to find any MSVC version, so we assume
        # that MinGW is installed instead. Accordingly, we use the
        # standard gcc/g++ conventions for lib prefixes and suffixes
        # in the following...
        isWindows = False
        isMingw = True

DefaultEnvironment(tools=[])  # test speedup
env = Environment()

# Make sure that the shared library can be located at runtime.
env.Append(RPATH=['.'])
env.Append(LIBPATH=['.'])

# We first bake the LIBSUFFIXES, so that it will not change as a
# side-effect of changing SHLIBSUFFIX.
env['LIBSUFFIXES'] = list(map( env.subst, env.get('LIBSUFFIXES', [])))

weird_prefixes = ['libXX', 'libYY']

if isWindows:
    weird_suffixes = ['.xxx', '.yyy', '.xxx.dll', '.yyy.dll']
    env.Append(CCFLAGS = '/MD')
elif env['PLATFORM'] == 'darwin':
    weird_suffixes = ['.xxx.dylib', '.yyy.dylib']
else:
    weird_suffixes = ['.xxx.so', '.yyy.so']

shlibprefix = env.subst('$SHLIBPREFIX')
shlibsuffix = env.subst('$SHLIBSUFFIX')

progprefix = env.subst('$PROGPREFIX')
progsuffix = env.subst('$PROGSUFFIX')

goo_obj = env.SharedObject(source='goo.c')
foo_obj = env.SharedObject(source='foo.c')
prog_obj = env.SharedObject(source='prog.c')

#
# The following functions define all the different ways that one can
# use to link against a shared library.
#
def nodeInSrc(source, lib, libname):
    return (source+lib, '')

def pathInSrc(source, lib, libname):
    return (source+list(map(str,lib)), '')

def nodeInLib(source, lib, libname):
    return (source, lib)

def pathInLib(source, lib, libname):
    return (source, list(map(str,lib)))

def nameInLib(source, lib, libname):
    # NOTE: libname must contain both the proper prefix and suffix.
    #
    # When using non-standard prefixes and suffixes, one has to
    # provide the full name of the library since scons can not know
    # which of the non-standard extension to use.
    #
    # Note that this is not necessarily SHLIBPREFIX and
    # SHLIBSUFFIX. These are the ixes of the target library, not the
    # ixes of the library that we are linking against.
    return (source, libname)

libmethods = [nodeInSrc, pathInSrc, nodeInLib, pathInLib]
# We skip the nameInLib test for MinGW and Cygwin...they would fail, due to
# the Tool's internal naming conventions
if not isMingw and not isCygwin:
    libmethods.extend([nameInLib])

def buildAndlinkAgainst(builder, target, source,  method, lib, libname, **kw):
    '''Build a target using a given builder while linking against a given
    library using a specified method for linking against the library.'''

    # On Windows, we have to link against the .lib file.
    if isWindows:
        for l in lib:
            if str(l)[-4:] == '.lib':
                lib = [l]
                break
    # If we use MinGW or Cygwin and create a SharedLibrary, we get two targets: a DLL,
    # and the import lib created by the "--out-implib" parameter. We always
    # want to link against the second one, in order to prevent naming issues
    # for the linker command line...
    if (isMingw or isCygwin) and len(lib) > 1:
        lib = lib[1:]

    # Apply the naming method to be tested and call the specified Builder.
    (source, LIBS) = method(source, lib, libname)
    #build = builder(target=target, source=source, LIBS=LIBS, **kw)
    kw = kw.copy()
    kw['target'] = target
    kw['source'] = source
    kw['LIBS'] = LIBS
    build = builder(**kw)

    # Check that the build target depends on at least one of the
    # library target.
    found_dep = False
    children = build[0].children()
    for l in lib:
        if l in children:
            found_dep = True
            break;
    assert found_dep, \
        "One of %s not found in %s, method=%s, libname=%s, shlibsuffix=%s" % \
        (list(map(str,lib)), list(map(str, build[0].children())), method.__name__, libname, shlibsuffix)
    return build

def prog(i,
         goomethod, goolibprefix, goolibsuffix,
         foomethod, foolibprefix, foolibsuffix):
    '''Build a program

     The program links against a shared library foo which itself links
     against a shared library goo. The libraries foo and goo can use
     arbitrary library prefixes and suffixes.'''

    goo_name =  goolibprefix+'goo'+str(i)+goolibsuffix
    foo_name =  foolibprefix+'foo'+str(i)+foolibsuffix
    prog_name = progprefix+'prog'+str(i)+progsuffix

    print('Prog: %d, %s, %s, %s' % (i, goo_name, foo_name, prog_name))

    # On Windows, we have to link against the .lib file.
    if isWindows:
        goo_libname =  goolibprefix+'goo'+str(i)+'.lib'
        foo_libname =  foolibprefix+'foo'+str(i)+'.lib'
    else:
        goo_libname =  goo_name
        foo_libname =  foo_name

    goo_lib = env.SharedLibrary(
        goo_name, goo_obj, SHLIBSUFFIX=goolibsuffix)
    foo_lib = buildAndlinkAgainst(
        env.SharedLibrary, foo_name, foo_obj,
        goomethod, goo_lib, goo_libname, SHLIBSUFFIX=foolibsuffix)
    prog = buildAndlinkAgainst(env.Program, prog_name, prog_obj,
        foomethod, foo_lib, foo_libname)


#
# Create the list of all possible permutations to test.
#
i = 0
tests = []
prefixes = [shlibprefix] +  weird_prefixes
suffixes = [shlibsuffix] +  weird_suffixes
for foolibprefix in prefixes:
    for foolibsuffix in suffixes:
        for foomethod in libmethods:
            for goolibprefix in prefixes:
                for goolibsuffix in suffixes:
                    for goomethod in libmethods:
                        tests.append(
                            (i,
                             goomethod, goolibprefix, goolibsuffix,
                             foomethod, foolibprefix, foolibsuffix))
                        i = i + 1

#
# Pseudo-randomly choose 200 tests to run out of the possible
# tests. (Testing every possible permutation would take too long.)
#
import random
random.seed(123456)
try:
    random.shuffle(tests)
except AttributeError:
    pass

for i in range(200):
  prog(*tests[i])

""")

test.write('goo.c', r"""
#include <stdio.h>

#ifdef _WIN32
#define EXPORT __declspec( dllexport )
#else
#define EXPORT
#endif

EXPORT void
goo(void)
{
        printf("goo.c\n");
}
""")

test.write('foo.c', r"""
#include <stdio.h>

void goo(void);

#ifdef _WIN32
#define EXPORT __declspec( dllexport )
#else
#define EXPORT
#endif

EXPORT void
foo(void)
{
        goo();
        printf("foo.c\n");
}
""")

test.write('prog.c', r"""
#include <stdio.h>

void foo(void);

int
main(int argc, char *argv[])
{
        argv[argc++] = "--";
        foo();
        printf("prog.c\n");
        return 0;
}
""")

test.run(arguments = '.',
         stderr=TestSCons.noisy_ar,
         match=TestSCons.match_re_dotall)

tests = re.findall(r'Prog: (\d+), (\S+), (\S+), (\S+)', test.stdout())
expected = "goo.c\nfoo.c\nprog.c\n"

for t in tests:
    if sys.platform != 'cygwin':
        test.must_exist(t[1])
        test.must_exist(t[2])
    else:
        # Cygwin turns libFoo.xxx into cygFoo.xxx
        for f in t[1:2]:
            test.must_exist(re.sub('^lib', 'cyg', f))

    test.must_exist(t[3])
    test.run(program = test.workpath(t[3]), stdout=expected)

test.pass_test()

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:
