from glob import glob
from itertools import chain, ifilter, imap
import re

from utils import instantiate


Import('env', 'excludedSources')
env = env.Clone(
    OCAML_LIBS=[
        'dynlink',
        'nums',
        'str',
        'unix',
        'cil',
    ],
    OCAML_WARN='Ae',
    tools=['expect', 'test'],
    toolpath=['.', '#scons-tools'],
)
env.AppendUnique(OCAML_PATH=['instrumentor', 'ocaml'])
env.AppendUnique(OCAML_PATH='$cil_paths')
env.AppendUnique(OCAML_PP='camlp4o pa_macro.cmo')

env.File([
        '#scons-tools/expect.py',
        'test.py',
        ])


########################################################################
#
#  determine CIL version number
#

def cilVersion(context):
    context.Message('Checking for CIL version...')
    env = context.env
    builder = env.OcamlProgram
    ok = context.TryBuild(builder, 'let _ = print_endline Cil.cilVersion', '.ml')
    if ok:
        program = context.lastTarget
        outfile = program.target_from_source('', '.out')
        env.Command(outfile, program, '$SOURCE >$TARGET')
        ok = context.sconf.BuildNodes([outfile])
        if ok:
            output = outfile.get_contents().rstrip()
            context.Result(output)
            pieces = tuple(map(int, output.split('.')))
            env['cil_version'] = pieces
            return 1, output
    return 0, ''

conf = env.Configure(
    help=False,
    custom_tests={
        'CilVersion': cilVersion,
        },
)
conf.CilVersion()
conf.Finish()

if env.get('cil_version') >= (1, 6):
    env['OCAML_PP'] += ' -DHAVE_COMPUTED_GOTO'

if env.get('cil_version') >= (1, 7):
    env['OCAML_PP'] += ' -DHAVE_CASE_RANGE'


########################################################################
#
#  create OCaml source defining sampler version number
#


[version_ml] = env.Template('version.ml.in', varlist=['VERSION'])


########################################################################
#
#  create OCaml source listing standard library functions
#


symbolPattern = re.compile('^[0-9a-f]{8}[0-9a-f]{8}? [TW] ([$\\w]+)')


def findSymbols(library):
    command = ['nm', '--defined-only', '-g', '-p', str(library)]
    if library.get_suffix() != '.a':
        command.append('-D')

    nm = env.ReadPipe(command)
    matches = imap(symbolPattern.match, nm)
    matches = ifilter(None, matches)
    return ( match.group(1) for match in matches )


def libfuncs(target, source, env):
    libraries = source[1:]
    symbols = chain(*imap(findSymbols, libraries))
    symbols = ['"%s"' % symbol for symbol in symbols ]

    instantiate(
        str(source[0]), str(target[0]),
        LIBRARY_FUNCTIONS=';\n'.join(symbols),
        LIBRARY_COUNT=len(symbols),
        )

env['libc_nonshared_dir'] = {
    'debian': '/usr/lib/$DISTRO_ARCH-linux-gnu',
    'rpm': '/usr/$lib64'
}[env['DISTRO_BASIS']]

env['libc_dir'] = {
    'debian': '/lib/$DISTRO_ARCH-linux-gnu',
    'rpm': '/$lib64',
}[env['DISTRO_BASIS']]

syslibs = env.subst([
    '$libc_nonshared_dir/libc_nonshared.a',
    '$libc_dir/libc.so.6',
    '$libc_dir/libm.so.6',
])
excludedSources |= set(syslibs)
[libraries_ml] = env.Command('libraries.ml', ['libraries.ml.in', syslibs], libfuncs)


########################################################################
#
#  main build targets
#


sources = [version_ml, libraries_ml] + glob('*.ml') + glob('*.mli')
env.OcamlObject(sources)

main = env.OcamlProgram('main.ml')
misc = map(env.OcamlProgram, [
    'cfgTest.ml',
    'cfgToDot.ml',
    'checker.ml',
    'dotify.ml',
    'dump.ml',
    'dumpConstants.ml',
    'loopless.ml',
])
Default(misc)

main = Install('#driver', main)
Default(main)

Alias('install', env.Install('$DESTDIR$driverdir', main))


########################################################################
#
#  source distribution
#


sources = set(sources)
for generated in ['libraries.ml', 'version.ml']:
    sources.discard(generated)
    sources.add(generated + '.in')


#########################################################################
#
#  regression test subdirs
#

SConscript(dirs=[
    'balance',
    'bounds',
    'branches',
    'coverage',
    'data',
    'function-entries',
    'g-object-unref',
    'nothing',
    'returns',
    'scalar-pairs',
    'timestamps',
    'trace',
    ],
           exports='env')
