diff --git a/lib/spack/spack/cmd/python.py b/lib/spack/spack/cmd/python.py
index 746f0fbe50f53210451c0a8f3cb04452ac2b639a..d7e8e1411fa71126e393f72cb36a34e6d521370d 100644
--- a/lib/spack/spack/cmd/python.py
+++ b/lib/spack/spack/cmd/python.py
@@ -5,6 +5,9 @@
 
 import spack
 
+def setup_parser(subparser):
+    subparser.add_argument('file', nargs='?', help="file to run")
+
 description = "Launch an interpreter as spack would launch a command"
 
 def python(parser, args):
@@ -16,6 +19,10 @@ def python(parser, args):
             with closing(open(startup_file)) as startup:
                 console.runsource(startup.read(), startup_file, 'exec')
 
-    console.interact("Spack version %s\nPython %s, %s %s"""
-                     % (spack.spack_version, platform.python_version(),
-                        platform.system(), platform.machine()))
+    if args.file:
+        with closing(open(args.file)) as file:
+            console.runsource(file.read(), args.file, 'exec')
+    else:
+        console.interact("Spack version %s\nPython %s, %s %s"""
+                         % (spack.spack_version, platform.python_version(),
+                            platform.system(), platform.machine()))
diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py
index e4674b89592fb6ab73972ab265f18ac5b434317f..f29f33f144b8ec55e8584d28501c3f587b134a26 100644
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -12,8 +12,9 @@
 import spack.arch
 import spack.compilers
 import spack.packages
+import spack.spec
 from spack.version import *
-from spack.spec import *
+
 
 
 class DefaultConcretizer(object):
@@ -31,12 +32,12 @@ def concretize_version(self, spec):
         if spec.versions.concrete:
             return
 
-        pkg = spec.package
-
         # If there are known avaialble versions, return the most recent
-        available_versions = pkg.available_versions
-        if available_versions:
-            spec.versions = ver([available_versions[-1]])
+        # version that satisfies the spec
+        pkg = spec.package
+        valid_versions = pkg.available_versions.intersection(spec.versions)
+        if valid_versions:
+            spec.versions = ver([valid_versions[-1]])
         else:
             spec.versions = ver([pkg.version])
 
@@ -91,9 +92,11 @@ def choose_provider(self, spec, providers):
         """This is invoked for virtual specs.  Given a spec with a virtual name,
            say "mpi", and a list of specs of possible providers of that spec,
            select a provider and return it.
-
-           Default implementation just chooses the last provider in sorted order.
         """
         assert(spec.virtual)
         assert(providers)
