# -*- python -*-
# SConstruct
# libsigx build script
#
# Process this file with 'scons' to build the project.
# For more information, please visit: http://www.scons.org/ .
#
# Usage:
#
#   . scons               build the library
#   . scons -h            see available configuration options
#   . scons opt=value     set a configuration option
#   . scons install       install library and include files (as root)
#   . scons tests				build test programs
#   . scons examples		build example programs
#   . scons dist          build a source package (.tar.bz2)
##   . scons docs          build documentation for the project (Doxygen)
#   . scons debian        help building a debian package, read and change the SConscript file in the debian/ directory!
#   . scons -c            clean intermediate files
#   . scons -c distclean  clean all target files
#


# scons version >0.98 is required, we need the NoClean function (0.97) and the AddMethod function (0.98)
EnsureSConsVersion(0, 98)

#import commands
import os, sys, re, string, glob, SCons.Node.FS

SConsignFile()


def Check_cxx_shared_ptr(context):
	context.Message('Checking for std::tr1::shared_ptr ...')
	ret = context.TryCompile("""
#include <tr1/memory>
int main()
{
	std::tr1::shared_ptr<int> p(new int(42));
	return 0;
}
""", '.cpp')
	context.Result(ret)
	return ret


subst_dict = { }
config_subst_dict = { }


#############
#  Options  #
#############

# explicit Command line options
opts = Options('options.cache')

opts.AddOptions(
	('DESTDIR', 'Destination root directory (useful for package maintainers)', ''), 
	PathOption('PREFIX', 'Installation prefix directory', '/usr/local'), 
	BoolOption('DEBUG', 'Debug version (useful for developers only)', 0), 
	BoolOption('SHAREDLIB', 'build a shared library, otherwise a static library', 1), 
	("TR1_INCLUDE_PATH", "Include path for c++ tr1\n(if your compiler has no tr1 or tr1 isn't in the standard include path)"), 
	("BOOST_INCLUDE_PATH", "Include path for c++ boost\nif boost isn't in the standard include path)")
)

###############################
#  Configuration Environment  #
###############################

# pull the whole environment in as construction variables;
# this allows us to get various environment variables like CXX, CXXFLAGS
construction_vars = os.environ.copy()
# ... and update the construction variables with explicitly specified arguments
construction_vars.update(**ARGUMENTS)

env = Environment(options = opts, tools = ['default', 'versionedlibrary', 'symlink'], ENV = os.environ, **dict(construction_vars))

if env.has_key('TR1_INCLUDE_PATH'):
	env.Append(CPPPATH = env['TR1_INCLUDE_PATH'])
if env.has_key('BOOST_INCLUDE_PATH'):
	env.Append(CPPPATH = env['BOOST_INCLUDE_PATH'])



# don't configure when cleaning
if not env.GetOption('clean'):
	conf = Configure(env, custom_tests = { 'Check_cxx_shared_ptr' : Check_cxx_shared_ptr })
	env.Append(SIGX_HAVE_CXX_SHARED_PTR = conf.Check_cxx_shared_ptr())
	env.Append(SIGX_HAVE_BOOST = conf.CheckCXXHeader('boost/mpl/eval_if.hpp'))
	if not env['SIGX_HAVE_CXX_SHARED_PTR']:
		print '<tr1/memory> header not found'
		Exit(1)
	if not env['SIGX_HAVE_BOOST']:
		print 'boost not found'
		Exit(1)
	env = conf.Finish()


# remember whether user called: scons -c distclean
if 'distclean' in COMMAND_LINE_TARGETS:
	do_distclean = True
else:
	do_distclean = False


env.Append(MAJOR='2', MINOR='0', MICRO='1')
env.Append(PACKAGE_NAME = 'sigx')
env.Append(VERSIONED_NAME = "%s-%s.%s" % (env['PACKAGE_NAME'], env['MAJOR'], env['MINOR']))

env.Append(CPPPATH = [ '#' ])

