check-py3-compat.py
92 lines
| 3.2 KiB
| text/x-python
|
PythonLexer
/ contrib / check-py3-compat.py
Gregory Szorc
|
r46434 | #!/usr/bin/env python3 | ||
Gregory Szorc
|
r27279 | # | ||
# 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. | ||||
import ast | ||||
Gregory Szorc
|
r32280 | import importlib | ||
Gregory Szorc
|
r28584 | import os | ||
Gregory Szorc
|
r27279 | import sys | ||
Gregory Szorc
|
r28584 | import traceback | ||
Gregory Szorc
|
r41696 | import warnings | ||
Gregory Szorc
|
r27279 | |||
Augie Fackler
|
r43346 | |||
Gregory Szorc
|
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: | ||||
Gregory Szorc
|
r41695 | ast.parse(content, filename=f) | ||
Gregory Szorc
|
r28583 | except SyntaxError as e: | ||
print('%s: invalid syntax: %s' % (f, e)) | ||||
return | ||||
Gregory Szorc
|
r28584 | # Try to import the module. | ||
Siddharth Agarwal
|
r32421 | # For now we only support modules in packages because figuring out module | ||
# paths for things not in a package can be confusing. | ||||
Augie Fackler
|
r43346 | if f.startswith( | ||
('hgdemandimport/', 'hgext/', 'mercurial/') | ||||
) and not f.endswith('__init__.py'): | ||||
Gregory Szorc
|
r28584 | assert f.endswith('.py') | ||
Yuya Nishihara
|
r32207 | name = f.replace('/', '.')[:-3] | ||
Yuya Nishihara
|
r30095 | try: | ||
importlib.import_module(name) | ||||
except Exception as e: | ||||
exc_type, exc_value, tb = sys.exc_info() | ||||
# 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 | ||||
Gregory Szorc
|
r28584 | |||
Yuya Nishihara
|
r30095 | if frame.filename: | ||
filename = os.path.basename(frame.filename) | ||||
Augie Fackler
|
r43346 | print( | ||
'%s: error importing: <%s> %s (error at %s:%d)' | ||||
% (f, type(e).__name__, e, filename, frame.lineno) | ||||
) | ||||
Yuya Nishihara
|
r30095 | else: | ||
Augie Fackler
|
r43346 | print( | ||
'%s: error importing module: <%s> %s (line %d)' | ||||
% (f, type(e).__name__, e, frame.lineno) | ||||
) | ||||
Gregory Szorc
|
r28584 | |||
Gregory Szorc
|
r27279 | if __name__ == '__main__': | ||
Gregory Szorc
|
r49727 | # check_compat_py3 will import every filename we specify as long as it | ||
# starts with one of a few prefixes. It does this by converting | ||||
# specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and | ||||
# importing that. When running standalone (not as part of a test), this | ||||
# means we actually import the installed versions, not the files we just | ||||
# specified. When running as test-check-py3-compat.t, we technically | ||||
# would import the correct paths, but it's cleaner to have both cases | ||||
# use the same import logic. | ||||
Raphaël Gomès
|
r50112 | sys.path.insert(0, os.getcwd()) | ||
Gregory Szorc
|
r28583 | |||
Gregory Szorc
|
r27279 | for f in sys.argv[1:]: | ||
Gregory Szorc
|
r41696 | with warnings.catch_warnings(record=True) as warns: | ||
Gregory Szorc
|
r49727 | check_compat_py3(f) | ||
Gregory Szorc
|
r41696 | |||
for w in warns: | ||||
Augie Fackler
|
r43346 | print( | ||
warnings.formatwarning( | ||||
w.message, w.category, w.filename, w.lineno | ||||
).rstrip() | ||||
) | ||||
Gregory Szorc
|
r27279 | |||
sys.exit(0) | ||||