| #! /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: |
| # - It's highly non-portable, since it knows about paths and libraries |
| # (there's a customization section though, and it knows how to |
| # distinguish an SGI from a Sun SPARC system -- adding knowledge |
| # about more systems is left as an exercise for the reader). |
| # - You need to have the Python source tree lying around as well as |
| # the "libpython.a" 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. Since |
| # sys.path in the resulting binary only contains '.', if your |
| # program searches its data files along sys.path (as the 'flp' |
| # modules does to find its forms definitions), you may need to |
| # change the program to extend the search path or instruct its users |
| # to set the environment variable PYTHONPATH to point to your data |
| # files. |
| # |
| # 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') |
| |
| |
| import os |
| import sys |
| import regex |
| import getopt |
| import regsub |
| import string |
| import marshal |
| |
| # Function to join two pathnames with a slash in between |
| j = os.path.join |
| |
| ################################## |
| # START OF CONFIGURATION SECTION # |
| ################################## |
| |
| # Attempt to guess machine architecture |
| if os.path.exists('/usr/lib/libgl_s'): ARCH = 'sgi' |
| elif os.path.exists('/etc/issue'): ARCH = 'sequent' |
| else: ARCH = 'sun4' |
| |
| # Site parametrizations (change to match your site) |
| CC = 'cc' # C compiler |
| TOP = '/ufs/guido/src' # Parent of all source trees |
| PYTHON = j(TOP, 'python') # Top of the Python source tree |
| SRC = j(PYTHON, 'src') # Python source directory |
| BLD = j(PYTHON, 'build.' + ARCH) # Python build directory |
| #BLD = SRC # Use this if you build in SRC |
| |
| LIBINST = '/ufs/guido/src/python/irix4/tmp/lib/python/lib' # installed libraries |
| INCLINST = '/ufs/guido/src/python/irix4/tmp/include/Py' # installed include files |
| |
| # Other packages (change to match your site) |
| DL = j(TOP, 'dl') # Top of the dl source tree |
| DL_DLD = j(TOP, 'dl-dld') # The dl-dld source directory |
| DLD = j(TOP, 'dld-3.2.3') # The dld source directory |
| FORMS = j(TOP, 'forms') # Top of the FORMS source tree |
| STDWIN = j(TOP, 'stdwin') # Top of the STDWIN source tree |
| READLINE = j(TOP, 'readline.' + ARCH) # Top of the GNU Readline source tree |
| SUN_X11 = '/usr/local/X11R5/lib/libX11.a' |
| |
| # File names (usually no need to change) |
| LIBP = [ # Main Python libraries |
| j(LIBINST, 'libPython.a'), |
| j(LIBINST, 'libParser.a'), |
| j(LIBINST, 'libObjects.a'), |
| j(LIBINST, 'libModules.a') |
| ] |
| CONFIG_IN = j(LIBINST, 'config.c.in') # Configuration source file |
| FMAIN = j(LIBINST, 'frozenmain.c') # Special main source file |
| |
| # Libraries needed when linking. First tuple item is built-in module |
| # for which it is needed (or '*' for always), rest are ld arguments. |
| # There is a separate list per architecture. |
| libdeps_sgi = [ \ |
| ('stdwin', j(STDWIN, 'Build/' + ARCH + '/x11/lib/lib.a')), \ |
| ('fl', j(FORMS, 'FORMS/libforms.a'), '-lfm_s'), \ |
| ('*', j(READLINE, 'libreadline.a'), '-ltermcap'), \ |
| ('al', '-laudio'), \ |
| ('sv', '-lsvideo', '-lXext'), \ |
| ('cd', '-lcdaudio', '-lds'), \ |
| ('cl', '-lcl'), \ |
| ('imgfile', '-limage', '-lgutil', '-lm'), \ |
| ('mpz', '/ufs/guido/src/gmp/libgmp.a'), \ |
| ('*', '-lsun'), \ |
| ('*', j(DL, 'libdl.a'), '-lmld'), \ |
| ('*', '-lmpc'), \ |
| ('fm', '-lfm_s'), \ |
| ('gl', '-lgl_s', '-lX11_s'), \ |
| ('stdwin', '-lX11_s'), \ |
| ('*', '-lm'), \ |
| ('*', '-lc_s'), \ |
| ] |
| libdeps_sun4 = [ \ |
| ('*', '-Bstatic'), \ |
| ('stdwin', j(STDWIN, 'Build/' + ARCH + '/x11/lib/lib.a')), \ |
| ('*', j(READLINE, 'libreadline.a')), \ |
| ('*', '-lm'), \ |
| ('*', j(DL_DLD,'libdl.a'), j(DLD,'libdld.a')), \ |
| ('*', SUN_X11), \ |
| ('*', '-ltermcap'), \ |
| ('*', '-lc'), \ |
| ] |
| libdeps_sequent = [ \ |
| ('*', j(LIBINST, 'libreadline.a'), '-ltermcap'), \ |
| ('*', '-lsocket'), \ |
| ('*', '-linet'), \ |
| ('*', '-lnsl'), \ |
| ('*', '-lm'), \ |
| ('*', '-lc'), \ |
| ] |
| libdeps = eval('libdeps_' + ARCH) |
| |
| ################################ |
| # END OF CONFIGURATION SECTION # |
| ################################ |
| |
| # Exception used when scanfile fails |
| NoSuchFile = 'NoSuchFile' |
| |
| # Global options |
| quiet = 0 # -q |
| verbose = 0 # -v |
| noexec = 0 # -n |
| nowrite = 0 # -N |
| ofile = 'a.out' # -o file |
| |
| # Main program -- argument parsing etc. |
| def main(): |
| global quiet, verbose, noexec, nowrite, ofile |
| try: |
| opts, args = getopt.getopt(sys.argv[1:], 'nNo:qv') |
| except getopt.error, msg: |
| usage(str(msg)) |
| sys.exit(2) |
| for o, a in opts: |
| if o == '-n': noexec = 1 |
| if o == '-N': nowrite = 1 |
| if o == '-o': ofile = a |
| if o == '-q': verbose = 0; quiet = 1 |
| if o == '-v': verbose = verbose + 1; quiet = 0 |
| 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 '-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 '-q : quiet (no messages at all except errors)' |
| print '-v : verbose (lots of extra messages)' |
| |
| # 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 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 ...' |
| 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') |
| todomodules = builtinmodules + dlmodules |
| for mod in dict.keys(): |
| if dict[mod] == '<builtin>' and \ |
| mod not in stdmodules: |
| builtinmodules.append(mod) |
| 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', '-DPYTHONPATH=\'"."\''] |
| # |
| incs = ['-I.', '-I' + INCLINST] |
| if dict.has_key('stdwin'): |
| incs.append('-I' + j(STDWIN, 'H')) |
| # |
| srcs = [config, FMAIN] |
| # |
| if type(LIBP) == type(''): |
| libs.append(LIBP) |
| else: |
| for lib in LIBP: |
| libs.append(lib) |
| for item in libdeps: |
| m = item[0] |
| if m == '*' or dict.has_key(m): |
| for l in item[1:]: |
| if l in libs: libs.remove(l) |
| libs.append(l) |
| # |
| sts = 0 |
| # |
| cmd = CC + ' -c' |
| 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) |
| 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 |
| 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 '};' |
| 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 ('<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 = {} |
| 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 '<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>' |
| |
| |
| # Call the main program |
| main() |