# -*- mode: python -*-
import textwrap

Import("env has_option")
Import("get_option")
Import("http_client")

import libdeps

hygienic = get_option('install-mode') == 'hygienic'

feature_dirs = [
    "fle",
    "search",
    "encryptdb",
    "hot_backups",
    "kerberos",
    "inmemory",
    "ldap",
    "mongohouse",
    "queryable",
]

env = env.Clone()


if get_option('lint-scope') == 'changed':
    patch_file = env.Command(
        target="$BUILD_DIR/current.enterprise.git.patch",
        source=[env.WhereIs("git"), env.Dir(".").srcnode().get_abspath()],
        action="cd ${SOURCES[1]} && ${SOURCES[0]} diff $GITDIFFFLAGS > ${TARGET.abspath}"
    )

    env.AlwaysBuild(patch_file)

    pylinters = env.Command(
        target="#lint-pylinters-enteprise",
        source=[
            "#buildscripts/pylinters.py",
            patch_file,
        ],
        action="$PYTHON ${SOURCES[0]} lint-patch ${SOURCES[1]}"
    )

    clang_format = env.Command(
        target="#lint-clang-format-enterprise",
        source=[
            "#buildscripts/clang_format.py",
            patch_file,
        ],
        action="$PYTHON ${SOURCES[0]} lint-patch ${SOURCES[1]}"
    )

    eslint = env.Command(
        target="#lint-eslint-enterprise",
        source=[
            "#buildscripts/eslint.py",
            patch_file,
        ],
        action="$PYTHON ${SOURCES[0]} lint-patch ${SOURCES[1]}"
    )

    env.Alias( "lint-fast" , [ eslint, clang_format, pylinters ] )
    env.Alias( "lint" , [ eslint, clang_format, pylinters ] )


if get_option( "ssl" ) != "on":
    env.FatalError("SSL not enabled. Enterprise MongoDB must be built with --ssl specified")

env.InjectMongoIncludePaths()
env.InjectModule("enterprise", builder=True, consumer=False)

conf = Configure(env, help=False)

if "sasl" in env['MONGO_ENTERPRISE_FEATURES'] and not conf.CheckLibWithHeader(
    "sasl2",
    ["stddef.h","sasl/sasl.h"], "C",
    "sasl_version_info(0, 0, 0, 0, 0, 0);",
    autoadd=False):
    env.ConfError("Could not find <sasl/sasl.h> and sasl library, required for "
        "enterprise build.")

if http_client != "on":
    env.ConfError("Enterprise build requires http client support")

if not env.TargetOSIs("windows"):
    if not conf.CheckLibWithHeader(
            "ldap",
            ["ldap.h"], "C",
            "ldap_is_ldap_url(\"ldap://127.0.0.1\");", autoadd=False):
        env.ConfError("Could not find <ldap.h> and ldap library from OpenLDAP, "
                       "required for LDAP authorization in the enterprise build")
    if not conf.CheckLibWithHeader(
            "lber",
            ["lber.h"], "C",
            "ber_free(NULL, 0);", autoadd=False):
        env.ConfError("Could not find <lber.h> and lber library from OpenLDAP, "
                      "required for LDAP authorizaton in the enterprise build")
    conf.env['MONGO_LDAP_LIB'] = ["ldap", "lber"]
else:
    conf.env['MONGO_LDAP_LIB'] = ["Wldap32"]

if conf.CheckLib(library="gssapi_krb5", autoadd=False):
    conf.env['MONGO_GSSAPI_IMPL'] = "gssapi"
    conf.env['MONGO_GSSAPI_LIB'] = "gssapi_krb5"
    if env.TargetOSIs("freebsd"):
        conf.env['MONGO_GSSAPI_LIB'] = ["gssapi", "gssapi_krb5"]
elif env.TargetOSIs("windows"):
    conf.env['MONGO_GSSAPI_IMPL'] = "sspi"
    conf.env['MONGO_GSSAPI_LIB'] = "secur32"
else:
    env.ConfError("Could not find gssapi_krb5 library nor Windows OS, required for "
                  "enterprise build.")

env = conf.Finish()

env.SConscript(
    dirs=[
        "src/%s" % feature for feature in feature_dirs if feature in env['MONGO_ENTERPRISE_FEATURES']
    ] + ['src/util'],
    exports=[
        'env',
    ],
)

