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.
|
||||
"""
|
||||
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:
|
||||
|
|
|
@ -77,6 +77,7 @@ def get(self, spec):
|
|||
|
||||
@_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)]
|
||||
|
||||
|
||||
|
|
|
@ -214,17 +214,17 @@ def _autospec(self, compiler_spec_like):
|
|||
|
||||
|
||||
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 @@ def _constrain_dependencies(self, other):
|
|||
# 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):
|
||||
|
|
|
@ -83,6 +83,14 @@ def assert_no_overlap(self, v1, 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):
|
||||
self.assertEqual(ver(expected), ver(a).intersection(ver(b)))
|
||||
|
||||
|
@ -301,3 +309,40 @@ def test_intersection(self):
|
|||
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')
|
||||
|
|
|
@ -143,6 +143,18 @@ def highest(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):
|
||||
"""Create a regex that will match variants of this version string."""
|
||||
def a_or_n(seg):
|
||||
|
@ -326,6 +338,37 @@ def __contains__(self, other):
|
|||
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
|
||||
def overlaps(self, other):
|
||||
return (other in self or self in other or
|
||||
|
@ -444,11 +487,6 @@ def highest(self):
|
|||
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:
|
||||
|
@ -465,6 +503,27 @@ def overlaps(self, other):
|
|||
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
|
||||
def update(self, other):
|
||||
for v in other.versions:
|
||||
|
|
Loading…
Reference in a new issue