13 import importlib |
13 import importlib |
14 import os |
14 import os |
15 import sys |
15 import sys |
16 import traceback |
16 import traceback |
17 import warnings |
17 import warnings |
18 |
|
19 |
|
20 def check_compat_py2(f): |
|
21 """Check Python 3 compatibility for a file with Python 2""" |
|
22 with open(f, 'rb') as fh: |
|
23 content = fh.read() |
|
24 root = ast.parse(content) |
|
25 |
|
26 # Ignore empty files. |
|
27 if not root.body: |
|
28 return |
|
29 |
|
30 futures = set() |
|
31 haveprint = False |
|
32 for node in ast.walk(root): |
|
33 if isinstance(node, ast.ImportFrom): |
|
34 if node.module == '__future__': |
|
35 futures |= {n.name for n in node.names} |
|
36 elif isinstance(node, ast.Print): |
|
37 haveprint = True |
|
38 |
|
39 if 'absolute_import' not in futures: |
|
40 print('%s not using absolute_import' % f) |
|
41 if haveprint and 'print_function' not in futures: |
|
42 print('%s requires print_function' % f) |
|
43 |
18 |
44 |
19 |
45 def check_compat_py3(f): |
20 def check_compat_py3(f): |
46 """Check Python 3 compatibility of a file with Python 3.""" |
21 """Check Python 3 compatibility of a file with Python 3.""" |
47 with open(f, 'rb') as fh: |
22 with open(f, 'rb') as fh: |
92 % (f, type(e).__name__, e, frame.lineno) |
67 % (f, type(e).__name__, e, frame.lineno) |
93 ) |
68 ) |
94 |
69 |
95 |
70 |
96 if __name__ == '__main__': |
71 if __name__ == '__main__': |
97 if sys.version_info[0] == 2: |
72 # check_compat_py3 will import every filename we specify as long as it |
98 fn = check_compat_py2 |
73 # starts with one of a few prefixes. It does this by converting |
99 else: |
74 # specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and |
100 # check_compat_py3 will import every filename we specify as long as it |
75 # importing that. When running standalone (not as part of a test), this |
101 # starts with one of a few prefixes. It does this by converting |
76 # means we actually import the installed versions, not the files we just |
102 # specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and |
77 # specified. When running as test-check-py3-compat.t, we technically |
103 # importing that. When running standalone (not as part of a test), this |
78 # would import the correct paths, but it's cleaner to have both cases |
104 # means we actually import the installed versions, not the files we just |
79 # use the same import logic. |
105 # specified. When running as test-check-py3-compat.t, we technically |
80 sys.path.insert(0, '.') |
106 # would import the correct paths, but it's cleaner to have both cases |
|
107 # use the same import logic. |
|
108 sys.path.insert(0, '.') |
|
109 fn = check_compat_py3 |
|
110 |
81 |
111 for f in sys.argv[1:]: |
82 for f in sys.argv[1:]: |
112 with warnings.catch_warnings(record=True) as warns: |
83 with warnings.catch_warnings(record=True) as warns: |
113 fn(f) |
84 check_compat_py3(f) |
114 |
85 |
115 for w in warns: |
86 for w in warns: |
116 print( |
87 print( |
117 warnings.formatwarning( |
88 warnings.formatwarning( |
118 w.message, w.category, w.filename, w.lineno |
89 w.message, w.category, w.filename, w.lineno |