Import('defenv')
print("Using Microsoft tools configuration (%s)" % defenv.get('MSVS_VERSION','<Default>'))

### flags

import SCons
if SCons.__version__ == '1.3.0':
  # workaround for http://scons.tigris.org/issues/show_bug.cgi?id=2614
  # force inclusion of Platform SDK, sometimes ignored by SCons 1.3.0 due to environment contamination
  defenv.Tool('mssdk')

def entry(x,u):
	if x == 'WinMain' and u:
		x = 'w' + x 
	return '/entry:' + x

defenv['ENTRY_FLAG'] = entry
defenv['MAP_FLAG'] = '/map'
defenv['NODEFLIBS_FLAG'] = '/NODEFAULTLIB'
defenv['C_FLAG'] = '/TC'
defenv['CPP_FLAG'] = '/TP'
defenv['CPP_REQUIRES_STDLIB'] = 0
defenv['SUBSYS_CON'] = '/subsystem:console'
defenv['SUBSYS_WIN'] = '/subsystem:windows'
defenv['MSVCRT_FLAG'] = '/MD'
defenv['MSVCRT_NEEDSMANIFEST'] = False
defenv['STDCALL'] = '__stdcall'

msvs_version = float(defenv['MSVS_VERSION'].replace('Exp',''))
if msvs_version >= 8.0:
	defenv['EXCEPTION_FLAG'] = '/EHsc'
	defenv.Append(CCFLAGS = ['/GS-'])
	defenv.Append(CPPDEFINES = ['_CRT_SECURE_NO_WARNINGS', '_CRT_NONSTDC_NO_WARNINGS', '_CRT_SECURE_NO_DEPRECATE', '_CRT_NON_CONFORMING_SWPRINTFS'])
	defenv['MSVCRT_FLAG'] = '/MT' # Avoid msvcr?0.dll dependency
else:
	defenv['EXCEPTION_FLAG'] = '/GX'
if msvs_version < 10.0:
	# not even /ALIGN:512 works for vc10... fails to load process
	defenv.Append(LINKFLAGS = ['/opt:nowin98'])
if defenv['MSTOOLKIT'] and msvs_version < 8.0:
	defenv['MSVCRT_FLAG'] = '/ML' # TK2003 does not have all libs
if msvs_version >= 11.0:
	defenv['SUBSYS_CON'] = '/subsystem:console,5.01' # support windows xp
	defenv['SUBSYS_WIN'] = '/subsystem:windows,5.01' # support windows xp

def setoptimizeflags(env):
	if not env['DEBUG']:
		env.Append(CCFLAGS = ['/Gy'])
		env.Append(LINKFLAGS = ['/OPT:REF'])
		env.Append(LINKFLAGS = ['/OPT:ICF,9'])
		#if msvs_version >= 7.0:
		#	env.Append(CCFLAGS = ['/GL']) # FIXME: Creates problems with memset in VS v8.0 (CL v14.00)
		#	env.Append(LINKFLAGS = ['/LTCG'])
		return

### defines

defenv.Append(CPPDEFINES = [('NSISCALL', '$STDCALL')])

### asm

if 'x86' in defenv.get('TARGET_ARCH','x86'):
	defenv.Append(ASFLAGS = ['/coff']) # ML64 does not support /coff

### debug

if defenv['DEBUG']:
	defenv.Append(CCFLAGS = ['/Zi'])
	defenv.Append(CCFLAGS = ['/Fd${TARGET.dir}\\${TARGET.dir.file}.pdb'])
	defenv.Append(LINKFLAGS = ['/debug'])
	defenv['MSVCRT_FLAG'] = defenv['MSVCRT_FLAG'] + 'd'
elif msvs_version >= 14.0 or defenv['TARGET_ARCH'] == 'arm64':
		defenv.Append(LINKFLAGS = ['/nocoffgrpinfo'])

### workarounds

# Some Platform SDK version includes a bad version of libcp.lib.
# if stl usage causes link failure, copy the good libcp.lib
# from one of the other lib folders and use it instead.

confenv = defenv.Clone()
conf = confenv.Configure()

