constrain() now consistent with satisfies()
- Added checks to constrain() so that it is consistent with satisfies() - Added many more test cases for satisfiability and constraints on deps - Virtual packages are handled properly in satisfies() and constrain() - bugfix: mpileaks^mpich2 would satisfy mpileaks^mpi@3: - this case is now handled.
This commit is contained in:
parent
7088cdf25f
commit
87dc2151b7
@ -93,17 +93,6 @@ def __call__(self, package_self, *args, **kwargs):
|
||||
"""
|
||||
spec = package_self.spec
|
||||
matching_specs = [s for s in self.method_map if s.satisfies(spec)]
|
||||
|
||||
# from pprint import pprint
|
||||
|
||||
# print "========"
|
||||
# print "called with " + str(spec)
|
||||
# print spec, matching_specs
|
||||
# pprint(self.method_map)
|
||||
# print "SATISFIES: ", [Spec('multimethod%gcc').satisfies(s) for s in self.method_map]
|
||||
# print [spec.satisfies(s) for s in self.method_map]
|
||||
# print
|
||||
|
||||
num_matches = len(matching_specs)
|
||||
if num_matches == 0:
|
||||
if self.default is None:
|
||||
|
@ -49,7 +49,11 @@ class ProviderIndex(object):
|
||||
matching implementation of MPI.
|
||||
"""
|
||||
def __init__(self, specs, **kwargs):
|
||||
# TODO: come up with another name for this. This "restricts" values to
|
||||
# the verbatim impu specs (i.e., it doesn't pre-apply package's constraints, and
|
||||
# keeps things as broad as possible, so it's really the wrong name)
|
||||
restrict = kwargs.setdefault('restrict', False)
|
||||
|
||||
self.providers = {}
|
||||
|
||||
for spec in specs:
|
||||
@ -106,17 +110,21 @@ def _cross_provider_maps(self, lmap, rmap):
|
||||
constrained = lspec.copy().constrain(rspec)
|
||||
if lmap[lspec].name != rmap[rspec].name:
|
||||
continue
|
||||
result[constrained] = lmap[lspec].copy().constrain(rmap[rspec])
|
||||
result[constrained] = lmap[lspec].copy().constrain(
|
||||
rmap[rspec], deps=False)
|
||||
except spack.spec.UnsatisfiableSpecError:
|
||||
continue
|
||||
return result
|
||||
|
||||
|
||||
def __contains__(self, name):
|
||||
"""Whether a particular vpkg name is in the index."""
|
||||
return name in self.providers
|
||||
|
||||
|
||||
def satisfies(self, other):
|
||||
"""Check that providers of virtual specs are compatible."""
|
||||
common = set(self.providers.keys())
|
||||
common.intersection_update(other.providers.keys())
|
||||
|
||||
common = set(self.providers) & set(other.providers)
|
||||
if not common:
|
||||
return True
|
||||
|
||||
@ -130,6 +138,7 @@ def satisfies(self, other):
|
||||
return bool(result)
|
||||
|
||||
|
||||
|
||||
@autospec
|
||||
def get(spec):
|
||||
if spec.virtual:
|
||||
|
@ -710,7 +710,10 @@ def validate_names(self):
|
||||
raise UnknownCompilerError(compiler_name)
|
||||
|
||||
|
||||
def constrain(self, other):
|
||||
def constrain(self, other, **kwargs):
|
||||
if not self.name == other.name:
|
||||
raise UnsatisfiableSpecNameError(self.name, other.name)
|
||||
|
||||
if not self.versions.overlaps(other.versions):
|
||||
raise UnsatisfiableVersionSpecError(self.versions, other.versions)
|
||||
|
||||
@ -734,7 +737,45 @@ def constrain(self, other):
|
||||
self.variants.update(other.variants)
|
||||
self.architecture = self.architecture or other.architecture
|
||||
|
||||
# TODO: constrain dependencies, too.
|
||||
if kwargs.get('deps', True):
|
||||
self.constrain_dependencies(other)
|
||||
|
||||
|
||||
def constrain_dependencies(self, other):
|
||||
"""Apply constraints of other spec's dependencies to this spec."""
|
||||
if not self.dependencies or not other.dependencies:
|
||||
return
|
||||
|
||||
# TODO: might want more detail than this, e.g. specific deps
|
||||
# in violation. if this becomes a priority get rid of this
|
||||
# check and be more specici about what's wrong.
|
||||
if not self.satisfies_dependencies(other):
|
||||
raise UnsatisfiableDependencySpecError(self, other)
|
||||
|
||||
# Handle common first-order constraints directly
|
||||
for name in self.common_dependencies(other):
|
||||
self[name].constrain(other[name], deps=False)
|
||||
|
||||
# Update with additional constraints from other spec
|
||||
for name in other.dep_difference(self):
|
||||
self._add_dependency(other[name].copy())
|
||||
|
||||
|
||||
def common_dependencies(self, other):
|
||||
"""Return names of dependencies that self an other have in common."""
|
||||
common = set(
|
||||
s.name for s in self.preorder_traversal(root=False))
|
||||
common.intersection_update(
|
||||
s.name for s in other.preorder_traversal(root=False))
|
||||
return common
|
||||
|
||||
|
||||
def dep_difference(self, other):
|
||||
"""Returns dependencies in self that are not in other."""
|
||||
mine = set(s.name for s in self.preorder_traversal(root=False))
|
||||
mine.difference_update(
|
||||
s.name for s in other.preorder_traversal(root=False))
|
||||
return mine
|
||||
|
||||
|
||||
def satisfies(self, other, **kwargs):
|
||||
@ -773,19 +814,33 @@ def satisfies_dependencies(self, other):
|
||||
if not self.dependencies or not other.dependencies:
|
||||
return True
|
||||
|
||||
common = set(s.name for s in self.preorder_traversal(root=False))
|
||||
common.intersection_update(s.name for s in other.preorder_traversal(root=False))
|
||||
|
||||
# Handle first-order constraints directly
|
||||
for name in common:
|
||||
for name in self.common_dependencies(other):
|
||||
if not self[name].satisfies(other[name]):
|
||||
return False
|
||||
|
||||
# For virtual dependencies, we need to dig a little deeper.
|
||||
self_index = packages.ProviderIndex(self.preorder_traversal())
|
||||
other_index = packages.ProviderIndex(other.preorder_traversal())
|
||||
self_index = packages.ProviderIndex(
|
||||
self.preorder_traversal(), restrict=True)
|
||||
other_index = packages.ProviderIndex(
|
||||
other.preorder_traversal(), restrict=True)
|
||||
|
||||
return self_index.satisfies(other_index)
|
||||
# This handles cases where there are already providers for both vpkgs
|
||||
if not self_index.satisfies(other_index):
|
||||
return False
|
||||
|
||||
# These two loops handle cases where there is an overly restrictive vpkg
|
||||
# in one spec for a provider in the other (e.g., mpi@3: is not compatible
|
||||
# with mpich2)
|
||||
for spec in self.virtual_dependencies():
|
||||
if spec.name in other_index and not other_index.providers_for(spec):
|
||||
return False
|
||||
|
||||
for spec in other.virtual_dependencies():
|
||||
if spec.name in self_index and not self_index.providers_for(spec):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def virtual_dependencies(self):
|
||||
@ -840,7 +895,7 @@ def version(self):
|
||||
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""TODO: does the way this is written make sense?"""
|
||||
"""TODO: reconcile __getitem__, _add_dependency, __contains__"""
|
||||
for spec in self.preorder_traversal():
|
||||
if spec.name == name:
|
||||
return spec
|
||||
@ -1268,6 +1323,13 @@ def __init__(self, provided, required, constraint_type):
|
||||
self.constraint_type = constraint_type
|
||||
|
||||
|
||||
class UnsatisfiableSpecNameError(UnsatisfiableSpecError):
|
||||
"""Raised when two specs aren't even for the same package."""
|
||||
def __init__(self, provided, required):
|
||||
super(UnsatisfiableVersionSpecError, self).__init__(
|
||||
provided, required, "name")
|
||||
|
||||
|
||||
class UnsatisfiableVersionSpecError(UnsatisfiableSpecError):
|
||||
"""Raised when a spec version conflicts with package constraints."""
|
||||
def __init__(self, provided, required):
|
||||
@ -1302,3 +1364,11 @@ class UnsatisfiableProviderSpecError(UnsatisfiableSpecError):
|
||||
def __init__(self, provided, required):
|
||||
super(UnsatisfiableProviderSpecError, self).__init__(
|
||||
provided, required, "provider")
|
||||
|
||||
# TODO: get rid of this and be more specific about particular incompatible
|
||||
# dep constraints
|
||||
class UnsatisfiableDependencySpecError(UnsatisfiableSpecError):
|
||||
"""Raised when some dependency of constrained specs are incompatible"""
|
||||
def __init__(self, provided, required):
|
||||
super(UnsatisfiableDependencySpecError, self).__init__(
|
||||
provided, required, "dependency")
|
||||
|
@ -277,8 +277,6 @@ def test_normalize_with_virtual_package(self):
|
||||
|
||||
def test_contains(self):
|
||||
spec = Spec('mpileaks ^mpi ^libelf@1.8.11 ^libdwarf')
|
||||
|
||||
print [s for s in spec.preorder_traversal()]
|
||||
self.assertIn(Spec('mpi'), spec)
|
||||
self.assertIn(Spec('libelf'), spec)
|
||||
self.assertIn(Spec('libelf@1.8.11'), spec)
|
||||
|
@ -80,13 +80,14 @@ def test_satisfies_architecture(self):
|
||||
|
||||
|
||||
def test_satisfies_dependencies(self):
|
||||
# self.check_satisfies('mpileaks^mpich', 'mpileaks^mpich')
|
||||
# self.check_satisfies('mpileaks^zmpi', 'mpileaks^zmpi')
|
||||
self.check_satisfies('mpileaks^mpich', 'mpileaks^mpich')
|
||||
self.check_satisfies('mpileaks^zmpi', 'mpileaks^zmpi')
|
||||
|
||||
self.check_unsatisfiable('mpileaks^mpich', 'mpileaks^zmpi')
|
||||
self.check_unsatisfiable('mpileaks^zmpi', 'mpileaks^mpich')
|
||||
|
||||
|
||||
def ztest_satisfies_dependency_versions(self):
|
||||
def test_satisfies_dependency_versions(self):
|
||||
self.check_satisfies('mpileaks^mpich@2.0', 'mpileaks^mpich@1:3')
|
||||
self.check_unsatisfiable('mpileaks^mpich@1.2', 'mpileaks^mpich@2.0')
|
||||
|
||||
@ -96,7 +97,7 @@ def ztest_satisfies_dependency_versions(self):
|
||||
self.check_unsatisfiable('mpileaks^mpich@4.0^callpath@1.7', 'mpileaks^mpich@1:3^callpath@1.4:1.6')
|
||||
|
||||
|
||||
def ztest_satisfies_virtual_dependencies(self):
|
||||
def test_satisfies_virtual_dependencies(self):
|
||||
self.check_satisfies('mpileaks^mpi', 'mpileaks^mpi')
|
||||
self.check_satisfies('mpileaks^mpi', 'mpileaks^mpich')
|
||||
|
||||
@ -104,7 +105,7 @@ def ztest_satisfies_virtual_dependencies(self):
|
||||
self.check_unsatisfiable('mpileaks^mpich', 'mpileaks^zmpi')
|
||||
|
||||
|
||||
def ztest_satisfies_virtual_dependency_versions(self):
|
||||
def test_satisfies_virtual_dependency_versions(self):
|
||||
self.check_satisfies('mpileaks^mpi@1.5', 'mpileaks^mpi@1.2:1.6')
|
||||
self.check_unsatisfiable('mpileaks^mpi@3', 'mpileaks^mpi@1.2:1.6')
|
||||
|
||||
@ -112,7 +113,11 @@ def ztest_satisfies_virtual_dependency_versions(self):
|
||||
self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich@3.0.4')
|
||||
self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich2@1.4')
|
||||
|
||||
self.check_satisfies('mpileaks^mpi@1:', 'mpileaks^mpich2')
|
||||
self.check_satisfies('mpileaks^mpi@2:', 'mpileaks^mpich2')
|
||||
|
||||
self.check_unsatisfiable('mpileaks^mpi@3:', 'mpileaks^mpich2@1.4')
|
||||
self.check_unsatisfiable('mpileaks^mpi@3:', 'mpileaks^mpich2')
|
||||
self.check_unsatisfiable('mpileaks^mpi@3:', 'mpileaks^mpich@1.0')
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user