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:
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()
# 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.
# 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.
# 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",
# * 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.
# 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",
]
else:
install_requires += [
- "pyOpenSSL == 0.13, == 0.13.1",
+ "pyOpenSSL >= 0.13, <= 0.13.1",
]
+from pkg_resources import Requirement
+
from twisted.trial import unittest
from allmydata import check_requirement, cross_check, PackagingError
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.