1#! /usr/bin/env python3
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
129    fail_import = exclude[:]
130
131    # output files
132    frozen_c = 'frozen.c'
133    config_c = 'config.c'
134    target = 'a.out'                    # normally derived from script name
135    makefile = 'Makefile'
136    subsystem = 'console'
137
138    # parse command line by first replacing any "-i" options with the
139    # file contents.
140    pos = 1
141    while pos < len(sys.argv)-1:
142        # last option can not be "-i", so this ensures "pos+1" is in range!
143        if sys.argv[pos] == '-i':
144            try:
145                options = open(sys.argv[pos+1]).read().split()
146            except IOError as why:
147                usage("File name '%s' specified with the -i option "
148                      "can not be read - %s" % (sys.argv[pos+1], why) )
149            # Replace the '-i' and the filename with the read params.
150            sys.argv[pos:pos+2] = options
151            pos = pos + len(options) - 1 # Skip the name and the included args.
152        pos = pos + 1
153
154    # Now parse the command line with the extras inserted.
155    try:
156        opts, args = getopt.getopt(sys.argv[1:], 'r:a:dEe:hmo:p:P:qs:wX:x:l:')
157    except getopt.error as msg:
158        usage('getopt error: ' + str(msg))
159
160    # process option arguments
161    for o, a in opts:
162        if o == '-h':
163            print(__doc__)
164            return
165        if o == '-d':
166            debug = debug + 1
167        if o == '-e':
168            extensions.append(a)
169        if o == '-m':
170            modargs = 1
171        if o == '-o':
172            odir = a
173        if o == '-p':
174            prefix = a
175        if o == '-P':
176            exec_prefix = a
177        if o == '-q':
178            debug = 0
179        if o == '-w':
180            win = not win
181        if o == '-s':
182            if not win:
183                usage("-s subsystem option only on Windows")
184            subsystem = a
185        if o == '-x':
186            exclude.append(a)
187        if o == '-X':
188            exclude.append(a)
189            fail_import.append(a)
190        if o == '-E':
191            error_if_any_missing = 1
192        if o == '-l':
193            addn_link.append(a)
194        if o == '-a':
195            modulefinder.AddPackagePath(*a.split("=", 2))
196        if o == '-r':
197            f,r = a.split("=", 2)
198            replace_paths.append( (f,r) )
199
200    # modules that are imported by the Python runtime
201    implicits = []
202    for module in ('site', 'warnings', 'encodings.utf_8', 'encodings.latin_1'):
203        if module not in exclude:
204            implicits.append(module)
205
206    # default prefix and exec_prefix
207    if not exec_prefix:
208        if prefix:
209            exec_prefix = prefix
210        else:
211            exec_prefix = sys.exec_prefix
212    if not prefix:
213        prefix = sys.prefix
214
215    # determine whether -p points to the Python source tree
216    ishome = os.path.exists(os.path.join(prefix, 'Python', 'ceval.c'))
217
218    # locations derived from options
219    version = '%d.%d' % sys.version_info[:2]
220    flagged_version = version + sys.abiflags
221    if win:
222        extensions_c = 'frozen_extensions.c'
223    if ishome:
224        print("(Using Python source directory)")
225        binlib = exec_prefix
226        incldir = os.path.join(prefix, 'Include')
227        config_h_dir = exec_prefix
228        config_c_in = os.path.join(prefix, 'Modules', 'config.c.in')
229        frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c')
230        makefile_in = os.path.join(exec_prefix, 'Makefile')
231        if win:
232            frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c')
233    else:
234        binlib = os.path.join(exec_prefix,
235                              'lib', 'python%s' % version,
236                              'config-%s' % flagged_version)
237        incldir = os.path.join(prefix, 'include', 'python%s' % flagged_version)
238        config_h_dir = os.path.join(exec_prefix, 'include',
239                                    'python%s' % flagged_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 OSError as 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 as 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    # Alias "importlib._bootstrap" to "_frozen_importlib" so that the
367    # import machinery can bootstrap.  Do the same for
368    # importlib._bootstrap_external.
369    mf.modules["_frozen_importlib"] = mf.modules["importlib._bootstrap"]
370    mf.modules["_frozen_importlib_external"] = mf.modules["importlib._bootstrap_external"]
371
372    # Add the main script as either __main__, or the actual module name.
373    if python_entry_is_main:
374        mf.run_script(scriptfile)
375    else:
376        mf.load_file(scriptfile)
377
378    if debug > 0:
379        mf.report()
380        print()
381    dict = mf.modules
382
383    if error_if_any_missing:
384        missing = mf.any_missing()
385        if missing:
386            sys.exit("There are some missing modules: %r" % missing)
387
388    # generate output for frozen modules
389    files = makefreeze.makefreeze(base, dict, debug, custom_entry_point,
390                                  fail_import)
391
392    # look for unfrozen modules (builtin and of unknown origin)
393    builtins = []
394    unknown = []
395    mods = sorted(dict.keys())
396    for mod in mods:
397        if dict[mod].__code__:
398            continue
399        if not dict[mod].__file__:
400            builtins.append(mod)
401        else:
402            unknown.append(mod)
403
404    # search for unknown modules in extensions directories (not on Windows)
405    addfiles = []
406    frozen_extensions = [] # Windows list of modules.
407    if unknown or (not win and builtins):
408        if not win:
409            addfiles, addmods = \
410                      checkextensions.checkextensions(unknown+builtins,
411                                                      extensions)
412            for mod in addmods:
413                if mod in unknown:
414                    unknown.remove(mod)
415                    builtins.append(mod)
416        else:
417            # Do the windows thang...
418            import checkextensions_win32
419            # Get a list of CExtension instances, each describing a module
420            # (including its source files)
421            frozen_extensions = checkextensions_win32.checkextensions(
422                unknown, extensions, prefix)
423            for mod in frozen_extensions:
424                unknown.remove(mod.name)
425
426    # report unknown modules
427    if unknown:
428        sys.stderr.write('Warning: unknown modules remain: %s\n' %
429                         ' '.join(unknown))
430
431    # windows gets different treatment
432    if win:
433        # Taking a shortcut here...
434        import winmakemakefile, checkextensions_win32
435        checkextensions_win32.write_extension_table(extensions_c,
436                                                    frozen_extensions)
437        # Create a module definition for the bootstrap C code.
438        xtras = [frozenmain_c, os.path.basename(frozen_c),
439                 frozendllmain_c, os.path.basename(extensions_c)] + files
440        maindefn = checkextensions_win32.CExtension( '__main__', xtras )
441        frozen_extensions.append( maindefn )
442        with open(makefile, 'w') as outfp:
443            winmakemakefile.makemakefile(outfp,
444                                         locals(),
445                                         frozen_extensions,
446                                         os.path.basename(target))
447        return
448
449    # generate config.c and Makefile
450    builtins.sort()
451    with open(config_c_in) as infp, bkfile.open(config_c, 'w') as outfp:
452        makeconfig.makeconfig(infp, outfp, builtins)
453
454    cflags = ['$(OPT)']
455    cppflags = defines + includes
456    libs = [os.path.join(binlib, '$(LDLIBRARY)')]
457
458    somevars = {}
459    if os.path.exists(makefile_in):
460        makevars = parsesetup.getmakevars(makefile_in)
461        for key in makevars:
462            somevars[key] = makevars[key]
463
464    somevars['CFLAGS'] = ' '.join(cflags) # override
465    somevars['CPPFLAGS'] = ' '.join(cppflags) # override
466    files = [base_config_c, base_frozen_c] + \
467            files + supp_sources +  addfiles + libs + \
468            ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)']
469
470    with bkfile.open(makefile, 'w') as outfp:
471        makemakefile.makemakefile(outfp, somevars, files, base_target)
472
473    # Done!
474
475    if odir:
476        print('Now run "make" in', odir, end=' ')
477        print('to build the target:', base_target)
478    else:
479        print('Now run "make" to build the target:', base_target)
480
481
482# Print usage message and exit
483
484def usage(msg):
485    sys.stdout = sys.stderr
486    print("Error:", msg)
487    print("Use ``%s -h'' for help" % sys.argv[0])
488    sys.exit(2)
489
490
491main()
492