### Copyright (c) 2003 by Scott Lystig Fritchie, <nospam@snookles.com>
### See the file "LICENSE" for full license terms.

import os
import string
import re
import SCons.Environment
import SCons.Script.SConscript
import SCons.Node.FS
import SCons.Builder

###
### All of the arguments to these functions are strings unless
### otherwise noted.  For lists of things, we assume that the
### things are separated by whitespace (any character that
### string.split() considers whitespace).
###
### The whitespace splitting and (occasionally) joining/adding
### can be a bit of a pain, but it seems easier than dealing
### with the Python rules for appending (or using the "+"
### operator) on lists vs. on strings.
###

###
### Subdirs -- Read the SConscript files in the specified subdirectories.
###
### NOTE: We assume that the environment we'll be using from the
### caller is "env".  Since this function is always called by my
### SConscripts in the boilerplate at the beginning, this has always
### been a safe assumption.  Caveat utilitor.
###

def Subdirs(dirlist):
	for file in _subconf_list(dirlist):
		#print "Subdirs: processing", file
		SCons.Script.SConscript.SConscript(file, "env")

###
### Program -- Specify the source files in the current directory 
### to be used to create the target executable.  Any additional
### libraries required for linking should be specified with
### AddLibs.
###

def Program(env, target, source):
	env.Program(target, string.split(source))

###
### AddCFlags -- Append flags to $CPPFLAGS.
###
### NOTE: Do not use for:
###   1. "-Ipath" flags for header file directories.  Use AddIncludeDirs.
###   2. "-Lpath" flags for library directories.  Use AddLibDirs.
###
### NOTE: FWIW, the sanity checking is compiler-specific.
###
### NOTE: SCons make a distinction between C/C++ preprocessor options
###       (stored in $CPPFLAGS), options only for the C compiler
###       (stored in $CCFLAGS), and options only for the C++ compiler
###       (stored in $CXXFLAGS).  See the SCons man page for full
###       details.  This function will append to $CPPFLAGS.
###

def AddCFlags(env, str):
	str = re.sub("\n", " ", str)	# Newline in flags is bad juju
	if re.search("-I", str):
		print "AddCFlags: Warning: -I flag found in '%s' in %s" %\
			(str, SCons.Node.FS.default_fs.Dir("."))
		print "\tPlease use the Mylib.AddIncludeDirs() function instead!\n"
	env.Append(CPPFLAGS = " " + str)

###
### AddIncludeDirs -- Specify directories to be added to compiler's
### header file search path.
###

def AddIncludeDirs(env, str):
	env.Append(CPPPATH = string.split(str))

###
### AddLibs -- Append libraries to the current environment.
###

def AddLibs(env, str):
	env.Append(LIBS = string.split(str))

###
### AddLibDirs -- Specify directories to be added to compiler's
### library search path.
###

def AddLibDirs(env, str):
	env.Append(LIBPATH = string.split(str))

###
### StaticLibrary -- Create a static library.
###

def StaticLibrary(env, target, source):
	env.StaticLibrary(target, string.split(source))

###
### SharedLibrary -- Same as StaticLibrary, but use the files in
### source to create a shared/dynamically loadable library.
###

def SharedLibrary(env, target, source):
	env.SharedLibrary(target, string.split(source))

###
### StaticLibMergeMembers -- Like StaticLibrary, but the objects that
### go into the library are not all found in the same directory.  Use
### this function to specify which objects will go into the final
### library file.
###
### WARNING: The way this is implemented assumes that the directory
###          that "libname" will be eventually be created is a *parent*
###	     of the directory that contains "files".  Furthermore, we
###	     are assuming that the SConscripts will process
###	     "libnames"'s member object files before calling
###	     CreateMergedStaticLibrary.  (This assumption is safe
###	     if using the boilerplate used by all the SConscript
###	     files in this example.)
###
###          The dir containing "libname" and "files" may be the
###	     *same* dir, but then the StaticLibMergeMembers function
###	     call(s) **must** be called before CreateMergedStaticLibrary.
###

