import os
from os.path import join as pjoin

Import('env')

# Build environment configuration --------------------------------------------

# Insert LIBRARY_PATH explicitly because some compilers
# ignore it in the system environment.
env.PrependUnique(LIBPATH=env['ENV'].get('LIBRARY_PATH', '').split(os.pathsep))

# Use C++ compiler specified by the 'tool' option.
# Tools are loaded into env in place, because subsidiary SConscripts
# Import the shared env object.
if env['tool'] == 'intelc':
    icpc = env.WhereIs('icpc')
    if not icpc:
        print("Cannot find the Intel C/C++ compiler 'icpc'.")
        Exit(1)
    env.Tool('intelc', topdir=icpc[:icpc.rfind('/bin')])

if env['tool'] == 'clang':
    if not env.WhereIs('clang++'):
        print("Cannot find the clang++ compiler.")
        Exit(1)
    env.Tool('clangxx')

if env['tool'] == 'gcc':
    if not env.WhereIs('g++'):
        print("Cannot find the g++ compiler.")
        Exit(1)
    if env['PLATFORM'] == 'win32':
        env.Tool('mingw')

# libobjcryst is built with double precision for ObjCryst's REAL type.
# Match that ABI in all consumers of ObjCryst headers.
env.PrependUnique(CPPDEFINES={'REAL': 'double'})

# Platform specific intricacies.
fast_linkflags = ['-s']

# macOS specific options
if env['PLATFORM'] == 'darwin':
    env.AppendUnique(CXXFLAGS='-ftemplate-depth-256')
    env.AppendUnique(SHLINKFLAGS=['-install_name', '$TARGET.abspath'])
    env.AppendUnique(SHLINKFLAGS='-headerpad_max_install_names')
    fast_linkflags[:] = []

# Compiler specific options
if env['PLATFORM'] == 'win32':
    env.AppendUnique(CPPDEFINES=['_USE_MATH_DEFINES'])
    if env['tool'] == 'clang':
        # clang++ on Windows
        env['CC'] = 'clang'
        env['CCFLAGS'] = ['-O3', '-ffast-math', '-m64']
        env.AppendUnique(CXXFLAGS='-std=c++23')
        env['LINKFLAGS'] = ['-fuse-ld=lld-link', '-m64', '-lstdc++']
    elif env['tool'] == 'gcc':
        # MinGW
        env.PrependUnique(CCFLAGS=['-O3', '-m64'])
        env.AppendUnique(CXXFLAGS='-std=c++23')
        env.AppendUnique(LINKFLAGS='-m64')
    else:
        # Visual C++
        # /W3 rather than /Wall, which floods the log with warnings from the
        # standard library and boost headers.  /bigobj is needed for the
        # boost serialization code.
        env.PrependUnique(CCFLAGS=['/Ox', '/EHsc', '/MD', '/W3', '/bigobj'])
        env.AppendUnique(CXXFLAGS='/std:c++latest')
        env.AppendUnique(CPPDEFINES={'NDEBUG': None})
        env.AppendUnique(LINKFLAGS=['/OPT:NOREF', '/OPT:NOICF'])

else:
    # Specify minimum C++ standard.  Allow later standard from sconscript.local.
    # In case of multiple `-std` options the last option holds.
    env.PrependUnique(CXXFLAGS='-std=c++23', delete_existing=1)
    if env['tool'] == 'intelc':
        # options for Intel C++ compiler on hpc dev-intel07
        env.PrependUnique(CCFLAGS=['-w1', '-fp-model', 'precise'])
        env.PrependUnique(LIBS=['imf'])
        fast_optimflags = ['-fast', '-no-ipo']
    else:
        # g++ options
        env.PrependUnique(CCFLAGS=['-Wall'])
        fast_optimflags = ['-ffast-math']

    # Configure build variants
    if env['build'] == 'debug':
        env.Append(CCFLAGS='-g')
    elif env['build'] == 'coverage':
        env.CacheDir(None)
        env.Append(CCFLAGS=['-g', '--coverage', '-O0'])
        env.Append(LINKFLAGS='--coverage')
    elif env['build'] == 'fast':
        env.AppendUnique(CCFLAGS=['-O3'] + fast_optimflags)
        env.AppendUnique(CPPDEFINES={'NDEBUG' : None})
        env.AppendUnique(LINKFLAGS=fast_linkflags)

    if env['profile']:
        env.AppendUnique(CCFLAGS='-pg')
        env.AppendUnique(LINKFLAGS='-pg')


# configure boost and ObjCryst libraries unless non-relevant.
skip_configure = (GetOption('clean') or GetOption('help') or
                  (['sdist'] == list(COMMAND_LINE_TARGETS)))

# Check dependencies
if not skip_configure:
    SConscript('SConscript.configure')

# Libraries used by libdiffpy.  Add them to the shared environment,
# because the unit tests also call GSL, boost_serialization and ObjCryst.
# SCons strips the "lib" prefix where the platform adds it, so
# 'libObjCryst' links libObjCryst.so on Linux and libObjCryst.lib on Windows.
env.AppendUnique(LIBS=['gsl', 'gslcblas', 'boost_serialization'])
if env.get('has_objcryst'):
    env.AppendUnique(LIBS=['libObjCryst'])

# when cleaning make sure to also purge ObjCryst files
if GetOption('clean'):
    env['has_objcryst'] = True

# Define lists for storing library source and include files.
env['lib_includes'] = []
env['lib_sources'] = []
env['lib_datafiles'] = []

# Subsidiary SConscripts -----------------------------------------------------

# The targets that concern unit tests.
targets_that_test = set(('test', 'alltests'))

