home *** CD-ROM | disk | FTP | other *** search
- #! /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()
-