SPACK-38: Allow specs to be indexed by virtual dependencies.

- The following now work differently:

      spec['mpi']
      spec['blas']

  This can return a spec for openmpi, mpich, mvapich, etc., EVEN if
  the spec is already concretized.  This means that in a package that
  `depends_on('mpi')`, you can do `spec['mpi']` to see what it was
  concretized to.  This should simplify MPI and BLAS packages.

      'mpi' in spec
      'blas' in spec

  Previously, if the spec had been concretized, these would be `False`
  because there was not a dependency in the DAG with either of these
  names. These will now be `True` even if the spec has been
  concretized.  So, e.g., this will print "YES"

      s = Spec('callpath ^mpich')
      if 'mpi' in spec:
          print "YES"

- Similarly, this will be True:

      Spec('mpich').satisfies('mpi')

- Because of the way virtual dependencies are currently implemented,
  the above required some fiddling around with `package.py` so that it
  would never call `Spec.__contains__` (and result in endless
  recursion).

- This should be fixed by allowing virutal dependnecies to have their
  own package class.
  - This would allow a quicker check for vdeps, without a call to
    `all_packages`.
  - For the time being, `package.py` shouldn't call `__contains__`
This commit is contained in:
Todd Gamblin 2015-06-07 15:21:31 -07:00
parent 0570660d81
commit 0fc3b58890
4 changed files with 122 additions and 18 deletions

View file