#env.Append(TARFLAGS = ['-c'])
#env.Append(TARFLAGS = ['--bzip2'])

# convert CXXFLAGS to an array because we append flags to it using lists
if env.has_key('CXXFLAGS') and type(env['CXXFLAGS']) == str:
	env['CXXFLAGS'] = env['CXXFLAGS'].split(' ');

if env['DEBUG'] == 1:
	env.Append(CXXFLAGS = ['-g'])
	env.Append(CXXFLAGS = ['-O0'])
	env.Append(CXXFLAGS = ['-W'])
	env.Append(CXXFLAGS = ['-Wall'])
	env.Append(CXXFLAGS = ['-ansi'])
	env.Append(CXXFLAGS = ['-pedantic'])
	env.Append(CXXFLAGS = ['-Wpointer-arith'])
	env.Append(CXXFLAGS = ['-Woverloaded-virtual'])
	env.Append(CXXFLAGS = ['-Wconversion'])
	env.Append(CXXFLAGS = ['-Wcast-align'])
	env.Append(CXXFLAGS = ['-Wshadow'])
else:
	env.Append(CXXFLAGS = ['-O2'])
	env.Append(CXXFLAGS = ['-DG_DISABLE_ASSERT=1'])
	#-fomit-frame-pointer -fmove-all-movables -fstrict-aliasing

# lib dependencies.
# we do depend on sigc-2.0 and glibmm;
# note that sigc is included in glibmm
env.ParseConfig ('pkg-config --cflags --libs glibmm-2.4')

# Generate help text for command line options
Help(opts.GenerateHelpText(env))

# Cache current options
opts.Save('options.cache', env)


##############
#  Builders  #
##############

def do_subst_in_file(targetfile, sourcefile, dict):
        """Replace all instances of the keys of dict with their values.
        For example, if dict is {'%VERSION%': '1.2345', '%BASE%': 'MyProg'},
        then all instances of %VERSION% in the file will be replaced with 1.2345 etc.
        """
        try:
            f = open(sourcefile, 'rb')
            contents = f.read()
            f.close()
        except:
            raise SCons.Errors.UserError, "Can't read source file %s"%sourcefile
        for (k,v) in dict.items():
            contents = re.sub(k, v, contents)
        try:
            f = open(targetfile, 'wb')
            f.write(contents)
            f.close()
        except:
            raise SCons.Errors.UserError, "Can't write target file %s"%targetfile
        return 0 # success
 
def subst_in_file(target, source, env):
        if not env.has_key('SUBST_DICT'):
            raise SCons.Errors.UserError, "SubstInFile requires SUBST_DICT to be set."
        d = dict(env['SUBST_DICT']) # copy it
        for (k,v) in d.items():
            if callable(v):
                d[k] = env.subst(v())
            elif SCons.Util.is_String(v):
                d[k]=env.subst(v)
            else:
                raise SCons.Errors.UserError, "SubstInFile: key %s: %s must be a string or callable"%(k, repr(v))
        for (t,s) in zip(target, source):
            return do_subst_in_file(str(t), str(s), d)
 
def subst_in_file_string(target, source, env):
        """This is what gets printed on the console."""
        return '\n'.join(['Substituting vars from %s into %s'%(str(s), str(t))
                          for (t,s) in zip(target, source)])
 
def subst_emitter(target, source, env):
        """Add dependency from substituted SUBST_DICT to target.
        Returns original target, source tuple unchanged.
        """
        d = env['SUBST_DICT'].copy() # copy it
        for (k,v) in d.items():
            if callable(v):
                d[k] = env.subst(v())
            elif SCons.Util.is_String(v):
                d[k]=env.subst(v)
        Depends(target, SCons.Node.Python.Value(d))
        # Depends(target, source) # this doesn't help the install-sapphire-linux.sh problem
        return target, source
 
