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.
This commit is contained in:
parent
285c5444ab
commit
ed6454fe78
5 changed files with 118 additions and 13 deletions
|
@ -117,7 +117,7 @@ def __call__(self, package_self, *args, **kwargs):
|
||||||
or if there is none, then raise a NoSuchMethodError.
|
or if there is none, then raise a NoSuchMethodError.
|
||||||
"""
|
"""
|
||||||
for spec, method in self.method_list:
|
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)
|
return method(package_self, *args, **kwargs)
|
||||||
|
|
||||||
if self.default:
|
if self.default:
|
||||||
|
|
|
@ -77,6 +77,7 @@ def get(self, spec):
|
||||||
|
|
||||||
@_autospec
|
@_autospec
|
||||||
def get_installed(self, spec):
|
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)]
|
return [s for s in self.installed_package_specs() if s.satisfies(spec)]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -214,17 +214,17 @@ def _autospec(self, compiler_spec_like):
|
||||||
|
|
||||||
|
|
||||||
def satisfies(self, other):
|
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)
|
other = self._autospec(other)
|
||||||
return (self.name == other.name and
|
return (self.name == other.name and
|
||||||
self.versions.overlaps(other.versions))
|
self.versions.satisfies(other.versions))
|
||||||
|
|
||||||
|
|
||||||
def constrain(self, other):
|
def constrain(self, other):
|
||||||
other = self._autospec(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)
|
self.versions.intersect(other.versions)
|
||||||
|
|
||||||
|
@ -866,8 +866,8 @@ def _constrain_dependencies(self, other):
|
||||||
# TODO: might want more detail than this, e.g. specific deps
|
# TODO: might want more detail than this, e.g. specific deps
|
||||||
# in violation. if this becomes a priority get rid of this
|
# in violation. if this becomes a priority get rid of this
|
||||||
# check and be more specici about what's wrong.
|
# check and be more specici about what's wrong.
|
||||||
if not self.satisfies_dependencies(other):
|
if not other.satisfies_dependencies(self):
|
||||||
raise UnsatisfiableDependencySpecError(self, other)
|
raise UnsatisfiableDependencySpecError(other, self)
|
||||||
|
|
||||||
# Handle common first-order constraints directly
|
# Handle common first-order constraints directly
|
||||||
for name in self.common_dependencies(other):
|
for name in self.common_dependencies(other):
|
||||||
|
|
|
@ -83,6 +83,14 @@ def assert_no_overlap(self, v1, v2):
|
||||||
self.assertFalse(ver(v1).overlaps(ver(v2)))
|
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):
|
def check_intersection(self, expected, a, b):
|
||||||
self.assertEqual(ver(expected), ver(a).intersection(ver(b)))
|
self.assertEqual(ver(expected), ver(a).intersection(ver(b)))
|
||||||
|
|
||||||
|
@ -301,3 +309,40 @@ def test_intersection(self):
|
||||||
self.check_intersection(['2.5:2.7'],
|
self.check_intersection(['2.5:2.7'],
|
||||||
['1.1:2.7'], ['2.5:3.0','1.0'])
|
['1.1:2.7'], ['2.5:3.0','1.0'])
|
||||||
self.check_intersection(['0:1'], [':'], ['0:1'])
|
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')
|
||||||
|
|
|
@ -143,6 +143,18 @@ def highest(self):
|
||||||
return self
|
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):
|
def wildcard(self):
|
||||||
"""Create a regex that will match variants of this version string."""
|
"""Create a regex that will match variants of this version string."""
|
||||||
def a_or_n(seg):
|
def a_or_n(seg):
|
||||||
|
@ -326,6 +338,37 @@ def __contains__(self, other):
|
||||||
none_high.le(other.end, self.end))
|
none_high.le(other.end, self.end))
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
@coerced
|
||||||
def overlaps(self, other):
|
def overlaps(self, other):
|
||||||
return (other in self or self in other or
|
return (other in self or self in other or
|
||||||
|
@ -444,11 +487,6 @@ def highest(self):
|
||||||
return self[-1].highest()
|
return self[-1].highest()
|
||||||
|
|
||||||
|
|
||||||
def satisfies(self, other):
|
|
||||||
"""Synonym for overlaps."""
|
|
||||||
return self.overlaps(other)
|
|
||||||
|
|
||||||
|
|
||||||
@coerced
|
@coerced
|
||||||
def overlaps(self, other):
|
def overlaps(self, other):
|
||||||
if not other or not self:
|
if not other or not self:
|
||||||
|
@ -465,6 +503,27 @@ def overlaps(self, other):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@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
|
@coerced
|
||||||
def update(self, other):
|
def update(self, other):
|
||||||
for v in other.versions:
|
for v in other.versions:
|
||||||
|
|
Loading…
Reference in a new issue