# -*- mode: python; -*-

# This SConscript describes build rules for the "mongo" project.

import os
import buildscripts.utils as utils

Import("env")
Import("shellEnv")
Import("testEnv")
Import("has_option")
Import("usesm usev8")
Import("installSetup getSysInfo")
Import("darwin windows solaris linux nix")

def add_exe( v ):
    return "${PROGPREFIX}%s${PROGSUFFIX}" % v

def setupBuildInfoFile( env, target, source, **kw ):
    version = utils.getGitVersion()
    sysInfo = getSysInfo()
    contents = '\n'.join([
        '#include "pch.h"',
        '#include <iostream>',
        '#include <boost/version.hpp>',
        'namespace mongo { const char * gitVersion(){ return "' + version + '"; } }',
        'namespace mongo { string sysInfo(){ return "' + sysInfo + ' BOOST_LIB_VERSION=" BOOST_LIB_VERSION ; } }',
        ])

    contents += '\n\n';

    out = open( str( target[0] ) , 'wb' )
    try:
        out.write( contents )
    finally:
        out.close()

env.AlwaysBuild( env.Command( 'buildinfo.cpp', [], setupBuildInfoFile ) )

# ------    SOURCE FILE SETUP -----------

commonFiles = [ "pch.cpp",
                "buildinfo.cpp",
                "db/indexkey.cpp",
                "db/jsobj.cpp",
                "bson/oid.cpp",
                "db/json.cpp",
                "db/lasterror.cpp",
                "db/nonce.cpp",
                "db/queryutil.cpp",
                "db/querypattern.cpp",
                "db/projection.cpp",
                "shell/mongo.cpp",
                "util/background.cpp",
                "util/intrusive_counter.cpp",
                "util/util.cpp",
                "util/file_allocator.cpp",
                 "util/assert_util.cpp",
                "util/log.cpp",
                "util/ramlog.cpp",
                "util/md5main.cpp",
                "util/base64.cpp",
                "util/concurrency/vars.cpp",
                "util/concurrency/task.cpp",
                "util/debug_util.cpp",
                 "util/concurrency/thread_pool.cpp",
                "util/password.cpp",
                "util/version.cpp",
                "util/signal_handlers.cpp",
                 "util/concurrency/rwlockimpl.cpp",
                "util/histogram.cpp",
                "util/concurrency/spin_lock.cpp",
                "util/text.cpp",
                "util/stringutils.cpp",
                 "util/concurrency/synchronization.cpp",
                "util/net/sock.cpp",
                "util/net/httpclient.cpp",
                "util/net/message.cpp",
                "util/net/message_port.cpp",
                "util/net/listen.cpp",
                "util/md5.cpp",
                "client/connpool.cpp",
                "client/dbclient.cpp",
                "client/dbclient_rs.cpp",
                "client/dbclientcursor.cpp",
                "client/model.cpp",
                "client/syncclusterconnection.cpp",
                "client/distlock.cpp",
                "s/shardconnection.cpp" ]

commonFiles += env['MONGO_COMMON_FILES']

env.StaticLibrary('mongocommon', commonFiles,
                  LIBDEPS=['$BUILD_DIR/third_party/pcrecpp'])

#mmap stuff

env.StaticLibrary("coredb", [ "db/commands.cpp" ])

coreServerFiles = [ "util/net/message_server_port.cpp",
                    "client/parallel.cpp",
                    "db/common.cpp",
                    "util/net/miniwebserver.cpp",
                    "db/dbwebserver.cpp",
                    "db/matcher.cpp",
                    "db/dbcommands_generic.cpp",
                    "db/commands/cloud.cpp",
                    "db/dbmessage.cpp",
                    "db/commands/pipeline.cpp",
                    "db/pipeline/accumulator.cpp",
                    "db/pipeline/accumulator_add_to_set.cpp",
                    "db/pipeline/accumulator_avg.cpp",
                    "db/pipeline/accumulator_first.cpp",
                    "db/pipeline/accumulator_last.cpp",
                    "db/pipeline/accumulator_min_max.cpp",
                    "db/pipeline/accumulator_push.cpp",
                    "db/pipeline/accumulator_single_value.cpp",
                    "db/pipeline/accumulator_sum.cpp",
                    "db/pipeline/builder.cpp",
                    "db/pipeline/doc_mem_monitor.cpp",
                    "db/pipeline/document.cpp",
                    "db/pipeline/document_source.cpp",
                    "db/pipeline/document_source_bson_array.cpp",
                    "db/pipeline/document_source_command_futures.cpp",
                    "db/pipeline/document_source_filter.cpp",
                    "db/pipeline/document_source_filter_base.cpp",
                    "db/pipeline/document_source_group.cpp",
                    "db/pipeline/document_source_limit.cpp",
                    "db/pipeline/document_source_match.cpp",
                    "db/pipeline/document_source_out.cpp",
                    "db/pipeline/document_source_project.cpp",
                    "db/pipeline/document_source_skip.cpp",
                    "db/pipeline/document_source_sort.cpp",
                    "db/pipeline/document_source_unwind.cpp",
                    "db/pipeline/expression.cpp",
                    "db/pipeline/expression_context.cpp",
                    "db/pipeline/field_path.cpp",
                    "db/pipeline/value.cpp",
                    "db/stats/counters.cpp",
                    "db/stats/service_stats.cpp",
                    "db/stats/top.cpp",
                    "db/commands/isself.cpp",
                    "db/security_common.cpp",
                    "db/security_commands.cpp",
                    "scripting/engine.cpp",
                    "scripting/utils.cpp",
                    "scripting/bench.cpp",
                    ] + env['MONGO_SCRIPTING_FILES']

