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