summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2014-05-17 15:17:40 -0700
committerTodd Gamblin <tgamblin@llnl.gov>2014-06-22 12:33:49 -0700
commited6454fe78c8de2efb08d3c85e41fddd6fe704fb (patch)
tree6f95eefa496f8789df1b3851f3d322a06c40a906 /lib
parent285c5444ab0e1522b8e04b2052bfe5649f6838a3 (diff)
downloadspack-ed6454fe78c8de2efb08d3c85e41fddd6fe704fb.tar.gz
spack-ed6454fe78c8de2efb08d3c85e41fddd6fe704fb.tar.bz2
spack-ed6454fe78c8de2efb08d3c85e41fddd6fe704fb.tar.xz
spack-ed6454fe78c8de2efb08d3c85e41fddd6fe704fb.zip
Better satisfies: e.g., v4.7.3 now satisfies v4.7
- Changed how satisfies() is defined for the various version classes - Can't just use overlaps() with version lists -- need to account for more and less specific versions. If the version is more specific than the constriant (e.g., 4.7.3 is more specific than 4.7), then it should satisfy the constraint, because if a user asks for 4.7 they likely do not care about the minor version. If they do, they can specify it. New Version.satisfies() takes this into account.
Diffstat (limited to 'lib')
-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)