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