# For VS 2005 and up, the single threaded version of C runtime
# need not be explicitly linked.
if float(defenv['MSVS_VERSION'].replace('Exp','')) < 8.0:
  libcptest = """
  #include <fstream>
  int main() {
    // %s
    std::ofstream header("test", std::ofstream::out);
    return 0;
  }
  """

  conf.env.Prepend(LIBPATH = Dir('#/.sconf_temp').abspath)
  conf.env.Append(CCFLAGS = ['$EXCEPTION_FLAG'])

  if not conf.TryLink(libcptest % 'no change', '.cpp'):
    import os, shutil

    libdirs = (defenv['ENV']['LIB']).split(os.pathsep) + defenv['LIBPATH']

    for libdir in libdirs:
      libdir = confenv.subst(libdir)
      try:
        libcp = r'%s\libcp.lib' % libdir
        shutil.copy(libcp, Dir('#/.sconf_temp').abspath)
        if conf.TryLink(libcptest % (r'using %s' % libcp), '.cpp'):
          defenv.PrependENVPath('LIB', Dir('#/.sconf_temp').abspath)
          break
      except IOError:
        continue
    else:
      print("*** Couldn't find a good version of libcp.lib")
      Exit(2)

conf.Finish()

# Linking to msvcr?0.dll (/MD) might require a manifest

confenv = defenv.Clone()
conf = confenv.Configure()
if msvs_version >= 7.0:
    conf.env.Append(CCFLAGS = [defenv['MSVCRT_FLAG']])
    code = """
        #include <stdlib.h>
        int main() { return atoi("0"); }
    """
    result = True # TryLink should not fail but default to True just in case so distribution will fail as well
    if conf.TryLink(code, '.cpp'):
        import os
        result = os.path.exists('%s.manifest' % conf.lastTarget)
    defenv['MSVCRT_NEEDSMANIFEST'] = result
conf.Finish()

# SCons 3.0.5 started including /nologo for rc.exe but it's not always supported

confenv = defenv.Clone()
conf = confenv.Configure()
if not conf.TryAction('$RCCOM', '', '.rc')[0]:
    defenv['RCFLAGS'] = ''
conf.Finish()

### unicode
tdefenv = defenv.Clone()
if tdefenv['UNICODE']:
	tdefenv.Append(CPPDEFINES = ['_UNICODE', 'UNICODE'])

### stub environment

stub_env = defenv.Clone()

setoptimizeflags(stub_env)
stub_env.Append(CPPPATH = ['#$BUILD_CONFIG'])

if not defenv['DEBUG']:
  stub_env.Append(CCFLAGS = ['/O1'])               # optimize for size
stub_env.Append(CCFLAGS = ['/W3'])                 # level 3 warnings
stub_env.Append(CCFLAGS = ['/FAcs'])               # full listing files
stub_env.Append(CCFLAGS = ['/Fa${TARGET}.lst'])    # listing file name

stub_env.Append(LINKFLAGS = ['$NODEFLIBS_FLAG'])   # no default libraries
stub_env.Append(LINKFLAGS = ['$MAP_FLAG'])         # generate map file

if msvs_version >= 10.0:
  if stub_env['TARGET_ARCH'] != 'arm64': # LNK1246: '/FIXED' not compatible with 'ARM64' target machine
    stub_env.Append(LINKFLAGS = ['/FIXED']) # no relocations that our resource editor ignores

stub_uenv = stub_env.Clone()
stub_uenv.Append(CPPDEFINES = ['_UNICODE', 'UNICODE'])

### makensis environment

makensis_env = tdefenv.Clone()

makensis_env.Append(CPPPATH = ['#$BUILD_CONFIG'])

if not defenv['DEBUG']:
	makensis_env.Append(CCFLAGS = ['/O2'])           # optimize for speed
makensis_env.Append(CCFLAGS = ['$EXCEPTION_FLAG']) # enable exceptions
makensis_env.Append(CCFLAGS = ['/W3'])             # level 3 warnings
makensis_env.Append(CCFLAGS = ['/FAcs'])               # full listing files
makensis_env.Append(CCFLAGS = ['/Fa${TARGET}.lst'])    # listing file name
makensis_env.Append(CCFLAGS = [defenv['MSVCRT_FLAG']])

makensis_env.Append(LINKFLAGS = ['$MAP_FLAG'])     # generate map file
makensis_env.Append(LINKFLAGS = ['$SUBSYS_CON'])   # console executable
if defenv['UNICODE']:
	makensis_env.Append(LINKFLAGS = ['/STACK:2097152'])  # need 2 MB of stack in Unicode (default is 1 MB)

### plugin environment

plugin_env = defenv.Clone(no_import_lib = 1)

setoptimizeflags(plugin_env)
if defenv['TARGET_ARCH'] == 'amd64':
	plugin_env.Append(LINKFLAGS = ['/MACHINE:AMD64']) # Contrib\System\Resource will not link without this

if not defenv['DEBUG']:
	plugin_env.Append(CCFLAGS = ['/O1'])             # optimize for size