-        return sorted(providers)[-1]
+
+        index = spack.spec.index_specs(providers)
+        first_key = sorted(index.keys())[0]
+        latest_version = sorted(index[first_key])[-1]
+        return latest_version
diff --git a/lib/spack/spack/packages/__init__.py b/lib/spack/spack/packages/__init__.py
index d4d1d0b7866f5be9fccd2140219b0bc6418fe564..68a5f7978ba97820132dde4679d2461dc421ef00 100644
--- a/lib/spack/spack/packages/__init__.py
+++ b/lib/spack/spack/packages/__init__.py
@@ -39,41 +39,53 @@ class ProviderIndex(object):
        Calling find_provider(spec) will find a package that provides a
        matching implementation of MPI.
     """
-    def __init__(self, providers):
-        """Takes a list of provider packagse and build an index of the virtual
-           packages they provide."""
+    def __init__(self, specs, **kwargs):
+        restrict = kwargs.setdefault('restrict', False)
         self.providers = {}
-        self.add(*providers)
 
+        for spec in specs:
+            if type(spec) != spack.spec.Spec:
+                spec = spack.spec.Spec(spec)
 
-    def add(self, *providers):
-        """Look at the provided map on the provider packages, invert it and
-           add it to this provider index."""
-        for pkg in providers:
+            if spec.virtual:
+                continue
+
+            pkg = spec.package
             for provided_spec, provider_spec in pkg.provided.iteritems():
-                provided_name = provided_spec.name
-                if provided_name not in self.providers:
-                    self.providers[provided_name] = {}
-                self.providers[provided_name][provided_spec] = provider_spec
+                if provider_spec.satisfies(spec):
+                    provided_name = provided_spec.name
+                    if provided_name not in self.providers:
+                        self.providers[provided_name] = {}
+
+                    if restrict:
+                        self.providers[provided_name][provided_spec] = spec
+
+                    else:
+                        # Before putting the spec in the map, constrain it so that
+                        # it provides what was asked for.
+                        constrained = spec.copy()
+                        constrained.constrain(provider_spec)
+                        self.providers[provided_name][provided_spec] = constrained
+
 
 
     def providers_for(self, *vpkg_specs):
         """Gives names of all packages that provide virtual packages
            with the supplied names."""
-        packages = set()
+        providers = set()
         for vspec in vpkg_specs:
             # Allow string names to be passed as input, as well as specs
             if type(vspec) == str:
                 vspec = spack.spec.Spec(vspec)
 
-            # Add all the packages that satisfy the vpkg spec.
+            # Add all the providers that satisfy the vpkg spec.
             if vspec.name in self.providers:
-                for provider_spec, pkg in self.providers[vspec.name].items():
+                for provider_spec, spec in self.providers[vspec.name].items():
                     if provider_spec.satisfies(vspec):
-                        packages.add(pkg)
+                        providers.add(spec)
 
-        # Return packages in order
-        return sorted(packages)
+        # Return providers in order
+        return sorted(providers)
 
 
 def get(pkg_name):
@@ -86,7 +98,7 @@ def get(pkg_name):
 
 def providers_for(vpkg_spec):
     if providers_for.index is None:
-        providers_for.index = ProviderIndex(all_packages())
+        providers_for.index = ProviderIndex(all_package_names())
 
     providers = providers_for.index.providers_for(vpkg_spec)
     if not providers:
diff --git a/lib/spack/spack/packages/mpich.py b/lib/spack/spack/packages/mpich.py
index d8cd67d5283ab8654ce3697cd90f7acc6e1d22d9..c8e84901e6e14f1f0cea6511eeb7cf710adb915c 100644
--- a/lib/spack/spack/packages/mpich.py
+++ b/lib/spack/spack/packages/mpich.py
@@ -5,6 +5,9 @@ class Mpich(Package):
     url      = "http://www.mpich.org/static/downloads/3.0.4/mpich-3.0.4.tar.gz"
     md5      = "9c5d5d4fe1e17dd12153f40bc5b6dbc0"
 
+    list_url   = "http://www.mpich.org/static/downloads/"
+    list_depth = 2
+
     def install(self, prefix):
         configure("--prefix=%s" % prefix)
         make()
diff --git a/lib/spack/spack/parse.py b/lib/spack/spack/parse.py
index f2381b1078bb3a6eee4270d2d9fada6e32d7747c..64a0ff9cf85475517a29bce71b9ba93fc7d85e49 100644
--- a/lib/spack/spack/parse.py
+++ b/lib/spack/spack/parse.py
@@ -43,9 +43,9 @@ def lex(self, text):
 class Parser(object):
     """Base class for simple recursive descent parsers."""
     def __init__(self, lexer):
-        self.tokens = iter([]) # iterators over tokens, handled in order.  Starts empty.
-        self.token = None      # last accepted token
-        self.next = None       # next token
+        self.tokens = iter([])   # iterators over tokens, handled in order.  Starts empty.
+        self.token = Token(None) # last accepted token starts at beginning of file
+        self.next = None         # next token
         self.lexer = lexer
         self.text = None
 
diff --git a/lib/spack/spack/relations.py b/lib/spack/spack/relations.py
index 2c8395d4086106705a20728d46591cdc291de745..ab884db528ddf282db001576d18ad62f54330e1c 100644
--- a/lib/spack/spack/relations.py
+++ b/lib/spack/spack/relations.py
@@ -94,8 +94,8 @@ def _ensure_caller_is_spack_package():
 
 def _parse_local_spec(spec_like, pkg_name):
     """Allow the user to omit the package name part of a spec in relations.