if "audit" in env['MONGO_ENTERPRISE_FEATURES']:
    env.Library(
        target='audit_enterprise',
        source=[
            'src/audit/audit_application_message.cpp',
            'src/audit/audit_authentication.cpp',
            'src/audit/audit_authz_check.cpp',
            'src/audit/audit_event.cpp',
            'src/audit/audit_indexes_collections_databases.cpp',
            'src/audit/audit_log.cpp',
            'src/audit/audit_manager_global.cpp',
            'src/audit/audit_options.cpp',
            'src/audit/audit_private.cpp',
            'src/audit/audit_replset.cpp',
            'src/audit/audit_role_management.cpp',
            'src/audit/audit_sharding.cpp',
            'src/audit/audit_shutdown.cpp',
            'src/audit/audit_user_management.cpp',
        ],
        LIBDEPS=[
            '$BUILD_DIR/mongo/base',
            '$BUILD_DIR/mongo/db/auth/auth',
            '$BUILD_DIR/mongo/db/ops/write_ops_parsers',
            '$BUILD_DIR/mongo/util/net/network',
        ],
        LIBDEPS_PRIVATE=[
            '$BUILD_DIR/mongo/db/auth/authprivilege',
            '$BUILD_DIR/mongo/db/matcher/expressions',
            '$BUILD_DIR/mongo/db/server_options_core',
            '$BUILD_DIR/mongo/util/options_parser/options_parser',
        ],
        LIBDEPS_DEPENDENTS=[
            ('$BUILD_DIR/mongo/db/audit', libdeps.dependency.Public),
        ],
    )

    env.Library(
        target='audit_command',
        source=[
            'src/audit/audit_command.cpp',
        ],
        LIBDEPS=[
            '$BUILD_DIR/mongo/db/auth/auth',
            '$BUILD_DIR/mongo/db/audit',
            '$BUILD_DIR/mongo/db/commands',
            '$BUILD_DIR/mongo/db/service_context',
        ],
        LIBDEPS_PRIVATE=[
            '$BUILD_DIR/mongo/db/auth/authprivilege',
        ],
        LIBDEPS_DEPENDENTS=[
            '$BUILD_DIR/mongo/db/commands/mongod',
        ],
    )

    # The auditing code needs to be built into the "coredb" library because there is code in there that
    # references audit functions.  However, the "coredb" library is also currently shared by server
    # programs, such as mongod and mongos, as well as client programs, such as mongodump and
    # mongoexport.  For these reasons, we have no choice but to build the audit module into all of
    # these, even though it's dead code in the client programs.  To avoid actually allowing the user to
    # configure this dead code, we need to separate the option registration into this
    # "audit_configuration" library and add it to only mongod and mongos.  Because audit defaults to
    # disabled this effectively prevents this code from being run in client programs.
    env.Library(
        target='audit_configuration',
        source=[
            'src/audit/audit_options.idl',
            'src/audit/audit_watchdog.cpp',
        ],
        LIBDEPS=[
            'audit_enterprise',
        ],
        LIBDEPS_PRIVATE=[
            '$BUILD_DIR/mongo/util/options_parser/options_parser',
            '$BUILD_DIR/mongo/idl/server_parameter',
            '$BUILD_DIR/mongo/watchdog/watchdog_register',
        ],
        PROGDEPS_DEPENDENTS=[
            '$BUILD_DIR/mongo/mongod',
            '$BUILD_DIR/mongo/mongos',
        ])

if not env.TargetOSIs("darwin") and "snmp" in env['MONGO_ENTERPRISE_FEATURES']:

    snmpEnv = env.Clone()

    if snmpEnv.TargetOSIs('windows'):
        snmpEnv.Append(
            CCFLAGS=[
                # C5033: 'register' is no longer a supported storage
                # class. The snmp library still uses the 'register'
                # keyword.
                "/wd5033",
            ],
        )

    snmpEnv.Library(
        target='mongosnmp',
        source=[
            'src/snmp/serverstatus_client.cpp',
            'src/snmp/snmp.cpp',
            'src/snmp/snmp_oid.cpp',
            'src/snmp/snmp_options.cpp',
            'src/snmp/snmp_options.idl',
        ],
        LIBDEPS=[
            '$BUILD_DIR/mongo/base',
            '$BUILD_DIR/mongo/client/clientdriver_network',
            '$BUILD_DIR/mongo/db/bson/dotted_path_support',
            '$BUILD_DIR/mongo/db/dbdirectclient',
            '$BUILD_DIR/mongo/db/initialize_snmp',
            '$BUILD_DIR/mongo/db/repl/repl_coordinator_interface',
            '$BUILD_DIR/mongo/db/repl/repl_settings',
            '$BUILD_DIR/mongo/util/processinfo',
        ],
        LIBDEPS_PRIVATE=[
            '$BUILD_DIR/mongo/util/options_parser/options_parser',
        ],
        PROGDEPS_DEPENDENTS=['$BUILD_DIR/mongo/mongod'],
        SYSLIBDEPS=snmpEnv.get('SNMP_SYSLIBDEPS', []),
    )

