##// END OF EJS Templates
import-checker: allow importing symbols from pure modules...
Yuya Nishihara -
r32507:95085d74 default
parent child Browse files
Show More
@@ -1,727 +1,734
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 from __future__ import absolute_import, print_function
3 from __future__ import absolute_import, print_function
4
4
5 import ast
5 import ast
6 import collections
6 import collections
7 import os
7 import os
8 import re
8 import re
9 import sys
9 import sys
10
10
11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
12 # to work when run from a virtualenv. The modules were chosen empirically
12 # to work when run from a virtualenv. The modules were chosen empirically
13 # so that the return value matches the return value without virtualenv.
13 # so that the return value matches the return value without virtualenv.
14 if True: # disable lexical sorting checks
14 if True: # disable lexical sorting checks
15 import BaseHTTPServer
15 import BaseHTTPServer
16 import zlib
16 import zlib
17
17
18 # Whitelist of modules that symbols can be directly imported from.
18 # Whitelist of modules that symbols can be directly imported from.
19 allowsymbolimports = (
19 allowsymbolimports = (
20 '__future__',
20 '__future__',
21 'mercurial.hgweb.common',
21 'mercurial.hgweb.common',
22 'mercurial.hgweb.request',
22 'mercurial.hgweb.request',
23 'mercurial.i18n',
23 'mercurial.i18n',
24 'mercurial.node',
24 'mercurial.node',
25 # for cffi modules to re-export pure functions
26 'mercurial.pure.base85',
27 'mercurial.pure.bdiff',
28 'mercurial.pure.diffhelpers',
29 'mercurial.pure.mpatch',
30 'mercurial.pure.osutil',
31 'mercurial.pure.parsers',
25 )
32 )
26
33
27 # Whitelist of symbols that can be directly imported.
34 # Whitelist of symbols that can be directly imported.
28 directsymbols = (
35 directsymbols = (
29 'demandimport',
36 'demandimport',
30 )
37 )
31
38
32 # Modules that must be aliased because they are commonly confused with
39 # Modules that must be aliased because they are commonly confused with
33 # common variables and can create aliasing and readability issues.
40 # common variables and can create aliasing and readability issues.
34 requirealias = {
41 requirealias = {
35 'ui': 'uimod',
42 'ui': 'uimod',
36 }
43 }
37
44
38 def usingabsolute(root):
45 def usingabsolute(root):
39 """Whether absolute imports are being used."""
46 """Whether absolute imports are being used."""
40 if sys.version_info[0] >= 3:
47 if sys.version_info[0] >= 3:
41 return True
48 return True
42
49
43 for node in ast.walk(root):
50 for node in ast.walk(root):
44 if isinstance(node, ast.ImportFrom):
51 if isinstance(node, ast.ImportFrom):
45 if node.module == '__future__':
52 if node.module == '__future__':
46 for n in node.names:
53 for n in node.names:
47 if n.name == 'absolute_import':
54 if n.name == 'absolute_import':
48 return True
55 return True
49
56
50 return False
57 return False
51
58
52 def walklocal(root):
59 def walklocal(root):
53 """Recursively yield all descendant nodes but not in a different scope"""
60 """Recursively yield all descendant nodes but not in a different scope"""
54 todo = collections.deque(ast.iter_child_nodes(root))
61 todo = collections.deque(ast.iter_child_nodes(root))
55 yield root, False
62 yield root, False
56 while todo:
63 while todo:
57 node = todo.popleft()
64 node = todo.popleft()
58 newscope = isinstance(node, ast.FunctionDef)
65 newscope = isinstance(node, ast.FunctionDef)
59 if not newscope:
66 if not newscope:
60 todo.extend(ast.iter_child_nodes(node))
67 todo.extend(ast.iter_child_nodes(node))
61 yield node, newscope
68 yield node, newscope
62
69
63 def dotted_name_of_path(path):
70 def dotted_name_of_path(path):
64 """Given a relative path to a source file, return its dotted module name.
71 """Given a relative path to a source file, return its dotted module name.
65
72
66 >>> dotted_name_of_path('mercurial/error.py')
73 >>> dotted_name_of_path('mercurial/error.py')
67 'mercurial.error'
74 'mercurial.error'
68 >>> dotted_name_of_path('zlibmodule.so')
75 >>> dotted_name_of_path('zlibmodule.so')
69 'zlib'
76 'zlib'
70 """
77 """
71 parts = path.replace(os.sep, '/').split('/')
78 parts = path.replace(os.sep, '/').split('/')
72 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
79 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
73 if parts[-1].endswith('module'):
80 if parts[-1].endswith('module'):
74 parts[-1] = parts[-1][:-6]
81 parts[-1] = parts[-1][:-6]
75 return '.'.join(parts)
82 return '.'.join(parts)
76
83
77 def fromlocalfunc(modulename, localmods):
84 def fromlocalfunc(modulename, localmods):
78 """Get a function to examine which locally defined module the
85 """Get a function to examine which locally defined module the
79 target source imports via a specified name.
86 target source imports via a specified name.
80
87
81 `modulename` is an `dotted_name_of_path()`-ed source file path,
88 `modulename` is an `dotted_name_of_path()`-ed source file path,
82 which may have `.__init__` at the end of it, of the target source.
89 which may have `.__init__` at the end of it, of the target source.
83
90
84 `localmods` is a dict (or set), of which key is an absolute
91 `localmods` is a dict (or set), of which key is an absolute
85 `dotted_name_of_path()`-ed source file path of locally defined (=
92 `dotted_name_of_path()`-ed source file path of locally defined (=
86 Mercurial specific) modules.
93 Mercurial specific) modules.
87
94
88 This function assumes that module names not existing in
95 This function assumes that module names not existing in
89 `localmods` are from the Python standard library.
96 `localmods` are from the Python standard library.
90
97
91 This function returns the function, which takes `name` argument,
98 This function returns the function, which takes `name` argument,
92 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
99 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
93 matches against locally defined module. Otherwise, it returns
100 matches against locally defined module. Otherwise, it returns
94 False.
101 False.
95
102
96 It is assumed that `name` doesn't have `.__init__`.
103 It is assumed that `name` doesn't have `.__init__`.
97
104
98 `absname` is an absolute module name of specified `name`
105 `absname` is an absolute module name of specified `name`
99 (e.g. "hgext.convert"). This can be used to compose prefix for sub
106 (e.g. "hgext.convert"). This can be used to compose prefix for sub
100 modules or so.
107 modules or so.
101
108
102 `dottedpath` is a `dotted_name_of_path()`-ed source file path
109 `dottedpath` is a `dotted_name_of_path()`-ed source file path
103 (e.g. "hgext.convert.__init__") of `name`. This is used to look
110 (e.g. "hgext.convert.__init__") of `name`. This is used to look
104 module up in `localmods` again.
111 module up in `localmods` again.
105
112
106 `hassubmod` is whether it may have sub modules under it (for
113 `hassubmod` is whether it may have sub modules under it (for
107 convenient, even though this is also equivalent to "absname !=
114 convenient, even though this is also equivalent to "absname !=
108 dottednpath")
115 dottednpath")
109
116
110 >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
117 >>> localmods = {'foo.__init__': True, 'foo.foo1': True,
111 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
118 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
112 ... 'baz.__init__': True, 'baz.baz1': True }
119 ... 'baz.__init__': True, 'baz.baz1': True }
113 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
120 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
114 >>> # relative
121 >>> # relative
115 >>> fromlocal('foo1')
122 >>> fromlocal('foo1')
116 ('foo.foo1', 'foo.foo1', False)
123 ('foo.foo1', 'foo.foo1', False)
117 >>> fromlocal('bar')
124 >>> fromlocal('bar')
118 ('foo.bar', 'foo.bar.__init__', True)
125 ('foo.bar', 'foo.bar.__init__', True)
119 >>> fromlocal('bar.bar1')
126 >>> fromlocal('bar.bar1')
120 ('foo.bar.bar1', 'foo.bar.bar1', False)
127 ('foo.bar.bar1', 'foo.bar.bar1', False)
121 >>> # absolute
128 >>> # absolute
122 >>> fromlocal('baz')
129 >>> fromlocal('baz')
123 ('baz', 'baz.__init__', True)
130 ('baz', 'baz.__init__', True)
124 >>> fromlocal('baz.baz1')
131 >>> fromlocal('baz.baz1')
125 ('baz.baz1', 'baz.baz1', False)
132 ('baz.baz1', 'baz.baz1', False)
126 >>> # unknown = maybe standard library
133 >>> # unknown = maybe standard library
127 >>> fromlocal('os')
134 >>> fromlocal('os')
128 False
135 False
129 >>> fromlocal(None, 1)
136 >>> fromlocal(None, 1)
130 ('foo', 'foo.__init__', True)
137 ('foo', 'foo.__init__', True)
131 >>> fromlocal('foo1', 1)
138 >>> fromlocal('foo1', 1)
132 ('foo.foo1', 'foo.foo1', False)
139 ('foo.foo1', 'foo.foo1', False)
133 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
140 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
134 >>> fromlocal2(None, 2)
141 >>> fromlocal2(None, 2)
135 ('foo', 'foo.__init__', True)
142 ('foo', 'foo.__init__', True)
136 >>> fromlocal2('bar2', 1)
143 >>> fromlocal2('bar2', 1)
137 False
144 False
138 >>> fromlocal2('bar', 2)
145 >>> fromlocal2('bar', 2)
139 ('foo.bar', 'foo.bar.__init__', True)
146 ('foo.bar', 'foo.bar.__init__', True)
140 """
147 """
141 prefix = '.'.join(modulename.split('.')[:-1])
148 prefix = '.'.join(modulename.split('.')[:-1])
142 if prefix:
149 if prefix:
143 prefix += '.'
150 prefix += '.'
144 def fromlocal(name, level=0):
151 def fromlocal(name, level=0):
145 # name is false value when relative imports are used.
152 # name is false value when relative imports are used.
146 if not name:
153 if not name:
147 # If relative imports are used, level must not be absolute.
154 # If relative imports are used, level must not be absolute.
148 assert level > 0
155 assert level > 0
149 candidates = ['.'.join(modulename.split('.')[:-level])]
156 candidates = ['.'.join(modulename.split('.')[:-level])]
150 else:
157 else:
151 if not level:
158 if not level:
152 # Check relative name first.
159 # Check relative name first.
153 candidates = [prefix + name, name]
160 candidates = [prefix + name, name]
154 else:
161 else:
155 candidates = ['.'.join(modulename.split('.')[:-level]) +
162 candidates = ['.'.join(modulename.split('.')[:-level]) +
156 '.' + name]
163 '.' + name]
157
164
158 for n in candidates:
165 for n in candidates:
159 if n in localmods:
166 if n in localmods:
160 return (n, n, False)
167 return (n, n, False)
161 dottedpath = n + '.__init__'
168 dottedpath = n + '.__init__'
162 if dottedpath in localmods:
169 if dottedpath in localmods:
163 return (n, dottedpath, True)
170 return (n, dottedpath, True)
164 return False
171 return False
165 return fromlocal
172 return fromlocal
166
173
167 def list_stdlib_modules():
174 def list_stdlib_modules():
168 """List the modules present in the stdlib.
175 """List the modules present in the stdlib.
169
176
170 >>> mods = set(list_stdlib_modules())
177 >>> mods = set(list_stdlib_modules())
171 >>> 'BaseHTTPServer' in mods
178 >>> 'BaseHTTPServer' in mods
172 True
179 True
173
180
174 os.path isn't really a module, so it's missing:
181 os.path isn't really a module, so it's missing:
175
182
176 >>> 'os.path' in mods
183 >>> 'os.path' in mods
177 False
184 False
178
185
179 sys requires special treatment, because it's baked into the
186 sys requires special treatment, because it's baked into the
180 interpreter, but it should still appear:
187 interpreter, but it should still appear:
181
188
182 >>> 'sys' in mods
189 >>> 'sys' in mods
183 True
190 True
184
191
185 >>> 'collections' in mods
192 >>> 'collections' in mods
186 True
193 True
187
194
188 >>> 'cStringIO' in mods
195 >>> 'cStringIO' in mods
189 True
196 True
190
197
191 >>> 'cffi' in mods
198 >>> 'cffi' in mods
192 True
199 True
193 """
200 """
194 for m in sys.builtin_module_names:
201 for m in sys.builtin_module_names:
195 yield m
202 yield m
196 # These modules only exist on windows, but we should always
203 # These modules only exist on windows, but we should always
197 # consider them stdlib.
204 # consider them stdlib.
198 for m in ['msvcrt', '_winreg']:
205 for m in ['msvcrt', '_winreg']:
199 yield m
206 yield m
200 yield 'builtins' # python3 only
207 yield 'builtins' # python3 only
201 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
208 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
202 yield m
209 yield m
203 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
210 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
204 yield m
211 yield m
205 for m in ['cffi']:
212 for m in ['cffi']:
206 yield m
213 yield m
207 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
214 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
208 # We need to supplement the list of prefixes for the search to work
215 # We need to supplement the list of prefixes for the search to work
209 # when run from within a virtualenv.
216 # when run from within a virtualenv.
210 for mod in (BaseHTTPServer, zlib):
217 for mod in (BaseHTTPServer, zlib):
211 try:
218 try:
212 # Not all module objects have a __file__ attribute.
219 # Not all module objects have a __file__ attribute.
213 filename = mod.__file__
220 filename = mod.__file__
214 except AttributeError:
221 except AttributeError:
215 continue
222 continue
216 dirname = os.path.dirname(filename)
223 dirname = os.path.dirname(filename)
217 for prefix in stdlib_prefixes:
224 for prefix in stdlib_prefixes:
218 if dirname.startswith(prefix):
225 if dirname.startswith(prefix):
219 # Then this directory is redundant.
226 # Then this directory is redundant.
220 break
227 break
221 else:
228 else:
222 stdlib_prefixes.add(dirname)
229 stdlib_prefixes.add(dirname)
223 for libpath in sys.path:
230 for libpath in sys.path:
224 # We want to walk everything in sys.path that starts with
231 # We want to walk everything in sys.path that starts with
225 # something in stdlib_prefixes.
232 # something in stdlib_prefixes.
226 if not any(libpath.startswith(p) for p in stdlib_prefixes):
233 if not any(libpath.startswith(p) for p in stdlib_prefixes):
227 continue
234 continue
228 for top, dirs, files in os.walk(libpath):
235 for top, dirs, files in os.walk(libpath):
229 for i, d in reversed(list(enumerate(dirs))):
236 for i, d in reversed(list(enumerate(dirs))):
230 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
237 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
231 or top == libpath and d in ('hgext', 'mercurial')):
238 or top == libpath and d in ('hgext', 'mercurial')):
232 del dirs[i]
239 del dirs[i]
233 for name in files:
240 for name in files:
234 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
241 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
235 continue
242 continue
236 if name.startswith('__init__.py'):
243 if name.startswith('__init__.py'):
237 full_path = top
244 full_path = top
238 else:
245 else:
239 full_path = os.path.join(top, name)
246 full_path = os.path.join(top, name)
240 rel_path = full_path[len(libpath) + 1:]
247 rel_path = full_path[len(libpath) + 1:]
241 mod = dotted_name_of_path(rel_path)
248 mod = dotted_name_of_path(rel_path)
242 yield mod
249 yield mod
243
250
244 stdlib_modules = set(list_stdlib_modules())
251 stdlib_modules = set(list_stdlib_modules())
245
252
246 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
253 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
247 """Given the source of a file as a string, yield the names
254 """Given the source of a file as a string, yield the names
248 imported by that file.
255 imported by that file.
249
256
250 Args:
257 Args:
251 source: The python source to examine as a string.
258 source: The python source to examine as a string.
252 modulename: of specified python source (may have `__init__`)
259 modulename: of specified python source (may have `__init__`)
253 localmods: dict of locally defined module names (may have `__init__`)
260 localmods: dict of locally defined module names (may have `__init__`)
254 ignore_nested: If true, import statements that do not start in
261 ignore_nested: If true, import statements that do not start in
255 column zero will be ignored.
262 column zero will be ignored.
256
263
257 Returns:
264 Returns:
258 A list of absolute module names imported by the given source.
265 A list of absolute module names imported by the given source.
259
266
260 >>> f = 'foo/xxx.py'
267 >>> f = 'foo/xxx.py'
261 >>> modulename = 'foo.xxx'
268 >>> modulename = 'foo.xxx'
262 >>> localmods = {'foo.__init__': True,
269 >>> localmods = {'foo.__init__': True,
263 ... 'foo.foo1': True, 'foo.foo2': True,
270 ... 'foo.foo1': True, 'foo.foo2': True,
264 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
271 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
265 ... 'baz.__init__': True, 'baz.baz1': True }
272 ... 'baz.__init__': True, 'baz.baz1': True }
266 >>> # standard library (= not locally defined ones)
273 >>> # standard library (= not locally defined ones)
267 >>> sorted(imported_modules(
274 >>> sorted(imported_modules(
268 ... 'from stdlib1 import foo, bar; import stdlib2',
275 ... 'from stdlib1 import foo, bar; import stdlib2',
269 ... modulename, f, localmods))
276 ... modulename, f, localmods))
270 []
277 []
271 >>> # relative importing
278 >>> # relative importing
272 >>> sorted(imported_modules(
279 >>> sorted(imported_modules(
273 ... 'import foo1; from bar import bar1',
280 ... 'import foo1; from bar import bar1',
274 ... modulename, f, localmods))
281 ... modulename, f, localmods))
275 ['foo.bar.bar1', 'foo.foo1']
282 ['foo.bar.bar1', 'foo.foo1']
276 >>> sorted(imported_modules(
283 >>> sorted(imported_modules(
277 ... 'from bar.bar1 import name1, name2, name3',
284 ... 'from bar.bar1 import name1, name2, name3',
278 ... modulename, f, localmods))
285 ... modulename, f, localmods))
279 ['foo.bar.bar1']
286 ['foo.bar.bar1']
280 >>> # absolute importing
287 >>> # absolute importing
281 >>> sorted(imported_modules(
288 >>> sorted(imported_modules(
282 ... 'from baz import baz1, name1',
289 ... 'from baz import baz1, name1',
283 ... modulename, f, localmods))
290 ... modulename, f, localmods))
284 ['baz.__init__', 'baz.baz1']
291 ['baz.__init__', 'baz.baz1']
285 >>> # mixed importing, even though it shouldn't be recommended
292 >>> # mixed importing, even though it shouldn't be recommended
286 >>> sorted(imported_modules(
293 >>> sorted(imported_modules(
287 ... 'import stdlib, foo1, baz',
294 ... 'import stdlib, foo1, baz',
288 ... modulename, f, localmods))
295 ... modulename, f, localmods))
289 ['baz.__init__', 'foo.foo1']
296 ['baz.__init__', 'foo.foo1']
290 >>> # ignore_nested
297 >>> # ignore_nested
291 >>> sorted(imported_modules(
298 >>> sorted(imported_modules(
292 ... '''import foo
299 ... '''import foo
293 ... def wat():
300 ... def wat():
294 ... import bar
301 ... import bar
295 ... ''', modulename, f, localmods))
302 ... ''', modulename, f, localmods))
296 ['foo.__init__', 'foo.bar.__init__']
303 ['foo.__init__', 'foo.bar.__init__']
297 >>> sorted(imported_modules(
304 >>> sorted(imported_modules(
298 ... '''import foo
305 ... '''import foo
299 ... def wat():
306 ... def wat():
300 ... import bar
307 ... import bar
301 ... ''', modulename, f, localmods, ignore_nested=True))
308 ... ''', modulename, f, localmods, ignore_nested=True))
302 ['foo.__init__']
309 ['foo.__init__']
303 """
310 """
304 fromlocal = fromlocalfunc(modulename, localmods)
311 fromlocal = fromlocalfunc(modulename, localmods)
305 for node in ast.walk(ast.parse(source, f)):
312 for node in ast.walk(ast.parse(source, f)):
306 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
313 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
307 continue
314 continue
308 if isinstance(node, ast.Import):
315 if isinstance(node, ast.Import):
309 for n in node.names:
316 for n in node.names:
310 found = fromlocal(n.name)
317 found = fromlocal(n.name)
311 if not found:
318 if not found:
312 # this should import standard library
319 # this should import standard library
313 continue
320 continue
314 yield found[1]
321 yield found[1]
315 elif isinstance(node, ast.ImportFrom):
322 elif isinstance(node, ast.ImportFrom):
316 found = fromlocal(node.module, node.level)
323 found = fromlocal(node.module, node.level)
317 if not found:
324 if not found:
318 # this should import standard library
325 # this should import standard library
319 continue
326 continue
320
327
321 absname, dottedpath, hassubmod = found
328 absname, dottedpath, hassubmod = found
322 if not hassubmod:
329 if not hassubmod:
323 # "dottedpath" is not a package; must be imported
330 # "dottedpath" is not a package; must be imported
324 yield dottedpath
331 yield dottedpath
325 # examination of "node.names" should be redundant
332 # examination of "node.names" should be redundant
326 # e.g.: from mercurial.node import nullid, nullrev
333 # e.g.: from mercurial.node import nullid, nullrev
327 continue
334 continue
328
335
329 modnotfound = False
336 modnotfound = False
330 prefix = absname + '.'
337 prefix = absname + '.'
331 for n in node.names:
338 for n in node.names:
332 found = fromlocal(prefix + n.name)
339 found = fromlocal(prefix + n.name)
333 if not found:
340 if not found:
334 # this should be a function or a property of "node.module"
341 # this should be a function or a property of "node.module"
335 modnotfound = True
342 modnotfound = True
336 continue
343 continue
337 yield found[1]
344 yield found[1]
338 if modnotfound:
345 if modnotfound:
339 # "dottedpath" is a package, but imported because of non-module
346 # "dottedpath" is a package, but imported because of non-module
340 # lookup
347 # lookup
341 yield dottedpath
348 yield dottedpath
342
349
343 def verify_import_convention(module, source, localmods):
350 def verify_import_convention(module, source, localmods):
344 """Verify imports match our established coding convention.
351 """Verify imports match our established coding convention.
345
352
346 We have 2 conventions: legacy and modern. The modern convention is in
353 We have 2 conventions: legacy and modern. The modern convention is in
347 effect when using absolute imports.
354 effect when using absolute imports.
348
355
349 The legacy convention only looks for mixed imports. The modern convention
356 The legacy convention only looks for mixed imports. The modern convention
350 is much more thorough.
357 is much more thorough.
351 """
358 """
352 root = ast.parse(source)
359 root = ast.parse(source)
353 absolute = usingabsolute(root)
360 absolute = usingabsolute(root)
354
361
355 if absolute:
362 if absolute:
356 return verify_modern_convention(module, root, localmods)
363 return verify_modern_convention(module, root, localmods)
357 else:
364 else:
358 return verify_stdlib_on_own_line(root)
365 return verify_stdlib_on_own_line(root)
359
366
360 def verify_modern_convention(module, root, localmods, root_col_offset=0):
367 def verify_modern_convention(module, root, localmods, root_col_offset=0):
361 """Verify a file conforms to the modern import convention rules.
368 """Verify a file conforms to the modern import convention rules.
362
369
363 The rules of the modern convention are:
370 The rules of the modern convention are:
364
371
365 * Ordering is stdlib followed by local imports. Each group is lexically
372 * Ordering is stdlib followed by local imports. Each group is lexically
366 sorted.
373 sorted.
367 * Importing multiple modules via "import X, Y" is not allowed: use
374 * Importing multiple modules via "import X, Y" is not allowed: use
368 separate import statements.
375 separate import statements.
369 * Importing multiple modules via "from X import ..." is allowed if using
376 * Importing multiple modules via "from X import ..." is allowed if using
370 parenthesis and one entry per line.
377 parenthesis and one entry per line.
371 * Only 1 relative import statement per import level ("from .", "from ..")
378 * Only 1 relative import statement per import level ("from .", "from ..")
372 is allowed.
379 is allowed.
373 * Relative imports from higher levels must occur before lower levels. e.g.
380 * Relative imports from higher levels must occur before lower levels. e.g.
374 "from .." must be before "from .".
381 "from .." must be before "from .".
375 * Imports from peer packages should use relative import (e.g. do not
382 * Imports from peer packages should use relative import (e.g. do not
376 "import mercurial.foo" from a "mercurial.*" module).
383 "import mercurial.foo" from a "mercurial.*" module).
377 * Symbols can only be imported from specific modules (see
384 * Symbols can only be imported from specific modules (see
378 `allowsymbolimports`). For other modules, first import the module then
385 `allowsymbolimports`). For other modules, first import the module then
379 assign the symbol to a module-level variable. In addition, these imports
386 assign the symbol to a module-level variable. In addition, these imports
380 must be performed before other local imports. This rule only
387 must be performed before other local imports. This rule only
381 applies to import statements outside of any blocks.
388 applies to import statements outside of any blocks.
382 * Relative imports from the standard library are not allowed.
389 * Relative imports from the standard library are not allowed.
383 * Certain modules must be aliased to alternate names to avoid aliasing
390 * Certain modules must be aliased to alternate names to avoid aliasing
384 and readability problems. See `requirealias`.
391 and readability problems. See `requirealias`.
385 """
392 """
386 topmodule = module.split('.')[0]
393 topmodule = module.split('.')[0]
387 fromlocal = fromlocalfunc(module, localmods)
394 fromlocal = fromlocalfunc(module, localmods)
388
395
389 # Whether a local/non-stdlib import has been performed.
396 # Whether a local/non-stdlib import has been performed.
390 seenlocal = None
397 seenlocal = None
391 # Whether a local/non-stdlib, non-symbol import has been seen.
398 # Whether a local/non-stdlib, non-symbol import has been seen.
392 seennonsymbollocal = False
399 seennonsymbollocal = False
393 # The last name to be imported (for sorting).
400 # The last name to be imported (for sorting).
394 lastname = None
401 lastname = None
395 laststdlib = None
402 laststdlib = None
396 # Relative import levels encountered so far.
403 # Relative import levels encountered so far.
397 seenlevels = set()
404 seenlevels = set()
398
405
399 for node, newscope in walklocal(root):
406 for node, newscope in walklocal(root):
400 def msg(fmt, *args):
407 def msg(fmt, *args):
401 return (fmt % args, node.lineno)
408 return (fmt % args, node.lineno)
402 if newscope:
409 if newscope:
403 # Check for local imports in function
410 # Check for local imports in function
404 for r in verify_modern_convention(module, node, localmods,
411 for r in verify_modern_convention(module, node, localmods,
405 node.col_offset + 4):
412 node.col_offset + 4):
406 yield r
413 yield r
407 elif isinstance(node, ast.Import):
414 elif isinstance(node, ast.Import):
408 # Disallow "import foo, bar" and require separate imports
415 # Disallow "import foo, bar" and require separate imports
409 # for each module.
416 # for each module.
410 if len(node.names) > 1:
417 if len(node.names) > 1:
411 yield msg('multiple imported names: %s',
418 yield msg('multiple imported names: %s',
412 ', '.join(n.name for n in node.names))
419 ', '.join(n.name for n in node.names))
413
420
414 name = node.names[0].name
421 name = node.names[0].name
415 asname = node.names[0].asname
422 asname = node.names[0].asname
416
423
417 stdlib = name in stdlib_modules
424 stdlib = name in stdlib_modules
418
425
419 # Ignore sorting rules on imports inside blocks.
426 # Ignore sorting rules on imports inside blocks.
420 if node.col_offset == root_col_offset:
427 if node.col_offset == root_col_offset:
421 if lastname and name < lastname and laststdlib == stdlib:
428 if lastname and name < lastname and laststdlib == stdlib:
422 yield msg('imports not lexically sorted: %s < %s',
429 yield msg('imports not lexically sorted: %s < %s',
423 name, lastname)
430 name, lastname)
424
431
425 lastname = name
432 lastname = name
426 laststdlib = stdlib
433 laststdlib = stdlib
427
434
428 # stdlib imports should be before local imports.
435 # stdlib imports should be before local imports.
429 if stdlib and seenlocal and node.col_offset == root_col_offset:
436 if stdlib and seenlocal and node.col_offset == root_col_offset:
430 yield msg('stdlib import "%s" follows local import: %s',
437 yield msg('stdlib import "%s" follows local import: %s',
431 name, seenlocal)
438 name, seenlocal)
432
439
433 if not stdlib:
440 if not stdlib:
434 seenlocal = name
441 seenlocal = name
435
442
436 # Import of sibling modules should use relative imports.
443 # Import of sibling modules should use relative imports.
437 topname = name.split('.')[0]
444 topname = name.split('.')[0]
438 if topname == topmodule:
445 if topname == topmodule:
439 yield msg('import should be relative: %s', name)
446 yield msg('import should be relative: %s', name)
440
447
441 if name in requirealias and asname != requirealias[name]:
448 if name in requirealias and asname != requirealias[name]:
442 yield msg('%s module must be "as" aliased to %s',
449 yield msg('%s module must be "as" aliased to %s',
443 name, requirealias[name])
450 name, requirealias[name])
444
451
445 elif isinstance(node, ast.ImportFrom):
452 elif isinstance(node, ast.ImportFrom):
446 # Resolve the full imported module name.
453 # Resolve the full imported module name.
447 if node.level > 0:
454 if node.level > 0:
448 fullname = '.'.join(module.split('.')[:-node.level])
455 fullname = '.'.join(module.split('.')[:-node.level])
449 if node.module:
456 if node.module:
450 fullname += '.%s' % node.module
457 fullname += '.%s' % node.module
451 else:
458 else:
452 assert node.module
459 assert node.module
453 fullname = node.module
460 fullname = node.module
454
461
455 topname = fullname.split('.')[0]
462 topname = fullname.split('.')[0]
456 if topname == topmodule:
463 if topname == topmodule:
457 yield msg('import should be relative: %s', fullname)
464 yield msg('import should be relative: %s', fullname)
458
465
459 # __future__ is special since it needs to come first and use
466 # __future__ is special since it needs to come first and use
460 # symbol import.
467 # symbol import.
461 if fullname != '__future__':
468 if fullname != '__future__':
462 if not fullname or fullname in stdlib_modules:
469 if not fullname or fullname in stdlib_modules:
463 yield msg('relative import of stdlib module')
470 yield msg('relative import of stdlib module')
464 else:
471 else:
465 seenlocal = fullname
472 seenlocal = fullname
466
473
467 # Direct symbol import is only allowed from certain modules and
474 # Direct symbol import is only allowed from certain modules and
468 # must occur before non-symbol imports.
475 # must occur before non-symbol imports.
469 found = fromlocal(node.module, node.level)
476 found = fromlocal(node.module, node.level)
470 if found and found[2]: # node.module is a package
477 if found and found[2]: # node.module is a package
471 prefix = found[0] + '.'
478 prefix = found[0] + '.'
472 symbols = (n.name for n in node.names
479 symbols = (n.name for n in node.names
473 if not fromlocal(prefix + n.name))
480 if not fromlocal(prefix + n.name))
474 else:
481 else:
475 symbols = (n.name for n in node.names)
482 symbols = (n.name for n in node.names)
476 symbols = [sym for sym in symbols if sym not in directsymbols]
483 symbols = [sym for sym in symbols if sym not in directsymbols]
477 if node.module and node.col_offset == root_col_offset:
484 if node.module and node.col_offset == root_col_offset:
478 if symbols and fullname not in allowsymbolimports:
485 if symbols and fullname not in allowsymbolimports:
479 yield msg('direct symbol import %s from %s',
486 yield msg('direct symbol import %s from %s',
480 ', '.join(symbols), fullname)
487 ', '.join(symbols), fullname)
481
488
482 if symbols and seennonsymbollocal:
489 if symbols and seennonsymbollocal:
483 yield msg('symbol import follows non-symbol import: %s',
490 yield msg('symbol import follows non-symbol import: %s',
484 fullname)
491 fullname)
485 if not symbols and fullname not in stdlib_modules:
492 if not symbols and fullname not in stdlib_modules:
486 seennonsymbollocal = True
493 seennonsymbollocal = True
487
494
488 if not node.module:
495 if not node.module:
489 assert node.level
496 assert node.level
490
497
491 # Only allow 1 group per level.
498 # Only allow 1 group per level.
492 if (node.level in seenlevels
499 if (node.level in seenlevels
493 and node.col_offset == root_col_offset):
500 and node.col_offset == root_col_offset):
494 yield msg('multiple "from %s import" statements',
501 yield msg('multiple "from %s import" statements',
495 '.' * node.level)
502 '.' * node.level)
496
503
497 # Higher-level groups come before lower-level groups.
504 # Higher-level groups come before lower-level groups.
498 if any(node.level > l for l in seenlevels):
505 if any(node.level > l for l in seenlevels):
499 yield msg('higher-level import should come first: %s',
506 yield msg('higher-level import should come first: %s',
500 fullname)
507 fullname)
501
508
502 seenlevels.add(node.level)
509 seenlevels.add(node.level)
503
510
504 # Entries in "from .X import ( ... )" lists must be lexically
511 # Entries in "from .X import ( ... )" lists must be lexically
505 # sorted.
512 # sorted.
506 lastentryname = None
513 lastentryname = None
507
514
508 for n in node.names:
515 for n in node.names:
509 if lastentryname and n.name < lastentryname:
516 if lastentryname and n.name < lastentryname:
510 yield msg('imports from %s not lexically sorted: %s < %s',
517 yield msg('imports from %s not lexically sorted: %s < %s',
511 fullname, n.name, lastentryname)
518 fullname, n.name, lastentryname)
512
519
513 lastentryname = n.name
520 lastentryname = n.name
514
521
515 if n.name in requirealias and n.asname != requirealias[n.name]:
522 if n.name in requirealias and n.asname != requirealias[n.name]:
516 yield msg('%s from %s must be "as" aliased to %s',
523 yield msg('%s from %s must be "as" aliased to %s',
517 n.name, fullname, requirealias[n.name])
524 n.name, fullname, requirealias[n.name])
518
525
519 def verify_stdlib_on_own_line(root):
526 def verify_stdlib_on_own_line(root):
520 """Given some python source, verify that stdlib imports are done
527 """Given some python source, verify that stdlib imports are done
521 in separate statements from relative local module imports.
528 in separate statements from relative local module imports.
522
529
523 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
530 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
524 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
531 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
525 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
532 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
526 []
533 []
527 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
534 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
528 []
535 []
529 """
536 """
530 for node in ast.walk(root):
537 for node in ast.walk(root):
531 if isinstance(node, ast.Import):
538 if isinstance(node, ast.Import):
532 from_stdlib = {False: [], True: []}
539 from_stdlib = {False: [], True: []}
533 for n in node.names:
540 for n in node.names:
534 from_stdlib[n.name in stdlib_modules].append(n.name)
541 from_stdlib[n.name in stdlib_modules].append(n.name)
535 if from_stdlib[True] and from_stdlib[False]:
542 if from_stdlib[True] and from_stdlib[False]:
536 yield ('mixed imports\n stdlib: %s\n relative: %s' %
543 yield ('mixed imports\n stdlib: %s\n relative: %s' %
537 (', '.join(sorted(from_stdlib[True])),
544 (', '.join(sorted(from_stdlib[True])),
538 ', '.join(sorted(from_stdlib[False]))), node.lineno)
545 ', '.join(sorted(from_stdlib[False]))), node.lineno)
539
546
540 class CircularImport(Exception):
547 class CircularImport(Exception):
541 pass
548 pass
542
549
543 def checkmod(mod, imports):
550 def checkmod(mod, imports):
544 shortest = {}
551 shortest = {}
545 visit = [[mod]]
552 visit = [[mod]]
546 while visit:
553 while visit:
547 path = visit.pop(0)
554 path = visit.pop(0)
548 for i in sorted(imports.get(path[-1], [])):
555 for i in sorted(imports.get(path[-1], [])):
549 if len(path) < shortest.get(i, 1000):
556 if len(path) < shortest.get(i, 1000):
550 shortest[i] = len(path)
557 shortest[i] = len(path)
551 if i in path:
558 if i in path:
552 if i == path[0]:
559 if i == path[0]:
553 raise CircularImport(path)
560 raise CircularImport(path)
554 continue
561 continue
555 visit.append(path + [i])
562 visit.append(path + [i])
556
563
557 def rotatecycle(cycle):
564 def rotatecycle(cycle):
558 """arrange a cycle so that the lexicographically first module listed first
565 """arrange a cycle so that the lexicographically first module listed first
559
566
560 >>> rotatecycle(['foo', 'bar'])
567 >>> rotatecycle(['foo', 'bar'])
561 ['bar', 'foo', 'bar']
568 ['bar', 'foo', 'bar']
562 """
569 """
563 lowest = min(cycle)
570 lowest = min(cycle)
564 idx = cycle.index(lowest)
571 idx = cycle.index(lowest)
565 return cycle[idx:] + cycle[:idx] + [lowest]
572 return cycle[idx:] + cycle[:idx] + [lowest]
566
573
567 def find_cycles(imports):
574 def find_cycles(imports):
568 """Find cycles in an already-loaded import graph.
575 """Find cycles in an already-loaded import graph.
569
576
570 All module names recorded in `imports` should be absolute one.
577 All module names recorded in `imports` should be absolute one.
571
578
572 >>> from __future__ import print_function
579 >>> from __future__ import print_function
573 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
580 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
574 ... 'top.bar': ['top.baz', 'sys'],
581 ... 'top.bar': ['top.baz', 'sys'],
575 ... 'top.baz': ['top.foo'],
582 ... 'top.baz': ['top.foo'],
576 ... 'top.qux': ['top.foo']}
583 ... 'top.qux': ['top.foo']}
577 >>> print('\\n'.join(sorted(find_cycles(imports))))
584 >>> print('\\n'.join(sorted(find_cycles(imports))))
578 top.bar -> top.baz -> top.foo -> top.bar
585 top.bar -> top.baz -> top.foo -> top.bar
579 top.foo -> top.qux -> top.foo
586 top.foo -> top.qux -> top.foo
580 """
587 """
581 cycles = set()
588 cycles = set()
582 for mod in sorted(imports.keys()):
589 for mod in sorted(imports.keys()):
583 try:
590 try:
584 checkmod(mod, imports)
591 checkmod(mod, imports)
585 except CircularImport as e:
592 except CircularImport as e:
586 cycle = e.args[0]
593 cycle = e.args[0]
587 cycles.add(" -> ".join(rotatecycle(cycle)))
594 cycles.add(" -> ".join(rotatecycle(cycle)))
588 return cycles
595 return cycles
589
596
590 def _cycle_sortkey(c):
597 def _cycle_sortkey(c):
591 return len(c), c
598 return len(c), c
592
599
593 def embedded(f, modname, src):
600 def embedded(f, modname, src):
594 """Extract embedded python code
601 """Extract embedded python code
595
602
596 >>> def test(fn, lines):
603 >>> def test(fn, lines):
597 ... for s, m, f, l in embedded(fn, "example", lines):
604 ... for s, m, f, l in embedded(fn, "example", lines):
598 ... print("%s %s %s" % (m, f, l))
605 ... print("%s %s %s" % (m, f, l))
599 ... print(repr(s))
606 ... print(repr(s))
600 >>> lines = [
607 >>> lines = [
601 ... 'comment',
608 ... 'comment',
602 ... ' >>> from __future__ import print_function',
609 ... ' >>> from __future__ import print_function',
603 ... " >>> ' multiline",
610 ... " >>> ' multiline",
604 ... " ... string'",
611 ... " ... string'",
605 ... ' ',
612 ... ' ',
606 ... 'comment',
613 ... 'comment',
607 ... ' $ cat > foo.py <<EOF',
614 ... ' $ cat > foo.py <<EOF',
608 ... ' > from __future__ import print_function',
615 ... ' > from __future__ import print_function',
609 ... ' > EOF',
616 ... ' > EOF',
610 ... ]
617 ... ]
611 >>> test("example.t", lines)
618 >>> test("example.t", lines)
612 example[2] doctest.py 2
619 example[2] doctest.py 2
613 "from __future__ import print_function\\n' multiline\\nstring'\\n"
620 "from __future__ import print_function\\n' multiline\\nstring'\\n"
614 example[7] foo.py 7
621 example[7] foo.py 7
615 'from __future__ import print_function\\n'
622 'from __future__ import print_function\\n'
616 """
623 """
617 inlinepython = 0
624 inlinepython = 0
618 shpython = 0
625 shpython = 0
619 script = []
626 script = []
620 prefix = 6
627 prefix = 6
621 t = ''
628 t = ''
622 n = 0
629 n = 0
623 for l in src:
630 for l in src:
624 n += 1
631 n += 1
625 if not l.endswith(b'\n'):
632 if not l.endswith(b'\n'):
626 l += b'\n'
633 l += b'\n'
627 if l.startswith(b' >>> '): # python inlines
634 if l.startswith(b' >>> '): # python inlines
628 if shpython:
635 if shpython:
629 print("%s:%d: Parse Error" % (f, n))
636 print("%s:%d: Parse Error" % (f, n))
630 if not inlinepython:
637 if not inlinepython:
631 # We've just entered a Python block.
638 # We've just entered a Python block.
632 inlinepython = n
639 inlinepython = n
633 t = 'doctest.py'
640 t = 'doctest.py'
634 script.append(l[prefix:])
641 script.append(l[prefix:])
635 continue
642 continue
636 if l.startswith(b' ... '): # python inlines
643 if l.startswith(b' ... '): # python inlines
637 script.append(l[prefix:])
644 script.append(l[prefix:])
638 continue
645 continue
639 cat = re.search(r"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
646 cat = re.search(r"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
640 if cat:
647 if cat:
641 if inlinepython:
648 if inlinepython:
642 yield ''.join(script), ("%s[%d]" %
649 yield ''.join(script), ("%s[%d]" %
643 (modname, inlinepython)), t, inlinepython
650 (modname, inlinepython)), t, inlinepython
644 script = []
651 script = []
645 inlinepython = 0
652 inlinepython = 0
646 shpython = n
653 shpython = n
647 t = cat.group(1)
654 t = cat.group(1)
648 continue
655 continue
649 if shpython and l.startswith(b' > '): # sh continuation
656 if shpython and l.startswith(b' > '): # sh continuation
650 if l == b' > EOF\n':
657 if l == b' > EOF\n':
651 yield ''.join(script), ("%s[%d]" %
658 yield ''.join(script), ("%s[%d]" %
652 (modname, shpython)), t, shpython
659 (modname, shpython)), t, shpython
653 script = []
660 script = []
654 shpython = 0
661 shpython = 0
655 else:
662 else:
656 script.append(l[4:])
663 script.append(l[4:])
657 continue
664 continue
658 if inlinepython and l == b' \n':
665 if inlinepython and l == b' \n':
659 yield ''.join(script), ("%s[%d]" %
666 yield ''.join(script), ("%s[%d]" %
660 (modname, inlinepython)), t, inlinepython
667 (modname, inlinepython)), t, inlinepython
661 script = []
668 script = []
662 inlinepython = 0
669 inlinepython = 0
663 continue
670 continue
664
671
665 def sources(f, modname):
672 def sources(f, modname):
666 """Yields possibly multiple sources from a filepath
673 """Yields possibly multiple sources from a filepath
667
674
668 input: filepath, modulename
675 input: filepath, modulename
669 yields: script(string), modulename, filepath, linenumber
676 yields: script(string), modulename, filepath, linenumber
670
677
671 For embedded scripts, the modulename and filepath will be different
678 For embedded scripts, the modulename and filepath will be different
672 from the function arguments. linenumber is an offset relative to
679 from the function arguments. linenumber is an offset relative to
673 the input file.
680 the input file.
674 """
681 """
675 py = False
682 py = False
676 if not f.endswith('.t'):
683 if not f.endswith('.t'):
677 with open(f) as src:
684 with open(f) as src:
678 yield src.read(), modname, f, 0
685 yield src.read(), modname, f, 0
679 py = True
686 py = True
680 if py or f.endswith('.t'):
687 if py or f.endswith('.t'):
681 with open(f) as src:
688 with open(f) as src:
682 for script, modname, t, line in embedded(f, modname, src):
689 for script, modname, t, line in embedded(f, modname, src):
683 yield script, modname, t, line
690 yield script, modname, t, line
684
691
685 def main(argv):
692 def main(argv):
686 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
693 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
687 print('Usage: %s {-|file [file] [file] ...}')
694 print('Usage: %s {-|file [file] [file] ...}')
688 return 1
695 return 1
689 if argv[1] == '-':
696 if argv[1] == '-':
690 argv = argv[:1]
697 argv = argv[:1]
691 argv.extend(l.rstrip() for l in sys.stdin.readlines())
698 argv.extend(l.rstrip() for l in sys.stdin.readlines())
692 localmods = {}
699 localmods = {}
693 used_imports = {}
700 used_imports = {}
694 any_errors = False
701 any_errors = False
695 for source_path in argv[1:]:
702 for source_path in argv[1:]:
696 modname = dotted_name_of_path(source_path)
703 modname = dotted_name_of_path(source_path)
697 localmods[modname] = source_path
704 localmods[modname] = source_path
698 for localmodname, source_path in sorted(localmods.items()):
705 for localmodname, source_path in sorted(localmods.items()):
699 for src, modname, name, line in sources(source_path, localmodname):
706 for src, modname, name, line in sources(source_path, localmodname):
700 try:
707 try:
701 used_imports[modname] = sorted(
708 used_imports[modname] = sorted(
702 imported_modules(src, modname, name, localmods,
709 imported_modules(src, modname, name, localmods,
703 ignore_nested=True))
710 ignore_nested=True))
704 for error, lineno in verify_import_convention(modname, src,
711 for error, lineno in verify_import_convention(modname, src,
705 localmods):
712 localmods):
706 any_errors = True
713 any_errors = True
707 print('%s:%d: %s' % (source_path, lineno + line, error))
714 print('%s:%d: %s' % (source_path, lineno + line, error))
708 except SyntaxError as e:
715 except SyntaxError as e:
709 print('%s:%d: SyntaxError: %s' %
716 print('%s:%d: SyntaxError: %s' %
710 (source_path, e.lineno + line, e))
717 (source_path, e.lineno + line, e))
711 cycles = find_cycles(used_imports)
718 cycles = find_cycles(used_imports)
712 if cycles:
719 if cycles:
713 firstmods = set()
720 firstmods = set()
714 for c in sorted(cycles, key=_cycle_sortkey):
721 for c in sorted(cycles, key=_cycle_sortkey):
715 first = c.split()[0]
722 first = c.split()[0]
716 # As a rough cut, ignore any cycle that starts with the
723 # As a rough cut, ignore any cycle that starts with the
717 # same module as some other cycle. Otherwise we see lots
724 # same module as some other cycle. Otherwise we see lots
718 # of cycles that are effectively duplicates.
725 # of cycles that are effectively duplicates.
719 if first in firstmods:
726 if first in firstmods:
720 continue
727 continue
721 print('Import cycle:', c)
728 print('Import cycle:', c)
722 firstmods.add(first)
729 firstmods.add(first)
723 any_errors = True
730 any_errors = True
724 return any_errors != 0
731 return any_errors != 0
725
732
726 if __name__ == '__main__':
733 if __name__ == '__main__':
727 sys.exit(int(main(sys.argv)))
734 sys.exit(int(main(sys.argv)))
General Comments 0
You need to be logged in to leave comments. Login now