
Fixes bug #1194807 In quantum, we currently have a URL based requirement: http://.../oslo.config-1.2.0a2.tar.gz#egg=oslo.config-1.2.0a2 The requirements check currently ignores this. It turns out that pip has a bug which doesn't where you can end up with the oslo.config 1.1.1 code installed. This is because oslo.config>=1.1.0 gets pulled in as a transitive dep and pip gets confused. You can reproduce with e.g. $> pip install \ http://.../oslo.config-1.2.0a3.tar.gz#egg=oslo.config-1.2.0a3 \ python-keystoneclient $> pip freeze | grep oslo.config oslo.config-1.2.0a3 $> python -c 'from oslo.config.cfg import DeprecatedOpt' Traceback (most recent call last): File "<string>", line 1, in <module> ImportError: cannot import name DeprecatedOpt This is because of a bug with pip where it sees oslo.config-1.2.0a3 and oslo.config as two unrelated things. It should strip the version part of the egg= fragment before using it as a package name, but it doesn't. However, we can simply use the -f/--find-links pip option in our requirements.txt to add the tarball URL to the list of URLs considered and also add the oslo.config>=1.2.0a3 dependency: $> pip install \ -f http://.../oslo.config-1.2.0a3.tar.gz#egg=oslo.config-1.2.0a3 \ 'oslo.config>=1.2.0a3' \ python-keystoneclient $> pip freeze | grep oslo.config oslo.config-1.2.0a3 $> python -c 'from oslo.config.cfg import DeprecatedOpt' This is actually exactly the semantics we want and we go to great lengths in pbr to get these semantics while using a single tarball URL. The only downside to this --find-links strategy is that we gain an extra line in our requirements.txt ... but it does work around the pip bug. I think it makes sense for the requirements check to just ignore --find-links lines for now like it does for URLs and -editable lines. Using this method means we actually do require new versions of libraries consumed this way to be approved into openstack/requirements first since we have an explicit 'oslo.config>=1.2.0a3' listed rather than that being derived from a URL. It may make sense in future to have automation around checking which find-links URLs are allowed ... but the same can be true for normal dependency URLs. This change allows us to move forward and use latest oslo.config in Nova, Neutron, etc. without falling foul of the pip bug. Change-Id: I6f3eb5fd2c75615d9a1cae172aed859b36b27d4c Reviewed-on: https://review.openstack.org/35296 Reviewed-by: James E. Blair <corvus@inaugust.com> Approved: Monty Taylor <mordred@inaugust.com> Reviewed-by: Monty Taylor <mordred@inaugust.com> Tested-by: Jenkins
115 lines
3.9 KiB
Python
Executable File
115 lines
3.9 KiB
Python
Executable File
#! /usr/bin/env python
|
|
# Copyright (C) 2011 OpenStack, LLC.
|
|
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
# Copyright (c) 2013 OpenStack Foundation
|
|
#
|
|
# 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.
|
|
|
|
import os
|
|
import pkg_resources
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
def run_command(cmd):
|
|
print(cmd)
|
|
cmd_list = shlex.split(str(cmd))
|
|
p = subprocess.Popen(cmd_list, stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT)
|
|
(out, nothing) = p.communicate()
|
|
return out.strip()
|
|
|
|
class RequirementsList(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.reqs = {}
|
|
self.failed = False
|
|
|
|
def read_requirements(self, fn):
|
|
for line in open(fn):
|
|
line = line[:line.find('#')]
|
|
line = line.strip()
|
|
if (not line or
|
|
line.startswith('http') or
|
|
line.startswith('-e') or
|
|
line.startswith('-f')):
|
|
continue
|
|
req = pkg_resources.Requirement.parse(line)
|
|
if req.project_name.lower() in self.reqs:
|
|
print("Duplicate requirement in %s: %s" %
|
|
(self.name, str(req)))
|
|
self.failed = True
|
|
self.reqs[req.project_name.lower()] = req
|
|
|
|
def read_all_requirements(self):
|
|
for fn in ['tools/pip-requires',
|
|
'tools/test-requires',
|
|
'requirements.txt',
|
|
'test-requirements.txt',
|
|
]:
|
|
if os.path.exists(fn):
|
|
self.read_requirements(fn)
|
|
|
|
|
|
def main():
|
|
branch = sys.argv[1]
|
|
head = run_command("git rev-parse HEAD").strip()
|
|
head_reqs = RequirementsList('HEAD')
|
|
head_reqs.read_all_requirements()
|
|
|
|
run_command("git remote update")
|
|
run_command("git checkout remotes/origin/%s" % branch)
|
|
branch_reqs = RequirementsList(branch)
|
|
branch_reqs.read_all_requirements()
|
|
|
|
run_command("git checkout %s" % head)
|
|
|
|
reqroot = tempfile.mkdtemp()
|
|
reqdir = os.path.join(reqroot, "requirements")
|
|
run_command("git clone https://review.openstack.org/p/openstack/"
|
|
"requirements --depth 1 %s" % reqdir)
|
|
os.chdir(reqdir)
|
|
run_command("git checkout remotes/origin/%s" % branch)
|
|
print "requirements git sha: %s" % run_command(
|
|
"git rev-parse HEAD").strip()
|
|
os_reqs = RequirementsList('openstack/requirements')
|
|
os_reqs.read_all_requirements()
|
|
|
|
failed = False
|
|
for req in head_reqs.reqs.values():
|
|
name = req.project_name.lower()
|
|
if name in branch_reqs.reqs and req == branch_reqs.reqs[name]:
|
|
continue
|
|
if name not in os_reqs.reqs:
|
|
print("Requirement %s not in openstack/requirements" % str(req))
|
|
failed = True
|
|
continue
|
|
# pkg_resources.Requirement implements __eq__() but not __ne__().
|
|
# There is no implied relationship between __eq__() and __ne__()
|
|
# so we must negate the result of == here instead of using !=.
|
|
if not (req == os_reqs.reqs[name]):
|
|
print("Requirement %s does not match openstack/requirements "
|
|
"value %s" % (str(req), str(os_reqs.reqs[name])))
|
|
failed = True
|
|
|
|
shutil.rmtree(reqroot)
|
|
if failed or os_reqs.failed or head_reqs.failed or branch_reqs.failed:
|
|
sys.exit(1)
|
|
print("Updated requirements match openstack/requirements.")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|