if "sasl" in env['MONGO_ENTERPRISE_FEATURES']:
    env.Library(
        target=[
            'sasl_aws_server',
        ],
        source=[
            'src/sasl/sasl_aws_server_protocol.cpp',
        ],
        LIBDEPS_PRIVATE=[
            '$BUILD_DIR/mongo/base',
            '$BUILD_DIR/mongo/client/sasl_aws_common',
            '$BUILD_DIR/mongo/db/server_options_core', # For object_check.h
        ],
    )

    kmsEnv = env.Clone()

    kmsEnv.InjectThirdParty(libraries=['kms-message'])

    kmsEnv.CppUnitTest(
        target='sasl_aws_protocol_test',
        source=[
            'src/sasl/sasl_aws_protocol_test.cpp',
        ],
        LIBDEPS=[
            '$BUILD_DIR/mongo/client/sasl_aws_common',
            '$BUILD_DIR/mongo/client/sasl_aws_client',
            'sasl_aws_server',
            '$BUILD_DIR/third_party/shim_kms_message',
        ],
    )

    env.Library(
        target='mongosaslserversession',
        source=[
            'src/sasl/auxprop_mongodb_internal.cpp' if env.TargetOSIs("windows") else [],
            'src/sasl/canon_mongodb_internal.cpp',
            'src/sasl/mongo_${MONGO_GSSAPI_IMPL}.cpp',
            'src/sasl/sasl_aws_server_options.idl',
            'src/sasl/sasl_aws_server_conversation.cpp',
        ],
        LIBDEPS=[
            'sasl_aws_server',
            '$BUILD_DIR/mongo/db/auth/saslauth',
        ] + (['src/util/gssapi_helpers'] if not env.TargetOSIs("windows") else []),
        LIBDEPS_PRIVATE=[
            '$BUILD_DIR/mongo/util/net/http_client',
        ],
        SYSLIBDEPS=['sasl2'] + [env['MONGO_GSSAPI_LIB']],
    )

    env.Library(
        target='auth_delay',
        source=[
            'src/sasl/auth_delay.idl',
        ],
        LIBDEPS_PRIVATE=[
            '$BUILD_DIR/mongo/db/auth/sasl_options',
            '$BUILD_DIR/mongo/idl/server_parameter',
        ],
        PROGDEPS_DEPENDENTS=[
            '$BUILD_DIR/mongo/mongod',
            '$BUILD_DIR/mongo/mongos',
        ],
    )

    env.Library(
        target='mongosaslservercommon',
        source=[
            'src/sasl/cyrus_sasl_authentication_session.cpp',
            'src/sasl/ldap_sasl_authentication_session.cpp'
        ],
        LIBDEPS=['src/ldap/ldap_manager',
                 'src/ldap/ldap_name_map',
                 'mongosaslserversession'],
        PROGDEPS_DEPENDENTS=[
            '$BUILD_DIR/mongo/mongod',
            '$BUILD_DIR/mongo/mongos',
        ],
    )

    if env.TargetOSIs("windows"):
        # Ensure we're building with /MD or /MDd
        mdFlags = ["/MD","/MDd"]
        hasFlag = 0
        for mdFlag in mdFlags:
            if mdFlag in env['CCFLAGS']:
                hasFlag += 1
        if hasFlag != 1:
            env.FatalError("You must enable dynamic CRT --dynamic-windows to build the "
                "enterprise version")
    else:
        gssapi_test = env.CppUnitTest('sasl_authentication_session_gssapi_test',
                                      ['src/sasl/sasl_authentication_session_gssapi_test.cpp'],
                                      UNITTEST_HAS_CUSTOM_MAINLINE=True,
                                      LIBDEPS=['mongosaslserversession',
                                               'mongosaslservercommon',
                                               'src/ldap/ldap_manager_init',
                                               '$BUILD_DIR/mongo/base',
                                               '$BUILD_DIR/mongo/util/net/network',
                                               '$BUILD_DIR/mongo/db/auth/auth',
                                               '$BUILD_DIR/mongo/db/auth/authmocks',
                                               '$BUILD_DIR/mongo/db/auth/saslauth',
                                               '$BUILD_DIR/mongo/client/clientdriver_network',
                                               '$BUILD_DIR/mongo/client/sasl_client',
                                               '$BUILD_DIR/mongo/db/service_context_test_fixture',
                                               '$BUILD_DIR/mongo/executor/thread_pool_task_executor',
                                               '$BUILD_DIR/mongo/executor/network_interface_thread_pool',
                                               '$BUILD_DIR/mongo/executor/network_interface_factory',
                                               '$BUILD_DIR/mongo/unittest/unittest'])