subst_action = Action (subst_in_file, subst_in_file_string)
subst_builder = Builder(action=subst_action, emitter=subst_emitter)
env.Append (BUILDERS = {'SubstInFile' : subst_builder})


#
# source tar file builder
#

def distcopy (target, source, env):
    treedir = str (target[0])

    try:
        os.mkdir (treedir)
    except OSError, (errnum, strerror):
        if errnum != errno.EEXIST:
            print 'mkdir ', treedir, ':', strerror

    cmd = 'tar cf - '
    #
    # we don't know what characters might be in the file names
    # so quote them all before passing them to the shell
    #
    all_files = ([ str(s) for s in source ])
    cmd += " ".join ([ "'%s'" % quoted for quoted in all_files])
    cmd += ' | (cd ' + treedir + ' && tar xf -)'
    p = os.popen (cmd)
    return p.close ();

def tarballer (target, source, env):            
    cmd = 'tar cjf ' + str (target[0]) +  ' ' + str(source[0]) + "  --exclude-from='sigx_tar_exclude_these_files.txt'"
    print 'running ', cmd, ' ... '
    p = os.popen (cmd)
    return p.close ()

dist_bld = Builder (action = distcopy,
                    target_factory = SCons.Node.FS.default_fs.Entry,
                    source_factory = SCons.Node.FS.default_fs.Entry,
                    multi = 1)

tarball_bld = Builder (action = tarballer,
                       target_factory = SCons.Node.FS.default_fs.Entry,
                       source_factory = SCons.Node.FS.default_fs.Entry)

env.Append (BUILDERS = {'Distribute' : dist_bld})
env.Append (BUILDERS = {'Tarball' : tarball_bld})

# Documentation generation system
doxygen_builder = Builder(action = 'doxygen $SOURCE')
env.Append(BUILDERS = { 'DoxygenDoc' : doxygen_builder })

# M4 builder
# note that scons has a built-in m4 builder but I define here my own because 
# the built-in one feeds sigx's m4 files through stdin which produces the wrong
# header file guard

m4_builder = Builder(action = 'm4 -Imacros $SOURCE > $TARGET')
env.Append(BUILDERS = { 'M4' : m4_builder })

########################
#  Installation rules  #
########################

env.Append(INSTALL_PREFIX = env['DESTDIR'] + env['PREFIX'])
env.Append(LIBDIR = "%s/lib" % env['INSTALL_PREFIX'])
env.Append(LIBINCLUDEDIR = "%s/lib/%s/" % (env['INSTALL_PREFIX'], env['VERSIONED_NAME']))
env.Append(INCLUDEDIR = "%s/include/%s/sigx" % (env['INSTALL_PREFIX'], env['VERSIONED_NAME']))

installpaths = [ env['LIBDIR'], env['LIBINCLUDEDIR'], env['INCLUDEDIR'] ]



###########################
#  Configuration summary  #
###########################

debugging = ""

if env['DEBUG']:
	debugging = "ON"
else:
	debugging = "OFF"

libtype = ""

if env['SHAREDLIB']:
	libtype = "shared"
else:
	libtype = "static"



print ""
print "+=================+"
print "|  CONFIGURATION  |"
print "+=================+"
print ""
print "Installation prefix      : " + env['PREFIX']
print "libdir                   : " + env['LIBDIR']
print "libincludedir            : " + env['LIBINCLUDEDIR']
print "includedir               : " + env['INCLUDEDIR']
print "Debugging mode           : " + debugging
if env.has_key('TR1_INCLUDE_PATH'):
	print "tr1 include path         : " + env['TR1_INCLUDE_PATH']
if env.has_key('BOOST_INCLUDE_PATH'):
	print "boost include path       : " + env['BOOST_INCLUDE_PATH']
print "Library                  : " + libtype
print "c++ compiler             : " + env['CXX']
print ""



#################
#  Build rules  #
#################

# generate templates


# Build directory
if env['DEBUG']:
	BuildDir("#build/debug", 'src', duplicate = 0)
	buildDirectory = 'build/debug/'
