# -*- mode: python -*-

# This SConscript describes build and install rules for the Mongo C++ driver and associated exmaple
# programs.
import os

Import('env has_option installSetup use_system_version_of_library')

Import('nix linux darwin windows')

buildShared = False
if has_option("sharedclient"):
    buildShared = True

clientEnv = env.Clone()
libEnv = env.Clone()

# We want to build the same objects in two different ways, once for a static (LIBDEPSy library)
# and once for a DLL, with different defines in play. That works fine on platforms where by
# default SHOBJSUFFIX and OBJSUFFIX differ, but by default on windows they don't. Repair that.
if windows:
    libEnv['SHOBJSUFFIX'] = libEnv['OBJSUFFIX'] + 's'

del env
clientEnv['CPPDEFINES'].remove('MONGO_EXPOSE_MACROS')

libEnv.Command(['mongo/base/error_codes.h', 'mongo/base/error_codes.cpp',],
               ['mongo/base/generate_error_codes.py', 'mongo/base/error_codes.err'],
               '$PYTHON $SOURCES $TARGETS')

clientSourceBasic = [
    'mongo/base/error_codes.cpp',
    'mongo/base/global_initializer.cpp',
    'mongo/base/global_initializer_registerer.cpp',
    'mongo/base/init.cpp',
    'mongo/base/initializer.cpp',
    'mongo/base/initializer_context.cpp',
    'mongo/base/initializer_dependency_graph.cpp',
    'mongo/base/make_string_vector.cpp',
    'mongo/base/parse_number.cpp',
    'mongo/base/status.cpp',
    'mongo/base/string_data.cpp',
    'mongo/bson/bson_validate.cpp',
    'mongo/bson/oid.cpp',
    'mongo/bson/optime.cpp',
    'mongo/bson/util/bson_extract.cpp',
    'mongo/buildinfo.cpp',
    'mongo/client/clientAndShell.cpp',
    'mongo/client/clientOnly.cpp',
    'mongo/client/connpool.cpp',
    'mongo/client/dbclient.cpp',
    'mongo/client/dbclient_rs.cpp',
    'mongo/client/replica_set_monitor.cpp',
    'mongo/client/dbclientcursor.cpp',
    'mongo/client/gridfs.cpp',
    'mongo/client/init.cpp',
    'mongo/client/sasl_client_authenticate.cpp',
    'mongo/client/syncclusterconnection.cpp',
    'mongo/db/jsobj.cpp',
    'mongo/db/json.cpp',
    'mongo/db/lasterror.cpp',
    'mongo/db/dbmessage.cpp',
    'mongo/db/server_options.cpp',
    'mongo/logger/console.cpp',
    'mongo/logger/log_manager.cpp',
    'mongo/logger/log_severity.cpp',
    'mongo/logger/logger.cpp',
    'mongo/logger/logstream_builder.cpp',
    'mongo/logger/message_event_utf8_encoder.cpp',
    'mongo/logger/message_log_domain.cpp',
    'mongo/logger/ramlog.cpp',
    'mongo/logger/rotatable_file_manager.cpp',
    'mongo/logger/rotatable_file_writer.cpp',
    'mongo/pch.cpp',
    'mongo/platform/backtrace.cpp',
    'mongo/platform/process_id.cpp',
    'mongo/platform/random.cpp',
    'mongo/util/assert_util.cpp',
    'mongo/util/background.cpp',
    'mongo/util/base64.cpp',
    'mongo/util/concurrency/spin_lock.cpp',
    'mongo/util/concurrency/synchronization.cpp',
    'mongo/util/concurrency/thread_name.cpp',
    'mongo/util/concurrency/mutexdebugger.cpp',
    'mongo/util/debug_util.cpp',
    'mongo/util/stacktrace.cpp',
    'mongo/util/fail_point.cpp',
    'mongo/util/fail_point_registry.cpp',
    'mongo/util/fail_point_service.cpp',
    'mongo/util/hex.cpp',
    'mongo/util/log.cpp',
    'mongo/util/md5.cpp',
    'mongo/util/password_digest.cpp',
    'mongo/util/net/httpclient.cpp',
    'mongo/util/net/listen.cpp',
    'mongo/util/net/message.cpp',
    'mongo/util/net/message_port.cpp',
    'mongo/util/net/sock.cpp',
    "mongo/util/net/socket_poll.cpp",
    'mongo/util/net/ssl_manager.cpp',
    'mongo/util/stringutils.cpp',
    'mongo/util/text.cpp',
    'mongo/util/time_support.cpp',
    'mongo/util/timer.cpp',
    'mongo/util/util.cpp',
    'mongo/util/version.cpp',
    'third_party/murmurhash3/MurmurHash3.cpp',
    ]

