diff --git a/bin/spack b/bin/spack
index f2b5d1d58387b02f39554def98946fe22d15f175..2f74761a78f01da8afd831679329aa1a805a9b55 100755
--- a/bin/spack
+++ b/bin/spack
@@ -24,11 +24,10 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import sys
-if not sys.version_info[:2] >= (2,7):
-    sys.exit("Spack requires Python 2.7.  Version was %s." % sys.version_info)
+if not sys.version_info[:2] >= (2,6):
+    sys.exit("Spack requires Python 2.6.  Version was %s." % sys.version_info)
 
 import os
-import argparse
 
 # Find spack's location and its prefix.
 SPACK_FILE = os.path.realpath(os.path.expanduser(__file__))
@@ -51,6 +50,7 @@ del SPACK_FILE, SPACK_PREFIX, SPACK_LIB_PATH
 import llnl.util.tty as tty
 import spack
 from spack.error import SpackError
+from external import argparse
 
 # Command parsing
 parser = argparse.ArgumentParser(
diff --git a/lib/spack/env/cc b/lib/spack/env/cc
index 9e71d25cafb53892d551d29e98e070e85be0dee0..266e41cb48616baf80cc7bd04374e64de25d0235 100755
--- a/lib/spack/env/cc
+++ b/lib/spack/env/cc
@@ -1,12 +1,11 @@
 #!/usr/bin/env python
 import sys
-if not sys.version_info[:2] >= (2,7):
-    sys.exit("Spack requires Python 2.7.  Version was %s." % sys.version_info)
+if not sys.version_info[:2] >= (2,6):
+    sys.exit("Spack requires Python 2.6.  Version was %s." % sys.version_info)
 
 import os
 import re
 import subprocess
-import argparse
 from contextlib import closing
 
 # Import spack parameters through the build environment.
@@ -18,6 +17,7 @@ if not spack_lib:
 # Grab a minimal set of spack packages
 sys.path.append(spack_lib)
 from spack.compilation import *
+from external import argparse
 import llnl.util.tty as tty
 
 spack_prefix        = get_env_var("SPACK_PREFIX")
diff --git a/lib/spack/external/__init__.py b/lib/spack/external/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1cc981930ad1d95fa8966cec003122e18525381c
--- /dev/null
+++ b/lib/spack/external/__init__.py
@@ -0,0 +1,33 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+"""
+This module contains external, potentially separately licensed,
+packages that are included in spack.
+
+So far:
+    argparse:    We include our own version to be Python 2.6 compatible.
+    pyqver2:     External script to query required python version of python source code.
+                 Used for ensuring 2.6 compatibility.
+"""
diff --git a/lib/spack/external/argparse.py b/lib/spack/external/argparse.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8dfdd3bed2e8f8713291698044fef2202266630
--- /dev/null
+++ b/lib/spack/external/argparse.py
@@ -0,0 +1,2382 @@
+# argparse is (c) 2006-2009 Steven J. Bethard <steven.bethard@gmail.com>.
+#
+# The argparse module was contributed to Python as of Python 2.7 and thus
+# was licensed under the Python license. Same license applies to all files in
+# the argparse package project.
+#
+# For details about the Python License, please see doc/Python-License.txt.
+#
+# History
+# -------
+#
+# Before (and including) argparse 1.1, the argparse package was licensed under
+# Apache License v2.0.
+#
+# After argparse 1.1, all project files from the argparse project were deleted
+# due to license compatibility issues between Apache License 2.0 and GNU GPL v2.
+#
+# The project repository then had a clean start with some files taken from
+# Python 2.7.1, so definitely all files are under Python License now.
+#
+# Author: Steven J. Bethard <steven.bethard@gmail.com>.
+#
+"""Command-line parsing library
+
+This module is an optparse-inspired command-line parsing library that:
+
+    - handles both optional and positional arguments
+    - produces highly informative usage messages
+    - supports parsers that dispatch to sub-parsers
+
+The following is a simple usage example that sums integers from the
+command-line and writes the result to a file::
+
+    parser = argparse.ArgumentParser(
+        description='sum the integers at the command line')
+    parser.add_argument(
+        'integers', metavar='int', nargs='+', type=int,
+        help='an integer to be summed')
+    parser.add_argument(
+        '--log', default=sys.stdout, type=argparse.FileType('w'),
+        help='the file where the sum should be written')
+    args = parser.parse_args()
+    args.log.write('%s' % sum(args.integers))
+    args.log.close()
+
+The module contains the following public classes:
+
+    - ArgumentParser -- The main entry point for command-line parsing. As the
+        example above shows, the add_argument() method is used to populate
+        the parser with actions for optional and positional arguments. Then
+        the parse_args() method is invoked to convert the args at the
+        command-line into an object with attributes.
+
+    - ArgumentError -- The exception raised by ArgumentParser objects when
+        there are errors with the parser's actions. Errors raised while
+        parsing the command-line are caught by ArgumentParser and emitted
+        as command-line messages.
+
+    - FileType -- A factory for defining types of files to be created. As the
+        example above shows, instances of FileType are typically passed as
+        the type= argument of add_argument() calls.
+
+    - Action -- The base class for parser actions. Typically actions are
+        selected by passing strings like 'store_true' or 'append_const' to
+        the action= argument of add_argument(). However, for greater
+        customization of ArgumentParser actions, subclasses of Action may
+        be defined and passed as the action= argument.
+
+    - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter,
+        ArgumentDefaultsHelpFormatter -- Formatter classes which
+        may be passed as the formatter_class= argument to the
+        ArgumentParser constructor. HelpFormatter is the default,
+        RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser
+        not to change the formatting for help text, and
+        ArgumentDefaultsHelpFormatter adds information about argument defaults
+        to the help.
+
+All other classes in this module are considered implementation details.
+(Also note that HelpFormatter and RawDescriptionHelpFormatter are only
+considered public as object names -- the API of the formatter objects is
+still considered an implementation detail.)
+"""
+
+__version__ = '1.2.1'
+__all__ = [
+    'ArgumentParser',
+    'ArgumentError',
+    'ArgumentTypeError',
+    'FileType',
+    'HelpFormatter',
+    'ArgumentDefaultsHelpFormatter',
+    'RawDescriptionHelpFormatter',
+    'RawTextHelpFormatter',
+    'Namespace',
+    'Action',
+    'ONE_OR_MORE',
+    'OPTIONAL',
+    'PARSER',
+    'REMAINDER',
+    'SUPPRESS',
+    'ZERO_OR_MORE',
+]
+
+
+import copy as _copy
+import os as _os
+import re as _re
+import sys as _sys
+import textwrap as _textwrap
+
+from gettext import gettext as _
+
+try:
+    set
+except NameError:
+    # for python < 2.4 compatibility (sets module is there since 2.3):
+    from sets import Set as set
+
+try:
+    basestring
+except NameError:
+    basestring = str
+
+try:
+    sorted
+except NameError:
+    # for python < 2.4 compatibility:
+    def sorted(iterable, reverse=False):
+        result = list(iterable)
+        result.sort()
+        if reverse:
+            result.reverse()
+        return result
+
+
+def _callable(obj):
+    return hasattr(obj, '__call__') or hasattr(obj, '__bases__')
+
+
+SUPPRESS = '==SUPPRESS=='
+
+OPTIONAL = '?'
+ZERO_OR_MORE = '*'
+ONE_OR_MORE = '+'
+PARSER = 'A...'
+REMAINDER = '...'
+_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args'
+
+# =============================
+# Utility functions and classes
+# =============================
+
+class _AttributeHolder(object):
+    """Abstract base class that provides __repr__.
+
+    The __repr__ method returns a string in the format::
+        ClassName(attr=name, attr=name, ...)
+    The attributes are determined either by a class-level attribute,
+    '_kwarg_names', or by inspecting the instance __dict__.
+    """
+
+    def __repr__(self):
+        type_name = type(self).__name__
+        arg_strings = []
+        for arg in self._get_args():
+            arg_strings.append(repr(arg))
+        for name, value in self._get_kwargs():
+            arg_strings.append('%s=%r' % (name, value))
+        return '%s(%s)' % (type_name, ', '.join(arg_strings))
+
+    def _get_kwargs(self):
+        return sorted(self.__dict__.items())
+
+    def _get_args(self):
+        return []
+
+
+def _ensure_value(namespace, name, value):
+    if getattr(namespace, name, None) is None:
+        setattr(namespace, name, value)
+    return getattr(namespace, name)
+
+
+# ===============
+# Formatting Help
+# ===============
+
+class HelpFormatter(object):
+    """Formatter for generating usage messages and argument help strings.
+
+    Only the name of this class is considered a public API. All the methods
+    provided by the class are considered an implementation detail.
+    """
+
+    def __init__(self,
+                 prog,
+                 indent_increment=2,
+                 max_help_position=24,
+                 width=None):
+
+        # default setting for width
+        if width is None:
+            try:
+                width = int(_os.environ['COLUMNS'])
+            except (KeyError, ValueError):
+                width = 80
+            width -= 2
+
+        self._prog = prog
+        self._indent_increment = indent_increment
+        self._max_help_position = max_help_position
+        self._width = width
+
+        self._current_indent = 0
+        self._level = 0
+        self._action_max_length = 0
+
+        self._root_section = self._Section(self, None)
+        self._current_section = self._root_section
+
+        self._whitespace_matcher = _re.compile(r'\s+')
+        self._long_break_matcher = _re.compile(r'\n\n\n+')
+
+    # ===============================
+    # Section and indentation methods
+    # ===============================
+    def _indent(self):
+        self._current_indent += self._indent_increment
+        self._level += 1
+
+    def _dedent(self):
+        self._current_indent -= self._indent_increment
+        assert self._current_indent >= 0, 'Indent decreased below 0.'
+        self._level -= 1
+
+    class _Section(object):
+
+        def __init__(self, formatter, parent, heading=None):
+            self.formatter = formatter
+            self.parent = parent
+            self.heading = heading
+            self.items = []
+
+        def format_help(self):
+            # format the indented section
+            if self.parent is not None:
+                self.formatter._indent()
+            join = self.formatter._join_parts
+            for func, args in self.items:
+                func(*args)
+            item_help = join([func(*args) for func, args in self.items])
+            if self.parent is not None:
+                self.formatter._dedent()
+
+            # return nothing if the section was empty
+            if not item_help:
+                return ''
+
+            # add the heading if the section was non-empty
+            if self.heading is not SUPPRESS and self.heading is not None:
+                current_indent = self.formatter._current_indent
+                heading = '%*s%s:\n' % (current_indent, '', self.heading)
+            else:
+                heading = ''
+
+            # join the section-initial newline, the heading and the help
+            return join(['\n', heading, item_help, '\n'])
+
+    def _add_item(self, func, args):
+        self._current_section.items.append((func, args))
+
+    # ========================
+    # Message building methods
+    # ========================
+    def start_section(self, heading):
+        self._indent()
+        section = self._Section(self, self._current_section, heading)
+        self._add_item(section.format_help, [])
+        self._current_section = section
+
+    def end_section(self):
+        self._current_section = self._current_section.parent
+        self._dedent()
+
+    def add_text(self, text):
+        if text is not SUPPRESS and text is not None:
+            self._add_item(self._format_text, [text])
+
+    def add_usage(self, usage, actions, groups, prefix=None):
+        if usage is not SUPPRESS:
+            args = usage, actions, groups, prefix
+            self._add_item(self._format_usage, args)
+
+    def add_argument(self, action):
+        if action.help is not SUPPRESS:
+
+            # find all invocations
+            get_invocation = self._format_action_invocation
+            invocations = [get_invocation(action)]
+            for subaction in self._iter_indented_subactions(action):
+                invocations.append(get_invocation(subaction))
+
+            # update the maximum item length
+            invocation_length = max([len(s) for s in invocations])
+            action_length = invocation_length + self._current_indent
+            self._action_max_length = max(self._action_max_length,
+                                          action_length)
+
+            # add the item to the list
+            self._add_item(self._format_action, [action])
+
+    def add_arguments(self, actions):
+        for action in actions:
+            self.add_argument(action)
+
+    # =======================
+    # Help-formatting methods
+    # =======================
+    def format_help(self):
+        help = self._root_section.format_help()
+        if help:
+            help = self._long_break_matcher.sub('\n\n', help)
+            help = help.strip('\n') + '\n'
+        return help
+
+    def _join_parts(self, part_strings):
+        return ''.join([part
+                        for part in part_strings
+                        if part and part is not SUPPRESS])
+
+    def _format_usage(self, usage, actions, groups, prefix):
+        if prefix is None:
+            prefix = _('usage: ')
+
+        # if usage is specified, use that
+        if usage is not None:
+            usage = usage % dict(prog=self._prog)
+
+        # if no optionals or positionals are available, usage is just prog
+        elif usage is None and not actions:
+            usage = '%(prog)s' % dict(prog=self._prog)
+
+        # if optionals and positionals are available, calculate usage
+        elif usage is None:
+            prog = '%(prog)s' % dict(prog=self._prog)
+
+            # split optionals from positionals
+            optionals = []
+            positionals = []
+            for action in actions:
+                if action.option_strings:
+                    optionals.append(action)
+                else:
+                    positionals.append(action)
+
+            # build full usage string
+            format = self._format_actions_usage
+            action_usage = format(optionals + positionals, groups)
+            usage = ' '.join([s for s in [prog, action_usage] if s])
+
+            # wrap the usage parts if it's too long
+            text_width = self._width - self._current_indent
+            if len(prefix) + len(usage) > text_width:
+
+                # break usage into wrappable parts
+                part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
+                opt_usage = format(optionals, groups)
+                pos_usage = format(positionals, groups)
+                opt_parts = _re.findall(part_regexp, opt_usage)
+                pos_parts = _re.findall(part_regexp, pos_usage)
+                assert ' '.join(opt_parts) == opt_usage
+                assert ' '.join(pos_parts) == pos_usage
+
+                # helper for wrapping lines
+                def get_lines(parts, indent, prefix=None):
+                    lines = []
+                    line = []
+                    if prefix is not None:
+                        line_len = len(prefix) - 1
+                    else:
+                        line_len = len(indent) - 1
+                    for part in parts:
+                        if line_len + 1 + len(part) > text_width:
+                            lines.append(indent + ' '.join(line))
+                            line = []
+                            line_len = len(indent) - 1
+                        line.append(part)
+                        line_len += len(part) + 1
+                    if line:
+                        lines.append(indent + ' '.join(line))
+                    if prefix is not None:
+                        lines[0] = lines[0][len(indent):]
+                    return lines
+
+                # if prog is short, follow it with optionals or positionals
+                if len(prefix) + len(prog) <= 0.75 * text_width:
+                    indent = ' ' * (len(prefix) + len(prog) + 1)
+                    if opt_parts:
+                        lines = get_lines([prog] + opt_parts, indent, prefix)
+                        lines.extend(get_lines(pos_parts, indent))
+                    elif pos_parts:
+                        lines = get_lines([prog] + pos_parts, indent, prefix)
+                    else:
+                        lines = [prog]
+
+                # if prog is long, put it on its own line
+                else:
+                    indent = ' ' * len(prefix)
+                    parts = opt_parts + pos_parts
+                    lines = get_lines(parts, indent)
+                    if len(lines) > 1:
+                        lines = []
+                        lines.extend(get_lines(opt_parts, indent))
+                        lines.extend(get_lines(pos_parts, indent))
+                    lines = [prog] + lines
+
+                # join lines into usage
+                usage = '\n'.join(lines)
+
+        # prefix with 'usage:'
+        return '%s%s\n\n' % (prefix, usage)
+
+    def _format_actions_usage(self, actions, groups):
+        # find group indices and identify actions in groups
+        group_actions = set()
+        inserts = {}
+        for group in groups:
+            try:
+                start = actions.index(group._group_actions[0])
+            except ValueError:
+                continue
+            else:
+                end = start + len(group._group_actions)
+                if actions[start:end] == group._group_actions:
+                    for action in group._group_actions:
+                        group_actions.add(action)
+                    if not group.required:
+                        if start in inserts:
+                            inserts[start] += ' ['
+                        else:
+                            inserts[start] = '['
+                        inserts[end] = ']'
+                    else:
+                        if start in inserts:
+                            inserts[start] += ' ('
+                        else:
+                            inserts[start] = '('
+                        inserts[end] = ')'
+                    for i in range(start + 1, end):
+                        inserts[i] = '|'
+
+        # collect all actions format strings
+        parts = []
+        for i, action in enumerate(actions):
+
+            # suppressed arguments are marked with None
+            # remove | separators for suppressed arguments
+            if action.help is SUPPRESS:
+                parts.append(None)
+                if inserts.get(i) == '|':
+                    inserts.pop(i)
+                elif inserts.get(i + 1) == '|':
+                    inserts.pop(i + 1)
+
+            # produce all arg strings
+            elif not action.option_strings:
+                part = self._format_args(action, action.dest)
+
+                # if it's in a group, strip the outer []
+                if action in group_actions:
+                    if part[0] == '[' and part[-1] == ']':
+                        part = part[1:-1]
+
+                # add the action string to the list
+                parts.append(part)
+
+            # produce the first way to invoke the option in brackets
+            else:
+                option_string = action.option_strings[0]
+
+                # if the Optional doesn't take a value, format is:
+                #    -s or --long
+                if action.nargs == 0:
+                    part = '%s' % option_string
+
+                # if the Optional takes a value, format is:
+                #    -s ARGS or --long ARGS
+                else:
+                    default = action.dest.upper()
+                    args_string = self._format_args(action, default)
+                    part = '%s %s' % (option_string, args_string)
+
+                # make it look optional if it's not required or in a group
+                if not action.required and action not in group_actions:
+                    part = '[%s]' % part
+
+                # add the action string to the list
+                parts.append(part)
+
+        # insert things at the necessary indices
+        for i in sorted(inserts, reverse=True):
+            parts[i:i] = [inserts[i]]
+
+        # join all the action items with spaces
+        text = ' '.join([item for item in parts if item is not None])
+
+        # clean up separators for mutually exclusive groups
+        open = r'[\[(]'
+        close = r'[\])]'
+        text = _re.sub(r'(%s) ' % open, r'\1', text)
+        text = _re.sub(r' (%s)' % close, r'\1', text)
+        text = _re.sub(r'%s *%s' % (open, close), r'', text)
+        text = _re.sub(r'\(([^|]*)\)', r'\1', text)
+        text = text.strip()
+
+        # return the text
+        return text
+
+    def _format_text(self, text):
+        if '%(prog)' in text:
+            text = text % dict(prog=self._prog)
+        text_width = self._width - self._current_indent
+        indent = ' ' * self._current_indent
+        return self._fill_text(text, text_width, indent) + '\n\n'
+
+    def _format_action(self, action):
+        # determine the required width and the entry label
+        help_position = min(self._action_max_length + 2,
+                            self._max_help_position)
+        help_width = self._width - help_position
+        action_width = help_position - self._current_indent - 2
+        action_header = self._format_action_invocation(action)
+
+        # ho nelp; start on same line and add a final newline
+        if not action.help:
+            tup = self._current_indent, '', action_header
+            action_header = '%*s%s\n' % tup
+
+        # short action name; start on the same line and pad two spaces
+        elif len(action_header) <= action_width:
+            tup = self._current_indent, '', action_width, action_header
+            action_header = '%*s%-*s  ' % tup
+            indent_first = 0
+
+        # long action name; start on the next line
+        else:
+            tup = self._current_indent, '', action_header
+            action_header = '%*s%s\n' % tup
+            indent_first = help_position
+
+        # collect the pieces of the action help
+        parts = [action_header]
+
+        # if there was help for the action, add lines of help text
+        if action.help:
+            help_text = self._expand_help(action)
+            help_lines = self._split_lines(help_text, help_width)
+            parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
+            for line in help_lines[1:]:
+                parts.append('%*s%s\n' % (help_position, '', line))
+
+        # or add a newline if the description doesn't end with one
+        elif not action_header.endswith('\n'):
+            parts.append('\n')
+
+        # if there are any sub-actions, add their help as well
+        for subaction in self._iter_indented_subactions(action):
+            parts.append(self._format_action(subaction))
+
+        # return a single string
+        return self._join_parts(parts)
+
+    def _format_action_invocation(self, action):
+        if not action.option_strings:
+            metavar, = self._metavar_formatter(action, action.dest)(1)
+            return metavar
+
+        else:
+            parts = []
+
+            # if the Optional doesn't take a value, format is:
+            #    -s, --long
+            if action.nargs == 0:
+                parts.extend(action.option_strings)
+
+            # if the Optional takes a value, format is:
+            #    -s ARGS, --long ARGS
+            else:
+                default = action.dest.upper()
+                args_string = self._format_args(action, default)
+                for option_string in action.option_strings:
+                    parts.append('%s %s' % (option_string, args_string))
+
+            return ', '.join(parts)
+
+    def _metavar_formatter(self, action, default_metavar):
+        if action.metavar is not None:
+            result = action.metavar
+        elif action.choices is not None:
+            choice_strs = [str(choice) for choice in action.choices]
+            result = '{%s}' % ','.join(choice_strs)
+        else:
+            result = default_metavar
+
+        def format(tuple_size):
+            if isinstance(result, tuple):
+                return result
+            else:
+                return (result, ) * tuple_size
+        return format
+
+    def _format_args(self, action, default_metavar):
+        get_metavar = self._metavar_formatter(action, default_metavar)
+        if action.nargs is None:
+            result = '%s' % get_metavar(1)
+        elif action.nargs == OPTIONAL:
+            result = '[%s]' % get_metavar(1)
+        elif action.nargs == ZERO_OR_MORE:
+            result = '[%s [%s ...]]' % get_metavar(2)
+        elif action.nargs == ONE_OR_MORE:
+            result = '%s [%s ...]' % get_metavar(2)
+        elif action.nargs == REMAINDER:
+            result = '...'
+        elif action.nargs == PARSER:
+            result = '%s ...' % get_metavar(1)
+        else:
+            formats = ['%s' for _ in range(action.nargs)]
+            result = ' '.join(formats) % get_metavar(action.nargs)
+        return result
+
+    def _expand_help(self, action):
+        params = dict(vars(action), prog=self._prog)
+        for name in list(params):
+            if params[name] is SUPPRESS:
+                del params[name]
+        for name in list(params):
+            if hasattr(params[name], '__name__'):
+                params[name] = params[name].__name__
+        if params.get('choices') is not None:
+            choices_str = ', '.join([str(c) for c in params['choices']])
+            params['choices'] = choices_str
+        return self._get_help_string(action) % params
+
+    def _iter_indented_subactions(self, action):
+        try:
+            get_subactions = action._get_subactions
+        except AttributeError:
+            pass
+        else:
+            self._indent()
+            for subaction in get_subactions():
+                yield subaction
+            self._dedent()
+
+    def _split_lines(self, text, width):
+        text = self._whitespace_matcher.sub(' ', text).strip()
+        return _textwrap.wrap(text, width)
+
+    def _fill_text(self, text, width, indent):
+        text = self._whitespace_matcher.sub(' ', text).strip()
+        return _textwrap.fill(text, width, initial_indent=indent,
+                                           subsequent_indent=indent)
+
+    def _get_help_string(self, action):
+        return action.help
+
+
+class RawDescriptionHelpFormatter(HelpFormatter):
+    """Help message formatter which retains any formatting in descriptions.
+
+    Only the name of this class is considered a public API. All the methods
+    provided by the class are considered an implementation detail.
+    """
+
+    def _fill_text(self, text, width, indent):
+        return ''.join([indent + line for line in text.splitlines(True)])
+
+
+class RawTextHelpFormatter(RawDescriptionHelpFormatter):
+    """Help message formatter which retains formatting of all help text.
+
+    Only the name of this class is considered a public API. All the methods
+    provided by the class are considered an implementation detail.
+    """
+
+    def _split_lines(self, text, width):
+        return text.splitlines()
+
+
+class ArgumentDefaultsHelpFormatter(HelpFormatter):
+    """Help message formatter which adds default values to argument help.
+
+    Only the name of this class is considered a public API. All the methods
+    provided by the class are considered an implementation detail.
+    """
+
+    def _get_help_string(self, action):
+        help = action.help
+        if '%(default)' not in action.help:
+            if action.default is not SUPPRESS:
+                defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
+                if action.option_strings or action.nargs in defaulting_nargs:
+                    help += ' (default: %(default)s)'
+        return help
+
+
+# =====================
+# Options and Arguments
+# =====================
+
+def _get_action_name(argument):
+    if argument is None:
+        return None
+    elif argument.option_strings:
+        return  '/'.join(argument.option_strings)
+    elif argument.metavar not in (None, SUPPRESS):
+        return argument.metavar
+    elif argument.dest not in (None, SUPPRESS):
+        return argument.dest
+    else:
+        return None
+
+
+class ArgumentError(Exception):
+    """An error from creating or using an argument (optional or positional).
+
+    The string value of this exception is the message, augmented with
+    information about the argument that caused it.
+    """
+
+    def __init__(self, argument, message):
+        self.argument_name = _get_action_name(argument)
+        self.message = message
+
+    def __str__(self):
+        if self.argument_name is None:
+            format = '%(message)s'
+        else:
+            format = 'argument %(argument_name)s: %(message)s'
+        return format % dict(message=self.message,
+                             argument_name=self.argument_name)
+
+
+class ArgumentTypeError(Exception):
+    """An error from trying to convert a command line string to a type."""
+    pass
+
+
+# ==============
+# Action classes
+# ==============
+
+class Action(_AttributeHolder):
+    """Information about how to convert command line strings to Python objects.
+
+    Action objects are used by an ArgumentParser to represent the information
+    needed to parse a single argument from one or more strings from the
+    command line. The keyword arguments to the Action constructor are also
+    all attributes of Action instances.
+
+    Keyword Arguments:
+
+        - option_strings -- A list of command-line option strings which
+            should be associated with this action.
+
+        - dest -- The name of the attribute to hold the created object(s)
+
+        - nargs -- The number of command-line arguments that should be
+            consumed. By default, one argument will be consumed and a single
+            value will be produced.  Other values include:
+                - N (an integer) consumes N arguments (and produces a list)
+                - '?' consumes zero or one arguments
+                - '*' consumes zero or more arguments (and produces a list)
+                - '+' consumes one or more arguments (and produces a list)
+            Note that the difference between the default and nargs=1 is that
+            with the default, a single value will be produced, while with
+            nargs=1, a list containing a single value will be produced.
+
+        - const -- The value to be produced if the option is specified and the
+            option uses an action that takes no values.
+
+        - default -- The value to be produced if the option is not specified.
+
+        - type -- The type which the command-line arguments should be converted
+            to, should be one of 'string', 'int', 'float', 'complex' or a
+            callable object that accepts a single string argument. If None,
+            'string' is assumed.
+
+        - choices -- A container of values that should be allowed. If not None,
+            after a command-line argument has been converted to the appropriate
+            type, an exception will be raised if it is not a member of this
+            collection.
+
+        - required -- True if the action must always be specified at the
+            command line. This is only meaningful for optional command-line
+            arguments.
+
+        - help -- The help string describing the argument.
+
+        - metavar -- The name to be used for the option's argument with the
+            help string. If None, the 'dest' value will be used as the name.
+    """
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 nargs=None,
+                 const=None,
+                 default=None,
+                 type=None,
+                 choices=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        self.option_strings = option_strings
+        self.dest = dest
+        self.nargs = nargs
+        self.const = const
+        self.default = default
+        self.type = type
+        self.choices = choices
+        self.required = required
+        self.help = help
+        self.metavar = metavar
+
+    def _get_kwargs(self):
+        names = [
+            'option_strings',
+            'dest',
+            'nargs',
+            'const',
+            'default',
+            'type',
+            'choices',
+            'help',
+            'metavar',
+        ]
+        return [(name, getattr(self, name)) for name in names]
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        raise NotImplementedError(_('.__call__() not defined'))
+
+
+class _StoreAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 nargs=None,
+                 const=None,
+                 default=None,
+                 type=None,
+                 choices=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        if nargs == 0:
+            raise ValueError('nargs for store actions must be > 0; if you '
+                             'have nothing to store, actions such as store '
+                             'true or store const may be more appropriate')
+        if const is not None and nargs != OPTIONAL:
+            raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+        super(_StoreAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=nargs,
+            const=const,
+            default=default,
+            type=type,
+            choices=choices,
+            required=required,
+            help=help,
+            metavar=metavar)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        setattr(namespace, self.dest, values)
+
+
+class _StoreConstAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 const,
+                 default=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        super(_StoreConstAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=0,
+            const=const,
+            default=default,
+            required=required,
+            help=help)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        setattr(namespace, self.dest, self.const)
+
+
+class _StoreTrueAction(_StoreConstAction):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 default=False,
+                 required=False,
+                 help=None):
+        super(_StoreTrueAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            const=True,
+            default=default,
+            required=required,
+            help=help)
+
+
+class _StoreFalseAction(_StoreConstAction):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 default=True,
+                 required=False,
+                 help=None):
+        super(_StoreFalseAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            const=False,
+            default=default,
+            required=required,
+            help=help)
+
+
+class _AppendAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 nargs=None,
+                 const=None,
+                 default=None,
+                 type=None,
+                 choices=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        if nargs == 0:
+            raise ValueError('nargs for append actions must be > 0; if arg '
+                             'strings are not supplying the value to append, '
+                             'the append const action may be more appropriate')
+        if const is not None and nargs != OPTIONAL:
+            raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+        super(_AppendAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=nargs,
+            const=const,
+            default=default,
+            type=type,
+            choices=choices,
+            required=required,
+            help=help,
+            metavar=metavar)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        items = _copy.copy(_ensure_value(namespace, self.dest, []))
+        items.append(values)
+        setattr(namespace, self.dest, items)
+
+
+class _AppendConstAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 const,
+                 default=None,
+                 required=False,
+                 help=None,
+                 metavar=None):
+        super(_AppendConstAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=0,
+            const=const,
+            default=default,
+            required=required,
+            help=help,
+            metavar=metavar)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        items = _copy.copy(_ensure_value(namespace, self.dest, []))
+        items.append(self.const)
+        setattr(namespace, self.dest, items)
+
+
+class _CountAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest,
+                 default=None,
+                 required=False,
+                 help=None):
+        super(_CountAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=0,
+            default=default,
+            required=required,
+            help=help)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        new_count = _ensure_value(namespace, self.dest, 0) + 1
+        setattr(namespace, self.dest, new_count)
+
+
+class _HelpAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 dest=SUPPRESS,
+                 default=SUPPRESS,
+                 help=None):
+        super(_HelpAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            default=default,
+            nargs=0,
+            help=help)
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        parser.print_help()
+        parser.exit()
+
+
+class _VersionAction(Action):
+
+    def __init__(self,
+                 option_strings,
+                 version=None,
+                 dest=SUPPRESS,
+                 default=SUPPRESS,
+                 help="show program's version number and exit"):
+        super(_VersionAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            default=default,
+            nargs=0,
+            help=help)
+        self.version = version
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        version = self.version
+        if version is None:
+            version = parser.version
+        formatter = parser._get_formatter()
+        formatter.add_text(version)
+        parser.exit(message=formatter.format_help())
+
+
+class _SubParsersAction(Action):
+
+    class _ChoicesPseudoAction(Action):
+
+        def __init__(self, name, help):
+            sup = super(_SubParsersAction._ChoicesPseudoAction, self)
+            sup.__init__(option_strings=[], dest=name, help=help)
+
+    def __init__(self,
+                 option_strings,
+                 prog,
+                 parser_class,
+                 dest=SUPPRESS,
+                 help=None,
+                 metavar=None):
+
+        self._prog_prefix = prog
+        self._parser_class = parser_class
+        self._name_parser_map = {}
+        self._choices_actions = []
+
+        super(_SubParsersAction, self).__init__(
+            option_strings=option_strings,
+            dest=dest,
+            nargs=PARSER,
+            choices=self._name_parser_map,
+            help=help,
+            metavar=metavar)
+
+    def add_parser(self, name, **kwargs):
+        # set prog from the existing prefix
+        if kwargs.get('prog') is None:
+            kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
+
+        # create a pseudo-action to hold the choice help
+        if 'help' in kwargs:
+            help = kwargs.pop('help')
+            choice_action = self._ChoicesPseudoAction(name, help)
+            self._choices_actions.append(choice_action)
+
+        # create the parser and add it to the map
+        parser = self._parser_class(**kwargs)
+        self._name_parser_map[name] = parser
+        return parser
+
+    def _get_subactions(self):
+        return self._choices_actions
+
+    def __call__(self, parser, namespace, values, option_string=None):
+        parser_name = values[0]
+        arg_strings = values[1:]
+
+        # set the parser name if requested
+        if self.dest is not SUPPRESS:
+            setattr(namespace, self.dest, parser_name)
+
+        # select the parser
+        try:
+            parser = self._name_parser_map[parser_name]
+        except KeyError:
+            tup = parser_name, ', '.join(self._name_parser_map)
+            msg = _('unknown parser %r (choices: %s)' % tup)
+            raise ArgumentError(self, msg)
+
+        # parse all the remaining options into the namespace
+        # store any unrecognized options on the object, so that the top
+        # level parser can decide what to do with them
+        namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
+        if arg_strings:
+            vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
+            getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
+
+
+# ==============
+# Type classes
+# ==============
+
+class FileType(object):
+    """Factory for creating file object types
+
+    Instances of FileType are typically passed as type= arguments to the
+    ArgumentParser add_argument() method.
+
+    Keyword Arguments:
+        - mode -- A string indicating how the file is to be opened. Accepts the
+            same values as the builtin open() function.
+        - bufsize -- The file's desired buffer size. Accepts the same values as
+            the builtin open() function.
+    """
+
+    def __init__(self, mode='r', bufsize=None):
+        self._mode = mode
+        self._bufsize = bufsize
+
+    def __call__(self, string):
+        # the special argument "-" means sys.std{in,out}
+        if string == '-':
+            if 'r' in self._mode:
+                return _sys.stdin
+            elif 'w' in self._mode:
+                return _sys.stdout
+            else:
+                msg = _('argument "-" with mode %r' % self._mode)
+                raise ValueError(msg)
+
+        # all other arguments are used as file names
+        if self._bufsize:
+            return open(string, self._mode, self._bufsize)
+        else:
+            return open(string, self._mode)
+
+    def __repr__(self):
+        args = [self._mode, self._bufsize]
+        args_str = ', '.join([repr(arg) for arg in args if arg is not None])
+        return '%s(%s)' % (type(self).__name__, args_str)
+
+# ===========================
+# Optional and Positional Parsing
+# ===========================
+
+class Namespace(_AttributeHolder):
+    """Simple object for storing attributes.
+
+    Implements equality by attribute names and values, and provides a simple
+    string representation.
+    """
+
+    def __init__(self, **kwargs):
+        for name in kwargs:
+            setattr(self, name, kwargs[name])
+
+    __hash__ = None
+
+    def __eq__(self, other):
+        return vars(self) == vars(other)
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __contains__(self, key):
+        return key in self.__dict__
+
+
+class _ActionsContainer(object):
+
+    def __init__(self,
+                 description,
+                 prefix_chars,
+                 argument_default,
+                 conflict_handler):
+        super(_ActionsContainer, self).__init__()
+
+        self.description = description
+        self.argument_default = argument_default
+        self.prefix_chars = prefix_chars
+        self.conflict_handler = conflict_handler
+
+        # set up registries
+        self._registries = {}
+
+        # register actions
+        self.register('action', None, _StoreAction)
+        self.register('action', 'store', _StoreAction)
+        self.register('action', 'store_const', _StoreConstAction)
+        self.register('action', 'store_true', _StoreTrueAction)
+        self.register('action', 'store_false', _StoreFalseAction)
+        self.register('action', 'append', _AppendAction)
+        self.register('action', 'append_const', _AppendConstAction)
+        self.register('action', 'count', _CountAction)
+        self.register('action', 'help', _HelpAction)
+        self.register('action', 'version', _VersionAction)
+        self.register('action', 'parsers', _SubParsersAction)
+
+        # raise an exception if the conflict handler is invalid
+        self._get_handler()
+
+        # action storage
+        self._actions = []
+        self._option_string_actions = {}
+
+        # groups
+        self._action_groups = []
+        self._mutually_exclusive_groups = []
+
+        # defaults storage
+        self._defaults = {}
+
+        # determines whether an "option" looks like a negative number
+        self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
+
+        # whether or not there are any optionals that look like negative
+        # numbers -- uses a list so it can be shared and edited
+        self._has_negative_number_optionals = []
+
+    # ====================
+    # Registration methods
+    # ====================
+    def register(self, registry_name, value, object):
+        registry = self._registries.setdefault(registry_name, {})
+        registry[value] = object
+
+    def _registry_get(self, registry_name, value, default=None):
+        return self._registries[registry_name].get(value, default)
+
+    # ==================================
+    # Namespace default accessor methods
+    # ==================================
+    def set_defaults(self, **kwargs):
+        self._defaults.update(kwargs)
+
+        # if these defaults match any existing arguments, replace
+        # the previous default on the object with the new one
+        for action in self._actions:
+            if action.dest in kwargs:
+                action.default = kwargs[action.dest]
+
+    def get_default(self, dest):
+        for action in self._actions:
+            if action.dest == dest and action.default is not None:
+                return action.default
+        return self._defaults.get(dest, None)
+
+
+    # =======================
+    # Adding argument actions
+    # =======================
+    def add_argument(self, *args, **kwargs):
+        """
+        add_argument(dest, ..., name=value, ...)
+        add_argument(option_string, option_string, ..., name=value, ...)
+        """
+
+        # if no positional args are supplied or only one is supplied and
+        # it doesn't look like an option string, parse a positional
+        # argument
+        chars = self.prefix_chars
+        if not args or len(args) == 1 and args[0][0] not in chars:
+            if args and 'dest' in kwargs:
+                raise ValueError('dest supplied twice for positional argument')
+            kwargs = self._get_positional_kwargs(*args, **kwargs)
+
+        # otherwise, we're adding an optional argument
+        else:
+            kwargs = self._get_optional_kwargs(*args, **kwargs)
+
+        # if no default was supplied, use the parser-level default
+        if 'default' not in kwargs:
+            dest = kwargs['dest']
+            if dest in self._defaults:
+                kwargs['default'] = self._defaults[dest]
+            elif self.argument_default is not None:
+                kwargs['default'] = self.argument_default
+
+        # create the action object, and add it to the parser
+        action_class = self._pop_action_class(kwargs)
+        if not _callable(action_class):
+            raise ValueError('unknown action "%s"' % action_class)
+        action = action_class(**kwargs)
+
+        # raise an error if the action type is not callable
+        type_func = self._registry_get('type', action.type, action.type)
+        if not _callable(type_func):
+            raise ValueError('%r is not callable' % type_func)
+
+        return self._add_action(action)
+
+    def add_argument_group(self, *args, **kwargs):
+        group = _ArgumentGroup(self, *args, **kwargs)
+        self._action_groups.append(group)
+        return group
+
+    def add_mutually_exclusive_group(self, **kwargs):
+        group = _MutuallyExclusiveGroup(self, **kwargs)
+        self._mutually_exclusive_groups.append(group)
+        return group
+
+    def _add_action(self, action):
+        # resolve any conflicts
+        self._check_conflict(action)
+
+        # add to actions list
+        self._actions.append(action)
+        action.container = self
+
+        # index the action by any option strings it has
+        for option_string in action.option_strings:
+            self._option_string_actions[option_string] = action
+
+        # set the flag if any option strings look like negative numbers
+        for option_string in action.option_strings:
+            if self._negative_number_matcher.match(option_string):
+                if not self._has_negative_number_optionals:
+                    self._has_negative_number_optionals.append(True)
+
+        # return the created action
+        return action
+
+    def _remove_action(self, action):
+        self._actions.remove(action)
+
+    def _add_container_actions(self, container):
+        # collect groups by titles
+        title_group_map = {}
+        for group in self._action_groups:
+            if group.title in title_group_map:
+                msg = _('cannot merge actions - two groups are named %r')
+                raise ValueError(msg % (group.title))
+            title_group_map[group.title] = group
+
+        # map each action to its group
+        group_map = {}
+        for group in container._action_groups:
+
+            # if a group with the title exists, use that, otherwise
+            # create a new group matching the container's group
+            if group.title not in title_group_map:
+                title_group_map[group.title] = self.add_argument_group(
+                    title=group.title,
+                    description=group.description,
+                    conflict_handler=group.conflict_handler)
+
+            # map the actions to their new group
+            for action in group._group_actions:
+                group_map[action] = title_group_map[group.title]
+
+        # add container's mutually exclusive groups
+        # NOTE: if add_mutually_exclusive_group ever gains title= and
+        # description= then this code will need to be expanded as above
+        for group in container._mutually_exclusive_groups:
+            mutex_group = self.add_mutually_exclusive_group(
+                required=group.required)
+
+            # map the actions to their new mutex group
+            for action in group._group_actions:
+                group_map[action] = mutex_group
+
+        # add all actions to this container or their group
+        for action in container._actions:
+            group_map.get(action, self)._add_action(action)
+
+    def _get_positional_kwargs(self, dest, **kwargs):
+        # make sure required is not specified
+        if 'required' in kwargs:
+            msg = _("'required' is an invalid argument for positionals")
+            raise TypeError(msg)
+
+        # mark positional arguments as required if at least one is
+        # always required
+        if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
+            kwargs['required'] = True
+        if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
+            kwargs['required'] = True
+
+        # return the keyword arguments with no option strings
+        return dict(kwargs, dest=dest, option_strings=[])
+
+    def _get_optional_kwargs(self, *args, **kwargs):
+        # determine short and long option strings
+        option_strings = []
+        long_option_strings = []
+        for option_string in args:
+            # error on strings that don't start with an appropriate prefix
+            if not option_string[0] in self.prefix_chars:
+                msg = _('invalid option string %r: '
+                        'must start with a character %r')
+                tup = option_string, self.prefix_chars
+                raise ValueError(msg % tup)
+
+            # strings starting with two prefix characters are long options
+            option_strings.append(option_string)
+            if option_string[0] in self.prefix_chars:
+                if len(option_string) > 1:
+                    if option_string[1] in self.prefix_chars:
+                        long_option_strings.append(option_string)
+
+        # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
+        dest = kwargs.pop('dest', None)
+        if dest is None:
+            if long_option_strings:
+                dest_option_string = long_option_strings[0]
+            else:
+                dest_option_string = option_strings[0]
+            dest = dest_option_string.lstrip(self.prefix_chars)
+            if not dest:
+                msg = _('dest= is required for options like %r')
+                raise ValueError(msg % option_string)
+            dest = dest.replace('-', '_')
+
+        # return the updated keyword arguments
+        return dict(kwargs, dest=dest, option_strings=option_strings)
+
+    def _pop_action_class(self, kwargs, default=None):
+        action = kwargs.pop('action', default)
+        return self._registry_get('action', action, action)
+
+    def _get_handler(self):
+        # determine function from conflict handler string
+        handler_func_name = '_handle_conflict_%s' % self.conflict_handler
+        try:
+            return getattr(self, handler_func_name)
+        except AttributeError:
+            msg = _('invalid conflict_resolution value: %r')
+            raise ValueError(msg % self.conflict_handler)
+
+    def _check_conflict(self, action):
+
+        # find all options that conflict with this option
+        confl_optionals = []
+        for option_string in action.option_strings:
+            if option_string in self._option_string_actions:
+                confl_optional = self._option_string_actions[option_string]
+                confl_optionals.append((option_string, confl_optional))
+
+        # resolve any conflicts
+        if confl_optionals:
+            conflict_handler = self._get_handler()
+            conflict_handler(action, confl_optionals)
+
+    def _handle_conflict_error(self, action, conflicting_actions):
+        message = _('conflicting option string(s): %s')
+        conflict_string = ', '.join([option_string
+                                     for option_string, action
+                                     in conflicting_actions])
+        raise ArgumentError(action, message % conflict_string)
+
+    def _handle_conflict_resolve(self, action, conflicting_actions):
+
+        # remove all conflicting options
+        for option_string, action in conflicting_actions:
+
+            # remove the conflicting option
+            action.option_strings.remove(option_string)
+            self._option_string_actions.pop(option_string, None)
+
+            # if the option now has no option string, remove it from the
+            # container holding it
+            if not action.option_strings:
+                action.container._remove_action(action)
+
+
+class _ArgumentGroup(_ActionsContainer):
+
+    def __init__(self, container, title=None, description=None, **kwargs):
+        # add any missing keyword arguments by checking the container
+        update = kwargs.setdefault
+        update('conflict_handler', container.conflict_handler)
+        update('prefix_chars', container.prefix_chars)
+        update('argument_default', container.argument_default)
+        super_init = super(_ArgumentGroup, self).__init__
+        super_init(description=description, **kwargs)
+
+        # group attributes
+        self.title = title
+        self._group_actions = []
+
+        # share most attributes with the container
+        self._registries = container._registries
+        self._actions = container._actions
+        self._option_string_actions = container._option_string_actions
+        self._defaults = container._defaults
+        self._has_negative_number_optionals = \
+            container._has_negative_number_optionals
+
+    def _add_action(self, action):
+        action = super(_ArgumentGroup, self)._add_action(action)
+        self._group_actions.append(action)
+        return action
+
+    def _remove_action(self, action):
+        super(_ArgumentGroup, self)._remove_action(action)
+        self._group_actions.remove(action)
+
+
+class _MutuallyExclusiveGroup(_ArgumentGroup):
+
+    def __init__(self, container, required=False):
+        super(_MutuallyExclusiveGroup, self).__init__(container)
+        self.required = required
+        self._container = container
+
+    def _add_action(self, action):
+        if action.required:
+            msg = _('mutually exclusive arguments must be optional')
+            raise ValueError(msg)
+        action = self._container._add_action(action)
+        self._group_actions.append(action)
+        return action
+
+    def _remove_action(self, action):
+        self._container._remove_action(action)
+        self._group_actions.remove(action)
+
+
+class ArgumentParser(_AttributeHolder, _ActionsContainer):
+    """Object for parsing command line strings into Python objects.
+
+    Keyword Arguments:
+        - prog -- The name of the program (default: sys.argv[0])
+        - usage -- A usage message (default: auto-generated from arguments)
+        - description -- A description of what the program does
+        - epilog -- Text following the argument descriptions
+        - parents -- Parsers whose arguments should be copied into this one
+        - formatter_class -- HelpFormatter class for printing help messages
+        - prefix_chars -- Characters that prefix optional arguments
+        - fromfile_prefix_chars -- Characters that prefix files containing
+            additional arguments
+        - argument_default -- The default value for all arguments
+        - conflict_handler -- String indicating how to handle conflicts
+        - add_help -- Add a -h/-help option
+    """
+
+    def __init__(self,
+                 prog=None,
+                 usage=None,
+                 description=None,
+                 epilog=None,
+                 version=None,
+                 parents=[],
+                 formatter_class=HelpFormatter,
+                 prefix_chars='-',
+                 fromfile_prefix_chars=None,
+                 argument_default=None,
+                 conflict_handler='error',
+                 add_help=True):
+
+        if version is not None:
+            import warnings
+            warnings.warn(
+                """The "version" argument to ArgumentParser is deprecated. """
+                """Please use """
+                """"add_argument(..., action='version', version="N", ...)" """
+                """instead""", DeprecationWarning)
+
+        superinit = super(ArgumentParser, self).__init__
+        superinit(description=description,
+                  prefix_chars=prefix_chars,
+                  argument_default=argument_default,
+                  conflict_handler=conflict_handler)
+
+        # default setting for prog
+        if prog is None:
+            prog = _os.path.basename(_sys.argv[0])
+
+        self.prog = prog
+        self.usage = usage
+        self.epilog = epilog
+        self.version = version
+        self.formatter_class = formatter_class
+        self.fromfile_prefix_chars = fromfile_prefix_chars
+        self.add_help = add_help
+
+        add_group = self.add_argument_group
+        self._positionals = add_group(_('positional arguments'))
+        self._optionals = add_group(_('optional arguments'))
+        self._subparsers = None
+
+        # register types
+        def identity(string):
+            return string
+        self.register('type', None, identity)
+
+        # add help and version arguments if necessary
+        # (using explicit default to override global argument_default)
+        if '-' in prefix_chars:
+            default_prefix = '-'
+        else:
+            default_prefix = prefix_chars[0]
+        if self.add_help:
+            self.add_argument(
+                default_prefix+'h', default_prefix*2+'help',
+                action='help', default=SUPPRESS,
+                help=_('show this help message and exit'))
+        if self.version:
+            self.add_argument(
+                default_prefix+'v', default_prefix*2+'version',
+                action='version', default=SUPPRESS,
+                version=self.version,
+                help=_("show program's version number and exit"))
+
+        # add parent arguments and defaults
+        for parent in parents:
+            self._add_container_actions(parent)
+            try:
+                defaults = parent._defaults
+            except AttributeError:
+                pass
+            else:
+                self._defaults.update(defaults)
+
+    # =======================
+    # Pretty __repr__ methods
+    # =======================
+    def _get_kwargs(self):
+        names = [
+            'prog',
+            'usage',
+            'description',
+            'version',
+            'formatter_class',
+            'conflict_handler',
+            'add_help',
+        ]
+        return [(name, getattr(self, name)) for name in names]
+
+    # ==================================
+    # Optional/Positional adding methods
+    # ==================================
+    def add_subparsers(self, **kwargs):
+        if self._subparsers is not None:
+            self.error(_('cannot have multiple subparser arguments'))
+
+        # add the parser class to the arguments if it's not present
+        kwargs.setdefault('parser_class', type(self))
+
+        if 'title' in kwargs or 'description' in kwargs:
+            title = _(kwargs.pop('title', 'subcommands'))
+            description = _(kwargs.pop('description', None))
+            self._subparsers = self.add_argument_group(title, description)
+        else:
+            self._subparsers = self._positionals
+
+        # prog defaults to the usage message of this parser, skipping
+        # optional arguments and with no "usage:" prefix
+        if kwargs.get('prog') is None:
+            formatter = self._get_formatter()
+            positionals = self._get_positional_actions()
+            groups = self._mutually_exclusive_groups
+            formatter.add_usage(self.usage, positionals, groups, '')
+            kwargs['prog'] = formatter.format_help().strip()
+
+        # create the parsers action and add it to the positionals list
+        parsers_class = self._pop_action_class(kwargs, 'parsers')
+        action = parsers_class(option_strings=[], **kwargs)
+        self._subparsers._add_action(action)
+
+        # return the created parsers action
+        return action
+
+    def _add_action(self, action):
+        if action.option_strings:
+            self._optionals._add_action(action)
+        else:
+            self._positionals._add_action(action)
+        return action
+
+    def _get_optional_actions(self):
+        return [action
+                for action in self._actions
+                if action.option_strings]
+
+    def _get_positional_actions(self):
+        return [action
+                for action in self._actions
+                if not action.option_strings]
+
+    # =====================================
+    # Command line argument parsing methods
+    # =====================================
+    def parse_args(self, args=None, namespace=None):
+        args, argv = self.parse_known_args(args, namespace)
+        if argv:
+            msg = _('unrecognized arguments: %s')
+            self.error(msg % ' '.join(argv))
+        return args
+
+    def parse_known_args(self, args=None, namespace=None):
+        # args default to the system args
+        if args is None:
+            args = _sys.argv[1:]
+
+        # default Namespace built from parser defaults
+        if namespace is None:
+            namespace = Namespace()
+
+        # add any action defaults that aren't present
+        for action in self._actions:
+            if action.dest is not SUPPRESS:
+                if not hasattr(namespace, action.dest):
+                    if action.default is not SUPPRESS:
+                        default = action.default
+                        if isinstance(action.default, basestring):
+                            default = self._get_value(action, default)
+                        setattr(namespace, action.dest, default)
+
+        # add any parser defaults that aren't present
+        for dest in self._defaults:
+            if not hasattr(namespace, dest):
+                setattr(namespace, dest, self._defaults[dest])
+
+        # parse the arguments and exit if there are any errors
+        try:
+            namespace, args = self._parse_known_args(args, namespace)
+            if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
+                args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
+                delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
+            return namespace, args
+        except ArgumentError:
+            err = _sys.exc_info()[1]
+            self.error(str(err))
+
+    def _parse_known_args(self, arg_strings, namespace):
+        # replace arg strings that are file references
+        if self.fromfile_prefix_chars is not None:
+            arg_strings = self._read_args_from_files(arg_strings)
+
+        # map all mutually exclusive arguments to the other arguments
+        # they can't occur with
+        action_conflicts = {}
+        for mutex_group in self._mutually_exclusive_groups:
+            group_actions = mutex_group._group_actions
+            for i, mutex_action in enumerate(mutex_group._group_actions):
+                conflicts = action_conflicts.setdefault(mutex_action, [])
+                conflicts.extend(group_actions[:i])
+                conflicts.extend(group_actions[i + 1:])
+
+        # find all option indices, and determine the arg_string_pattern
+        # which has an 'O' if there is an option at an index,
+        # an 'A' if there is an argument, or a '-' if there is a '--'
+        option_string_indices = {}
+        arg_string_pattern_parts = []
+        arg_strings_iter = iter(arg_strings)
+        for i, arg_string in enumerate(arg_strings_iter):
+
+            # all args after -- are non-options
+            if arg_string == '--':
+                arg_string_pattern_parts.append('-')
+                for arg_string in arg_strings_iter:
+                    arg_string_pattern_parts.append('A')
+
+            # otherwise, add the arg to the arg strings
+            # and note the index if it was an option
+            else:
+                option_tuple = self._parse_optional(arg_string)
+                if option_tuple is None:
+                    pattern = 'A'
+                else:
+                    option_string_indices[i] = option_tuple
+                    pattern = 'O'
+                arg_string_pattern_parts.append(pattern)
+
+        # join the pieces together to form the pattern
+        arg_strings_pattern = ''.join(arg_string_pattern_parts)
+
+        # converts arg strings to the appropriate and then takes the action
+        seen_actions = set()
+        seen_non_default_actions = set()
+
+        def take_action(action, argument_strings, option_string=None):
+            seen_actions.add(action)
+            argument_values = self._get_values(action, argument_strings)
+
+            # error if this argument is not allowed with other previously
+            # seen arguments, assuming that actions that use the default
+            # value don't really count as "present"
+            if argument_values is not action.default:
+                seen_non_default_actions.add(action)
+                for conflict_action in action_conflicts.get(action, []):
+                    if conflict_action in seen_non_default_actions:
+                        msg = _('not allowed with argument %s')
+                        action_name = _get_action_name(conflict_action)
+                        raise ArgumentError(action, msg % action_name)
+
+            # take the action if we didn't receive a SUPPRESS value
+            # (e.g. from a default)
+            if argument_values is not SUPPRESS:
+                action(self, namespace, argument_values, option_string)
+
+        # function to convert arg_strings into an optional action
+        def consume_optional(start_index):
+
+            # get the optional identified at this index
+            option_tuple = option_string_indices[start_index]
+            action, option_string, explicit_arg = option_tuple
+
+            # identify additional optionals in the same arg string
+            # (e.g. -xyz is the same as -x -y -z if no args are required)
+            match_argument = self._match_argument
+            action_tuples = []
+            while True:
+
+                # if we found no optional action, skip it
+                if action is None:
+                    extras.append(arg_strings[start_index])
+                    return start_index + 1
+
+                # if there is an explicit argument, try to match the
+                # optional's string arguments to only this
+                if explicit_arg is not None:
+                    arg_count = match_argument(action, 'A')
+
+                    # if the action is a single-dash option and takes no
+                    # arguments, try to parse more single-dash options out
+                    # of the tail of the option string
+                    chars = self.prefix_chars
+                    if arg_count == 0 and option_string[1] not in chars:
+                        action_tuples.append((action, [], option_string))
+                        char = option_string[0]
+                        option_string = char + explicit_arg[0]
+                        new_explicit_arg = explicit_arg[1:] or None
+                        optionals_map = self._option_string_actions
+                        if option_string in optionals_map:
+                            action = optionals_map[option_string]
+                            explicit_arg = new_explicit_arg
+                        else:
+                            msg = _('ignored explicit argument %r')
+                            raise ArgumentError(action, msg % explicit_arg)
+
+                    # if the action expect exactly one argument, we've
+                    # successfully matched the option; exit the loop
+                    elif arg_count == 1:
+                        stop = start_index + 1
+                        args = [explicit_arg]
+                        action_tuples.append((action, args, option_string))
+                        break
+
+                    # error if a double-dash option did not use the
+                    # explicit argument
+                    else:
+                        msg = _('ignored explicit argument %r')
+                        raise ArgumentError(action, msg % explicit_arg)
+
+                # if there is no explicit argument, try to match the
+                # optional's string arguments with the following strings
+                # if successful, exit the loop
+                else:
+                    start = start_index + 1
+                    selected_patterns = arg_strings_pattern[start:]
+                    arg_count = match_argument(action, selected_patterns)
+                    stop = start + arg_count
+                    args = arg_strings[start:stop]
+                    action_tuples.append((action, args, option_string))
+                    break
+
+            # add the Optional to the list and return the index at which
+            # the Optional's string args stopped
+            assert action_tuples
+            for action, args, option_string in action_tuples:
+                take_action(action, args, option_string)
+            return stop
+
+        # the list of Positionals left to be parsed; this is modified
+        # by consume_positionals()
+        positionals = self._get_positional_actions()
+
+        # function to convert arg_strings into positional actions
+        def consume_positionals(start_index):
+            # match as many Positionals as possible
+            match_partial = self._match_arguments_partial
+            selected_pattern = arg_strings_pattern[start_index:]
+            arg_counts = match_partial(positionals, selected_pattern)
+
+            # slice off the appropriate arg strings for each Positional
+            # and add the Positional and its args to the list
+            for action, arg_count in zip(positionals, arg_counts):
+                args = arg_strings[start_index: start_index + arg_count]
+                start_index += arg_count
+                take_action(action, args)
+
+            # slice off the Positionals that we just parsed and return the
+            # index at which the Positionals' string args stopped
+            positionals[:] = positionals[len(arg_counts):]
+            return start_index
+
+        # consume Positionals and Optionals alternately, until we have
+        # passed the last option string
+        extras = []
+        start_index = 0
+        if option_string_indices:
+            max_option_string_index = max(option_string_indices)
+        else:
+            max_option_string_index = -1
+        while start_index <= max_option_string_index:
+
+            # consume any Positionals preceding the next option
+            next_option_string_index = min([
+                index
+                for index in option_string_indices
+                if index >= start_index])
+            if start_index != next_option_string_index:
+                positionals_end_index = consume_positionals(start_index)
+
+                # only try to parse the next optional if we didn't consume
+                # the option string during the positionals parsing
+                if positionals_end_index > start_index:
+                    start_index = positionals_end_index
+                    continue
+                else:
+                    start_index = positionals_end_index
+
+            # if we consumed all the positionals we could and we're not
+            # at the index of an option string, there were extra arguments
+            if start_index not in option_string_indices:
+                strings = arg_strings[start_index:next_option_string_index]
+                extras.extend(strings)
+                start_index = next_option_string_index
+
+            # consume the next optional and any arguments for it
+            start_index = consume_optional(start_index)
+
+        # consume any positionals following the last Optional
+        stop_index = consume_positionals(start_index)
+
+        # if we didn't consume all the argument strings, there were extras
+        extras.extend(arg_strings[stop_index:])
+
+        # if we didn't use all the Positional objects, there were too few
+        # arg strings supplied.
+        if positionals:
+            self.error(_('too few arguments'))
+
+        # make sure all required actions were present
+        for action in self._actions:
+            if action.required:
+                if action not in seen_actions:
+                    name = _get_action_name(action)
+                    self.error(_('argument %s is required') % name)
+
+        # make sure all required groups had one option present
+        for group in self._mutually_exclusive_groups:
+            if group.required:
+                for action in group._group_actions:
+                    if action in seen_non_default_actions:
+                        break
+
+                # if no actions were used, report the error
+                else:
+                    names = [_get_action_name(action)
+                             for action in group._group_actions
+                             if action.help is not SUPPRESS]
+                    msg = _('one of the arguments %s is required')
+                    self.error(msg % ' '.join(names))
+
+        # return the updated namespace and the extra arguments
+        return namespace, extras
+
+    def _read_args_from_files(self, arg_strings):
+        # expand arguments referencing files
+        new_arg_strings = []
+        for arg_string in arg_strings:
+
+            # for regular arguments, just add them back into the list
+            if arg_string[0] not in self.fromfile_prefix_chars:
+                new_arg_strings.append(arg_string)
+
+            # replace arguments referencing files with the file content
+            else:
+                try:
+                    args_file = open(arg_string[1:])
+                    try:
+                        arg_strings = []
+                        for arg_line in args_file.read().splitlines():
+                            for arg in self.convert_arg_line_to_args(arg_line):
+                                arg_strings.append(arg)
+                        arg_strings = self._read_args_from_files(arg_strings)
+                        new_arg_strings.extend(arg_strings)
+                    finally:
+                        args_file.close()
+                except IOError:
+                    err = _sys.exc_info()[1]
+                    self.error(str(err))
+
+        # return the modified argument list
+        return new_arg_strings
+
+    def convert_arg_line_to_args(self, arg_line):
+        return [arg_line]
+
+    def _match_argument(self, action, arg_strings_pattern):
+        # match the pattern for this action to the arg strings
+        nargs_pattern = self._get_nargs_pattern(action)
+        match = _re.match(nargs_pattern, arg_strings_pattern)
+
+        # raise an exception if we weren't able to find a match
+        if match is None:
+            nargs_errors = {
+                None: _('expected one argument'),
+                OPTIONAL: _('expected at most one argument'),
+                ONE_OR_MORE: _('expected at least one argument'),
+            }
+            default = _('expected %s argument(s)') % action.nargs
+            msg = nargs_errors.get(action.nargs, default)
+            raise ArgumentError(action, msg)
+
+        # return the number of arguments matched
+        return len(match.group(1))
+
+    def _match_arguments_partial(self, actions, arg_strings_pattern):
+        # progressively shorten the actions list by slicing off the
+        # final actions until we find a match
+        result = []
+        for i in range(len(actions), 0, -1):
+            actions_slice = actions[:i]
+            pattern = ''.join([self._get_nargs_pattern(action)
+                               for action in actions_slice])
+            match = _re.match(pattern, arg_strings_pattern)
+            if match is not None:
+                result.extend([len(string) for string in match.groups()])
+                break
+
+        # return the list of arg string counts
+        return result
+
+    def _parse_optional(self, arg_string):
+        # if it's an empty string, it was meant to be a positional
+        if not arg_string:
+            return None
+
+        # if it doesn't start with a prefix, it was meant to be positional
+        if not arg_string[0] in self.prefix_chars:
+            return None
+
+        # if the option string is present in the parser, return the action
+        if arg_string in self._option_string_actions:
+            action = self._option_string_actions[arg_string]
+            return action, arg_string, None
+
+        # if it's just a single character, it was meant to be positional
+        if len(arg_string) == 1:
+            return None
+
+        # if the option string before the "=" is present, return the action
+        if '=' in arg_string:
+            option_string, explicit_arg = arg_string.split('=', 1)
+            if option_string in self._option_string_actions:
+                action = self._option_string_actions[option_string]
+                return action, option_string, explicit_arg
+
+        # search through all possible prefixes of the option string
+        # and all actions in the parser for possible interpretations
+        option_tuples = self._get_option_tuples(arg_string)
+
+        # if multiple actions match, the option string was ambiguous
+        if len(option_tuples) > 1:
+            options = ', '.join([option_string
+                for action, option_string, explicit_arg in option_tuples])
+            tup = arg_string, options
+            self.error(_('ambiguous option: %s could match %s') % tup)
+
+        # if exactly one action matched, this segmentation is good,
+        # so return the parsed action
+        elif len(option_tuples) == 1:
+            option_tuple, = option_tuples
+            return option_tuple
+
+        # if it was not found as an option, but it looks like a negative
+        # number, it was meant to be positional
+        # unless there are negative-number-like options
+        if self._negative_number_matcher.match(arg_string):
+            if not self._has_negative_number_optionals:
+                return None
+
+        # if it contains a space, it was meant to be a positional
+        if ' ' in arg_string:
+            return None
+
+        # it was meant to be an optional but there is no such option
+        # in this parser (though it might be a valid option in a subparser)
+        return None, arg_string, None
+
+    def _get_option_tuples(self, option_string):
+        result = []
+
+        # option strings starting with two prefix characters are only
+        # split at the '='
+        chars = self.prefix_chars
+        if option_string[0] in chars and option_string[1] in chars:
+            if '=' in option_string:
+                option_prefix, explicit_arg = option_string.split('=', 1)
+            else:
+                option_prefix = option_string
+                explicit_arg = None
+            for option_string in self._option_string_actions:
+                if option_string.startswith(option_prefix):
+                    action = self._option_string_actions[option_string]
+                    tup = action, option_string, explicit_arg
+                    result.append(tup)
+
+        # single character options can be concatenated with their arguments
+        # but multiple character options always have to have their argument
+        # separate
+        elif option_string[0] in chars and option_string[1] not in chars:
+            option_prefix = option_string
+            explicit_arg = None
+            short_option_prefix = option_string[:2]
+            short_explicit_arg = option_string[2:]
+
+            for option_string in self._option_string_actions:
+                if option_string == short_option_prefix:
+                    action = self._option_string_actions[option_string]
+                    tup = action, option_string, short_explicit_arg
+                    result.append(tup)
+                elif option_string.startswith(option_prefix):
+                    action = self._option_string_actions[option_string]
+                    tup = action, option_string, explicit_arg
+                    result.append(tup)
+
+        # shouldn't ever get here
+        else:
+            self.error(_('unexpected option string: %s') % option_string)
+
+        # return the collected option tuples
+        return result
+
+    def _get_nargs_pattern(self, action):
+        # in all examples below, we have to allow for '--' args
+        # which are represented as '-' in the pattern
+        nargs = action.nargs
+
+        # the default (None) is assumed to be a single argument
+        if nargs is None:
+            nargs_pattern = '(-*A-*)'
+
+        # allow zero or one arguments
+        elif nargs == OPTIONAL:
+            nargs_pattern = '(-*A?-*)'
+
+        # allow zero or more arguments
+        elif nargs == ZERO_OR_MORE:
+            nargs_pattern = '(-*[A-]*)'
+
+        # allow one or more arguments
+        elif nargs == ONE_OR_MORE:
+            nargs_pattern = '(-*A[A-]*)'
+
+        # allow any number of options or arguments
+        elif nargs == REMAINDER:
+            nargs_pattern = '([-AO]*)'
+
+        # allow one argument followed by any number of options or arguments
+        elif nargs == PARSER:
+            nargs_pattern = '(-*A[-AO]*)'
+
+        # all others should be integers
+        else:
+            nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
+
+        # if this is an optional action, -- is not allowed
+        if action.option_strings:
+            nargs_pattern = nargs_pattern.replace('-*', '')
+            nargs_pattern = nargs_pattern.replace('-', '')
+
+        # return the pattern
+        return nargs_pattern
+
+    # ========================
+    # Value conversion methods
+    # ========================
+    def _get_values(self, action, arg_strings):
+        # for everything but PARSER args, strip out '--'
+        if action.nargs not in [PARSER, REMAINDER]:
+            arg_strings = [s for s in arg_strings if s != '--']
+
+        # optional argument produces a default when not present
+        if not arg_strings and action.nargs == OPTIONAL:
+            if action.option_strings:
+                value = action.const
+            else:
+                value = action.default
+            if isinstance(value, basestring):
+                value = self._get_value(action, value)
+                self._check_value(action, value)
+
+        # when nargs='*' on a positional, if there were no command-line
+        # args, use the default if it is anything other than None
+        elif (not arg_strings and action.nargs == ZERO_OR_MORE and
+              not action.option_strings):
+            if action.default is not None:
+                value = action.default
+            else:
+                value = arg_strings
+            self._check_value(action, value)
+
+        # single argument or optional argument produces a single value
+        elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
+            arg_string, = arg_strings
+            value = self._get_value(action, arg_string)
+            self._check_value(action, value)
+
+        # REMAINDER arguments convert all values, checking none
+        elif action.nargs == REMAINDER:
+            value = [self._get_value(action, v) for v in arg_strings]
+
+        # PARSER arguments convert all values, but check only the first
+        elif action.nargs == PARSER:
+            value = [self._get_value(action, v) for v in arg_strings]
+            self._check_value(action, value[0])
+
+        # all other types of nargs produce a list
+        else:
+            value = [self._get_value(action, v) for v in arg_strings]
+            for v in value:
+                self._check_value(action, v)
+
+        # return the converted value
+        return value
+
+    def _get_value(self, action, arg_string):
+        type_func = self._registry_get('type', action.type, action.type)
+        if not _callable(type_func):
+            msg = _('%r is not callable')
+            raise ArgumentError(action, msg % type_func)
+
+        # convert the value to the appropriate type
+        try:
+            result = type_func(arg_string)
+
+        # ArgumentTypeErrors indicate errors
+        except ArgumentTypeError:
+            name = getattr(action.type, '__name__', repr(action.type))
+            msg = str(_sys.exc_info()[1])
+            raise ArgumentError(action, msg)
+
+        # TypeErrors or ValueErrors also indicate errors
+        except (TypeError, ValueError):
+            name = getattr(action.type, '__name__', repr(action.type))
+            msg = _('invalid %s value: %r')
+            raise ArgumentError(action, msg % (name, arg_string))
+
+        # return the converted value
+        return result
+
+    def _check_value(self, action, value):
+        # converted value must be one of the choices (if specified)
+        if action.choices is not None and value not in action.choices:
+            tup = value, ', '.join(map(repr, action.choices))
+            msg = _('invalid choice: %r (choose from %s)') % tup
+            raise ArgumentError(action, msg)
+
+    # =======================
+    # Help-formatting methods
+    # =======================
+    def format_usage(self):
+        formatter = self._get_formatter()
+        formatter.add_usage(self.usage, self._actions,
+                            self._mutually_exclusive_groups)
+        return formatter.format_help()
+
+    def format_help(self):
+        formatter = self._get_formatter()
+
+        # usage
+        formatter.add_usage(self.usage, self._actions,
+                            self._mutually_exclusive_groups)
+
+        # description
+        formatter.add_text(self.description)
+
+        # positionals, optionals and user-defined groups
+        for action_group in self._action_groups:
+            formatter.start_section(action_group.title)
+            formatter.add_text(action_group.description)
+            formatter.add_arguments(action_group._group_actions)
+            formatter.end_section()
+
+        # epilog
+        formatter.add_text(self.epilog)
+
+        # determine help from format above
+        return formatter.format_help()
+
+    def format_version(self):
+        import warnings
+        warnings.warn(
+            'The format_version method is deprecated -- the "version" '
+            'argument to ArgumentParser is no longer supported.',
+            DeprecationWarning)
+        formatter = self._get_formatter()
+        formatter.add_text(self.version)
+        return formatter.format_help()
+
+    def _get_formatter(self):
+        return self.formatter_class(prog=self.prog)
+
+    # =====================
+    # Help-printing methods
+    # =====================
+    def print_usage(self, file=None):
+        if file is None:
+            file = _sys.stdout
+        self._print_message(self.format_usage(), file)
+
+    def print_help(self, file=None):
+        if file is None:
+            file = _sys.stdout
+        self._print_message(self.format_help(), file)
+
+    def print_version(self, file=None):
+        import warnings
+        warnings.warn(
+            'The print_version method is deprecated -- the "version" '
+            'argument to ArgumentParser is no longer supported.',
+            DeprecationWarning)
+        self._print_message(self.format_version(), file)
+
+    def _print_message(self, message, file=None):
+        if message:
+            if file is None:
+                file = _sys.stderr
+            file.write(message)
+
+    # ===============
+    # Exiting methods
+    # ===============
+    def exit(self, status=0, message=None):
+        if message:
+            self._print_message(message, _sys.stderr)
+        _sys.exit(status)
+
+    def error(self, message):
+        """error(message: string)
+
+        Prints a usage message incorporating the message to stderr and
+        exits.
+
+        If you override this in a subclass, it should not return -- it
+        should either exit or raise an exception.
+        """
+        self.print_usage(_sys.stderr)
+        self.exit(2, _('%s: error: %s\n') % (self.prog, message))
diff --git a/lib/spack/external/functools.py b/lib/spack/external/functools.py
new file mode 100644
index 0000000000000000000000000000000000000000..19f0903c8222e7988f6e1321aa4e32fd3f2e6ca8
--- /dev/null
+++ b/lib/spack/external/functools.py
@@ -0,0 +1,30 @@
+#
+# Backport of Python 2.7's total_ordering.
+#
+
+def total_ordering(cls):
+    """Class decorator that fills in missing ordering methods"""
+    convert = {
+        '__lt__': [('__gt__', lambda self, other: not (self < other or self == other)),
+                   ('__le__', lambda self, other: self < other or self == other),
+                   ('__ge__', lambda self, other: not self < other)],
+        '__le__': [('__ge__', lambda self, other: not self <= other or self == other),
+                   ('__lt__', lambda self, other: self <= other and not self == other),
+                   ('__gt__', lambda self, other: not self <= other)],
+        '__gt__': [('__lt__', lambda self, other: not (self > other or self == other)),
+                   ('__ge__', lambda self, other: self > other or self == other),
+                   ('__le__', lambda self, other: not self > other)],
+        '__ge__': [('__le__', lambda self, other: (not self >= other) or self == other),
+                   ('__gt__', lambda self, other: self >= other and not self == other),
+                   ('__lt__', lambda self, other: not self >= other)]
+    }
+    roots = set(dir(cls)) & set(convert)
+    if not roots:
+        raise ValueError('must define at least one ordering operation: < > <= >=')
+    root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
+    for opname, opfunc in convert[root]:
+        if opname not in roots:
+            opfunc.__name__ = opname
+            opfunc.__doc__ = getattr(int, opname).__doc__
+            setattr(cls, opname, opfunc)
+    return cls
diff --git a/lib/spack/external/ordereddict.py b/lib/spack/external/ordereddict.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ddad1477ea93148c6096a6ece3e14baf4e563e9
--- /dev/null
+++ b/lib/spack/external/ordereddict.py
@@ -0,0 +1,262 @@
+#
+# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
+# Passes Python2.7's test suite and incorporates all the latest updates.
+#
+# From http://code.activestate.com/recipes/576693-ordered-dictionary-for-py24/
+# This file is in the public domain, and has no particular license.
+#
+try:
+    from thread import get_ident as _get_ident
+except ImportError:
+    from dummy_thread import get_ident as _get_ident
+
+try:
+    from _abcoll import KeysView, ValuesView, ItemsView
+except ImportError:
+    pass
+
+
+class OrderedDict(dict):
+    'Dictionary that remembers insertion order'
+    # An inherited dict maps keys to values.
+    # The inherited dict provides __getitem__, __len__, __contains__, and get.
+    # The remaining methods are order-aware.
+    # Big-O running times for all methods are the same as for regular dictionaries.
+
+    # The internal self.__map dictionary maps keys to links in a doubly linked list.
+    # The circular doubly linked list starts and ends with a sentinel element.
+    # The sentinel element never gets deleted (this simplifies the algorithm).
+    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].
+
+    def __init__(self, *args, **kwds):
+        '''Initialize an ordered dictionary.  Signature is the same as for
+        regular dictionaries, but keyword arguments are not recommended
+        because their insertion order is arbitrary.
+
+        '''
+        if len(args) > 1:
+            raise TypeError('expected at most 1 arguments, got %d' % len(args))
+        try:
+            self.__root
+        except AttributeError:
+            self.__root = root = []                     # sentinel node
+            root[:] = [root, root, None]
+            self.__map = {}
+        self.__update(*args, **kwds)
+
+    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
+        'od.__setitem__(i, y) <==> od[i]=y'
+        # Setting a new item creates a new link which goes at the end of the linked
+        # list, and the inherited dictionary is updated with the new key/value pair.
+        if key not in self:
+            root = self.__root
+            last = root[0]
+            last[1] = root[0] = self.__map[key] = [last, root, key]
+        dict_setitem(self, key, value)
+
+    def __delitem__(self, key, dict_delitem=dict.__delitem__):
+        'od.__delitem__(y) <==> del od[y]'
+        # Deleting an existing item uses self.__map to find the link which is
+        # then removed by updating the links in the predecessor and successor nodes.
+        dict_delitem(self, key)
+        link_prev, link_next, key = self.__map.pop(key)
+        link_prev[1] = link_next
+        link_next[0] = link_prev
+
+    def __iter__(self):
+        'od.__iter__() <==> iter(od)'
+        root = self.__root
+        curr = root[1]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[1]
+
+    def __reversed__(self):
+        'od.__reversed__() <==> reversed(od)'
+        root = self.__root
+        curr = root[0]
+        while curr is not root:
+            yield curr[2]
+            curr = curr[0]
+
+    def clear(self):
+        'od.clear() -> None.  Remove all items from od.'
+        try:
+            for node in self.__map.itervalues():
+                del node[:]
+            root = self.__root
+            root[:] = [root, root, None]
+            self.__map.clear()
+        except AttributeError:
+            pass
+        dict.clear(self)
+
+    def popitem(self, last=True):
+        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+        Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+        '''
+        if not self:
+            raise KeyError('dictionary is empty')
+        root = self.__root
+        if last:
+            link = root[0]
+            link_prev = link[0]
+            link_prev[1] = root
+            root[0] = link_prev
+        else:
+            link = root[1]
+            link_next = link[1]
+            root[1] = link_next
+            link_next[0] = root
+        key = link[2]
+        del self.__map[key]
+        value = dict.pop(self, key)
+        return key, value
+
+    # -- the following methods do not depend on the internal structure --
+
+    def keys(self):
+        'od.keys() -> list of keys in od'
+        return list(self)
+
+    def values(self):
+        'od.values() -> list of values in od'
+        return [self[key] for key in self]
+
+    def items(self):
+        'od.items() -> list of (key, value) pairs in od'
+        return [(key, self[key]) for key in self]
+
+    def iterkeys(self):
+        'od.iterkeys() -> an iterator over the keys in od'
+        return iter(self)
+
+    def itervalues(self):
+        'od.itervalues -> an iterator over the values in od'
+        for k in self:
+            yield self[k]
+
+    def iteritems(self):
+        'od.iteritems -> an iterator over the (key, value) items in od'
+        for k in self:
+            yield (k, self[k])
+
+    def update(*args, **kwds):
+        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.
+
+        If E is a dict instance, does:           for k in E: od[k] = E[k]
+        If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]
+        Or if E is an iterable of items, does:   for k, v in E: od[k] = v
+        In either case, this is followed by:     for k, v in F.items(): od[k] = v
+
+        '''
+        if len(args) > 2:
+            raise TypeError('update() takes at most 2 positional '
+                            'arguments (%d given)' % (len(args),))
+        elif not args:
+            raise TypeError('update() takes at least 1 argument (0 given)')
+        self = args[0]
+        # Make progressively weaker assumptions about "other"
+        other = ()
+        if len(args) == 2:
+            other = args[1]
+        if isinstance(other, dict):
+            for key in other:
+                self[key] = other[key]
+        elif hasattr(other, 'keys'):
+            for key in other.keys():
+                self[key] = other[key]
+        else:
+            for key, value in other:
+                self[key] = value
+        for key, value in kwds.items():
+            self[key] = value
+
+    __update = update  # let subclasses override update without breaking __init__
+
+    __marker = object()
+
+    def pop(self, key, default=__marker):
+        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
+        If key is not found, d is returned if given, otherwise KeyError is raised.
+
+        '''
+        if key in self:
+            result = self[key]
+            del self[key]
+            return result
+        if default is self.__marker:
+            raise KeyError(key)
+        return default
+
+    def setdefault(self, key, default=None):
+        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+        if key in self:
+            return self[key]
+        self[key] = default
+        return default
+
+    def __repr__(self, _repr_running={}):
+        'od.__repr__() <==> repr(od)'
+        call_key = id(self), _get_ident()
+        if call_key in _repr_running:
+            return '...'
+        _repr_running[call_key] = 1
+        try:
+            if not self:
+                return '%s()' % (self.__class__.__name__,)
+            return '%s(%r)' % (self.__class__.__name__, self.items())
+        finally:
+            del _repr_running[call_key]
+
+    def __reduce__(self):
+        'Return state information for pickling'
+        items = [[k, self[k]] for k in self]
+        inst_dict = vars(self).copy()
+        for k in vars(OrderedDict()):
+            inst_dict.pop(k, None)
+        if inst_dict:
+            return (self.__class__, (items,), inst_dict)
+        return self.__class__, (items,)
+
+    def copy(self):
+        'od.copy() -> a shallow copy of od'
+        return self.__class__(self)
+
+    @classmethod
+    def fromkeys(cls, iterable, value=None):
+        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
+        and values equal to v (which defaults to None).
+
+        '''
+        d = cls()
+        for key in iterable:
+            d[key] = value
+        return d
+
+    def __eq__(self, other):
+        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
+        while comparison to a regular mapping is order-insensitive.
+
+        '''
+        if isinstance(other, OrderedDict):
+            return len(self)==len(other) and self.items() == other.items()
+        return dict.__eq__(self, other)
+
+    def __ne__(self, other):
+        return not self == other
+
+    # -- the following methods are only used in Python 2.7 --
+
+    def viewkeys(self):
+        "od.viewkeys() -> a set-like object providing a view on od's keys"
+        return KeysView(self)
+
+    def viewvalues(self):
+        "od.viewvalues() -> an object providing a view on od's values"
+        return ValuesView(self)
+
+    def viewitems(self):
+        "od.viewitems() -> a set-like object providing a view on od's items"
+        return ItemsView(self)
diff --git a/lib/spack/external/pyqver2.py b/lib/spack/external/pyqver2.py
new file mode 100755
index 0000000000000000000000000000000000000000..cd45bf948f8f97361ff097ed44c524c8615f7245
--- /dev/null
+++ b/lib/spack/external/pyqver2.py
@@ -0,0 +1,393 @@
+#!/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),
+    "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.Getattr):
+                r = rollup(n.expr)
+                if r:
+                    return r + "." + n.attrname
+        name = rollup(node.node)
+        if name:
+            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):
+    """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())
+
+
+if __name__ == '__main__':
+
+    Verbose = False
+    MinVersion = (2, 3)
+    Lint = False
+
+    files = []
+    i = 1
+    while i < len(sys.argv):
+        a = sys.argv[i]
+        if a == "--test":
+            import doctest
+            doctest.testmod()
+            sys.exit(0)
+        if a == "-v" or a == "--verbose":
+            Verbose = True
+        elif a == "-l" or a == "--lint":
+            Lint = True
+        elif a == "-m" or a == "--min-version":
+            i += 1
+            MinVersion = tuple(map(int, sys.argv[i].split(".")))
+        else:
+            files.append(a)
+        i += 1
+
+    if not files:
+        print >>sys.stderr, """Usage: %s [options] source ...
+
+        Report minimum Python version required to run given source files.
+
+        -m x.y or --min-version x.y (default 2.3)
+            report version triggers at or above version x.y in verbose mode
+        -v or --verbose
+            print more detailed report of version triggers for each version
+    """ % sys.argv[0]
+        sys.exit(1)
+
+    for fn in files:
+        try:
+            f = open(fn)
+            source = f.read()
+            f.close()
+            ver = get_versions(source)
+            if Verbose:
+                print fn
+                for v in sorted([k for k in ver.keys() if k >= MinVersion], reverse=True):
+                    reasons = [x for x in uniq(ver[v]) if x]
+                    if reasons:
+                        # each reason is (lineno, message)
+                        print "\t%s\t%s" % (".".join(map(str, v)), ", ".join([x[1] for x in reasons]))
+            elif Lint:
+                for v in sorted([k for k in ver.keys() if k >= MinVersion], reverse=True):
+                    reasons = [x for x in uniq(ver[v]) if x]
+                    for r in reasons:
+                        # each reason is (lineno, message)
+                        print "%s:%s: %s %s" % (fn, r[0], ".".join(map(str, v)), r[1])
+            else:
+                print "%s\t%s" % (".".join(map(str, max(ver.keys()))), fn)
+        except SyntaxError, x:
+            print "%s: syntax error compiling with Python %s: %s" % (fn, platform.python_version(), x)
diff --git a/lib/spack/spack/cmd/bootstrap.py b/lib/spack/spack/cmd/bootstrap.py
index 31c908d42b73cd041e3324c41c25d5f8e415fc23..f75b68b00ac302d941909b40d02b5696fff3b674 100644
--- a/lib/spack/spack/cmd/bootstrap.py
+++ b/lib/spack/spack/cmd/bootstrap.py
@@ -23,12 +23,13 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import os
-from subprocess import check_call, check_output
+from subprocess import check_call
 
 import llnl.util.tty as tty
-from llnl.util.filesystem import join_path
+from llnl.util.filesystem import join_path, mkdirp
 
 import spack
+from spack.util.executable import which
 
 description = "Create a new installation of spack in another prefix"
 
@@ -38,8 +39,10 @@ def setup_parser(subparser):
 
 def get_origin_url():
     git_dir = join_path(spack.prefix, '.git')
-    origin_url = check_output(
-        ['git', '--git-dir=%s' % git_dir, 'config', '--get', 'remote.origin.url'])
+    git = which('git', required=True)
+    origin_url = git(
+        '--git-dir=%s' % git_dir, 'config', '--get', 'remote.origin.url',
+        return_output=True)
     return origin_url.strip()
 
 
@@ -49,6 +52,11 @@ def bootstrap(parser, args):
 
     tty.msg("Fetching spack from origin: %s" % origin_url)
 
+    if os.path.isfile(prefix):
+        tty.die("There is already a file at %s" % prefix)
+
+    mkdirp(prefix)
+
     if os.path.exists(join_path(prefix, '.git')):
         tty.die("There already seems to be a git repository in %s" % prefix)
 
@@ -62,10 +70,11 @@ def bootstrap(parser, args):
             "%s/lib/spack/..." % prefix)
 
     os.chdir(prefix)
-    check_call(['git', 'init', '--shared', '-q'])
-    check_call(['git', 'remote', 'add', 'origin', origin_url])
-    check_call(['git', 'fetch', 'origin', 'master:refs/remotes/origin/master', '-n', '-q'])
-    check_call(['git', 'reset', '--hard', 'origin/master', '-q'])
+    git = which('git', required=True)
+    git('init', '--shared', '-q')
+    git('remote', 'add', 'origin', origin_url)
+    git('fetch', 'origin', 'master:refs/remotes/origin/master', '-n', '-q')
+    git('reset', '--hard', 'origin/master', '-q')
 
     tty.msg("Successfully created a new spack in %s" % prefix,
             "Run %s/bin/spack to use this installation." % prefix)
diff --git a/lib/spack/spack/cmd/checksum.py b/lib/spack/spack/cmd/checksum.py
index f5cf0d0143c2286bb7204f68e1a56d24a7c4253e..5a8109b70f2717cc39f969e37346ee513f9ffd58 100644
--- a/lib/spack/spack/cmd/checksum.py
+++ b/lib/spack/spack/cmd/checksum.py
@@ -24,7 +24,7 @@
 ##############################################################################
 import os
 import re
-import argparse
+from external import argparse
 import hashlib
 from pprint import pprint
 from subprocess import CalledProcessError
diff --git a/lib/spack/spack/cmd/clean.py b/lib/spack/spack/cmd/clean.py
index 6091cae6c8e1aa05c654f5c02683ab6b2c831228..1df9d87ae26aa365fdfd653087767cf1570121b7 100644
--- a/lib/spack/spack/cmd/clean.py
+++ b/lib/spack/spack/cmd/clean.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 
 import llnl.util.tty as tty
 
diff --git a/lib/spack/spack/cmd/compiler.py b/lib/spack/spack/cmd/compiler.py
index a4cd2df7e2f7f57e7c5c68b5943225665bf12f7c..ac9c844a4c83eb3a8eac7158eaacba7848d81a11 100644
--- a/lib/spack/spack/cmd/compiler.py
+++ b/lib/spack/spack/cmd/compiler.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 
 import llnl.util.tty as tty
 from llnl.util.tty.colify import colify
diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py
index 85f96420197c9280505ba7b336b17db31023c11b..283bfc19b9f2dd18fb36f41b9e93455b66e25088 100644
--- a/lib/spack/spack/cmd/config.py
+++ b/lib/spack/spack/cmd/config.py
@@ -23,7 +23,7 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import sys
-import argparse
+from external import argparse
 
 import llnl.util.tty as tty
 
diff --git a/lib/spack/spack/cmd/dependents.py b/lib/spack/spack/cmd/dependents.py
index 129a4eeb237037d009760f5976465c292fc06bba..652f243b98809e0f81952b443c9a7a24c135bd69 100644
--- a/lib/spack/spack/cmd/dependents.py
+++ b/lib/spack/spack/cmd/dependents.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 
 import llnl.util.tty as tty
 
diff --git a/lib/spack/spack/cmd/fetch.py b/lib/spack/spack/cmd/fetch.py
index 1dd8703daf23a004c513bad91b41cadb6578612d..0ccebd9486f66e90c0d42eb89129c786458623be 100644
--- a/lib/spack/spack/cmd/fetch.py
+++ b/lib/spack/spack/cmd/fetch.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 
 import spack
 import spack.cmd
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
index 72df69d18a8e0b554dbc8f5fcdd8c8aa37049d06..2238484a2167352d271272daa56880edbb263850 100644
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -24,7 +24,7 @@
 ##############################################################################
 import sys
 import collections
-import argparse
+from external import argparse
 from StringIO import StringIO
 
 import llnl.util.tty as tty
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index 4570d6c40f4752c65ed4a985567e5cd531cc3a25..2374d02feba0763dfd1ec8b9a1f172df1e6eb62d 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -23,7 +23,7 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import sys
-import argparse
+from external import argparse
 
 import spack
 import spack.cmd
diff --git a/lib/spack/spack/cmd/load.py b/lib/spack/spack/cmd/load.py
index 5bc6b15784c6c537c5465f641fdb1aff6d1dca8a..06574d9725d3da5fff9c7463114475067171929d 100644
--- a/lib/spack/spack/cmd/load.py
+++ b/lib/spack/spack/cmd/load.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 import spack.modules
 
 description ="Add package to environment using modules."
diff --git a/lib/spack/spack/cmd/mirror.py b/lib/spack/spack/cmd/mirror.py
index 129ac6bd459eb988f2b84247662856ce4b1a4d5e..a039e3383a339d0981c831f411a60243ae716f49 100644
--- a/lib/spack/spack/cmd/mirror.py
+++ b/lib/spack/spack/cmd/mirror.py
@@ -24,10 +24,10 @@
 ##############################################################################
 import os
 import shutil
-import argparse
 from datetime import datetime
 from contextlib import closing
 
+from external import argparse
 import llnl.util.tty as tty
 from llnl.util.tty.colify import colify
 from llnl.util.filesystem import mkdirp, join_path
diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py
index 4f6de18532eaef18d9d1bfa821ae4f157f5ae908..f23f3d2f9f212cacd208584e7b07bb27166cbab6 100644
--- a/lib/spack/spack/cmd/module.py
+++ b/lib/spack/spack/cmd/module.py
@@ -25,7 +25,7 @@
 import sys
 import os
 import shutil
-import argparse
+from external import argparse
 
 import llnl.util.tty as tty
 from llnl.util.lang import partition_list
diff --git a/lib/spack/spack/cmd/patch.py b/lib/spack/spack/cmd/patch.py
index 2356583b07466f681223f7ae3b38e6988d00e1c5..a6556c4828c14afe32be2f54f19034313934d5ef 100644
--- a/lib/spack/spack/cmd/patch.py
+++ b/lib/spack/spack/cmd/patch.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 
 import spack.cmd
 import spack
diff --git a/lib/spack/spack/cmd/providers.py b/lib/spack/spack/cmd/providers.py
index 1a652c82d173f016e3bb353100623f53562f0838..2bcdc9fba2aebf2b2acb23e2ab4a7f316a3f9334 100644
--- a/lib/spack/spack/cmd/providers.py
+++ b/lib/spack/spack/cmd/providers.py
@@ -23,7 +23,7 @@
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
 import os
-import argparse
+from external import argparse
 
 from llnl.util.tty.colify import colify
 
diff --git a/lib/spack/spack/cmd/python.py b/lib/spack/spack/cmd/python.py
index 641394044c13a6969dda0796e41ac6c7b2c781da..86b8c827f8f4c6a7d9fcd47946bd4f1bf4e62fa3 100644
--- a/lib/spack/spack/cmd/python.py
+++ b/lib/spack/spack/cmd/python.py
@@ -25,7 +25,7 @@
 import os
 import sys
 import code
-import argparse
+from external import argparse
 import platform
 from contextlib import closing
 
diff --git a/lib/spack/spack/cmd/spec.py b/lib/spack/spack/cmd/spec.py
index fb5a900c3f91f7dbd1d4257eb5c10b62dc03b08d..5fcb0a9b5aa1f0e2a8fe8ca22c5f1825cd9af1f4 100644
--- a/lib/spack/spack/cmd/spec.py
+++ b/lib/spack/spack/cmd/spec.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 import spack.cmd
 
 import llnl.util.tty as tty
diff --git a/lib/spack/spack/cmd/stage.py b/lib/spack/spack/cmd/stage.py
index 1bf1f93c2fbd20a397ec1498d6a44d6c910db513..2673cdc266117ee431fe9bd9dfd3be239c33cb6a 100644
--- a/lib/spack/spack/cmd/stage.py
+++ b/lib/spack/spack/cmd/stage.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 
 import spack
 import spack.cmd
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index 73c98a203ba51ff4a02087273a2ec2adcfabde1b..84eb4703a6802d8091171f9a64930c6ebc8516cc 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 
 import llnl.util.tty as tty
 
diff --git a/lib/spack/spack/cmd/unload.py b/lib/spack/spack/cmd/unload.py
index 24e49b3f24aed0d40c8edb798fa3d981cca40e81..6442c48cb15ee95fb727f1fc9e6523645861ffdb 100644
--- a/lib/spack/spack/cmd/unload.py
+++ b/lib/spack/spack/cmd/unload.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 import spack.modules
 
 description ="Remove package from environment using module."
diff --git a/lib/spack/spack/cmd/unuse.py b/lib/spack/spack/cmd/unuse.py
index 7f0b384ea05bbb336b5a6289dbe79968bfec21ed..2a7229a3a0143dbabcee62fd864518439a22560e 100644
--- a/lib/spack/spack/cmd/unuse.py
+++ b/lib/spack/spack/cmd/unuse.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 import spack.modules
 
 description ="Remove package from environment using dotkit."
diff --git a/lib/spack/spack/cmd/use.py b/lib/spack/spack/cmd/use.py
index 4990fea2f821441947872007ce463fd21cd1fa24..e34c1947390cb2edb14a3461340428a46fba6f1c 100644
--- a/lib/spack/spack/cmd/use.py
+++ b/lib/spack/spack/cmd/use.py
@@ -22,7 +22,7 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##############################################################################
-import argparse
+from external import argparse
 import spack.modules
 
 description ="Add package to environment using dotkit."
diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py
index 716356bdd2a568289086dcbfabdd23a7cc2ccc74..582f49eaf2861eda3271b26fc78d02765c64aa58 100644
--- a/lib/spack/spack/compiler.py
+++ b/lib/spack/spack/compiler.py
@@ -189,7 +189,7 @@ def check(key):
                 return None
 
         successful = [key for key in parmap(check, checks) if key is not None]
-        return { (v, p, s) : path for v, p, s, path in successful }
+        return dict(((v, p, s), path) for v, p, s, path in successful)
 
     @classmethod
     def find(cls, *path):
diff --git a/lib/spack/spack/compilers/__init__.py b/lib/spack/spack/compilers/__init__.py
index c5bfe21ed43ab02bc8416ed7d52d0b35e4c64321..467472cced5aaf301b41b676d572cc8a3b127371 100644
--- a/lib/spack/spack/compilers/__init__.py
+++ b/lib/spack/spack/compilers/__init__.py
@@ -176,7 +176,7 @@ def compilers_for_spec(compiler_spec):
     config = _get_config()
 
     def get_compiler(cspec):
-        items = { k:v for k,v in config.items('compiler "%s"' % cspec) }
+        items = dict((k,v) for k,v in config.items('compiler "%s"' % cspec))
 
         if not all(n in items for n in _required_instance_vars):
             raise InvalidCompilerConfigurationError(cspec)
diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py
index 5494adc324a680d087a3e352b0a9af80c5c3474f..85ee16a1c2070a3eb58fc7cdee6ff122b827c27e 100644
--- a/lib/spack/spack/config.py
+++ b/lib/spack/spack/config.py
@@ -84,10 +84,9 @@
 import re
 import inspect
 import ConfigParser as cp
-from collections import OrderedDict
 
+from external.ordereddict import OrderedDict
 from llnl.util.lang import memoized
-
 import spack.error
 
 __all__ = [
@@ -222,7 +221,6 @@ class SpackConfigParser(cp.RawConfigParser):
     """
     # Slightly modify Python option expressions to allow leading whitespace
     OPTCRE    = re.compile(r'\s*' + cp.RawConfigParser.OPTCRE.pattern)
-    OPTCRE_NV = re.compile(r'\s*' + cp.RawConfigParser.OPTCRE_NV.pattern)
 
     def __init__(self, file_or_files):
         cp.RawConfigParser.__init__(self, dict_type=OrderedDict)
@@ -341,14 +339,13 @@ def write(self, path_or_fp=None):
 
 
     def _read(self, fp, fpname):
-        """This is a copy of Python 2.7's _read() method, with support for
-           continuation lines removed.
-        """
-        cursect = None                        # None, or a dictionary
+        """This is a copy of Python 2.6's _read() method, with support for
+           continuation lines removed."""
+        cursect = None                            # None, or a dictionary
         optname = None
-        lineno = 0
         comment = 0
-        e = None                              # None, or an exception
+        lineno = 0
+        e = None                                  # None, or an exception
         while True:
             line = fp.readline()
             if not line:
@@ -359,7 +356,6 @@ def _read(self, fp, fpname):
                 (line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR")):
                 self._sections["comment-%d" % comment] = line
                 comment += 1
-                continue
             # a section header or option header?
             else:
                 # is it a section header?
@@ -381,27 +377,21 @@ def _read(self, fp, fpname):
                     raise cp.MissingSectionHeaderError(fpname, lineno, line)
                 # an option line?
                 else:
-                    mo = self._optcre.match(line)
+                    mo = self.OPTCRE.match(line)
                     if mo:
                         optname, vi, optval = mo.group('option', 'vi', 'value')
+                        if vi in ('=', ':') and ';' in optval:
+                            # ';' is a comment delimiter only if it follows
+                            # a spacing character
+                            pos = optval.find(';')
+                            if pos != -1 and optval[pos-1].isspace():
+                                optval = optval[:pos]
+                        optval = optval.strip()
+                        # allow empty values
+                        if optval == '""':
+                            optval = ''
                         optname = self.optionxform(optname.rstrip())
-                        # This check is fine because the OPTCRE cannot
-                        # match if it would set optval to None
-                        if optval is not None:
-                            if vi in ('=', ':') and ';' in optval:
-                                # ';' is a comment delimiter only if it follows
-                                # a spacing character
-                                pos = optval.find(';')
-                                if pos != -1 and optval[pos-1].isspace():
-                                    optval = optval[:pos]
-                            optval = optval.strip()
-                            # allow empty values
-                            if optval == '""':
-                                optval = ''
-                            cursect[optname] = [optval]
-                        else:
-                            # valueless option handling
-                            cursect[optname] = optval
+                        cursect[optname] = optval
                     else:
                         # a non-fatal parsing error occurred.  set up the
                         # exception but keep going. the exception will be
@@ -414,23 +404,13 @@ def _read(self, fp, fpname):
         if e:
             raise e
 
-        # join the multi-line values collected while reading
-        all_sections = [self._defaults]
-        all_sections.extend(self._sections.values())
-        for options in all_sections:
-            # skip comments
-            if isinstance(options, basestring):
-                continue
 
-            for name, val in options.items():
-                if isinstance(val, list):
-                    options[name] = '\n'.join(val)
 
 
     def _write(self, fp):
         """Write an .ini-format representation of the configuration state.
 
-           This is taken from the default Python 2.7 source.  It writes 4
+           This is taken from the default Python 2.6 source.  It writes 4
            spaces at the beginning of lines instead of no leading space.
         """
         if self._defaults:
@@ -449,11 +429,9 @@ def _write(self, fp):
                 # Allow leading whitespace
                 fp.write("[%s]\n" % section)
                 for (key, value) in self._sections[section].items():
-                    if key == "__name__":
-                        continue
-                    if (value is not None) or (self._optcre == self.OPTCRE):
-                        key = " = ".join((key, str(value).replace('\n', '\n\t')))
-                    fp.write("    %s\n" % (key))
+                    if key != "__name__":
+                        fp.write("    %s = %s\n" %
+                                 (key, str(value).replace('\n', '\n\t')))
 
 
 class SpackConfigurationError(spack.error.SpackError):
diff --git a/lib/spack/spack/error.py b/lib/spack/spack/error.py
index 40e0e75fdb4e4f62ab271d6c9c6097bcc5ce0b8e..8bcbd83c288d96207e00e5b6d0a9ee6305fa6465 100644
--- a/lib/spack/spack/error.py
+++ b/lib/spack/spack/error.py
@@ -28,7 +28,8 @@ class SpackError(Exception):
        Subclasses can be found in the modules they have to do with.
     """
     def __init__(self, message, long_message=None):
-        super(SpackError, self).__init__(message)
+        super(SpackError, self).__init__()
+        self.message = message
         self.long_message = long_message
 
 
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 8df658e6603737c814284056d9d8d53d6c6f3b5e..b0a9dd76b97c9643ab8b17c9b36caebafd292b26 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -360,7 +360,7 @@ def sanity_check_dict(attr_name):
 
         # Version-ize the keys in versions dict
         try:
-            self.versions = { Version(v):h for v,h in self.versions.items() }
+            self.versions = dict((Version(v), h) for v,h in self.versions.items())
         except ValueError:
             raise ValueError("Keys of versions dict in package %s must be versions!"
                              % self.name)
diff --git a/lib/spack/spack/relations.py b/lib/spack/spack/relations.py
index a7b46cfb331e109b88609d04cdfbac2f7c1669d5..5afb7e7624d59d2bcd3a17b6afc7580d35a8b9aa 100644
--- a/lib/spack/spack/relations.py
+++ b/lib/spack/spack/relations.py
@@ -72,7 +72,6 @@ class Mpileaks(Package):
 
 import re
 import inspect
-import importlib
 
 from llnl.util.lang import *
 
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index aa6397271b70672f5c693ddd0e7046a4ee835c65..4838fd99466b197ed583f91afc7c647cc3012b7c 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -838,7 +838,7 @@ def normalize(self, **kwargs):
 
         # If there are deps specified but not visited, they're not
         # actually deps of this package.  Raise an error.
-        extra = set(spec_deps.viewkeys()).difference(visited)
+        extra = set(spec_deps.keys()).difference(visited)
 
         # Also subtract out all the packags that provide a needed vpkg
         vdeps = [v for v in self.package.virtual_dependencies()]
diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py
index 839555d6303bfb885668640e0f8919202cdacc68..3dac7983963092aee5fb8b7890efefdda3a411b4 100644
--- a/lib/spack/spack/stage.py
+++ b/lib/spack/spack/stage.py
@@ -120,8 +120,7 @@ def _need_to_create_path(self):
             if spack.use_tmp_stage:
                 # If we're using a tmp dir, it's a link, and it points at the right spot,
                 # then keep it.
-                if (os.path.commonprefix((real_path, real_tmp)) == real_tmp
-                    and os.path.exists(real_path)):
+                if (real_path.startswith(real_tmp) and os.path.exists(real_path)):
                     return False
                 else:
                     # otherwise, just unlink it and start over.
diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py
index c2dfc51aa3f26c7d02a1045f09bd6a1247bfba4a..8ddc7f227d85d2e6f7376282d8abf81f836f3668 100644
--- a/lib/spack/spack/test/__init__.py
+++ b/lib/spack/spack/test/__init__.py
@@ -46,7 +46,8 @@
               'install',
               'package_sanity',
               'config',
-              'directory_layout']
+              'directory_layout',
+              'python_version']
 
 
 def list_tests():
@@ -71,7 +72,7 @@ def run(names, verbose=False):
 
     runner = unittest.TextTestRunner(verbosity=verbosity)
 
-    testsRun = errors = failures = skipped = 0
+    testsRun = errors = failures = 0
     for test in names:
         module = 'spack.test.' + test
         print module
@@ -82,12 +83,10 @@ def run(names, verbose=False):
         testsRun += result.testsRun
         errors   += len(result.errors)
         failures += len(result.failures)
-        skipped  += len(result.skipped)
 
     succeeded = not errors and not failures
     tty.msg("Tests Complete.",
             "%5d tests run" % testsRun,
-            "%5d skipped" % skipped,
             "%5d failures" % failures,
             "%5d errors" % errors)
 
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 6ad2ef29d8d37627974ab45782f37515dd185ed6..a7f4812c8cf068ecfdaeef867ecd562aa9781021 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -134,29 +134,29 @@ def test_concretize_with_provides_when(self):
     def test_virtual_is_fully_expanded_for_callpath(self):
         # force dependence on fake "zmpi" by asking for MPI 10.0
         spec = Spec('callpath ^mpi@10.0')
-        self.assertIn('mpi', spec.dependencies)
-        self.assertNotIn('fake', spec)
+        self.assertTrue('mpi' in spec.dependencies)
+        self.assertFalse('fake' in spec)
 
         spec.concretize()
 
-        self.assertIn('zmpi', spec.dependencies)
-        self.assertNotIn('mpi', spec)
-        self.assertIn('fake', spec.dependencies['zmpi'])
+        self.assertTrue('zmpi' in spec.dependencies)
+        self.assertFalse('mpi' in spec)
+        self.assertTrue('fake' in spec.dependencies['zmpi'])
 
 
     def test_virtual_is_fully_expanded_for_mpileaks(self):
         spec = Spec('mpileaks ^mpi@10.0')
-        self.assertIn('mpi', spec.dependencies)
-        self.assertNotIn('fake', spec)
+        self.assertTrue('mpi' in spec.dependencies)
+        self.assertFalse('fake' in spec)
 
         spec.concretize()
 
-        self.assertIn('zmpi', spec.dependencies)
-        self.assertIn('callpath', spec.dependencies)
-        self.assertIn('zmpi', spec.dependencies['callpath'].dependencies)
-        self.assertIn('fake', spec.dependencies['callpath'].dependencies['zmpi'].dependencies)
+        self.assertTrue('zmpi' in spec.dependencies)
+        self.assertTrue('callpath' in spec.dependencies)
+        self.assertTrue('zmpi' in spec.dependencies['callpath'].dependencies)
+        self.assertTrue('fake' in spec.dependencies['callpath'].dependencies['zmpi'].dependencies)
 
-        self.assertNotIn('mpi', spec)
+        self.assertFalse('mpi' in spec)
 
 
     def test_my_dep_depends_on_provider_of_my_virtual_dep(self):
diff --git a/lib/spack/spack/test/mock_packages_test.py b/lib/spack/spack/test/mock_packages_test.py
index adde70ff6c276700559bc556e3bbdbadf399cbfa..e94837603935eff2df33de3b9717f80b31c38168 100644
--- a/lib/spack/spack/test/mock_packages_test.py
+++ b/lib/spack/spack/test/mock_packages_test.py
@@ -39,7 +39,6 @@ def set_pkg_dep(pkg, spec):
 
 
 class MockPackagesTest(unittest.TestCase):
-    @classmethod
     def setUp(self):
         # Use the mock packages database for these tests.  This allows
         # us to set up contrived packages that don't interfere with
@@ -52,7 +51,7 @@ def setUp(self):
             'site' : spack.mock_site_config,
             'user' : spack.mock_user_config }
 
-    @classmethod
+
     def tearDown(self):
         """Restore the real packages path after any test."""
         spack.db = self.real_db
diff --git a/lib/spack/spack/test/python_version.py b/lib/spack/spack/test/python_version.py
new file mode 100644
index 0000000000000000000000000000000000000000..04b4eadf3452bf91227a9d91dbff1752367f4c37
--- /dev/null
+++ b/lib/spack/spack/test/python_version.py
@@ -0,0 +1,97 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+"""
+This test ensures that all Spack files are Python version 2.6 or less.
+
+Spack was originally 2.7, but enough systems in 2014 are still using
+2.6 on their frontend nodes that we need 2.6 to get adopted.
+"""
+import unittest
+import os
+import re
+from contextlib import closing
+
+import llnl.util.tty as tty
+
+from external import pyqver2
+import spack
+
+spack_max_version = (2,6)
+
+class PythonVersionTest(unittest.TestCase):
+
+    def spack_python_files(self):
+        # first file is the spack script.
+        yield spack.spack_file
+        yield os.path.join(spack.build_env_path, 'cc')
+
+        # Next files are all the source files and package files.
+        search_paths = [spack.lib_path, spack.var_path]
+
+        # Iterate through the whole spack source tree.
+        for path in search_paths:
+            for root, dirnames, filenames in os.walk(path):
+                for filename in filenames:
+                    if re.match(r'^[^.#].*\.py$', filename):
+                        yield os.path.join(root, filename)
+
+
+    def test_python_versions(self):
+        # dict version -> filename -> reasons
+        all_issues = {}
+
+        for fn in self.spack_python_files():
+            with closing(open(fn)) as pyfile:
+                versions = pyqver2.get_versions(pyfile.read())
+                for ver, reasons in versions.items():
+                    if ver > spack_max_version:
+                        if not ver in all_issues:
+                            all_issues[ver] = {}
+                        all_issues[ver][fn] = reasons
+
+        if all_issues:
+            tty.error("Spack must run on Python version %d.%d"
+                      % spack_max_version)
+
+        for v in sorted(all_issues.keys(), reverse=True):
+            msgs = []
+            for fn in sorted(all_issues[v].keys()):
+                short_fn = fn
+                if fn.startswith(spack.prefix):
+                    short_fn = fn[len(spack.prefix):]
+
+                reasons = [r for r in set(all_issues[v][fn]) if r]
+                for r in reasons:
+                    msgs.append(("%s:%s" % ('spack' + short_fn, r[0]), r[1]))
+
+            tty.error("These files require version %d.%d:" % v)
+            maxlen = max(len(f) for f, prob in msgs)
+            fmt = "%%-%ds%%s" % (maxlen+3)
+            print fmt % ('File', 'Reason')
+            print fmt % ('-' * (maxlen), '-' * 20)
+            for msg in msgs:
+                print fmt % msg
+
+        self.assertTrue(len(all_issues) == 0)
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index 322f34cf024b57cbd9f5f14d3af75237540eb23e..fb67aa8a8da02658b96b533d01bff443da5e6ed9 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -57,10 +57,10 @@ def test_preorder_node_traversal(self):
         pairs = zip([0,1,2,3,4,2,3], names)
 
         traversal = dag.traverse()
-        self.assertListEqual([x.name for x in traversal], names)
+        self.assertEqual([x.name for x in traversal], names)
 
         traversal = dag.traverse(depth=True)
-        self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
+        self.assertEqual([(x, y.name) for x,y in traversal], pairs)
 
 
     def test_preorder_edge_traversal(self):
@@ -72,10 +72,10 @@ def test_preorder_edge_traversal(self):
         pairs = zip([0,1,2,3,4,3,2,3,1], names)
 
         traversal = dag.traverse(cover='edges')
-        self.assertListEqual([x.name for x in traversal], names)
+        self.assertEqual([x.name for x in traversal], names)
 
         traversal = dag.traverse(cover='edges', depth=True)
-        self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
+        self.assertEqual([(x, y.name) for x,y in traversal], pairs)
 
 
     def test_preorder_path_traversal(self):
@@ -87,10 +87,10 @@ def test_preorder_path_traversal(self):
         pairs = zip([0,1,2,3,4,3,2,3,1,2], names)
 
         traversal = dag.traverse(cover='paths')
-        self.assertListEqual([x.name for x in traversal], names)
+        self.assertEqual([x.name for x in traversal], names)
 
         traversal = dag.traverse(cover='paths', depth=True)
-        self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
+        self.assertEqual([(x, y.name) for x,y in traversal], pairs)
 
 
     def test_postorder_node_traversal(self):
@@ -102,10 +102,10 @@ def test_postorder_node_traversal(self):
         pairs = zip([4,3,2,3,2,1,0], names)
 
         traversal = dag.traverse(order='post')
-        self.assertListEqual([x.name for x in traversal], names)
+        self.assertEqual([x.name for x in traversal], names)
 
         traversal = dag.traverse(depth=True, order='post')
-        self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
+        self.assertEqual([(x, y.name) for x,y in traversal], pairs)
 
 
     def test_postorder_edge_traversal(self):
@@ -117,10 +117,10 @@ def test_postorder_edge_traversal(self):
         pairs = zip([4,3,3,2,3,2,1,1,0], names)
 
         traversal = dag.traverse(cover='edges', order='post')
-        self.assertListEqual([x.name for x in traversal], names)
+        self.assertEqual([x.name for x in traversal], names)
 
         traversal = dag.traverse(cover='edges', depth=True, order='post')
-        self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
+        self.assertEqual([(x, y.name) for x,y in traversal], pairs)
 
 
     def test_postorder_path_traversal(self):
@@ -132,10 +132,10 @@ def test_postorder_path_traversal(self):
         pairs = zip([4,3,3,2,3,2,1,2,1,0], names)
 
         traversal = dag.traverse(cover='paths', order='post')
-        self.assertListEqual([x.name for x in traversal], names)
+        self.assertEqual([x.name for x in traversal], names)
 
         traversal = dag.traverse(cover='paths', depth=True, order='post')
-        self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
+        self.assertEqual([(x, y.name) for x,y in traversal], pairs)
 
 
     def test_conflicting_spec_constraints(self):
@@ -199,13 +199,13 @@ def test_normalize_with_virtual_spec(self):
     def check_links(self, spec_to_check):
         for spec in spec_to_check.traverse():
             for dependent in spec.dependents.values():
-                self.assertIn(
-                    spec.name, dependent.dependencies,
+                self.assertTrue(
+                    spec.name in dependent.dependencies,
                     "%s not in dependencies of %s" % (spec.name, dependent.name))
 
             for dependency in spec.dependencies.values():
-                self.assertIn(
-                    spec.name, dependency.dependents,
+                self.assertTrue(
+                    spec.name in dependency.dependents,
                     "%s not in dependents of %s" % (spec.name, dependency.name))
 
 
@@ -385,13 +385,13 @@ def test_normalize_with_virtual_package(self):
 
     def test_contains(self):
         spec = Spec('mpileaks ^mpi ^libelf@1.8.11 ^libdwarf')
-        self.assertIn(Spec('mpi'), spec)
-        self.assertIn(Spec('libelf'), spec)
-        self.assertIn(Spec('libelf@1.8.11'), spec)
-        self.assertNotIn(Spec('libelf@1.8.12'), spec)
-        self.assertIn(Spec('libdwarf'), spec)
-        self.assertNotIn(Spec('libgoblin'), spec)
-        self.assertIn(Spec('mpileaks'), spec)
+        self.assertTrue(Spec('mpi') in spec)
+        self.assertTrue(Spec('libelf') in spec)
+        self.assertTrue(Spec('libelf@1.8.11') in spec)
+        self.assertFalse(Spec('libelf@1.8.12') in spec)
+        self.assertTrue(Spec('libdwarf') in spec)
+        self.assertFalse(Spec('libgoblin') in spec)
+        self.assertTrue(Spec('mpileaks') in spec)
 
 
     def test_copy_simple(self):
diff --git a/lib/spack/spack/test/stage.py b/lib/spack/spack/test/stage.py
index 08899f9810596dc0030ecc1551d02a27b41696ff..a412549dc71cdffc09ef7cbb97a2a7a4ff637b07 100644
--- a/lib/spack/spack/test/stage.py
+++ b/lib/spack/spack/test/stage.py
@@ -51,28 +51,20 @@
 stage_name = 'spack-test-stage'
 
 
-class with_tmp(object):
-    """Decorator that executes a function with or without spack set to use
-       a temp dir.  Spack allows builds to happen directly in the
-       stage directory or in a tmp dir and symlinked into the stage
-       directory, so this lets us use the same test in both cases.
+@contextmanager
+def use_tmp(use_tmp):
+    """Allow some test code to be executed with spack.use_tmp_stage
+       set to a certain value.  Context manager makes sure it's reset
+       on failure.
     """
-    def __init__(self, use_tmp):
-        self.use_tmp = use_tmp
-
-    def __call__(self, fun):
-        use_tmp = self.use_tmp
-        def new_test_function(self):
-            old_tmp = spack.use_tmp_stage
-            spack.use_tmp_stage = use_tmp
-            fun(self)
-            spack.use_tmp_stage = old_tmp
-        return new_test_function
+    old_tmp = spack.use_tmp_stage
+    spack.use_tmp_stage = use_tmp
+    yield
+    spack.use_tmp_stage = old_tmp
 
 
 class StageTest(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls):
+    def setUp(self):
         """This sets up a mock archive to fetch, and a mock temp space for use
            by the Stage class.  It doesn't actually create the Stage -- that
            is done by individual tests.
@@ -92,52 +84,58 @@ def setUpClass(cls):
             tar('czf', archive_name, archive_dir)
 
         # Make spack use the test environment for tmp stuff.
-        cls.old_tmp_dirs = spack.tmp_dirs
+        self.old_tmp_dirs = spack.tmp_dirs
         spack.tmp_dirs = [test_tmp_path]
 
+        # record this since this test changes to directories that will
+        # be removed.
+        self.working_dir = os.getcwd()
+
 
-    @classmethod
-    def tearDownClass(cls):
+    def tearDown(self):
         """Blows away the test environment directory."""
         shutil.rmtree(test_files_dir)
 
+        # chdir back to original working dir
+        os.chdir(self.working_dir)
+
         # restore spack's original tmp environment
-        spack.tmp_dirs = cls.old_tmp_dirs
+        spack.tmp_dirs = self.old_tmp_dirs
 
 
     def get_stage_path(self, stage, stage_name):
-        """Figure out based on a stage and an intended name where it should
-           be living.  This depends on whether it's named or not.
+        """Figure out where a stage should be living.  This depends on
+           whether it's named.
         """
-        if stage_name:
+        if stage_name is not None:
             # If it is a named stage, we know where the stage should be
-            stage_path = join_path(spack.stage_path, stage_name)
+            return join_path(spack.stage_path, stage_name)
         else:
             # If it's unnamed, ensure that we ran mkdtemp in the right spot.
-            stage_path = stage.path
-            self.assertIsNotNone(stage_path)
-            self.assertEqual(
-                os.path.commonprefix((stage_path, spack.stage_path)),
-                spack.stage_path)
-        return stage_path
+            self.assertTrue(stage.path is not None)
+            self.assertTrue(stage.path.startswith(spack.stage_path))
+            return stage.path
 
 
     def check_setup(self, stage, stage_name):
         """Figure out whether a stage was set up correctly."""
         stage_path = self.get_stage_path(stage, stage_name)
+
+        # Ensure stage was created in the spack stage directory
         self.assertTrue(os.path.isdir(stage_path))
 
         if spack.use_tmp_stage:
-            # Make sure everything was created and linked correctly for
-            # a tmp stage.
+            # Check that the stage dir is really a symlink.
             self.assertTrue(os.path.islink(stage_path))
 
+            # Make sure it points to a valid directory
             target = os.path.realpath(stage_path)
             self.assertTrue(os.path.isdir(target))
             self.assertFalse(os.path.islink(target))
-            self.assertEqual(
-                os.path.commonprefix((target, test_tmp_path)),
-                test_tmp_path)
+
+            # Make sure the directory is in the place we asked it to
+            # be (see setUp and tearDown)
+            self.assertTrue(target.startswith(test_tmp_path))
 
         else:
             # Make sure the stage path is NOT a link for a non-tmp stage
@@ -146,15 +144,15 @@ def check_setup(self, stage, stage_name):
 
     def check_fetch(self, stage, stage_name):
         stage_path = self.get_stage_path(stage, stage_name)
-        self.assertIn(archive_name, os.listdir(stage_path))
+        self.assertTrue(archive_name in os.listdir(stage_path))
         self.assertEqual(join_path(stage_path, archive_name),
                          stage.archive_file)
 
 
     def check_expand_archive(self, stage, stage_name):
         stage_path = self.get_stage_path(stage, stage_name)
-        self.assertIn(archive_name, os.listdir(stage_path))
-        self.assertIn(archive_dir, os.listdir(stage_path))
+        self.assertTrue(archive_name in os.listdir(stage_path))
+        self.assertTrue(archive_dir in os.listdir(stage_path))
 
         self.assertEqual(
             join_path(stage_path, archive_dir),
@@ -192,32 +190,40 @@ def check_destroy(self, stage, stage_name):
             self.assertFalse(os.path.exists(target))
 
 
-    def checkSetupAndDestroy(self, stage_name=None):
-        stage = Stage(archive_url, name=stage_name)
-        self.check_setup(stage, stage_name)
-
-        stage.destroy()
-        self.check_destroy(stage, stage_name)
-
-
-    @with_tmp(True)
     def test_setup_and_destroy_name_with_tmp(self):
-        self.checkSetupAndDestroy(stage_name)
+        with use_tmp(True):
+            stage = Stage(archive_url, name=stage_name)
+            self.check_setup(stage, stage_name)
+
+            stage.destroy()
+            self.check_destroy(stage, stage_name)
 
 
-    @with_tmp(False)
     def test_setup_and_destroy_name_without_tmp(self):
-        self.checkSetupAndDestroy(stage_name)
+        with use_tmp(False):
+            stage = Stage(archive_url, name=stage_name)
+            self.check_setup(stage, stage_name)
+
+            stage.destroy()
+            self.check_destroy(stage, stage_name)
 
 
-    @with_tmp(True)
     def test_setup_and_destroy_no_name_with_tmp(self):
-        self.checkSetupAndDestroy(None)
+        with use_tmp(True):
+            stage = Stage(archive_url)
+            self.check_setup(stage, None)
+
+            stage.destroy()
+            self.check_destroy(stage, None)
 
 
-    @with_tmp(False)
     def test_setup_and_destroy_no_name_without_tmp(self):
-        self.checkSetupAndDestroy(None)
+        with use_tmp(False):
+            stage = Stage(archive_url)
+            self.check_setup(stage, None)
+
+            stage.destroy()
+            self.check_destroy(stage, None)
 
 
     def test_chdir(self):
@@ -286,7 +292,7 @@ def test_restage(self):
         with closing(open('foobar', 'w')) as file:
             file.write("this file is to be destroyed.")
 
-        self.assertIn('foobar', os.listdir(stage.expanded_archive_path))
+        self.assertTrue('foobar' in os.listdir(stage.expanded_archive_path))
 
         # Make sure the file is not there after restage.
         stage.restage()
@@ -295,7 +301,7 @@ def test_restage(self):
 
         stage.chdir_to_archive()
         self.check_chdir_to_archive(stage, stage_name)
-        self.assertNotIn('foobar', os.listdir(stage.expanded_archive_path))
+        self.assertFalse('foobar' in os.listdir(stage.expanded_archive_path))
 
         stage.destroy()
         self.check_destroy(stage, stage_name)
diff --git a/lib/spack/spack/util/crypto.py b/lib/spack/spack/util/crypto.py
index 4d8681bed95e3a91dac1140912b39977a1e9736f..950e807596983ccd6f791272e54cc2c3b6d1cc87 100644
--- a/lib/spack/spack/util/crypto.py
+++ b/lib/spack/spack/util/crypto.py
@@ -35,7 +35,7 @@
     hashlib.sha512 ]
 
 """Index for looking up hasher for a digest."""
-_size_to_hash = { h().digest_size : h for h in _acceptable_hashes }
+_size_to_hash = dict((h().digest_size, h) for h in _acceptable_hashes)
 
 
 def checksum(hashlib_algo, filename, **kwargs):
diff --git a/lib/spack/spack/version.py b/lib/spack/spack/version.py
index 4558f8838438b7446ecaf38c2be32122fbb3f64c..fbf86db8e1887aeb75cd8388957c755d512b9621 100644
--- a/lib/spack/spack/version.py
+++ b/lib/spack/spack/version.py
@@ -47,7 +47,8 @@
 import sys
 import re
 from bisect import bisect_left
-from functools import total_ordering, wraps
+from functools import wraps
+from external.functools import total_ordering
 
 import llnl.util.compare.none_high as none_high
 import llnl.util.compare.none_low as none_low