if usesm:
    coreServerFiles.append( "scripting/engine_spidermonkey.cpp" )
elif usev8:
    coreServerFiles.append( Glob( "scripting/*v8*.cpp" ) )
else:
    coreServerFiles.append( "scripting/engine_none.cpp"  )

mmapFiles = [ "util/mmap.cpp" ]

if has_option( "mm" ):
    mmapFiles += [ "util/mmap_mm.cpp" ]
else:
    mmapFiles += [ "util/mmap_${OS_FAMILY}.cpp" ]

# handle processinfo*
processInfoFiles = [ "util/processinfo.cpp" ]

processInfoPlatformFile = env.File( "util/processinfo_${PYSYSPLATFORM}.cpp" )
# NOTE( schwerin ): This is a very un-scons-y way to make this decision, and prevents one from using
# code generation to produce util/processinfo_$PYSYSPLATFORM.cpp.
if not os.path.exists( str( processInfoPlatformFile ) ):
    processInfoPlatformFile = env.File( "util/processinfo_none.cpp" )

processInfoFiles.append( processInfoPlatformFile )

coreServerFiles += processInfoFiles

# handle systeminfo*
systemInfoPlatformFile = env.File( "util/systeminfo_${PYSYSPLATFORM}.cpp" )
# NOTE( schwerin ): This is a very un-scons-y way to make this decision, and prevents one from using
# code generation to produce util/systeminfo_$PYSYSPLATFORM.cpp.
if not os.path.exists( str( systemInfoPlatformFile ) ):
    systemInfoPlatformFile = env.File( "util/systeminfo_none.cpp" )

coreServerFiles.append( systemInfoPlatformFile )

if has_option( "asio" ):
    coreServerFiles += [ "util/net/message_server_asio.cpp" ]

# mongod files - also files used in tools. present in dbtests, but not in mongos and not in client libs.
serverOnlyFiles = [ "db/curop.cpp",
                    "db/d_globals.cpp",
                    "db/pagefault.cpp",
                    "util/compress.cpp",
                    "db/d_concurrency.cpp",
                    "db/key.cpp",
                    "db/btreebuilder.cpp",
                    "util/logfile.cpp",
                    "util/alignedbuilder.cpp",
                    "db/mongommf.cpp",
                    "db/dur.cpp",
                    "db/durop.cpp",
                    "db/dur_writetodatafiles.cpp",
                    "db/dur_preplogbuffer.cpp",
                    "db/dur_commitjob.cpp",
                    "db/dur_recover.cpp",
                    "db/dur_journal.cpp",
                    "db/introspect.cpp",
                    "db/btree.cpp",
                    "db/clientcursor.cpp",
                    "db/tests.cpp",
                    "db/repl.cpp",
                    "db/repl/rs.cpp",
                    "db/repl/consensus.cpp",
                    "db/repl/rs_initiate.cpp",
                    "db/repl/replset_commands.cpp",
                    "db/repl/manager.cpp",
                    "db/repl/health.cpp",
                    "db/repl/heartbeat.cpp",
                    "db/repl/rs_config.cpp",
                    "db/repl/rs_rollback.cpp",
                    "db/repl/rs_sync.cpp",
                    "db/repl/rs_initialsync.cpp",
                    "db/oplog.cpp",
                    "db/repl_block.cpp",
                    "db/btreecursor.cpp",
                    "db/cloner.cpp",
                    "db/namespace.cpp",
                    "db/cap.cpp",
                    "db/matcher_covered.cpp",
                    "db/dbeval.cpp",
                    "db/restapi.cpp",
                    "db/dbhelpers.cpp",
                    "db/instance.cpp",
                    "db/client.cpp",
                    "db/database.cpp",
                    "db/pdfile.cpp",
                    "db/record.cpp",
                    "db/cursor.cpp",
                    "db/security.cpp",
                    "db/queryoptimizer.cpp",
                    "db/queryoptimizercursor.cpp",
                    "db/extsort.cpp",
                    "db/index.cpp",
                    "db/scanandorder.cpp",
                    "db/geo/2d.cpp",
                    "db/geo/haystack.cpp",
                    "db/ops/count.cpp",
                    "db/ops/delete.cpp",
                    "db/ops/query.cpp",
                    "db/ops/update.cpp",
                    "db/dbcommands.cpp",
                    "db/dbcommands_admin.cpp",

                    # most commands are only for mongod
                    "db/commands/distinct.cpp",
                    "db/commands/find_and_modify.cpp",
                    "db/commands/group.cpp",
                    "db/commands/mr.cpp",
                    "db/commands/pipeline_command.cpp",
                    "db/commands/pipeline_d.cpp",
                    "db/commands/document_source_cursor.cpp",
                    "db/driverHelpers.cpp" ] + env['MONGO_SERVER_ONLY_FILES']

