Totally new "freeze" program.
diff --git a/Tools/freeze/findmodules.py b/Tools/freeze/findmodules.py
new file mode 100644
index 0000000..177f1fd
--- /dev/null
+++ b/Tools/freeze/findmodules.py
@@ -0,0 +1,128 @@
+# Determine the names and filenames of the modules imported by a
+# script, recursively.  This is done by scanning for lines containing
+# import statements.  (The scanning has only superficial knowledge of
+# Python syntax and no knowledge of semantics, so in theory the result
+# may be incorrect -- however this is quite unlikely if you don't
+# intentionally obscure your Python code.)
+
+import os
+import regex
+import string
+import sys
+
+
+# Top-level interface.
+# First argument is the main program (script).
+# Second optional argument is list of modules to be searched as well.
+
+def findmodules(scriptfile, modules = [], path = sys.path):
+	todo = {}
+	todo['__main__'] = scriptfile
+	for name in modules:
+		mod = os.path.basename(name)
+		if mod[-3:] == '.py': mod = mod[:-3]
+		todo[mod] = name
+	done = closure(todo)
+	return done
+
+
+# Compute the closure of scanfile() and findmodule().
+# Return a dictionary mapping module names to filenames.
+# Writes to stderr if a file can't be or read.
+
+def closure(todo):
+	done = {}
+	while todo:
+		newtodo = {}
+		for modname in todo.keys():
+			if not done.has_key(modname):
+				filename = todo[modname]
+				if filename is None:
+					filename = findmodule(modname)
+				done[modname] = filename
+				if filename in ('<builtin>', '<unknown>'):
+					continue
+				try:
+					modules = scanfile(filename)
+				except IOError, msg:
+					sys.stderr.write("%s: %s\n" %
+							 (filename, str(msg)))
+					continue
+				for m in modules:
+					if not done.has_key(m):
+						newtodo[m] = None
+		todo = newtodo
+	return done
+
+
+# Scan a file looking for import statements.
+# Return list of module names.
+# Can raise IOError.
+
+importstr = '\(^\|:\)[ \t]*import[ \t]+\([a-zA-Z0-9_, \t]+\)'
+fromstr   = '\(^\|:\)[ \t]*from[ \t]+\([a-zA-Z0-9_]+\)[ \t]+import[ \t]+'
+isimport = regex.compile(importstr)
+isfrom = regex.compile(fromstr)
+
+def scanfile(filename):
+	allmodules = {}
+	f = open(filename, 'r')
+	try:
+		while 1:
+			line = f.readline()
+			if not line: break # EOF
+			while line[-2:] == '\\\n': # Continuation line
+				line = line[:-2] + ' '
+				line = line + f.readline()
+			if isimport.search(line) >= 0:
+				rawmodules = isimport.group(2)
+				modules = string.splitfields(rawmodules, ',')
+				for i in range(len(modules)):
+					modules[i] = string.strip(modules[i])
+			elif isfrom.search(line) >= 0:
+				modules = [isfrom.group(2)]
+			else:
+				continue
+			for mod in modules:
+				allmodules[mod] = None
+	finally:
+		f.close()
+	return allmodules.keys()
+
+
+# Find the file containing a module, given its name.
+# Return filename, or '<builtin>', or '<unknown>'.
+
+builtins = sys.builtin_module_names
+if 'sys' not in builtins: builtins.append('sys')
+# XXX this table may have to be changed depending on your platform:
+tails = ['.so', 'module.so', '.py', '.pyc']
+
+def findmodule(modname, path = sys.path):
+	if modname in builtins: return '<builtin>'
+	for dirname in path:
+		for tail in tails:
+			fullname = os.path.join(dirname, modname + tail)
+			try:
+				f = open(fullname, 'r')
+			except IOError:
+				continue
+			f.close()
+			return fullname
+	return '<unknown>'
+
+
+# Test the above functions.
+
+def test():
+	if not sys.argv[1:]:
+		print 'usage: python findmodules.py scriptfile [morefiles ...]'
+		sys.exit(2)
+	done = findmodules(sys.argv[1], sys.argv[2:])
+	items = done.items()
+	items.sort()
+	for mod, file in [('Module', 'File')] + items:
+		print "%-15s %s" % (mod, file)
+
+if __name__ == '__main__':
+	test()
diff --git a/Tools/freeze/freeze.py b/Tools/freeze/freeze.py
index 52285a6..2588c60 100755
--- a/Tools/freeze/freeze.py
+++ b/Tools/freeze/freeze.py
@@ -1,576 +1,176 @@
 #! /usr/local/bin/python
 
