| # encoding: utf-8 |
| import atexit |
| import zipfile |
| |
| # TODO: Move all CLR-specific functions to clr_tools |
| |
| from pycharm_generator_utils.module_redeclarator import * |
| from pycharm_generator_utils.util_methods import * |
| from pycharm_generator_utils.constants import * |
| from pycharm_generator_utils.clr_tools import * |
| |
| |
| debug_mode = False |
| |
| |
| def redo_module(module_name, outfile, module_file_name, doing_builtins): |
| # gobject does 'del _gobject' in its __init__.py, so the chained attribute lookup code |
| # fails to find 'gobject._gobject'. thus we need to pull the module directly out of |
| # sys.modules |
| mod = sys.modules.get(module_name) |
| mod_path = module_name.split('.') |
| if not mod and sys.platform == 'cli': |
| # "import System.Collections" in IronPython 2.7 doesn't actually put System.Collections in sys.modules |
| # instead, sys.modules['System'] get set to a Microsoft.Scripting.Actions.NamespaceTracker and Collections can be |
| # accessed as its attribute |
| mod = sys.modules[mod_path[0]] |
| for component in mod_path[1:]: |
| try: |
| mod = getattr(mod, component) |
| except AttributeError: |
| mod = None |
| report("Failed to find CLR module " + module_name) |
| break |
| if mod: |
| action("restoring") |
| r = ModuleRedeclarator(mod, outfile, module_file_name, doing_builtins=doing_builtins) |
| r.redo(module_name, ".".join(mod_path[:-1]) in MODULES_INSPECT_DIR) |
| action("flushing") |
| r.flush() |
| else: |
| report("Failed to find imported module in sys.modules " + module_name) |
| |
| # find_binaries functionality |
| def cut_binary_lib_suffix(path, f): |
| """ |
| @param path where f lives |
| @param f file name of a possible binary lib file (no path) |
| @return f without a binary suffix (that is, an importable name) if path+f is indeed a binary lib, or None. |
| Note: if for .pyc or .pyo file a .py is found, None is returned. |
| """ |
| 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"): |
| return None |
| ret = None |
| match = BIN_MODULE_FNAME_PAT.match(f) |
| if match: |
| ret = match.group(1) |
| modlen = len('module') |
| retlen = len(ret) |
| if ret.endswith('module') and retlen > modlen and f.endswith('.so'): # what for? |
| ret = ret[:(retlen - modlen)] |
| if f.endswith('.pyc') or f.endswith('.pyo'): |
| fullname = os.path.join(path, f[:-1]) # check for __pycache__ is made outside |
| if os.path.exists(fullname): |
| ret = None |
| pat_match = TYPELIB_MODULE_FNAME_PAT.match(f) |
| if pat_match: |
| ret = "gi.repository." + pat_match.group(1) |
| return ret |
| |
| |
| def is_posix_skipped_module(path, f): |
| if os.name == 'posix': |
| name = os.path.join(path, f) |
| for mod in POSIX_SKIP_MODULES: |
| if name.endswith(mod): |
| return True |
| return False |
| |
| |
| def is_mac_skipped_module(path, f): |
| fullname = os.path.join(path, f) |
| m = MAC_STDLIB_PATTERN.match(fullname) |
| if not m: return 0 |
| relpath = m.group(2) |
| for module in MAC_SKIP_MODULES: |
| if relpath.startswith(module): return 1 |
| return 0 |
| |
| |
| def is_skipped_module(path, f): |
| return is_mac_skipped_module(path, f) or is_posix_skipped_module(path, f[:f.rindex('.')]) or 'pynestkernel' in f |
| |
| |
| def is_module(d, root): |
| return (os.path.exists(os.path.join(root, d, "__init__.py")) or |
| os.path.exists(os.path.join(root, d, "__init__.pyc")) or |
| os.path.exists(os.path.join(root, d, "__init__.pyo"))) |
| |
| |
| def walk_python_path(path): |
| for root, dirs, files in os.walk(path): |
| if root.endswith('__pycache__'): |
| continue |
| dirs_copy = list(dirs) |
| for d in dirs_copy: |
| if d.endswith('__pycache__') or not is_module(d, root): |
| dirs.remove(d) |
| # some files show up but are actually non-existent symlinks |
| yield root, [f for f in files if os.path.exists(os.path.join(root, f))] |
| |
| |
| def list_binaries(paths): |
| """ |
| Finds binaries in the given list of paths. |
| Understands nested paths, as sys.paths have it (both "a/b" and "a/b/c"). |
| Tries to be case-insensitive, but case-preserving. |
| @param paths: list of paths. |
| @return: dict[module_name, full_path] |
| """ |
| SEP = os.path.sep |
| res = {} # {name.upper(): (name, full_path)} # b/c windows is case-oblivious |
| if not paths: |
| return {} |
| if IS_JAVA: # jython can't have binary modules |
| return {} |
| paths = sorted_no_case(paths) |
| for path in paths: |
| if path == os.path.dirname(sys.argv[0]): continue |
| for root, files in walk_python_path(path): |
| cutpoint = path.rfind(SEP) |
| if cutpoint > 0: |
| preprefix = path[(cutpoint + len(SEP)):] + '.' |
| else: |
| preprefix = '' |
| prefix = root[(len(path) + len(SEP)):].replace(SEP, '.') |
| if prefix: |
| prefix += '.' |
| note("root: %s path: %s prefix: %s preprefix: %s", root, path, prefix, preprefix) |
| for f in files: |
| name = cut_binary_lib_suffix(root, f) |
| if name and not is_skipped_module(root, f): |
| note("cutout: %s", name) |
| if preprefix: |
| note("prefixes: %s %s", prefix, preprefix) |
| pre_name = (preprefix + prefix + name).upper() |
| if pre_name in res: |
| res.pop(pre_name) # there might be a dupe, if paths got both a/b and a/b/c |
| note("done with %s", name) |
| the_name = prefix + name |
| file_path = os.path.join(root, f) |
| |
| res[the_name.upper()] = (the_name, file_path, os.path.getsize(file_path), int(os.stat(file_path).st_mtime)) |
| return list(res.values()) |
| |
| |
| def list_sources(paths): |
| #noinspection PyBroadException |
| try: |
| for path in paths: |
| if path == os.path.dirname(sys.argv[0]): continue |
| |
| path = os.path.normpath(path) |
| |
| if path.endswith('.egg') and os.path.isfile(path): |
| say("%s\t%s\t%d", path, path, os.path.getsize(path)) |
| |
| for root, files in walk_python_path(path): |
| for name in files: |
| if name.endswith('.py'): |
| file_path = os.path.join(root, name) |
| say("%s\t%s\t%d", os.path.normpath(file_path), path, os.path.getsize(file_path)) |
| say('END') |
| sys.stdout.flush() |
| except: |
| import traceback |
| |
| traceback.print_exc() |
| sys.exit(1) |
| |
| |
| #noinspection PyBroadException |
| def zip_sources(zip_path): |
| if not os.path.exists(zip_path): |
| os.makedirs(zip_path) |
| |
| zip_filename = os.path.normpath(os.path.sep.join([zip_path, "skeletons.zip"])) |
| |
| try: |
| zip = zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) |
| except: |
| zip = zipfile.ZipFile(zip_filename, 'w') |
| |
| try: |
| try: |
| while True: |
| line = sys.stdin.readline() |
| line = line.strip() |
| |
| if line == '-': |
| break |
| |
| if line: |
| # This line will break the split: |
| # /.../dist-packages/setuptools/script template (dev).py setuptools/script template (dev).py |
| split_items = line.split() |
| if len(split_items) > 2: |
| match_two_files = re.match(r'^(.+\.py)\s+(.+\.py)$', line) |
| if not match_two_files: |
| report("Error(zip_sources): invalid line '%s'" % line) |
| continue |
| split_items = match_two_files.group(1, 2) |
| (path, arcpath) = split_items |
| zip.write(path, arcpath) |
| else: |
| # busy waiting for input from PyCharm... |
| time.sleep(0.10) |
| say('OK: ' + zip_filename) |
| sys.stdout.flush() |
| except: |
| import traceback |
| |
| traceback.print_exc() |
| say('Error creating archive.') |
| |
| sys.exit(1) |
| finally: |
| zip.close() |
| |
| |
| # command-line interface |
| #noinspection PyBroadException |
| def process_one(name, mod_file_name, doing_builtins, subdir): |
| """ |
| Processes a single module named name defined in file_name (autodetect if not given). |
| Returns True on success. |
| """ |
| if has_regular_python_ext(name): |
| report("Ignored a regular Python file %r", name) |
| return True |
| if not quiet: |
| say(name) |
| sys.stdout.flush() |
| action("doing nothing") |
| |
| try: |
| fname = build_output_name(subdir, name) |
| action("opening %r", fname) |
| old_modules = list(sys.modules.keys()) |
| imported_module_names = [] |
| |
| class MyFinder: |
| #noinspection PyMethodMayBeStatic |
| def find_module(self, fullname, path=None): |
| if fullname != name: |
| imported_module_names.append(fullname) |
| return None |
| |
| my_finder = None |
| if hasattr(sys, 'meta_path'): |
| my_finder = MyFinder() |
| sys.meta_path.append(my_finder) |
| else: |
| imported_module_names = None |
| |
| action("importing") |
| __import__(name) # sys.modules will fill up with what we want |
| |
| if my_finder: |
| sys.meta_path.remove(my_finder) |
| if imported_module_names is None: |
| imported_module_names = [m for m in sys.modules.keys() if m not in old_modules] |
| |
| redo_module(name, fname, mod_file_name, doing_builtins) |
| # The C library may have called Py_InitModule() multiple times to define several modules (gtk._gtk and gtk.gdk); |
| # restore all of them |
| path = name.split(".") |
| redo_imports = not ".".join(path[:-1]) in MODULES_INSPECT_DIR |
| if imported_module_names and redo_imports: |
| for m in sys.modules.keys(): |
| if m.startswith("pycharm_generator_utils"): continue |
| action("looking at possible submodule %r", m) |
| # if module has __file__ defined, it has Python source code and doesn't need a skeleton |
| if m not in old_modules and m not in imported_module_names and m != name and not hasattr( |
| sys.modules[m], '__file__'): |
| if not quiet: |
| say(m) |
| sys.stdout.flush() |
| fname = build_output_name(subdir, m) |
| action("opening %r", fname) |
| try: |
| redo_module(m, fname, mod_file_name, doing_builtins) |
| finally: |
| action("closing %r", fname) |
| except: |
| exctype, value = sys.exc_info()[:2] |
| msg = "Failed to process %r while %s: %s" |
| args = name, CURRENT_ACTION, str(value) |
| report(msg, *args) |
| if debug_mode: |
| if sys.platform == 'cli': |
| import traceback |
| traceback.print_exc(file=sys.stderr) |
| raise |
| return False |
| return True |
| |
| |
| def get_help_text(): |
| return ( |
| #01234567890123456789012345678901234567890123456789012345678901234567890123456789 |
| 'Generates interface skeletons for python modules.' '\n' |
| 'Usage: ' '\n' |
| ' generator [options] [module_name [file_name]]' '\n' |
| ' generator [options] -L ' '\n' |
| 'module_name is fully qualified, and file_name is where the module is defined.' '\n' |
| 'E.g. foo.bar /usr/lib/python/foo_bar.so' '\n' |
| 'For built-in modules file_name is not provided.' '\n' |
| 'Output files will be named as modules plus ".py" suffix.' '\n' |
| 'Normally every name processed will be printed and stdout flushed.' '\n' |
| 'directory_list is one string separated by OS-specific path separtors.' '\n' |
| '\n' |
| 'Options are:' '\n' |
| ' -h -- prints this help message.' '\n' |
| ' -d dir -- output dir, must be writable. If not given, current dir is used.' '\n' |
| ' -b -- use names from sys.builtin_module_names' '\n' |
| ' -q -- quiet, do not print anything on stdout. Errors still go to stderr.' '\n' |
| ' -x -- die on exceptions with a stacktrace; only for debugging.' '\n' |
| ' -v -- be verbose, print lots of debug output to stderr' '\n' |
| ' -c modules -- import CLR assemblies with specified names' '\n' |
| ' -p -- run CLR profiler ' '\n' |
| ' -s path_list -- add paths to sys.path before run; path_list lists directories' '\n' |
| ' separated by path separator char, e.g. "c:\\foo;d:\\bar;c:\\with space"' '\n' |
| ' -L -- print version and then a list of binary module files found ' '\n' |
| ' on sys.path and in directories in directory_list;' '\n' |
| ' lines are "qualified.module.name /full/path/to/module_file.{pyd,dll,so}"' '\n' |
| ' -S -- lists all python sources found in sys.path and in directories in directory_list\n' |
| ' -z archive_name -- zip files to archive_name. Accepts files to be archived from stdin in format <filepath> <name in archive>' |
| ) |
| |
| |
| if __name__ == "__main__": |
| from getopt import getopt |
| |
| helptext = get_help_text() |
| opts, args = getopt(sys.argv[1:], "d:hbqxvc:ps:LSz") |
| opts = dict(opts) |
| |
| quiet = '-q' in opts |
| _is_verbose = '-v' in opts |
| subdir = opts.get('-d', '') |
| |
| if not opts or '-h' in opts: |
| say(helptext) |
| sys.exit(0) |
| |
| if '-L' not in opts and '-b' not in opts and '-S' not in opts and not args: |
| report("Neither -L nor -b nor -S nor any module name given") |
| sys.exit(1) |
| |
| if "-x" in opts: |
| debug_mode = True |
| |
| # patch sys.path? |
| extra_path = opts.get('-s', None) |
| if extra_path: |
| source_dirs = extra_path.split(os.path.pathsep) |
| for p in source_dirs: |
| if p and p not in sys.path: |
| sys.path.append(p) # we need this to make things in additional dirs importable |
| note("Altered sys.path: %r", sys.path) |
| |
| # find binaries? |
| if "-L" in opts: |
| if len(args) > 0: |
| report("Expected no args with -L, got %d args", len(args)) |
| sys.exit(1) |
| say(VERSION) |
| results = list(list_binaries(sys.path)) |
| results.sort() |
| for name, path, size, last_modified in results: |
| say("%s\t%s\t%d\t%d", name, path, size, last_modified) |
| sys.exit(0) |
| |
| if "-S" in opts: |
| if len(args) > 0: |
| report("Expected no args with -S, got %d args", len(args)) |
| sys.exit(1) |
| say(VERSION) |
| list_sources(sys.path) |
| sys.exit(0) |
| |
| if "-z" in opts: |
| if len(args) != 1: |
| report("Expected 1 arg with -z, got %d args", len(args)) |
| sys.exit(1) |
| zip_sources(args[0]) |
| sys.exit(0) |
| |
| # build skeleton(s) |
| |
| timer = Timer() |
| # determine names |
| if '-b' in opts: |
| if args: |
| report("No names should be specified with -b") |
| sys.exit(1) |
| names = list(sys.builtin_module_names) |
| if not BUILTIN_MOD_NAME in names: |
| names.append(BUILTIN_MOD_NAME) |
| if '__main__' in names: |
| names.remove('__main__') # we don't want ourselves processed |
| ok = True |
| for name in names: |
| ok = process_one(name, None, True, subdir) and ok |
| if not ok: |
| sys.exit(1) |
| |
| else: |
| if len(args) > 2: |
| report("Only module_name or module_name and file_name should be specified; got %d args", len(args)) |
| sys.exit(1) |
| name = args[0] |
| if len(args) == 2: |
| mod_file_name = args[1] |
| else: |
| mod_file_name = None |
| |
| if sys.platform == 'cli': |
| #noinspection PyUnresolvedReferences |
| import clr |
| |
| refs = opts.get('-c', '') |
| if refs: |
| for ref in refs.split(';'): clr.AddReferenceByPartialName(ref) |
| |
| if '-p' in opts: |
| atexit.register(print_profile) |
| |
| # We take module name from import statement |
| name = get_namespace_by_name(name) |
| |
| if not process_one(name, mod_file_name, False, subdir): |
| sys.exit(1) |
| |
| say("Generation completed in %d ms", timer.elapsed()) |