# -*- mode: python -*-
# ET build script
# TTimo <ttimo@idsoftware.com>
# http://scons.sourceforge.net

import sys, os, time, commands, re, pickle, StringIO, popen2, commands, pdb, zipfile, string, tempfile
import SCons

import scons_utils

conf_filename='site.conf'
# choose configuration variables which should be saved between runs
# ( we handle all those as strings )
serialized=[ 'CC', 'CXX', 'JOBS', 'BUILD', 'BUILD_ROOT', 'TARGET_CGAME', 'TARGET_GAME', 'TARGET_UI', 'PROFILER', 'WITH_OMNIBOT' , 'WITH_LUA' ]

# help -------------------------------------------

Help("""
Usage: scons [OPTIONS] [TARGET] [CONFIG]

[OPTIONS] and [TARGET] are covered in command line options, use scons -H

[CONFIG]: KEY="VALUE" [...]
a number of configuration options saved between runs in the """ + conf_filename + """ file
erase """ + conf_filename + """ to start with default settings again

CC (default gcc)
CXX (default g++)
	Specify C and C++ compilers (defaults gcc and g++)
	ex: CC="gcc-3.3"
	You can use ccache and distcc, for instance:
	CC="ccache distcc gcc" CXX="ccache distcc g++"

JOBS (default 1)
	Parallel build

BUILD (default debug)
	Use debug-all/debug/release to select build settings
	ex: BUILD="release"
	debug-all: no optimisations, debugging symbols
	debug: -O -g
	release: all optimisations, including CPU target etc.
	
DEDICATED (default 2)
	Control regular / dedicated type of build:
	0 - client
	1 - dedicated server
	2 - both

TARGET_GAME (default 1)
	Build game module

TARGET_CGAME (default 1)
	Build cgame module

TARGET_UI (default 1)
	Build ui module

PROFILER (default 0)
	Build profiling module
	
WITH_OMNIBOT (default 1)
	Build with omnibot support

WITH_LUA (default 1)
	Build with LUA support
		
BUILD_ROOT (default 'build')
	change the build root directory
	
NOCONF (default 0, not saved)
	ignore site configuration and use defaults + command line only

COPYBINS (default 0, not saved)
	copy the binaries in a ready-to-release format

BUILDMPBIN (default 0, not saved)
	build mp_bin.pk3 using bin/ directories and game binaries for all platforms

BUILDBUNDLE (default 0, not saved)
	create mac bundle files
"""
)

# end help ---------------------------------------

# sanity -----------------------------------------

EnsureSConsVersion( 0, 96 )

# end sanity -------------------------------------

# system detection -------------------------------

# CPU type
cpu = commands.getoutput('uname -m')
dll_cpu = '???' # grmbl, alternative naming for .so
exp = re.compile('.*i?86.*')
if exp.match(cpu):
	cpu = 'x86'
	dll_cpu = 'i386'
else:
	cpu = commands.getoutput('uname -p')
	if ( cpu == 'powerpc' ):
		cpu = 'ppc'
		dll_cpu = cpu
	else:
		cpu = 'cpu'
		dll_cpu = cpu
OS = commands.getoutput( 'uname -s' )


# Jaybird - overrides of Intel Mac build
if (OS == 'Darwin') and ( cpu == 'powerpc' ):
	cpu = 'ppc'
	dll_cpu = cpu


# end system detection ---------------------------

# default settings -------------------------------

CC = 'gcc'
CXX = 'g++'
JOBS = '1'
BUILD = 'debug'
DEDICATED = '2'
TARGET_GAME = '1'
TARGET_CGAME = '1'
TARGET_UI = '1'
BUILD_ROOT = 'build'
NOCONF = '0'
COPYBINS = '0'
BUILDMPBIN = '0'
BUILDBUNDLE = '0'
PROFILER = '0'
WITH_OMNIBOT = '1'
WITH_LUA = '1'

# end default settings ---------------------------

# site settings ----------------------------------

if ( not ARGUMENTS.has_key( 'NOCONF' ) or ARGUMENTS['NOCONF'] != '1' ):
	site_dict = {}
	if (os.path.exists(conf_filename)):
		site_file = open(conf_filename, 'r')
		p = pickle.Unpickler(site_file)
		site_dict = p.load()
		print 'Loading build configuration from ' + conf_filename + ':'
		for k, v in site_dict.items():
			exec_cmd = k + '=\'' + v + '\''
			print '  ' + exec_cmd
			exec(exec_cmd)
