|
#! /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)) |
|
|
|
# process 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() |