| #! /usr/bin/env python
|
|
|
| """Freeze a Python script into a binary.
|
|
|
| usage: freeze [options...] script [module]...
|
|
|
| Options:
|
| -p prefix: This is the prefix used when you ran ``make install''
|
| in the Python build directory.
|
| (If you never ran this, freeze won't work.)
|
| The default is whatever sys.prefix evaluates to.
|
| It can also be the top directory of the Python source
|
| tree; then -P must point to the build tree.
|
|
|
| -P exec_prefix: Like -p but this is the 'exec_prefix', used to
|
| install objects etc. The default is whatever sys.exec_prefix
|
| evaluates to, or the -p argument if given.
|
| If -p points to the Python source tree, -P must point
|
| to the build tree, if different.
|
|
|
| -e extension: A directory containing additional .o files that
|
| may be used to resolve modules. This directory
|
| should also have a Setup file describing the .o files.
|
| On Windows, the name of a .INI file describing one
|
| or more extensions is passed.
|
| More than one -e option may be given.
|
|
|
| -o dir: Directory where the output files are created; default '.'.
|
|
|
| -m: Additional arguments are module names instead of filenames.
|
|
|
| -a package=dir: Additional directories to be added to the package's
|
| __path__. Used to simulate directories added by the
|
| package at runtime (eg, by OpenGL and win32com).
|
| More than one -a option may be given for each package.
|
|
|
| -l file: Pass the file to the linker (windows only)
|
|
|
| -d: Debugging mode for the module finder.
|
|
|
| -q: Make the module finder totally quiet.
|
|
|
| -h: Print this help message.
|
|
|
| -x module Exclude the specified module. It will still be imported
|
| by the frozen binary if it exists on the host system.
|
|
|
| -X module Like -x, except the module can never be imported by
|
| the frozen binary.
|
|
|
| -E: Freeze will fail if any modules can't be found (that
|
| were not excluded using -x or -X).
|
|
|
| -i filename: Include a file with additional command line options. Used
|
| to prevent command lines growing beyond the capabilities of
|
| the shell/OS. All arguments specified in filename
|
| are read and the -i option replaced with the parsed
|
| params (note - quoting args in this file is NOT supported)
|
|
|
| -s subsystem: Specify the subsystem (For Windows only.);
|
| 'console' (default), 'windows', 'service' or 'com_dll'
|
|
|
| -w: Toggle Windows (NT or 95) behavior.
|
| (For debugging only -- on a win32 platform, win32 behavior
|
| is automatic.)
|
|
|
| -r prefix=f: Replace path prefix.
|
| Replace prefix with f in the source path references
|
| contained in the resulting binary.
|
|
|
| Arguments:
|
|
|
| script: The Python script to be executed by the resulting binary.
|
|
|
| module ...: Additional Python modules (referenced by pathname)
|
| that will be included in the resulting binary. These
|
| may be .py or .pyc files. If -m is specified, these are
|
| module names that are search in the path instead.
|
|
|
| NOTES:
|
|
|
| In order to use freeze successfully, you must have built Python and
|
| installed it ("make install").
|
|
|
| The script should not use modules provided only as shared libraries;
|
| if it does, the resulting binary is not self-contained.
|
| """
|
|
|
|
|
| # Import standard modules
|
|
|
| import modulefinder
|
| import getopt
|
| import os
|
| import sys
|
|
|
|
|
| # Import the freeze-private modules
|
|
|
| import checkextensions
|
| import makeconfig
|
| import makefreeze
|
| import makemakefile
|
| import parsesetup
|
| import bkfile
|
|
|
|
|
| # Main program
|
|
|
| def main():
|
| # overridable context
|
| prefix = None # settable with -p option
|
| exec_prefix = None # settable with -P option
|
| extensions = []
|
| exclude = [] # settable with -x option
|
| addn_link = [] # settable with -l, but only honored under Windows.
|
| path = sys.path[:]
|
| modargs = 0
|
| debug = 1
|
| odir = ''
|
| win = sys.platform[:3] == 'win'
|
| replace_paths = [] # settable with -r option
|
| error_if_any_missing = 0
|
|
|
| # default the exclude list for each platform
|
| if win: exclude = exclude + [
|
| 'dos', 'dospath', 'mac', 'macpath', 'macfs', 'MACFS', 'posix',
|
| 'os2', 'ce', 'riscos', 'riscosenviron', 'riscospath',
|
| ]
|
|
|
| fail_import = exclude[:]
|
|
|
| # output files
|
| frozen_c = 'frozen.c'
|
| config_c = 'config.c'
|
| target = 'a.out' # normally derived from script name
|
| makefile = 'Makefile'
|
| subsystem = 'console'
|
|
|
| # parse command line by first replacing any "-i" options with the
|
| # file contents.
|
| pos = 1
|
| while pos < len(sys.argv)-1:
|
| # last option can not be "-i", so this ensures "pos+1" is in range!
|
| if sys.argv[pos] == '-i':
|
| try:
|
| options = open(sys.argv[pos+1]).read().split()
|
| except IOError, why:
|
| usage("File name '%s' specified with the -i option "
|
| "can not be read - %s" % (sys.argv[pos+1], why) )
|
| # Replace the '-i' and the filename with the read params.
|
| sys.argv[pos:pos+2] = options
|
| pos = pos + len(options) - 1 # Skip the name and the included args.
|
| pos = pos + 1
|
|
|
| # Now parse the command line with the extras inserted.
|
| try:
|
| opts, args = getopt.getopt(sys.argv[1:], 'r:a:dEe:hmo:p:P:qs:wX:x:l:')
|
| except getopt.error, msg:
|
| usage('getopt error: ' + str(msg))
|
|
|
| # proces option arguments
|
| for o, a in opts:
|
| if o == '-h':
|
| print __doc__
|
| return
|
| if o == '-d':
|
| debug = debug + 1
|
| if o == '-e':
|
| extensions.append(a)
|
| if o == '-m':
|
| modargs = 1
|
| if o == '-o':
|
| odir = a
|
| if o == '-p':
|
| prefix = a
|
| if o == '-P':
|
| exec_prefix = a
|
| if o == '-q':
|
| debug = 0
|
| if o == '-w':
|
| win = not win
|
| if o == '-s':
|
| if not win:
|
| usage("-s subsystem option only on Windows")
|
| subsystem = a
|
| if o == '-x':
|
| exclude.append(a)
|
| if o == '-X':
|
| exclude.append(a)
|
| fail_import.append(a)
|
| if o == '-E':
|
| error_if_any_missing = 1
|
| if o == '-l':
|
| addn_link.append(a)
|
| if o == '-a':
|
| apply(modulefinder.AddPackagePath, tuple(a.split("=", 2)))
|
| if o == '-r':
|
| f,r = a.split("=", 2)
|
| replace_paths.append( (f,r) )
|
|
|
| # modules that are imported by the Python runtime
|
| implicits = []
|
| for module in ('site', 'warnings',):
|
| if module not in exclude:
|
| implicits.append(module)
|
|
|
| # default prefix and exec_prefix
|
| if not exec_prefix:
|
| if prefix:
|
| exec_prefix = prefix
|
| else:
|
| exec_prefix = sys.exec_prefix
|
| if not prefix:
|
| prefix = sys.prefix
|
|
|
| # determine whether -p points to the Python source tree
|
| ishome = os.path.exists(os.path.join(prefix, 'Python', 'ceval.c'))
|
|
|
| # locations derived from options
|
| version = sys.version[:3]
|
| if win:
|
| extensions_c = 'frozen_extensions.c'
|
| if ishome:
|
| print "(Using Python source directory)"
|
| binlib = exec_prefix
|
| incldir = os.path.join(prefix, 'Include')
|
| config_h_dir = exec_prefix
|
| config_c_in = os.path.join(prefix, 'Modules', 'config.c.in')
|
| frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c')
|
| makefile_in = os.path.join(exec_prefix, 'Makefile')
|
| if win:
|
| frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c')
|
| else:
|
| binlib = os.path.join(exec_prefix,
|
| 'lib', 'python%s' % version, 'config')
|
| incldir = os.path.join(prefix, 'include', 'python%s' % version)
|
| config_h_dir = os.path.join(exec_prefix, 'include',
|
| 'python%s' % version)
|
| 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')
|
| frozendllmain_c = os.path.join(binlib, 'frozen_dllmain.c')
|
| supp_sources = []
|
| defines = []
|
| includes = ['-I' + incldir, '-I' + config_h_dir]
|
|
|
| # sanity check of directories and files
|
| check_dirs = [prefix, exec_prefix, binlib, incldir]
|
| if not win:
|
| # These are not directories on Windows.
|
| check_dirs = check_dirs + extensions
|
| for dir in check_dirs:
|
| if not os.path.exists(dir):
|
| usage('needed directory %s not found' % dir)
|
| if not os.path.isdir(dir):
|
| usage('%s: not a directory' % dir)
|
| if win:
|
| files = supp_sources + extensions # extensions are files on Windows.
|
| else:
|
| files = [config_c_in, makefile_in] + supp_sources
|
| for file in supp_sources:
|
| 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)
|
| if not win:
|
| for dir in extensions:
|
| setup = os.path.join(dir, 'Setup')
|
| if not os.path.exists(setup):
|
| usage('needed file %s not found' % setup)
|
| if not os.path.isfile(setup):
|
| usage('%s: not a plain file' % setup)
|
|
|
| # check that enough arguments are passed
|
| if not args:
|
| usage('at least one filename argument required')
|
|
|
| # check that file arguments exist
|
| for arg in args:
|
| if arg == '-m':
|
| break
|
| # if user specified -m on the command line before _any_
|
| # file names, then nothing should be checked (as the
|
| # very first file should be a module name)
|
| if modargs:
|
| break
|
| 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:
|
| target = base + '.bin'
|
|
|
| # handle -o option
|
| base_frozen_c = frozen_c
|
| base_config_c = config_c
|
| base_target = target
|
| if odir and not os.path.isdir(odir):
|
| try:
|
| os.mkdir(odir)
|
| print "Created output directory", odir
|
| except os.error, msg:
|
| usage('%s: mkdir failed (%s)' % (odir, str(msg)))
|
| base = ''
|
| if odir:
|
| base = os.path.join(odir, '')
|
| frozen_c = os.path.join(odir, frozen_c)
|
| config_c = os.path.join(odir, config_c)
|
| target = os.path.join(odir, target)
|
| makefile = os.path.join(odir, makefile)
|
| if win: extensions_c = os.path.join(odir, extensions_c)
|
|
|
| # Handle special entry point requirements
|
| # (on Windows, some frozen programs do not use __main__, but
|
| # import the module directly. Eg, DLLs, Services, etc
|
| custom_entry_point = None # Currently only used on Windows
|
| python_entry_is_main = 1 # Is the entry point called __main__?
|
| # handle -s option on Windows
|
| if win:
|
| import winmakemakefile
|
| try:
|
| custom_entry_point, python_entry_is_main = \
|
| winmakemakefile.get_custom_entry_point(subsystem)
|
| except ValueError, why:
|
| usage(why)
|
|
|
|
|
| # Actual work starts here...
|
|
|
| # collect all modules of the program
|
| dir = os.path.dirname(scriptfile)
|
| path[0] = dir
|
| mf = modulefinder.ModuleFinder(path, debug, exclude, replace_paths)
|
|
|
| if win and subsystem=='service':
|
| # If a Windows service, then add the "built-in" module.
|
| mod = mf.add_module("servicemanager")
|
| mod.__file__="dummy.pyd" # really built-in to the resulting EXE
|
|
|
| for mod in implicits:
|
| mf.import_hook(mod)
|
| for mod in modules:
|
| if mod == '-m':
|
| modargs = 1
|
| continue
|
| if modargs:
|
| if mod[-2:] == '.*':
|
| mf.import_hook(mod[:-2], None, ["*"])
|
| else:
|
| mf.import_hook(mod)
|
| else:
|
| mf.load_file(mod)
|
|
|
| # Add the main script as either __main__, or the actual module name.
|
| if python_entry_is_main:
|
| mf.run_script(scriptfile)
|
| else:
|
| mf.load_file(scriptfile)
|
|
|
| if debug > 0:
|
| mf.report()
|
| print
|
| dict = mf.modules
|
|
|
| if error_if_any_missing:
|
| missing = mf.any_missing()
|
| if missing:
|
| sys.exit("There are some missing modules: %r" % missing)
|
|
|
| # generate output for frozen modules
|
| files = makefreeze.makefreeze(base, dict, debug, custom_entry_point,
|
| fail_import)
|
|
|
| # look for unfrozen modules (builtin and of unknown origin)
|
| builtins = []
|
| unknown = []
|
| mods = dict.keys()
|
| mods.sort()
|
| for mod in mods:
|
| if dict[mod].__code__:
|
| continue
|
| if not dict[mod].__file__:
|
| builtins.append(mod)
|
| else:
|
| unknown.append(mod)
|
|
|
| # search for unknown modules in extensions directories (not on Windows)
|
| addfiles = []
|
| frozen_extensions = [] # Windows list of modules.
|
| if unknown or (not win and builtins):
|
| if not win:
|
| addfiles, addmods = \
|
| checkextensions.checkextensions(unknown+builtins,
|
| extensions)
|
| for mod in addmods:
|
| if mod in unknown:
|
| unknown.remove(mod)
|
| builtins.append(mod)
|
| else:
|
| # Do the windows thang...
|
| import checkextensions_win32
|
| # Get a list of CExtension instances, each describing a module
|
| # (including its source files)
|
| frozen_extensions = checkextensions_win32.checkextensions(
|
| unknown, extensions, prefix)
|
| for mod in frozen_extensions:
|
| unknown.remove(mod.name)
|
|
|
| # report unknown modules
|
| if unknown:
|
| sys.stderr.write('Warning: unknown modules remain: %s\n' %
|
| ' '.join(unknown))
|
|
|
| # windows gets different treatment
|
| if win:
|
| # Taking a shortcut here...
|
| import winmakemakefile, checkextensions_win32
|
| checkextensions_win32.write_extension_table(extensions_c,
|
| frozen_extensions)
|
| # Create a module definition for the bootstrap C code.
|
| xtras = [frozenmain_c, os.path.basename(frozen_c),
|
| frozendllmain_c, os.path.basename(extensions_c)] + files
|
| maindefn = checkextensions_win32.CExtension( '__main__', xtras )
|
| frozen_extensions.append( maindefn )
|
| outfp = open(makefile, 'w')
|
| try:
|
| winmakemakefile.makemakefile(outfp,
|
| locals(),
|
| frozen_extensions,
|
| os.path.basename(target))
|
| finally:
|
| outfp.close()
|
| return
|
|
|
| # generate config.c and Makefile
|
| builtins.sort()
|
| infp = open(config_c_in)
|
| outfp = bkfile.open(config_c, 'w')
|
| try:
|
| makeconfig.makeconfig(infp, outfp, builtins)
|
| finally:
|
| outfp.close()
|
| infp.close()
|
|
|
| cflags = ['$(OPT)']
|
| cppflags = defines + includes
|
| libs = [os.path.join(binlib, 'libpython$(VERSION).a')]
|
|
|
| somevars = {}
|
| if os.path.exists(makefile_in):
|
| makevars = parsesetup.getmakevars(makefile_in)
|
| for key in makevars.keys():
|
| somevars[key] = makevars[key]
|
|
|
| somevars['CFLAGS'] = ' '.join(cflags) # override
|
| somevars['CPPFLAGS'] = ' '.join(cppflags) # override
|
| files = [base_config_c, base_frozen_c] + \
|
| files + supp_sources + addfiles + libs + \
|
| ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)']
|
|
|
| outfp = bkfile.open(makefile, 'w')
|
| try:
|
| makemakefile.makemakefile(outfp, somevars, files, base_target)
|
| finally:
|
| outfp.close()
|
|
|
| # Done!
|
|
|
| if odir:
|
| print 'Now run "make" in', odir,
|
| print 'to build the target:', base_target
|
| else:
|
| print 'Now run "make" to build the target:', base_target
|
|
|
|
|
| # Print usage message and exit
|
|
|
| def usage(msg):
|
| sys.stdout = sys.stderr
|
| print "Error:", msg
|
| print "Use ``%s -h'' for help" % sys.argv[0]
|
| sys.exit(2)
|
|
|
|
|
| main()
|