-       e.g., provides('mpi@2.1', when='@1.9:') says that this package provides
-       MPI 2.1 when its version is higher than 1.9.
+       e.g., provides('mpi@2', when='@1.9:') says that this package provides
+       MPI-3 when its version is higher than 1.9.
     """
     if type(spec_like) not in (str, Spec):
         raise TypeError('spec must be Spec or spec string.  Found %s'
@@ -104,7 +104,7 @@ def _parse_local_spec(spec_like, pkg_name):
     if type(spec_like) == str:
         try:
             local_spec = Spec(spec_like)
-        except ParseError:
+        except spack.parse.ParseError:
             local_spec = Spec(pkg_name + spec_like)
             if local_spec.name != pkg_name: raise ValueError(
                     "Invalid spec for package %s: %s" % (pkg_name, spec_like))
@@ -118,21 +118,17 @@ def _parse_local_spec(spec_like, pkg_name):
     return local_spec
 
 
-
-
-def _make_relation(map_name):
-    def relation_fun(*specs):
-        _ensure_caller_is_spack_package()
-        package_map = _caller_locals().setdefault(map_name, {})
-        for string in specs:
-            for spec in spack.spec.parse(string):
-                package_map[spec.name] = spec
-    return relation_fun
-
-
 """Adds a dependencies local variable in the locals of
    the calling class, based on args. """
-depends_on = _make_relation("dependencies")
+def depends_on(*specs):
+    pkg = _ensure_caller_is_spack_package()
+
+    dependencies = _caller_locals().setdefault('dependencies', {})
+    for string in specs:
+        for spec in spack.spec.parse(string):
+            if pkg == spec.name:
+                raise CircularDependencyError('depends_on', pkg)
+            dependencies[spec.name] = spec
 
 
 def provides(*specs, **kwargs):
@@ -153,13 +149,30 @@ def provides(*specs, **kwargs):
 """Packages can declare conflicts with other packages.
    This can be as specific as you like: use regular spec syntax.
 """
-conflicts  = _make_relation("conflicted")
+def conflicts(*specs):
+    # TODO: implement conflicts
+    pass
 
 
 
-class ScopeError(spack.error.SpackError):
+
+class RelationError(spack.error.SpackError):
+    """This is raised when something is wrong with a package relation."""
+    def __init__(self, relation, message):
+        super(RelationError, self).__init__(message)
+        self.relation = relation
+
+
+class ScopeError(RelationError):
     """This is raised when a relation is called from outside a spack package."""
     def __init__(self, relation):
         super(ScopeError, self).__init__(
+            relation,
             "Cannot inovke '%s' from outside of a Spack package!" % relation)
-        self.relation = relation
+
+
+class CircularDependencyError(RelationError):
+    """This is raised when something depends on itself."""
+    def __init__(self, relation, package):
+        super(CircularDependencyError, self).__init__(
+            relation, "Package %s cannot depend on itself." % package)
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index a7c3488cd55c6894ed70414eccafea439989dff7..d853aa78e94f4b37b0b1462e6c9669635a27ada5 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -93,6 +93,19 @@
 separators = '[%s]' % ''.join(color_formats.keys())
 
 
+def index_specs(specs):
+    """Take a list of specs and return a dict of lists.  Dict is
+       keyed by spec name and lists include all specs with the
+       same name.
+    """
+    spec_dict = {}
+    for spec in specs:
+        if not spec.name in spec_dict:
+            spec_dict[spec.name] = []
+        spec_dict[spec.name].append(spec)
+    return spec_dict
+
+
 def colorize_spec(spec):
     """Returns a spec colorized according to the colors specified in
        color_formats."""
@@ -360,11 +373,17 @@ def preorder_traversal(self, visited=None, d=0, **kwargs):
         """Generic preorder traversal of the DAG represented by this spec.
            This will yield each node in the spec.  Options:
 