if "encryptdb" in env['MONGO_ENTERPRISE_FEATURES']:
    mongodecrypt = env.Program(
        target="mongodecrypt",
        source=[
            "src/encryptdb/decrypt_tool.cpp",
            "src/encryptdb/decrypt_tool_options.cpp",
            'src/encryptdb/decrypt_tool_options.idl',
        ] + env.WindowsResourceFile("src/encryptdb/decrypt_tool.rc"),
        AIB_COMPONENT="mongodecrypt",
        AIB_COMPONENTS_EXTRA=[
            "dist",
            "dist-test",
            "tools",
        ],
        LIBDEPS=[
            "$BUILD_DIR/mongo/base",
            "$BUILD_DIR/mongo/crypto/symmetric_crypto",
            "$BUILD_DIR/mongo/db/log_process_details",
            "$BUILD_DIR/mongo/util/options_parser/options_parser_init",
            "$BUILD_DIR/mongo/util/signal_handlers",
            "$BUILD_DIR/mongo/util/version_impl",
            "src/encryptdb/key_acquisition",
            "src/encryptdb/symmetric_crypto_enterprise",
        ],
    )

    env.Alias("all", mongodecrypt)

    if not hygienic:
        env.Install("#/", mongodecrypt)

if "ldap" in env['MONGO_ENTERPRISE_FEATURES']:
    mongoldap = env.Program(
        target="mongoldap",
        source=[
            "src/ldap/ldap_tool.cpp",
            "src/ldap/ldap_tool_options.cpp",
            'src/ldap/ldap_tool_options.idl',
        ] +  env.WindowsResourceFile("src/ldap/ldap_tool.rc"),
        AIB_COMPONENT="mongoldap",
        AIB_COMPONENTS_EXTRA=[
            "dist",
            "dist-test",
            "tools",
        ],
        LIBDEPS=[
            "$BUILD_DIR/mongo/base",
            "$BUILD_DIR/mongo/base/secure_allocator",
            "$BUILD_DIR/mongo/db/log_process_details",
            "$BUILD_DIR/mongo/util/options_parser/options_parser_init",
            "$BUILD_DIR/mongo/util/signal_handlers",
            "$BUILD_DIR/mongo/util/version_impl",
            "src/ldap/ldap_manager",
            "src/ldap/ldap_options_mongod",
            "src/util/report_tool",
        ],
    )

    env.Alias("all", mongoldap)

    if not hygienic:
        env.Install("#/", mongoldap)