env.Library( "dbcmdline", "db/cmdline.cpp" )

serverOnlyFiles += mmapFiles

serverOnlyFiles += [ "db/stats/snapshots.cpp" ]

env.Library( "coreshard", [ "s/config.cpp",
                            "s/grid.cpp",
                            "s/chunk.cpp",
                            "s/shard.cpp",
                            "s/shardkey.cpp"] )

shardServerFiles = [
    "s/strategy.cpp",
    "s/strategy_shard.cpp",
    "s/strategy_single.cpp",
    "s/commands_admin.cpp",
    "s/commands_public.cpp",
    "s/request.cpp",
    "s/client.cpp",
    "s/cursors.cpp",
    "s/server.cpp",
    "s/config_migrate.cpp",
    "s/s_only.cpp",
    "s/stats.cpp",
    "s/balance.cpp",
    "s/balancer_policy.cpp",
    "s/writeback_listener.cpp",
    "s/shard_version.cpp",
    "s/mr_shard.cpp",
    "s/security.cpp",
    ]

serverOnlyFiles += [ "s/d_logic.cpp",
                     "s/d_writeback.cpp",
                     "s/d_migrate.cpp",
                     "s/d_state.cpp",
                     "s/d_split.cpp",
                     "client/distlock_test.cpp",
                     "s/d_chunk_manager.cpp",
                     "db/module.cpp",
                     "db/modules/mms.cpp", ]

env.StaticLibrary("defaultversion", "s/default_version.cpp")

env.StaticLibrary("serveronly", serverOnlyFiles, LIBDEPS=["coreshard", "dbcmdline", "defaultversion"])

modules = []
moduleNames = []

mongodOnlyFiles = [ "db/db.cpp", "db/compact.cpp" ]
if "win32" == os.sys.platform:
    mongodOnlyFiles.append( "util/ntservice.cpp" )

# ----- TARGETS ------

env.StaticLibrary("gridfs", "client/gridfs.cpp")

if has_option( 'use-cpu-profiler' ):
    coreServerFiles.append( 'db/commands/cpuprofile.cpp' )
    env.Append( LIBS=['profiler'] )

env.StaticLibrary("coreserver", coreServerFiles, LIBDEPS=["mongocommon"])

# main db target
mongod = env.Install( '#/', env.Program( "mongod", mongodOnlyFiles,
                                         LIBDEPS=["coreserver", "serveronly", "coredb"],
                                         _LIBDEPS='$_LIBDEPS_OBJS' ) )
Default( mongod )

# tools
allToolFiles = [ "tools/tool.cpp", "tools/stat_util.cpp" ]
env.StaticLibrary("alltools", allToolFiles, LIBDEPS=["serveronly", "coreserver", "coredb"])

normalTools = [ "dump", "restore", "export", "import", "stat", "top", "oplog" ]
env.Alias( "tools", [ "#/${PROGPREFIX}mongo" + x + "${PROGSUFFIX}" for x in normalTools ] )
for x in normalTools:
    env.Install( '#/', env.Program( "mongo" + x, [ "tools/" + x + ".cpp" ],
                                    LIBDEPS=["alltools"]) )