-           unique   [=True]
-               When True (default), every node in the DAG is yielded only once.
-               When False, the traversal will yield already visited
-               nodes but not their children.  This lets you see that a node
-               points to an already-visited subgraph without descending into it.
+           cover    [=nodes|edges|paths]
+               Determines how extensively to cover the dag.  Possible vlaues:
+
+               'nodes': Visit each node in the dag only once.  Every node
+                        yielded by this function will be unique.
+               'edges': If a node has been visited once but is reached along a
+                        new path from the root, yield it but do not descend
+                        into it.  This traverses each 'edge' in the DAG once.
+               'paths': Explore every unique path reachable from the root.
+                        This descends into visited subtrees and will yield
+                        nodes twice if they're reachable by multiple paths.
 
            depth    [=False]
                Defaults to False.  When True, yields not just nodes in the
@@ -375,30 +394,37 @@ def preorder_traversal(self, visited=None, d=0, **kwargs):
                Allow a custom key function to track the identity of nodes
                in the traversal.
 
-           noroot   [=False]
-               If true, this won't yield the root node, just its descendents.
+           root     [=True]
+               If false, this won't yield the root node, just its descendents.
         """
-        unique = kwargs.setdefault('unique', True)
-        depth  = kwargs.setdefault('depth', False)
-        keyfun = kwargs.setdefault('key', id)
-        noroot = kwargs.setdefault('noroot', False)
+        depth      = kwargs.setdefault('depth', False)
+        key_fun    = kwargs.setdefault('key', id)
+        yield_root = kwargs.setdefault('root', True)
+        cover      = kwargs.setdefault('cover', 'nodes')
+
+        cover_values = ('nodes', 'edges', 'paths')
+        if cover not in cover_values:
+            raise ValueError("Invalid value for cover: %s.  Choices are %s"
+                             % (cover, ",".join(cover_values)))
 
         if visited is None:
             visited = set()
 
-        if keyfun(self) in visited:
-            if not unique:
-                yield (d, self) if depth else self
-            return
-        visited.add(keyfun(self))
+        result = (d, self) if depth else self
+        key = key_fun(self)
 
-        if d > 0 or not noroot:
-            yield (d, self) if depth else self
+        if key in visited:
+            if cover == 'nodes':    return
+            if yield_root or d > 0: yield result
+            if cover == 'edges':    return
+        else:
+            if yield_root or d > 0: yield result
 
-        for key in sorted(self.dependencies.keys()):
-            for result in self.dependencies[key].preorder_traversal(
-                    visited, d+1, **kwargs):
-                yield result
+        visited.add(key)
+        for name in sorted(self.dependencies):
+            child = self.dependencies[name]
+            for elt in child.preorder_traversal(visited, d+1, **kwargs):
+                yield elt
 
 
     def _concretize_helper(self, presets=None, visited=None):
@@ -436,6 +462,14 @@ def _expand_virtual_packages(self):
         """Find virtual packages in this spec, replace them with providers,
            and normalize again to include the provider's (potentially virtual)
            dependencies.  Repeat until there are no virtual deps.
