summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/spack/spack/multimethod.py2
-rw-r--r--lib/spack/spack/packages.py1
-rw-r--r--lib/spack/spack/spec.py14
-rw-r--r--lib/spack/spack/test/versions.py45
-rw-r--r--lib/spack/spack/version.py69
5 files changed, 118 insertions, 13 deletions
diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py
index 8d91e4f86d..974401e1aa 100644
--- a/lib/spack/spack/multimethod.py
+++ b/lib/spack/spack/multimethod.py
@@ -117,7 +117,7 @@ class SpecMultiMethod(object):
or if there is none, then raise a NoSuchMethodError.
"""
for spec, method in self.method_list:
- if spec.satisfies(package_self.spec):
+ if package_self.spec.satisfies(spec):
return method(package_self, *args, **kwargs)
if self.default:
diff --git a/lib/spack/spack/packages.py b/lib/spack/spack/packages.py
index 36f3d4286a..5a31f1fbb9 100644
--- a/lib/spack/spack/packages.py
+++ b/lib/spack/spack/packages.py
@@ -77,6 +77,7 @@ class PackageDB(object):
@_autospec
def get_installed(self, spec):
+ """Get all the installed specs that satisfy the provided spec constraint."""
return [s for s in self.installed_package_specs() if s.satisfies(spec)]
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index f0244695bc..35a17621b6 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -214,17 +214,17 @@ class CompilerSpec(object):
def satisfies(self, other):
- # TODO: This should not just look for overlapping versions.
- # TODO: e.g., 4.7.3 should satisfy a requirement for 4.7.
other = self._autospec(other)
return (self.name == other.name and
- self.versions.overlaps(other.versions))
+ self.versions.satisfies(other.versions))
def constrain(self, other):
other = self._autospec(other)
- if not self.satisfies(other):
- raise UnsatisfiableCompilerSpecError(self, other)
+
+ # ensure that other will actually constrain this spec.
+ if not other.satisfies(self):
+ raise UnsatisfiableCompilerSpecError(other, self)
self.versions.intersect(other.versions)
@@ -866,8 +866,8 @@ class Spec(object):
# 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)
+ if not other.satisfies_dependencies(self):
+ raise UnsatisfiableDependencySpecError(other, self)
# Handle common first-order constraints directly
for name in self.common_dependencies(other):
diff --git a/lib/spack/spack/test/versions.py b/lib/spack/spack/test/versions.py
index 37fd28a8e7..e272274a4f 100644
--- a/lib/spack/spack/test/versions.py
+++ b/lib/spack/spack/test/versions.py
@@ -83,6 +83,14 @@ class VersionsTest(unittest.TestCase):
self.assertFalse(ver(v1).overlaps(ver(v2)))
+ def assert_satisfies(self, v1, v2):
+ self.assertTrue(ver(v1).satisfies(ver(v2)))
+
+
+ def assert_does_not_satisfy(self, v1, v2):
+ self.assertFalse(ver(v1).satisfies(ver(v2)))
+
+
def check_intersection(self, expected, a, b):
self.assertEqual(ver(expected), ver(a).intersection(ver(b)))
@@ -301,3 +309,40 @@ class VersionsTest(unittest.TestCase):
self.check_intersection(['2.5:2.7'],
['1.1:2.7'], ['2.5:3.0','1.0'])
self.check_intersection(['0:1'], [':'], ['0:1'])
+
+
+ def test_satisfaction(self):
+ self.assert_satisfies('4.7.3', '4.7.3')
+
+ self.assert_satisfies('4.7.3', '4.7')
+ self.assert_satisfies('4.7.3b2', '4.7')
+ self.assert_satisfies('4.7b6', '4.7')
+
+ self.assert_satisfies('4.7.3', '4')
+ self.assert_satisfies('4.7.3b2', '4')
+ self.assert_satisfies('4.7b6', '4')
+
+ self.assert_does_not_satisfy('4.8.0', '4.9')
+ self.assert_does_not_satisfy('4.8', '4.9')
+ self.assert_does_not_satisfy('4', '4.9')
+
+ self.assert_satisfies('4.7b6', '4.3:4.7')
+ self.assert_satisfies('4.3.0', '4.3:4.7')
+ self.assert_satisfies('4.3.2', '4.3:4.7')
+
+ self.assert_does_not_satisfy('4.8.0', '4.3:4.7')
+ self.assert_does_not_satisfy('4.3', '4.4:4.7')
+
+ self.assert_satisfies('4.7b6', '4.3:4.7')
+ self.assert_does_not_satisfy('4.8.0', '4.3:4.7')
+
+ self.assert_satisfies('4.7', '4.3, 4.6, 4.7')
+ self.assert_satisfies('4.7.3', '4.3, 4.6, 4.7')
+ self.assert_satisfies('4.6.5', '4.3, 4.6, 4.7')
+ self.assert_satisfies('4.6.5.2', '4.3, 4.6, 4.7')
+
+ self.assert_does_not_satisfy('4', '4.3, 4.6, 4.7')
+ self.assert_does_not_satisfy('4.8.0', '4.2, 4.3:4.7')
+
+ self.assert_satisfies('4.8.0', '4.2, 4.3:4.8')
+ self.assert_satisfies('4.8.2', '4.2, 4.3:4.8')
diff --git a/lib/spack/spack/version.py b/lib/spack/spack/version.py
index 0b5125fdf0..ce94303a9c 100644
--- a/lib/spack/spack/version.py
+++ b/lib/spack/spack/version.py
@@ -143,6 +143,18 @@ class Version(object):
return self
+ @coerced
+ def satisfies(self, other):
+ """A Version 'satisfies' another if it is at least as specific and has a
+ common prefix. e.g., we want gcc@4.7.3 to satisfy a request for
+ gcc@4.7 so that when a user asks to build with gcc@4.7, we can find
+ a suitable compiler.
+ """
+ nself = len(self.version)
+ nother = len(other.version)
+ return nother <= nself and self.version[:nother] == other.version
+
+
def wildcard(self):
"""Create a regex that will match variants of this version string."""
def a_or_n(seg):
@@ -327,6 +339,37 @@ class VersionRange(object):
@coerced
+ def satisfies(self, other):
+ """A VersionRange satisfies another if some version in this range
+ would satisfy some version in the other range. To do this it must
+ either:
+ a) Overlap with the other range
+ b) The start of this range satisfies the end of the other range.
+
+ This is essentially the same as overlaps(), but overlaps assumes
+ that its arguments are specific. That is, 4.7 is interpreted as
+ 4.7.0.0.0.0... . This funciton assumes that 4.7 woudl be satisfied
+ by 4.7.3.5, etc.
+
+ Rationale:
+ If a user asks for gcc@4.5:4.7, and a package is only compatible with
+ gcc@4.7.3:4.8, then that package should be able to build under the
+ constraints. Just using overlaps() would not work here.
+
+ Note that we don't need to check whether the end of this range
+ would satisfy the start of the other range, because overlaps()
+ already covers that case.
+
+ Note further that overlaps() is a symmetric operation, while
+ satisfies() is not.
+ """
+ return (self.overlaps(other) or
+ # if either self.start or other.end are None, then this can't
+ # satisfy, or overlaps() would've taken care of it.
+ self.start and other.end and self.start.satisfies(other.end))
+
+
+ @coerced
def overlaps(self, other):
return (other in self or self in other or
((self.start == None or other.end is None or
@@ -444,11 +487,6 @@ class VersionList(object):
return self[-1].highest()
- def satisfies(self, other):
- """Synonym for overlaps."""
- return self.overlaps(other)
-
-
@coerced
def overlaps(self, other):
if not other or not self:
@@ -466,6 +504,27 @@ class VersionList(object):
@coerced
+ def satisfies(self, other):
+ """A VersionList satisfies another if some version in the list would
+ would satisfy some version in the other list. This uses essentially
+ the same algorithm as overlaps() does for VersionList, but it calls
+ satisfies() on member Versions and VersionRanges.
+ """
+ if not other or not self:
+ return False
+
+ s = o = 0
+ while s < len(self) and o < len(other):
+ if self[s].satisfies(other[o]):
+ return True
+ elif self[s] < other[o]:
+ s += 1
+ else:
+ o += 1
+ return False
+
+
+ @coerced
def update(self, other):
for v in other.versions:
self.add(v)