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