-# Given a Python script, create a binary that runs the script.
-# The binary is 100% independent of Python libraries and binaries.
-# It will not contain any Python source code -- only "compiled" Python
-# (as initialized static variables containing marshalled code objects).
-# It even does the right thing for dynamically loaded modules!
-# The module search path of the binary is set to the current directory.
-#
-# Some problems remain:
-# - You need to have the Python source tree lying around as well as
-#   the various libraries used to generate the Python binary.
-# - For scripts that use many modules it generates absurdly large
-#   files (frozen.c and config.o as well as the final binary),
-#   and is consequently rather slow.
-#
-# Caveats:
-# - The search for modules sometimes finds modules that are never
-#   actually imported since the code importing them is never executed.
-# - If an imported module isn't found, you get a warning but the
-#   process of freezing continues.  The binary will fail if it
-#   actually tries to import one of these modules.
-# - This often happens with the module 'mac', which module 'os' tries
-#   to import (to determine whether it is running on a Macintosh).
-#   You can ignore the warning about this.
-# - If the program dynamically reads or generates Python code and
-#   executes it, this code may reference built-in or library modules
-#   that aren't present in the frozen binary, and this will fail.
-# - Your program may be using external data files, e.g. compiled
-#   forms definitions (*.fd).  These aren't incorporated. By default,
-#   the sys.path in the resulting binary is only '.' (but you can override
-#   that with the -P option).
-#
-# Usage hints:
-# - If you have a bunch of scripts that you want to freeze, instead
-#   of freezing each of them separately, you might consider writing
-#   a tiny main script that looks at sys.argv[0] and then imports
-#   the corresponding module.  You can then make links to the
-#   frozen binary named after the various scripts you support.
-#   Pass the additional scripts as arguments after the main script.
-#   A minimal script to do this is the following.
-#       import sys, posixpath
-#       exec('import ' + posixpath.basename(sys.argv[0]) + '\n')
-#
-# Mods by Jack, August 94:
-# - Removed all static configuration stuff. Now, Setup and Makefile files
-#   are parsed to obtain the linking info for the libraries. You have to
-#   supply the -B option, though.
-# - Added -P (set sys.path) and -I/-D/-L/-l options (passed on to cc and
-#   ld).
+# "Freeze" a Python script into a binary.
+# Usage: see first function below (before the imports!)
 
-import os
-import sys
-import regex
+# HINTS:
+# - Edit the line at XXX below before running!
+# - You must have done "make inclinstall libainstall" in the Python
+#   build directory.
+# - The script should not use dynamically loaded modules
+#   (*.so on most systems).
+
+
+# XXX Change the following line to point to your Demo/freeze directory!
+pack = '/ufs/guido/src/python/Demo/freeze'
+
+
+# Print usage message and exit
+
+def usage(msg = None):
+	if msg:
+		sys.stderr.write(str(msg) + '\n')
+	sys.stderr.write('usage: freeze [-p prefix] script [module] ...\n')
+	sys.exit(2)
+
+
+# Import standard modules
+
 import getopt
-import regsub
+import os
 import string
-import marshal
+import sys
+import addpack
 
-# Exception used when scanfile fails
-NoSuchFile = 'NoSuchFile'
 
-# Global options
-builddir = ''				# -B dir
-quiet = 0				# -q
-verbose = 0				# -v
-noexec = 0				# -n
-nowrite = 0				# -N
-ofile = 'a.out'				# -o file
-path = '\'"."\''			# -P path
+# Set the directory to look for the freeze-private modules
 
-cc_options = []				# Collects cc options
-ld_options = []				# Collects ld options
-module_libraries = {}			# ld options for each module
-global_libraries = []			# Libraries we always need
-include_path = ''			# Include path, from Makefile 
-lib_path = ''				# and lib path, ditto
-compiler = 'cc'				# and compiler
+dir = os.path.dirname(sys.argv[0])
+if dir:
+	pack = dir
+addpack.addpack(pack)
 
-# Main program -- argument parsing etc.
+
+# Import the freeze-private modules
+
+import findmodules
+import makeconfig
+import makefreeze
+import makemakefile
+import parsesetup
+
+hint = """
+Use the '-p prefix' command line option to specify the prefix used
+when you ran 'Make inclinstall libainstall' in the Python build directory.
+(Please specify an absolute path.)
+"""
+
+
+# Main program
+
 def main():
-	global quiet, verbose, noexec, nowrite, ofile, builddir, path
+	# overridable context
+	prefix = '/usr/local'		# settable with -p option
+	path = sys.path
+
+	# output files
+	frozen_c = 'frozen.c'
+	config_c = 'config.c'
+	target = 'a.out'		# normally derived from script name
+	makefile = 'Makefile'
+
+	# parse command line
 	try:
-		opts, args = getopt.getopt(sys.argv[1:], 'B:nNo:P:qvI:D:L:l:')
+		opts, args = getopt.getopt(sys.argv[1:], 'p:')
+		if not args:
+			raise getopt.error, 'not enough arguments'
 	except getopt.error, msg:
