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:
		| @@ -471,14 +471,19 @@ 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 | ||||||
|  |  | ||||||
|  |         # TODO: allow more than one extendee. | ||||||
|         name = next(iter(self.extendees)) |         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] |         spec, kwargs = self.extendees[name] | ||||||
|         return spec |         return spec | ||||||
|  |  | ||||||
|         # Need to do this to get the concrete version of the spec |  | ||||||
|         return self.spec[name] |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def extendee_args(self): |     def extendee_args(self): | ||||||
| @@ -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,7 +566,10 @@ 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: | ||||||
|  |                 continue | ||||||
|  |             for dep in spec.traverse(): | ||||||
|  |                 if spec == dep: | ||||||
|                     dependents.append(spec) |                     dependents.append(spec) | ||||||
|         return dependents |         return dependents | ||||||
|  |  | ||||||
| @@ -985,7 +993,10 @@ 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: | ||||||
|  |                     continue | ||||||
|  |                 for dep in aspec.traverse(): | ||||||
|  |                     if self.spec == dep: | ||||||
|                         raise ActivationError( |                         raise ActivationError( | ||||||
|                             "Cannot deactivate %s beacuse %s is activated and depends on it." |                             "Cannot deactivate %s beacuse %s is activated and depends on it." | ||||||
|                             % (self.spec.short_spec, aspec.short_spec)) |                             % (self.spec.short_spec, aspec.short_spec)) | ||||||
|   | |||||||
| @@ -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 | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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): | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Todd Gamblin
					Todd Gamblin