#some special tools
env.Install( '#/', [
        env.Program( "mongofiles", "tools/files.cpp", LIBDEPS=["alltools", "gridfs"] ),
        env.Program( "bsondump", "tools/bsondump.cpp", LIBDEPS=["alltools"]),
        env.Program( "mongobridge", "tools/bridge.cpp", LIBDEPS=["alltools"]),
        env.Program( "mongoperf", "client/examples/mongoperf.cpp", LIBDEPS=["alltools"] ),
        ] )

# mongos
mongos = env.Program( "mongos", shardServerFiles,
                      LIBDEPS=["coreserver", "coredb", "mongocommon", "coreshard", "dbcmdline"],
                      _LIBDEPS='$_LIBDEPS_OBJS' )
env.Install( '#/', mongos )

env.Library("clientandshell", "client/clientAndShell.cpp", LIBDEPS=["mongocommon", "coredb", "defaultversion", "gridfs"])
env.Library("allclient", "client/clientOnly.cpp", LIBDEPS=["clientandshell"])
# c++ library
clientLib = env.MergeLibrary( "mongoclient", ["allclient"] )
env.Install( '#/', clientLib )
clientLibName = str( clientLib[0] )
if has_option( "sharedclient" ):
    sharedClientLibName = str( env.SharedLibrary( "mongoclient", [], LIBDEPS=["allclient"], _LIBDEPS='$_LIBDEPS_OBJS' )[0] )

clientEnv = env.Clone();
clientEnv.Append( CPPPATH=["../"] )
clientEnv.Prepend( LIBS=[ clientLib ] )
clientEnv.Prepend( LIBPATH=["."] )
clientEnv["CPPDEFINES"].remove( "MONGO_EXPOSE_MACROS" )
l = clientEnv[ "LIBS" ]

# examples
clientTests = [
    clientEnv.Program( "firstExample", [ "client/examples/first.cpp" ] ),
    clientEnv.Program( "rsExample", [ "client/examples/rs.cpp" ] ),
    clientEnv.Program( "secondExample", [ "client/examples/second.cpp" ] ),
    clientEnv.Program( "whereExample", [ "client/examples/whereExample.cpp" ] ),
    clientEnv.Program( "authTest", [ "client/examples/authTest.cpp" ] ),
    clientEnv.Program( "httpClientTest", [ "client/examples/httpClientTest.cpp" ] ),
    clientEnv.Program( "bsondemo", [ "bson/bsondemo/bsondemo.cpp" ] ),
    ]

# dbtests test binary
env.StaticLibrary('testframework', ['dbtests/framework.cpp'])

test = testEnv.Install(
    '#/',
    testEnv.Program( "test", [ f for f in Glob( "dbtests/*.cpp" ) if not str( f ).endswith( 'framework.cpp' ) ],
                     LIBDEPS=["mongocommon", "serveronly", "coreserver", "coredb", "testframework" ] ) )

if len(testEnv.subst('$PROGSUFFIX')):
    testEnv.Alias( "test", "#/${PROGPREFIX}test${PROGSUFFIX}" )

env.Install( '#/', testEnv.Program( "perftest", [ "dbtests/perf/perftest.cpp" ], LIBDEPS=["serveronly", "coreserver", "coredb", "testframework" ] ) )
clientTests += [ clientEnv.Program( "clientTest", [ "client/examples/clientTest.cpp" ] ) ]

env.Install( '#/', clientTests )

# --- sniffer ---
mongosniff_built = False
if darwin or clientEnv["_HAVEPCAP"]:
    mongosniff_built = True
    sniffEnv = env.Clone()
    sniffEnv.Append( CPPDEFINES="MONGO_EXPOSE_MACROS" )

    if not windows:
        sniffEnv.Append( LIBS=[ "pcap" ] )
    else:
        sniffEnv.Append( LIBS=[ "wpcap" ] )

    sniffEnv.Install( '#/', sniffEnv.Program( "mongosniff", "tools/sniffer.cpp",
                                              LIBDEPS=["gridfs", "serveronly", "coreserver", "coredb"] ) )

# --- shell ---

# note, if you add a file here, you need to add it in scripting/engine.cpp and shell/msvc/createCPPfromJavaScriptFiles.js as well
env.Depends( "shell/dbshell.cpp",
             env.JSHeader( "shell/mongo.cpp" , 
                           Glob( "shell/utils*.js" ) + 
                           [ "shell/db.js","shell/mongo.js","shell/mr.js","shell/query.js","shell/collection.js"] ) )

env.JSHeader( "shell/mongo-server.cpp" , [ "shell/servers.js"] )

