0331b08c64
- Update YAML version to support Python 3 - Python 3 support for ordereddict backport - Exclude Python3 YAML from version tests. - Vendor six into Spack. - Make Python version-check tests work with Python 3 - Add ability to add version check exceptions with '# nopyqver' line comments.
248 lines
10 KiB
Python
Executable file
248 lines
10 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
#
|
|
# pyqver3.py
|
|
# by Greg Hewgill
|
|
# https://github.com/ghewgill/pyqver
|
|
#
|
|
# This software is provided 'as-is', without any express or implied
|
|
# warranty. In no event will the author be held liable for any damages
|
|
# arising from the use of this software.
|
|
#
|
|
# Permission is granted to anyone to use this software for any purpose,
|
|
# including commercial applications, and to alter it and redistribute it
|
|
# freely, subject to the following restrictions:
|
|
#
|
|
# 1. The origin of this software must not be misrepresented; you must not
|
|
# claim that you wrote the original software. If you use this software
|
|
# in a product, an acknowledgment in the product documentation would be
|
|
# appreciated but is not required.
|
|
# 2. Altered source versions must be plainly marked as such, and must not be
|
|
# misrepresented as being the original software.
|
|
# 3. This notice may not be removed or altered from any source distribution.
|
|
#
|
|
# Copyright (c) 2009-2013 Greg Hewgill http://hewgill.com
|
|
#
|
|
import ast
|
|
import platform
|
|
import sys
|
|
|
|
StandardModules = {
|
|
# skip argparse now that it's in lib/spack/external
|
|
# "argparse": (3, 2),
|
|
"faulthandler": (3, 3),
|
|
"importlib": (3, 1),
|
|
"ipaddress": (3, 3),
|
|
"lzma": (3, 3),
|
|
"tkinter.ttk": (3, 1),
|
|
"unittest.mock": (3, 3),
|
|
"venv": (3, 3),
|
|
}
|
|
|
|
Functions = {
|
|
"bytearray.maketrans": (3, 1),
|
|
"bytes.maketrans": (3, 1),
|
|
"bz2.open": (3, 3),
|
|
"collections.Counter": (3, 1),
|
|
"collections.OrderedDict": (3, 1),
|
|
"crypt.mksalt": (3, 3),
|
|
"email.generator.BytesGenerator": (3, 2),
|
|
"email.message_from_binary_file": (3, 2),
|
|
"email.message_from_bytes": (3, 2),
|
|
"functools.lru_cache": (3, 2),
|
|
"gzip.compress": (3, 2),
|
|
"gzip.decompress": (3, 2),
|
|
"inspect.getclosurevars": (3, 3),
|
|
"inspect.getgeneratorlocals": (3, 3),
|
|
"inspect.getgeneratorstate": (3, 2),
|
|
"itertools.combinations_with_replacement": (3, 1),
|
|
"itertools.compress": (3, 1),
|
|
"logging.config.dictConfig": (3, 2),
|
|
"logging.NullHandler": (3, 1),
|
|
"math.erf": (3, 2),
|
|
"math.erfc": (3, 2),
|
|
"math.expm1": (3, 2),
|
|
"math.gamma": (3, 2),
|
|
"math.isfinite": (3, 2),
|
|
"math.lgamma": (3, 2),
|
|
"math.log2": (3, 3),
|
|
"os.environb": (3, 2),
|
|
"os.fsdecode": (3, 2),
|
|
"os.fsencode": (3, 2),
|
|
"os.fwalk": (3, 3),
|
|
"os.getenvb": (3, 2),
|
|
"os.get_exec_path": (3, 2),
|
|
"os.getgrouplist": (3, 3),
|
|
"os.getpriority": (3, 3),
|
|
"os.getresgid": (3, 2),
|
|
"os.getresuid": (3, 2),
|
|
"os.get_terminal_size": (3, 3),
|
|
"os.getxattr": (3, 3),
|
|
"os.initgroups": (3, 2),
|
|
"os.listxattr": (3, 3),
|
|
"os.lockf": (3, 3),
|
|
"os.pipe2": (3, 3),
|
|
"os.posix_fadvise": (3, 3),
|
|
"os.posix_fallocate": (3, 3),
|
|
"os.pread": (3, 3),
|
|
"os.pwrite": (3, 3),
|
|
"os.readv": (3, 3),
|
|
"os.removexattr": (3, 3),
|
|
"os.replace": (3, 3),
|
|
"os.sched_get_priority_max": (3, 3),
|
|
"os.sched_get_priority_min": (3, 3),
|
|
"os.sched_getaffinity": (3, 3),
|
|
"os.sched_getparam": (3, 3),
|
|
"os.sched_getscheduler": (3, 3),
|
|
"os.sched_rr_get_interval": (3, 3),
|
|
"os.sched_setaffinity": (3, 3),
|
|
"os.sched_setparam": (3, 3),
|
|
"os.sched_setscheduler": (3, 3),
|
|
"os.sched_yield": (3, 3),
|
|
"os.sendfile": (3, 3),
|
|
"os.setpriority": (3, 3),
|
|
"os.setresgid": (3, 2),
|
|
"os.setresuid": (3, 2),
|
|
"os.setxattr": (3, 3),
|
|
"os.sync": (3, 3),
|
|
"os.truncate": (3, 3),
|
|
"os.waitid": (3, 3),
|
|
"os.writev": (3, 3),
|
|
"shutil.chown": (3, 3),
|
|
"shutil.disk_usage": (3, 3),
|
|
"shutil.get_archive_formats": (3, 3),
|
|
"shutil.get_terminal_size": (3, 3),
|
|
"shutil.get_unpack_formats": (3, 3),
|
|
"shutil.make_archive": (3, 3),
|
|
"shutil.register_archive_format": (3, 3),
|
|
"shutil.register_unpack_format": (3, 3),
|
|
"shutil.unpack_archive": (3, 3),
|
|
"shutil.unregister_archive_format": (3, 3),
|
|
"shutil.unregister_unpack_format": (3, 3),
|
|
"shutil.which": (3, 3),
|
|
"signal.pthread_kill": (3, 3),
|
|
"signal.pthread_sigmask": (3, 3),
|
|
"signal.sigpending": (3, 3),
|
|
"signal.sigtimedwait": (3, 3),
|
|
"signal.sigwait": (3, 3),
|
|
"signal.sigwaitinfo": (3, 3),
|
|
"socket.CMSG_LEN": (3, 3),
|
|
"socket.CMSG_SPACE": (3, 3),
|
|
"socket.fromshare": (3, 3),
|
|
"socket.if_indextoname": (3, 3),
|
|
"socket.if_nameindex": (3, 3),
|
|
"socket.if_nametoindex": (3, 3),
|
|
"socket.sethostname": (3, 3),
|
|
"ssl.match_hostname": (3, 2),
|
|
"ssl.RAND_bytes": (3, 3),
|
|
"ssl.RAND_pseudo_bytes": (3, 3),
|
|
"ssl.SSLContext": (3, 2),
|
|
"ssl.SSLEOFError": (3, 3),
|
|
"ssl.SSLSyscallError": (3, 3),
|
|
"ssl.SSLWantReadError": (3, 3),
|
|
"ssl.SSLWantWriteError": (3, 3),
|
|
"ssl.SSLZeroReturnError": (3, 3),
|
|
"stat.filemode": (3, 3),
|
|
"textwrap.indent": (3, 3),
|
|
"threading.get_ident": (3, 3),
|
|
"time.clock_getres": (3, 3),
|
|
"time.clock_gettime": (3, 3),
|
|
"time.clock_settime": (3, 3),
|
|
"time.get_clock_info": (3, 3),
|
|
"time.monotonic": (3, 3),
|
|
"time.perf_counter": (3, 3),
|
|
"time.process_time": (3, 3),
|
|
"types.new_class": (3, 3),
|
|
"types.prepare_class": (3, 3),
|
|
}
|
|
|
|
def uniq(a):
|
|
if len(a) == 0:
|
|
return []
|
|
else:
|
|
return [a[0]] + uniq([x for x in a if x != a[0]])
|
|
|
|
class NodeChecker(ast.NodeVisitor):
|
|
def __init__(self):
|
|
self.vers = dict()
|
|
self.vers[(3,0)] = []
|
|
def add(self, node, ver, msg):
|
|
if ver not in self.vers:
|
|
self.vers[ver] = []
|
|
self.vers[ver].append((node.lineno, msg))
|
|
def visit_Call(self, node):
|
|
def rollup(n):
|
|
if isinstance(n, ast.Name):
|
|
return n.id
|
|
elif isinstance(n, ast.Attribute):
|
|
r = rollup(n.value)
|
|
if r:
|
|
return r + "." + n.attr
|
|
name = rollup(node.func)
|
|
if name:
|
|
v = Functions.get(name)
|
|
if v is not None:
|
|
self.add(node, v, name)
|
|
self.generic_visit(node)
|
|
def visit_Import(self, node):
|
|
for n in node.names:
|
|
v = StandardModules.get(n.name)
|
|
if v is not None:
|
|
self.add(node, v, n.name)
|
|
self.generic_visit(node)
|
|
def visit_ImportFrom(self, node):
|
|
v = StandardModules.get(node.module)
|
|
if v is not None:
|
|
self.add(node, v, node.module)
|
|
for n in node.names:
|
|
name = node.module + "." + n.name
|
|
v = Functions.get(name)
|
|
if v is not None:
|
|
self.add(node, v, name)
|
|
def visit_Raise(self, node):
|
|
if isinstance(node.cause, ast.Name) and node.cause.id == "None":
|
|
self.add(node, (3,3), "raise ... from None")
|
|
def visit_YieldFrom(self, node):
|
|
self.add(node, (3,3), "yield from")
|
|
|
|
def get_versions(source, filename=None):
|
|
"""Return information about the Python versions required for specific features.
|
|
|
|
The return value is a dictionary with keys as a version number as a tuple
|
|
(for example Python 3.1 is (3,1)) and the value are a list of features that
|
|
require the indicated Python version.
|
|
"""
|
|
tree = ast.parse(source, filename=filename)
|
|
checker = NodeChecker()
|
|
checker.visit(tree)
|
|
return checker.vers
|
|
|
|
def v33(source):
|
|
if sys.version_info >= (3, 3):
|
|
return qver(source)
|
|
else:
|
|
print("Not all features tested, run --test with Python 3.3", file=sys.stderr)
|
|
return (3, 3)
|
|
|
|
def qver(source):
|
|
"""Return the minimum Python version required to run a particular bit of code.
|
|
|
|
>>> qver('print("hello world")')
|
|
(3, 0)
|
|
>>> qver("import importlib")
|
|
(3, 1)
|
|
>>> qver("from importlib import x")
|
|
(3, 1)
|
|
>>> qver("import tkinter.ttk")
|
|
(3, 1)
|
|
>>> qver("from collections import Counter")
|
|
(3, 1)
|
|
>>> qver("collections.OrderedDict()")
|
|
(3, 1)
|
|
>>> qver("import functools\\n@functools.lru_cache()\\ndef f(x): x*x")
|
|
(3, 2)
|
|
>>> v33("yield from x")
|
|
(3, 3)
|
|
>>> v33("raise x from None")
|
|
(3, 3)
|
|
"""
|
|
return max(get_versions(source).keys())
|