blob: d09f6d918e186e562c7d9f829f59d1bc1d27c887 [file] [log] [blame]
# 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())