+
+           TODO: If a provider depends on something that conflicts with
+                 other dependencies in the spec being expanded, this can
+                 produce a conflicting spec.  For example, if mpich depends
+                 on hwloc@:1.3 but something in the spec needs hwloc1.4:,
+                 then we should choose an MPI other than mpich.  Cases like
+                 this are infrequent, but should implement this before it is
+                 a problem.
         """
         while True:
             virtuals =[v for v in self.preorder_traversal() if v.virtual]
@@ -545,8 +579,7 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
                 providers = provider_index.providers_for(pkg_dep)
 
                 # If there is a provider for the vpkg, then use that instead of
-                # the virtual package.  If there isn't a provider, just merge
-                # constraints on the virtual package.
+                # the virtual package.
                 if providers:
                     # Can't have multiple providers for the same thing in one spec.
                     if len(providers) > 1:
@@ -613,7 +646,7 @@ def normalize(self):
         # Remove virtual deps that are already provided by something in the spec
         spec_packages = [d.package for d in spec_deps.values() if not d.virtual]
 
-        index = packages.ProviderIndex(spec_packages)
+        index = packages.ProviderIndex(spec_deps.values(), restrict=True)
         visited = set()
         self._normalize_helper(visited, spec_deps, index)
 
@@ -666,6 +699,9 @@ def constrain(self, other):
 
 
     def satisfies(self, other):
+        if type(other) != Spec:
+            other = Spec(other)
+
         def sat(attribute):
             s = getattr(self, attribute)
             o = getattr(other, attribute)
@@ -716,6 +752,7 @@ def copy(self, **kwargs):
         clone._dup(self, **kwargs)
         return clone
 
+
     @property
     def version(self):
         if not self.concrete:
@@ -723,6 +760,27 @@ def version(self):
         return self.versions[0]
 
 
+    def __getitem__(self, name):
+        """TODO: does the way this is written make sense?"""
+        for spec in self.preorder_traversal():
+            if spec.name == name:
+                return spec
+
+        raise KeyError("No spec with name %s in %s" % (name, self))
+
+
+    def __contains__(self, spec):
+        """True if this spec has any dependency that satisfies the supplied
+           spec."""
+        if type(spec) != Spec:
+            spec = Spec(spec)
+
+        for s in self.preorder_traversal():
+            if s.satisfies(spec):
+                return True
+        return False
+
+
     def _cmp_key(self):
         return (self.name, self.versions, self.variants,
                 self.architecture, self.compiler, self.dependencies)
@@ -757,16 +815,20 @@ def tree(self, **kwargs):
         """Prints out this spec and its dependencies, tree-formatted
            with indentation."""
         color = kwargs.get('color', False)
+        depth = kwargs.get('depth', False)
+        cover = kwargs.get('cover', 'paths')
 
         out = ""
         cur_id = 0
         ids = {}
-        for d, node in self.preorder_traversal(unique=False, depth=True):
+        for d, node in self.preorder_traversal(cover=cover, depth=True):
+            if depth:
+                out += "%-4d" % d
             if not id(node) in ids:
                 cur_id += 1
                 ids[id(node)] = cur_id
-            out += str(ids[id(node)])
-            out += " "+ ("    " * d)
+            out += "%-4d" % ids[id(node)]
+            out += ("    " * d)
             out += node.str_no_deps(color=color) + "\n"
         return out
 
@@ -777,7 +839,7 @@ def __repr__(self):
 
     def __str__(self):
         byname = lambda d: d.name
-        deps = self.preorder_traversal(key=byname, noroot=True)
+        deps = self.preorder_traversal(key=byname, root=False)
         sorted_deps = sorted(deps, key=byname)
         dep_string = ''.join("^" + dep.str_no_deps() for dep in sorted_deps)
         return self.str_no_deps() + dep_string
@@ -1011,8 +1073,8 @@ class MultipleProviderError(SpecError):
     """
     def __init__(self, vpkg, providers):
         """Takes the name of the vpkg"""
