Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 1 | # encoding: utf-8 |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 2 | import atexit |
| 3 | import zipfile |
| 4 | |
Tor Norbye | 9258464 | 2014-04-17 08:39:25 -0700 | [diff] [blame] | 5 | # TODO: Move all CLR-specific functions to clr_tools |
| 6 | |
Tor Norbye | 809cb3e | 2014-01-27 09:36:41 -0800 | [diff] [blame] | 7 | from pycharm_generator_utils.module_redeclarator import * |
| 8 | from pycharm_generator_utils.util_methods import * |
| 9 | from pycharm_generator_utils.constants import * |
Tor Norbye | 9258464 | 2014-04-17 08:39:25 -0700 | [diff] [blame] | 10 | from pycharm_generator_utils.clr_tools import * |
Tor Norbye | 809cb3e | 2014-01-27 09:36:41 -0800 | [diff] [blame] | 11 | |
| 12 | |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 13 | debug_mode = False |
| 14 | |
| 15 | |
Tor Norbye | 8668e1b | 2013-12-20 09:14:04 -0800 | [diff] [blame] | 16 | def redo_module(module_name, outfile, module_file_name, doing_builtins): |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 17 | # gobject does 'del _gobject' in its __init__.py, so the chained attribute lookup code |
| 18 | # fails to find 'gobject._gobject'. thus we need to pull the module directly out of |
| 19 | # sys.modules |
Tor Norbye | 8668e1b | 2013-12-20 09:14:04 -0800 | [diff] [blame] | 20 | mod = sys.modules.get(module_name) |
| 21 | mod_path = module_name.split('.') |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 22 | if not mod and sys.platform == 'cli': |
| 23 | # "import System.Collections" in IronPython 2.7 doesn't actually put System.Collections in sys.modules |
| 24 | # instead, sys.modules['System'] get set to a Microsoft.Scripting.Actions.NamespaceTracker and Collections can be |
| 25 | # accessed as its attribute |
| 26 | mod = sys.modules[mod_path[0]] |
| 27 | for component in mod_path[1:]: |
| 28 | try: |
| 29 | mod = getattr(mod, component) |
| 30 | except AttributeError: |
| 31 | mod = None |
Tor Norbye | 8668e1b | 2013-12-20 09:14:04 -0800 | [diff] [blame] | 32 | report("Failed to find CLR module " + module_name) |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 33 | break |
| 34 | if mod: |
| 35 | action("restoring") |
| 36 | r = ModuleRedeclarator(mod, outfile, module_file_name, doing_builtins=doing_builtins) |
Tor Norbye | 8668e1b | 2013-12-20 09:14:04 -0800 | [diff] [blame] | 37 | r.redo(module_name, ".".join(mod_path[:-1]) in MODULES_INSPECT_DIR) |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 38 | action("flushing") |
| 39 | r.flush() |
| 40 | else: |
Tor Norbye | 8668e1b | 2013-12-20 09:14:04 -0800 | [diff] [blame] | 41 | report("Failed to find imported module in sys.modules " + module_name) |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 42 | |
| 43 | # find_binaries functionality |
| 44 | def cut_binary_lib_suffix(path, f): |
| 45 | """ |
| 46 | @param path where f lives |
| 47 | @param f file name of a possible binary lib file (no path) |
| 48 | @return f without a binary suffix (that is, an importable name) if path+f is indeed a binary lib, or None. |
| 49 | Note: if for .pyc or .pyo file a .py is found, None is returned. |
| 50 | """ |
| 51 | if not f.endswith(".pyc") and not f.endswith(".typelib") and not f.endswith(".pyo") and not f.endswith(".so") and not f.endswith(".pyd"): |
| 52 | return None |
| 53 | ret = None |
| 54 | match = BIN_MODULE_FNAME_PAT.match(f) |
| 55 | if match: |
| 56 | ret = match.group(1) |
| 57 | modlen = len('module') |
| 58 | retlen = len(ret) |
| 59 | if ret.endswith('module') and retlen > modlen and f.endswith('.so'): # what for? |
| 60 | ret = ret[:(retlen - modlen)] |
| 61 | if f.endswith('.pyc') or f.endswith('.pyo'): |
| 62 | fullname = os.path.join(path, f[:-1]) # check for __pycache__ is made outside |
| 63 | if os.path.exists(fullname): |
| 64 | ret = None |
| 65 | pat_match = TYPELIB_MODULE_FNAME_PAT.match(f) |
| 66 | if pat_match: |
| 67 | ret = "gi.repository." + pat_match.group(1) |
| 68 | return ret |
| 69 | |
| 70 | |
| 71 | def is_posix_skipped_module(path, f): |
| 72 | if os.name == 'posix': |
| 73 | name = os.path.join(path, f) |
| 74 | for mod in POSIX_SKIP_MODULES: |
| 75 | if name.endswith(mod): |
| 76 | return True |
| 77 | return False |
| 78 | |
| 79 | |
| 80 | def is_mac_skipped_module(path, f): |
| 81 | fullname = os.path.join(path, f) |
| 82 | m = MAC_STDLIB_PATTERN.match(fullname) |
| 83 | if not m: return 0 |
| 84 | relpath = m.group(2) |
| 85 | for module in MAC_SKIP_MODULES: |
| 86 | if relpath.startswith(module): return 1 |
| 87 | return 0 |
| 88 | |
| 89 | |
| 90 | def is_skipped_module(path, f): |
| 91 | return is_mac_skipped_module(path, f) or is_posix_skipped_module(path, f[:f.rindex('.')]) or 'pynestkernel' in f |
| 92 | |
| 93 | |
| 94 | def is_module(d, root): |
| 95 | return (os.path.exists(os.path.join(root, d, "__init__.py")) or |
| 96 | os.path.exists(os.path.join(root, d, "__init__.pyc")) or |
| 97 | os.path.exists(os.path.join(root, d, "__init__.pyo"))) |
| 98 | |
| 99 | |
Tor Norbye | 8668e1b | 2013-12-20 09:14:04 -0800 | [diff] [blame] | 100 | def walk_python_path(path): |
| 101 | for root, dirs, files in os.walk(path): |
| 102 | if root.endswith('__pycache__'): |
| 103 | continue |
| 104 | dirs_copy = list(dirs) |
| 105 | for d in dirs_copy: |
| 106 | if d.endswith('__pycache__') or not is_module(d, root): |
| 107 | dirs.remove(d) |
| 108 | # some files show up but are actually non-existent symlinks |
| 109 | yield root, [f for f in files if os.path.exists(os.path.join(root, f))] |
| 110 | |
| 111 | |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 112 | def list_binaries(paths): |
| 113 | """ |
| 114 | Finds binaries in the given list of paths. |
| 115 | Understands nested paths, as sys.paths have it (both "a/b" and "a/b/c"). |
| 116 | Tries to be case-insensitive, but case-preserving. |
| 117 | @param paths: list of paths. |
| 118 | @return: dict[module_name, full_path] |
| 119 | """ |
| 120 | SEP = os.path.sep |
| 121 | res = {} # {name.upper(): (name, full_path)} # b/c windows is case-oblivious |
| 122 | if not paths: |
| 123 | return {} |
| 124 | if IS_JAVA: # jython can't have binary modules |
| 125 | return {} |
| 126 | paths = sorted_no_case(paths) |
| 127 | for path in paths: |
| 128 | if path == os.path.dirname(sys.argv[0]): continue |
Tor Norbye | 8668e1b | 2013-12-20 09:14:04 -0800 | [diff] [blame] | 129 | for root, files in walk_python_path(path): |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 130 | cutpoint = path.rfind(SEP) |
| 131 | if cutpoint > 0: |
| 132 | preprefix = path[(cutpoint + len(SEP)):] + '.' |
| 133 | else: |
| 134 | preprefix = '' |
| 135 | prefix = root[(len(path) + len(SEP)):].replace(SEP, '.') |
| 136 | if prefix: |
| 137 | prefix += '.' |
| 138 | note("root: %s path: %s prefix: %s preprefix: %s", root, path, prefix, preprefix) |
| 139 | for f in files: |
| 140 | name = cut_binary_lib_suffix(root, f) |
| 141 | if name and not is_skipped_module(root, f): |
| 142 | note("cutout: %s", name) |
| 143 | if preprefix: |
| 144 | note("prefixes: %s %s", prefix, preprefix) |
| 145 | pre_name = (preprefix + prefix + name).upper() |
| 146 | if pre_name in res: |
| 147 | res.pop(pre_name) # there might be a dupe, if paths got both a/b and a/b/c |
| 148 | note("done with %s", name) |
| 149 | the_name = prefix + name |
| 150 | file_path = os.path.join(root, f) |
| 151 | |
| 152 | res[the_name.upper()] = (the_name, file_path, os.path.getsize(file_path), int(os.stat(file_path).st_mtime)) |
| 153 | return list(res.values()) |
| 154 | |
| 155 | |
| 156 | def list_sources(paths): |
| 157 | #noinspection PyBroadException |
| 158 | try: |
| 159 | for path in paths: |
| 160 | if path == os.path.dirname(sys.argv[0]): continue |
| 161 | |
| 162 | path = os.path.normpath(path) |
| 163 | |
Tor Norbye | 2e5965e | 2014-07-25 12:24:15 -0700 | [diff] [blame] | 164 | if path.endswith('.egg') and os.path.isfile(path): |
| 165 | say("%s\t%s\t%d", path, path, os.path.getsize(path)) |
| 166 | |
Tor Norbye | 8668e1b | 2013-12-20 09:14:04 -0800 | [diff] [blame] | 167 | for root, files in walk_python_path(path): |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 168 | for name in files: |
| 169 | if name.endswith('.py'): |
| 170 | file_path = os.path.join(root, name) |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 171 | say("%s\t%s\t%d", os.path.normpath(file_path), path, os.path.getsize(file_path)) |
| 172 | say('END') |
| 173 | sys.stdout.flush() |
| 174 | except: |
| 175 | import traceback |
| 176 | |
| 177 | traceback.print_exc() |
| 178 | sys.exit(1) |
| 179 | |
| 180 | |
| 181 | #noinspection PyBroadException |
| 182 | def zip_sources(zip_path): |
| 183 | if not os.path.exists(zip_path): |
| 184 | os.makedirs(zip_path) |
| 185 | |
| 186 | zip_filename = os.path.normpath(os.path.sep.join([zip_path, "skeletons.zip"])) |
| 187 | |
| 188 | try: |
| 189 | zip = zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) |
| 190 | except: |
| 191 | zip = zipfile.ZipFile(zip_filename, 'w') |
| 192 | |
| 193 | try: |
| 194 | try: |
| 195 | while True: |
| 196 | line = sys.stdin.readline() |
| 197 | line = line.strip() |
| 198 | |
| 199 | if line == '-': |
| 200 | break |
| 201 | |
| 202 | if line: |
| 203 | # This line will break the split: |
| 204 | # /.../dist-packages/setuptools/script template (dev).py setuptools/script template (dev).py |
| 205 | split_items = line.split() |
| 206 | if len(split_items) > 2: |
| 207 | match_two_files = re.match(r'^(.+\.py)\s+(.+\.py)$', line) |
| 208 | if not match_two_files: |
| 209 | report("Error(zip_sources): invalid line '%s'" % line) |
| 210 | continue |
| 211 | split_items = match_two_files.group(1, 2) |
| 212 | (path, arcpath) = split_items |
| 213 | zip.write(path, arcpath) |
| 214 | else: |
| 215 | # busy waiting for input from PyCharm... |
| 216 | time.sleep(0.10) |
| 217 | say('OK: ' + zip_filename) |
| 218 | sys.stdout.flush() |
| 219 | except: |
| 220 | import traceback |
| 221 | |
| 222 | traceback.print_exc() |
| 223 | say('Error creating archive.') |
| 224 | |
| 225 | sys.exit(1) |
| 226 | finally: |
| 227 | zip.close() |
| 228 | |
| 229 | |
| 230 | # command-line interface |
| 231 | #noinspection PyBroadException |
| 232 | def process_one(name, mod_file_name, doing_builtins, subdir): |
| 233 | """ |
| 234 | Processes a single module named name defined in file_name (autodetect if not given). |
| 235 | Returns True on success. |
| 236 | """ |
| 237 | if has_regular_python_ext(name): |
| 238 | report("Ignored a regular Python file %r", name) |
| 239 | return True |
| 240 | if not quiet: |
| 241 | say(name) |
| 242 | sys.stdout.flush() |
| 243 | action("doing nothing") |
Tor Norbye | 809cb3e | 2014-01-27 09:36:41 -0800 | [diff] [blame] | 244 | |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 245 | try: |
Tor Norbye | 809cb3e | 2014-01-27 09:36:41 -0800 | [diff] [blame] | 246 | fname = build_output_name(subdir, name) |
| 247 | action("opening %r", fname) |
| 248 | old_modules = list(sys.modules.keys()) |
| 249 | imported_module_names = [] |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 250 | |
Tor Norbye | 809cb3e | 2014-01-27 09:36:41 -0800 | [diff] [blame] | 251 | class MyFinder: |
| 252 | #noinspection PyMethodMayBeStatic |
| 253 | def find_module(self, fullname, path=None): |
| 254 | if fullname != name: |
| 255 | imported_module_names.append(fullname) |
| 256 | return None |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 257 | |
Tor Norbye | 809cb3e | 2014-01-27 09:36:41 -0800 | [diff] [blame] | 258 | my_finder = None |
| 259 | if hasattr(sys, 'meta_path'): |
| 260 | my_finder = MyFinder() |
| 261 | sys.meta_path.append(my_finder) |
| 262 | else: |
| 263 | imported_module_names = None |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 264 | |
Tor Norbye | 809cb3e | 2014-01-27 09:36:41 -0800 | [diff] [blame] | 265 | action("importing") |
| 266 | __import__(name) # sys.modules will fill up with what we want |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 267 | |
Tor Norbye | 809cb3e | 2014-01-27 09:36:41 -0800 | [diff] [blame] | 268 | if my_finder: |
| 269 | sys.meta_path.remove(my_finder) |
| 270 | if imported_module_names is None: |
| 271 | imported_module_names = [m for m in sys.modules.keys() if m not in old_modules] |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 272 | |
Tor Norbye | 809cb3e | 2014-01-27 09:36:41 -0800 | [diff] [blame] | 273 | redo_module(name, fname, mod_file_name, doing_builtins) |
| 274 | # The C library may have called Py_InitModule() multiple times to define several modules (gtk._gtk and gtk.gdk); |
| 275 | # restore all of them |
| 276 | path = name.split(".") |
| 277 | redo_imports = not ".".join(path[:-1]) in MODULES_INSPECT_DIR |
| 278 | if imported_module_names and redo_imports: |
| 279 | for m in sys.modules.keys(): |
| 280 | if m.startswith("pycharm_generator_utils"): continue |
| 281 | action("looking at possible submodule %r", m) |
| 282 | # if module has __file__ defined, it has Python source code and doesn't need a skeleton |
| 283 | if m not in old_modules and m not in imported_module_names and m != name and not hasattr( |
| 284 | sys.modules[m], '__file__'): |
| 285 | if not quiet: |
| 286 | say(m) |
| 287 | sys.stdout.flush() |
| 288 | fname = build_output_name(subdir, m) |
| 289 | action("opening %r", fname) |
| 290 | try: |
| 291 | redo_module(m, fname, mod_file_name, doing_builtins) |
| 292 | finally: |
| 293 | action("closing %r", fname) |
| 294 | except: |
| 295 | exctype, value = sys.exc_info()[:2] |
| 296 | msg = "Failed to process %r while %s: %s" |
| 297 | args = name, CURRENT_ACTION, str(value) |
| 298 | report(msg, *args) |
| 299 | if debug_mode: |
| 300 | if sys.platform == 'cli': |
| 301 | import traceback |
| 302 | traceback.print_exc(file=sys.stderr) |
| 303 | raise |
| 304 | return False |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 305 | return True |
| 306 | |
| 307 | |
| 308 | def get_help_text(): |
| 309 | return ( |
| 310 | #01234567890123456789012345678901234567890123456789012345678901234567890123456789 |
| 311 | 'Generates interface skeletons for python modules.' '\n' |
| 312 | 'Usage: ' '\n' |
| 313 | ' generator [options] [module_name [file_name]]' '\n' |
| 314 | ' generator [options] -L ' '\n' |
| 315 | 'module_name is fully qualified, and file_name is where the module is defined.' '\n' |
| 316 | 'E.g. foo.bar /usr/lib/python/foo_bar.so' '\n' |
| 317 | 'For built-in modules file_name is not provided.' '\n' |
| 318 | 'Output files will be named as modules plus ".py" suffix.' '\n' |
| 319 | 'Normally every name processed will be printed and stdout flushed.' '\n' |
| 320 | 'directory_list is one string separated by OS-specific path separtors.' '\n' |
| 321 | '\n' |
| 322 | 'Options are:' '\n' |
| 323 | ' -h -- prints this help message.' '\n' |
| 324 | ' -d dir -- output dir, must be writable. If not given, current dir is used.' '\n' |
| 325 | ' -b -- use names from sys.builtin_module_names' '\n' |
| 326 | ' -q -- quiet, do not print anything on stdout. Errors still go to stderr.' '\n' |
| 327 | ' -x -- die on exceptions with a stacktrace; only for debugging.' '\n' |
| 328 | ' -v -- be verbose, print lots of debug output to stderr' '\n' |
| 329 | ' -c modules -- import CLR assemblies with specified names' '\n' |
| 330 | ' -p -- run CLR profiler ' '\n' |
| 331 | ' -s path_list -- add paths to sys.path before run; path_list lists directories' '\n' |
| 332 | ' separated by path separator char, e.g. "c:\\foo;d:\\bar;c:\\with space"' '\n' |
| 333 | ' -L -- print version and then a list of binary module files found ' '\n' |
| 334 | ' on sys.path and in directories in directory_list;' '\n' |
| 335 | ' lines are "qualified.module.name /full/path/to/module_file.{pyd,dll,so}"' '\n' |
| 336 | ' -S -- lists all python sources found in sys.path and in directories in directory_list\n' |
| 337 | ' -z archive_name -- zip files to archive_name. Accepts files to be archived from stdin in format <filepath> <name in archive>' |
| 338 | ) |
| 339 | |
| 340 | |
| 341 | if __name__ == "__main__": |
| 342 | from getopt import getopt |
| 343 | |
| 344 | helptext = get_help_text() |
| 345 | opts, args = getopt(sys.argv[1:], "d:hbqxvc:ps:LSz") |
| 346 | opts = dict(opts) |
| 347 | |
| 348 | quiet = '-q' in opts |
| 349 | _is_verbose = '-v' in opts |
| 350 | subdir = opts.get('-d', '') |
| 351 | |
| 352 | if not opts or '-h' in opts: |
| 353 | say(helptext) |
| 354 | sys.exit(0) |
| 355 | |
| 356 | if '-L' not in opts and '-b' not in opts and '-S' not in opts and not args: |
| 357 | report("Neither -L nor -b nor -S nor any module name given") |
| 358 | sys.exit(1) |
| 359 | |
| 360 | if "-x" in opts: |
| 361 | debug_mode = True |
| 362 | |
| 363 | # patch sys.path? |
| 364 | extra_path = opts.get('-s', None) |
| 365 | if extra_path: |
| 366 | source_dirs = extra_path.split(os.path.pathsep) |
| 367 | for p in source_dirs: |
| 368 | if p and p not in sys.path: |
| 369 | sys.path.append(p) # we need this to make things in additional dirs importable |
| 370 | note("Altered sys.path: %r", sys.path) |
| 371 | |
| 372 | # find binaries? |
| 373 | if "-L" in opts: |
| 374 | if len(args) > 0: |
| 375 | report("Expected no args with -L, got %d args", len(args)) |
| 376 | sys.exit(1) |
| 377 | say(VERSION) |
| 378 | results = list(list_binaries(sys.path)) |
| 379 | results.sort() |
| 380 | for name, path, size, last_modified in results: |
| 381 | say("%s\t%s\t%d\t%d", name, path, size, last_modified) |
| 382 | sys.exit(0) |
| 383 | |
| 384 | if "-S" in opts: |
| 385 | if len(args) > 0: |
| 386 | report("Expected no args with -S, got %d args", len(args)) |
| 387 | sys.exit(1) |
| 388 | say(VERSION) |
| 389 | list_sources(sys.path) |
| 390 | sys.exit(0) |
| 391 | |
| 392 | if "-z" in opts: |
| 393 | if len(args) != 1: |
Tor Norbye | c667c1f | 2014-05-28 17:06:51 -0700 | [diff] [blame] | 394 | report("Expected 1 arg with -z, got %d args", len(args)) |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 395 | sys.exit(1) |
| 396 | zip_sources(args[0]) |
| 397 | sys.exit(0) |
| 398 | |
| 399 | # build skeleton(s) |
| 400 | |
| 401 | timer = Timer() |
| 402 | # determine names |
| 403 | if '-b' in opts: |
| 404 | if args: |
| 405 | report("No names should be specified with -b") |
| 406 | sys.exit(1) |
| 407 | names = list(sys.builtin_module_names) |
| 408 | if not BUILTIN_MOD_NAME in names: |
| 409 | names.append(BUILTIN_MOD_NAME) |
| 410 | if '__main__' in names: |
| 411 | names.remove('__main__') # we don't want ourselves processed |
| 412 | ok = True |
| 413 | for name in names: |
| 414 | ok = process_one(name, None, True, subdir) and ok |
| 415 | if not ok: |
| 416 | sys.exit(1) |
| 417 | |
| 418 | else: |
| 419 | if len(args) > 2: |
| 420 | report("Only module_name or module_name and file_name should be specified; got %d args", len(args)) |
| 421 | sys.exit(1) |
| 422 | name = args[0] |
| 423 | if len(args) == 2: |
| 424 | mod_file_name = args[1] |
| 425 | else: |
| 426 | mod_file_name = None |
| 427 | |
| 428 | if sys.platform == 'cli': |
| 429 | #noinspection PyUnresolvedReferences |
| 430 | import clr |
| 431 | |
| 432 | refs = opts.get('-c', '') |
| 433 | if refs: |
| 434 | for ref in refs.split(';'): clr.AddReferenceByPartialName(ref) |
| 435 | |
| 436 | if '-p' in opts: |
| 437 | atexit.register(print_profile) |
| 438 | |
Tor Norbye | 9258464 | 2014-04-17 08:39:25 -0700 | [diff] [blame] | 439 | # We take module name from import statement |
| 440 | name = get_namespace_by_name(name) |
| 441 | |
Tor Norbye | 3a2425a5 | 2013-11-04 10:16:08 -0800 | [diff] [blame] | 442 | if not process_one(name, mod_file_name, False, subdir): |
| 443 | sys.exit(1) |
| 444 | |
| 445 | say("Generation completed in %d ms", timer.elapsed()) |