Guido van Rossum | dbaf332 | 1994-10-03 10:25:54 +0000 | [diff] [blame^] | 1 | #! /usr/local/bin/python |
| 2 | |
| 3 | # Given a Python script, create a binary that runs the script. |
| 4 | # The binary is 100% independent of Python libraries and binaries. |
| 5 | # It will not contain any Python source code -- only "compiled" Python |
| 6 | # (as initialized static variables containing marshalled code objects). |
| 7 | # It even does the right thing for dynamically loaded modules! |
| 8 | # The module search path of the binary is set to the current directory. |
| 9 | # |
| 10 | # Some problems remain: |
| 11 | # - You need to have the Python source tree lying around as well as |
| 12 | # the various libraries used to generate the Python binary. |
| 13 | # - For scripts that use many modules it generates absurdly large |
| 14 | # files (frozen.c and config.o as well as the final binary), |
| 15 | # and is consequently rather slow. |
| 16 | # |
| 17 | # Caveats: |
| 18 | # - The search for modules sometimes finds modules that are never |
| 19 | # actually imported since the code importing them is never executed. |
| 20 | # - If an imported module isn't found, you get a warning but the |
| 21 | # process of freezing continues. The binary will fail if it |
| 22 | # actually tries to import one of these modules. |
| 23 | # - This often happens with the module 'mac', which module 'os' tries |
| 24 | # to import (to determine whether it is running on a Macintosh). |
| 25 | # You can ignore the warning about this. |
| 26 | # - If the program dynamically reads or generates Python code and |
| 27 | # executes it, this code may reference built-in or library modules |
| 28 | # that aren't present in the frozen binary, and this will fail. |
| 29 | # - Your program may be using external data files, e.g. compiled |
| 30 | # forms definitions (*.fd). These aren't incorporated. By default, |
| 31 | # the sys.path in the resulting binary is only '.' (but you can override |
| 32 | # that with the -P option). |
| 33 | # |
| 34 | # Usage hints: |
| 35 | # - If you have a bunch of scripts that you want to freeze, instead |
| 36 | # of freezing each of them separately, you might consider writing |
| 37 | # a tiny main script that looks at sys.argv[0] and then imports |
| 38 | # the corresponding module. You can then make links to the |
| 39 | # frozen binary named after the various scripts you support. |
| 40 | # Pass the additional scripts as arguments after the main script. |
| 41 | # A minimal script to do this is the following. |
| 42 | # import sys, posixpath |
| 43 | # exec('import ' + posixpath.basename(sys.argv[0]) + '\n') |
| 44 | # |
| 45 | # Mods by Jack, August 94: |
| 46 | # - Removed all static configuration stuff. Now, Setup and Makefile files |
| 47 | # are parsed to obtain the linking info for the libraries. You have to |
| 48 | # supply the -B option, though. |
| 49 | # - Added -P (set sys.path) and -I/-D/-L/-l options (passed on to cc and |
| 50 | # ld). |
| 51 | |
| 52 | import os |
| 53 | import sys |
| 54 | import regex |
| 55 | import getopt |
| 56 | import regsub |
| 57 | import string |
| 58 | import marshal |
| 59 | |
| 60 | # Exception used when scanfile fails |
| 61 | NoSuchFile = 'NoSuchFile' |
| 62 | |
| 63 | # Global options |
| 64 | builddir = '' # -B dir |
| 65 | quiet = 0 # -q |
| 66 | verbose = 0 # -v |
| 67 | noexec = 0 # -n |
| 68 | nowrite = 0 # -N |
| 69 | ofile = 'a.out' # -o file |
| 70 | path = '\'"."\'' # -P path |
| 71 | |
| 72 | cc_options = [] # Collects cc options |
| 73 | ld_options = [] # Collects ld options |
| 74 | module_libraries = {} # ld options for each module |
| 75 | global_libraries = [] # Libraries we always need |
| 76 | include_path = '' # Include path, from Makefile |
| 77 | lib_path = '' # and lib path, ditto |
| 78 | compiler = 'cc' # and compiler |
| 79 | |
| 80 | # Main program -- argument parsing etc. |
| 81 | def main(): |
| 82 | global quiet, verbose, noexec, nowrite, ofile, builddir, path |
| 83 | try: |
| 84 | opts, args = getopt.getopt(sys.argv[1:], 'B:nNo:P:qvI:D:L:l:') |
| 85 | except getopt.error, msg: |
| 86 | usage(str(msg)) |
| 87 | sys.exit(2) |
| 88 | for o, a in opts: |
| 89 | if o == '-B': builddir = a |
| 90 | if o == '-n': noexec = 1 |
| 91 | if o == '-N': nowrite = 1 |
| 92 | if o == '-o': ofile = a |
| 93 | if o == '-P': |
| 94 | if '"' in a: |
| 95 | usage('sorry, cannot have " in -P option') |
| 96 | sys.exit(2) |
| 97 | path = `'"' + a + '"'` |
| 98 | if o == '-q': verbose = 0; quiet = 1 |
| 99 | if o == '-v': verbose = verbose + 1; quiet = 0 |
| 100 | if o in ('-I', '-D'): cc_options.append(o+a) |
| 101 | if o in ('-L', '-l'): ld_options.append(o+a) |
| 102 | if not builddir: |
| 103 | usage('sorry, you have to pass a -B option') |
| 104 | sys.exit(2) |
| 105 | if len(args) < 1: |
| 106 | usage('please pass at least one file argument') |
| 107 | sys.exit(2) |
| 108 | process(args[0], args[1:]) |
| 109 | |
| 110 | # Print usage message to stderr |
| 111 | def usage(*msgs): |
| 112 | sys.stdout = sys.stderr |
| 113 | for msg in msgs: print msg |
| 114 | print 'Usage: freeze [options] scriptfile [modulefile ...]' |
| 115 | print '-B dir : name of python build dir (no default)' |
| 116 | print '-n : generate the files but don\'t compile and link' |
| 117 | print '-N : don\'t write frozen.c (do compile unless -n given)' |
| 118 | print '-o file : binary output file (default a.out)' |
| 119 | print '-P path : set sys.path for program (default ".")' |
| 120 | print '-q : quiet (no messages at all except errors)' |
| 121 | print '-v : verbose (lots of extra messages)' |
| 122 | print '-D and -I options are passed to cc, -L and -l to ld' |
| 123 | |
| 124 | # Process the script file |
| 125 | def process(filename, addmodules): |
| 126 | global noexec |
| 127 | # |
| 128 | if not quiet: print 'Computing needed modules ...' |
| 129 | todo = {} |
| 130 | todo['__main__'] = filename |
| 131 | for name in addmodules: |
| 132 | mod = os.path.basename(name) |
| 133 | if mod[-3:] == '.py': mod = mod[:-3] |
| 134 | todo[mod] = name |
| 135 | try: |
| 136 | dict = closure(todo) |
| 137 | except NoSuchFile, filename: |
| 138 | sys.stderr.write('Can\'t open file %s\n' % filename) |
| 139 | sys.exit(1) |
| 140 | # |
| 141 | mods = dict.keys() |
| 142 | mods.sort() |
| 143 | # |
| 144 | if verbose: |
| 145 | print '%-15s %s' % ('Module', 'Filename') |
| 146 | for mod in mods: |
| 147 | print '%-15s %s' % (`mod`, dict[mod]) |
| 148 | # |
| 149 | if not quiet: print 'Looking for dynamically linked modules ...' |
| 150 | dlmodules = [] |
| 151 | objs = [] |
| 152 | libs = [] |
| 153 | for mod in mods: |
| 154 | if dict[mod][-2:] == '.o': |
| 155 | if verbose: print 'Found', mod, dict[mod] |
| 156 | dlmodules.append(mod) |
| 157 | objs.append(dict[mod]) |
| 158 | libsname = dict[mod][:-2] + '.libs' |
| 159 | try: |
| 160 | f = open(libsname, 'r') |
| 161 | except IOError: |
| 162 | f = None |
| 163 | if f: |
| 164 | libtext = f.read() |
| 165 | f.close() |
| 166 | for lib in string.split(libtext): |
| 167 | if lib in libs: libs.remove(lib) |
| 168 | libs.append(lib) |
| 169 | # |
| 170 | if not nowrite: |
| 171 | if not quiet: print 'Writing frozen.c ...' |
| 172 | writefrozen('frozen.c', dict) |
| 173 | else: |
| 174 | if not quiet: print 'NOT writing frozen.c ...' |
| 175 | # |
| 176 | if not quiet: |
| 177 | print 'Deducing compile/link options from', builddir |
| 178 | # |
| 179 | # Parse the config info |
| 180 | # |
| 181 | parse(builddir) |
| 182 | CONFIG_IN = lib_path + '/config.c.in' |
| 183 | FMAIN = lib_path + '/frozenmain.c' |
| 184 | CC = compiler |
| 185 | # |
| 186 | ## if not dlmodules: |
| 187 | if 0: |
| 188 | config = CONFIG |
| 189 | if not quiet: print 'Using existing', config, '...' |
| 190 | else: |
| 191 | config = 'tmpconfig.c' |
| 192 | if nowrite: |
| 193 | if not quiet: print 'NOT writing config.c ...' |
| 194 | else: |
| 195 | if not quiet: |
| 196 | print 'Writing config.c with dl modules ...' |
| 197 | f = open(CONFIG_IN, 'r') |
| 198 | g = open(config, 'w') |
| 199 | m1 = regex.compile('-- ADDMODULE MARKER 1 --') |
| 200 | m2 = regex.compile('-- ADDMODULE MARKER 2 --') |
| 201 | builtinmodules = [] |
| 202 | stdmodules = ('sys', '__main__', '__builtin__', |
| 203 | 'marshal') |
| 204 | for mod in dict.keys(): |
| 205 | if dict[mod] == '<builtin>' and \ |
| 206 | mod not in stdmodules: |
| 207 | builtinmodules.append(mod) |
| 208 | todomodules = builtinmodules + dlmodules |
| 209 | while 1: |
| 210 | line = f.readline() |
| 211 | if not line: break |
| 212 | g.write(line) |
| 213 | if m1.search(line) >= 0: |
| 214 | if verbose: print 'Marker 1 ...' |
| 215 | for mod in todomodules: |
| 216 | g.write('extern void init' + \ |
| 217 | mod + '();\n') |
| 218 | if m2.search(line) >= 0: |
| 219 | if verbose: print 'Marker 2 ...' |
| 220 | for mod in todomodules: |
| 221 | g.write('{"' + mod + \ |
| 222 | '", init' + mod + '},\n') |
| 223 | g.close() |
| 224 | # |
| 225 | if not quiet: |
| 226 | if noexec: print 'Generating compilation commands ...' |
| 227 | else: print 'Starting compilation ...' |
| 228 | defs = ['-DNO_MAIN', '-DUSE_FROZEN'] |
| 229 | defs.append('-DPYTHONPATH='+path) |
| 230 | # |
| 231 | incs = ['-I.', '-I' + include_path] |
| 232 | # if dict.has_key('stdwin'): |
| 233 | # incs.append('-I' + j(STDWIN, 'H')) |
| 234 | # |
| 235 | srcs = [config, FMAIN] |
| 236 | # |
| 237 | modlibs = module_libraries |
| 238 | |
| 239 | for mod in dict.keys(): |
| 240 | if modlibs.has_key(mod): |
| 241 | libs = libs + modlibs[mod] |
| 242 | |
| 243 | libs = libs + global_libraries |
| 244 | # |
| 245 | # remove dups: |
| 246 | # XXXX Not well tested... |
| 247 | nskip = 0 |
| 248 | newlibs = [] |
| 249 | while libs: |
| 250 | l = libs[0] |
| 251 | del libs[0] |
| 252 | if l[:2] == '-L' and l in newlibs: |
| 253 | nskip = nskip + 1 |
| 254 | continue |
| 255 | if (l[:2] == '-l' or l[-2:] == '.a') and l in libs: |
| 256 | nskip = nskip + 1 |
| 257 | continue |
| 258 | newlibs.append(l) |
| 259 | libs = newlibs |
| 260 | if nskip and not quiet: |
| 261 | print 'Removed %d duplicate libraries'%nskip |
| 262 | # |
| 263 | sts = 0 |
| 264 | # |
| 265 | cmd = CC + ' -c' |
| 266 | if cc_options: |
| 267 | cmd = cmd + ' ' + string.join(cc_options) |
| 268 | cmd = cmd + ' ' + string.join(defs) |
| 269 | cmd = cmd + ' ' + string.join(incs) |
| 270 | cmd = cmd + ' ' + string.join(srcs) |
| 271 | print cmd |
| 272 | # |
| 273 | if not noexec: |
| 274 | sts = os.system(cmd) |
| 275 | if sts: |
| 276 | print 'Exit status', sts, '-- turning on -n' |
| 277 | noexec = 1 |
| 278 | # |
| 279 | for s in srcs: |
| 280 | s = os.path.basename(s) |
| 281 | if s[-2:] == '.c': s = s[:-2] |
| 282 | o = s + '.o' |
| 283 | objs.insert(0, o) |
| 284 | # |
| 285 | cmd = CC |
| 286 | cmd = cmd + ' ' + string.join(objs) |
| 287 | cmd = cmd + ' ' + string.join(libs) |
| 288 | if ld_options: |
| 289 | cmd = cmd + ' ' + string.join(ld_options) |
| 290 | cmd = cmd + ' -o ' + ofile |
| 291 | print cmd |
| 292 | # |
| 293 | if not noexec: |
| 294 | sts = os.system(cmd) |
| 295 | if sts: |
| 296 | print 'Exit status', sts |
| 297 | else: |
| 298 | print 'Done.' |
| 299 | # |
| 300 | if not quiet and not noexec and sts == 0: |
| 301 | print 'Note: consider this:'; print '\tstrip', ofile |
| 302 | # |
| 303 | sys.exit(sts) |
| 304 | |
| 305 | |
| 306 | # Generate code for a given module |
| 307 | def makecode(filename): |
| 308 | if filename[-2:] == '.o': |
| 309 | return None |
| 310 | try: |
| 311 | f = open(filename, 'r') |
| 312 | except IOError: |
| 313 | return None |
| 314 | if verbose: print 'Making code from', filename, '...' |
| 315 | text = f.read() |
| 316 | code = compile(text, filename, 'exec') |
| 317 | f.close() |
| 318 | return marshal.dumps(code) |
| 319 | |
| 320 | |
| 321 | # Write the C source file containing the frozen Python code |
| 322 | def writefrozen(filename, dict): |
| 323 | f = open(filename, 'w') |
| 324 | codelist = [] |
| 325 | for mod in dict.keys(): |
| 326 | codestring = makecode(dict[mod]) |
| 327 | if codestring is not None: |
| 328 | codelist.append((mod, codestring)) |
| 329 | write = sys.stdout.write |
| 330 | save_stdout = sys.stdout |
| 331 | try: |
| 332 | sys.stdout = f |
| 333 | for mod, codestring in codelist: |
| 334 | if verbose: |
| 335 | write('Writing initializer for %s\n'%mod) |
| 336 | print 'static unsigned char M_' + mod + '[' + \ |
| 337 | str(len(codestring)) + '+1] = {' |
| 338 | for i in range(0, len(codestring), 16): |
| 339 | for c in codestring[i:i+16]: |
| 340 | print str(ord(c)) + ',', |
| 341 | print |
| 342 | print '};' |
| 343 | print 'struct frozen {' |
| 344 | print ' char *name;' |
| 345 | print ' unsigned char *code;' |
| 346 | print ' int size;' |
| 347 | print '} frozen_modules[] = {' |
| 348 | for mod, codestring in codelist: |
| 349 | print ' {"' + mod + '",', |
| 350 | print 'M_' + mod + ',', |
| 351 | print str(len(codestring)) + '},' |
| 352 | print ' {0, 0, 0} /* sentinel */' |
| 353 | print '};' |
| 354 | finally: |
| 355 | sys.stdout = save_stdout |
| 356 | f.close() |
| 357 | |
| 358 | |
| 359 | # Determine the names and filenames of the modules imported by the |
| 360 | # script, recursively. This is done by scanning for lines containing |
| 361 | # import statements. (The scanning has only superficial knowledge of |
| 362 | # Python syntax and no knowledge of semantics, so in theory the result |
| 363 | # may be incorrect -- however this is quite unlikely if you don't |
| 364 | # intentionally obscure your Python code.) |
| 365 | |
| 366 | # Compute the closure of scanfile() -- special first file because of script |
| 367 | def closure(todo): |
| 368 | done = {} |
| 369 | while todo: |
| 370 | newtodo = {} |
| 371 | for modname in todo.keys(): |
| 372 | if not done.has_key(modname): |
| 373 | filename = todo[modname] |
| 374 | if filename is None: |
| 375 | filename = findmodule(modname) |
| 376 | done[modname] = filename |
| 377 | if filename in ('<builtin>', '<unknown>'): |
| 378 | continue |
| 379 | modules = scanfile(filename) |
| 380 | for m in modules: |
| 381 | if not done.has_key(m): |
| 382 | newtodo[m] = None |
| 383 | todo = newtodo |
| 384 | return done |
| 385 | |
| 386 | # Scan a file looking for import statements |
| 387 | importstr = '\(^\|:\)[ \t]*import[ \t]+\([a-zA-Z0-9_, \t]+\)' |
| 388 | fromstr = '\(^\|:\)[ \t]*from[ \t]+\([a-zA-Z0-9_]+\)[ \t]+import[ \t]+' |
| 389 | isimport = regex.compile(importstr) |
| 390 | isfrom = regex.compile(fromstr) |
| 391 | def scanfile(filename): |
| 392 | allmodules = {} |
| 393 | try: |
| 394 | f = open(filename, 'r') |
| 395 | except IOError, msg: |
| 396 | raise NoSuchFile, filename |
| 397 | while 1: |
| 398 | line = f.readline() |
| 399 | if not line: break # EOF |
| 400 | while line[-2:] == '\\\n': # Continuation line |
| 401 | line = line[:-2] + ' ' |
| 402 | line = line + f.readline() |
| 403 | if isimport.search(line) >= 0: |
| 404 | rawmodules = isimport.group(2) |
| 405 | modules = string.splitfields(rawmodules, ',') |
| 406 | for i in range(len(modules)): |
| 407 | modules[i] = string.strip(modules[i]) |
| 408 | elif isfrom.search(line) >= 0: |
| 409 | modules = [isfrom.group(2)] |
| 410 | else: |
| 411 | continue |
| 412 | for mod in modules: |
| 413 | allmodules[mod] = None |
| 414 | f.close() |
| 415 | return allmodules.keys() |
| 416 | |
| 417 | # Find the file containing a module, given its name; None if not found |
| 418 | builtins = sys.builtin_module_names + ['sys'] |
| 419 | def findmodule(modname): |
| 420 | if modname in builtins: return '<builtin>' |
| 421 | for dirname in sys.path: |
| 422 | dlfullname = os.path.join(dirname, modname + 'module.o') |
| 423 | try: |
| 424 | f = open(dlfullname, 'r') |
| 425 | except IOError: |
| 426 | f = None |
| 427 | if f: |
| 428 | f.close() |
| 429 | return dlfullname |
| 430 | fullname = os.path.join(dirname, modname + '.py') |
| 431 | try: |
| 432 | f = open(fullname, 'r') |
| 433 | except IOError: |
| 434 | continue |
| 435 | f.close() |
| 436 | return fullname |
| 437 | if not quiet: |
| 438 | sys.stderr.write('Warning: module %s not found\n' % modname) |
| 439 | return '<unknown>' |
| 440 | # |
| 441 | # Parse a setup file. Returns two dictionaries, one containing variables |
| 442 | # defined with their values and one containing module definitions |
| 443 | # |
| 444 | def parse_setup(fp): |
| 445 | modules = {} |
| 446 | variables = {} |
| 447 | for line in fp.readlines(): |
| 448 | if '#' in line: # Strip comments |
| 449 | line = string.splitfields(line, '#')[0] |
| 450 | line = string.strip(line[:-1]) # Strip whitespace |
| 451 | if not line: |
| 452 | continue |
| 453 | words = string.split(line) |
| 454 | if '=' in words[0]: |
| 455 | # |
| 456 | # equal sign before first space. Definition |
| 457 | # |
| 458 | pos = string.index(line, '=') |
| 459 | name = line[:pos] |
| 460 | value = string.strip(line[pos+1:]) |
| 461 | variables[name] = value |
| 462 | else: |
| 463 | modules[words[0]] = words[1:] |
| 464 | return modules, variables |
| 465 | # |
| 466 | # Parse a makefile. Returns a list of the variables defined. |
| 467 | # |
| 468 | def parse_makefile(fp): |
| 469 | variables = {} |
| 470 | for line in fp.readlines(): |
| 471 | if '#' in line: # Strip comments |
| 472 | line = string.splitfields(line, '#')[0] |
| 473 | if not line: |
| 474 | continue |
| 475 | if line[0] in string.whitespace: |
| 476 | continue |
| 477 | line = string.strip(line[:-1]) # Strip whitespace |
| 478 | if not line: |
| 479 | continue |
| 480 | if '=' in string.splitfields(line, ':')[0]: |
| 481 | # |
| 482 | # equal sign before first colon. Definition |
| 483 | # |
| 484 | pos = string.index(line, '=') |
| 485 | name = line[:pos] |
| 486 | value = string.strip(line[pos+1:]) |
| 487 | variables[name] = value |
| 488 | return variables |
| 489 | |
| 490 | # |
| 491 | # Recursively add loader options from Setup files in extension |
| 492 | # directories. |
| 493 | # |
| 494 | def add_extension_directory(name, isinstalldir): |
| 495 | if verbose: |
| 496 | print 'Adding extension directory', name |
| 497 | fp = open(name + '/Setup', 'r') |
| 498 | modules, variables = parse_setup(fp) |
| 499 | # |
| 500 | # Locate all new modules and remember the ld flags needed for them |
| 501 | # |
| 502 | for m in modules.keys(): |
| 503 | if module_libraries.has_key(m): |
| 504 | continue |
| 505 | options = modules[m] |
| 506 | if isinstalldir: |
| 507 | ld_options = [] |
| 508 | else: |
| 509 | ld_options = [name + '/lib.a'] |
| 510 | for o in options: |
| 511 | # ld options are all capital except DUIC and l |
| 512 | if o[:-2] == '.a': |
| 513 | ld_options.append(o) |
| 514 | elif o[0] == '-': |
| 515 | if o[1] == 'l': |
| 516 | ld_options.append(o) |
| 517 | elif o[1] in string.uppercase and not o[1] in 'DUIC': |
| 518 | ld_options.append(o) |
| 519 | module_libraries[m] = ld_options |
| 520 | # |
| 521 | # See if we have to bother with base setups |
| 522 | # |
| 523 | if variables.has_key('BASESETUP'): |
| 524 | if isinstalldir: |
| 525 | raise 'installdir has base setup' |
| 526 | setupfiles = string.split(variables['BASESETUP']) |
| 527 | for s in setupfiles: |
| 528 | if s[-6:] <> '/Setup': |
| 529 | raise 'Incorrect BASESETUP', s |
| 530 | s = s[:-6] |
| 531 | if s[0] <> '/': |
| 532 | s = name + '/' + s |
| 533 | s = os.path.normpath(s) |
| 534 | add_extension_directory(s, 0) |
| 535 | # |
| 536 | # Main routine for this module: given a build directory, get all |
| 537 | # information needed for the linker. |
| 538 | # |
| 539 | def parse(dir): |
| 540 | global include_path |
| 541 | global lib_path |
| 542 | global compiler |
| 543 | |
| 544 | fp = open(dir + '/Makefile', 'r') |
| 545 | # |
| 546 | # First find the global libraries and the base python |
| 547 | # |
| 548 | vars = parse_makefile(fp) |
| 549 | if vars.has_key('CC'): |
| 550 | compiler = vars['CC'] |
| 551 | if not vars.has_key('installdir'): |
| 552 | raise 'No $installdir in Makefile' |
| 553 | include_path = vars['installdir'] + '/include/Py' |
| 554 | lib_path = vars['installdir'] + '/lib/python/lib' |
| 555 | global_libraries.append('-L' + lib_path) |
| 556 | global_libraries.append('-lPython') |
| 557 | global_libraries.append('-lParser') |
| 558 | global_libraries.append('-lObjects') |
| 559 | global_libraries.append('-lModules') |
| 560 | for name in ('LIBS', 'LIBM', 'LIBC'): |
| 561 | if not vars.has_key(name): |
| 562 | raise 'Missing required def in Makefile', name |
| 563 | for lib in string.split(vars[name]): |
| 564 | global_libraries.append(lib) |
| 565 | # |
| 566 | # Next, parse the modules from the base python |
| 567 | # |
| 568 | add_extension_directory(lib_path, 1) |
| 569 | # |
| 570 | # Finally, parse the modules from the extension python |
| 571 | # |
| 572 | if dir <> lib_path: |
| 573 | add_extension_directory(dir, 0) |
| 574 | |
| 575 | # Call the main program |
| 576 | main() |