# -*- mode: python; -*-
#
# This is the principle SConscript file, invoked by the SConstruct.  Its job is
# to delegate to any and all per-module SConscript files.

from functools import partial

from site_scons.mongo import insort_wrapper

import SCons

Import('dynamicRT')
Import('env')
Import('get_option')
Import('has_option')
Import('module_sconscripts')


def shim_hack(target, source, env, inject_target=None, exclusions=None):
    if exclusions is None:
        exclusions = set(inject_target)
    elif isinstance(exclusions, str):
        exclusions = {exclusions, inject_target}
    elif isinstance(exclusions, (list, set)):
        exclusions = set(exclusions)
        exclusions.add(inject_target)

    # If we allowed conftests to become dependent, any TryLink
    # that happened after we made the below modifications would
    # cause the configure steps to try to compile tcmalloc and any
    # of its dependencies. Oops!
    if any('conftest' in str(t) for t in target):
        return target, source

    # It is possible that 'env' isn't a unique
    # OverrideEnvironment, since if you didn't pass any kw args
    # into your builder call, you just reuse the env you were
    # called with. That could mean that we see the same
    # environment here multiple times. But that is really OK,
    # since the operation we are performing would be performed on
    # all of them anyway.
    libdeps_no_inherit = set(env.get('LIBDEPS_NO_INHERIT', []))
    exclusions.update(libdeps_no_inherit)

    if f"$BUILD_DIR/{inject_target}" not in exclusions:
        lds = env.get('LIBDEPS', [])
        shim_target = f"$BUILD_DIR/{inject_target}"
        if shim_target not in lds:
            insort_wrapper(lds, shim_target)
            env['LIBDEPS'] = lds

    return target, source


def nodefaultlibs_hack(target, source, env):
    if any('conftest' in str(t) for t in target):
        return target, source

    runtime_shim_names = [
        'shim_crt',
        'shim_cxx',
    ]
    runtime_shims = [f"$BUILD_DIR/{name}" for name in runtime_shim_names]

    # If we're building either the CRT or CXX shim, don't insert the
    # nodefaultlibs argument because it makes static library insertion not work
    # in clang and gcc.
    if any(name in str(t) for name in runtime_shim_names for t in target):
        return target, source

    libdeps_no_inherit = set(env.get('LIBDEPS_NO_INHERIT', []))

    if not any(shim in libdeps_no_inherit for shim in runtime_shims):
        linkflags = env.get('LINKFLAGS', [])
        if '-nodefaultlibs' not in linkflags:
            linkflags = ['-nodefaultlibs'] + linkflags

        env['LINKFLAGS'] = linkflags

    return target, source


def hack_builder_emitters(env, hack_method):
    for builder_name in ('Program', 'SharedLibrary', 'LoadableModule', 'StaticLibrary'):
        builder = env['BUILDERS'][builder_name]
        base_emitter = builder.emitter
        builder.emitter = SCons.Builder.ListEmitter([hack_method, base_emitter])


# Here, we "hoist" libgcc*.a symbols out of the toolchain and stuff them into
# our own library so they don't get added piecemeal to every shared object and
# executable in a build directly out of the toolchain.
libcrtEnv = env.Clone(LIBS=[])
libcxxEnv = env.Clone(LIBS=[])
if dynamicRT == "force":

    if env.ToolchainIs('gcc', 'clang'):
        # Clang ang GCC get configured here because the dynamic runtimes are
        # injected in ways that would not be fully applied to conftests. The
        # only way to fix that means running part of the build before we can
        # run conftests. It may be worth it to do that at some point, but it's
        # complexity we should consider later if it's determined we need it.

        hack_builder_emitters(env, nodefaultlibs_hack)

        libcrtEnv.AppendUnique(
            LINKFLAGS=[
                "-Wl,-z,muldefs",
                "-static-libgcc",
                "-Wl,--push-state",
                "-Wl,-Bstatic",
                "-Wl,--no-warn-execstack",
                "-Wl,--whole-archive",
                "-lgcc",
                "-lgcc_eh",
                "-Wl,--no-whole-archive",
                "-Wl,--pop-state",
            ],
            SYSLIBDEPS=[
                ':libgcc_s.so',
            ]
        )

        if has_option("libc++"):
            cxx_lib = "c++"
        else:
            cxx_lib = "stdc++"

        libcxxEnv.AppendUnique(
            LINKFLAGS=[
                "-Wl,-z,muldefs",
                f"-static-lib{cxx_lib}",
                "-Wl,--push-state",
                "-Wl,-Bstatic",
                "-Wl,--no-warn-execstack",
                "-Wl,--whole-archive",
                f"-l{cxx_lib}",
                "-Wl,--no-whole-archive",
                "-Wl,--pop-state",
            ]
        )

libcrtEnv.ShimLibrary(
    name="crt",
    needs_link=(dynamicRT == "force"),
    LIBDEPS_TAGS=[
        # TODO: Remove when SERVER-48291 is merged into stable build tools.
        # An inserted dependency must be linked to every node, including what would
        # be considered a leaf node to ensure that a system dependency is not linked
        # in before this one. This tag allows nodes tagged as leaf nodes to still
        # get the correct allocator.
        'lint-leaf-node-allowed-dep',
        # This tag allows this dependency to be linked to nodes marked as not
        # allowed to have public dependencies.
        'lint-public-dep-allowed'
    ]
)

libcxxEnv.ShimLibrary(
    name="cxx",
    needs_link=(dynamicRT == "force"),
    LIBDEPS_TAGS=[
        # TODO: Remove when SERVER-48291 is merged into stable build tools.
        # An inserted dependency must be linked to every node, including what would
        # be considered a leaf node to ensure that a system dependency is not linked
        # in before this one. This tag allows nodes tagged as leaf nodes to still
        # get the correct allocator.
        'lint-leaf-node-allowed-dep',
        # This tag allows this dependency to be linked to nodes marked as not
        # allowed to have public dependencies.
        'lint-public-dep-allowed'
    ]
)


if get_option("build-tools") == "next":
    # Add any "global" dependencies here. This is where we make every build node
    # depend on a list of other build nodes, such as an allocator or libunwind
    # or libstdx or similar.
    env.AppendUnique(
        LIBDEPS_GLOBAL=[
            '$BUILD_DIR/shim_crt' if dynamicRT == "force" else [],
            '$BUILD_DIR/shim_cxx' if dynamicRT == "force" else [],
            '$BUILD_DIR/third_party/shim_allocator',
        ],
    )
else:
    if dynamicRT == "force":
        hack_builder_emitters(
            env,
            partial(
                shim_hack,
                inject_target='shim_crt'))
        hack_builder_emitters(
            env,
            partial(
                shim_hack,
                inject_target='shim_cxx'))
    hack_builder_emitters(
        env,
        partial(
            shim_hack,
            inject_target='third_party/shim_allocator',
            exclusions='gperftools/gperftools'))


# NOTE: We must do third_party first as it adds methods to the environment
# that we need in the mongo sconscript
env.SConscript('third_party/SConscript', exports=['env'])

# Inject common dependencies from third_party globally for all core mongo code
# and modules. Ideally, pcre wouldn't be here, but enough things require it
# now that it seems hopeless to remove it.
env.InjectThirdParty(libraries=[
    'abseil-cpp',
    'boost',
    'fmt',
    'pcre',
    'safeint',
    'variant',
])

# Run the core mongodb SConscript.
env.SConscript('mongo/SConscript', exports=['env'])

# Run SConscripts for any modules in play
env.SConscript(module_sconscripts, exports=['env'])