if "kerberos" in env['MONGO_ENTERPRISE_FEATURES']:
    def CheckKRB5Features(context):
        test_body = """
        #include <krb5.h>
        int main(int argc, char *argv[]) {
            krb5_context context = nullptr;
            krb5_keytab kt = nullptr;
            krb5_kt_have_content(context, kt);
            krb5_kt_client_default(context, &kt);
            return 0;
        }
        """

        context.Message("Checking for KRB5 Features...")
        ret = context.TryCompile(textwrap.dedent(test_body), ".cpp")
        context.Result(ret)
        return ret

    krb5conf = Configure(env, custom_tests={'CheckKRB5Features': CheckKRB5Features})
    if krb5conf.CheckKRB5Features():
        env.Append(CPPDEFINES=[("MONGO_KRB5_HAVE_FEATURES", 1)])

    mongokerberos = env.Program(
        target="mongokerberos",
        source=[
            "src/kerberos/kerberos_tool.cpp",
        ],
        AIB_COMPONENT="mongokerberos",
        AIB_COMPONENTS_EXTRA=[
            "dist",
            "dist-test",
            "tools",
        ],
        LIBDEPS=[
            "$BUILD_DIR/mongo/base",
            "$BUILD_DIR/mongo/base/secure_allocator",
            "$BUILD_DIR/mongo/util/options_parser/options_parser_init",
            "$BUILD_DIR/mongo/util/signal_handlers",
            "$BUILD_DIR/mongo/util/version_impl",
            "src/kerberos/kerberos_tool",
            "src/util/report_tool",
        ] + (["src/util/gssapi_helpers"] if not env.TargetOSIs("windows") else []),
    )

    env.Alias("all", mongokerberos)

    if not hygienic:
        env.Install("#/", mongokerberos)

if "fle" in env['MONGO_ENTERPRISE_FEATURES']:
    mongocryptd = env.Program(
        target="mongocryptd",
        source=[
            "src/fle/cryptd/cryptd_main_shim.cpp"
        ],
        LIBDEPS_PRIVATE=[
            'src/fle/mongocryptd_core',
            'src/fle/cryptd_commands',
        ],
        AIB_COMPONENT="mongocryptd",
        AIB_COMPONENTS_EXTRA=[
            "dist",
            "dist-test",
            "tools",
        ],
    )

    if not hygienic:
        env.Install("#/", mongocryptd)

    env.Alias("all", mongocryptd)

if "search" in env['MONGO_ENTERPRISE_FEATURES']:
    mongotmock = env.Program(
        target="mongotmock",
        source=[
            "src/search/mongotmock/mongotmock_main_shim.cpp"
        ],
        LIBDEPS_PRIVATE=[
            'src/fle/mongocryptd_core',
            'src/search/mongotmock_core',
        ],
        AIB_COMPONENT="mongotmock",
        AIB_COMPONENTS_EXTRA=["dist-test"],
    )

    if not hygienic:
        env.Alias("core", env.Install("#/", mongotmock))

if hygienic:
    # These are all the files added in ARCHIVE_ADDITIONS
    #
    # Much of this is duplicated from the build.py, in hygienic mode
    # it cannot exist there since there is a dependency ordering
    # issue. When hygienic is the default we can clean this up and
    # remove the duplication in the build.py
    #
    # SNMP doc files
    env.AutoInstall(
        target='$PREFIX/snmp',
        source=env.Glob("docs/*"),
        AIB_COMPONENT='snmp',
        AIB_COMPONENTS_EXTRA=[
            'dist',
            'dist-test',
        ],
        AIB_ROLE="base",
    )

    if env.TargetOSIs("windows"):
        import os
        import winreg
        import re
        import subprocess

        extra_files = []

        if 'sasl' in env['MONGO_ENTERPRISE_FEATURES']:
            extra_files.extend([
                'libsasl.dll',
                'libsasl.pdb',
            ])

        if 'snmp' in env['MONGO_ENTERPRISE_FEATURES']:
            extra_files.extend([
                'netsnmp.dll',
                'netsnmp.pdb',
            ])

        for file_name in extra_files:
            paths = [str(env.Dir(libpath).Dir('..').Dir('bin')) for libpath in env.get('LIBPATH', [])]
            for path in paths:
                full_file_name = os.path.join(os.path.normpath(path.lower()), file_name)
                if os.path.exists(full_file_name):
                    env.AutoInstall(
                        target='$PREFIX_BINDIR',
                        source=[full_file_name],
                        AIB_COMPONENT='dist',
                        AIB_COMPONENTS_EXTRA=[
                            'dist-test',
                        ],
                        AIB_ROLE="debug" if full_file_name.endswith(".pdb") else "runtime",
                    )


        redist_file = env['MSVS'].get('VCREDISTEXE', None)
        if not redist_file:
            env.FatalError('Required CRT redistributable not found; cannot build distribution package')

        env.AutoInstall(
            target='$PREFIX_BINDIR',
            source=[
                "${MSVS['VCREDISTEXE']}"
            ],
            AIB_COMPONENT="dist",
            AIB_ROLE="runtime",
        )
