• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    if sys.platform == "darwin" and sysconfig.get_config_var("PYTHONFRAMEWORK"):
140        print(f"{sys.argv[0]} cannot be used with framework builds of Python", file=sys.stderr)
141        sys.exit(1)
142
143
144    # parse command line by first replacing any "-i" options with the
145    # file contents.
146    pos = 1
147    while pos < len(sys.argv)-1:
148        # last option can not be "-i", so this ensures "pos+1" is in range!
149        if sys.argv[pos] == '-i':
150            try:
151                with open(sys.argv[pos+1]) as infp:
152                    options = infp.read().split()
153            except IOError as why:
154                usage("File name '%s' specified with the -i option "
155                      "can not be read - %s" % (sys.argv[pos+1], why) )
156            # Replace the '-i' and the filename with the read params.
157            sys.argv[pos:pos+2] = options
158            pos = pos + len(options) - 1 # Skip the name and the included args.
159        pos = pos + 1
160
161    # Now parse the command line with the extras inserted.
162    try:
163        opts, args = getopt.getopt(sys.argv[1:], 'r:a:dEe:hmo:p:P:qs:wX:x:l:')
164    except getopt.error as msg:
165        usage('getopt error: ' + str(msg))
166
167    # process option arguments
168    for o, a in opts:
169        if o == '-h':
170            print(__doc__)
171            return
172        if o == '-d':
173            debug = debug + 1
174        if o == '-e':
175            extensions.append(a)
176        if o == '-m':
177            modargs = 1
178        if o == '-o':
179            odir = a
180        if o == '-p':
181            prefix = a
182        if o == '-P':
183            exec_prefix = a
184        if o == '-q':
185            debug = 0
186        if o == '-w':
187            win = not win
188        if o == '-s':
189            if not win:
190                usage("-s subsystem option only on Windows")
191            subsystem = a
192        if o == '-x':
193            exclude.append(a)
194        if o == '-X':
195            exclude.append(a)
196            fail_import.append(a)
197        if o == '-E':
198            error_if_any_missing = 1
199        if o == '-l':
200            addn_link.append(a)
201        if o == '-a':
202            modulefinder.AddPackagePath(*a.split("=", 2))
203        if o == '-r':
204            f,r = a.split("=", 2)
205            replace_paths.append( (f,r) )
206
207    # modules that are imported by the Python runtime
208    implicits = []
209    for module in ('site', 'warnings', 'encodings.utf_8', 'encodings.latin_1'):
210        if module not in exclude:
211            implicits.append(module)
212
213    # default prefix and exec_prefix
214    if not exec_prefix:
215        if prefix:
216            exec_prefix = prefix
217        else:
218            exec_prefix = sys.exec_prefix
219    if not prefix:
220        prefix = sys.prefix
221
222    # determine whether -p points to the Python source tree
223    ishome = os.path.exists(os.path.join(prefix, 'Python', 'ceval.c'))
224
225    # locations derived from options
226    version = '%d.%d' % sys.version_info[:2]
227    if hasattr(sys, 'abiflags'):
228        flagged_version = version + sys.abiflags
229    else:
230        flagged_version = version
231    if win:
232        extensions_c = 'frozen_extensions.c'
233    if ishome:
234        print("(Using Python source directory)")
235        configdir = exec_prefix
236        incldir = os.path.join(prefix, 'Include')
237        config_h_dir = exec_prefix
238        config_c_in = os.path.join(prefix, 'Modules', 'config.c.in')
239        frozenmain_c = os.path.join(prefix, 'Python', 'frozenmain.c')
240        makefile_in = os.path.join(exec_prefix, 'Makefile')
241        if win:
242            frozendllmain_c = os.path.join(exec_prefix, 'Pc\\frozen_dllmain.c')
243    else:
244        configdir = sysconfig.get_config_var('LIBPL')
245        incldir = os.path.join(prefix, 'include', 'python%s' % flagged_version)
246        config_h_dir = os.path.join(exec_prefix, 'include',
247                                    'python%s' % flagged_version)
248        config_c_in = os.path.join(configdir, 'config.c.in')
249        frozenmain_c = os.path.join(configdir, 'frozenmain.c')
250        makefile_in = os.path.join(configdir, 'Makefile')
251        frozendllmain_c = os.path.join(configdir, 'frozen_dllmain.c')
252    libdir = sysconfig.get_config_var('LIBDIR')
253    supp_sources = []
254    defines = []
255    includes = ['-I' + incldir, '-I' + config_h_dir]
256
257    # sanity check of directories and files
258    check_dirs = [prefix, exec_prefix, configdir, incldir]
259    if not win:
260        # These are not directories on Windows.
261        check_dirs = check_dirs + extensions
262    for dir in check_dirs:
263        if not os.path.exists(dir):
264            usage('needed directory %s not found' % dir)
265        if not os.path.isdir(dir):
266            usage('%s: not a directory' % dir)
267    if win:
268        files = supp_sources + extensions # extensions are files on Windows.
269    else:
270        files = [config_c_in, makefile_in] + supp_sources
271    for file in supp_sources:
272        if not os.path.exists(file):
273            usage('needed file %s not found' % file)
274        if not os.path.isfile(file):
275            usage('%s: not a plain file' % file)
276    if not win:
277        for dir in extensions:
278            setup = os.path.join(dir, 'Setup')
279            if not os.path.exists(setup):
280                usage('needed file %s not found' % setup)
281            if not os.path.isfile(setup):
282                usage('%s: not a plain file' % setup)
283
284    # check that enough arguments are passed
285    if not args:
286        usage('at least one filename argument required')
287
288    # check that file arguments exist
289    for arg in args:
290        if arg == '-m':
291            break
292        # if user specified -m on the command line before _any_
293        # file names, then nothing should be checked (as the
294        # very first file should be a module name)
295        if modargs:
296            break
297        if not os.path.exists(arg):
298            usage('argument %s not found' % arg)
299        if not os.path.isfile(arg):
300            usage('%s: not a plain file' % arg)
301
302    # process non-option arguments
303    scriptfile = args[0]
304    modules = args[1:]
305
306    # derive target name from script name
307    base = os.path.basename(scriptfile)
308    base, ext = os.path.splitext(base)
309    if base:
310        if base != scriptfile:
311            target = base
312        else:
313            target = base + '.bin'
314
315    # handle -o option
316    base_frozen_c = frozen_c
317    base_config_c = config_c
318    base_target = target
319    if odir and not os.path.isdir(odir):
320        try:
321            os.mkdir(odir)
322            print("Created output directory", odir)
323        except OSError as msg:
324            usage('%s: mkdir failed (%s)' % (odir, str(msg)))
325    base = ''
326    if odir:
327        base = os.path.join(odir, '')
328        frozen_c = os.path.join(odir, frozen_c)
329        config_c = os.path.join(odir, config_c)
330        target = os.path.join(odir, target)
331        makefile = os.path.join(odir, makefile)
332        if win: extensions_c = os.path.join(odir, extensions_c)
333
334    # Handle special entry point requirements
335    # (on Windows, some frozen programs do not use __main__, but
336    # import the module directly.  Eg, DLLs, Services, etc
337    custom_entry_point = None  # Currently only used on Windows
338    python_entry_is_main = 1   # Is the entry point called __main__?
339    # handle -s option on Windows
340    if win:
341        import winmakemakefile
342        try:
343            custom_entry_point, python_entry_is_main = \
344                winmakemakefile.get_custom_entry_point(subsystem)
345        except ValueError as why:
346            usage(why)
347
348
349    # Actual work starts here...
350
351    # collect all modules of the program
352    dir = os.path.dirname(scriptfile)
353    path[0] = dir
354    mf = modulefinder.ModuleFinder(path, debug, exclude, replace_paths)
355
356    if win and subsystem=='service':
357        # If a Windows service, then add the "built-in" module.
358        mod = mf.add_module("servicemanager")
359        mod.__file__="dummy.pyd" # really built-in to the resulting EXE
360
361    for mod in implicits:
362        mf.import_hook(mod)
363    for mod in modules:
364        if mod == '-m':
365            modargs = 1
366            continue
367        if modargs:
368            if mod[-2:] == '.*':
369                mf.import_hook(mod[:-2], None, ["*"])
370            else:
371                mf.import_hook(mod)
372        else:
373            mf.load_file(mod)
374
375    # Add the main script as either __main__, or the actual module name.
376    if python_entry_is_main:
377        mf.run_script(scriptfile)
378    else:
379        mf.load_file(scriptfile)
380
381    if debug > 0:
382        mf.report()
383        print()
384    dict = mf.modules
385
386    if error_if_any_missing:
387        missing = mf.any_missing()
388        if missing:
389            sys.exit("There are some missing modules: %r" % missing)
390
391    # generate output for frozen modules
392    files = makefreeze.makefreeze(base, dict, debug, custom_entry_point,
393                                  fail_import)
394
395    # look for unfrozen modules (builtin and of unknown origin)
396    builtins = []
397    unknown = []
398    mods = sorted(dict.keys())
399    for mod in mods:
400        if dict[mod].__code__:
401            continue
402        if not dict[mod].__file__:
403            builtins.append(mod)
404        else:
405            unknown.append(mod)
406
407    # search for unknown modules in extensions directories (not on Windows)
408    addfiles = []
409    frozen_extensions = [] # Windows list of modules.
410    if unknown or (not win and builtins):
411        if not win:
412            addfiles, addmods = \
413                      checkextensions.checkextensions(unknown+builtins,
414                                                      extensions)
415            for mod in addmods:
416                if mod in unknown:
417                    unknown.remove(mod)
418                    builtins.append(mod)
419        else:
420            # Do the windows thang...
421            import checkextensions_win32
422            # Get a list of CExtension instances, each describing a module
423            # (including its source files)
424            frozen_extensions = checkextensions_win32.checkextensions(
425                unknown, extensions, prefix)
426            for mod in frozen_extensions:
427                unknown.remove(mod.name)
428
429    # report unknown modules
430    if unknown:
431        sys.stderr.write('Warning: unknown modules remain: %s\n' %
432                         ' '.join(unknown))
433
434    # windows gets different treatment
435    if win:
436        # Taking a shortcut here...
437        import winmakemakefile, checkextensions_win32
438        checkextensions_win32.write_extension_table(extensions_c,
439                                                    frozen_extensions)
440        # Create a module definition for the bootstrap C code.
441        xtras = [frozenmain_c, os.path.basename(frozen_c),
442                 frozendllmain_c, os.path.basename(extensions_c)] + files
443        maindefn = checkextensions_win32.CExtension( '__main__', xtras )
444        frozen_extensions.append( maindefn )
445        with open(makefile, 'w') as outfp:
446            winmakemakefile.makemakefile(outfp,
447                                         locals(),
448                                         frozen_extensions,
449                                         os.path.basename(target))
450        return
451
452    # generate config.c and Makefile
453    builtins.sort()
454    with open(config_c_in) as infp, bkfile.open(config_c, 'w') as outfp:
455        makeconfig.makeconfig(infp, outfp, builtins)
456
457    cflags = ['$(OPT)']
458    cppflags = defines + includes
459    libs = [os.path.join(libdir, '$(LDLIBRARY)')]
460
461    somevars = {}
462    if os.path.exists(makefile_in):
463        makevars = parsesetup.getmakevars(makefile_in)
464        for key in makevars:
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    with bkfile.open(makefile, 'w') as outfp:
474        makemakefile.makemakefile(outfp, somevars, files, base_target)
475
476    # Done!
477
478    if odir:
479        print('Now run "make" in', odir, end=' ')
480        print('to build the target:', base_target)
481    else:
482        print('Now run "make" to build the target:', base_target)
483
484
485# Print usage message and exit
486
487def usage(msg):
488    sys.stdout = sys.stderr
489    print("Error:", msg)
490    print("Use ``%s -h'' for help" % sys.argv[0])
491    sys.exit(2)
492
493
494main()
495