-		usage(str(msg))
-		sys.exit(2)
+		usage('getopt error: ' + str(msg))
+
+	# proces option arguments
 	for o, a in opts:
-	        if o == '-B': builddir = a
-		if o == '-n': noexec = 1
-		if o == '-N': nowrite = 1
-		if o == '-o': ofile = a
-		if o == '-P':
-		    if '"' in a:
-			usage('sorry, cannot have " in -P option')
-			sys.exit(2)
-		    path = `'"' + a + '"'`
-		if o == '-q': verbose = 0; quiet = 1
-		if o == '-v': verbose = verbose + 1; quiet = 0
-		if o in ('-I', '-D'): cc_options.append(o+a)
-		if o in ('-L', '-l'): ld_options.append(o+a)
-	if not builddir:
-	    usage('sorry, you have to pass a -B option')
-	    sys.exit(2)
-	if len(args) < 1:
-		usage('please pass at least one file argument')
-		sys.exit(2)
-	process(args[0], args[1:])
+		if o == '-p':
+			prefix = a
 
-# Print usage message to stderr
-def usage(*msgs):
-	sys.stdout = sys.stderr
-	for msg in msgs: print msg
-	print 'Usage: freeze [options] scriptfile [modulefile ...]'
-	print '-B dir  : name of python build dir (no default)'
-	print '-n      : generate the files but don\'t compile and link'
-	print '-N      : don\'t write frozen.c (do compile unless -n given)'
-	print '-o file : binary output file (default a.out)'
-	print '-P path : set sys.path for program (default ".")'
-	print '-q      : quiet (no messages at all except errors)'
-	print '-v      : verbose (lots of extra messages)'
-	print '-D and -I options are passed to cc, -L and -l to ld'
+	# locations derived from options
+	binlib = os.path.join(prefix, 'lib/python/lib')
+	incldir = os.path.join(prefix, 'include/Py')
+	config_c_in = os.path.join(binlib, 'config.c.in')
+	frozenmain_c = os.path.join(binlib, 'frozenmain.c')
+	makefile_in = os.path.join(binlib, 'Makefile')
+	defines = ['-DHAVE_CONFIG_H', '-DUSE_FROZEN', '-DNO_MAIN',
+		   '-DPTHONPATH=\\"$(PYTHONPATH)\\"']
+	includes = ['-I' + incldir, '-I' + binlib]
 
-# Process the script file
-def process(filename, addmodules):
-	global noexec
-	#
-	if not quiet: print 'Computing needed modules ...'
-	todo = {}
-	todo['__main__'] = filename
-	for name in addmodules:
-		mod = os.path.basename(name)
-		if mod[-3:] == '.py': mod = mod[:-3]
-		todo[mod] = name
-	try:
-		dict = closure(todo)
-	except NoSuchFile, filename:
-		sys.stderr.write('Can\'t open file %s\n' % filename)
-		sys.exit(1)
-	#
-	mods = dict.keys()
-	mods.sort()
-	#
-	if verbose:
-		print '%-15s %s' % ('Module', 'Filename')
-		for mod in mods:
-			print '%-15s %s' % (`mod`, dict[mod])
-	#
-	if not quiet: print 'Looking for dynamically linked modules ...'
-	dlmodules = []
-	objs = []
-	libs = []
-	for mod in mods:
-		if dict[mod][-2:] == '.o':
-			if verbose: print 'Found', mod, dict[mod]
-			dlmodules.append(mod)
-			objs.append(dict[mod])
-			libsname = dict[mod][:-2] + '.libs'
-			try:
-				f = open(libsname, 'r')
-			except IOError:
-				f = None
-			if f:
-				libtext = f.read()
-				f.close()
-				for lib in string.split(libtext):
-					if lib in libs: libs.remove(lib)
-					libs.append(lib)
-	#
-	if not nowrite:
-		if not quiet: print 'Writing frozen.c ...'
-		writefrozen('frozen.c', dict)
-	else:
-		if not quiet: print 'NOT writing frozen.c ...'
-	#
-	if not quiet:
-	    print 'Deducing compile/link options from', builddir
-	#
-	# Parse the config info
-	#
-	parse(builddir)
-	CONFIG_IN = lib_path + '/config.c.in'
-	FMAIN = lib_path + '/frozenmain.c'
-	CC = compiler
-	#
-##	if not dlmodules:
-	if 0:
-		config = CONFIG
-		if not quiet: print 'Using existing', config, '...'
-	else:
-		config = 'tmpconfig.c'
-		if nowrite:
-			if not quiet: print 'NOT writing config.c ...'
+	# sanity check of locations
+	for dir in prefix, binlib, incldir:
+		if not os.path.exists(dir):
+			usage('needed directory %s not found' % dir + hint)
+		if not os.path.isdir(dir):
+			usage('%s: not a directory' % dir)
+	for file in config_c_in, makefile_in, frozenmain_c:
+		if not os.path.exists(file):
+			usage('needed file %s not found' % file)
+		if not os.path.isfile(file):
+			usage('%s: not a plain file' % file)
+
+	# check that file arguments exist
+	for arg in args:
+		if not os.path.exists(arg):
+			usage('argument %s not found' % arg)
+		if not os.path.isfile(arg):
+			usage('%s: not a plain file' % arg)
+
+	# process non-option arguments
+	scriptfile = args[0]
+	modules = args[1:]
+
+	# derive target name from script name
+	base = os.path.basename(scriptfile)
+	base, ext = os.path.splitext(base)
+	if base:
+		if base != scriptfile:
+			target = base
 		else:
-			if not quiet:
-				print 'Writing config.c with dl modules ...'
-			f = open(CONFIG_IN, 'r')
-			g = open(config, 'w')
-			m1 = regex.compile('-- ADDMODULE MARKER 1 --')
-			m2 = regex.compile('-- ADDMODULE MARKER 2 --')
-			builtinmodules = []
-			stdmodules = ('sys', '__main__', '__builtin__',
-				      'marshal')
-			for mod in dict.keys():
-				if dict[mod] == '<builtin>' and \
-					  mod not in stdmodules:
-					builtinmodules.append(mod)
-			todomodules = builtinmodules + dlmodules
-			while 1:
-				line = f.readline()
-				if not line: break
-				g.write(line)
-				if m1.search(line) >= 0:
-					if verbose: print 'Marker 1 ...'
-					for mod in todomodules:
-						g.write('extern void init' + \
-						  mod + '();\n')
-				if m2.search(line) >= 0:
-					if verbose: print 'Marker 2 ...'
-					for mod in todomodules:
-						g.write('{"' + mod + \
-						  '", init' + mod + '},\n')
-			g.close()
-	#
-	if not quiet:
-		if noexec: print 'Generating compilation commands ...'
-		else: print 'Starting compilation ...'
-	defs = ['-DNO_MAIN', '-DUSE_FROZEN']
-	defs.append('-DPYTHONPATH='+path)
-	#
-	incs = ['-I.', '-I' + include_path]
-#	if dict.has_key('stdwin'):
-#		incs.append('-I' + j(STDWIN, 'H'))
-	#
-	srcs = [config, FMAIN]
-	#
-	modlibs = module_libraries
-	
+			target = base + '.bin'
+
+	# Actual work starts here...
+
+	dict = findmodules.findmodules(scriptfile, modules, path)
+
+	builtins = []
 	for mod in dict.keys():
-	    if modlibs.has_key(mod):
-		libs = libs + modlibs[mod]
+		if dict[mod] == '<builtin>':
+			builtins.append(mod)
 
-	libs = libs + global_libraries
-	#
-	# remove dups:
-	# XXXX Not well tested...
-	nskip = 0
-	newlibs = []
-	while libs:
-	    l = libs[0]
-	    del libs[0]
-	    if l[:2] == '-L' and l in newlibs:
-		nskip = nskip + 1
-		continue
-	    if (l[:2] == '-l' or l[-2:] == '.a') and l in libs:
-		nskip = nskip + 1
-		continue
-	    newlibs.append(l)
-	libs = newlibs
-	if nskip and not quiet:
-	    print 'Removed %d duplicate libraries'%nskip
-	#
-	sts = 0
-	#
-	cmd = CC + ' -c'
-	if cc_options:
-	    cmd = cmd + ' ' + string.join(cc_options)
-	cmd = cmd + ' ' + string.join(defs)
-	cmd = cmd + ' ' + string.join(incs)
-	cmd = cmd + ' ' + string.join(srcs)
-	print cmd
-	#
-	if not noexec:
-		sts = os.system(cmd)
-		if sts:
-			print 'Exit status', sts, '-- turning on -n'
-			noexec = 1
-	#
-	for s in srcs:
-		s = os.path.basename(s)
-		if s[-2:] == '.c': s = s[:-2]
-		o = s + '.o'
-		objs.insert(0, o)
-	#
-	cmd = CC
-	cmd = cmd + ' ' + string.join(objs)
-	cmd = cmd + ' ' + string.join(libs)
-	if ld_options:
-	    cmd = cmd + ' ' + string.join(ld_options)
-	cmd = cmd + ' -o ' + ofile
-	print cmd
-	#
-	if not noexec:
-		sts = os.system(cmd)
-		if sts:
-			print 'Exit status', sts
-		else:
-			print 'Done.'
-	#
-	if not quiet and not noexec and sts == 0:
-		print 'Note: consider this:'; print '\tstrip', ofile
-	#
-	sys.exit(sts)
-
-
-# Generate code for a given module
-def makecode(filename):
-	if filename[-2:] == '.o':
-		return None
+	outfp = open(frozen_c, 'w')
 	try:
-		f = open(filename, 'r')
-	except IOError:
-		return None
-	if verbose: print 'Making code from', filename, '...'
-	text = f.read()
-	code = compile(text, filename, 'exec')
-	f.close()
-	return marshal.dumps(code)
-
-
-# Write the C source file containing the frozen Python code
-def writefrozen(filename, dict):
-	f = open(filename, 'w')
-	codelist = []
-	for mod in dict.keys():
-		codestring = makecode(dict[mod])
-		if codestring is not None:
-			codelist.append((mod, codestring))
-	write = sys.stdout.write
-	save_stdout = sys.stdout
-	try:
-		sys.stdout = f
-		for mod, codestring in codelist:
-			if verbose:
-				write('Writing initializer for %s\n'%mod)
-			print 'static unsigned char M_' + mod + '[' + \
-				  str(len(codestring)) + '+1] = {'
-			for i in range(0, len(codestring), 16):
-				for c in codestring[i:i+16]:
-					print str(ord(c)) + ',',
-				print
-			print '};'
-		print 'struct frozen {'
-		print '  char *name;'
-		print '  unsigned char *code;'
-		print '  int size;'
-		print '} frozen_modules[] = {'
-		for mod, codestring in codelist:
-			print '  {"' + mod + '",',
-			print 'M_' + mod + ',',
-			print str(len(codestring)) + '},'
-		print '  {0, 0, 0} /* sentinel */'
-		print '};'
+		makefreeze.makefreeze(outfp, dict)
 	finally:
-		sys.stdout = save_stdout
-	f.close()
+		outfp.close()
 
-
-# Determine the names and filenames of the modules imported by the
-# script, recursively.  This is done by scanning for lines containing
-# import statements.  (The scanning has only superficial knowledge of
-# Python syntax and no knowledge of semantics, so in theory the result
-# may be incorrect -- however this is quite unlikely if you don't
-# intentionally obscure your Python code.)
-
-# Compute the closure of scanfile() -- special first file because of script
-def closure(todo):
-	done = {}
-	while todo:
-		newtodo = {}
-		for modname in todo.keys():
-			if not done.has_key(modname):
-				filename = todo[modname]
-				if filename is None:
-					filename = findmodule(modname)
-				done[modname] = filename
-				if filename in ('<builtin>', '<unknown>'):
-					continue
-				modules = scanfile(filename)
-				for m in modules:
-					if not done.has_key(m):
-						newtodo[m] = None
-		todo = newtodo
-	return done
-
-# Scan a file looking for import statements
-importstr = '\(^\|:\)[ \t]*import[ \t]+\([a-zA-Z0-9_, \t]+\)'
-fromstr   = '\(^\|:\)[ \t]*from[ \t]+\([a-zA-Z0-9_]+\)[ \t]+import[ \t]+'
-isimport = regex.compile(importstr)
-isfrom = regex.compile(fromstr)
-def scanfile(filename):
-	allmodules = {}
+	infp = open(config_c_in)
+	outfp = open(config_c, 'w')
 	try:
-		f = open(filename, 'r')
-	except IOError, msg:
-		raise NoSuchFile, filename
-	while 1:
-		line = f.readline()
-		if not line: break # EOF
-		while line[-2:] == '\\\n': # Continuation line
-			line = line[:-2] + ' '
-			line = line + f.readline()
-		if isimport.search(line) >= 0:
-			rawmodules = isimport.group(2)
-			modules = string.splitfields(rawmodules, ',')
-			for i in range(len(modules)):
-				modules[i] = string.strip(modules[i])
-		elif isfrom.search(line) >= 0:
-			modules = [isfrom.group(2)]
-		else:
-			continue
-		for mod in modules:
-			allmodules[mod] = None
-	f.close()
-	return allmodules.keys()
+		makeconfig.makeconfig(infp, outfp, builtins)
+	finally:
+		outfp.close()
+	infp.close()
 