else:
	print 'Site settings ignored'

# end site settings ------------------------------

# command line settings --------------------------

for k in ARGUMENTS.keys():
	exec_cmd = k + '=\'' + ARGUMENTS[k] + '\''
	print 'Command line: ' + exec_cmd
	exec( exec_cmd )

# end command line settings ----------------------

# arch detection ---------------------------------
# redeye - do arch and compiler detection after parsing command line options (e.g. if CC is set)

# Arkox - GCC 4.0.0 instead of 4.2 for Mac Intel Build (with 10.4u SDKs)
if (OS	== 'Darwin') and ( cpu == 'x86' ):
		CC = 'gcc-4.0'		# this is needed to compile correctly for Tiger

def gcc_major():
	major = os.popen( CC + ' -dumpversion' ).read().strip()
	print 'dumpversion: %s' % major
	major = re.sub('^([^.]+)\\..*$', '\\1', major)
	print 'gcc major: %s' % major
	return major

gcc_major_ver = gcc_major()

def gcc_is_mingw():
	mingw = os.popen( CC + ' -dumpmachine' ).read()
	return re.search('mingw', mingw) != None

if gcc_is_mingw():
	g_os = 'win32'
elif cpu == 'ppc':
	g_os = 'Darwin'
elif cpu == 'x86' and OS == 'Darwin':		# Arkox - if Mac Intel
	g_os = 'Darwin'
else:
	g_os = 'Linux'
print 'os: %s' % g_os
print 'cpu: ' + cpu

win32_build = (g_os == 'win32')

# end arch detection -----------------------------

# save site configuration ----------------------

if ( not ARGUMENTS.has_key( 'NOCONF' ) or ARGUMENTS['NOCONF'] != '1' ):
	for k in serialized:
		exec_cmd = 'site_dict[\'' + k + '\'] = ' + k
		exec(exec_cmd)

	site_file = open(conf_filename, 'w')
	p = pickle.Pickler(site_file)
	p.dump(site_dict)
	site_file.close()

# end save site configuration ------------------

# general configuration, target selection --------

if (win32_build):
	g_build = BUILD_ROOT + '-mingw/' + BUILD
else:
	g_build = BUILD_ROOT + '/' + BUILD

SConsignFile( 'scons.signatures' )

SetOption('num_jobs', JOBS)

if ( OS == 'Linux' ):
	LINK = CC
else:
	LINK = CXX

# common flags
# BASE + GAME + OPT for game

BASECPPFLAGS = [ ]
CORECPPPATH = [ ]
CORELIBPATH = [ ]
CORECPPFLAGS = [ ]
GAMECPPFLAGS = [ ]
BASELINKFLAGS = [ ]
CORELINKFLAGS = [ ]

# for release build, further optimisations that may not work on all files
OPTCPPFLAGS = [ ]

if ( OS == 'Darwin' ):
	# taken from xcode's default project settings
	#BASECPPFLAGS += [ '-D__MACOS__', '-Wno-long-double', '-arch', 'ppc', '-arch', 'i386', '-fasm-blocks', '-fpascal-strings', '-faltivec', '-Wno-unknown-pragmas', '-flat_namespace' ]
	BASECPPFLAGS += [ '-D__MACOS__', '-arch', 'ppc', '-arch', 'i386', '-fasm-blocks', '-fpascal-strings', '-faltivec', '-Wno-unknown-pragmas', '-flat_namespace' ]

BASECPPFLAGS.append( '-pipe' )
# warn all
BASECPPFLAGS.append( '-Wall' )
# don't wrap gcc messages
BASECPPFLAGS.append( '-fmessage-length=0' )

# Arkox : Mac Universal Binary (10.4 Tiger to 10.5 Leopard)
# ilDuca : but only for darwin...
if ( OS == 'Darwin' ):
	BASECPPFLAGS.append( '-isysroot' )
	BASECPPFLAGS.append( '/Developer/SDKs/MacOSX10.4u.sdk' )
	#BASECPPFLAGS.append( '/Developer/SDKs/MacOSX10.5.sdk' )


if ( BUILD == 'debug-all' ):
	BASECPPFLAGS.append( '-g' )
	BASECPPFLAGS.append( '-O0' )
	BASECPPFLAGS.append( '-D_DEBUG' )
elif ( BUILD == 'debug' ):
	BASECPPFLAGS.append( '-g' )
	BASECPPFLAGS.append( '-O1' )
	BASECPPFLAGS.append( '-D_DEBUG' )
