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

View file

@ -498,7 +498,13 @@ def virtual(self):
Possible idea: just use conventin and make virtual deps all Possible idea: just use conventin and make virtual deps all
caps, e.g., MPI vs mpi. 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 @property
@ -1224,7 +1230,17 @@ def satisfies(self, other, deps=True, strict=False):
""" """
other = self._autospec(other) 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: if self.name != other.name:
return False return False
@ -1364,11 +1380,21 @@ def version(self):
def __getitem__(self, name): def __getitem__(self, name):
"""TODO: reconcile __getitem__, _add_dependency, __contains__""" """Get a dependency from the spec by its name."""
for spec in self.traverse(): for spec in self.traverse():
if spec.name == name: if spec.name == name:
return spec 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)) raise KeyError("No spec with name %s in %s" % (name, self))
@ -1380,6 +1406,7 @@ def __contains__(self, spec):
for s in self.traverse(): for s in self.traverse():
if s.satisfies(spec, strict=True): if s.satisfies(spec, strict=True):
return True return True
return False return False

View file

@ -152,7 +152,10 @@ def test_virtual_is_fully_expanded_for_callpath(self):
spec.concretize() spec.concretize()
self.assertTrue('zmpi' in spec.dependencies) 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']) 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('zmpi' in spec.dependencies['callpath'].dependencies)
self.assertTrue('fake' in spec.dependencies['callpath'].dependencies['zmpi'].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): 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') 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 # Constraints