print("Using GNU tools configuration")

Import('defenv')

### imports

Import('FlagsConfigure GetOptionOrEnv GetStdSysEnvVarList')

### HACKS!
if GetOptionOrEnv('NSIS_SCONS_GNU_ENVPATHHACK'):
	import os
	defenv['ENV']['PATH'] = os.getenv('PATH') # Major hack!
	import_env = GetStdSysEnvVarList(path=True, temp=True)
	for var in import_env: 
		if var in os.environ:
			defenv['ENV'][var] = os.environ.get(var, '')
	#print(defenv.Dump())

### cross compiling

def cross_env(env):
	if env['PLATFORM'] != 'win32':
		env.Tool('crossmingw', toolpath = [Dir('../Tools').rdir()])

### flags

code_failonmswin = """
	#ifdef _WIN32
	#error Not supported on Windows
	#endif
"""

def entry(x,u):
	if defenv['TARGET_ARCH'] == 'x86':
		if x == 'NSISWinMainNOCRT':
			x = '_' + x
		elif x == 'DllMain':
			x = '_DllMain@12'
	return '-Wl,-e%s' % x

defenv['ENTRY_FLAG'] = entry
defenv['MAP_FLAG'] = '-Wl,-Map,${TARGET.base}.map'
defenv['EXCEPTION_FLAG'] = ''
defenv['NODEFLIBS_FLAG'] = '-nostdlib -Wl,--exclude-libs,msvcrt.a'
defenv['C_FLAG'] = '-xc'
defenv['CPP_FLAG'] = '-xc++'
defenv['ALIGN_FLAG'] = '-Wl,--file-alignment,512'
defenv['CPP_REQUIRES_STDLIB'] = 1
defenv['SUBSYS_CON'] = '-Wl,--subsystem,console'
defenv['SUBSYS_WIN'] = '-Wl,--subsystem,windows'
defenv['MSVCRT_FLAG'] = ''
defenv['STDCALL'] = '"__attribute__((__stdcall__))"'

# Don't allow mingw to link with LIBGCC*.DLL and LIBSTDC++-*.DLL
def configure_static_libs(env):
	env.Append(LINKFLAGS = ['-static-libgcc'])
	env.Append(LINKFLAGS = ['-static-libstdc++']) # MinGW GCC 4.5.2 warns about unrecognized option but it also actually needs it!

if defenv['PLATFORM'] == 'win32':
	configure_static_libs(defenv)

### defines

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

### helper functions

# on Mac OS X, programs built with g++ 4.0, stl and -s error out:
#   dyld: lazy symbol binding failed: lazy pointer not found
#   dyld: lazy pointer not found
#
# to avoid this, this function checks if -s works

def TestStrip(ctx):
	c = """
		#include <vector>

		int main() {
			std::vector<int> v;
			return 0;
		}
	"""
	ctx.CheckLinkFlag('-s', run = 1, extension = '.cpp', code = c)

### debug

if defenv['DEBUG']:
	defenv.Append(CCFLAGS = '-g')

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

### stub environment

stub_env = defenv.Clone()
cross_env(stub_env)

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

if not defenv['DEBUG']:
	stub_env.Append(CCFLAGS = ['-Os'])                # optimize for size
stub_env.Append(CCFLAGS = ['-Wall'])                # all warnings
stub_env.Append(CCFLAGS = ['-xc'])                  # force compile as c
stub_env.Append(CCFLAGS = ['-fno-strict-aliasing']) # not safe for strict aliasing

if not defenv['DEBUG'] and defenv['STRIP'] and defenv['STRIP_W32']:
	stub_env.Append(LINKFLAGS = ['-s'])               # strip
