Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/freeze/freeze.py
12 views
1
#! /usr/bin/env python3
2
3
"""Freeze a Python script into a binary.
4
5
usage: freeze [options...] script [module]...
6
7
Options:
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
71
Arguments:
72
73
script: The Python script to be executed by the resulting binary.
74
75
module ...: 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
80
NOTES:
81
82
In order to use freeze successfully, you must have built Python and
83
installed it ("make install").
84
85
The script should not use modules provided only as shared libraries;
86
if it does, the resulting binary is not self-contained.
87
"""
88
89
90
# Import standard modules
91
92
import modulefinder
93
import getopt
94
import os
95
import sys
96
import sysconfig
97
98
99
# Import the freeze-private modules
100
101
import checkextensions
102
import makeconfig
103
import makefreeze
104
import makemakefile
105
import parsesetup
106
import bkfile
107
108
109
# Main program
110
111
def 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
482
def 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
489
main()
490
491