clientSourceTz = [] if clientEnv['MONGO_HAVE_TIMEGM'] else ['third_party/tz/timegm.c']

clientSourceSasl = [
    'mongo/client/sasl_client_authenticate_impl.cpp',
    'mongo/client/sasl_client_session.cpp',
    'mongo/client/sasl_sspi.cpp',
]

clientSourceAll = clientSourceBasic + clientSourceTz + clientSourceSasl

usingSasl = libEnv['MONGO_BUILD_SASL_CLIENT']

clientSource = list(clientSourceBasic)
if usingSasl:
    clientSource += clientSourceSasl

exampleSourceMap = [
        ('arrayExample', 'mongo/client/examples/arrayExample.cpp'),
        ('authTest', 'mongo/client/examples/authTest.cpp'),
        ('clientTest', 'mongo/client/examples/clientTest.cpp'),
        ('firstExample', 'mongo/client/examples/first.cpp'),
        ('httpClientTest', 'mongo/client/examples/httpClientTest.cpp'),
        ('insertDemo', 'mongo/client/examples/insert_demo.cpp'),
        ('rsExample', 'mongo/client/examples/rs.cpp'),
        ('secondExample', 'mongo/client/examples/second.cpp'),
        ('simpleClientDemo', 'mongo/client/examples/simple_client_demo.cpp'),
        ('tutorial', 'mongo/client/examples/tutorial.cpp'),
        ('whereExample', 'mongo/client/examples/whereExample.cpp'),
        ('bsondemo', 'mongo/bson/bsondemo/bsondemo.cpp'),
        ]