-        super(NoProviderError, self).__init__(
-            "Multiple providers found for vpkg '%s': %s"
+        super(MultipleProviderError, self).__init__(
+            "Multiple providers found for '%s': %s"
             % (vpkg, [str(s) for s in providers]))
         self.vpkg = vpkg
         self.providers = providers
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index d1a82a58b130c0c55054dccd819be9f002bde780..b27b6b68134941a5d20e20b4fb4ca0a2699e5f8e 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -1,4 +1,6 @@
 import unittest
+
+import spack.packages as packages
 from spack.spec import Spec
 from spack.test.mock_packages_test import *
 
@@ -35,8 +37,99 @@ def test_concretize_no_deps(self):
 
 
     def test_concretize_dag(self):
-        spec = Spec('mpileaks')
-        spec.normalize()
-
         self.check_concretize('callpath')
         self.check_concretize('mpileaks')
+        self.check_concretize('libelf')
+
+
+    def test_concretize_with_virtual(self):
+        self.check_concretize('mpileaks ^mpi')
+        self.check_concretize('mpileaks ^mpi@:1.1')
+        self.check_concretize('mpileaks ^mpi@2:')
+        self.check_concretize('mpileaks ^mpi@2.1')
+        self.check_concretize('mpileaks ^mpi@2.2')
+        self.check_concretize('mpileaks ^mpi@2.2')
+        self.check_concretize('mpileaks ^mpi@:1')
+        self.check_concretize('mpileaks ^mpi@1.2:2')
+
+
+    def test_concretize_with_restricted_virtual(self):
+        self.check_concretize('mpileaks ^mpich2')
+
+        concrete = self.check_concretize('mpileaks   ^mpich2@1.1')
+        self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.1'))
+
+        concrete = self.check_concretize('mpileaks   ^mpich2@1.2')
+        self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.2'))
+
+        concrete = self.check_concretize('mpileaks   ^mpich2@:1.5')
+        self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.5'))
+
+        concrete = self.check_concretize('mpileaks   ^mpich2@:1.3')
+        self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.3'))
+
+        concrete = self.check_concretize('mpileaks   ^mpich2@:1.2')
+        self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.2'))
+
+        concrete = self.check_concretize('mpileaks   ^mpich2@:1.1')
+        self.assertTrue(concrete['mpich2'].satisfies('mpich2@:1.1'))
+
+        concrete = self.check_concretize('mpileaks   ^mpich2@1.1:')
+        self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.1:'))
+
+        concrete = self.check_concretize('mpileaks   ^mpich2@1.5:')
+        self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.5:'))
+
+        concrete = self.check_concretize('mpileaks   ^mpich2@1.3.1:1.4')
+        self.assertTrue(concrete['mpich2'].satisfies('mpich2@1.3.1:1.4'))
+
+
+    def test_concretize_with_provides_when(self):
+        """Make sure insufficient versions of MPI are not in providers list when
+           we ask for some advanced version.
+        """
+        self.assertTrue(not any(spec.satisfies('mpich2@:1.0')
+                                for spec in packages.providers_for('mpi@2.1')))
+
+        self.assertTrue(not any(spec.satisfies('mpich2@:1.1')
+                                for spec in packages.providers_for('mpi@2.2')))
+
+        self.assertTrue(not any(spec.satisfies('mpich2@:1.1')
+                                for spec in packages.providers_for('mpi@2.2')))
+
+        self.assertTrue(not any(spec.satisfies('mpich@:1')
+                                for spec in packages.providers_for('mpi@2')))
+
+        self.assertTrue(not any(spec.satisfies('mpich@:1')
+                                for spec in packages.providers_for('mpi@3')))
+
+        self.assertTrue(not any(spec.satisfies('mpich2')
+                                for spec in packages.providers_for('mpi@3')))
+
+
+    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)
+
+        spec.concretize()
+
+        self.assertIn('zmpi', spec.dependencies)
+        self.assertNotIn('mpi', spec)
+        self.assertIn('fake', 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)
+
+        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.assertNotIn('mpi', spec)
diff --git a/lib/spack/spack/test/mock_packages/fake.py b/lib/spack/spack/test/mock_packages/fake.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b04e76b01fedd2dc6dd7e40df1bc6858a63cb43
--- /dev/null
+++ b/lib/spack/spack/test/mock_packages/fake.py
@@ -0,0 +1,13 @@
+from spack import *
+
+class Fake(Package):
+    homepage = "http://www.fake-spack-example.org"
+    url      = "http://www.fake-spack-example.org/downloads/fake-1.0.tar.gz"
+    md5      = "foobarbaz"
+
+    versions = '1.0'
+
+    def install(self, prefix):
+        configure("--prefix=%s" % prefix)
+        make()
+        make("install")
diff --git a/lib/spack/spack/test/mock_packages/mpich.py b/lib/spack/spack/test/mock_packages/mpich.py
index 2972c34df02a47ebb2f1b45aec4ab08efcb18186..1e964cd380f55e87cc01c5951fba2f00965a079c 100644
--- a/lib/spack/spack/test/mock_packages/mpich.py
+++ b/lib/spack/spack/test/mock_packages/mpich.py
@@ -8,9 +8,10 @@ class Mpich(Package):
     list_url   = "http://www.mpich.org/static/downloads/"
     list_depth = 2
 
