mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2024-12-20 05:28:04 +00:00
dependecy specs: tolerate new PEP440 semantics too
The latest setuptools (version 8) changed the way dependency specifications ("I can handle libfoo version 2 or 3, but not 4") are interpreted. The new version follows PEP440, which is simpler but somewhat less expressive. Tahoe's _auto_deps.py now uses dep-specs which are correctly parsed by both old and new setuptools. Fixes ticket:2354. * Restrict the requirements in _auto_deps.py to work with either the old or PEP 440 semantics. * Update check_requirement and tests to take account of changes for PEP 440 compatibility. * Fix an error message. * Remove a superfluous TODO.
This commit is contained in:
parent
f77f358dc1
commit
ef455df990
@ -217,11 +217,10 @@ def get_package_versions_and_locations():
|
||||
|
||||
|
||||
def check_requirement(req, vers_and_locs):
|
||||
# TODO: check [] options
|
||||
# We support only disjunctions of <=, >=, and ==
|
||||
# We support only conjunctions of <=, >=, and !=
|
||||
|
||||
reqlist = req.split(',')
|
||||
name = reqlist[0].split('<=')[0].split('>=')[0].split('==')[0].strip(' ').split('[')[0]
|
||||
name = reqlist[0].split('<=')[0].split('>=')[0].split('!=')[0].strip(' ').split('[')[0]
|
||||
if name not in vers_and_locs:
|
||||
raise PackagingError("no version info for %s" % (name,))
|
||||
if req.strip(' ') == name:
|
||||
@ -234,33 +233,38 @@ def check_requirement(req, vers_and_locs):
|
||||
return
|
||||
actualver = normalized_version(actual, what="actual version %r of %s from %r" % (actual, name, location))
|
||||
|
||||
if not match_requirement(req, reqlist, actualver):
|
||||
msg = ("We require %s, but could only find version %s.\n" % (req, actual))
|
||||
if location and location != 'unknown':
|
||||
msg += "The version we found is from %r.\n" % (location,)
|
||||
msg += ("To resolve this problem, uninstall that version, either using your\n"
|
||||
"operating system's package manager or by moving aside the directory.")
|
||||
raise PackagingError(msg)
|
||||
|
||||
|
||||
def match_requirement(req, reqlist, actualver):
|
||||
for r in reqlist:
|
||||
s = r.split('<=')
|
||||
if len(s) == 2:
|
||||
required = s[1].strip(' ')
|
||||
if actualver <= normalized_version(required, what="required maximum version %r in %r" % (required, req)):
|
||||
return # maximum requirement met
|
||||
if not (actualver <= normalized_version(required, what="required maximum version %r in %r" % (required, req))):
|
||||
return False # maximum requirement not met
|
||||
else:
|
||||
s = r.split('>=')
|
||||
if len(s) == 2:
|
||||
required = s[1].strip(' ')
|
||||
if actualver >= normalized_version(required, what="required minimum version %r in %r" % (required, req)):
|
||||
return # minimum requirement met
|
||||
if not (actualver >= normalized_version(required, what="required minimum version %r in %r" % (required, req))):
|
||||
return False # minimum requirement not met
|
||||
else:
|
||||
s = r.split('==')
|
||||
s = r.split('!=')
|
||||
if len(s) == 2:
|
||||
required = s[1].strip(' ')
|
||||
if actualver == normalized_version(required, what="required exact version %r in %r" % (required, req)):
|
||||
return # exact requirement met
|
||||
if not (actualver != normalized_version(required, what="excluded version %r in %r" % (required, req))):
|
||||
return False # not-equal requirement not met
|
||||
else:
|
||||
raise PackagingError("no version info or could not understand requirement %r" % (req,))
|
||||
|
||||
msg = ("We require %s, but could only find version %s.\n" % (req, actual))
|
||||
if location and location != 'unknown':
|
||||
msg += "The version we found is from %r.\n" % (location,)
|
||||
msg += ("To resolve this problem, uninstall that version, either using your\n"
|
||||
"operating system's package manager or by moving aside the directory.")
|
||||
raise PackagingError(msg)
|
||||
return True
|
||||
|
||||
|
||||
_vers_and_locs_list = get_package_versions_and_locations()
|
||||
|
@ -4,6 +4,17 @@
|
||||
# It is ok to import modules from the Python Standard Library if they are
|
||||
# always available, or the import is protected by try...except ImportError.
|
||||
|
||||
# The semantics for requirement specs changed incompatibly in setuptools 8,
|
||||
# which now follows PEP 440. The requirements used in this file must be valid
|
||||
# under both the old and new semantics. That can be achieved by limiting
|
||||
# requirement specs to one of the following forms:
|
||||
#
|
||||
# * >= X, <= Y where X < Y
|
||||
# * >= X, != Y, != Z, ... where X < Y < Z...
|
||||
#
|
||||
# (In addition, check_requirement in allmydata/__init__.py only supports
|
||||
# >=, <= and != operators.)
|
||||
|
||||
install_requires = [
|
||||
# We require newer versions of setuptools (actually
|
||||
# zetuptoolz) to build, but can handle older versions to run.
|
||||
@ -16,7 +27,7 @@ install_requires = [
|
||||
|
||||
# zope.interface >= 3.6.0 is required for Twisted >= 12.1.0.
|
||||
# zope.interface 3.6.3 and 3.6.4 are incompatible with Nevow (#1435).
|
||||
"zope.interface == 3.6.0, == 3.6.1, == 3.6.2, >= 3.6.5",
|
||||
"zope.interface >= 3.6.0, != 3.6.3, != 3.6.4",
|
||||
|
||||
# * foolscap < 0.5.1 had a performance bug which spent O(N**2) CPU for
|
||||
# transferring large mutable files of size N.
|
||||
@ -28,7 +39,7 @@ install_requires = [
|
||||
# Needed for SFTP.
|
||||
# pycrypto 2.2 doesn't work due to <https://bugs.launchpad.net/pycrypto/+bug/620253>
|
||||
# pycrypto 2.4 doesn't work due to <https://bugs.launchpad.net/pycrypto/+bug/881130>
|
||||
"pycrypto == 2.1.0, == 2.3, >= 2.4.1",
|
||||
"pycrypto >= 2.1.0, != 2.2, != 2.4",
|
||||
|
||||
# <http://www.voidspace.org.uk/python/mock/>, 0.8.0 provides "call"
|
||||
"mock >= 0.8.0",
|
||||
@ -102,7 +113,7 @@ if sys.platform == "win32":
|
||||
# * We don't want Twisted >= 12.3.0 to avoid a dependency of its endpoints
|
||||
# code on pywin32. <https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2028>
|
||||
#
|
||||
"Twisted == 11.0.0, == 11.1.0, == 12.0.0, == 12.1.0, == 12.2.0",
|
||||
"Twisted >= 11.0.0, <= 12.2.0",
|
||||
|
||||
# * We need Nevow >= 0.9.33 to avoid a bug in Nevow's setup.py
|
||||
# which imported twisted at setup time.
|
||||
@ -110,7 +121,7 @@ if sys.platform == "win32":
|
||||
# which conflicts with the Twisted requirement above.
|
||||
# <https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2291>
|
||||
#
|
||||
"Nevow == 0.9.33, == 0.10",
|
||||
"Nevow >= 0.9.33, <= 0.10",
|
||||
|
||||
# pyasn1 is needed by twisted.conch in Twisted >= 9.0.
|
||||
"pyasn1 >= 0.0.8a",
|
||||
@ -204,7 +215,7 @@ if _can_use_pyOpenSSL_0_14:
|
||||
]
|
||||
else:
|
||||
install_requires += [
|
||||
"pyOpenSSL == 0.13, == 0.13.1",
|
||||
"pyOpenSSL >= 0.13, <= 0.13.1",
|
||||
]
|
||||
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
|
||||
from pkg_resources import Requirement
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from allmydata import check_requirement, cross_check, PackagingError
|
||||
@ -9,39 +11,49 @@ from allmydata.util.verlib import NormalizedVersion as V, \
|
||||
|
||||
class CheckRequirement(unittest.TestCase):
|
||||
def test_check_requirement(self):
|
||||
check_requirement("setuptools >= 0.6c6", {"setuptools": ("0.6", "", None)})
|
||||
check_requirement("setuptools >= 0.6c6", {"setuptools": ("0.6", "", "distribute")})
|
||||
check_requirement("pycrypto == 2.0.1, == 2.1, >= 2.3", {"pycrypto": ("2.1.0", "", None)})
|
||||
check_requirement("pycrypto == 2.0.1, == 2.1, >= 2.3", {"pycrypto": ("2.4.0", "", None)})
|
||||
check_requirement("zope.interface <= 3.6.2, >= 3.6.6", {"zope.interface": ("3.6.1", "", None)})
|
||||
check_requirement("zope.interface <= 3.6.2, >= 3.6.6", {"zope.interface": ("3.6.6", "", None)})
|
||||
self._check_success("setuptools >= 0.6c6", {"setuptools": ("0.6", "", None)})
|
||||
self._check_success("setuptools >= 0.6c6", {"setuptools": ("0.6", "", "distribute")})
|
||||
self._check_success("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.1.0", "", None)})
|
||||
self._check_success("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.3.0", "", None)})
|
||||
self._check_success("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.4.1", "", None)})
|
||||
self._check_success("Twisted >= 11.0.0, <= 12.2.0", {"Twisted": ("11.0.0", "", None)})
|
||||
self._check_success("Twisted >= 11.0.0, <= 12.2.0", {"Twisted": ("12.2.0", "", None)})
|
||||
|
||||
check_requirement("zope.interface", {"zope.interface": ("unknown", "", None)})
|
||||
check_requirement("mock", {"mock": ("0.6.0", "", None)})
|
||||
check_requirement("foo >= 1.0", {"foo": ("1.0", "", None), "bar": ("2.0", "", None)})
|
||||
self._check_success("zope.interface", {"zope.interface": ("unknown", "", None)})
|
||||
self._check_success("mock", {"mock": ("0.6.0", "", None)})
|
||||
self._check_success("foo >= 1.0", {"foo": ("1.0", "", None), "bar": ("2.0", "", None)})
|
||||
|
||||
check_requirement("foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.7.0", "", None)})
|
||||
self._check_success("foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.7.0", "", None)})
|
||||
|
||||
try:
|
||||
check_requirement("foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.6.1+", "", None)})
|
||||
self._check_success("foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.6.1+", "", None)})
|
||||
# succeeding is ok
|
||||
except PackagingError, e:
|
||||
self.failUnlessIn("could not parse", str(e))
|
||||
|
||||
self.failUnlessRaises(PackagingError, check_requirement,
|
||||
"foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.5.1", "", None)})
|
||||
self.failUnlessRaises(PackagingError, check_requirement,
|
||||
"pycrypto == 2.0.1, == 2.1, >= 2.3", {"pycrypto": ("2.2.0", "", None)})
|
||||
self.failUnlessRaises(PackagingError, check_requirement,
|
||||
"zope.interface <= 3.6.2, >= 3.6.6", {"zope.interface": ("3.6.4", "", None)})
|
||||
self.failUnlessRaises(PackagingError, check_requirement,
|
||||
"foo >= 1.0", {})
|
||||
self.failUnlessRaises(PackagingError, check_requirement,
|
||||
"foo >= 1.0", {"foo": ("irrational", "", None)})
|
||||
self._check_failure("foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.5.1", "", None)})
|
||||
self._check_failure("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.2.0", "", None)})
|
||||
self._check_failure("pycrypto >= 2.1.0, != 2.2, != 2.4", {"pycrypto": ("2.0.0", "", None)})
|
||||
self._check_failure("Twisted >= 11.0.0, <= 12.2.0", {"Twisted": ("10.2.0", "", None)})
|
||||
self._check_failure("Twisted >= 11.0.0, <= 12.2.0", {"Twisted": ("13.0.0", "", None)})
|
||||
self._check_failure("foo >= 1.0", {})
|
||||
self._check_failure("foo >= 1.0", {"foo": ("irrational", "", None)})
|
||||
|
||||
self.failUnlessRaises(ImportError, check_requirement,
|
||||
"foo >= 1.0", {"foo": (None, None, "foomodule")})
|
||||
|
||||
def _check_success(self, req, vers_and_locs):
|
||||
check_requirement(req, vers_and_locs)
|
||||
|
||||
for pkg, ver in vers_and_locs.items():
|
||||
self.failUnless(ver[0] in Requirement.parse(req), str((ver, req)))
|
||||
|
||||
def _check_failure(self, req, vers_and_locs):
|
||||
self.failUnlessRaises(PackagingError, check_requirement, req, vers_and_locs)
|
||||
|
||||
for pkg, ver in vers_and_locs.items():
|
||||
self.failIf(ver[0] in Requirement.parse(req), str((ver, req)))
|
||||
|
||||
def test_cross_check_ticket_1355(self):
|
||||
# The bug in #1355 is triggered when a version string from either pkg_resources or import
|
||||
# is not parseable at all by normalized_version.
|
||||
|
Loading…
Reference in New Issue
Block a user