-# Find the file containing a module, given its name; None if not found
-builtins = sys.builtin_module_names + ['sys']
-def findmodule(modname):
-	if modname in builtins: return '<builtin>'
-	for dirname in sys.path:
-		dlfullname = os.path.join(dirname, modname + 'module.o')
-		try:
-			f = open(dlfullname, 'r')
-		except IOError:
-			f = None
-		if f:
-			f.close()
-			return dlfullname
-		fullname = os.path.join(dirname, modname + '.py')
-		try:
-			f = open(fullname, 'r')
-		except IOError:
-			continue
-		f.close()
-		return fullname
-	if not quiet:
-		sys.stderr.write('Warning: module %s not found\n' % modname)
-	return '<unknown>'
-#
-# Parse a setup file. Returns two dictionaries, one containing variables
-# defined with their values and one containing module definitions
-#
-def parse_setup(fp):
-    modules = {}
-    variables = {}
-    for line in fp.readlines():
-	if '#' in line:				# Strip comments
-	    line = string.splitfields(line, '#')[0]
-	line = string.strip(line[:-1])		# Strip whitespace
-	if not line:
-	    continue
-	words = string.split(line)
-	if '=' in words[0]:
-	    #
-	    # equal sign before first space. Definition
-	    #
-	    pos = string.index(line, '=')
-	    name = line[:pos]
-	    value = string.strip(line[pos+1:])
-	    variables[name] = value
-	else:
-	    modules[words[0]] = words[1:]
-    return modules, variables
-#
-# Parse a makefile. Returns a list of the variables defined.
-#
-def parse_makefile(fp):
-    variables = {}
-    for line in fp.readlines():
-	if '#' in line:				# Strip comments
-	    line = string.splitfields(line, '#')[0]
-	if not line:
-	    continue
-	if line[0] in string.whitespace:
-	    continue
-	line = string.strip(line[:-1])		# Strip whitespace
-	if not line:
-	    continue
-	if '=' in string.splitfields(line, ':')[0]: 
-	    #
-	    # equal sign before first colon. Definition
-	    #
-	    pos = string.index(line, '=')
-	    name = line[:pos]
-	    value = string.strip(line[pos+1:])
-	    variables[name] = value
-    return variables
+	cflags = defines + includes + ['$(OPT)']
+	libs = []
+	for n in 'Modules', 'Python', 'Objects', 'Parser':
+		n = 'lib%s.a' % n
+		n = os.path.join(binlib, n)
+		libs.append(n)
 
-#
-# Recursively add loader options from Setup files in extension
-# directories.
-#
-def add_extension_directory(name, isinstalldir):
-    if verbose:
-	print 'Adding extension directory', name
-    fp = open(name + '/Setup', 'r')
-    modules, variables = parse_setup(fp)
-    #
-    # Locate all new modules and remember the ld flags needed for them
-    #
-    for m in modules.keys():
-	if module_libraries.has_key(m):
-	    continue
-	options = modules[m]
-	if isinstalldir:
-	    ld_options = []
-	else:
-	    ld_options = [name + '/lib.a']
-	for o in options:
-	    # ld options are all capital except DUIC and l
-	    if o[:-2] == '.a':
-		ld_options.append(o)
-	    elif o[0] == '-':
-		if o[1] == 'l':
-		    ld_options.append(o)
-		elif o[1] in string.uppercase and not o[1] in 'DUIC':
-		    ld_options.append(o)
-	module_libraries[m] = ld_options
-    #
-    # See if we have to bother with base setups
-    #
-    if variables.has_key('BASESETUP'):
-	if isinstalldir:
-	    raise 'installdir has base setup'
-	setupfiles = string.split(variables['BASESETUP'])
-	for s in setupfiles:
-	    if s[-6:] <> '/Setup':
-		raise 'Incorrect BASESETUP', s
-	    s = s[:-6]
-	    if s[0] <> '/':
-		s = name + '/' + s
-		s = os.path.normpath(s)
-	    add_extension_directory(s, 0)
-#
-# Main routine for this module: given a build directory, get all
-# information needed for the linker.
-#
-def parse(dir):
-    global include_path
-    global lib_path
-    global compiler
-    
-    fp = open(dir + '/Makefile', 'r')
-    #
-    # First find the global libraries and the base python
-    #
-    vars = parse_makefile(fp)
-    if vars.has_key('CC'):
-	compiler = vars['CC']
-    if not vars.has_key('installdir'):
-	raise 'No $installdir in Makefile'
-    include_path = vars['installdir'] + '/include/Py'
-    lib_path = vars['installdir'] + '/lib/python/lib'
-    global_libraries.append('-L' + lib_path)
-    global_libraries.append('-lPython')
-    global_libraries.append('-lParser')
-    global_libraries.append('-lObjects')
-    global_libraries.append('-lModules')
-    for name in ('LIBS', 'LIBM', 'LIBC'):
-	if not vars.has_key(name):
-	    raise 'Missing required def in Makefile', name
-	for lib in string.split(vars[name]):
-	    global_libraries.append(lib)
-    #
-    # Next, parse the modules from the base python
-    #
-    add_extension_directory(lib_path, 1)
-    #
-    # Finally, parse the modules from the extension python
-    #
-    if dir <> lib_path:
-	add_extension_directory(dir, 0)
+	makevars = parsesetup.getmakevars(makefile_in)
+	somevars = {}
+	for key in makevars.keys():
+		somevars[key] = makevars[key]
 