def StaticLibMergeMembers(local_env, libname, files):
	hackpath = os.path.join("#", str(SCons.Node.FS.default_fs.Dir(".")))
        for file in string.split(files):
                objname = re.sub("\.(c|C|cpp|CPP|cxx|CXX|c\+\+|C\+\+|cc|CC)$",
			local_env["OBJSUFFIX"], file)
                local_env.Object(target = objname, source = file)
                e = "local_env[\"TopEnv\"].Append(%s = [\"%s\"])" % (libname, os.path.join(hackpath, objname))
                exec(e)

###
### CreateMergedStaticLibrary -- Use in the directory where the
### library file will be created.
###

def CreateMergedStaticLibrary(env, libname):
	objpaths = env["TopEnv"][libname]
        libname = "%s%s%s" % (env["LIBPREFIX"], libname, env["LIBSUFFIX"])
        env.StaticLibMerge(target = libname, source = objpaths)

###
### Gen_StaticLibMerge -- The generator trigged by
### CreateMergedStaticLibrary.
###

def Gen_StaticLibMerge(source, target, env, for_signature):
        target_string = ""
        for t in target:
                target_string = str(t)
        subdir = os.path.dirname(target_string)
        srclist = []
        for src in source:
                srclist.append(src)
        return [["ar", "cq"] + target + srclist, ["ranlib"] + target]

###
### ExportHeader -- Copy header files into the $EXPORT_INCLUDE
### directory.  This isn't the sort of scheme that everyone is fond
### of ... myself included.  However, Mylib was cooked up to mimic
### a pre-existing, "make"-driven development environment.  In that
### environment, all header files used by some other software
### component is copied into the "export" directory.
###
### Although this scheme can cause headaches if a less-current
### version of a header remains in the "export" dir, there are a
### couple of reasons why I keep ExportHeader here:
###
###  1. Unlike poorly-written Makefiles, SCons doesn't forget to copy
###     the most recent version of all headers to the "export" dir.  :-)
###  2. In the project Mylib was written for, the alternative was to
###     include over 75 (!) "-I" directives for each "gcc" command line.
###

def ExportHeader(env, headers):
	env.Install(dir = env["EXPORT_INCLUDE"], source = string.split(headers))

###
### ExportStaticLibrary -- Same reason that ExportHeader exists: to
### reduce the number of "-L" directives on each "gcc" command line.
### If you've got some other way of telling SCons where all your
### librarys' parent directories are and would rather not use this
### function, then don't use it.  :-)
###

def ExportStaticLibrary(env, libs):
	target_dir = env["EXPORT_LIB"]
	for lib in string.split(libs):
		libfile = "%s%s%s" %\
			(env["LIBPREFIX"], lib, env["LIBSUFFIX"])
		env.Install(dir = target_dir, source = libfile)
		#g_env = env["TopEnv"]
		#g_env.Append(LibraryList = "%s/%s" % (target_dir, libfile))

###
### InstallBin -- Copy a list of executables to the $INSTALL_BIN
### directory.  For general usage, this function would probably take
### another argument, namely the directory into which the executables
### should be copied....
###

def InstallBin(env, bins):
	env.Install(dir = env["INSTALL_BIN"], source = string.split(bins))

###
### CompileIDL -- Included here as a proof of concept ... it probably
### will not work as-is for you.
###

def CompileIDL(env, idlfile, xflags = ""):
	current_dir = SCons.Node.FS.default_fs.Dir(".")
	base = string.replace(idlfile, ".idl", "")
	l_env = env.Copy()
	#
	# Set up two special environment vars to be used by the	builder:
	# __XFLAGS: any extra flags to be passed to the IDL compiler
	# __OUTPUT_DIR: directory store the IDL compiler's output
	#
	l_env["__XFLAGS"] = xflags
	l_env["__OUTPUT_DIR"] = current_dir
	#
	# We've have an emitter that spits out correct targets, so
	# don't list any generated files here.
	#
	l_env.CompileIDL(target = [], source = idlfile)

###
### Emitter_IDL -- Included here as a proof of concept ... it probably
### will not work as-is for your IDL compiler.
###

def Emitter_IDL(target, source, env):
	base = string.replace(source[0], ".idl", "")
	(f1, f2, f3, f4) = string.split("%s.cpp %s.h %s_skel.cpp %s_skel.h" %\
			(base, base, base, base))
	return [f1, f2, f3, f4], source

###
### Misc helper functions.
###

def _subconf_list(dirlist):
	return map(lambda x: os.path.join(x, "SConscript"), string.split(dirlist))

