##// END OF EJS Templates
import-checker: don't treat modules as relative one if not found...
FUJIWARA Katsunori -
r25175:10e6c4b7 default
parent child Browse files
Show More
@@ -1,377 +1,377 b''
1 import ast
1 import ast
2 import os
2 import os
3 import sys
3 import sys
4
4
5 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
5 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
6 # to work when run from a virtualenv. The modules were chosen empirically
6 # to work when run from a virtualenv. The modules were chosen empirically
7 # so that the return value matches the return value without virtualenv.
7 # so that the return value matches the return value without virtualenv.
8 import BaseHTTPServer
8 import BaseHTTPServer
9 import zlib
9 import zlib
10
10
11 def dotted_name_of_path(path, trimpure=False):
11 def dotted_name_of_path(path, trimpure=False):
12 """Given a relative path to a source file, return its dotted module name.
12 """Given a relative path to a source file, return its dotted module name.
13
13
14 >>> dotted_name_of_path('mercurial/error.py')
14 >>> dotted_name_of_path('mercurial/error.py')
15 'mercurial.error'
15 'mercurial.error'
16 >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True)
16 >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True)
17 'mercurial.parsers'
17 'mercurial.parsers'
18 >>> dotted_name_of_path('zlibmodule.so')
18 >>> dotted_name_of_path('zlibmodule.so')
19 'zlib'
19 'zlib'
20 """
20 """
21 parts = path.split('/')
21 parts = path.split('/')
22 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
22 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
23 if parts[-1].endswith('module'):
23 if parts[-1].endswith('module'):
24 parts[-1] = parts[-1][:-6]
24 parts[-1] = parts[-1][:-6]
25 if trimpure:
25 if trimpure:
26 return '.'.join(p for p in parts if p != 'pure')
26 return '.'.join(p for p in parts if p != 'pure')
27 return '.'.join(parts)
27 return '.'.join(parts)
28
28
29 def fromlocalfunc(modulename, localmods):
29 def fromlocalfunc(modulename, localmods):
30 """Get a function to examine which locally defined module the
30 """Get a function to examine which locally defined module the
31 target source imports via a specified name.
31 target source imports via a specified name.
32
32
33 `modulename` is an `dotted_name_of_path()`-ed source file path,
33 `modulename` is an `dotted_name_of_path()`-ed source file path,
34 which may have `.__init__` at the end of it, of the target source.
34 which may have `.__init__` at the end of it, of the target source.
35
35
36 `localmods` is a dict (or set), of which key is an absolute
36 `localmods` is a dict (or set), of which key is an absolute
37 `dotted_name_of_path()`-ed source file path of locally defined (=
37 `dotted_name_of_path()`-ed source file path of locally defined (=
38 Mercurial specific) modules.
38 Mercurial specific) modules.
39
39
40 This function assumes that module names not existing in
40 This function assumes that module names not existing in
41 `localmods` are ones of Python standard libarary.
41 `localmods` are ones of Python standard libarary.
42
42
43 This function returns the function, which takes `name` argument,
43 This function returns the function, which takes `name` argument,
44 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
44 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
45 matches against locally defined module. Otherwise, it returns
45 matches against locally defined module. Otherwise, it returns
46 False.
46 False.
47
47
48 It is assumed that `name` doesn't have `.__init__`.
48 It is assumed that `name` doesn't have `.__init__`.
49
49
50 `absname` is an absolute module name of specified `name`
50 `absname` is an absolute module name of specified `name`
51 (e.g. "hgext.convert"). This can be used to compose prefix for sub
51 (e.g. "hgext.convert"). This can be used to compose prefix for sub
52 modules or so.
52 modules or so.
53
53
54 `dottedpath` is a `dotted_name_of_path()`-ed source file path
54 `dottedpath` is a `dotted_name_of_path()`-ed source file path
55 (e.g. "hgext.convert.__init__") of `name`. This is used to look
55 (e.g. "hgext.convert.__init__") of `name`. This is used to look
56 module up in `localmods` again.
56 module up in `localmods` again.
57
57
58 `hassubmod` is whether it may have sub modules under it (for
58 `hassubmod` is whether it may have sub modules under it (for
59 convenient, even though this is also equivalent to "absname !=
59 convenient, even though this is also equivalent to "absname !=
60 dottednpath")
60 dottednpath")
61
61
62 >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
62 >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
63 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
63 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
64 ... 'baz.__init__': True, 'baz.baz1': True }
64 ... 'baz.__init__': True, 'baz.baz1': True }
65 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
65 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
66 >>> # relative
66 >>> # relative
67 >>> fromlocal('foo1')
67 >>> fromlocal('foo1')
68 ('foo.foo1', 'foo.foo1', False)
68 ('foo.foo1', 'foo.foo1', False)
69 >>> fromlocal('bar')
69 >>> fromlocal('bar')
70 ('foo.bar', 'foo.bar.__init__', True)
70 ('foo.bar', 'foo.bar.__init__', True)
71 >>> fromlocal('bar.bar1')
71 >>> fromlocal('bar.bar1')
72 ('foo.bar.bar1', 'foo.bar.bar1', False)
72 ('foo.bar.bar1', 'foo.bar.bar1', False)
73 >>> # absolute
73 >>> # absolute
74 >>> fromlocal('baz')
74 >>> fromlocal('baz')
75 ('baz', 'baz.__init__', True)
75 ('baz', 'baz.__init__', True)
76 >>> fromlocal('baz.baz1')
76 >>> fromlocal('baz.baz1')
77 ('baz.baz1', 'baz.baz1', False)
77 ('baz.baz1', 'baz.baz1', False)
78 >>> # unknown = maybe standard library
78 >>> # unknown = maybe standard library
79 >>> fromlocal('os')
79 >>> fromlocal('os')
80 False
80 False
81 """
81 """
82 prefix = '.'.join(modulename.split('.')[:-1])
82 prefix = '.'.join(modulename.split('.')[:-1])
83 if prefix:
83 if prefix:
84 prefix += '.'
84 prefix += '.'
85 def fromlocal(name):
85 def fromlocal(name):
86 # check relative name at first
86 # check relative name at first
87 for n in prefix + name, name:
87 for n in prefix + name, name:
88 if n in localmods:
88 if n in localmods:
89 return (n, n, False)
89 return (n, n, False)
90 dottedpath = n + '.__init__'
90 dottedpath = n + '.__init__'
91 if dottedpath in localmods:
91 if dottedpath in localmods:
92 return (n, dottedpath, True)
92 return (n, dottedpath, True)
93 return False
93 return False
94 return fromlocal
94 return fromlocal
95
95
96 def list_stdlib_modules():
96 def list_stdlib_modules():
97 """List the modules present in the stdlib.
97 """List the modules present in the stdlib.
98
98
99 >>> mods = set(list_stdlib_modules())
99 >>> mods = set(list_stdlib_modules())
100 >>> 'BaseHTTPServer' in mods
100 >>> 'BaseHTTPServer' in mods
101 True
101 True
102
102
103 os.path isn't really a module, so it's missing:
103 os.path isn't really a module, so it's missing:
104
104
105 >>> 'os.path' in mods
105 >>> 'os.path' in mods
106 False
106 False
107
107
108 sys requires special treatment, because it's baked into the
108 sys requires special treatment, because it's baked into the
109 interpreter, but it should still appear:
109 interpreter, but it should still appear:
110
110
111 >>> 'sys' in mods
111 >>> 'sys' in mods
112 True
112 True
113
113
114 >>> 'collections' in mods
114 >>> 'collections' in mods
115 True
115 True
116
116
117 >>> 'cStringIO' in mods
117 >>> 'cStringIO' in mods
118 True
118 True
119 """
119 """
120 for m in sys.builtin_module_names:
120 for m in sys.builtin_module_names:
121 yield m
121 yield m
122 # These modules only exist on windows, but we should always
122 # These modules only exist on windows, but we should always
123 # consider them stdlib.
123 # consider them stdlib.
124 for m in ['msvcrt', '_winreg']:
124 for m in ['msvcrt', '_winreg']:
125 yield m
125 yield m
126 # These get missed too
126 # These get missed too
127 for m in 'ctypes', 'email':
127 for m in 'ctypes', 'email':
128 yield m
128 yield m
129 yield 'builtins' # python3 only
129 yield 'builtins' # python3 only
130 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
130 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
131 yield m
131 yield m
132 stdlib_prefixes = set([sys.prefix, sys.exec_prefix])
132 stdlib_prefixes = set([sys.prefix, sys.exec_prefix])
133 # We need to supplement the list of prefixes for the search to work
133 # We need to supplement the list of prefixes for the search to work
134 # when run from within a virtualenv.
134 # when run from within a virtualenv.
135 for mod in (BaseHTTPServer, zlib):
135 for mod in (BaseHTTPServer, zlib):
136 try:
136 try:
137 # Not all module objects have a __file__ attribute.
137 # Not all module objects have a __file__ attribute.
138 filename = mod.__file__
138 filename = mod.__file__
139 except AttributeError:
139 except AttributeError:
140 continue
140 continue
141 dirname = os.path.dirname(filename)
141 dirname = os.path.dirname(filename)
142 for prefix in stdlib_prefixes:
142 for prefix in stdlib_prefixes:
143 if dirname.startswith(prefix):
143 if dirname.startswith(prefix):
144 # Then this directory is redundant.
144 # Then this directory is redundant.
145 break
145 break
146 else:
146 else:
147 stdlib_prefixes.add(dirname)
147 stdlib_prefixes.add(dirname)
148 for libpath in sys.path:
148 for libpath in sys.path:
149 # We want to walk everything in sys.path that starts with
149 # We want to walk everything in sys.path that starts with
150 # something in stdlib_prefixes. check-code suppressed because
150 # something in stdlib_prefixes. check-code suppressed because
151 # the ast module used by this script implies the availability
151 # the ast module used by this script implies the availability
152 # of any().
152 # of any().
153 if not any(libpath.startswith(p) for p in stdlib_prefixes): # no-py24
153 if not any(libpath.startswith(p) for p in stdlib_prefixes): # no-py24
154 continue
154 continue
155 if 'site-packages' in libpath:
155 if 'site-packages' in libpath:
156 continue
156 continue
157 for top, dirs, files in os.walk(libpath):
157 for top, dirs, files in os.walk(libpath):
158 for name in files:
158 for name in files:
159 if name == '__init__.py':
159 if name == '__init__.py':
160 continue
160 continue
161 if not (name.endswith('.py') or name.endswith('.so')
161 if not (name.endswith('.py') or name.endswith('.so')
162 or name.endswith('.pyd')):
162 or name.endswith('.pyd')):
163 continue
163 continue
164 full_path = os.path.join(top, name)
164 full_path = os.path.join(top, name)
165 if 'site-packages' in full_path:
165 if 'site-packages' in full_path:
166 continue
166 continue
167 rel_path = full_path[len(libpath) + 1:]
167 rel_path = full_path[len(libpath) + 1:]
168 mod = dotted_name_of_path(rel_path)
168 mod = dotted_name_of_path(rel_path)
169 yield mod
169 yield mod
170
170
171 stdlib_modules = set(list_stdlib_modules())
171 stdlib_modules = set(list_stdlib_modules())
172
172
173 def imported_modules(source, modulename, localmods, ignore_nested=False):
173 def imported_modules(source, modulename, localmods, ignore_nested=False):
174 """Given the source of a file as a string, yield the names
174 """Given the source of a file as a string, yield the names
175 imported by that file.
175 imported by that file.
176
176
177 Args:
177 Args:
178 source: The python source to examine as a string.
178 source: The python source to examine as a string.
179 modulename: of specified python source (may have `__init__`)
179 modulename: of specified python source (may have `__init__`)
180 localmods: dict of locally defined module names (may have `__init__`)
180 localmods: dict of locally defined module names (may have `__init__`)
181 ignore_nested: If true, import statements that do not start in
181 ignore_nested: If true, import statements that do not start in
182 column zero will be ignored.
182 column zero will be ignored.
183
183
184 Returns:
184 Returns:
185 A list of absolute module names imported by the given source.
185 A list of absolute module names imported by the given source.
186
186
187 >>> modulename = 'foo.xxx'
187 >>> modulename = 'foo.xxx'
188 >>> localmods = {'foo.__init__': True,
188 >>> localmods = {'foo.__init__': True,
189 ... 'foo.foo1': True, 'foo.foo2': True,
189 ... 'foo.foo1': True, 'foo.foo2': True,
190 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
190 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
191 ... 'baz.__init__': True, 'baz.baz1': True }
191 ... 'baz.__init__': True, 'baz.baz1': True }
192 >>> # standard library (= not locally defined ones)
192 >>> # standard library (= not locally defined ones)
193 >>> sorted(imported_modules(
193 >>> sorted(imported_modules(
194 ... 'from stdlib1 import foo, bar; import stdlib2',
194 ... 'from stdlib1 import foo, bar; import stdlib2',
195 ... modulename, localmods))
195 ... modulename, localmods))
196 []
196 []
197 >>> # relative importing
197 >>> # relative importing
198 >>> sorted(imported_modules(
198 >>> sorted(imported_modules(
199 ... 'import foo1; from bar import bar1',
199 ... 'import foo1; from bar import bar1',
200 ... modulename, localmods))
200 ... modulename, localmods))
201 ['foo.bar.__init__', 'foo.bar.bar1', 'foo.foo1']
201 ['foo.bar.__init__', 'foo.bar.bar1', 'foo.foo1']
202 >>> sorted(imported_modules(
202 >>> sorted(imported_modules(
203 ... 'from bar.bar1 import name1, name2, name3',
203 ... 'from bar.bar1 import name1, name2, name3',
204 ... modulename, localmods))
204 ... modulename, localmods))
205 ['foo.bar.bar1']
205 ['foo.bar.bar1']
206 >>> # absolute importing
206 >>> # absolute importing
207 >>> sorted(imported_modules(
207 >>> sorted(imported_modules(
208 ... 'from baz import baz1, name1',
208 ... 'from baz import baz1, name1',
209 ... modulename, localmods))
209 ... modulename, localmods))
210 ['baz.__init__', 'baz.baz1']
210 ['baz.__init__', 'baz.baz1']
211 >>> # mixed importing, even though it shouldn't be recommended
211 >>> # mixed importing, even though it shouldn't be recommended
212 >>> sorted(imported_modules(
212 >>> sorted(imported_modules(
213 ... 'import stdlib, foo1, baz',
213 ... 'import stdlib, foo1, baz',
214 ... modulename, localmods))
214 ... modulename, localmods))
215 ['baz.__init__', 'foo.foo1']
215 ['baz.__init__', 'foo.foo1']
216 >>> # ignore_nested
216 >>> # ignore_nested
217 >>> sorted(imported_modules(
217 >>> sorted(imported_modules(
218 ... '''import foo
218 ... '''import foo
219 ... def wat():
219 ... def wat():
220 ... import bar
220 ... import bar
221 ... ''', modulename, localmods))
221 ... ''', modulename, localmods))
222 ['foo.__init__', 'foo.bar.__init__']
222 ['foo.__init__', 'foo.bar.__init__']
223 >>> sorted(imported_modules(
223 >>> sorted(imported_modules(
224 ... '''import foo
224 ... '''import foo
225 ... def wat():
225 ... def wat():
226 ... import bar
226 ... import bar
227 ... ''', modulename, localmods, ignore_nested=True))
227 ... ''', modulename, localmods, ignore_nested=True))
228 ['foo.__init__']
228 ['foo.__init__']
229 """
229 """
230 fromlocal = fromlocalfunc(modulename, localmods)
230 fromlocal = fromlocalfunc(modulename, localmods)
231 for node in ast.walk(ast.parse(source)):
231 for node in ast.walk(ast.parse(source)):
232 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
232 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
233 continue
233 continue
234 if isinstance(node, ast.Import):
234 if isinstance(node, ast.Import):
235 for n in node.names:
235 for n in node.names:
236 found = fromlocal(n.name)
236 found = fromlocal(n.name)
237 if not found:
237 if not found:
238 # this should import standard library
238 # this should import standard library
239 continue
239 continue
240 yield found[1]
240 yield found[1]
241 elif isinstance(node, ast.ImportFrom):
241 elif isinstance(node, ast.ImportFrom):
242 found = fromlocal(node.module)
242 found = fromlocal(node.module)
243 if not found:
243 if not found:
244 # this should import standard library
244 # this should import standard library
245 continue
245 continue
246
246
247 absname, dottedpath, hassubmod = found
247 absname, dottedpath, hassubmod = found
248 yield dottedpath
248 yield dottedpath
249 if not hassubmod:
249 if not hassubmod:
250 # examination of "node.names" should be redundant
250 # examination of "node.names" should be redundant
251 # e.g.: from mercurial.node import nullid, nullrev
251 # e.g.: from mercurial.node import nullid, nullrev
252 continue
252 continue
253
253
254 prefix = absname + '.'
254 prefix = absname + '.'
255 for n in node.names:
255 for n in node.names:
256 found = fromlocal(prefix + n.name)
256 found = fromlocal(prefix + n.name)
257 if not found:
257 if not found:
258 # this should be a function or a property of "node.module"
258 # this should be a function or a property of "node.module"
259 continue
259 continue
260 yield found[1]
260 yield found[1]
261
261
262 def verify_stdlib_on_own_line(source):
262 def verify_stdlib_on_own_line(source):
263 """Given some python source, verify that stdlib imports are done
263 """Given some python source, verify that stdlib imports are done
264 in separate statements from relative local module imports.
264 in separate statements from relative local module imports.
265
265
266 Observing this limitation is important as it works around an
266 Observing this limitation is important as it works around an
267 annoying lib2to3 bug in relative import rewrites:
267 annoying lib2to3 bug in relative import rewrites:
268 http://bugs.python.org/issue19510.
268 http://bugs.python.org/issue19510.
269
269
270 >>> list(verify_stdlib_on_own_line('import sys, foo'))
270 >>> list(verify_stdlib_on_own_line('import sys, foo'))
271 ['mixed imports\\n stdlib: sys\\n relative: foo']
271 ['mixed imports\\n stdlib: sys\\n relative: foo']
272 >>> list(verify_stdlib_on_own_line('import sys, os'))
272 >>> list(verify_stdlib_on_own_line('import sys, os'))
273 []
273 []
274 >>> list(verify_stdlib_on_own_line('import foo, bar'))
274 >>> list(verify_stdlib_on_own_line('import foo, bar'))
275 []
275 []
276 """
276 """
277 for node in ast.walk(ast.parse(source)):
277 for node in ast.walk(ast.parse(source)):
278 if isinstance(node, ast.Import):
278 if isinstance(node, ast.Import):
279 from_stdlib = {False: [], True: []}
279 from_stdlib = {False: [], True: []}
280 for n in node.names:
280 for n in node.names:
281 from_stdlib[n.name in stdlib_modules].append(n.name)
281 from_stdlib[n.name in stdlib_modules].append(n.name)
282 if from_stdlib[True] and from_stdlib[False]:
282 if from_stdlib[True] and from_stdlib[False]:
283 yield ('mixed imports\n stdlib: %s\n relative: %s' %
283 yield ('mixed imports\n stdlib: %s\n relative: %s' %
284 (', '.join(sorted(from_stdlib[True])),
284 (', '.join(sorted(from_stdlib[True])),
285 ', '.join(sorted(from_stdlib[False]))))
285 ', '.join(sorted(from_stdlib[False]))))
286
286
287 class CircularImport(Exception):
287 class CircularImport(Exception):
288 pass
288 pass
289
289
290 def checkmod(mod, imports):
290 def checkmod(mod, imports):
291 shortest = {}
291 shortest = {}
292 visit = [[mod]]
292 visit = [[mod]]
293 while visit:
293 while visit:
294 path = visit.pop(0)
294 path = visit.pop(0)
295 for i in sorted(imports.get(path[-1], [])):
295 for i in sorted(imports.get(path[-1], [])):
296 if i not in stdlib_modules and not i.startswith('mercurial.'):
297 i = mod.rsplit('.', 1)[0] + '.' + i
298 if len(path) < shortest.get(i, 1000):
296 if len(path) < shortest.get(i, 1000):
299 shortest[i] = len(path)
297 shortest[i] = len(path)
300 if i in path:
298 if i in path:
301 if i == path[0]:
299 if i == path[0]:
302 raise CircularImport(path)
300 raise CircularImport(path)
303 continue
301 continue
304 visit.append(path + [i])
302 visit.append(path + [i])
305
303
306 def rotatecycle(cycle):
304 def rotatecycle(cycle):
307 """arrange a cycle so that the lexicographically first module listed first
305 """arrange a cycle so that the lexicographically first module listed first
308
306
309 >>> rotatecycle(['foo', 'bar'])
307 >>> rotatecycle(['foo', 'bar'])
310 ['bar', 'foo', 'bar']
308 ['bar', 'foo', 'bar']
311 """
309 """
312 lowest = min(cycle)
310 lowest = min(cycle)
313 idx = cycle.index(lowest)
311 idx = cycle.index(lowest)
314 return cycle[idx:] + cycle[:idx] + [lowest]
312 return cycle[idx:] + cycle[:idx] + [lowest]
315
313
316 def find_cycles(imports):
314 def find_cycles(imports):
317 """Find cycles in an already-loaded import graph.
315 """Find cycles in an already-loaded import graph.
318
316
319 >>> imports = {'top.foo': ['bar', 'os.path', 'qux'],
317 All module names recorded in `imports` should be absolute one.
320 ... 'top.bar': ['baz', 'sys'],
318
321 ... 'top.baz': ['foo'],
319 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
322 ... 'top.qux': ['foo']}
320 ... 'top.bar': ['top.baz', 'sys'],
321 ... 'top.baz': ['top.foo'],
322 ... 'top.qux': ['top.foo']}
323 >>> print '\\n'.join(sorted(find_cycles(imports)))
323 >>> print '\\n'.join(sorted(find_cycles(imports)))
324 top.bar -> top.baz -> top.foo -> top.bar
324 top.bar -> top.baz -> top.foo -> top.bar
325 top.foo -> top.qux -> top.foo
325 top.foo -> top.qux -> top.foo
326 """
326 """
327 cycles = set()
327 cycles = set()
328 for mod in sorted(imports.iterkeys()):
328 for mod in sorted(imports.iterkeys()):
329 try:
329 try:
330 checkmod(mod, imports)
330 checkmod(mod, imports)
331 except CircularImport, e:
331 except CircularImport, e:
332 cycle = e.args[0]
332 cycle = e.args[0]
333 cycles.add(" -> ".join(rotatecycle(cycle)))
333 cycles.add(" -> ".join(rotatecycle(cycle)))
334 return cycles
334 return cycles
335
335
336 def _cycle_sortkey(c):
336 def _cycle_sortkey(c):
337 return len(c), c
337 return len(c), c
338
338
339 def main(argv):
339 def main(argv):
340 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
340 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
341 print 'Usage: %s {-|file [file] [file] ...}'
341 print 'Usage: %s {-|file [file] [file] ...}'
342 return 1
342 return 1
343 if argv[1] == '-':
343 if argv[1] == '-':
344 argv = argv[:1]
344 argv = argv[:1]
345 argv.extend(l.rstrip() for l in sys.stdin.readlines())
345 argv.extend(l.rstrip() for l in sys.stdin.readlines())
346 localmods = {}
346 localmods = {}
347 used_imports = {}
347 used_imports = {}
348 any_errors = False
348 any_errors = False
349 for source_path in argv[1:]:
349 for source_path in argv[1:]:
350 modname = dotted_name_of_path(source_path, trimpure=True)
350 modname = dotted_name_of_path(source_path, trimpure=True)
351 localmods[modname] = source_path
351 localmods[modname] = source_path
352 for modname, source_path in sorted(localmods.iteritems()):
352 for modname, source_path in sorted(localmods.iteritems()):
353 f = open(source_path)
353 f = open(source_path)
354 src = f.read()
354 src = f.read()
355 used_imports[modname] = sorted(
355 used_imports[modname] = sorted(
356 imported_modules(src, modname, localmods, ignore_nested=True))
356 imported_modules(src, modname, localmods, ignore_nested=True))
357 for error in verify_stdlib_on_own_line(src):
357 for error in verify_stdlib_on_own_line(src):
358 any_errors = True
358 any_errors = True
359 print source_path, error
359 print source_path, error
360 f.close()
360 f.close()
361 cycles = find_cycles(used_imports)
361 cycles = find_cycles(used_imports)
362 if cycles:
362 if cycles:
363 firstmods = set()
363 firstmods = set()
364 for c in sorted(cycles, key=_cycle_sortkey):
364 for c in sorted(cycles, key=_cycle_sortkey):
365 first = c.split()[0]
365 first = c.split()[0]
366 # As a rough cut, ignore any cycle that starts with the
366 # As a rough cut, ignore any cycle that starts with the
367 # same module as some other cycle. Otherwise we see lots
367 # same module as some other cycle. Otherwise we see lots
368 # of cycles that are effectively duplicates.
368 # of cycles that are effectively duplicates.
369 if first in firstmods:
369 if first in firstmods:
370 continue
370 continue
371 print 'Import cycle:', c
371 print 'Import cycle:', c
372 firstmods.add(first)
372 firstmods.add(first)
373 any_errors = True
373 any_errors = True
374 return not any_errors
374 return not any_errors
375
375
376 if __name__ == '__main__':
376 if __name__ == '__main__':
377 sys.exit(int(main(sys.argv)))
377 sys.exit(int(main(sys.argv)))
General Comments 0
You need to be logged in to leave comments. Login now