plugin_env.Append(CCFLAGS = ['/W3'])               # level 3 warnings

plugin_env.Append(LINKFLAGS = ['$MAP_FLAG'])       # generate map file

plugin_uenv = plugin_env.Clone()
plugin_uenv.Append(CPPDEFINES = ['_UNICODE', 'UNICODE'])

### util environment

util_env = tdefenv.Clone()

setoptimizeflags(util_env)
if not defenv['DEBUG']:
	util_env.Append(CCFLAGS = ['/O1'])            # optimize for speed
util_env.Append(CCFLAGS = ['/W3'])              # level 3 warnings
util_env.Append(CCFLAGS = [defenv['MSVCRT_FLAG']])

util_env.Append(LINKFLAGS = ['$MAP_FLAG'])      # generate map file

### cross-platform util environment

cp_util_env = util_env.Clone()

cp_util_env.Append(CPPPATH = ['#$BUILD_CONFIG'])

### test environment

test_env = defenv.Clone()

test_env.Append(CPPPATH = ['#$BUILD_CONFIG'])

### weird compiler requirements

def check_requirement(ctx, func, trigger):
	ctx.Message('Checking for %s requirement... ' % func)

	flags = ctx.env['LINKFLAGS']

	ctx.env.Append(LINKFLAGS = ['$NODEFLIBS_FLAG'])
	ctx.env.Append(LINKFLAGS = ['/entry:main'])

	test = """
		int main() {
			%s
			return 0;
		}
	""" % trigger

	result = not ctx.TryLink(test, '.c')
	ctx.Result(result)

	ctx.env['LINKFLAGS'] = flags

	return result

def add_file_to_emitter(env, emitter_name, file, obj_name=None):
	if obj_name is None:
		obj_name = emitter_name
	try:
		original_emitter = env[emitter_name]
		if type(original_emitter) == list:
			original_emitter = original_emitter[0]
	except KeyError:
		original_emitter = None

	def emitter(target, source, env):
		if original_emitter:
			target, source = original_emitter(target, source, env)

		if '$NODEFLIBS_FLAG' not in env['LINKFLAGS']:
			return target, source

		return target, source + env.Object(obj_name, file)

	env[emitter_name] = emitter

def add_file(file, obj_name=None):
	file = File(file)
	add_file_to_emitter(stub_env, 'PROGEMITTER', file, obj_name)
	add_file_to_emitter(stub_uenv, 'PROGEMITTER', file, obj_name)
	add_file_to_emitter(util_env, 'PROGEMITTER', file, obj_name)
	add_file_to_emitter(plugin_env, 'SHLIBEMITTER', file, obj_name)
	add_file_to_emitter(plugin_uenv, 'SHLIBEMITTER', file, obj_name)

#
# MSVC 6 SP6 doesn't like direct shifting of 64-bit integers.
# It generates a call to ___aullshr which requires libc, which
# we don't like. However, it does agree to work properly with
# a call to Int64ShrlMod32.
#

conf = stub_env.Configure()
conf.env.Append(LINKFLAGS = ['/entry:WinMain'])

int64test = """
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst,LPSTR lpszCmdParam, int nCmdShow) {
	ULARGE_INTEGER *i = 0;
	return (int)(i->QuadPart >> 10);
}
"""

if not conf.TryLink(int64test, '.c'):
	stub_env.Append(CPPDEFINES = ['_NSIS_NO_INT64_SHR'])
	stub_uenv.Append(CPPDEFINES = ['_NSIS_NO_INT64_SHR'])

conf.Finish()

#
# MSVC 2005 requires the memset CRT function to be present
# and the stubs might need memcpy (entry struct copy on x64)
# BUGBUG: The tests are currently broken on x64 and designed to fail!
#

conf = defenv.Configure(custom_tests = { 'CheckRequirement' : check_requirement })
if conf.CheckRequirement('memset', 'char c[128] = "test";switch(sizeof(void*)){case 8:break;case sizeof(void*):return 1;}'):
	add_file('memset.c', 'memset')

memcpy_test = """
switch(sizeof(void*)){case 8:break;case sizeof(void*):return 1;}
"""
if conf.CheckRequirement('memcpy', memcpy_test):
	add_file('memcpy.c', 'memcpy')
	stub_env.Append(CPPDEFINES = ['_NSIS_NODEFLIB_CRTMEMCPY'])
	stub_uenv.Append(CPPDEFINES = ['_NSIS_NODEFLIB_CRTMEMCPY'])

conf.Finish()

### return

Return('stub_env makensis_env plugin_env util_env cp_util_env test_env stub_uenv plugin_uenv')
