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
96import sysconfig
97
98
99# Import the freeze-private modules
100
101import checkextensions
102import makeconfig
103import makefreeze
104import makemakefile
105import parsesetup
106import bkfile
107
108
109# Main program
110
111def main():
112    # overridable context
113    prefix = None                       # settable with -p option
114    exec_prefix = None                  # settable with -P option
115    extensions = []
116    exclude = []                        # settable with -x option
117    addn_link = []      # settable with -l, but only honored under Windows.
118    path = sys.path[:]
119    modargs = 0
120    debug = 1
121    odir = ''
122    win = sys.platform[:3] == 'win'
123    replace_paths = []                  # settable with -r option
124    error_if_any_missing = 0
125
126    # default the exclude list for each platform
127    if win: exclude = exclude + [
128        'dos', 'dospath', 'mac', 'macfs', 'MACFS', 'posix', ]
129
130    fail_import = exclude[:]
131
132    # output files
133    frozen_c = 'frozen.c'
134    config_c = 'config.c'
135    target = 'a.out'                    # normally derived from script name
136    makefile = 'Makefile'
137    subsystem = 'console'
138
139    # parse command line by first replacing any "-i" options with the
140    # file contents.
141    pos = 1
142    while pos < len(sys.argv)-1:
143        # last option can not be "-i", so this ensures "pos+1" is in range!
144        if sys.argv[pos] == '-i':
145            try:
146                with open(sys.argv[pos+1]) as infp:
147                    options = infp.read().split()
148            except IOError as 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 as msg:
160        usage('getopt error: ' + str(msg))
161
162    # process 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            modulefinder.AddPackagePath(*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', 'encodings.utf_8', 'encodings.latin_1'):
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 = '%d.%d' % sys.version_info[:2]
222    if hasattr(sys, 'abiflags'):
223        flagged_version = version + sys.abiflags
224    else:
225        flagged_version = version
226    if win:
227        extensions_c = 'frozen_extensions.c'
228    if ishome:
229        print("(Using Python source directory)")
230        configdir = exec_prefix
231        incldir = os.path.join(prefix, 'Include')
232        config_h_dir = exec_prefix
233        config_c_in = os.path.join(prefix, 'Modules', 'config.c.in')
234        frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c')
235        makefile_in = os.path.join(exec_prefix, 'Makefile')
236        if win:
237            frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c')
238    else:
239        configdir = sysconfig.get_config_var('LIBPL')
240        incldir = os.path.join(prefix, 'include', 'python%s' % flagged_version)
241        config_h_dir = os.path.join(exec_prefix, 'include',
242                                    'python%s' % flagged_version)
243        config_c_in = os.path.join(configdir, 'config.c.in')
244        frozenmain_c = os.path.join(configdir, 'frozenmain.c')
245        makefile_in = os.path.join(configdir, 'Makefile')
246        frozendllmain_c = os.path.join(configdir, 'frozen_dllmain.c')
247    libdir = sysconfig.get_config_var('LIBDIR')
248    supp_sources = []
249    defines = []
250    includes = ['-I' + incldir, '-I' + config_h_dir]
251
252    # sanity check of directories and files
253    check_dirs = [prefix, exec_prefix, configdir, incldir]
254    if not win:
255        # These are not directories on Windows.
256        check_dirs = check_dirs + extensions
257    for dir in check_dirs:
258        if not os.path.exists(dir):
259            usage('needed directory %s not found' % dir)
260        if not os.path.isdir(dir):
261            usage('%s: not a directory' % dir)
262    if win:
263        files = supp_sources + extensions # extensions are files on Windows.
264    else:
265        files = [config_c_in, makefile_in] + supp_sources
266    for file in supp_sources:
267        if not os.path.exists(file):
268            usage('needed file %s not found' % file)
269        if not os.path.isfile(file):
270            usage('%s: not a plain file' % file)
271    if not win:
272        for dir in extensions:
273            setup = os.path.join(dir, 'Setup')
274            if not os.path.exists(setup):
275                usage('needed file %s not found' % setup)
276            if not os.path.isfile(setup):
277                usage('%s: not a plain file' % setup)
278
279    # check that enough arguments are passed
280    if not args:
281        usage('at least one filename argument required')
282
283    # check that file arguments exist
284    for arg in args:
285        if arg == '-m':
286            break
287        # if user specified -m on the command line before _any_
288        # file names, then nothing should be checked (as the
289        # very first file should be a module name)
290        if modargs:
291            break
292        if not os.path.exists(arg):
293            usage('argument %s not found' % arg)
294        if not os.path.isfile(arg):
295            usage('%s: not a plain file' % arg)
296
297    # process non-option arguments
298    scriptfile = args[0]
299    modules = args[1:]
300
301    # derive target name from script name
302    base = os.path.basename(scriptfile)
303    base, ext = os.path.splitext(base)
304    if base:
305        if base != scriptfile:
306            target = base
307        else:
308            target = base + '.bin'
309
310    # handle -o option
311    base_frozen_c = frozen_c
312    base_config_c = config_c
313    base_target = target
314    if odir and not os.path.isdir(odir):
315        try:
316            os.mkdir(odir)
317            print("Created output directory", odir)
318        except OSError as msg:
319            usage('%s: mkdir failed (%s)' % (odir, str(msg)))
320    base = ''
321    if odir:
322        base = os.path.join(odir, '')
323        frozen_c = os.path.join(odir, frozen_c)
324        config_c = os.path.join(odir, config_c)
325        target = os.path.join(odir, target)
326        makefile = os.path.join(odir, makefile)
327        if win: extensions_c = os.path.join(odir, extensions_c)
328
329    # Handle special entry point requirements
330    # (on Windows, some frozen programs do not use __main__, but
331    # import the module directly.  Eg, DLLs, Services, etc
332    custom_entry_point = None  # Currently only used on Windows
333    python_entry_is_main = 1   # Is the entry point called __main__?
334    # handle -s option on Windows
335    if win:
336        import winmakemakefile
337        try:
338            custom_entry_point, python_entry_is_main = \
339                winmakemakefile.get_custom_entry_point(subsystem)
340        except ValueError as why:
341            usage(why)
342
343
344    # Actual work starts here...
345
346    # collect all modules of the program
347    dir = os.path.dirname(scriptfile)
348    path[0] = dir
349    mf = modulefinder.ModuleFinder(path, debug, exclude, replace_paths)
350
351    if win and subsystem=='service':
352        # If a Windows service, then add the "built-in" module.
353        mod = mf.add_module("servicemanager")
354        mod.__file__="dummy.pyd" # really built-in to the resulting EXE
355
356    for mod in implicits:
357        mf.import_hook(mod)
358    for mod in modules:
359        if mod == '-m':
360            modargs = 1
361            continue
362        if modargs:
363            if mod[-2:] == '.*':
364                mf.import_hook(mod[:-2], None, ["*"])
365            else:
366                mf.import_hook(mod)
367        else:
368            mf.load_file(mod)
369
370    # Alias "importlib._bootstrap" to "_frozen_importlib" so that the
371    # import machinery can bootstrap.  Do the same for
372    # importlib._bootstrap_external.
373    mf.modules["_frozen_importlib"] = mf.modules["importlib._bootstrap"]
374    mf.modules["_frozen_importlib_external"] = mf.modules["importlib._bootstrap_external"]
375
376    # Add the main script as either __main__, or the actual module name.
377    if python_entry_is_main:
378        mf.run_script(scriptfile)
379    else:
380        mf.load_file(scriptfile)
381
382    if debug > 0:
383        mf.report()
384        print()
385    dict = mf.modules
386
387    if error_if_any_missing:
388        missing = mf.any_missing()
389        if missing:
390            sys.exit("There are some missing modules: %r" % missing)
391
392    # generate output for frozen modules
393    files = makefreeze.makefreeze(base, dict, debug, custom_entry_point,
394                                  fail_import)
395
396    # look for unfrozen modules (builtin and of unknown origin)
397    builtins = []
398    unknown = []
399    mods = sorted(dict.keys())
400    for mod in mods:
401        if dict[mod].__code__:
402            continue
403        if not dict[mod].__file__:
404            builtins.append(mod)
405        else:
406            unknown.append(mod)
407
408    # search for unknown modules in extensions directories (not on Windows)
409    addfiles = []
410    frozen_extensions = [] # Windows list of modules.
411    if unknown or (not win and builtins):
412        if not win:
413            addfiles, addmods = \
414                      checkextensions.checkextensions(unknown+builtins,
415                                                      extensions)
416            for mod in addmods:
417                if mod in unknown:
418                    unknown.remove(mod)
419                    builtins.append(mod)
420        else:
421            # Do the windows thang...
422            import checkextensions_win32
423            # Get a list of CExtension instances, each describing a module
424            # (including its source files)
425            frozen_extensions = checkextensions_win32.checkextensions(
426                unknown, extensions, prefix)
427            for mod in frozen_extensions:
428                unknown.remove(mod.name)
429
430    # report unknown modules
431    if unknown:
432        sys.stderr.write('Warning: unknown modules remain: %s\n' %
433                         ' '.join(unknown))
434
435    # windows gets different treatment
436    if win:
437        # Taking a shortcut here...
438        import winmakemakefile, checkextensions_win32
439        checkextensions_win32.write_extension_table(extensions_c,
440                                                    frozen_extensions)
441        # Create a module definition for the bootstrap C code.
442        xtras = [frozenmain_c, os.path.basename(frozen_c),
443                 frozendllmain_c, os.path.basename(extensions_c)] + files
444        maindefn = checkextensions_win32.CExtension( '__main__', xtras )
445        frozen_extensions.append( maindefn )
446        with open(makefile, 'w') as outfp:
447            winmakemakefile.makemakefile(outfp,
448                                         locals(),
449                                         frozen_extensions,
450                                         os.path.basename(target))
451        return
452
453    # generate config.c and Makefile
454    builtins.sort()
455    with open(config_c_in) as infp, bkfile.open(config_c, 'w') as outfp:
456        makeconfig.makeconfig(infp, outfp, builtins)
457
458    cflags = ['$(OPT)']
459    cppflags = defines + includes
460    libs = [os.path.join(libdir, '$(LDLIBRARY)')]
461
462    somevars = {}
463    if os.path.exists(makefile_in):
464        makevars = parsesetup.getmakevars(makefile_in)
465        for key in makevars:
466            somevars[key] = makevars[key]
467
468    somevars['CFLAGS'] = ' '.join(cflags) # override
469    somevars['CPPFLAGS'] = ' '.join(cppflags) # override
470    files = [base_config_c, base_frozen_c] + \
471            files + supp_sources +  addfiles + libs + \
472            ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)']
473
474    with bkfile.open(makefile, 'w') as outfp:
475        makemakefile.makemakefile(outfp, somevars, files, base_target)
476
477    # Done!
478
479    if odir:
480        print('Now run "make" in', odir, end=' ')
481        print('to build the target:', base_target)
482    else:
483        print('Now run "make" to build the target:', base_target)
484
485
486# Print usage message and exit
487
488def usage(msg):
489    sys.stdout = sys.stderr
490    print("Error:", msg)
491    print("Use ``%s -h'' for help" % sys.argv[0])
492    sys.exit(2)
493
494
495main()
496