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