scons: Add a env.CodeGenerate method to simplify code generation via python scripts.

env.CodeGenerate(
	target = 'my_source.c',
	script = 'my_generator.py',
	source = ['input.txt', 'another.txt'],
	command = 'python $SCRIPT $SOURCE > $TARGET'
)

It will take care generating all appropriate dependencies, including any
module imported by the generator script, and the respective .pyc file
side effects.
diff --git a/scons/gallium.py b/scons/gallium.py
index 59ba348..bfdd2de 100644
--- a/scons/gallium.py
+++ b/scons/gallium.py
@@ -30,10 +30,13 @@
 # 
 
 
+import os
 import os.path
+import re
 
 import SCons.Action
 import SCons.Builder
+import SCons.Scanner
 
 
 def quietCommandLines(env):
@@ -70,11 +73,73 @@
                                   src_suffix = '$SHOBJSUFFIX',
                                   src_builder = 'SharedObject')
         env['BUILDERS']['ConvenienceLibrary'] = convenience_lib
-        env['BUILDERS']['Library'] = convenience_lib
 
     return convenience_lib
 
 
+# TODO: handle import statements with multiple modules
+# TODO: handle from import statements
+import_re = re.compile(r'^import\s+(\S+)$', re.M)
+
+def python_scan(node, env, path):
+	# http://www.scons.org/doc/0.98.5/HTML/scons-user/c2781.html#AEN2789
+	contents = node.get_contents()
+	source_dir = node.get_dir()
+	imports = import_re.findall(contents)
+	results = []
+	for imp in imports:
+		for dir in path:
+			file = os.path.join(str(dir), imp.replace('.', os.sep) + '.py')
+			if os.path.exists(file):  
+				results.append(env.File(file))
+				break
+			file = os.path.join(str(dir), imp.replace('.', os.sep), '__init__.py')
+			if os.path.exists(file):  
+				results.append(env.File(file))
+				break
+	return results
+
+python_scanner = SCons.Scanner.Scanner(function = python_scan, skeys = ['.py'])
+
+
+def code_generate(env, script, target, source, command):
+	"""Method to simplify code generation via python scripts.
+	
+	http://www.scons.org/wiki/UsingCodeGenerators
+	http://www.scons.org/doc/0.98.5/HTML/scons-user/c2768.html
+	"""
+	
+	# We're generating code using Python scripts, so we have to be 
+	# careful with our scons elements.  This entry represents
+	# the generator file *in the source directory*.
+	script_src = env.File(script).srcnode()
+	
+	# This command creates generated code *in the build directory*.
+	command = command.replace('$SCRIPT', script_src.path)
+	code = env.Command(target, source, command)
+
+	# Explicitly mark that the generated code depends on the generator,
+	# and on implicitly imported python modules
+	path = (script_src.get_dir(),)
+	deps = [script_src]
+	deps += script_src.get_implicit_deps(env, python_scanner, path)
+	env.Depends(code, deps)
+	
+	# Running the Python script causes .pyc files to be generated in the
+	# source directory.  When we clean up, they should go too. So add side 
+	# effects for .pyc files
+	for dep in deps:
+		pyc = env.File(str(dep) + 'c')
+		env.SideEffect(pyc, code)
+		
+	return code
+
+
+def createCodeGenerateMethod(env):
+	env.Append(SCANNERS = python_scanner)
+	env.AddMethod(code_generate, 'CodeGenerate')
+
+
 def generate(env):
 	"""Common environment generation code"""
 	
@@ -336,9 +401,10 @@
 			]
 	env.Append(LINKFLAGS = linkflags)
 
-	# Convenience Library Builder
+	# Custom builders and methods
 	createConvenienceLibBuilder(env)
-	
+	createCodeGenerateMethod(env)
+
 	# for debugging
 	#print env.Dump()