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