else:
	BuildDir("#build/release", 'src', duplicate = 0)
	buildDirectory = 'build/release/'

# remember build directory for SConscript files
env['BUILDDIR'] = buildDirectory

Export('env')




#########################
#  Generate sigx.pc  #
#########################

subst_dict['%MAJOR%'] = env['MAJOR']
subst_dict['%MINOR%'] = env['MINOR']
subst_dict['%MICRO%'] = env['MICRO']
subst_dict['%PREFIX%'] = env['PREFIX']
subst_dict['%VERSIONED_NAME%'] = env['VERSIONED_NAME']

pcfilename =  "%s.pc" % env['VERSIONED_NAME']
pcbuild = env.SubstInFile ( pcfilename,'sigx.pc.in', SUBST_DICT = subst_dict)


###############################
#  Generate sigxconfig.h  #
###############################

# substitute certain defines sin sigxconfig.h.in
#config_subst_dict['#undef SIGX_HAVE_XYZ'] = '#define SIGX_HAVE_XYZ 1';

configbuild = env.SubstInFile('sigxconfig.h', 'sigxconfig.h.in', SUBST_DICT = config_subst_dict)




###################
#  Documentation  #
##################

#subst_dict['%TOP_SRCDIR%'] = env['ENV']['PWD']
doxybuild = env.SubstInFile ( 'docs/Doxyfile', 'docs/Doxyfile.in', SUBST_DICT = subst_dict)

doxygen_path = '(doxygen-generated-files)'
env.DoxygenDoc(doxygen_path, 'docs/Doxyfile')


#######################
#  build and targets  #
#######################

m4build = SConscript(os.path.join('macros', 'SConscript'))
sigxbuild = SConscript(os.path.join(buildDirectory, 'SConscript'))
test_programs = SConscript(os.path.join('tests', 'SConscript'))
example_programs = SConscript(os.path.join('examples', 'SConscript'))
debianpkg = SConscript(os.path.join('debian', 'SConscript'))

# prevent removal of some target files when cleaning (scons -c);
# they don't need to be regenerated every time
# note: pcbuild + configbuild + doxybuild 
env.NoClean(m4build)
if not do_distclean:
	env.NoClean(pcbuild + configbuild + doxybuild)


Default(doxybuild)
Default(pcbuild)
Default(configbuild)
Default(m4build)
#Default(sigxbuild)
Default(buildDirectory)


# just a test function
def InstallSymlink(env, dir, target, source):
	linksource = os.path.basename(str(source))
	targets = []
	for tgt in target:
		targets += env.SymbolicLink(os.path.join(dir, os.path.basename(str(tgt))), source)
	return targets

#y = env.Install(env['LIBDIR'], sigxbuild[0])
#InstallSymlink(env, env['LIBDIR'], [sigxbuild[1], sigxbuild[2]], y)
# note: assumes that sigxbuild is a list with 3 elements containing the output and two symlinks
env.InstallVersionedLibrary(env['LIBDIR'], sigxbuild)
env.Install(env['INCLUDEDIR'], glob.glob('sigx/*.h'))
env.Install(env['LIBINCLUDEDIR'], glob.glob('sigxconfig.h'))
env.Install(env['LIBDIR'] + '/pkgconfig', pcfilename)

#tarball_path = 'sigx.tar.bz'
#env.Tarball(tarball_path, ['.'])

# Provide targets "install", "docs", "tests", "examples", "debian" (experimental)
# ie. 'scons install', ...
env.Alias('install', installpaths)
env.Alias('docs', doxygen_path)
env.Alias('tests', test_programs)
env.Alias('examples', example_programs)
env.Alias('debian', debianpkg)
#env.Alias('tarball', tarball_path)
# provide a distclean target: scons -c distclean, removing all target files
if env.GetOption('clean'):
	env.Alias('distclean', ['.'])
