From 00ff4336a9c4258d72aa11fb5160f7afbbe88d84 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 3 Oct 1994 16:33:08 +0000 Subject: Totally new "freeze" program. --- Tools/freeze/findmodules.py | 128 ++++++++ Tools/freeze/freeze.py | 720 ++++++++++--------------------------------- Tools/freeze/makeconfig.py | 57 ++++ Tools/freeze/makefreeze.py | 91 ++++++ Tools/freeze/makemakefile.py | 27 ++ Tools/freeze/parsesetup.py | 98 ++++++ 6 files changed, 561 insertions(+), 560 deletions(-) create mode 100644 Tools/freeze/findmodules.py create mode 100644 Tools/freeze/makeconfig.py create mode 100644 Tools/freeze/makefreeze.py create mode 100644 Tools/freeze/makemakefile.py create mode 100644 Tools/freeze/parsesetup.py 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 ('', ''): + 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 '', or ''. + +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 '' + 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 '' + + +# 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!) + +# 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 os -import sys -import regex import getopt -import regsub +import os import string -import marshal - -# 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 - -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 - -# Main program -- argument parsing etc. +import sys +import addpack + + +# Set the directory to look for the freeze-private modules + +dir = os.path.dirname(sys.argv[0]) +if dir: + pack = dir +addpack.addpack(pack) + + +# 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:]) - -# 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' - -# 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 ...' + if o == '-p': + prefix = a + + # 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] + + # 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] == '' 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] - - 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 + if dict[mod] == '': + builtins.append(mod) + + 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 + makefreeze.makefreeze(outfp, dict) + finally: + outfp.close() + + infp = open(config_c_in) + outfp = open(config_c, 'w') 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 '};' + makeconfig.makeconfig(infp, outfp, builtins) finally: - sys.stdout = save_stdout - f.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 ('', ''): - 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 = {} + outfp.close() + infp.close() + + 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) + + makevars = parsesetup.getmakevars(makefile_in) + somevars = {} + for key in makevars.keys(): + somevars[key] = makevars[key] + + somevars['CFLAGS'] = string.join(cflags) # override + files = ['$(OPT)', config_c, frozenmain_c] + libs + \ + ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)'] + + outfp = open(makefile, '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() - -# 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 '' - 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 '' -# -# 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 - -# -# 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) - -# Call the main program + 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_. + +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() -- cgit v0.12