clientHeaders = [
    'mongo/base/disallow_copying.h',
    'mongo/base/error_codes.h',
    'mongo/base/parse_number.h',
    'mongo/base/status-inl.h',
    'mongo/base/status.h',
    'mongo/base/status_with.h',
    'mongo/base/string_data-inl.h',
    'mongo/base/string_data.h',
    'mongo/bson/bson-inl.h',
    'mongo/bson/bson.h',
    'mongo/bson/bson_builder_base.h',
    'mongo/bson/bson_db.h',
    'mongo/bson/bson_field.h',
    'mongo/bson/bsonelement.h',
    'mongo/bson/bsonmisc.h',
    'mongo/bson/bsonobj.h',
    'mongo/bson/bsonobjbuilder.h',
    'mongo/bson/bsonobjiterator.h',
    'mongo/bson/bsontypes.h',
    'mongo/bson/inline_decls.h',
    'mongo/bson/oid.h',
    'mongo/bson/optime.h',
    'mongo/bson/ordering.h',
    'mongo/bson/util/atomic_int.h',
    'mongo/bson/util/builder.h',
    'mongo/bson/util/misc.h',
    'mongo/client/connpool.h',
    'mongo/client/dbclient.h',
    'mongo/client/dbclient_rs.h',
    'mongo/client/dbclientcursor.h',
    'mongo/client/dbclientinterface.h',
    'mongo/client/export_macros.h',
    'mongo/client/gridfs.h',
    'mongo/client/init.h',
    'mongo/client/redef_macros.h',
    'mongo/client/sasl_client_authenticate.h',
    'mongo/client/syncclusterconnection.h',
    'mongo/client/undef_macros.h',
    'mongo/db/jsobj.h',
    'mongo/db/json.h',
    'mongo/db/server_options.h',
    'mongo/logger/appender.h',
    'mongo/logger/labeled_level.h',
    'mongo/logger/log_domain.h',
    'mongo/logger/log_manager.h',
    'mongo/logger/log_severity-inl.h',
    'mongo/logger/log_severity.h',
    'mongo/logger/logger.h',
    'mongo/logger/logstream_builder.h',
    'mongo/logger/message_event.h',
    'mongo/logger/message_log_domain.h',
    'mongo/logger/rotatable_file_manager.h',
    'mongo/logger/rotatable_file_writer.h',
    'mongo/logger/tee.h',
    'mongo/pch.h',
    'mongo/platform/atomic_intrinsics.h',
    'mongo/platform/atomic_intrinsics_gcc_generic.h',
    'mongo/platform/atomic_intrinsics_gcc_intel.h',
    'mongo/platform/atomic_intrinsics_win32.h',
    'mongo/platform/atomic_word.h',
    'mongo/platform/basic.h',
    'mongo/platform/compiler.h',
    'mongo/platform/compiler_gcc.h',
    'mongo/platform/compiler_msvc.h',
    'mongo/platform/cstdint.h',
    'mongo/platform/float_utils.h',
    'mongo/platform/hash_namespace.h',
    'mongo/platform/process_id.h',
    'mongo/platform/random.h',
    'mongo/platform/unordered_map.h',
    'mongo/platform/windows_basic.h',
    'mongo/util/allocator.h',
    'mongo/util/assert_util.h',
    'mongo/util/background.h',
    'mongo/util/bufreader.h',
    'mongo/util/concurrency/mutex.h',
    'mongo/util/concurrency/mutexdebugger.h',
    'mongo/util/concurrency/thread_name.h',
    'mongo/util/concurrency/threadlocal.h',
    'mongo/util/concurrency/ticketholder.h',
    'mongo/util/debug_util.h',
    'mongo/util/exit_code.h',
    'mongo/util/goodies.h',
    'mongo/util/heapcheck.h',
    'mongo/util/hex.h',
    'mongo/util/log.h',
    'mongo/util/mongoutils/str.h',
    'mongo/util/net/hostandport.h',
    'mongo/util/net/listen.h',
    'mongo/util/net/message.h',
    'mongo/util/net/message_port.h',
    'mongo/util/net/sock.h',
    'mongo/util/net/ssl_manager.h',
    'mongo/util/net/ssl_options.h',
    'mongo/util/time_support.h',
    'mongo/util/version.h',
]

mongoClientLibs = []
mongoClientLibDeps = ['$BUILD_DIR/third_party/shim_boost']
mongoClientSysLibDeps = []

if usingSasl:
    mongoClientSysLibDeps += ["sasl2"]
    if windows:
        mongoClientSysLibDeps += ["secur32"]

mongoClientInstalls = []
mongoClientPrefixInstalls = []

staticLibEnv = libEnv.Clone()
staticLibEnv.AppendUnique(
    LIBDEPS=mongoClientLibDeps,
    SYSLIBDEPS=mongoClientSysLibDeps)

if windows:
    # On Windows, we need to have the static library target and the
    # import library target have different names, so that they aren't
    # ambiguous to scons.  Boost names the static library libxxxx.lib and
    # the import library xxxx.lib, so we shall follow their lead.
    staticLibName = 'libmongoclient'
else:
    staticLibName = 'mongoclient'

mongoClientStaticLib = staticLibEnv.StaticLibrary(
    staticLibName, clientSource),
mongoClientInstalls.append(staticLibEnv.Install('#/', mongoClientStaticLib))

if installSetup.libraries:
    mongoClientPrefixInstalls.append(libEnv.Install("$INSTALL_DIR/lib", mongoClientStaticLib))

mongoClientSharedLib = None