elif ( BUILD == 'release' ):
	BASECPPFLAGS.append( '-DNDEBUG' )
	if ( OS == 'Linux' ):
		# -fomit-frame-pointer: gcc manual indicates -O sets this implicitely,
		# only if that doesn't affect debugging
		# on Linux, this affects backtrace capability, so I'm assuming this is needed
		# -finline-functions: implicit at -O3
		# -fschedule-insns2: implicit at -O3
		# -funroll-loops ?
		# -mfpmath=sse -msse ?
		OPTCPPFLAGS = [ '-O3', '-march=i686', '-Winline', '-Wno-unused-value', '-ffast-math', '-fomit-frame-pointer', '-finline-functions', '-fschedule-insns2' ]
elif ( OS == 'Darwin' ):
	OPTCPPFLAGS = [ '-O3', '-falign-functions=16', '-falign-loops=16', '-finline' ]		
else:
	print 'Unknown build configuration ' + BUILD
	sys.exit(0)

# create the build environments
g_base_env = scons_utils.NQEnvironment( ENV = os.environ, CC = CC, CXX = CXX, LINK = LINK, CPPFLAGS = BASECPPFLAGS, LINKFLAGS = BASELINKFLAGS, CPPPATH = CORECPPPATH, LIBPATH = CORELIBPATH )
scons_utils.SetupUtils( g_base_env )

g_env = g_base_env.Copy()

g_env['CPPFLAGS'] += OPTCPPFLAGS
g_env['CPPFLAGS'] += CORECPPFLAGS
g_env['LINKFLAGS'] += CORELINKFLAGS

if ( OS == 'Darwin' ):
	# configure for dynamic bundle
	g_env['SHLINKFLAGS'] = '$LINKFLAGS -bundle -flat_namespace -undefined suppress -arch ppc -arch i386 -Wl,-syslibroot,/Developer/SDKs/MacOSX10.4u.sdk'
	g_env['SHLIBSUFFIX'] = '.so'

# maintain this dangerous optimization off at all times
g_env.Append( CPPFLAGS = '-fno-strict-aliasing' )

# redeye: use idBuffering.buffered_spawn() only on Scons < 1.2 because it's broken with Scons 1.2
# Without buffering compiler warnings/errors get mixed with the compiler command line,
# not nice but also nothing critical
if ( int(JOBS) > 1 ):
	version = string.split(SCons.__version__, '.')
	v_major = int(version[0])
	v_minor = int(re.match('\d+', version[1]).group())
	if (v_major < 1) or (v_major >= 1 and v_minor < 2):
		print 'Using buffered process output'
		scons_utils.SetupBufferedOutput( g_env )

# mark the globals

GLOBALS = 'g_env OS g_os BUILD gcc_major_ver cpu WITH_OMNIBOT WITH_LUA'

# end general configuration ----------------------

# output control --------------------------

if ARGUMENTS.get('VERBOSE') != '1':
	g_env['SHCCCOMSTR'] = "CC $TARGET"
	g_env['SHCXXCOMSTR'] = "CXX $TARGET"
	g_env['SHLINKCOMSTR'] = "LD $TARGET"

# end output control --------------------------

# win32 cross compilation ----------------------

if g_os == 'win32' and os.name != 'nt':
	# mingw doesn't define the cpu type, but our only target is x86
	g_env.Append(CPPDEFINES = '_M_IX86=400')
	g_env.Append(LINKFLAGS = '-static-libgcc')
	# scons doesn't support cross-compiling, so set these up manually
	g_env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
	g_env['WIN32DEFSUFFIX']	= '.def'
	g_env['PROGSUFFIX']	= '.exe'
	g_env['SHLIBSUFFIX']	= '.dll'
	g_env['SHCCFLAGS']	= '$CCFLAGS'

# end win32 cross compilation ------------------

# targets ----------------------------------------

toplevel_targets = []

if ( TARGET_GAME == '1' ):
	Export( 'GLOBALS ' + GLOBALS )
	BuildDir( g_build + '/game', '.', duplicate = 0 )
	game = SConscript( g_build + '/game/SConscript.game' )
	if ( win32_build == 1 ):
		toplevel_targets.append( InstallAs( '#qagame_mp_x86.dll', game ) )
	else:
		toplevel_targets.append( InstallAs( '#qagame.mp.%s.so' % dll_cpu, game ) )

