6bfb61fc81
There could be an issue in previous tagged releases due to external factors. In that case, there is no way to fix that version. Validation currently checks out the previous tag and creates an sdist in order to compare requirements changes on bugfix version bumps only. In the case where the previous release has an issue, just handle the failure and move along. Change-Id: I60db41a475c3a13359556198c0611489dffa4b3f Signed-off-by: Sean McGinnis <sean.mcginnis@gmail.com>
164 lines
5.2 KiB
Python
164 lines
5.2 KiB
Python
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""Tools for working with requirements lists."""
|
|
|
|
import logging
|
|
import os.path
|
|
|
|
import pkg_resources
|
|
|
|
from openstack_releases import gitutils
|
|
from openstack_releases import processutils
|
|
from openstack_releases import pythonutils
|
|
from openstack_releases import versionutils
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def find_bad_lower_bound_increases(workdir, repo,
|
|
start_version, new_version, hash,
|
|
report):
|
|
if (new_version.split('.')[-1] == '0' or
|
|
versionutils.looks_like_preversion(new_version)):
|
|
# There is no need to look at the requirements so don't do the
|
|
# extra work.
|
|
return
|
|
start_reqs = get_requirements_at_ref(workdir, repo, start_version)
|
|
hash_reqs = get_requirements_at_ref(workdir, repo, hash)
|
|
compare_lower_bounds(start_reqs, hash_reqs, report)
|
|
|
|
|
|
def compare_lower_bounds(start_reqs, hash_reqs, report):
|
|
for (section, name), req in sorted(hash_reqs.items()):
|
|
if section:
|
|
display_name = '[{}]{}'.format(section, name)
|
|
else:
|
|
display_name = name
|
|
|
|
if (section, name) not in start_reqs:
|
|
report('New dependency {}'.format(display_name))
|
|
|
|
else:
|
|
old_req = start_reqs[(section, name)]
|
|
old_min = get_min_specifier(old_req.specifier)
|
|
new_min = get_min_specifier(req.specifier)
|
|
|
|
if old_min is None and new_min is None:
|
|
# No minimums are specified.
|
|
continue
|
|
|
|
if old_min is None:
|
|
# There is no minimum on the old dependency but there
|
|
# is now a new minimum.
|
|
report(('Added minimum version for dependency {} of {} '
|
|
'without at least incrementing minor number').format(
|
|
display_name, new_min))
|
|
continue
|
|
|
|
if new_min is None:
|
|
# There was a minimum but it has been removed.
|
|
continue
|
|
|
|
if old_min.version not in req.specifier:
|
|
# The old minimum is not in the new spec.
|
|
report(('Changed supported versions for dependency {} from {} '
|
|
'to {} without at least incrementing minor number').format(
|
|
display_name, old_req.specifier, req.specifier))
|
|
|
|
|
|
def get_min_specifier(specifier_set):
|
|
"Find the specifier in the set that controls the lower bound."
|
|
for s in specifier_set:
|
|
if '>' in s.operator:
|
|
return s
|
|
|
|
|
|
def get_requirements_at_ref(workdir, repo, ref):
|
|
"""Check out the repo at the ref and load the list of requirements."""
|
|
body = ''
|
|
|
|
try:
|
|
dest = gitutils.clone_repo(workdir, repo, ref=ref)
|
|
processutils.check_call(['python3', 'setup.py', 'sdist'], cwd=dest)
|
|
sdist_name = pythonutils.get_sdist_name(workdir, repo)
|
|
requirements_filename = os.path.join(
|
|
dest, sdist_name + '.egg-info', 'requires.txt',
|
|
)
|
|
if os.path.exists(requirements_filename):
|
|
with open(requirements_filename, 'r') as f:
|
|
body = f.read()
|
|
else:
|
|
# The package has no dependencies.
|
|
pass
|
|
except Exception:
|
|
# We've had a few cases where a previous version had an issue and could
|
|
# no longer be installed. In this case, just move along.
|
|
LOG.warning('Unable to create sdist, unable to get requirements.')
|
|
LOG.warning('!!! Perform manual comparison for requirements changes'
|
|
'!!!')
|
|
|
|
return parse_requirements(body)
|
|
|
|
|
|
def parse_requirements(body):
|
|
"""Given the requires.txt file for an sdist, parse it.
|
|
|
|
Returns a dictionary mapping (section, pkg name) to the
|
|
requirements specifier.
|
|
|
|
Parses files that look like:
|
|
|
|
pbr>=1.6
|
|
keyring==7.3
|
|
requests>=2.5.2
|
|
PyYAML>=3.1.0
|
|
yamlordereddictloader
|
|
prompt_toolkit
|
|
tqdm
|
|
packaging>=15.2
|
|
mwclient==0.8.1
|
|
jsonschema>=2.6.0
|
|
Jinja2>=2.6
|
|
parawrap
|
|
reno>=2.0.0
|
|
sphinx>=1.6.2
|
|
pyfiglet>=0.7.5
|
|
|
|
[sphinxext]
|
|
sphinx<1.6.1,>=1.5.1
|
|
openstackdocstheme
|
|
sphinxcontrib.datatemplates
|
|
icalendar
|
|
|
|
"""
|
|
requirements = {}
|
|
section = ''
|
|
for line in body.splitlines():
|
|
# Ignore blank lines and comments
|
|
if (not line.strip()) or line.startswith('#'):
|
|
continue
|
|
if line.startswith('['):
|
|
section = line.lstrip('[').rstrip(']')
|
|
continue
|
|
|
|
try:
|
|
parsed_req = pkg_resources.Requirement.parse(line)
|
|
except ValueError:
|
|
LOG.warning('failed to parse %r', line)
|
|
else:
|
|
requirements[(section, parsed_req.name)] = parsed_req
|
|
|
|
return requirements
|