Fix for SPACK-39: Concretization was too restrictive.
- concretize_version() now Use satisfies(), not intersection. - version class updated with better intersection/union commands - version now 1.6 "contains" 1.6.5 - added test for new version functionality - remove none_high and none_low classes - version module is now self-contained; save for external 2.7 functools.total_ordering for 2.6 compatibility.
This commit is contained in:
parent
37e96ff6e1
commit
4bde771970
@ -1,70 +0,0 @@
|
||||
##############################################################################
|
||||
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
|
||||
# Produced at the Lawrence Livermore National Laboratory.
|
||||
#
|
||||
# This file is part of Spack.
|
||||
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
||||
# LLNL-CODE-647188
|
||||
#
|
||||
# For details, see https://scalability-llnl.github.io/spack
|
||||
# Please also see the LICENSE file for our notice and the LGPL.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License (as published by
|
||||
# the Free Software Foundation) version 2.1 dated February 1999.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
|
||||
# conditions of the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
##############################################################################
|
||||
"""
|
||||
Functions for comparing values that may potentially be None.
|
||||
These none_high functions consider None as greater than all other values.
|
||||
"""
|
||||
|
||||
# Preserve builtin min and max functions
|
||||
_builtin_min = min
|
||||
_builtin_max = max
|
||||
|
||||
|
||||
def lt(lhs, rhs):
|
||||
"""Less-than comparison. None is greater than any value."""
|
||||
return lhs != rhs and (rhs is None or (lhs is not None and lhs < rhs))
|
||||
|
||||
|
||||
def le(lhs, rhs):
|
||||
"""Less-than-or-equal comparison. None is greater than any value."""
|
||||
return lhs == rhs or lt(lhs, rhs)
|
||||
|
||||
|
||||
def gt(lhs, rhs):
|
||||
"""Greater-than comparison. None is greater than any value."""
|
||||
return lhs != rhs and not lt(lhs, rhs)
|
||||
|
||||
|
||||
def ge(lhs, rhs):
|
||||
"""Greater-than-or-equal comparison. None is greater than any value."""
|
||||
return lhs == rhs or gt(lhs, rhs)
|
||||
|
||||
|
||||
def min(lhs, rhs):
|
||||
"""Minimum function where None is greater than any value."""
|
||||
if lhs is None:
|
||||
return rhs
|
||||
elif rhs is None:
|
||||
return lhs
|
||||
else:
|
||||
return _builtin_min(lhs, rhs)
|
||||
|
||||
|
||||
def max(lhs, rhs):
|
||||
"""Maximum function where None is greater than any value."""
|
||||
if lhs is None or rhs is None:
|
||||
return None
|
||||
else:
|
||||
return _builtin_max(lhs, rhs)
|
@ -1,70 +0,0 @@
|
||||
##############################################################################
|
||||
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
|
||||
# Produced at the Lawrence Livermore National Laboratory.
|
||||
#
|
||||
# This file is part of Spack.
|
||||
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
||||
# LLNL-CODE-647188
|
||||
#
|
||||
# For details, see https://scalability-llnl.github.io/spack
|
||||
# Please also see the LICENSE file for our notice and the LGPL.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License (as published by
|
||||
# the Free Software Foundation) version 2.1 dated February 1999.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
|
||||
# conditions of the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
##############################################################################
|
||||
"""
|
||||
Functions for comparing values that may potentially be None.
|
||||
These none_low functions consider None as less than all other values.
|
||||
"""
|
||||
|
||||
# Preserve builtin min and max functions
|
||||
_builtin_min = min
|
||||
_builtin_max = max
|
||||
|
||||
|
||||
def lt(lhs, rhs):
|
||||
"""Less-than comparison. None is lower than any value."""
|
||||
return lhs != rhs and (lhs is None or (rhs is not None and lhs < rhs))
|
||||
|
||||
|
||||
def le(lhs, rhs):
|
||||
"""Less-than-or-equal comparison. None is less than any value."""
|
||||
return lhs == rhs or lt(lhs, rhs)
|
||||
|
||||
|
||||
def gt(lhs, rhs):
|
||||
"""Greater-than comparison. None is less than any value."""
|
||||
return lhs != rhs and not lt(lhs, rhs)
|
||||
|
||||
|
||||
def ge(lhs, rhs):
|
||||
"""Greater-than-or-equal comparison. None is less than any value."""
|
||||
return lhs == rhs or gt(lhs, rhs)
|
||||
|
||||
|
||||
def min(lhs, rhs):
|
||||
"""Minimum function where None is less than any value."""
|
||||
if lhs is None or rhs is None:
|
||||
return None
|
||||
else:
|
||||
return _builtin_min(lhs, rhs)
|
||||
|
||||
|
||||
def max(lhs, rhs):
|
||||
"""Maximum function where None is less than any value."""
|
||||
if lhs is None:
|
||||
return rhs
|
||||
elif rhs is None:
|
||||
return lhs
|
||||
else:
|
||||
return _builtin_max(lhs, rhs)
|
@ -68,11 +68,13 @@ def concretize_version(self, spec):
|
||||
# If there are known avaialble versions, return the most recent
|
||||
# version that satisfies the spec
|
||||
pkg = spec.package
|
||||
valid_versions = pkg.available_versions.intersection(spec.versions)
|
||||
valid_versions = [v for v in pkg.available_versions
|
||||
if any(v.satisfies(sv) for sv in spec.versions)]
|
||||
if valid_versions:
|
||||
spec.versions = ver([valid_versions[-1]])
|
||||
else:
|
||||
raise NoValidVerionError(spec)
|
||||
print spec
|
||||
raise NoValidVersionError(spec)
|
||||
|
||||
|
||||
def concretize_architecture(self, spec):
|
||||
@ -160,9 +162,9 @@ def __init__(self, compiler_spec):
|
||||
"Run 'spack compilers' to see available compiler Options.")
|
||||
|
||||
|
||||
class NoValidVerionError(spack.error.SpackError):
|
||||
class NoValidVersionError(spack.error.SpackError):
|
||||
"""Raised when there is no available version for a package that
|
||||
satisfies a spec."""
|
||||
def __init__(self, spec):
|
||||
super(NoValidVerionError, self).__init__(
|
||||
super(NoValidVersionError, self).__init__(
|
||||
"No available version of %s matches '%s'" % (spec.name, spec.versions))
|
||||
|
@ -95,6 +95,10 @@ def check_intersection(self, expected, a, b):
|
||||
self.assertEqual(ver(expected), ver(a).intersection(ver(b)))
|
||||
|
||||
|
||||
def check_union(self, expected, a, b):
|
||||
self.assertEqual(ver(expected), ver(a).union(ver(b)))
|
||||
|
||||
|
||||
def test_two_segments(self):
|
||||
self.assert_ver_eq('1.0', '1.0')
|
||||
self.assert_ver_lt('1.0', '2.0')
|
||||
@ -217,12 +221,16 @@ def test_contains(self):
|
||||
self.assert_in('1.3.5-7', '1.2:1.4')
|
||||
self.assert_not_in('1.1', '1.2:1.4')
|
||||
self.assert_not_in('1.5', '1.2:1.4')
|
||||
self.assert_not_in('1.4.2', '1.2:1.4')
|
||||
|
||||
self.assert_in('1.4.2', '1.2:1.4')
|
||||
self.assert_not_in('1.4.2', '1.2:1.4.0')
|
||||
|
||||
self.assert_in('1.2.8', '1.2.7:1.4')
|
||||
self.assert_in('1.2.7:1.4', ':')
|
||||
self.assert_not_in('1.2.5', '1.2.7:1.4')
|
||||
self.assert_not_in('1.4.1', '1.2.7:1.4')
|
||||
|
||||
self.assert_in('1.4.1', '1.2.7:1.4')
|
||||
self.assert_not_in('1.4.1', '1.2.7:1.4.0')
|
||||
|
||||
|
||||
def test_in_list(self):
|
||||
@ -254,6 +262,17 @@ def test_ranges_overlap(self):
|
||||
self.assert_overlaps('1.6:1.9', ':')
|
||||
|
||||
|
||||
def test_overlap_with_containment(self):
|
||||
self.assert_in('1.6.5', '1.6')
|
||||
self.assert_in('1.6.5', ':1.6')
|
||||
|
||||
self.assert_overlaps('1.6.5', ':1.6')
|
||||
self.assert_overlaps(':1.6', '1.6.5')
|
||||
|
||||
self.assert_not_in(':1.6', '1.6.5')
|
||||
self.assert_in('1.6.5', ':1.6')
|
||||
|
||||
|
||||
def test_lists_overlap(self):
|
||||
self.assert_overlaps('1.2b:1.7,5', '1.6:1.9,1')
|
||||
self.assert_overlaps('1,2,3,4,5', '3,4,5,6,7')
|
||||
@ -311,6 +330,32 @@ def test_intersection(self):
|
||||
self.check_intersection(['0:1'], [':'], ['0:1'])
|
||||
|
||||
|
||||
def test_intersect_with_containment(self):
|
||||
self.check_intersection('1.6.5', '1.6.5', ':1.6')
|
||||
self.check_intersection('1.6.5', ':1.6', '1.6.5')
|
||||
|
||||
self.check_intersection('1.6:1.6.5', ':1.6.5', '1.6')
|
||||
self.check_intersection('1.6:1.6.5', '1.6', ':1.6.5')
|
||||
|
||||
|
||||
def test_union_with_containment(self):
|
||||
self.check_union(':1.6', '1.6.5', ':1.6')
|
||||
self.check_union(':1.6', ':1.6', '1.6.5')
|
||||
|
||||
self.check_union(':1.6', ':1.6.5', '1.6')
|
||||
self.check_union(':1.6', '1.6', ':1.6.5')
|
||||
|
||||
|
||||
def test_union_with_containment(self):
|
||||
self.check_union(':', '1.0:', ':2.0')
|
||||
|
||||
self.check_union('1:4', '1:3', '2:4')
|
||||
self.check_union('1:4', '2:4', '1:3')
|
||||
|
||||
# Tests successor/predecessor case.
|
||||
self.check_union('1:4', '1:2', '3:4')
|
||||
|
||||
|
||||
def test_basic_version_satisfaction(self):
|
||||
self.assert_satisfies('4.7.3', '4.7.3')
|
||||
|
||||
@ -326,6 +371,7 @@ def test_basic_version_satisfaction(self):
|
||||
self.assert_does_not_satisfy('4.8', '4.9')
|
||||
self.assert_does_not_satisfy('4', '4.9')
|
||||
|
||||
|
||||
def test_basic_version_satisfaction_in_lists(self):
|
||||
self.assert_satisfies(['4.7.3'], ['4.7.3'])
|
||||
|
||||
@ -341,6 +387,7 @@ def test_basic_version_satisfaction_in_lists(self):
|
||||
self.assert_does_not_satisfy(['4.8'], ['4.9'])
|
||||
self.assert_does_not_satisfy(['4'], ['4.9'])
|
||||
|
||||
|
||||
def test_version_range_satisfaction(self):
|
||||
self.assert_satisfies('4.7b6', '4.3:4.7')
|
||||
self.assert_satisfies('4.3.0', '4.3:4.7')
|
||||
@ -352,6 +399,7 @@ def test_version_range_satisfaction(self):
|
||||
self.assert_satisfies('4.7b6', '4.3:4.7')
|
||||
self.assert_does_not_satisfy('4.8.0', '4.3:4.7')
|
||||
|
||||
|
||||
def test_version_range_satisfaction_in_lists(self):
|
||||
self.assert_satisfies(['4.7b6'], ['4.3:4.7'])
|
||||
self.assert_satisfies(['4.3.0'], ['4.3:4.7'])
|
||||
|
@ -50,10 +50,6 @@
|
||||
from functools import wraps
|
||||
from external.functools import total_ordering
|
||||
|
||||
import llnl.util.compare.none_high as none_high
|
||||
import llnl.util.compare.none_low as none_low
|
||||
import spack.error
|
||||
|
||||
# Valid version characters
|
||||
VALID_VERSION = r'[A-Za-z0-9_.-]'
|
||||
|
||||
@ -256,18 +252,39 @@ def __hash__(self):
|
||||
|
||||
@coerced
|
||||
def __contains__(self, other):
|
||||
return self == other
|
||||
if other is None:
|
||||
return False
|
||||
return other.version[:len(self.version)] == self.version
|
||||
|
||||
|
||||
def is_predecessor(self, other):
|
||||
"""True if the other version is the immediate predecessor of this one.
|
||||
That is, NO versions v exist such that:
|
||||
(self < v < other and v not in self).
|
||||
"""
|
||||
if len(self.version) != len(other.version):
|
||||
return False
|
||||
|
||||
sl = self.version[-1]
|
||||
ol = other.version[-1]
|
||||
return type(sl) == int and type(ol) == int and (ol - sl == 1)
|
||||
|
||||
|
||||
def is_successor(self, other):
|
||||
return other.is_predecessor(self)
|
||||
|
||||
|
||||
@coerced
|
||||
def overlaps(self, other):
|
||||
return self == other
|
||||
return self in other or other in self
|
||||
|
||||
|
||||
@coerced
|
||||
def union(self, other):
|
||||
if self == other:
|
||||
if self == other or other in self:
|
||||
return self
|
||||
elif self in other:
|
||||
return other
|
||||
else:
|
||||
return VersionList([self, other])
|
||||
|
||||
@ -312,9 +329,12 @@ def __lt__(self, other):
|
||||
if other is None:
|
||||
return False
|
||||
|
||||
return (none_low.lt(self.start, other.start) or
|
||||
(self.start == other.start and
|
||||
none_high.lt(self.end, other.end)))
|
||||
s, o = self, other
|
||||
if s.start != o.start:
|
||||
return s.start is None or (o.start is not None and s.start < o.start)
|
||||
|
||||
return (s.end != o.end and
|
||||
o.end is None or (s.end is not None and s.end < o.end))
|
||||
|
||||
|
||||
@coerced
|
||||
@ -335,8 +355,23 @@ def concrete(self):
|
||||
|
||||
@coerced
|
||||
def __contains__(self, other):
|
||||
return (none_low.ge(other.start, self.start) and
|
||||
none_high.le(other.end, self.end))
|
||||
if other is None:
|
||||
return False
|
||||
|
||||
in_lower = (self.start == other.start or
|
||||
self.start is None or
|
||||
(other.start is not None and (
|
||||
self.start < other.start or
|
||||
other.start in self.start)))
|
||||
if not in_lower:
|
||||
return False
|
||||
|
||||
in_upper = (self.end == other.end or
|
||||
self.end is None or
|
||||
(other.end is not None and (
|
||||
self.end > other.end or
|
||||
other.end in self.end)))
|
||||
return in_upper
|
||||
|
||||
|
||||
@coerced
|
||||
@ -372,27 +407,75 @@ def satisfies(self, other):
|
||||
|
||||
@coerced
|
||||
def overlaps(self, other):
|
||||
return (other in self or self in other or
|
||||
((self.start == None or other.end is None or
|
||||
self.start <= other.end) and
|
||||
return ((self.start == None or other.end is None or
|
||||
self.start <= other.end or
|
||||
other.end in self.start or self.start in other.end) and
|
||||
(other.start is None or self.end == None or
|
||||
other.start <= self.end)))
|
||||
other.start <= self.end or
|
||||
other.start in self.end or self.end in other.start))
|
||||
|
||||
|
||||
@coerced
|
||||
def union(self, other):
|
||||
if self.overlaps(other):
|
||||
return VersionRange(none_low.min(self.start, other.start),
|
||||
none_high.max(self.end, other.end))
|
||||
else:
|
||||
if not self.overlaps(other):
|
||||
if (self.end is not None and other.start is not None and
|
||||
self.end.is_predecessor(other.start)):
|
||||
return VersionRange(self.start, other.end)
|
||||
|
||||
if (other.end is not None and self.start is not None and
|
||||
other.end.is_predecessor(self.start)):
|
||||
return VersionRange(other.start, self.end)
|
||||
|
||||
return VersionList([self, other])
|
||||
|
||||
# if we're here, then we know the ranges overlap.
|
||||
if self.start is None or other.start is None:
|
||||
start = None
|
||||
else:
|
||||
start = self.start
|
||||
# TODO: See note in intersection() about < and in discrepancy.
|
||||
if self.start in other.start or other.start < self.start:
|
||||
start = other.start
|
||||
|
||||
if self.end is None or other.end is None:
|
||||
end = None
|
||||
else:
|
||||
end = self.end
|
||||
# TODO: See note in intersection() about < and in discrepancy.
|
||||
if not other.end in self.end:
|
||||
if end in other.end or other.end > self.end:
|
||||
end = other.end
|
||||
|
||||
return VersionRange(start, end)
|
||||
|
||||
|
||||
@coerced
|
||||
def intersection(self, other):
|
||||
if self.overlaps(other):
|
||||
return VersionRange(none_low.max(self.start, other.start),
|
||||
none_high.min(self.end, other.end))
|
||||
if self.start is None:
|
||||
start = other.start
|
||||
else:
|
||||
start = self.start
|
||||
if other.start is not None:
|
||||
if other.start > start or other.start in start:
|
||||
start = other.start
|
||||
|
||||
if self.end is None:
|
||||
end = other.end
|
||||
else:
|
||||
end = self.end
|
||||
# TODO: does this make sense?
|
||||
# This is tricky:
|
||||
# 1.6.5 in 1.6 = True (1.6.5 is more specific)
|
||||
# 1.6 < 1.6.5 = True (lexicographic)
|
||||
# Should 1.6 NOT be less than 1.6.5? Hm.
|
||||
# Here we test (not end in other.end) first to avoid paradox.
|
||||
if other.end is not None and not end in other.end:
|
||||
if other.end < end or other.end in end:
|
||||
end = other.end
|
||||
|
||||
return VersionRange(start, end)
|
||||
|
||||
else:
|
||||
return VersionList()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user