-# Call the main program
+	somevars['CFLAGS'] = string.join(cflags) # override
+	files = ['$(OPT)', config_c, frozenmain_c] + libs + \
+		['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)']
+
+	outfp = open(makefile, 'w')
+	try:
+		makemakefile.makemakefile(outfp, somevars, files, target)
+	finally:
+		outfp.close()
+
+	# Done!
+
+	print 'Now run make to build the target:', target
+
 main()
diff --git a/Tools/freeze/makeconfig.py b/Tools/freeze/makeconfig.py
new file mode 100644
index 0000000..958c2be
--- /dev/null
+++ b/Tools/freeze/makeconfig.py
@@ -0,0 +1,57 @@
+import regex
+
+
+# Write the config.c file
+
+never = ['marshal', '__main__', '__builtin__', 'sys']
+
+def makeconfig(infp, outfp, modules):
+	m1 = regex.compile('-- ADDMODULE MARKER 1 --')
+	m2 = regex.compile('-- ADDMODULE MARKER 2 --')
+	while 1:
+		line = infp.readline()
+		if not line: break
+		outfp.write(line)
+		if m1 and m1.search(line) >= 0:
+			m1 = None
+			for mod in modules:
+				if mod in never:
+					continue
+				outfp.write('extern void init%s();\n' % mod)
+		elif m2 and m2.search(line) >= 0:
+			m2 = None
+			for mod in modules:
+				if mod in never:
+					continue
+				outfp.write('\t{"%s", init%s},\n' %
+					    (mod, mod))
+	if m1:
+		sys.stderr.write('MARKER 1 never found\n')
+	elif m2:
+		sys.stderr.write('MARKER 2 never found\n')
+
+
+# Test program.
+
+def test():
+	import sys
+	if not sys.argv[3:]:
+		print 'usage: python makeconfig.py config.c.in outputfile',
+		print 'modulename ...'
+		sys.exit(2)
+	if sys.argv[1] == '-':
+		infp = sys.stdin
+	else:
+		infp = open(sys.argv[1])
+	if sys.argv[2] == '-':
+		outfp = sys.stdout
+	else:
+		outfp = open(sys.argv[2], 'w')
+	makeconfig(infp, outfp, sys.argv[3:])
+	if outfp != sys.stdout:
+		outfp.close()
+	if infp != sys.stdin:
+		infp.close()
+
+if __name__ == '__main__':
+	test()
diff --git a/Tools/freeze/makefreeze.py b/Tools/freeze/makefreeze.py
new file mode 100644
index 0000000..4a3da99
--- /dev/null
+++ b/Tools/freeze/makefreeze.py
@@ -0,0 +1,91 @@
+import marshal
+
+
+# Write a file containing frozen code for the modules in the dictionary.
+
+header = """
+struct frozen {
+	char *name;
+	unsigned char *code;
+	int size;
+} frozen_modules[] = {
+"""
+trailer = """\
+	{0, 0, 0} /* sentinel */
+};
+"""
+
+def makefreeze(outfp, dict):
+	done = []
+	mods = dict.keys()
+	mods.sort()
+	for mod in mods:
+		modfn = dict[mod]
+		try:
+			str = makecode(modfn)
+		except IOError, msg:
+			sys.stderr.write("%s: %s\n" % (modfn, str(msg)))
+			continue
+		if str:
+			done.append(mod, len(str))
+			writecode(outfp, mod, str)
+	outfp.write(header)
+	for mod, size in done:
+		outfp.write('\t{"%s", M_%s, %d},\n' % (mod, mod, size))
+	outfp.write(trailer)
+
+
+# Return code string for a given module -- either a .py or a .pyc
+# file.  Return either a string or None (if it's not Python code).
+# May raise IOError.
+
+def makecode(filename):
+	if filename[-3:] == '.py':
+		f = open(filename, 'r')
+		try:
+			text = f.read()
+			code = compile(text, filename, 'exec')
+		finally:
+			f.close()
+		return marshal.dumps(code)
+	if filename[-4:] == '.pyc':
+		f = open(filename, 'rb')
+		try:
+			f.seek(8)
+			str = f.read()
+		finally:
+			f.close()
+		return str
+	# Can't generate code for this extension
+	return None
+
+
+# Write a C initializer for a module containing the frozen python code.
+# The array is called M_<mod>.
+
+def writecode(outfp, mod, str):
+	outfp.write('static unsigned char M_%s[] = {' % mod)
+	for i in range(0, len(str), 16):
+		outfp.write('\n\t')
+		for c in str[i:i+16]:
+			outfp.write('%d,' % ord(c))
+	outfp.write('\n};\n')
+
+
+# Test for the above functions.
+
+def test():
+	import os
+	import sys
+	if not sys.argv[1:]:
+		print 'usage: python freezepython.py file.py(c) ...'
+		sys.exit(2)
+	dict = {}
+	for arg in sys.argv[1:]:
+		base = os.path.basename(arg)
+		mod, ext = os.path.splitext(base)
+		dict[mod] = arg
+	makefreeze(sys.stdout, dict)
+
+if __name__ == '__main__':
+	test()
diff --git a/Tools/freeze/makemakefile.py b/Tools/freeze/makemakefile.py
new file mode 100644
index 0000000..5418f93
--- /dev/null
+++ b/Tools/freeze/makemakefile.py
@@ -0,0 +1,27 @@
+# Write the actual Makefile.
+
+import os
+import string
+
+def makemakefile(outfp, makevars, files, target):
+	outfp.write("# Makefile generated by freeze.py script\n\n")
+
+	keys = makevars.keys()
+	keys.sort()
+	for key in keys:
+		outfp.write("%s=%s\n" % (key, makevars[key]))
+	outfp.write("\nall: %s\n" % target)
+
+	deps = []
+	for i in range(len(files)):
+		file = files[i]
+		if file[-2:] == '.c':
+			base = os.path.basename(file)
+			dest = base[:-2] + '.o'
+			outfp.write("%s: %s\n" % (dest, file))
+			outfp.write("\t$(CC) $(CFLAGS) -c %s\n" % file)
+			files[i] = dest
+			deps.append(dest)
+
+	outfp.write("\n%s: %s\n" % (target, string.join(deps)))
+	outfp.write("\t$(CC) %s -o %s\n" % (string.join(files), target))
diff --git a/Tools/freeze/parsesetup.py b/Tools/freeze/parsesetup.py
new file mode 100644
index 0000000..1795671
--- /dev/null
+++ b/Tools/freeze/parsesetup.py
@@ -0,0 +1,98 @@
+# Parse Makefiles and Python Setup(.in) files.
+
+import regex
+import string
+
+
+# Extract variable definitions from a Makefile.
+# Return a dictionary mapping names to values.
+# May raise IOError.
+
+makevardef = regex.compile('^\([a-zA-Z0-9_]+\)[ \t]*=\(.*\)')
+
+def getmakevars(filename):
+	variables = {}
+	fp = open(filename)
+	try:
+		while 1:
+			line = fp.readline()
+			if not line:
+				break
+			if makevardef.match(line) < 0:
+				continue
+			name, value = makevardef.group(1, 2)
+			# Strip trailing comment
+			i = string.find(value, '#')
+			if i >= 0:
+				value = value[:i]
+			value = string.strip(value)
+			variables[name] = value
+	finally:
+		fp.close()
+	return variables
+
+
+# Parse a Python Setup(.in) file.
+# Return two dictionaries, the first mapping modules to their
+# definitions, the second mapping variable names to their values.
+# May raise IOError.
+
+setupvardef = regex.compile('^\([a-zA-Z0-9_]+\)=\(.*\)')
+
+def getsetupinfo(filename):
+	modules = {}
+	variables = {}
+	fp = open(filename)
+	try:
+		while 1:
+			line = fp.readline()
+			if not line:
+				break
+			# Strip comments
+			i = string.find(line, '#')
+			if i >= 0:
+				line = line[:i]
+			if setupvardef.match(line) >= 0:
+				name, value = setupvardef.group(1, 2)
+				variables[name] = string.strip(value)
+			else:
+				words = string.split(line)
+				if words:
+					modules[words[0]] = words[1:]
+	finally:
+		fp.close()
+	return modules, variables
+
+
+# Test the above functions.
+
+def test():
+	import sys
+	import os
+	if not sys.argv[1:]:
+		print 'usage: python parsesetup.py Makefile*|Setup* ...'
+		sys.exit(2)
+	for arg in sys.argv[1:]:
+		base = os.path.basename(arg)
+		if base[:8] == 'Makefile':
+			print 'Make style parsing:', arg
+			v = getmakevars(arg)
+			prdict(v)
+		elif base[:5] == 'Setup':
+			print 'Setup style parsing:', arg
+			m, v = getsetupinfo(arg)
+			prdict(m)
+			prdict(v)
+		else:
+			print arg, 'is neither a Makefile nor a Setup file'
+			print '(name must begin with "Makefile" or "Setup")'
+
+def prdict(d):
+	keys = d.keys()
+	keys.sort()
+	for key in keys:
+		value = d[key]
+		print "%-15s" % key, str(value)
+
+if __name__ == '__main__':
+	test()