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()