if buildShared:

    # TODO: When we are ready to set a SONAME for mongoclient, set SHLIBVERSION=x.y.z in this
    # environment to enable SCons versioned shared library support, and then change the two
    # 'Install' calls in this block to 'InstallVersionedLibrary'. SHLIBVERSION and
    # InstallVersionedLibrary support is only stable in SCons > 2.3.0, so if you add support
    # here, be sure to add an EnsuredSconsVersion here as well.
    sharedLibEnv = libEnv.Clone()

    # This causes export macros to be applied, which we want only for shared librray or DLL builds.
    sharedLibEnv.Append(CPPDEFINES='LIBMONGOCLIENT_BUILDING')

    sharedLibEnv.AppendUnique(LIBS=mongoClientSysLibDeps)

    # On windows, we always want to attach the shim, since independently of whether we are
    # using the system or the vendored boost, the DLL must have complete symbols. For unix like
    # systems, attach the shim only if we are using the external libraries so that we pick up
    # what configure found for us. Otherwise, produce a dynamic object in which the boost
    # symbols are undefined, ensuring that we pass any necessary OS flags to produce such a
    # library.
    if windows or use_system_version_of_library("boost"):
        sharedLibEnv.AppendUnique(LIBDEPS=mongoClientLibDeps)
        if linux and not has_option('sanitize'):
            sharedLibEnv.AppendUnique(SHLINKFLAGS=["-Wl,--as-needed", "-Wl,-zdefs"])
    elif darwin:
        sharedLibEnv.AppendUnique(SHLINKFLAGS=["-Wl,-undefined,dynamic_lookup"])

    # On non-windows systems, we want to match the behavior of DLLs
    # and not export all symbols from the dynamic shared library unless 
    # they have been explicitly been marked.
    if not windows:
        sharedLibEnv.AppendUnique(CCFLAGS="-fvisibility=hidden")
        sharedLibEnv.AppendUnique(SHLINKFLAGS="-fvisibility=hidden")

    mongoClientSharedLib = sharedLibEnv.SharedLibrary('mongoclient', clientSource)

    mongoClientSharedLibInstall = sharedLibEnv.Install(
        '#/sharedclient', mongoClientSharedLib)

    if darwin:
        # Set up the copy of the client library in #/sharedclient so that things that link
        # against it record the local directory as the install_name.
        sharedLibEnv.AddPostAction(
            mongoClientSharedLibInstall,
            "install_name_tool -id @executable_path/%s %s" % (
                mongoClientSharedLibInstall[0].name,
                mongoClientSharedLibInstall[0]
            ))
    mongoClientInstalls.append(mongoClientSharedLibInstall)

    if installSetup.libraries:
        mongoClientSharedLibPrefixInstall = sharedLibEnv.Install(
            '$INSTALL_DIR/lib', mongoClientSharedLib)
        if darwin:
            sharedLibEnv.AddPostAction(
                mongoClientSharedLibPrefixInstall,
                "install_name_tool -id %s %s" % (
                    mongoClientSharedLibPrefixInstall[0],
                    mongoClientSharedLibPrefixInstall[0]
                ))
        mongoClientPrefixInstalls.append(mongoClientSharedLibPrefixInstall)

libEnv.Alias('mongoclient', mongoClientInstalls)

if installSetup.headers:
    inst = libEnv.InstallAs(['$INSTALL_DIR/include/' + x for x in clientHeaders], clientHeaders)
    libEnv.AddPostAction(inst, Chmod('$TARGET', 0644))
    mongoClientPrefixInstalls.append(inst);