if ( TARGET_CGAME == '1' ):
	Export( 'GLOBALS ' + GLOBALS )
	BuildDir( g_build + '/cgame', '.', duplicate = 0 )
	cgame = SConscript( g_build + '/cgame/SConscript.cgame' )
	if ( win32_build == 1 ):
		toplevel_targets.append( InstallAs( '#cgame_mp_x86.dll', cgame ) )
	else:
		toplevel_targets.append( InstallAs( '#cgame.mp.%s.so' % dll_cpu, cgame ) )

if ( TARGET_UI == '1' ):
	Export( 'GLOBALS ' + GLOBALS )
	BuildDir( g_build + '/ui', '.', duplicate = 0 )
	ui = SConscript( g_build + '/ui/SConscript.ui' )
	if ( win32_build == 1 ):
		toplevel_targets.append( InstallAs( '#ui_mp_x86.dll', ui ) )
	else:
		toplevel_targets.append( InstallAs( '#ui.mp.%s.so' % dll_cpu, ui ) )

if ( PROFILER == '1' ):
	Export( 'GLOBALS ' + GLOBALS )
	BuildDir( g_build + '/profiler', '.', duplicate = 0 )
	profiler = SConscript( g_build + '/profiler/SConscript.profiler' )
	if ( win32_build == 1 ):
		toplevel_targets.append( InstallAs( '#profiler_mp_x86.dll', profiler ) )
	else:
		toplevel_targets.append( InstallAs( '#profiler.mp.%s.so' % dll_cpu, profiler ) )

class CopyBins(scons_utils.idSetupBase):
	def copy_bins( self, target, source, env ):
		for i in source:
			j = os.path.normpath( os.path.join( os.path.dirname( i.abspath ), '../bin', os.path.basename( i.abspath ) ) )
			self.SimpleCommand( 'cp ' + i.abspath + ' ' + j )
			if ( OS == 'Linux' ):
				self.SimpleCommand( 'strip ' + j )
			else:
				# see strip and otool man pages on mac
				self.SimpleCommand( 'strip -ur ' + j )

copybins_target = []
if ( COPYBINS != '0' ):
	copy = CopyBins()
	copybins_target.append( Command( 'copybins', toplevel_targets, Action( copy.copy_bins ) ) )

class MpBin(scons_utils.idSetupBase):
	def mp_bin( self, target, source, env ):
		temp_dir = tempfile.mkdtemp( prefix = 'mp_bin' )
		self.SimpleCommand( 'cp ../bin/ui* ' + temp_dir )
		self.SimpleCommand( 'cp ../bin/cgame* ' + temp_dir )
		# zip the mac bundles
		mac_bundle_dir = tempfile.mkdtemp( prefix = 'mp_mac' )
		self.SimpleCommand( 'cp -R "../bin/Wolfenstein ET.app/Contents/Resources/ui_mac.bundle" ' + mac_bundle_dir )
		self.SimpleCommand( 'cp -R "../bin/Wolfenstein ET.app/Contents/Resources/cgame_mac.bundle" ' + mac_bundle_dir )
		self.SimpleCommand( 'find %s -name \.svn | xargs rm -rf' % mac_bundle_dir )
		self.SimpleCommand( 'cd %s ; zip -r -D %s/ui_mac.zip ui_mac.bundle ; mv %s/ui_mac.zip %s/ui_mac' % ( mac_bundle_dir, temp_dir, temp_dir, temp_dir ) )
		self.SimpleCommand( 'cd %s ; zip -r -D %s/cgame_mac.zip cgame_mac.bundle ; mv %s/cgame_mac.zip %s/cgame_mac' % ( mac_bundle_dir, temp_dir, temp_dir, temp_dir ) )
		mp_bin_path = os.path.abspath( os.path.join ( os.getcwd(), '../etmain/mp_bin.pk3' ) )
		self.SimpleCommand( 'cd %s ; zip -r -D %s *' % ( temp_dir, mp_bin_path ) )

if ( BUILDMPBIN != '0' ):
	mp_bin = MpBin()
	mpbin_target = Command( 'mp_bin', toplevel_targets + copybins_target, Action( mp_bin.mp_bin ) )

class BuildBundle(scons_utils.idSetupBase):
	def make_bundle( self, target, source, env ):
		for i in source:
			self.SimpleCommand( './makebundle.sh %s' % i )

if ( BUILDBUNDLE != '0' ):
	build_bundle = BuildBundle()
	bundle_target = Command( 'build_bundle', toplevel_targets, Action( build_bundle.make_bundle ) )

# end targets ------------------------------------

