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    # Add the main script as either __main__, or the actual module name.
371    if python_entry_is_main:
372        mf.run_script(scriptfile)
373    else:
374        mf.load_file(scriptfile)
375
376    if debug > 0:
377        mf.report()
378        print()
379    dict = mf.modules
380
381    if error_if_any_missing:
382        missing = mf.any_missing()
383        if missing:
384            sys.exit("There are some missing modules: %r" % missing)
385
386    # generate output for frozen modules
387    files = makefreeze.makefreeze(base, dict, debug, custom_entry_point,
388                                  fail_import)
389
390    # look for unfrozen modules (builtin and of unknown origin)
391    builtins = []
392    unknown = []
393    mods = sorted(dict.keys())
394    for mod in mods:
395        if dict[mod].__code__:
396            continue
397        if not dict[mod].__file__:
398            builtins.append(mod)
399        else:
400            unknown.append(mod)
401
402    # search for unknown modules in extensions directories (not on Windows)
403    addfiles = []
404    frozen_extensions = [] # Windows list of modules.
405    if unknown or (not win and builtins):
406        if not win:
407            addfiles, addmods = \
408                      checkextensions.checkextensions(unknown+builtins,
409                                                      extensions)
410            for mod in addmods:
411                if mod in unknown:
412                    unknown.remove(mod)
413                    builtins.append(mod)
414        else:
415            # Do the windows thang...
416            import checkextensions_win32
417            # Get a list of CExtension instances, each describing a module
418            # (including its source files)
419            frozen_extensions = checkextensions_win32.checkextensions(
420                unknown, extensions, prefix)
421            for mod in frozen_extensions:
422                unknown.remove(mod.name)
423
424    # report unknown modules
425    if unknown:
426        sys.stderr.write('Warning: unknown modules remain: %s\n' %
427                         ' '.join(unknown))
428
429    # windows gets different treatment
430    if win:
431        # Taking a shortcut here...
432        import winmakemakefile, checkextensions_win32
433        checkextensions_win32.write_extension_table(extensions_c,
434                                                    frozen_extensions)
435        # Create a module definition for the bootstrap C code.
436        xtras = [frozenmain_c, os.path.basename(frozen_c),
437                 frozendllmain_c, os.path.basename(extensions_c)] + files
438        maindefn = checkextensions_win32.CExtension( '__main__', xtras )
439        frozen_extensions.append( maindefn )
440        with open(makefile, 'w') as outfp:
441            winmakemakefile.makemakefile(outfp,
442                                         locals(),
443                                         frozen_extensions,
444                                         os.path.basename(target))
445        return
446
447    # generate config.c and Makefile
448    builtins.sort()
449    with open(config_c_in) as infp, bkfile.open(config_c, 'w') as outfp:
450        makeconfig.makeconfig(infp, outfp, builtins)
451
452    cflags = ['$(OPT)']
453    cppflags = defines + includes
454    libs = [os.path.join(libdir, '$(LDLIBRARY)')]
455
456    somevars = {}
457    if os.path.exists(makefile_in):
458        makevars = parsesetup.getmakevars(makefile_in)
459        for key in makevars:
460            somevars[key] = makevars[key]
461
462    somevars['CFLAGS'] = ' '.join(cflags) # override
463    somevars['CPPFLAGS'] = ' '.join(cppflags) # override
464    files = [base_config_c, base_frozen_c] + \
465            files + supp_sources +  addfiles + libs + \
466            ['$(MODLIBS)', '$(LIBS)', '$(SYSLIBS)']
467
468    with bkfile.open(makefile, 'w') as outfp:
469        makemakefile.makemakefile(outfp, somevars, files, base_target)
470
471    # Done!
472
473    if odir:
474        print('Now run "make" in', odir, end=' ')
475        print('to build the target:', base_target)
476    else:
477        print('Now run "make" to build the target:', base_target)
478
479
480# Print usage message and exit
481
482def usage(msg):
483    sys.stdout = sys.stderr
484    print("Error:", msg)
485    print("Use ``%s -h'' for help" % sys.argv[0])
486    sys.exit(2)
487
488
489main()
490