stub_env.Append(LINKFLAGS = ['-mwindows'])          # build windows executables
stub_env.Append(LINKFLAGS = ['$ALIGN_FLAG'])        # 512 bytes align
stub_env.Append(LINKFLAGS = ['$MAP_FLAG'])          # generate map file
conf = FlagsConfigure(stub_env)
conf.CheckCompileFlag('-fno-tree-loop-distribute-patterns')  # GCC 10: Don't generate msvcrt!memmove calls (bug #1248)
conf.CheckLinkFlag('-Wl,--disable-reloc-section') # binutils 2.36, ld will include a .reloc section by default (bug #1283)
conf.Finish()
stub_env.Append(LINKFLAGS = ['$NODEFLIBS_FLAG'])    # no standard libraries

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
makensis_env.Append(CFLAGS = ['-Wall'])                   # all warnings
makensis_env.Append(CXXFLAGS = ['-Wno-non-virtual-dtor']) # ignore virtual dtor warnings
makensis_env.Append(CXXFLAGS = ['-Wall'])                 # all warnings
makensis_env['STDCALL'] = ''                              # avoid warnings

conf = FlagsConfigure(makensis_env)
conf.CheckLinkFlag('$MAP_FLAG')                   # generate map file
if not defenv['DEBUG'] and defenv['STRIP'] and defenv['STRIP_CP']:
	TestStrip(conf)                                 # strip
conf.Finish()

### plugin environment

plugin_env = defenv.Clone()
cross_env(plugin_env)

if not defenv['DEBUG']:
	plugin_env.Append(CCFLAGS = ['-Os'])              # optimize for size
plugin_env.Append(CCFLAGS = ['-Wall'])              # level 3 warnings
plugin_env.Append(CCFLAGS = ['-fno-strict-aliasing']) # not safe for strict aliasing

if not defenv['DEBUG'] and defenv['STRIP'] and defenv['STRIP_W32']:
	plugin_env.Append(LINKFLAGS = ['-s'])             # strip
plugin_env.Append(LINKFLAGS = ['-mwindows'])        # build windows executables
plugin_env.Append(LINKFLAGS = ['$ALIGN_FLAG'])      # 512 bytes align
plugin_env.Append(LINKFLAGS = ['$MAP_FLAG'])        # generate map file
configure_static_libs(plugin_env)                   # remove libgcc*.dll & libstdc++*.dll dependency

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

### cross-platform util environment

if defenv['PLATFORM'] == 'win32':
	cp_util_env = tdefenv.Clone()
else:
	cp_util_env = defenv.Clone()

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

if cp_util_env['PLATFORM'] == 'win32':
	cp_util_env.Append(LINKFLAGS = ['$ALIGN_FLAG']) # 512 bytes align

if not defenv['DEBUG']:
	cp_util_env.Append(CCFLAGS = ['-O2'])             # optimize
cp_util_env.Append(CCFLAGS = ['-Wall'])             # all warnings
cp_util_env.Append(CCFLAGS = ['-fno-strict-aliasing']) # not safe for strict aliasing

conf = FlagsConfigure(cp_util_env)
conf.CheckLinkFlag('$MAP_FLAG')                   # generate map file
conf.Finish()

### util environment

util_env = tdefenv.Clone()
cross_env(util_env)

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

if not defenv['DEBUG']:
	util_env.Append(CCFLAGS = ['-O2'])             # optimize
util_env.Append(CCFLAGS = ['-Wall'])             # all warnings
util_env.Append(CCFLAGS = ['-fno-strict-aliasing']) # not safe for strict aliasing

util_env.Append(LINKFLAGS = ['-mwindows'])          # build windows executables
util_env.Append(LINKFLAGS = ['$ALIGN_FLAG'])        # 512 bytes align


conf = FlagsConfigure(util_env)
if not defenv['DEBUG'] and defenv['STRIP'] and defenv['STRIP_W32']:
	util_env.Append(LINKFLAGS = ['-s'])                   # strip
conf.Finish()

### cross-platform util environment adjustments

conf = FlagsConfigure(cp_util_env)
if not defenv['DEBUG'] and defenv['STRIP'] and defenv['STRIP_CP']:
	TestStrip(conf)                                 # strip
conf.Finish()

### test environment

test_env = defenv.Clone()
test_env['STDCALL'] = ''                                # avoid warnings
test_env.Append(CPPPATH = ['#$BUILD_CONFIG'])
conf = FlagsConfigure(test_env)
conf.Finish()

### weird GCC requirements

