1#! /usr/bin/env python
2
3"""Freeze a Python script into a binary.
4
5usage: freeze [options...] script [module]...
6
7Options:
8-p prefix:    This is the prefix used when you ran ``make install''
9              in the Python build directory.
10              (If you never ran this, freeze won't work.)
11              The default is whatever sys.prefix evaluates to.
12              It can also be the top directory of the Python source
13              tree; then -P must point to the build tree.
14
15-P exec_prefix: Like -p but this is the 'exec_prefix', used to
16                install objects etc.  The default is whatever sys.exec_prefix
17                evaluates to, or the -p argument if given.
18                If -p points to the Python source tree, -P must point
19                to the build tree, if different.
20
21-e extension: A directory containing additional .o files that
22              may be used to resolve modules.  This directory
23              should also have a Setup file describing the .o files.
24              On Windows, the name of a .INI file describing one
25              or more extensions is passed.
26              More than one -e option may be given.
27
28-o dir:       Directory where the output files are created; default '.'.
29
30-m:           Additional arguments are module names instead of filenames.
31
32-a package=dir: Additional directories to be added to the package's
33                __path__.  Used to simulate directories added by the
34                package at runtime (eg, by OpenGL and win32com).
35                More than one -a option may be given for each package.
36
37-l file:      Pass the file to the linker (windows only)
38
39-d:           Debugging mode for the module finder.
40
41-q:           Make the module finder totally quiet.
42
43-h:           Print this help message.
44
45-x module     Exclude the specified module. It will still be imported
46              by the frozen binary if it exists on the host system.
47
48-X module     Like -x, except the module can never be imported by
49              the frozen binary.
50
51-E:           Freeze will fail if any modules can't be found (that
52              were not excluded using -x or -X).
53
54-i filename:  Include a file with additional command line options.  Used
55              to prevent command lines growing beyond the capabilities of
56              the shell/OS.  All arguments specified in filename
57              are read and the -i option replaced with the parsed
58              params (note - quoting args in this file is NOT supported)
59
60-s subsystem: Specify the subsystem (For Windows only.);
61              'console' (default), 'windows', 'service' or 'com_dll'
62
63-w:           Toggle Windows (NT or 95) behavior.
64              (For debugging only -- on a win32 platform, win32 behavior
65              is automatic.)
66
67-r prefix=f:  Replace path prefix.
68              Replace prefix with f in the source path references
69              contained in the resulting binary.
70
71Arguments:
72
73script:       The Python script to be executed by the resulting binary.
74
75module ...:   Additional Python modules (referenced by pathname)
76              that will be included in the resulting binary.  These
77              may be .py or .pyc files.  If -m is specified, these are
78              module names that are search in the path instead.
79
80NOTES:
81
82In order to use freeze successfully, you must have built Python and
83installed it ("make install").
84
85The script should not use modules provided only as shared libraries;
86if it does, the resulting binary is not self-contained.
87"""
88
89
90# Import standard modules
91
92import modulefinder
93import getopt
94import os
95import sys
96
97
98# Import the freeze-private modules
99
100import checkextensions
101import makeconfig
102import makefreeze
103import makemakefile
104import parsesetup
105import bkfile
106
107
108# Main program
109
110def main():
111    # overridable context
112    prefix = None                       # settable with -p option
113    exec_prefix = None                  # settable with -P option
114    extensions = []
115    exclude = []                        # settable with -x option
116    addn_link = []      # settable with -l, but only honored under Windows.
117    path = sys.path[:]
118    modargs = 0
119    debug = 1
120    odir = ''
121    win = sys.platform[:3] == 'win'
122    replace_paths = []                  # settable with -r option
123    error_if_any_missing = 0
124
125    # default the exclude list for each platform
126    if win: exclude = exclude + [
127        'dos', 'dospath', 'mac', 'macpath', 'macfs', 'MACFS', 'posix',
128        'os2', 'ce', 'riscos', 'riscosenviron', 'riscospath',
129        ]
130
131    fail_import = exclude[:]
132
133    # output files
134    frozen_c = 'frozen.c'
135    config_c = 'config.c'
136    target = 'a.out'                    # normally derived from script name
137    makefile = 'Makefile'
138    subsystem = 'console'
139
140    # parse command line by first replacing any "-i" options with the
141    # file contents.
142    pos = 1
143    while pos < len(sys.argv)-1:
144        # last option can not be "-i", so this ensures "pos+1" is in range!
145        if sys.argv[pos] == '-i':
146            try:
147                options = open(sys.argv[pos+1]).read().split()
148            except IOError, why:
149                usage("File name '%s' specified with the -i option "
150                      "can not be read - %s" % (sys.argv[pos+1], why) )
151            # Replace the '-i' and the filename with the read params.
152            sys.argv[pos:pos+2] = options
153            pos = pos + len(options) - 1 # Skip the name and the included args.
154        pos = pos + 1
155
156    # Now parse the command line with the extras inserted.
157    try:
158        opts, args = getopt.getopt(sys.argv[1:], 'r:a:dEe:hmo:p:P:qs:wX:x:l:')
159    except getopt.error, msg:
160        usage('getopt error: ' + str(msg))
161
162    # proces option arguments
163    for o, a in opts:
164        if o == '-h':
165            print __doc__
166            return
167        if o == '-d':
168            debug = debug + 1
169        if o == '-e':
170            extensions.append(a)
171        if o == '-m':
172            modargs = 1
173        if o == '-o':
174            odir = a
175        if o == '-p':
176            prefix = a
177        if o == '-P':
178            exec_prefix = a
179        if o == '-q':
180            debug = 0
181        if o == '-w':
182            win = not win
183        if o == '-s':
184            if not win:
185                usage("-s subsystem option only on Windows")
186            subsystem = a
187        if o == '-x':
188            exclude.append(a)
189        if o == '-X':
190            exclude.append(a)
191            fail_import.append(a)
192        if o == '-E':
193            error_if_any_missing = 1
194        if o == '-l':
195            addn_link.append(a)
196        if o == '-a':
197            apply(modulefinder.AddPackagePath, tuple(a.split("=", 2)))
198        if o == '-r':
199            f,r = a.split("=", 2)
200            replace_paths.append( (f,r) )
201
202    # modules that are imported by the Python runtime
203    implicits = []
204    for module in ('site', 'warnings',):
205        if module not in exclude:
206            implicits.append(module)
207
208    # default prefix and exec_prefix
209    if not exec_prefix:
210        if prefix:
211            exec_prefix = prefix
212        else:
213            exec_prefix = sys.exec_prefix
214    if not prefix:
215        prefix = sys.prefix
216
217    # determine whether -p points to the Python source tree
218    ishome = os.path.exists(os.path.join(prefix, 'Python', 'ceval.c'))
219
220    # locations derived from options
221    version = sys.version[:3]
222    if win:
223        extensions_c = 'frozen_extensions.c'
224    if ishome:
225        print "(Using Python source directory)"
226        binlib = exec_prefix
227        incldir = os.path.join(prefix, 'Include')
228        config_h_dir = exec_prefix
229        config_c_in = os.path.join(prefix, 'Modules', 'config.c.in')
230        frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c')
231        makefile_in = os.path.join(exec_prefix, 'Makefile')
232        if win:
233            frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c')
234    else:
235        binlib = os.path.join(exec_prefix,
236                              'lib', 'python%s' % version, 'config')
237        incldir = os.path.join(prefix, 'include', 'python%s' % version)
238        config_h_dir = os.path.join(exec_prefix, 'include',
239                                    'python%s' % version)
240        config_c_in = os.path.join(binlib, 'config.c.in')
241        frozenmain_c = os.path.join(binlib, 'frozenmain.c')
242        makefile_in = os.path.join(binlib, 'Makefile')
243        frozendllmain_c = os.path.join(binlib, 'frozen_dllmain.c')
244    supp_sources = []
245    defines = []
246    includes = ['-I' + incldir, '-I' + config_h_dir]
247
248    # sanity check of directories and files
249    check_dirs = [prefix, exec_prefix, binlib, incldir]
250    if not win:
251        # These are not directories on Windows.
252        check_dirs = check_dirs + extensions
253    for dir in check_dirs:
254        if not os.path.exists(dir):
255            usage('needed directory %s not found' % dir)
256        if not os.path.isdir(dir):
257            usage('%s: not a directory' % dir)
258    if win:
259        files = supp_sources + extensions # extensions are files on Windows.
260    else:
261        files = [config_c_in, makefile_in] + supp_sources
262    for file in supp_sources:
263        if not os.path.exists(file):
264            usage('needed file %s not found' % file)
265        if not os.path.isfile(file):
266            usage('%s: not a plain file' % file)
267    if not win:
268        for dir in extensions:
269            setup = os.path.join(dir, 'Setup')
270            if not os.path.exists(setup):
271                usage('needed file %s not found' % setup)
272            if not os.path.isfile(setup):
273                usage('%s: not a plain file' % setup)
274
275    # check that enough arguments are passed
276    if not args:
277        usage('at least one filename argument required')
278
279    # check that file arguments exist
280    for arg in args:
281        if arg == '-m':
282            break
283        # if user specified -m on the command line before _any_
284        # file names, then nothing should be checked (as the
285        # very first file should be a module name)
286        if modargs:
287            break
288        if not os.path.exists(arg):
289            usage('argument %s not found' % arg)
290        if not os.path.isfile(arg):
291            usage('%s: not a plain file' % arg)
292
293    # process non-option arguments
294    scriptfile = args[0]
295    modules = args[1:]
296
297    # derive target name from script name
298    base = os.path.basename(scriptfile)
299    base, ext = os.path.splitext(base)
300    if base:
301        if base != scriptfile:
302            target = base
303        else:
304            target = base + '.bin'
305
306    # handle -o option
307    base_frozen_c = frozen_c
308    base_config_c = config_c
309    base_target = target
310    if odir and not os.path.isdir(odir):
311        try:
312            os.mkdir(odir)
313            print "Created output directory", odir
314        except os.error, msg:
315            usage('%s: mkdir failed (%s)' % (odir, str(msg)))
316    base = ''
317    if odir:
318        base = os.path.join(odir, '')
319        frozen_c = os.path.join(odir, frozen_c)
320        config_c = os.path.join(odir, config_c)
321        target = os.path.join(odir, target)
322        makefile = os.path.join(odir, makefile)
323        if win: extensions_c = os.path.join(odir, extensions_c)
324
325    # Handle special entry point requirements
326    # (on Windows, some frozen programs do not use __main__, but
327    # import the module directly.  Eg, DLLs, Services, etc
328    custom_entry_point = None  # Currently only used on Windows
329    python_entry_is_main = 1   # Is the entry point called __main__?
330    # handle -s option on Windows
331    if win:
332        import winmakemakefile
333        try:
334            custom_entry_point, python_entry_is_main = \
335                winmakemakefile.get_custom_entry_point(subsystem)
336        except ValueError, why:
337            usage(why)
338
339
340    # Actual work starts here...
341
342    # collect all modules of the program
343    dir = os.path.dirname(scriptfile)
344    path[0] = dir
345    mf = modulefinder.ModuleFinder(path, debug, exclude, replace_paths)
346
347    if win and subsystem=='service':
348        # If a Windows service, then add the "built-in" module.
349        mod = mf.add_module("servicemanager")
350        mod.__file__="dummy.pyd" # really built-in to the resulting EXE
351
352    for mod in implicits:
353        mf.import_hook(mod)
354    for mod in modules:
355        if mod == '-m':
356            modargs = 1
357            continue
358        if modargs:
359            if mod[-2:] == '.*':
360                mf.import_hook(mod[:-2], None, ["*"])
361            else:
362                mf.import_hook(mod)
363        else:
364            mf.load_file(mod)
365
366    # Add the main script as either __main__, or the actual module name.
367    if python_entry_is_main:
368        mf.run_script(scriptfile)
369    else:
370        mf.load_file(scriptfile)
371
372    if debug > 0:
373        mf.report()
374        print
375    dict = mf.modules
376
377    if error_if_any_missing:
378        missing = mf.any_missing()
379        if missing:
380            sys.exit("There are some missing modules: %r" % missing)
381
382    # generate output for frozen modules
383    files = makefreeze.makefreeze(base, dict, debug, custom_entry_point,
384                                  fail_import)
385
386    # look for unfrozen modules (builtin and of unknown origin)
387    builtins = []
388    unknown = []
389    mods = dict.keys()
390    mods.sort()
391    for mod in mods:
392        if dict[mod].__code__:
393            continue
394        if not dict[mod].__file__:
395            builtins.append(mod)
396        else:
397            unknown.append(mod)
398
399    # search for unknown modules in extensions directories (not on Windows)
400    addfiles = []
401    frozen_extensions = [] # Windows list of modules.
402    if unknown or (not win and builtins):
403        if not win:
404            addfiles, addmods = \
405                      checkextensions.checkextensions(unknown+builtins,
406                                                      extensions)
407            for mod in addmods:
408                if mod in unknown:
409                    unknown.remove(mod)
410                    builtins.append(mod)
411        else:
412            # Do the windows thang...
413            import checkextensions_win32
414            # Get a list of CExtension instances, each describing a module
415            # (including its source files)
416            frozen_extensions = checkextensions_win32.checkextensions(
417                unknown, extensions, prefix)
418            for mod in frozen_extensions:
419                unknown.remove(mod.name)
420
421    # report unknown modules
422    if unknown:
423        sys.stderr.write('Warning: unknown modules remain: %s\n' %
424                         ' '.join(unknown))
425
426    # windows gets different treatment
427    if win:
428        # Taking a shortcut here...
429        import winmakemakefile, checkextensions_win32
430        checkextensions_win32.write_extension_table(extensions_c,
431                                                    frozen_extensions)
432        # Create a module definition for the bootstrap C code.
433        xtras = [frozenmain_c, os.path.basename(frozen_c),
434                 frozendllmain_c, os.path.basename(extensions_c)] + files
435        maindefn = checkextensions_win32.CExtension( '__main__', xtras )
436        frozen_extensions.append( maindefn )
437        outfp = open(makefile, 'w')
438        try:
439            winmakemakefile.makemakefile(outfp,
440                                         locals(),
441                                         frozen_extensions,
442                                         os.path.basename(target))
443        finally:
444            outfp.close()
445        return
446
447    # generate config.c and Makefile
448    builtins.sort()
449    infp = open(config_c_in)
450    outfp = bkfile.open(config_c, 'w')
451    try:
452        makeconfig.makeconfig(infp, outfp, builtins)
453    finally:
454        outfp.close()
455    infp.close()
456
457    cflags = ['$(OPT)']
458    cppflags = defines + includes
459    libs = [os.path.join(binlib, 'libpython$(VERSION).a')]
460
461    somevars = {}
462    if os.path.exists(makefile_in):
463        makevars = parsesetup.getmakevars(makefile_in)
464        for key in makevars.keys():
465            somevars[key] = makevars[key]
466
467    somevars['CFLAGS'] = ' '.join(cflags) # override
468    somevars['CPPFLAGS'] = ' '.join(cppflags) # override
469    files = [base_config_c, base_frozen_c] + \
470            files + supp_sources +  addfiles + libs + \
471            ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)']
472
473    outfp = bkfile.open(makefile, 'w')
474    try:
475        makemakefile.makemakefile(outfp, somevars, files, base_target)
476    finally:
477        outfp.close()
478
479    # Done!
480
481    if odir:
482        print 'Now run "make" in', odir,
483        print 'to build the target:', base_target
484    else:
485        print 'Now run "make" to build the target:', base_target
486
487
488# Print usage message and exit
489
490def usage(msg):
491    sys.stdout = sys.stderr
492    print "Error:", msg
493    print "Use ``%s -h'' for help" % sys.argv[0]
494    sys.exit(2)
495
496
497main()
498