tests: check min required python version with vermin (#14289)
This commit removes the `python_version.py` unit test module and the vendored dependencies `pyqver2.py` and `pyqver3.py`. It substitutes them with an equivalent check done using `vermin` that is run as a separate workflow via Github Actions. This allows us to delete 2 vendored dependencies that are unmaintained and substitutes them with a maintained tool. Also, updates the list of vendored dependencies.
This commit is contained in:
parent
1e2c9d960c
commit
d333e14721
30
.github/workflows/minimum_python_versions.yaml
vendored
Normal file
30
.github/workflows/minimum_python_versions.yaml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: Minimum Python Versions
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 2.7
|
||||||
|
- name: Install Python Packages
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install --upgrade vermin
|
||||||
|
- name: Minimum Version (Spack's Core)
|
||||||
|
run: vermin --backport argparse -t=2.6- -t=3.5- -v lib/spack/spack/ lib/spack/llnl/ bin/
|
||||||
|
- name: Minimum Version (Repositories)
|
||||||
|
run: vermin --backport argparse -t=2.6- -t=3.5- -v var/spack/repos
|
8
lib/spack/external/__init__.py
vendored
8
lib/spack/external/__init__.py
vendored
@ -82,14 +82,6 @@
|
|||||||
ini-parsing, io, code, and log facilities.
|
ini-parsing, io, code, and log facilities.
|
||||||
* Version: 1.4.34 (last version supporting Python 2.6)
|
* Version: 1.4.34 (last version supporting Python 2.6)
|
||||||
|
|
||||||
pyqver
|
|
||||||
------
|
|
||||||
|
|
||||||
* Homepage: https://github.com/ghewgill/pyqver
|
|
||||||
* Usage: External script to query required python version of
|
|
||||||
python source code. Used for ensuring 2.6 compatibility.
|
|
||||||
* Version: Unversioned
|
|
||||||
|
|
||||||
pytest
|
pytest
|
||||||
------
|
------
|
||||||
|
|
||||||
|
344
lib/spack/external/pyqver2.py
vendored
344
lib/spack/external/pyqver2.py
vendored
@ -1,344 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# pyqver2.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 compiler
|
|
||||||
import platform
|
|
||||||
import sys
|
|
||||||
|
|
||||||
StandardModules = {
|
|
||||||
"__future__": (2, 1),
|
|
||||||
"abc": (2, 6),
|
|
||||||
# skip argparse now that it's in lib/spack/external
|
|
||||||
# "argparse": (2, 7),
|
|
||||||
"ast": (2, 6),
|
|
||||||
"atexit": (2, 0),
|
|
||||||
"bz2": (2, 3),
|
|
||||||
"cgitb": (2, 2),
|
|
||||||
"collections": (2, 4),
|
|
||||||
"contextlib": (2, 5),
|
|
||||||
"cookielib": (2, 4),
|
|
||||||
"cProfile": (2, 5),
|
|
||||||
"csv": (2, 3),
|
|
||||||
"ctypes": (2, 5),
|
|
||||||
"datetime": (2, 3),
|
|
||||||
"decimal": (2, 4),
|
|
||||||
"difflib": (2, 1),
|
|
||||||
"DocXMLRPCServer": (2, 3),
|
|
||||||
"dummy_thread": (2, 3),
|
|
||||||
"dummy_threading": (2, 3),
|
|
||||||
"email": (2, 2),
|
|
||||||
"fractions": (2, 6),
|
|
||||||
"functools": (2, 5),
|
|
||||||
"future_builtins": (2, 6),
|
|
||||||
"hashlib": (2, 5),
|
|
||||||
"heapq": (2, 3),
|
|
||||||
"hmac": (2, 2),
|
|
||||||
"hotshot": (2, 2),
|
|
||||||
"HTMLParser": (2, 2),
|
|
||||||
"importlib": (2, 7),
|
|
||||||
"inspect": (2, 1),
|
|
||||||
"io": (2, 6),
|
|
||||||
"itertools": (2, 3),
|
|
||||||
"json": (2, 6),
|
|
||||||
"logging": (2, 3),
|
|
||||||
"modulefinder": (2, 3),
|
|
||||||
"msilib": (2, 5),
|
|
||||||
"multiprocessing": (2, 6),
|
|
||||||
"netrc": (1, 5, 2),
|
|
||||||
"numbers": (2, 6),
|
|
||||||
"optparse": (2, 3),
|
|
||||||
"ossaudiodev": (2, 3),
|
|
||||||
"pickletools": (2, 3),
|
|
||||||
"pkgutil": (2, 3),
|
|
||||||
"platform": (2, 3),
|
|
||||||
"pydoc": (2, 1),
|
|
||||||
"runpy": (2, 5),
|
|
||||||
"sets": (2, 3),
|
|
||||||
"shlex": (1, 5, 2),
|
|
||||||
"SimpleXMLRPCServer": (2, 2),
|
|
||||||
"spwd": (2, 5),
|
|
||||||
"sqlite3": (2, 5),
|
|
||||||
"ssl": (2, 6),
|
|
||||||
"stringprep": (2, 3),
|
|
||||||
"subprocess": (2, 4),
|
|
||||||
"sysconfig": (2, 7),
|
|
||||||
"tarfile": (2, 3),
|
|
||||||
"textwrap": (2, 3),
|
|
||||||
"timeit": (2, 3),
|
|
||||||
"unittest": (2, 1),
|
|
||||||
"uuid": (2, 5),
|
|
||||||
"warnings": (2, 1),
|
|
||||||
"weakref": (2, 1),
|
|
||||||
"winsound": (1, 5, 2),
|
|
||||||
"wsgiref": (2, 5),
|
|
||||||
"xml.dom": (2, 0),
|
|
||||||
"xml.dom.minidom": (2, 0),
|
|
||||||
"xml.dom.pulldom": (2, 0),
|
|
||||||
"xml.etree.ElementTree": (2, 5),
|
|
||||||
"xml.parsers.expat":(2, 0),
|
|
||||||
"xml.sax": (2, 0),
|
|
||||||
"xml.sax.handler": (2, 0),
|
|
||||||
"xml.sax.saxutils": (2, 0),
|
|
||||||
"xml.sax.xmlreader":(2, 0),
|
|
||||||
"xmlrpclib": (2, 2),
|
|
||||||
"zipfile": (1, 6),
|
|
||||||
"zipimport": (2, 3),
|
|
||||||
"_ast": (2, 5),
|
|
||||||
"_winreg": (2, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
Functions = {
|
|
||||||
"all": (2, 5),
|
|
||||||
"any": (2, 5),
|
|
||||||
"collections.Counter": (2, 7),
|
|
||||||
"collections.defaultdict": (2, 5),
|
|
||||||
"collections.OrderedDict": (2, 7),
|
|
||||||
"functools.total_ordering": (2, 7),
|
|
||||||
"enumerate": (2, 3),
|
|
||||||
"frozenset": (2, 4),
|
|
||||||
"itertools.compress": (2, 7),
|
|
||||||
"math.erf": (2, 7),
|
|
||||||
"math.erfc": (2, 7),
|
|
||||||
"math.expm1": (2, 7),
|
|
||||||
"math.gamma": (2, 7),
|
|
||||||
"math.lgamma": (2, 7),
|
|
||||||
"memoryview": (2, 7),
|
|
||||||
"next": (2, 6),
|
|
||||||
"os.getresgid": (2, 7),
|
|
||||||
"os.getresuid": (2, 7),
|
|
||||||
"os.initgroups": (2, 7),
|
|
||||||
"os.setresgid": (2, 7),
|
|
||||||
"os.setresuid": (2, 7),
|
|
||||||
"reversed": (2, 4),
|
|
||||||
"set": (2, 4),
|
|
||||||
"subprocess.check_call": (2, 5),
|
|
||||||
"subprocess.check_output": (2, 7),
|
|
||||||
"sum": (2, 3),
|
|
||||||
"symtable.is_declared_global": (2, 7),
|
|
||||||
"weakref.WeakSet": (2, 7),
|
|
||||||
}
|
|
||||||
|
|
||||||
Identifiers = {
|
|
||||||
"False": (2, 2),
|
|
||||||
"True": (2, 2),
|
|
||||||
}
|
|
||||||
|
|
||||||
def uniq(a):
|
|
||||||
if len(a) == 0:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return [a[0]] + uniq([x for x in a if x != a[0]])
|
|
||||||
|
|
||||||
class NodeChecker(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.vers = dict()
|
|
||||||
self.vers[(2,0)] = []
|
|
||||||
def add(self, node, ver, msg):
|
|
||||||
if ver not in self.vers:
|
|
||||||
self.vers[ver] = []
|
|
||||||
self.vers[ver].append((node.lineno, msg))
|
|
||||||
def default(self, node):
|
|
||||||
for child in node.getChildNodes():
|
|
||||||
self.visit(child)
|
|
||||||
def visitCallFunc(self, node):
|
|
||||||
def rollup(n):
|
|
||||||
if isinstance(n, compiler.ast.Name):
|
|
||||||
return n.name
|
|
||||||
elif isinstance(n, compiler.ast.Const):
|
|
||||||
return type(n.value).__name__
|
|
||||||
elif isinstance(n, compiler.ast.Getattr):
|
|
||||||
r = rollup(n.expr)
|
|
||||||
if r:
|
|
||||||
return r + "." + n.attrname
|
|
||||||
name = rollup(node.node)
|
|
||||||
if name:
|
|
||||||
# Special handling for empty format strings, which aren't
|
|
||||||
# allowed in Python 2.6
|
|
||||||
if name in ('unicode.format', 'str.format'):
|
|
||||||
n = node.node
|
|
||||||
if isinstance(n, compiler.ast.Getattr):
|
|
||||||
n = n.expr
|
|
||||||
if isinstance(n, compiler.ast.Const):
|
|
||||||
if '{}' in n.value:
|
|
||||||
self.add(node, (2,7), name + ' with {} format string')
|
|
||||||
|
|
||||||
v = Functions.get(name)
|
|
||||||
if v is not None:
|
|
||||||
self.add(node, v, name)
|
|
||||||
self.default(node)
|
|
||||||
def visitClass(self, node):
|
|
||||||
if node.bases:
|
|
||||||
self.add(node, (2,2), "new-style class")
|
|
||||||
if node.decorators:
|
|
||||||
self.add(node, (2,6), "class decorator")
|
|
||||||
self.default(node)
|
|
||||||
def visitDictComp(self, node):
|
|
||||||
self.add(node, (2,7), "dictionary comprehension")
|
|
||||||
self.default(node)
|
|
||||||
def visitFloorDiv(self, node):
|
|
||||||
self.add(node, (2,2), "// operator")
|
|
||||||
self.default(node)
|
|
||||||
def visitFrom(self, node):
|
|
||||||
v = StandardModules.get(node.modname)
|
|
||||||
if v is not None:
|
|
||||||
self.add(node, v, node.modname)
|
|
||||||
for n in node.names:
|
|
||||||
name = node.modname + "." + n[0]
|
|
||||||
v = Functions.get(name)
|
|
||||||
if v is not None:
|
|
||||||
self.add(node, v, name)
|
|
||||||
def visitFunction(self, node):
|
|
||||||
if node.decorators:
|
|
||||||
self.add(node, (2,4), "function decorator")
|
|
||||||
self.default(node)
|
|
||||||
def visitGenExpr(self, node):
|
|
||||||
self.add(node, (2,4), "generator expression")
|
|
||||||
self.default(node)
|
|
||||||
def visitGetattr(self, node):
|
|
||||||
if (isinstance(node.expr, compiler.ast.Const)
|
|
||||||
and isinstance(node.expr.value, str)
|
|
||||||
and node.attrname == "format"):
|
|
||||||
self.add(node, (2,6), "string literal .format()")
|
|
||||||
self.default(node)
|
|
||||||
def visitIfExp(self, node):
|
|
||||||
self.add(node, (2,5), "inline if expression")
|
|
||||||
self.default(node)
|
|
||||||
def visitImport(self, node):
|
|
||||||
for n in node.names:
|
|
||||||
v = StandardModules.get(n[0])
|
|
||||||
if v is not None:
|
|
||||||
self.add(node, v, n[0])
|
|
||||||
self.default(node)
|
|
||||||
def visitName(self, node):
|
|
||||||
v = Identifiers.get(node.name)
|
|
||||||
if v is not None:
|
|
||||||
self.add(node, v, node.name)
|
|
||||||
self.default(node)
|
|
||||||
def visitSet(self, node):
|
|
||||||
self.add(node, (2,7), "set literal")
|
|
||||||
self.default(node)
|
|
||||||
def visitSetComp(self, node):
|
|
||||||
self.add(node, (2,7), "set comprehension")
|
|
||||||
self.default(node)
|
|
||||||
def visitTryFinally(self, node):
|
|
||||||
# try/finally with a suite generates a Stmt node as the body,
|
|
||||||
# but try/except/finally generates a TryExcept as the body
|
|
||||||
if isinstance(node.body, compiler.ast.TryExcept):
|
|
||||||
self.add(node, (2,5), "try/except/finally")
|
|
||||||
self.default(node)
|
|
||||||
def visitWith(self, node):
|
|
||||||
if isinstance(node.body, compiler.ast.With):
|
|
||||||
self.add(node, (2,7), "with statement with multiple contexts")
|
|
||||||
else:
|
|
||||||
self.add(node, (2,5), "with statement")
|
|
||||||
self.default(node)
|
|
||||||
def visitYield(self, node):
|
|
||||||
self.add(node, (2,2), "yield expression")
|
|
||||||
self.default(node)
|
|
||||||
|
|
||||||
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 2.6 is (2,6)) and the value are a list of features that
|
|
||||||
require the indicated Python version.
|
|
||||||
"""
|
|
||||||
tree = compiler.parse(source)
|
|
||||||
checker = compiler.walk(tree, NodeChecker())
|
|
||||||
return checker.vers
|
|
||||||
|
|
||||||
def v27(source):
|
|
||||||
if sys.version_info >= (2, 7):
|
|
||||||
return qver(source)
|
|
||||||
else:
|
|
||||||
print >>sys.stderr, "Not all features tested, run --test with Python 2.7"
|
|
||||||
return (2, 7)
|
|
||||||
|
|
||||||
def qver(source):
|
|
||||||
"""Return the minimum Python version required to run a particular bit of code.
|
|
||||||
|
|
||||||
>>> qver('print "hello world"')
|
|
||||||
(2, 0)
|
|
||||||
>>> qver('class test(object): pass')
|
|
||||||
(2, 2)
|
|
||||||
>>> qver('yield 1')
|
|
||||||
(2, 2)
|
|
||||||
>>> qver('a // b')
|
|
||||||
(2, 2)
|
|
||||||
>>> qver('True')
|
|
||||||
(2, 2)
|
|
||||||
>>> qver('enumerate(a)')
|
|
||||||
(2, 3)
|
|
||||||
>>> qver('total = sum')
|
|
||||||
(2, 0)
|
|
||||||
>>> qver('sum(a)')
|
|
||||||
(2, 3)
|
|
||||||
>>> qver('(x*x for x in range(5))')
|
|
||||||
(2, 4)
|
|
||||||
>>> qver('class C:\\n @classmethod\\n def m(): pass')
|
|
||||||
(2, 4)
|
|
||||||
>>> qver('y if x else z')
|
|
||||||
(2, 5)
|
|
||||||
>>> qver('import hashlib')
|
|
||||||
(2, 5)
|
|
||||||
>>> qver('from hashlib import md5')
|
|
||||||
(2, 5)
|
|
||||||
>>> qver('import xml.etree.ElementTree')
|
|
||||||
(2, 5)
|
|
||||||
>>> qver('try:\\n try: pass;\\n except: pass;\\nfinally: pass')
|
|
||||||
(2, 0)
|
|
||||||
>>> qver('try: pass;\\nexcept: pass;\\nfinally: pass')
|
|
||||||
(2, 5)
|
|
||||||
>>> qver('from __future__ import with_statement\\nwith x: pass')
|
|
||||||
(2, 5)
|
|
||||||
>>> qver('collections.defaultdict(list)')
|
|
||||||
(2, 5)
|
|
||||||
>>> qver('from collections import defaultdict')
|
|
||||||
(2, 5)
|
|
||||||
>>> qver('"{0}".format(0)')
|
|
||||||
(2, 6)
|
|
||||||
>>> qver('memoryview(x)')
|
|
||||||
(2, 7)
|
|
||||||
>>> v27('{1, 2, 3}')
|
|
||||||
(2, 7)
|
|
||||||
>>> v27('{x for x in s}')
|
|
||||||
(2, 7)
|
|
||||||
>>> v27('{x: y for x in s}')
|
|
||||||
(2, 7)
|
|
||||||
>>> qver('from __future__ import with_statement\\nwith x:\\n with y: pass')
|
|
||||||
(2, 5)
|
|
||||||
>>> v27('from __future__ import with_statement\\nwith x, y: pass')
|
|
||||||
(2, 7)
|
|
||||||
>>> qver('@decorator\\ndef f(): pass')
|
|
||||||
(2, 4)
|
|
||||||
>>> qver('@decorator\\nclass test:\\n pass')
|
|
||||||
(2, 6)
|
|
||||||
|
|
||||||
#>>> qver('0o0')
|
|
||||||
#(2, 6)
|
|
||||||
#>>> qver('@foo\\nclass C: pass')
|
|
||||||
#(2, 6)
|
|
||||||
"""
|
|
||||||
return max(get_versions(source).keys())
|
|
248
lib/spack/external/pyqver3.py
vendored
248
lib/spack/external/pyqver3.py
vendored
@ -1,248 +0,0 @@
|
|||||||
#!/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())
|
|
@ -8,7 +8,7 @@
|
|||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence # novm
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from collections import Sequence
|
from collections import Sequence
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from collections.abc import MutableMapping
|
from collections.abc import MutableMapping # novm
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from collections import MutableMapping
|
from collections import MutableMapping
|
||||||
|
|
||||||
|
@ -612,12 +612,14 @@ def load_module_from_file(module_name, module_path):
|
|||||||
"""
|
"""
|
||||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 5:
|
if sys.version_info[0] == 3 and sys.version_info[1] >= 5:
|
||||||
import importlib.util
|
import importlib.util
|
||||||
spec = importlib.util.spec_from_file_location(module_name, module_path)
|
spec = importlib.util.spec_from_file_location( # novm
|
||||||
module = importlib.util.module_from_spec(spec)
|
module_name, module_path)
|
||||||
|
module = importlib.util.module_from_spec(spec) # novm
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
elif sys.version_info[0] == 3 and sys.version_info[1] < 5:
|
elif sys.version_info[0] == 3 and sys.version_info[1] < 5:
|
||||||
import importlib.machinery
|
import importlib.machinery
|
||||||
loader = importlib.machinery.SourceFileLoader(module_name, module_path)
|
loader = importlib.machinery.SourceFileLoader( # novm
|
||||||
|
module_name, module_path)
|
||||||
module = loader.load_module()
|
module = loader.load_module()
|
||||||
elif sys.version_info[0] == 2:
|
elif sys.version_info[0] == 2:
|
||||||
import imp
|
import imp
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
from ordereddict_backport import OrderedDict
|
from ordereddict_backport import OrderedDict
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping # novm
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from collections import Mapping
|
from collections import Mapping
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
from six import string_types, add_metaclass
|
from six import string_types, add_metaclass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping # novm
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from collections import Mapping
|
from collections import Mapping
|
||||||
|
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
|
|
||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
|
||||||
|
|
||||||
"""Check that Spack complies with minimum supported python versions.
|
|
||||||
|
|
||||||
We ensure that all Spack files work with Python2 >= 2.6 and Python3 >= 3.0.
|
|
||||||
|
|
||||||
We'd like to drop 2.6 support at some point, but there are still many HPC
|
|
||||||
systems that ship with RHEL6/CentOS 6, which have Python 2.6 as the
|
|
||||||
default version. Once those go away, we can likely drop 2.6 and increase
|
|
||||||
the minimum supported Python 3 version, as well.
|
|
||||||
"""
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
|
||||||
|
|
||||||
import spack.paths
|
|
||||||
from spack.paths import lib_path as spack_lib_path
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# This test uses pyqver, by Greg Hewgill, which is a dual-source module.
|
|
||||||
# That means we need to do different checks depending on whether we're
|
|
||||||
# running Python 2 or Python 3.
|
|
||||||
#
|
|
||||||
if sys.version_info[0] < 3:
|
|
||||||
import pyqver2 as pyqver
|
|
||||||
spack_min_supported = (2, 6)
|
|
||||||
|
|
||||||
# Exclude Python 3 versions of dual-source modules when using Python 2
|
|
||||||
exclude_paths = [
|
|
||||||
# Jinja 2 has some 'async def' functions that are not treated correctly
|
|
||||||
# by pyqver.py
|
|
||||||
os.path.join(spack_lib_path, 'external', 'jinja2', 'asyncfilters.py'),
|
|
||||||
os.path.join(spack_lib_path, 'external', 'jinja2', 'asyncsupport.py'),
|
|
||||||
os.path.join(spack_lib_path, 'external', 'yaml', 'lib3'),
|
|
||||||
os.path.join(spack_lib_path, 'external', 'pyqver3.py'),
|
|
||||||
# Uses importlib
|
|
||||||
os.path.join(spack_lib_path, 'spack', 'test', 'schema.py')
|
|
||||||
]
|
|
||||||
|
|
||||||
else:
|
|
||||||
import pyqver3 as pyqver
|
|
||||||
spack_min_supported = (3, 0)
|
|
||||||
|
|
||||||
# Exclude Python 2 versions of dual-source modules when using Python 3
|
|
||||||
exclude_paths = [
|
|
||||||
# Jinja 2 has some 'async def' functions that are not treated correctly
|
|
||||||
# by pyqver.py
|
|
||||||
os.path.join(spack_lib_path, 'external', 'jinja2', 'asyncfilters.py'),
|
|
||||||
os.path.join(spack_lib_path, 'external', 'jinja2', 'asyncsupport.py'),
|
|
||||||
os.path.join(spack_lib_path, 'external', 'yaml', 'lib'),
|
|
||||||
os.path.join(spack_lib_path, 'external', 'pyqver2.py'),
|
|
||||||
# Uses importlib
|
|
||||||
os.path.join(spack_lib_path, 'spack', 'test', 'schema.py')
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def pyfiles(search_paths, exclude=()):
|
|
||||||
"""Generator that yields all the python files in the search paths.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
search_paths (list of str): list of paths to search for python files
|
|
||||||
exclude (list of str): file paths to exclude from search
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
python files in the search path.
|
|
||||||
"""
|
|
||||||
# first file is the spack script.
|
|
||||||
yield spack.paths.spack_script
|
|
||||||
|
|
||||||
# Iterate through the whole spack source tree.
|
|
||||||
for path in search_paths:
|
|
||||||
for root, dirnames, filenames in os.walk(path):
|
|
||||||
for filename in filenames:
|
|
||||||
realpath = os.path.realpath(os.path.join(root, filename))
|
|
||||||
if any(realpath.startswith(p) for p in exclude):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if re.match(r'^[^.#].*\.py$', filename):
|
|
||||||
yield os.path.join(root, filename)
|
|
||||||
|
|
||||||
|
|
||||||
def check_python_versions(files):
|
|
||||||
"""Check that a set of Python files works with supported Ptyhon versions"""
|
|
||||||
# This is a dict dict mapping:
|
|
||||||
# version -> filename -> reasons
|
|
||||||
#
|
|
||||||
# Reasons are tuples of (lineno, string), where the string is the
|
|
||||||
# cause for a version incompatibility.
|
|
||||||
all_issues = {}
|
|
||||||
|
|
||||||
# Parse files and run pyqver on each file.
|
|
||||||
for path in files:
|
|
||||||
with open(path) as pyfile:
|
|
||||||
full_text = pyfile.read()
|
|
||||||
versions = pyqver.get_versions(full_text, path)
|
|
||||||
|
|
||||||
for ver, reasons in versions.items():
|
|
||||||
if ver <= spack_min_supported:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Record issues. Mark exceptions with '# nopyqver' comment
|
|
||||||
for lineno, cause in reasons:
|
|
||||||
lines = full_text.split('\n')
|
|
||||||
if not re.search(r'#\s*nopyqver\s*$', lines[lineno - 1]):
|
|
||||||
all_issues.setdefault(ver, {})[path] = reasons
|
|
||||||
|
|
||||||
# Print a message if there are are issues
|
|
||||||
if all_issues:
|
|
||||||
tty.msg("Spack must remain compatible with Python version %d.%d"
|
|
||||||
% spack_min_supported)
|
|
||||||
|
|
||||||
# Print out a table showing which files/linenos require which
|
|
||||||
# python version, and a string describing why.
|
|
||||||
for v in sorted(all_issues.keys(), reverse=True):
|
|
||||||
messages = []
|
|
||||||
for path in sorted(all_issues[v].keys()):
|
|
||||||
short_path = path
|
|
||||||
if path.startswith(spack.paths.prefix):
|
|
||||||
short_path = path[len(spack.paths.prefix):]
|
|
||||||
|
|
||||||
reasons = [r for r in set(all_issues[v][path]) if r]
|
|
||||||
for lineno, cause in reasons:
|
|
||||||
file_line = "%s:%s" % (short_path.lstrip('/'), lineno)
|
|
||||||
messages.append((file_line, cause))
|
|
||||||
|
|
||||||
print()
|
|
||||||
tty.msg("These files require version %d.%d:" % v)
|
|
||||||
maxlen = max(len(f) for f, prob in messages)
|
|
||||||
fmt = "%%-%ds%%s" % (maxlen + 3)
|
|
||||||
print(fmt % ('File', 'Reason'))
|
|
||||||
print(fmt % ('-' * (maxlen), '-' * 20))
|
|
||||||
for msg in messages:
|
|
||||||
print(fmt % msg)
|
|
||||||
|
|
||||||
# Fail this test if there were issues.
|
|
||||||
assert not all_issues
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.maybeslow
|
|
||||||
def test_core_module_compatibility():
|
|
||||||
"""Test that all core spack modules work with supported Python versions."""
|
|
||||||
check_python_versions(
|
|
||||||
pyfiles([spack_lib_path], exclude=exclude_paths))
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.maybeslow
|
|
||||||
def test_package_module_compatibility():
|
|
||||||
"""Test that all spack packages work with supported Python versions."""
|
|
||||||
check_python_versions(pyfiles([spack.paths.packages_path]))
|
|
@ -107,7 +107,7 @@ def test_module_suffixes(module_suffixes_schema):
|
|||||||
'repos'
|
'repos'
|
||||||
])
|
])
|
||||||
def test_schema_validation(meta_schema, config_name):
|
def test_schema_validation(meta_schema, config_name):
|
||||||
import importlib
|
import importlib # novm
|
||||||
module_name = 'spack.schema.{0}'.format(config_name)
|
module_name = 'spack.schema.{0}'.format(config_name)
|
||||||
module = importlib.import_module(module_name)
|
module = importlib.import_module(module_name)
|
||||||
schema = getattr(module, 'schema')
|
schema = getattr(module, 'schema')
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
``importlib`` is only fully implemented in Python 3.
|
``importlib`` is only fully implemented in Python 3.
|
||||||
"""
|
"""
|
||||||
from importlib.machinery import SourceFileLoader
|
from importlib.machinery import SourceFileLoader # novm
|
||||||
|
|
||||||
|
|
||||||
class PrependFileLoader(SourceFileLoader):
|
class PrependFileLoader(SourceFileLoader):
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
import spack.error as error
|
import spack.error as error
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence # novm
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from collections import Sequence
|
from collections import Sequence
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user