@ -471,14 +471,19 @@ def extendee_spec(self):
"""Spec of the extendee of this package, or None if it is not an extension."""
if not self.extendees:
return None
# TODO: allow more than one extendee.
name = next(iter(self.extendees))
if not name in self.spec:
# If the extendee is in the spec's deps already, return that.
for dep in self.spec.traverse():
if name == dep.name:
return dep
# Otherwise return the spec from the extends() directive
spec, kwargs = self.extendees[name]
return spec
# Need to do this to get the concrete version of the spec
return self.spec[name]
@property
def extendee_args(self):
@ -542,7 +547,7 @@ def preorder_traversal(self, visited=None, **kwargs):
def provides(self, vpkg_name):
"""True if this package provides a virtual package with the specified name."""
return vpkg_name in self.provided
return any(s.name == vpkg_name for s in self.provided)
def virtual_dependencies(self, visited=None):
@ -561,7 +566,10 @@ def installed_dependents(self):
on this one."""
dependents = []
for spec in spack.db.installed_package_specs():
if self.name != spec.name and self.spec in spec:
if self.name == spec.name:
continue
for dep in spec.traverse():
if spec == dep:
dependents.append(spec)
return dependents
@ -985,7 +993,10 @@ def do_deactivate(self, **kwargs):
activated = spack.install_layout.extension_map(self.extendee_spec)
for name, aspec in activated.items():
if aspec != self.spec and self.spec in aspec:
if aspec == self.spec:
continue
for dep in aspec.traverse():
if self.spec == dep:
raise ActivationError(
"Cannot deactivate %s beacuse %s is activated and depends on it."
% (self.spec.short_spec, aspec.short_spec))

View file

@ -498,7 +498,13 @@ def virtual(self):
Possible idea: just use conventin and make virtual deps all
caps, e.g., MPI vs mpi.
"""
return not spack.db.exists(self.name)
return Spec.is_virtual(self.name)
@staticmethod
def is_virtual(name):
"""Test if a name is virtual without requiring a Spec."""
return not spack.db.exists(name)
@property
@ -1224,7 +1230,17 @@ def satisfies(self, other, deps=True, strict=False):
"""
other = self._autospec(other)
# First thing we care about is whether the name matches
# A concrete provider can satisfy a virtual dependency.
if not self.virtual and other.virtual:
pkg = spack.db.get(self.name)
if pkg.provides(other.name):
for provided, when_spec in pkg.provided.items():
if self.satisfies(when_spec, deps=False, strict=strict):
if provided.satisfies(other):
return True
return False
# Otherwise, first thing we care about is whether the name matches
if self.name != other.name:
return False
@ -1364,11 +1380,21 @@ def version(self):
def __getitem__(self, name):
"""TODO: reconcile __getitem__, _add_dependency, __contains__"""
"""Get a dependency from the spec by its name."""
for spec in self.traverse():
if spec.name == name:
return spec
if Spec.is_virtual(name):
# TODO: this is a kind of kludgy way to find providers
# TODO: should we just keep virtual deps in the DAG instead of
# TODO: removing them on concretize?
for spec in self.traverse():
if spec.virtual:
continue
if spec.package.provides(name):
return spec
raise KeyError("No spec with name %s in %s" % (name, self))
@ -1380,6 +1406,7 @@ def __contains__(self, spec):
for s in self.traverse():
if s.satisfies(spec, strict=True):
return True
return False

View file

@ -152,7 +152,10 @@ def test_virtual_is_fully_expanded_for_callpath(self):
spec.concretize()
self.assertTrue('zmpi' in spec.dependencies)
self.assertFalse('mpi' in spec)
self.assertTrue(all(not 'mpi' in d.dependencies for d in spec.traverse()))
self.assertTrue('zmpi' in spec)
self.assertTrue('mpi' in spec)
self.assertTrue('fake' in spec.dependencies['zmpi'])
@ -168,7 +171,9 @@ def test_virtual_is_fully_expanded_for_mpileaks(self):
self.assertTrue('zmpi' in spec.dependencies['callpath'].dependencies)
self.assertTrue('fake' in spec.dependencies['callpath'].dependencies['zmpi'].dependencies)
self.assertFalse('mpi' in spec)
self.assertTrue(all(not 'mpi' in d.dependencies for d in spec.traverse()))
self.assertTrue('zmpi' in spec)
self.assertTrue('mpi' in spec)
def test_my_dep_depends_on_provider_of_my_virtual_dep(self):

View file

@ -189,6 +189,67 @@ def test_unsatisfiable_variant_mismatch(self):
self.check_unsatisfiable('mpich+foo', 'mpich~foo')
def test_satisfies_virtual(self):
self.assertTrue(Spec('mpich').satisfies(Spec('mpi')))
self.assertTrue(Spec('mpich2').satisfies(Spec('mpi')))
self.assertTrue(Spec('zmpi').satisfies(Spec('mpi')))
# ================================================================================
# Indexing specs
# ================================================================================
def test_self_index(self):
s = Spec('callpath')
self.assertTrue(s['callpath'] == s)
def test_dep_index(self):
s = Spec('callpath')
s.normalize()
self.assertTrue(s['callpath'] == s)
self.assertTrue(type(s['dyninst']) == Spec)
self.assertTrue(type(s['libdwarf']) == Spec)
self.assertTrue(type(s['libelf']) == Spec)
self.assertTrue(type(s['mpi']) == Spec)
self.assertTrue(s['dyninst'].name == 'dyninst')
self.assertTrue(s['libdwarf'].name == 'libdwarf')
self.assertTrue(s['libelf'].name == 'libelf')
self.assertTrue(s['mpi'].name == 'mpi')
def test_spec_contains_deps(self):
s = Spec('callpath')
s.normalize()
self.assertTrue('dyninst' in s)
self.assertTrue('libdwarf' in s)
self.assertTrue('libelf' in s)
self.assertTrue('mpi' in s)
def test_virtual_index(self):
s = Spec('callpath')
s.concretize()
s_mpich = Spec('callpath ^mpich')
s_mpich.concretize()
s_mpich2 = Spec('callpath ^mpich2')
s_mpich2.concretize()
s_zmpi = Spec('callpath ^zmpi')
s_zmpi.concretize()
self.assertTrue(s['mpi'].name != 'mpi')
self.assertTrue(s_mpich['mpi'].name == 'mpich')
self.assertTrue(s_mpich2['mpi'].name == 'mpich2')
self.assertTrue(s_zmpi['zmpi'].name == 'zmpi')
for spec in [s, s_mpich, s_mpich2, s_zmpi]:
self.assertTrue('mpi' in spec)
# ================================================================================
# Constraints