# Short circuit if we test an already installed library.  Do not define
# any further targets as they may conflict with the installed files.
if env['test_installed']:
    SConscript('tests/SConscript')
    if not targets_that_test.issuperset(COMMAND_LINE_TARGETS):
        print('Warning: only test targets are available when '
              '"test_installed=True".')
    Return()

assert not env['test_installed']

# Here we do not test the installed library.  Any diffpy headers
# should thus be looked up from our source tree.
env.PrependUnique(CPPPATH=Dir('.'))

# Load the version script first to resolve the majorminor tuple
SConscript('diffpy/SConscript.version')

# Path where datafiles should be installed
env['runtimepath'] = os.path.join(
        env['datadir'], 'diffpy/libdiffpy-%i.%i' % env['majorminor'])
SConscript('runtime/SConscript')

# Load all other sconscripts that update lib_includes and lib_sources
SConscript('diffpy/SConscript')

# Define sdist target for creating source distribution bundle
# Do so only if required to avoid extra git executions.
# Note: See .gitattributes for files excluded from sdist.
if 'sdist' in COMMAND_LINE_TARGETS:
    SConscript('SConscript.sdist')

# Top Level Targets ----------------------------------------------------------

# lib -- shared library object

# use new environment with extra libraries needed for libdiffpy.
env_lib = env.Clone()
if env['PLATFORM'] == 'win32':
    # Export the API from the DLL, see diffpy/Export.hpp.  Code that uses
    # the library, including the unit tests, imports it instead.
    env_lib.AppendUnique(CPPDEFINES=['BUILD_DLL'])
# The dladdr call in runtimepath.cpp requires the dl library.
# Windows uses GetModuleHandleEx instead.
if env['PLATFORM'] != 'win32':
    env_lib.AppendUnique(LIBS=['dl'])

libdiffpy = env_lib.SharedLibrary('diffpy', env['lib_sources'])

# Clean up .gcda and .gcno files from coverage analysis.
env_lib.Clean(libdiffpy, Glob('diffpy/*.gc??'))
env_lib.Clean(libdiffpy, Glob('diffpy/srreal/*.gc??'))
env_lib.Depends(libdiffpy, env['lib_includes'])
Export('libdiffpy')

lib = Alias('lib', [libdiffpy])
Default(lib)

# Define targets related to testing.  Do so only when testing is requested.
# This enables library build on machines without cxxtest.
if targets_that_test.intersection(COMMAND_LINE_TARGETS):
    SConscript('tests/SConscript')

# Installation targets.

prefix = env['prefix']

# install-lib
if env['PLATFORM'] in ['darwin', 'posix']:
    install_lib = env.Install(env['libdir'], libdiffpy)
    if env['PLATFORM'] == 'darwin':
        # DARWIN_INSTALL_NAME can be pre-set in sconscript.local
        env.SetDefault(DARWIN_INSTALL_NAME='$TARGET.abspath')
        env.AddPostAction(install_lib,
                          'install_name_tool -id $DARWIN_INSTALL_NAME $TARGET')
    if env['PLATFORM'] == 'posix' and WhereIs('ldconfig'):
        opts = '' if os.getuid() == 0 else '-n'
        env.AddPostAction(install_lib,
                          'ldconfig %s $TARGET.dir' % opts)
    Alias('install-lib', install_lib)

if env['PLATFORM'] == 'win32':
    bindir = pjoin(prefix, 'Library', 'bin')
    dll_node = lib_node = None
    for node in libdiffpy:
        ext = os.path.splitext(str(node))[1].lower()
        if ext == '.dll':
            dll_node = node
        elif ext == '.lib':
            lib_node = node

    if dll_node is None:
        print("Warning: DLL file not found in build outputs.")
    if lib_node is None:
        print("Warning: LIB file not found in build outputs.")

    install_dll = env.Install(bindir, dll_node)
    install_lib = env.Install(env['libdir'], lib_node)
    Alias('install-lib', [install_dll, install_lib])

# install-include
ninc = len(Dir('.').path) + 1
inc_target_path = lambda f: os.path.join(env['includedir'], f.path[ninc:])
include_targets = [inc_target_path(f) for f in env['lib_includes']]
install_include = InstallAs(include_targets, env['lib_includes'])
Alias('install-include', install_include)

# install-data
nrt = len(Dir('runtime').path) + 1
data_target_path = lambda f: os.path.join(env['runtimepath'], f.path[nrt:])
data_targets = [data_target_path(f) for f in env['lib_datafiles']]
install_data = InstallAs(data_targets, env['lib_datafiles'])
Alias('install-data', install_data)

# install
Alias('install', ['install-lib', 'install-include', 'install-data'])
# do not install headers and data when shared library cannot be installed.
if 'install' in COMMAND_LINE_TARGETS:
    Requires(install_include, install_lib)
    Requires(install_data, install_lib)

# Coverage related targets.

# zerocounters
cdatadirs = ['diffpy', 'diffpy/srreal', 'tests']
cdatafiles = [f for d in cdatadirs for f in Glob(d + '/*.gcda')]
zerocounters = Alias('zerocounters', [], [Delete(f) for f in cdatafiles])
Ignore(cdatadirs, cdatafiles)
AlwaysBuild(zerocounters)

# when both 'test' and 'zerocounters' are requested, make sure
# to build them in correct order.
require_ordered_targets = ('test' in COMMAND_LINE_TARGETS and
                           'zerocounters' in COMMAND_LINE_TARGETS)
if require_ordered_targets:
    Depends('test', zerocounters)

# vim: ft=python