#
# GCC puts new PE sections, added by code, between other sections.
# This is not good for the .ndata section because makensis changes
# its size dynamically. This is not good if RVAs to sections below
# it are saved in other places. The RVAs will point to garbage.
#
# To fix this, a linker script is provided. The linker script makes
# sure the sections will be written in the correct order.
#

petype = 'pei-i386'
if defenv['TARGET_ARCH'] == 'amd64':
	petype = 'pei-x86-64'
stub_env.Append(LINKFLAGS = ['-B', petype]) # --oformat petype also works in GCC 4.5.2
stub_uenv.Append(LINKFLAGS = ['-B', petype])
stub_env.Append(LINKFLAGS = ['-T', File('linker_script').rfile()])
stub_uenv.Append(LINKFLAGS = ['-T', File('linker_script').rfile()])


#
# GCC requires some functions from the CRT to be present, if certain
# operations are done. For example, if a small string is assigned to
# a larger buffer, GCC 3.4+ uses memset to fill the remaining of the
# buffer with zeros.
#

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

	flags = ctx.env['LINKFLAGS']
	ctx.env.Append(LINKFLAGS = ['$NODEFLIBS_FLAG'])

	codeprepend = """
		#define CONFCHECK_CALLFUNC() check
		static int check() { %s }
	""" % trigger
	Import('GenerateTryLinkCode')
	code = GenerateTryLinkCode(codeprepend = codeprepend)
	result = not ctx.TryLink(code, '.c')
	ctx.Result(result)

	ctx.env['LINKFLAGS'] = flags
	return result

def add_file_to_emitter(env, emitter_name, file):
	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 + [file]

	env[emitter_name] = emitter

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

cenv = defenv.Clone()
cross_env(cenv)
conf = cenv.Configure(custom_tests = { 'CheckRequirement' : check_requirement })

memcpy_test = """
struct s { char c[128]; } t = { "test" }; // gcc 3
char a[] = {'/', 'F', 'I' ,'L', 'L', 'S', 'C', 'R', 'E', 'E', 'N', 0}; // gcc 4
int i;
for (i = 0; i < 100; i++) i += a[i % sizeof(a)] ^ t.c[i]; // avoid a and t being optimized out
return i;
"""

memset_test = """
char c[128] = "test";
c[0] = '6'; // avoid c being optimized out
return c[1]; // avoid c being optimized out
"""

if conf.CheckRequirement('memcpy', memcpy_test):
	add_file('memcpy.c')

if conf.CheckRequirement('memset', memset_test):
	add_file('memset.c')

conf.Finish()

#
# Some platforms, like FreeBSD, require -pthread flag to be passed
# instead of -lpthread.
#

conf = FlagsConfigure(makensis_env)
conf.CheckLinkFlag('-pthread', codeprepend = code_failonmswin)
conf.Finish()

#
# GCC doesn't define __BIG_ENDIAN__ or __LITTLE_ENDIAN__, so manually check
# for the endianness and define __BIG_ENDIAN__ if needed.
#

def check_big_endian(ctx):
	ctx.Message('Checking for __BIG_ENDIAN__... ')

	test = """
		int main() {
			#ifdef __BIG_ENDIAN__
				// already defined, no need to define again
				return 0;
			#else
				int i = 1;
				char *c = (char *) &i;
				return c[0] != 1;
			#endif
		}
	"""

	result = not ctx.TryRun(test, '.c')[0]
	ctx.Result(result)
	return result

conf = defenv.Configure(custom_tests = { 'CheckBigEndian' : check_big_endian })
if conf.CheckBigEndian():
	makensis_env.Append(CPPDEFINES = ['__BIG_ENDIAN__'])
	test_env.Append(CPPDEFINES = ['__BIG_ENDIAN__'])
conf.Finish()

if makensis_env['PLATFORM'] == 'hpux':
	makensis_env.Append(CPPDEFINES = ['NSIS_HPUX_ALLOW_UNALIGNED_DATA_ACCESS'])
	makensis_conf = makensis_env.Configure()
	makensis_conf.CheckLib("unalign")
	makensis_conf.CheckLib("hppa")
	makensis_conf.Finish()

### print version info
stub_env.Execute('$CC --version')
makensis_env.Execute('$CXX --version')

### return

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