-    versions = '1.0.3, 1.3.2p1, 1.4.1p1, 3.0.4, 3.1b1'
+    versions = '3.0.4, 3.0.3, 3.0.2, 3.0.1, 3.0'
 
-    provides('mpi')
+    provides('mpi@:3', when='@3:')
+    provides('mpi@:1', when='@1:')
 
     def install(self, prefix):
         configure("--prefix=%s" % prefix)
diff --git a/lib/spack/spack/test/mock_packages/mpich2.py b/lib/spack/spack/test/mock_packages/mpich2.py
new file mode 100644
index 0000000000000000000000000000000000000000..7150fa7249193f887e9cf9793bb3d496cd2e5b98
--- /dev/null
+++ b/lib/spack/spack/test/mock_packages/mpich2.py
@@ -0,0 +1,20 @@
+from spack import *
+
+class Mpich2(Package):
+    homepage = "http://www.mpich.org"
+    url      = "http://www.mpich.org/static/downloads/1.5/mpich2-1.5.tar.gz"
+    md5      = "9c5d5d4fe1e17dd12153f40bc5b6dbc0"
+
+    list_url   = "http://www.mpich.org/static/downloads/"
+    list_depth = 2
+
+    versions = '1.5, 1.4, 1.3, 1.2, 1.1, 1.0'
+
+    provides('mpi@:2.0')
+    provides('mpi@:2.1', when='@1.1:')
+    provides('mpi@:2.2', when='@1.2:')
+
+    def install(self, prefix):
+        configure("--prefix=%s" % prefix)
+        make()
+        make("install")
diff --git a/lib/spack/spack/test/mock_packages/zmpi.py b/lib/spack/spack/test/mock_packages/zmpi.py
new file mode 100644
index 0000000000000000000000000000000000000000..f36f6a5bc41725c43deca6e626762f588b207a98
--- /dev/null
+++ b/lib/spack/spack/test/mock_packages/zmpi.py
@@ -0,0 +1,18 @@
+from spack import *
+
+class Zmpi(Package):
+    """This is a fake MPI package used to demonstrate virtual package providers
+       with dependencies."""
+    homepage = "http://www.spack-fake-zmpi.org"
+    url      = "http://www.spack-fake-zmpi.org/downloads/zmpi-1.0.tar.gz"
+    md5      = "foobarbaz"
+
+    versions = '1.0'
+
+    provides('mpi@10.0:')
+    depends_on('fake')
+
+    def install(self, prefix):
+        configure("--prefix=%s" % prefix)
+        make()
+        make("install")
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index 914cd967fa521e601726cb233907fdc487741228..4d857358c634d2a9ca14d47515f5cf01d90a9684 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -28,42 +28,49 @@ def test_conflicting_package_constraints(self):
                           spec.package.validate_dependencies)
 
 
