From 524a8f29df2250529b87f8fbd1f25c98608e4cdb Mon Sep 17 00:00:00 2001
From: Daira Hopwood <daira@jacaranda.org>
Date: Fri, 17 Apr 2015 23:39:55 +0100
Subject: [PATCH] Fix version checking by supporting '==' requirements.

Signed-off-by: Daira Hopwood <daira@jacaranda.org>
---
 src/allmydata/__init__.py          | 35 +++++++++++++++---------------
 src/allmydata/_auto_deps.py        |  6 +++--
 src/allmydata/test/test_version.py |  2 ++
 3 files changed, 23 insertions(+), 20 deletions(-)

diff --git a/src/allmydata/__init__.py b/src/allmydata/__init__.py
index 44f971f8..c0f96e86 100644
--- a/src/allmydata/__init__.py
+++ b/src/allmydata/__init__.py
@@ -226,10 +226,14 @@ def get_package_versions_and_locations():
 
 
 def check_requirement(req, vers_and_locs):
-    # We support only conjunctions 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]
+                      .split('!=')[0]
+                      .strip(' ').split('[')[0])
     if name not in vers_and_locs:
         raise PackagingError("no version info for %s" % (name,))
     if req.strip(' ') == name:
@@ -253,25 +257,20 @@ def check_requirement(req, vers_and_locs):
 
 def match_requirement(req, reqlist, actualver):
     for r in reqlist:
-        s = r.split('<=')
-        if len(s) == 2:
-            required = s[1].strip(' ')
-            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('>=')
+        for op, compare, desc in [('<=', lambda x, y: x <= y, "required maximum version"),
+                                  ('>=', lambda x, y: x >= y, "required minimum version"),
+                                  ('==', lambda x, y: x == y, "required exact version"),
+                                  ('!=', lambda x, y: x != y, "excluded version"),
+                                  (None, None, None)]:
+            if op is None:
+                raise PackagingError("no version info or could not understand requirement %r" % (req,))
+            s = r.split(op)
             if len(s) == 2:
                 required = s[1].strip(' ')
-                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('!=')
-                if len(s) == 2:
-                    required = s[1].strip(' ')
-                    if not (actualver != normalized_version(required, what="excluded version %r in %r" % (required, req))):
-                        return False  # not-equal requirement not met
+                if not compare(actualver, normalized_version(required, what="%s %r in %r" % (desc, required, req))):
+                    return False  # requirement not met
                 else:
-                    raise PackagingError("no version info or could not understand requirement %r" % (req,))
+                    break  # next requirement from reqlist
 
     return True
 
diff --git a/src/allmydata/_auto_deps.py b/src/allmydata/_auto_deps.py
index 859cb77b..75e90e53 100644
--- a/src/allmydata/_auto_deps.py
+++ b/src/allmydata/_auto_deps.py
@@ -9,11 +9,12 @@
 # under both the old and new semantics. That can be achieved by limiting
 # requirement specs to one of the following forms:
 #
+#   * == X
 #   * >= X, <= Y where X < Y
 #   * >= X, != Y, != Z, ... where X < Y < Z...
 #
 # (In addition, check_requirement in allmydata/__init__.py only supports
-# >=, <= and != operators.)
+# >=, <=, ==, and != operators.)
 
 install_requires = [
     # We require newer versions of setuptools (actually
@@ -84,7 +85,6 @@ not_import_versionable = [
     'mock',
     'pyasn1',
     'pyasn1-modules',
-    'python-gflags',
 ]
 
 # Dependencies reported by pkg_resources that we can safely ignore.
@@ -96,6 +96,8 @@ ignorable = [
     'twisted-web',
     'twisted-core',
     'twisted-conch',
+    'python-gflags',
+    'httplib2',
 ]
 
 import sys
diff --git a/src/allmydata/test/test_version.py b/src/allmydata/test/test_version.py
index fc642777..1b49de6c 100644
--- a/src/allmydata/test/test_version.py
+++ b/src/allmydata/test/test_version.py
@@ -18,6 +18,7 @@ class CheckRequirement(unittest.TestCase):
         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)})
+        self._check_success("oauth2client == 1.1.0", {"oauth2client": ("1.1.0", "", None)})
 
         self._check_success("zope.interface", {"zope.interface": ("unknown", "", None)})
         self._check_success("mock", {"mock": ("0.6.0", "", None)})
@@ -36,6 +37,7 @@ class CheckRequirement(unittest.TestCase):
         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("oauth2client == 1.1.0", {"oauth2client": ("1.0.0", "", None)})
         self._check_failure("foo >= 1.0", {})
         self._check_failure("foo >= 1.0", {"foo": ("irrational", "", None)})
 
-- 
2.45.2