if installSetup.headers or installSetup.libraries:
    installAlias = libEnv.Alias('install-mongoclient', mongoClientPrefixInstalls)

    # Build some simple mainlines that include the dbclient.h and bson.h headers
    # with things set up to include from the installation directory.

    # Remove anything that says $BUILD_DIR from the CPPPATH, except for things in third party,
    # so we have a source for boost, etc if we aren't getting them from the system.
    limit_cpppath=[p for p in libEnv['CPPPATH']
                   if not ('$BUILD_DIR' in p and not 'third_party' in p)]

    # Create an environment where include files and libraries come from the installation directory.
    installationTestEnv = libEnv.Clone(
        CPPPATH=["$INSTALL_DIR/include"] + limit_cpppath,
        LIBPATH=["$INSTALL_DIR/lib"],
        CPPDEFINES=[],
    )

    include_dbclienth_test_file = 'mongo/client/include_dbclienth_test.cpp'
    libEnv.Depends(include_dbclienth_test_file, installAlias)
    include_dbclienth_test = installationTestEnv.Object(
        target='include_dbclienth_test',
        source=[include_dbclienth_test_file])
    libEnv.AlwaysBuild(include_dbclienth_test)
    libEnv.NoCache(include_dbclienth_test)

    include_bsonh_test_file = 'mongo/client/include_bsonh_test.cpp'
    libEnv.Depends(include_bsonh_test_file, installAlias)
    include_bsonh_test = installationTestEnv.Object(
        target='include_bsonh_test',
        source=[include_bsonh_test_file])
    libEnv.AlwaysBuild(include_bsonh_test)
    libEnv.NoCache(include_bsonh_test)

    libEnv.Alias('check-install-mongoclient', [
        include_dbclienth_test,
        include_bsonh_test,
    ])

staticClientEnv = clientEnv.Clone()
staticClientEnv.PrependUnique(LIBDEPS=[mongoClientStaticLib])
staticClientEnv.AppendUnique(CPPDEFINES=['STATIC_LIBMONGOCLIENT'])

# Build each statically linked client program
staticClientPrograms = [
    staticClientEnv.Program(target, source) for (target, source) in exampleSourceMap
]

# Install them to the root, and append the install targets to the list of client tests
clientTests = staticClientEnv.Install("#/", staticClientPrograms)

# Do the same for the shared library case, if we are doing that.
if buildShared:
    sharedClientEnv = clientEnv.Clone()

    # Arrange for the tests to link against the mongoclient in the #/sharedclient directory,
    # and against boost explicitly rather than picking up the symbols through mongoclient.
    sharedClientEnv.PrependUnique(
        LIBS=['mongoclient'],
        LIBPATH=["#/sharedclient"],
        LIBDEPS=mongoClientLibDeps,
    )

    # Pretend that these files are shared object suffixed so they don't conflict with the objects
    # for the static library.
    sharedClientEnv['OBJSUFFIX'] = sharedClientEnv['OBJSUFFIX'] + 's'

    # Deal with the different lookup models between regular UNIX and Darwin. For regular unix,
    # we set $ORIGIN to pull the copy we run against from the current directory
    # (#/sharedclient). On Darwin, the staged copy of the mongoclient dylib in #sharedclient
    # has @executable path set as its install_name, so we pick up the same behavior that way.
    if nix and not darwin:
        sharedClientEnv.PrependUnique(
            LINKFLAGS="-Wl,-z,origin",
            RPATH=[sharedClientEnv.Literal("\\$$ORIGIN")])

    sharedClientPrograms = [
        sharedClientEnv.Program(
            "sharedclient/" + target, source) for (target, source) in exampleSourceMap]
    libEnv.Depends(sharedClientPrograms, mongoClientInstalls)

    sharedClientProgramInstalls = sharedClientEnv.Install("#/sharedclient", sharedClientPrograms)
    clientTests.extend(sharedClientProgramInstalls)

clientEnv.Alias('clientTests', clientTests)

# NOTE: There must be a mongod listening on 127.0.0.1:27999 (the traditional mongodb smoke test
# port) for the smokeClient target to run. In the server repo that is no problem, the smoke.py
# subsystem can start a mongod as needed. We don't have the same ability in this repo, since we
# have no way of reaching a mongod executable or knowing if it is safe to start one.
for clientTest in clientTests:
    # the rsExample test needs a replica set to talk to. The old smokeClient never tested it
    # The old smoke subsystem did test authTest, not sure why it isn't working now.
    if 'rsExample' in clientTest.abspath:
        continue

    # On windows, the installs also install PDB files, which we end up picking up.
    # Obviously, we can't run those, so filter them out. Lets hope nobody creates
    # a new client test with pdb in the name.
    if clientTest.abspath.endswith('.pdb'):
        continue

    clientEnv.AlwaysBuild(clientEnv.Alias(
        'smokeClient',
        [clientTest],
        "%s --port 27999" % clientTest.abspath))
