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 # 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 482def 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 489main() 490