coreShellFiles = [ "shell/dbshell.cpp",
                   "shell/shell_utils.cpp",
                   "shell/mongo-server.cpp",
                   "../third_party/linenoise/linenoise.cpp"]

shellFilesToUse = coreShellFiles

if shellEnv is not None:
    mongo_shell = shellEnv.Program(
        "mongo",
         [ "shell/dbshell.cpp",
           "shell/shell_utils.cpp",
           "shell/mongo-server.cpp",
           "../third_party/linenoise/linenoise.cpp" ],
        LIBDEPS=["coreserver", "clientandshell",
                 "$BUILD_DIR/third_party/pcrecpp"] )

    shellEnv.Install( '#/', mongo_shell )

#  ----  INSTALL -------

# binaries

def checkGlibc(target,source,env):
    import subprocess
    stringProcess = subprocess.Popen( [ "strings", str( target[0] ) ], stdout=subprocess.PIPE )
    stringResult = stringProcess.communicate()[0]
    if stringResult.count( "GLIBC_2.4" ) > 0:
        print( "************* " + str( target[0] ) + " has GLIBC_2.4 dependencies!" )
        Exit(-3)

allBinaries = []

def installBinary( e, name ):
    if not installSetup.binaries:
        return

    global allBinaries

    name = add_exe( name )

    inst = e.Install( "$INSTALL_DIR/bin", name )

    allBinaries += [ name ]
    if (solaris or linux) and (not has_option("nostrip")):
        e.AddPostAction( inst, 'strip $TARGET' )

    if not has_option( "no-glibc-check" ) and linux and len( COMMAND_LINE_TARGETS ) == 1 and str( COMMAND_LINE_TARGETS[0] ) == "s3dist":
        e.AddPostAction( inst, checkGlibc )

    if nix:
        e.AddPostAction( inst, 'chmod 755 $TARGET' )

for x in normalTools:
    installBinary( env, "mongo" + x )
installBinary( env, "bsondump" )
installBinary( env, "mongoperf" )

if mongosniff_built:
    installBinary(env, "mongosniff")

installBinary( env, "mongod" )
installBinary( env, "mongos" )

if shellEnv is not None:
    installBinary( env, "mongo" )

env.Alias( "all", ['#/%s' % b for b in allBinaries ] )
env.Alias( "core", [ '#/%s' % b for b in [ add_exe( "mongo" ), add_exe( "mongod" ), add_exe( "mongos" ) ] ] )

#headers
if installSetup.headers:
    for id in [ "", "util/", "util/net/", "util/mongoutils/", "util/concurrency/", "db/", "db/stats/", "db/repl/", "db/ops/", "client/", "bson/", "bson/util/", "s/", "scripting/" ]:
        env.Install( "$INSTALL_DIR/" + installSetup.headerRoot + "/" + id, Glob( id + "*.h" ) )
        env.Install( "$INSTALL_DIR/" + installSetup.headerRoot + "/" + id, Glob( id + "*.hpp" ) )

if installSetup.clientSrc:
    print "ERROR!  NOT IMPLEMENTED: client source installation"
    Exit(1)

#lib
if installSetup.libraries:
    env.Install( "$INSTALL_DIR/$NIX_LIB_DIR", clientLibName )
    if has_option( "sharedclient" ):
        env.Install( "$INSTALL_DIR/$NIX_LIB_DIR", sharedClientLibName )

#textfiles
env.Install( "$INSTALL_DIR", installSetup.bannerFiles )

if installSetup.clientTestsDir:
    for x in os.listdir( installSetup.clientTestsDir ):
        full = installSetup.clientTestsDir + "/" + x
        if os.path.isdir( full ):
            continue
        if x.find( "~" ) >= 0:
            continue
        env.Install( '$INSTALL_DIR/' + installSetup.clientTestsDir, full )

#final alias
env.Alias( "install", "$INSTALL_DIR" )

# aliases
env.Alias( "mongoclient", '#/%s' % ( has_option( "sharedclient" ) and sharedClientLibName or clientLibName ) )

# client dist
def build_and_test_client(env, target, source):
    from subprocess import call

    installDir = env.subst('$INSTALL_DIR', target=target, source=source)
    if GetOption("extrapath") is not None:
        scons_command = ["scons", "--extrapath=" + GetOption("extrapath")]
    else:
        scons_command = ["scons"]

    call(scons_command + ["libmongoclient.a", "clientTests"], cwd=installDir)

    return bool(call(["python", "buildscripts/smoke.py",
                      "--test-path", installDir, "client"]))
env.Alias("clientBuild", [mongod, '$INSTALL_DIR'], [build_and_test_client])
env.AlwaysBuild("clientBuild")
