Remove lower-constraint management
We stopped doing this nearly 3½ years ago [1]. We no longer use this tooling in any of our jobs either. It is therefore time to delete all this code. [1] https://governance.openstack.org/tc/resolutions/20220414-drop-lower-constraints Change-Id: I64ba0cec3eeac3f7ded1354bd768f7022d863dc4 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
@@ -18,7 +18,6 @@ import collections
|
||||
import re
|
||||
|
||||
from packaging import markers
|
||||
from packaging import specifiers
|
||||
|
||||
from openstack_requirements import project
|
||||
from openstack_requirements import requirement
|
||||
@@ -303,130 +302,3 @@ def validate(
|
||||
)
|
||||
|
||||
return failed
|
||||
|
||||
|
||||
def _find_constraint(req, constraints):
|
||||
"""Return the constraint matching the markers for req.
|
||||
|
||||
Given a requirement, find the constraint with matching markers.
|
||||
If none match, find a constraint without any markers at all.
|
||||
Otherwise return None.
|
||||
"""
|
||||
if req.markers:
|
||||
req_markers = markers.Marker(req.markers)
|
||||
for constraint_setting, _ in constraints:
|
||||
if constraint_setting.markers == req.markers:
|
||||
return constraint_setting
|
||||
if not constraint_setting.markers:
|
||||
# There is no point in performing the complex
|
||||
# comparison for a constraint that has no markers, so
|
||||
# we skip it here. If we find no closer match then the
|
||||
# loop at the end of the function will look for a
|
||||
# constraint without a marker and use that.
|
||||
continue
|
||||
# NOTE(dhellmann): This is a very naive attempt to check
|
||||
# marker compatibility that relies on internal
|
||||
# implementation details of the packaging library. The
|
||||
# best way to ensure the constraint and requirements match
|
||||
# is to use the same marker string in the corresponding
|
||||
# lines.
|
||||
c_markers = markers.Marker(constraint_setting.markers)
|
||||
env = {
|
||||
str(var): str(val)
|
||||
for var, op, val in c_markers._markers # WARNING: internals
|
||||
}
|
||||
if req_markers.evaluate(env):
|
||||
return constraint_setting
|
||||
# Try looking for a constraint without any markers.
|
||||
for constraint_setting, _ in constraints:
|
||||
if not constraint_setting.markers:
|
||||
return constraint_setting
|
||||
return None
|
||||
|
||||
|
||||
def validate_lower_constraints(req_list, constraints, denylist):
|
||||
"""Return True if there is an error.
|
||||
|
||||
:param reqs: RequirementsList for the head of the branch
|
||||
:param constraints: Parsed lower-constraints.txt or None
|
||||
|
||||
"""
|
||||
if constraints is None:
|
||||
return False
|
||||
|
||||
parsed_constraints = requirement.parse(constraints)
|
||||
|
||||
failed = False
|
||||
|
||||
for fname, freqs in req_list.reqs_by_file.items():
|
||||
|
||||
if fname == 'doc/requirements.txt':
|
||||
# Skip things that are not needed for unit or functional
|
||||
# tests.
|
||||
continue
|
||||
|
||||
print("Validating lower constraints of {}".format(fname))
|
||||
|
||||
for name, reqs in freqs.items():
|
||||
|
||||
if name in denylist:
|
||||
continue
|
||||
|
||||
if name not in parsed_constraints:
|
||||
print('ERROR: Package {!r} is used in {} '
|
||||
'but not in lower-constraints.txt'.format(
|
||||
name, fname))
|
||||
failed = True
|
||||
continue
|
||||
|
||||
for req in reqs:
|
||||
spec = specifiers.SpecifierSet(req.specifiers)
|
||||
# FIXME(dhellmann): This will only find constraints
|
||||
# where the markers match the requirements list
|
||||
# exactly, so we can't do things like use different
|
||||
# constrained versions for different versions of
|
||||
# python 3 if the requirement range is expressed as
|
||||
# python_version>3.0. We can support different
|
||||
# versions if there is a different requirement
|
||||
# specification for each version of python. I don't
|
||||
# really know how smart we want this to be, because
|
||||
# I'm not sure we want to support extremely
|
||||
# complicated dependency sets.
|
||||
constraint_setting = _find_constraint(
|
||||
req,
|
||||
parsed_constraints[name],
|
||||
)
|
||||
if not constraint_setting:
|
||||
print('ERROR: Unable to find constraint for {} '
|
||||
'matching {!r} or without any markers.'.format(
|
||||
name, req.markers))
|
||||
failed = True
|
||||
continue
|
||||
|
||||
version = constraint_setting.specifiers.lstrip('=')
|
||||
|
||||
if not spec.contains(version):
|
||||
print('ERROR: Package {!r} is constrained to {} '
|
||||
'which is incompatible with the settings {} '
|
||||
'from {}.'.format(
|
||||
name, version, req, fname))
|
||||
failed = True
|
||||
|
||||
min = [
|
||||
s
|
||||
for s in req.specifiers.split(',')
|
||||
if '>' in s
|
||||
]
|
||||
if not min:
|
||||
# No minimum specified. Ignore this and let some
|
||||
# other validation trap the error.
|
||||
continue
|
||||
|
||||
expected = min[0].lstrip('>=')
|
||||
if version != expected:
|
||||
print('ERROR: Package {!r} is constrained to {} '
|
||||
'which does not match '
|
||||
'the minimum version specifier {} in {}'.format(
|
||||
name, version, expected, fname))
|
||||
failed = True
|
||||
return failed
|
||||
|
||||
@@ -71,8 +71,4 @@ def read(root):
|
||||
target_files.append('test-requirements-py%s.txt' % py_version)
|
||||
for target_file in target_files:
|
||||
_safe_read(result, target_file, output=requirements)
|
||||
# Read lower-constraints.txt and ensure the key is always present
|
||||
# in case the file is missing.
|
||||
result['lower-constraints.txt'] = None
|
||||
_safe_read(result, 'lower-constraints.txt')
|
||||
return result
|
||||
|
||||
@@ -541,256 +541,6 @@ class TestValidateOne(testtools.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestValidateLowerConstraints(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestValidateLowerConstraints, self).setUp()
|
||||
self._stdout_fixture = fixtures.StringStream('stdout')
|
||||
self.stdout = self.useFixture(self._stdout_fixture).stream
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout))
|
||||
|
||||
def test_no_constraints_file(self):
|
||||
constraints_content = None
|
||||
project_data = {
|
||||
'requirements': {'requirements.txt': 'name>=1.2,!=1.4'},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertFalse(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_no_min(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name==1.2
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {'requirements.txt': 'name!=1.4'},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertFalse(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_matches(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name==1.2
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {'requirements.txt': 'name>=1.2,!=1.4'},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertFalse(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_not_constrained(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {'requirements.txt': 'name>=1.2,!=1.4'},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertTrue(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_mismatch_denylisted(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name==1.2
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {'requirements.txt': 'name>=1.3,!=1.4'},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertFalse(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse('name'),
|
||||
)
|
||||
)
|
||||
|
||||
def test_lower_bound_lower(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name==1.2
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {'requirements.txt': 'name>=1.1,!=1.4'},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertTrue(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_lower_bound_higher(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name==1.2
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {'requirements.txt': 'name>=1.3,!=1.4'},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertTrue(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_constrained_version_excluded(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name==1.2
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {'requirements.txt': 'name>=1.1,!=1.2'},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertTrue(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_constraints_with_markers(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name==1.1;python_version=='2.7'
|
||||
name==2.0;python_version=='3.5'
|
||||
name==2.0;python_version=='3.6'
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {
|
||||
'requirements.txt': textwrap.dedent("""
|
||||
name>=1.1,!=1.2;python_version=='2.7'
|
||||
name>=2.0;python_version=='3.5'
|
||||
name>=2.0;python_version=='3.6'
|
||||
"""),
|
||||
},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertFalse(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_constraints_with_markers_missing_one_req(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name==1.1;python_version=='2.7'
|
||||
name==2.0;python_version=='3.5'
|
||||
name==2.0;python_version=='3.6'
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {
|
||||
'requirements.txt': textwrap.dedent("""
|
||||
name>=1.1,!=1.2;python_version=='2.7'
|
||||
name>=2.0;python_version=='3.5'
|
||||
"""),
|
||||
},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertFalse(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_constraints_with_markers_missing_one_marker(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name==1.1;python_version=='2.7'
|
||||
name==2.0;python_version=='3.5'
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {
|
||||
'requirements.txt': textwrap.dedent("""
|
||||
name>=1.1,!=1.2;python_version=='2.7'
|
||||
name>=2.0;python_version=='3.5'
|
||||
name>=2.0;python_version=='3.6'
|
||||
"""),
|
||||
},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertTrue(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
def test_complex_marker_evaluation(self):
|
||||
constraints_content = textwrap.dedent("""
|
||||
name===0.8.0;python_version=='2.7'
|
||||
name===1.0.0;python_version>='3.0'
|
||||
""")
|
||||
project_data = {
|
||||
'requirements': {
|
||||
'requirements.txt': textwrap.dedent("""
|
||||
name>=0.8.0;python_version<'3.0' # BSD
|
||||
name>=1.0.0;python_version>='3.0' # BSD
|
||||
"""),
|
||||
},
|
||||
'lower-constraints.txt': constraints_content,
|
||||
}
|
||||
head_reqs = check.RequirementsList('testproj', project_data)
|
||||
head_reqs.process(False)
|
||||
self.assertFalse(
|
||||
check.validate_lower_constraints(
|
||||
req_list=head_reqs,
|
||||
constraints=project_data['lower-constraints.txt'],
|
||||
denylist=requirement.parse(''),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TestBackportPythonMarkers(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
@@ -42,8 +42,8 @@ class TestReadProject(testtools.TestCase):
|
||||
root = self.useFixture(fixtures.TempDir()).path
|
||||
proj = project.read(root)
|
||||
self.expectThat(
|
||||
proj, matchers.Equals({'root': root, 'requirements': {},
|
||||
'lower-constraints.txt': None}))
|
||||
proj, matchers.Equals({'root': root, 'requirements': {}})
|
||||
)
|
||||
|
||||
|
||||
class TestProjectExtras(testtools.TestCase):
|
||||
|
||||
@@ -145,15 +145,6 @@ def main():
|
||||
allow_3_only=python_3_branch,
|
||||
)
|
||||
|
||||
failed = (
|
||||
check.validate_lower_constraints(
|
||||
head_reqs,
|
||||
head_proj['lower-constraints.txt'],
|
||||
denylist,
|
||||
)
|
||||
or failed
|
||||
)
|
||||
|
||||
# report the results
|
||||
if failed or head_reqs.failed:
|
||||
print("*** Incompatible requirement found!")
|
||||
|
||||
Reference in New Issue
Block a user