diff --git a/.github/workflows/minimum_python_versions.yaml b/.github/workflows/minimum_python_versions.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f0a5736849addd5ca7d698f4ffc731a8ae992509
--- /dev/null
+++ b/.github/workflows/minimum_python_versions.yaml
@@ -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
diff --git a/lib/spack/external/__init__.py b/lib/spack/external/__init__.py
index 1001ff5ffff3b6eec4a567a6ce8b26af061989dc..3550a4235f002f4f5980de07fecc5efe249737fe 100644
--- a/lib/spack/external/__init__.py
+++ b/lib/spack/external/__init__.py
@@ -82,14 +82,6 @@
   ini-parsing, io, code, and log facilities.
 * 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
 ------
 
diff --git a/lib/spack/external/pyqver2.py b/lib/spack/external/pyqver2.py
deleted file mode 100755
index 07b191425b25816576c96de7198bcd1f50d2d9d7..0000000000000000000000000000000000000000
--- a/lib/spack/external/pyqver2.py
+++ /dev/null
@@ -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())
diff --git a/lib/spack/external/pyqver3.py b/lib/spack/external/pyqver3.py
deleted file mode 100755
index b63576a0647dc09457d2fc8f451114639358295f..0000000000000000000000000000000000000000
--- a/lib/spack/external/pyqver3.py
+++ /dev/null
@@ -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())
diff --git a/lib/spack/llnl/util/cpu/microarchitecture.py b/lib/spack/llnl/util/cpu/microarchitecture.py
index 3d1590376a0db77c0b18d2957b1d8714e2952324..759bff5058d109b3306e80802ba215ed03f5a8d0 100644
--- a/lib/spack/llnl/util/cpu/microarchitecture.py
+++ b/lib/spack/llnl/util/cpu/microarchitecture.py
@@ -8,7 +8,7 @@
 import warnings
 
 try:
-    from collections.abc import Sequence
+    from collections.abc import Sequence  # novm
 except ImportError:
     from collections import Sequence
 
diff --git a/lib/spack/llnl/util/cpu/schema.py b/lib/spack/llnl/util/cpu/schema.py
index cc15cb64bac85f06d34bb92a33e06e1282542ec9..14cdf4e00f0106f5a466ae07015d342929dee956 100644
--- a/lib/spack/llnl/util/cpu/schema.py
+++ b/lib/spack/llnl/util/cpu/schema.py
@@ -6,7 +6,7 @@
 import os.path
 
 try:
-    from collections.abc import MutableMapping
+    from collections.abc import MutableMapping  # novm
 except ImportError:
     from collections import MutableMapping
 
diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py
index 11820a7f58ff1e28cbe25e4275fd5676857729cc..bdb551dcad64398a99fbd398dbab243e7c1f355a 100644
--- a/lib/spack/llnl/util/lang.py
+++ b/lib/spack/llnl/util/lang.py
@@ -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:
         import importlib.util
-        spec = importlib.util.spec_from_file_location(module_name, module_path)
-        module = importlib.util.module_from_spec(spec)
+        spec = importlib.util.spec_from_file_location(  # novm
+            module_name, module_path)
+        module = importlib.util.module_from_spec(spec)  # novm
         spec.loader.exec_module(module)
     elif sys.version_info[0] == 3 and sys.version_info[1] < 5:
         import importlib.machinery
-        loader = importlib.machinery.SourceFileLoader(module_name, module_path)
+        loader = importlib.machinery.SourceFileLoader(  # novm
+            module_name, module_path)
         module = loader.load_module()
     elif sys.version_info[0] == 2:
         import imp
diff --git a/lib/spack/spack/mirror.py b/lib/spack/spack/mirror.py
index ceb53801ceb0003a2640949f8447808f33b8e8a8..650b7bd4094ee748f831002fbcb270c9aa9b1c59 100644
--- a/lib/spack/spack/mirror.py
+++ b/lib/spack/spack/mirror.py
@@ -24,7 +24,7 @@
 from ordereddict_backport import OrderedDict
 
 try:
-    from collections.abc import Mapping
+    from collections.abc import Mapping  # novm
 except ImportError:
     from collections import Mapping
 
diff --git a/lib/spack/spack/repo.py b/lib/spack/spack/repo.py
index 3ea196dd4d7c5e580439190579d206b8216a9b15..8b124a4a1805dd5826580d09bf8692ca9980b757 100644
--- a/lib/spack/spack/repo.py
+++ b/lib/spack/spack/repo.py
@@ -20,7 +20,7 @@
 from six import string_types, add_metaclass
 
 try:
-    from collections.abc import Mapping
+    from collections.abc import Mapping  # novm
 except ImportError:
     from collections import Mapping
 
diff --git a/lib/spack/spack/test/python_version.py b/lib/spack/spack/test/python_version.py
deleted file mode 100644
index 6875aa4655a933cb5e734938352434302dbcf147..0000000000000000000000000000000000000000
--- a/lib/spack/spack/test/python_version.py
+++ /dev/null
@@ -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]))
diff --git a/lib/spack/spack/test/schema.py b/lib/spack/spack/test/schema.py
index 9149b77b3703a848581f71124dea54acb6995d07..c86964000d9dea4b1d5bec6da9348a8224a055ce 100644
--- a/lib/spack/spack/test/schema.py
+++ b/lib/spack/spack/test/schema.py
@@ -107,7 +107,7 @@ def test_module_suffixes(module_suffixes_schema):
     'repos'
 ])
 def test_schema_validation(meta_schema, config_name):
-    import importlib
+    import importlib  # novm
     module_name = 'spack.schema.{0}'.format(config_name)
     module = importlib.import_module(module_name)
     schema = getattr(module, 'schema')
diff --git a/lib/spack/spack/util/imp/importlib_importer.py b/lib/spack/spack/util/imp/importlib_importer.py
index 33c50cb601a32e15ef3a2049bbc5b57cfa4de487..fb4ad58aa6a013e89aaad33a22c9c4ee9c431fa6 100644
--- a/lib/spack/spack/util/imp/importlib_importer.py
+++ b/lib/spack/spack/util/imp/importlib_importer.py
@@ -7,7 +7,7 @@
 
 ``importlib`` is only fully implemented in Python 3.
 """
-from importlib.machinery import SourceFileLoader
+from importlib.machinery import SourceFileLoader  # novm
 
 
 class PrependFileLoader(SourceFileLoader):
diff --git a/lib/spack/spack/variant.py b/lib/spack/spack/variant.py
index bdee9d35525c7cc2a7704d80544fdc3069eac95d..250843f1153dd676d089cb1c3652e052b59ae28d 100644
--- a/lib/spack/spack/variant.py
+++ b/lib/spack/spack/variant.py
@@ -21,7 +21,7 @@
 import spack.error as error
 
 try:
-    from collections.abc import Sequence
+    from collections.abc import Sequence  # novm
 except ImportError:
     from collections import Sequence