-    def test_preorder_traversal(self):
-        dag = Spec('mpileaks',
-                   Spec('callpath',
-                        Spec('dyninst',
-                             Spec('libdwarf',
-                                  Spec('libelf')),
-                             Spec('libelf')),
-                        Spec('mpich')),
-                   Spec('mpich'))
+    def test_unique_node_traversal(self):
+        dag = Spec('mpileaks ^zmpi')
         dag.normalize()
 
-        unique_names = [
-            'mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf', 'mpich']
-        unique_depths = [0,1,2,3,4,2]
+        names = ['mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf',
+                 'zmpi', 'fake']
+        pairs = zip([0,1,2,3,4,2,3], names)
+
+        traversal = dag.preorder_traversal()
+        self.assertListEqual([x.name for x in traversal], names)
 
-        non_unique_names = [
-            'mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf', 'libelf',
-            'mpich', 'mpich']
-        non_unique_depths = [0,1,2,3,4,3,2,1]
+        traversal = dag.preorder_traversal(depth=True)
+        self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
 
-        self.assertListEqual(
-            [x.name for x in dag.preorder_traversal()],
-            unique_names)
 
-        self.assertListEqual(
-            [(x, y.name) for x,y in dag.preorder_traversal(depth=True)],
-            zip(unique_depths, unique_names))
+    def test_unique_edge_traversal(self):
+        dag = Spec('mpileaks ^zmpi')
+        dag.normalize()
 
-        self.assertListEqual(
-            [x.name for x in dag.preorder_traversal(unique=False)],
-            non_unique_names)
+        names = ['mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf',
+                 'libelf', 'zmpi', 'fake', 'zmpi']
+        pairs = zip([0,1,2,3,4,3,2,3,1], names)
 
-        self.assertListEqual(
-            [(x, y.name) for x,y in dag.preorder_traversal(unique=False, depth=True)],
-            zip(non_unique_depths, non_unique_names))
+        traversal = dag.preorder_traversal(cover='edges')
+        self.assertListEqual([x.name for x in traversal], names)
 
+        traversal = dag.preorder_traversal(cover='edges', depth=True)
+        self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
+
+
+    def test_unique_path_traversal(self):
+        dag = Spec('mpileaks ^zmpi')
+        dag.normalize()
+
+        names = ['mpileaks', 'callpath', 'dyninst', 'libdwarf', 'libelf',
+                 'libelf', 'zmpi', 'fake', 'zmpi', 'fake']
+        pairs = zip([0,1,2,3,4,3,2,3,1,2], names)
+
+        traversal = dag.preorder_traversal(cover='paths')
+        self.assertListEqual([x.name for x in traversal], names)
+
+        traversal = dag.preorder_traversal(cover='paths', depth=True)
+        self.assertListEqual([(x, y.name) for x,y in traversal], pairs)
 
 
     def test_conflicting_spec_constraints(self):
@@ -270,3 +277,14 @@ def test_normalize_with_virtual_package(self):
                  Spec('mpi')), Spec('mpi'))
 
         self.assertEqual(str(spec), str(expected_normalized))
+
+
+    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)
diff --git a/lib/spack/spack/util/lang.py b/lib/spack/spack/util/lang.py
index a916dfee57cefa26f3069a59a073ea6d01a6a1e5..bbea0f66a1f03b24c7f66c1e0058b3041e5128b3 100644
--- a/lib/spack/spack/util/lang.py
+++ b/lib/spack/spack/util/lang.py
@@ -113,3 +113,16 @@ def copy(self):
         for key in self:
             clone[key] = self[key].copy()
         return clone
+
+
+def in_function(function_name):
+    """True if the caller was called from some function with
+       the supplied Name, False otherwise."""
+    stack = inspect.stack()
+    try:
+        for elt in stack[2:]:
+            if elt[3] == function_name:
+                return True
+        return False
+    finally:
+        del stack