check-py3-compat.py
96 lines
| 3.2 KiB
| text/x-python
|
PythonLexer
/ contrib / check-py3-compat.py
|
r27279 | #!/usr/bin/env python | ||
# | ||||
# check-py3-compat - check Python 3 compatibility of Mercurial files | ||||
# | ||||
# Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
from __future__ import absolute_import, print_function | ||||
import ast | ||||
|
r28584 | import imp | ||
import os | ||||
|
r27279 | import sys | ||
|
r28584 | import traceback | ||
|
r27279 | |||
|
r28583 | def check_compat_py2(f): | ||
"""Check Python 3 compatibility for a file with Python 2""" | ||||
|
r27279 | with open(f, 'rb') as fh: | ||
content = fh.read() | ||||
|
r28475 | root = ast.parse(content) | ||
|
r27279 | |||
|
r27331 | # Ignore empty files. | ||
|
r28475 | if not root.body: | ||
|
r27331 | return | ||
|
r27279 | futures = set() | ||
haveprint = False | ||||
for node in ast.walk(root): | ||||
if isinstance(node, ast.ImportFrom): | ||||
if node.module == '__future__': | ||||
futures |= set(n.name for n in node.names) | ||||
elif isinstance(node, ast.Print): | ||||
haveprint = True | ||||
if 'absolute_import' not in futures: | ||||
print('%s not using absolute_import' % f) | ||||
if haveprint and 'print_function' not in futures: | ||||
print('%s requires print_function' % f) | ||||
|
r28583 | def check_compat_py3(f): | ||
"""Check Python 3 compatibility of a file with Python 3.""" | ||||
with open(f, 'rb') as fh: | ||||
content = fh.read() | ||||
try: | ||||
ast.parse(content) | ||||
except SyntaxError as e: | ||||
print('%s: invalid syntax: %s' % (f, e)) | ||||
return | ||||
|
r28584 | # Try to import the module. | ||
# For now we only support mercurial.* and hgext.* modules because figuring | ||||
# out module paths for things not in a package can be confusing. | ||||
if f.startswith(('hgext/', 'mercurial/')) and not f.endswith('__init__.py'): | ||||
assert f.endswith('.py') | ||||
name = f.replace('/', '.')[:-3] | ||||
with open(f, 'r') as fh: | ||||
try: | ||||
imp.load_module(name, fh, '', ('py', 'r', imp.PY_SOURCE)) | ||||
except Exception as e: | ||||
exc_type, exc_value, tb = sys.exc_info() | ||||
|
r29550 | # We walk the stack and ignore frames from our custom importer, | ||
# import mechanisms, and stdlib modules. This kinda/sorta | ||||
# emulates CPython behavior in import.c while also attempting | ||||
# to pin blame on a Mercurial file. | ||||
for frame in reversed(traceback.extract_tb(tb)): | ||||
if frame.name == '_call_with_frames_removed': | ||||
continue | ||||
if 'importlib' in frame.filename: | ||||
continue | ||||
if 'mercurial/__init__.py' in frame.filename: | ||||
continue | ||||
if frame.filename.startswith(sys.prefix): | ||||
continue | ||||
break | ||||
|
r28584 | |||
if frame.filename: | ||||
filename = os.path.basename(frame.filename) | ||||
print('%s: error importing: <%s> %s (error at %s:%d)' % ( | ||||
f, type(e).__name__, e, filename, frame.lineno)) | ||||
else: | ||||
print('%s: error importing module: <%s> %s (line %d)' % ( | ||||
f, type(e).__name__, e, frame.lineno)) | ||||
|
r27279 | if __name__ == '__main__': | ||
|
r28583 | if sys.version_info[0] == 2: | ||
fn = check_compat_py2 | ||||
else: | ||||
fn = check_compat_py3 | ||||
|
r27279 | for f in sys.argv[1:]: | ||
|
r28583 | fn(f) | ||
|
r27279 | |||
sys.exit(0) | ||||