mirror of
https://github.com/tahoe-lafs/tahoe-lafs.git
synced 2025-06-19 07:48:11 +00:00
Refactor _auto_deps.py and __init__.py, adding more robust checking of dependency versions, and not trusting pkg_resources to get the versions right. refs #1258, #1287
This commit is contained in:
@ -4,56 +4,12 @@ Decentralized storage grid.
|
|||||||
community web site: U{http://tahoe-lafs.org/}
|
community web site: U{http://tahoe-lafs.org/}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We want to call require_auto_deps() before other imports, because the setuptools
|
class PackagingError(EnvironmentError):
|
||||||
# docs claim that if a distribution is installed with --multi-version, it might not
|
"""
|
||||||
# be importable until after pkg_resources.require() has been called for it. We don't
|
Raised when there is an error in packaging of Tahoe-LAFS or its
|
||||||
# have an example of this happening at this time. It is possible that require() isn't
|
dependencies which makes it impossible to proceed safely.
|
||||||
# actually needed because we set __requires__ in the generated startup script, but
|
"""
|
||||||
# that would be an undocumented property of the setuptools implementation.
|
pass
|
||||||
|
|
||||||
from allmydata import _auto_deps
|
|
||||||
_auto_deps.require_auto_deps()
|
|
||||||
|
|
||||||
# This is just to suppress DeprecationWarnings from nevow and twisted.
|
|
||||||
# See http://allmydata.org/trac/tahoe/ticket/859 and
|
|
||||||
# http://divmod.org/trac/ticket/2994 .
|
|
||||||
import warnings
|
|
||||||
warnings.filterwarnings("ignore", category=DeprecationWarning,
|
|
||||||
message="the sha module is deprecated; use the hashlib module instead",
|
|
||||||
append=True)
|
|
||||||
warnings.filterwarnings("ignore", category=DeprecationWarning,
|
|
||||||
message="object.__new__\(\) takes no parameters",
|
|
||||||
append=True)
|
|
||||||
warnings.filterwarnings("ignore", category=DeprecationWarning,
|
|
||||||
message="The popen2 module is deprecated. Use the subprocess module.",
|
|
||||||
append=True)
|
|
||||||
warnings.filterwarnings("ignore", category=DeprecationWarning,
|
|
||||||
message="the md5 module is deprecated; use hashlib instead",
|
|
||||||
append=True)
|
|
||||||
warnings.filterwarnings("ignore", category=DeprecationWarning,
|
|
||||||
message="twisted.web.error.NoResource is deprecated since Twisted 9.0. See twisted.web.resource.NoResource.",
|
|
||||||
append=True)
|
|
||||||
try:
|
|
||||||
import nevow
|
|
||||||
from twisted.persisted import sob
|
|
||||||
from twisted.python import filepath
|
|
||||||
hush_pyflakes = (nevow, sob, filepath)
|
|
||||||
del hush_pyflakes
|
|
||||||
finally:
|
|
||||||
warnings.filters.pop()
|
|
||||||
warnings.filters.pop()
|
|
||||||
warnings.filters.pop()
|
|
||||||
warnings.filters.pop()
|
|
||||||
# Don't pop the filter for the sha module warning because it is also generated
|
|
||||||
# by pycrypto (which we don't want to import unless needed).
|
|
||||||
# warnings.filters.pop()
|
|
||||||
|
|
||||||
# This warning is generated by twisted, PyRex, and possibly other packages,
|
|
||||||
# but can happen at any time, not only when they are imported. See
|
|
||||||
# http://tahoe-lafs.org/trac/tahoe-lafs/ticket/1129 .
|
|
||||||
warnings.filterwarnings("ignore", category=DeprecationWarning,
|
|
||||||
message="BaseException.message has been deprecated as of Python 2.6",
|
|
||||||
append=True)
|
|
||||||
|
|
||||||
__version__ = "unknown"
|
__version__ = "unknown"
|
||||||
try:
|
try:
|
||||||
@ -175,88 +131,245 @@ def get_platform():
|
|||||||
else:
|
else:
|
||||||
return platform.platform()
|
return platform.platform()
|
||||||
|
|
||||||
def get_package_versions_from_setuptools():
|
|
||||||
import pkg_resources
|
|
||||||
return dict([(p.project_name, (p.version, p.location)) for p in pkg_resources.require(__appname__)])
|
|
||||||
|
|
||||||
def package_dir(srcfile):
|
from allmydata.util import verlib
|
||||||
return os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(srcfile))))
|
def normalized_version(verstr):
|
||||||
|
return verlib.NormalizedVersion(verlib.suggest_normalized_version(verstr))
|
||||||
|
|
||||||
|
|
||||||
def get_package_versions_and_locations():
|
def get_package_versions_and_locations():
|
||||||
# because there are a few dependencies that are outside setuptools's ken
|
import warnings
|
||||||
# (Python and platform, and sqlite3 if you are on Python >= 2.5), and
|
from _auto_deps import package_imports, deprecation_messages, deprecation_imports
|
||||||
# because setuptools might fail to find something even though import
|
|
||||||
# finds it:
|
def package_dir(srcfile):
|
||||||
import OpenSSL, allmydata, foolscap.api, nevow, platform, pycryptopp, setuptools, simplejson, twisted, zfec, zope.interface
|
return os.path.dirname(os.path.dirname(os.path.normcase(os.path.realpath(srcfile))))
|
||||||
pysqlitever = None
|
|
||||||
pysqlitefile = None
|
# pkg_resources.require returns the distribution that pkg_resources attempted to put
|
||||||
sqlitever = None
|
# on sys.path, which can differ from the one that we actually import due to #1258,
|
||||||
|
# or any other bug that causes sys.path to be set up incorrectly. Therefore we
|
||||||
|
# must import the packages in order to check their versions and paths.
|
||||||
|
|
||||||
|
# This warning is generated by twisted, PyRex, and possibly other packages,
|
||||||
|
# but can happen at any time, not only when they are imported. See
|
||||||
|
# http://tahoe-lafs.org/trac/tahoe-lafs/ticket/1129 .
|
||||||
|
warnings.filterwarnings("ignore", category=DeprecationWarning,
|
||||||
|
message="BaseException.message has been deprecated as of Python 2.6",
|
||||||
|
append=True)
|
||||||
|
|
||||||
|
# This is to suppress various DeprecationWarnings that occur when modules are imported.
|
||||||
|
# See http://allmydata.org/trac/tahoe/ticket/859 and http://divmod.org/trac/ticket/2994 .
|
||||||
|
|
||||||
|
for msg in deprecation_messages:
|
||||||
|
warnings.filterwarnings("ignore", category=DeprecationWarning, message=msg, append=True)
|
||||||
try:
|
try:
|
||||||
import sqlite3
|
for modulename in deprecation_imports:
|
||||||
except ImportError:
|
|
||||||
try:
|
try:
|
||||||
from pysqlite2 import dbapi2
|
__import__(modulename)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
else:
|
finally:
|
||||||
pysqlitever = dbapi2.version
|
for ign in deprecation_messages:
|
||||||
pysqlitefile = package_dir(dbapi2.__file__)
|
warnings.filters.pop()
|
||||||
sqlitever = dbapi2.sqlite_version
|
|
||||||
else:
|
|
||||||
pysqlitever = sqlite3.version
|
|
||||||
pysqlitefile = package_dir(sqlite3.__file__)
|
|
||||||
sqlitever = sqlite3.sqlite_version
|
|
||||||
|
|
||||||
d1 = {
|
packages = []
|
||||||
'pyOpenSSL': (OpenSSL.__version__, package_dir(OpenSSL.__file__)),
|
|
||||||
__appname__: (allmydata.__version__, package_dir(allmydata.__file__)),
|
def get_version(module, attr):
|
||||||
'foolscap': (foolscap.api.__version__, package_dir(foolscap.__file__)),
|
return str(getattr(module, attr, 'unknown'))
|
||||||
'Nevow': (nevow.__version__, package_dir(nevow.__file__)),
|
|
||||||
'pycryptopp': (pycryptopp.__version__, package_dir(pycryptopp.__file__)),
|
for pkgname, modulename in [(__appname__, 'allmydata')] + package_imports:
|
||||||
'setuptools': (setuptools.__version__, package_dir(setuptools.__file__)),
|
if modulename:
|
||||||
'simplejson': (simplejson.__version__, package_dir(simplejson.__file__)),
|
try:
|
||||||
'pysqlite': (pysqlitever, pysqlitefile),
|
__import__(modulename)
|
||||||
'sqlite': (sqlitever, 'unknown'),
|
module = sys.modules[modulename]
|
||||||
'zope.interface': ('unknown', package_dir(zope.interface.__file__)),
|
except ImportError:
|
||||||
'Twisted': (twisted.__version__, package_dir(twisted.__file__)),
|
packages.append((pkgname, (None, modulename)))
|
||||||
'zfec': (zfec.__version__, package_dir(zfec.__file__)),
|
else:
|
||||||
'python': (platform.python_version(), sys.executable),
|
if 'sqlite' in pkgname:
|
||||||
'platform': (get_platform(), None),
|
packages.append( (pkgname, (get_version(module, 'version'), package_dir(module.__file__))) )
|
||||||
}
|
packages.append( ('sqlite', (get_version(module, 'sqlite_version'), package_dir(module.__file__))) )
|
||||||
|
else:
|
||||||
|
packages.append( (pkgname, (get_version(module, '__version__'), package_dir(module.__file__))) )
|
||||||
|
elif pkgname == 'python':
|
||||||
|
packages.append( (pkgname, (platform.python_version(), sys.executable)) )
|
||||||
|
elif pkgname == 'platform':
|
||||||
|
packages.append( (pkgname, (get_platform(), None)) )
|
||||||
|
|
||||||
|
return packages
|
||||||
|
|
||||||
|
|
||||||
|
def check_requirement(req, vers_and_locs):
|
||||||
|
# TODO: check [] options
|
||||||
|
# We support only disjunctions of >= and ==
|
||||||
|
|
||||||
|
reqlist = req.split(',')
|
||||||
|
name = reqlist[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
|
||||||
|
(actual, location) = vers_and_locs[name]
|
||||||
|
if actual is None:
|
||||||
|
raise ImportError("could not import %r for requirement %r" % (location, req))
|
||||||
|
if actual == 'unknown':
|
||||||
|
return
|
||||||
|
actualver = normalized_version(actual)
|
||||||
|
|
||||||
|
for r in reqlist:
|
||||||
|
s = r.split('>=')
|
||||||
|
if len(s) == 2:
|
||||||
|
required = s[1].strip(' ')
|
||||||
|
if actualver >= normalized_version(required):
|
||||||
|
return # minimum requirement met
|
||||||
|
else:
|
||||||
|
s = r.split('==')
|
||||||
|
if len(s) == 2:
|
||||||
|
required = s[1].strip(' ')
|
||||||
|
if actualver == normalized_version(required):
|
||||||
|
return # exact requirement 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)
|
||||||
|
|
||||||
|
|
||||||
|
_vers_and_locs_list = get_package_versions_and_locations()
|
||||||
|
|
||||||
|
|
||||||
|
def cross_check_pkg_resources_versus_import():
|
||||||
|
"""This function returns a list of errors due to any failed cross-checks."""
|
||||||
|
|
||||||
# But we prefer to get all the dependencies as known by setuptools:
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
try:
|
from _auto_deps import install_requires
|
||||||
d2 = get_package_versions_from_setuptools()
|
|
||||||
except pkg_resources.DistributionNotFound:
|
errors = []
|
||||||
# See docstring in _auto_deps.require_auto_deps() to explain why it makes sense to ignore this exception.
|
not_pkg_resourceable = set(['sqlite', 'sqlite3', 'python', 'platform', __appname__.lower()])
|
||||||
pass
|
not_import_versionable = set(['zope.interface', 'mock', 'pyasn1'])
|
||||||
else:
|
ignorable = set(['argparse', 'pyutil', 'zbase32'])
|
||||||
d1.update(d2)
|
|
||||||
|
pkg_resources_vers_and_locs = dict([(p.project_name.lower(), (str(p.version), p.location))
|
||||||
|
for p in pkg_resources.require(install_requires)])
|
||||||
|
|
||||||
|
for name, (imp_ver, imp_loc) in _vers_and_locs_list:
|
||||||
|
name = name.lower()
|
||||||
|
if name not in not_pkg_resourceable:
|
||||||
|
if name not in pkg_resources_vers_and_locs:
|
||||||
|
errors.append("Warning: dependency %s (version %s imported from %r) was not found by pkg_resources."
|
||||||
|
% (name, imp_ver, imp_loc))
|
||||||
|
|
||||||
|
pr_ver, pr_loc = pkg_resources_vers_and_locs[name]
|
||||||
|
try:
|
||||||
|
pr_normver = normalized_version(pr_ver)
|
||||||
|
except Exception, e:
|
||||||
|
errors.append("Warning: version number %s found for dependency %s by pkg_resources could not be parsed. "
|
||||||
|
"The version found by import was %s from %r. "
|
||||||
|
"pkg_resources thought it should be found at %r. "
|
||||||
|
"The exception was %s: %s"
|
||||||
|
% (pr_ver, name, imp_ver, imp_loc, pr_loc, e.__class__.name, e))
|
||||||
|
else:
|
||||||
|
if imp_ver == 'unknown':
|
||||||
|
if name not in not_import_versionable:
|
||||||
|
errors.append("Warning: unexpectedly could not find a version number for dependency %s imported from %r. "
|
||||||
|
"pkg_resources thought it should be version %s at %r."
|
||||||
|
% (name, imp_loc, pr_ver, pr_loc))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
imp_normver = normalized_version(imp_ver)
|
||||||
|
except Exception, e:
|
||||||
|
errors.append("Warning: version number %s found for dependency %s (imported from %r) could not be parsed. "
|
||||||
|
"pkg_resources thought it should be version %s at %r. "
|
||||||
|
"The exception was %s: %s"
|
||||||
|
% (imp_ver, name, imp_loc, pr_ver, pr_loc, e.__class__.name, e))
|
||||||
|
else:
|
||||||
|
if pr_ver == 'unknown' or (pr_normver != imp_normver):
|
||||||
|
if not os.path.normpath(os.path.realpath(pr_loc)) == os.path.normpath(os.path.realpath(imp_loc)):
|
||||||
|
errors.append("Warning: dependency %s found to have version number %s (normalized to %s, from %r) "
|
||||||
|
"by pkg_resources, but version %s (normalized to %s, from %r) by import."
|
||||||
|
% (name, pr_ver, str(pr_normver), pr_loc, imp_ver, str(imp_normver), imp_loc))
|
||||||
|
|
||||||
|
imported_packages = set([p.lower() for (p, _) in _vers_and_locs_list])
|
||||||
|
for pr_name, (pr_ver, pr_loc) in pkg_resources_vers_and_locs.iteritems():
|
||||||
|
if pr_name not in imported_packages and pr_name not in ignorable:
|
||||||
|
errors.append("Warning: dependency %s (version %s) found by pkg_resources not found by import."
|
||||||
|
% (pr_name, pr_ver))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def get_error_string(errors):
|
||||||
|
from allmydata._auto_deps import install_requires
|
||||||
|
|
||||||
|
return ("\n%s\n\n"
|
||||||
|
"For debugging purposes, the PYTHONPATH was\n"
|
||||||
|
" %r\n"
|
||||||
|
"install_requires was\n"
|
||||||
|
" %r\n"
|
||||||
|
"sys.path after importing pkg_resources was\n"
|
||||||
|
" %s\n"
|
||||||
|
% ("\n".join(errors), os.environ.get('PYTHONPATH'), install_requires, (os.pathsep+"\n ").join(sys.path)) )
|
||||||
|
|
||||||
|
def check_all_requirements():
|
||||||
|
"""This function returns a list of errors due to any failed checks."""
|
||||||
|
|
||||||
|
from allmydata._auto_deps import install_requires
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# we require 2.4.4 on non-UCS-2, non-Redhat builds to avoid <http://www.python.org/news/security/PSF-2006-001/>
|
||||||
|
# we require 2.4.3 on non-UCS-2 Redhat, because 2.4.3 is common on Redhat-based distros and will have patched the above bug
|
||||||
|
# we require at least 2.4.2 in any case to avoid a bug in the base64 module: <http://bugs.python.org/issue1171487>
|
||||||
|
if sys.maxunicode == 65535:
|
||||||
|
if sys.version_info < (2, 4, 2) or sys.version_info[0] > 2:
|
||||||
|
errors.append("Tahoe-LAFS current requires Python v2.4.2 or greater "
|
||||||
|
"for a UCS-2 build (but less than v3), not %r" %
|
||||||
|
(sys.version_info,))
|
||||||
|
elif platform.platform().lower().find('redhat') >= 0:
|
||||||
|
if sys.version_info < (2, 4, 3) or sys.version_info[0] > 2:
|
||||||
|
errors.append("Tahoe-LAFS current requires Python v2.4.3 or greater "
|
||||||
|
"on Redhat-based distributions (but less than v3), not %r" %
|
||||||
|
(sys.version_info,))
|
||||||
|
else:
|
||||||
|
if sys.version_info < (2, 4, 4) or sys.version_info[0] > 2:
|
||||||
|
errors.append("Tahoe-LAFS current requires Python v2.4.4 or greater "
|
||||||
|
"for a non-UCS-2 build (but less than v3), not %r" %
|
||||||
|
(sys.version_info,))
|
||||||
|
|
||||||
|
vers_and_locs = dict(_vers_and_locs_list)
|
||||||
|
for requirement in install_requires:
|
||||||
|
try:
|
||||||
|
check_requirement(requirement, vers_and_locs)
|
||||||
|
except Exception, e:
|
||||||
|
errors.append("%s: %s" % (e.__class__.__name__, e))
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
raise PackagingError(get_error_string(errors))
|
||||||
|
|
||||||
|
check_all_requirements()
|
||||||
|
|
||||||
return d1
|
|
||||||
|
|
||||||
def get_package_versions():
|
def get_package_versions():
|
||||||
return dict([(k, v) for k, (v, l) in get_package_versions_and_locations().iteritems()])
|
return dict([(k, v) for k, (v, l) in _vers_and_locs_list])
|
||||||
|
|
||||||
def get_package_locations():
|
def get_package_locations():
|
||||||
return dict([(k, l) for k, (v, l) in get_package_versions_and_locations().iteritems()])
|
return dict([(k, l) for k, (v, l) in _vers_and_locs_list])
|
||||||
|
|
||||||
def get_package_versions_string(show_paths=False):
|
def get_package_versions_string(show_paths=False):
|
||||||
vers_and_locs = get_package_versions_and_locations()
|
|
||||||
res = []
|
res = []
|
||||||
for p in [__appname__, "foolscap", "pycryptopp", "zfec", "Twisted", "Nevow", "zope.interface", "python", "platform"]:
|
for p, (v, loc) in _vers_and_locs_list:
|
||||||
(ver, loc) = vers_and_locs.get(p, ('UNKNOWN', 'UNKNOWN'))
|
|
||||||
info = str(p) + ": " + str(ver)
|
|
||||||
if show_paths:
|
|
||||||
info = info + " (%s)" % str(loc)
|
|
||||||
res.append(info)
|
|
||||||
if vers_and_locs.has_key(p):
|
|
||||||
del vers_and_locs[p]
|
|
||||||
|
|
||||||
for p, (v, loc) in vers_and_locs.iteritems():
|
|
||||||
info = str(p) + ": " + str(v)
|
info = str(p) + ": " + str(v)
|
||||||
if show_paths:
|
if show_paths:
|
||||||
info = info + " (%s)" % str(loc)
|
info = info + " (%s)" % str(loc)
|
||||||
res.append(info)
|
res.append(info)
|
||||||
return ', '.join(res)
|
|
||||||
|
output = ",\n".join(res) + "\n"
|
||||||
|
|
||||||
|
if not hasattr(sys, 'frozen'):
|
||||||
|
errors = cross_check_pkg_resources_versus_import()
|
||||||
|
if errors:
|
||||||
|
output += get_error_string(errors)
|
||||||
|
|
||||||
|
return output
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
# Note: do not import any module from Tahoe-LAFS itself in this
|
# Note: please minimize imports in this file. In particular, do not import
|
||||||
# file. Also please avoid importing modules from other packages than
|
# any module from Tahoe-LAFS or its dependencies, and do not import any
|
||||||
# the Python Standard Library if at all possible (exception: we rely
|
# modules at all at global level. That includes setuptools and pkg_resources.
|
||||||
# on importing pkg_resources, which is provided by setuptools,
|
# It is ok to import modules from the Python Standard Library if they are
|
||||||
# zetuptoolz, distribute, and perhaps in the future distutils2, for
|
# always available, or the import is protected by try...except ImportError.
|
||||||
# the require_auto_deps() function.)
|
|
||||||
|
|
||||||
install_requires=[
|
|
||||||
# we require newer versions of setuptools (actually
|
|
||||||
# zetuptoolz) to build, but can handle older versions to run
|
|
||||||
"setuptools >= 0.6c6",
|
|
||||||
|
|
||||||
|
install_requires = [
|
||||||
"zfec >= 1.1.0",
|
"zfec >= 1.1.0",
|
||||||
|
|
||||||
# Feisty has simplejson 1.4
|
# Feisty has simplejson 1.4
|
||||||
"simplejson >= 1.4",
|
"simplejson >= 1.4",
|
||||||
|
|
||||||
"zope.interface",
|
"zope.interface",
|
||||||
|
|
||||||
"Twisted >= 2.4.0",
|
"Twisted >= 2.4.0",
|
||||||
|
|
||||||
# foolscap < 0.5.1 had a performance bug which spent
|
# foolscap < 0.5.1 had a performance bug which spent
|
||||||
@ -24,11 +20,12 @@ install_requires=[
|
|||||||
# foolscap < 0.6 is incompatible with Twisted 10.2.0.
|
# foolscap < 0.6 is incompatible with Twisted 10.2.0.
|
||||||
# foolscap 0.6.1 quiets a DeprecationWarning.
|
# foolscap 0.6.1 quiets a DeprecationWarning.
|
||||||
"foolscap[secure_connections] >= 0.6.1",
|
"foolscap[secure_connections] >= 0.6.1",
|
||||||
|
|
||||||
"Nevow >= 0.6.0",
|
"Nevow >= 0.6.0",
|
||||||
|
|
||||||
# Needed for SFTP. pyasn1 is needed by twisted.conch in Twisted >= 9.0.
|
# Needed for SFTP. pyasn1 is needed by twisted.conch in Twisted >= 9.0.
|
||||||
# pycrypto 2.2 doesn't work due to https://bugs.launchpad.net/pycrypto/+bug/620253
|
# pycrypto 2.2 doesn't work due to https://bugs.launchpad.net/pycrypto/+bug/620253
|
||||||
"pycrypto == 2.0.1, == 2.1, >= 2.3",
|
"pycrypto == 2.0.1, == 2.1.0, >= 2.3",
|
||||||
"pyasn1 >= 0.0.8a",
|
"pyasn1 >= 0.0.8a",
|
||||||
|
|
||||||
# http://www.voidspace.org.uk/python/mock/
|
# http://www.voidspace.org.uk/python/mock/
|
||||||
@ -36,76 +33,73 @@ install_requires=[
|
|||||||
|
|
||||||
# Will be needed to test web apps, but not yet. See #1001.
|
# Will be needed to test web apps, but not yet. See #1001.
|
||||||
#"windmill >= 1.3",
|
#"windmill >= 1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
import platform
|
# Includes some indirect dependencies, but does not include allmydata.
|
||||||
if platform.machine().lower() in ['i386', 'x86_64', 'amd64', 'x86', '']:
|
# These are in the order they should be listed by --version, etc.
|
||||||
|
package_imports = [
|
||||||
|
# package name module name
|
||||||
|
('foolscap', 'foolscap'),
|
||||||
|
('pycryptopp', 'pycryptopp'),
|
||||||
|
('zfec', 'zfec'),
|
||||||
|
('Twisted', 'twisted'),
|
||||||
|
('Nevow', 'nevow'),
|
||||||
|
('zope.interface', 'zope.interface'),
|
||||||
|
('python', None),
|
||||||
|
('platform', None),
|
||||||
|
('pyOpenSSL', 'OpenSSL'),
|
||||||
|
('simplejson', 'simplejson'),
|
||||||
|
('pycrypto', 'Crypto'),
|
||||||
|
('pyasn1', 'pyasn1'),
|
||||||
|
('mock', 'mock'),
|
||||||
|
]
|
||||||
|
|
||||||
|
def require_more():
|
||||||
|
import platform, sys
|
||||||
|
|
||||||
|
if platform.machine().lower() in ['i386', 'x86_64', 'amd64', 'x86', '']:
|
||||||
# pycryptopp v0.5.20 fixes bugs in SHA-256 and AES on x86 or amd64
|
# pycryptopp v0.5.20 fixes bugs in SHA-256 and AES on x86 or amd64
|
||||||
# (from Crypto++ revisions 470, 471, 480, 492). The '' is there
|
# (from Crypto++ revisions 470, 471, 480, 492). The '' is there
|
||||||
# in case platform.machine is broken and this is actually an x86
|
# in case platform.machine is broken and this is actually an x86
|
||||||
# or amd64 machine.
|
# or amd64 machine.
|
||||||
install_requires.append("pycryptopp >= 0.5.20")
|
install_requires.append("pycryptopp >= 0.5.20")
|
||||||
else:
|
else:
|
||||||
# pycryptopp v0.5.13 had a new bundled version of Crypto++
|
# pycryptopp v0.5.13 had a new bundled version of Crypto++
|
||||||
# (v5.6.0) and a new bundled version of setuptools (although that
|
# (v5.6.0) and a new bundled version of setuptools (although that
|
||||||
# shouldn't make any different to users of pycryptopp).
|
# shouldn't make any difference to users of pycryptopp).
|
||||||
install_requires.append("pycryptopp >= 0.5.14")
|
install_requires.append("pycryptopp >= 0.5.14")
|
||||||
|
|
||||||
|
# Sqlite comes built into Python >= 2.5, and is provided by the "pysqlite"
|
||||||
# Sqlite comes built into Python >= 2.5, and is provided by the "pysqlite"
|
# distribution for Python 2.4.
|
||||||
# distribution for Python 2.4.
|
try:
|
||||||
import sys
|
import sqlite3
|
||||||
if sys.version_info < (2, 5):
|
sqlite3 # hush pyflakes
|
||||||
|
package_imports.append(('sqlite3', 'sqlite3'))
|
||||||
|
except ImportError:
|
||||||
# pysqlite v2.0.5 was shipped in Ubuntu 6.06 LTS "dapper" and Nexenta NCP 1.
|
# pysqlite v2.0.5 was shipped in Ubuntu 6.06 LTS "dapper" and Nexenta NCP 1.
|
||||||
install_requires.append("pysqlite >= 2.0.5")
|
install_requires.append("pysqlite >= 2.0.5")
|
||||||
|
package_imports.append(('pysqlite', 'pysqlite.dbapi2'))
|
||||||
|
|
||||||
if hasattr(sys, 'frozen'): # for py2exe
|
if not hasattr(sys, 'frozen'):
|
||||||
install_requires=[]
|
# we require newer versions of setuptools (actually
|
||||||
del sys # clean up namespace
|
# zetuptoolz) to build, but can handle older versions to run
|
||||||
|
install_requires.append("setuptools >= 0.6c6")
|
||||||
|
package_imports.append(('setuptools', 'setuptools'))
|
||||||
|
|
||||||
def require_python_version():
|
require_more()
|
||||||
import sys, platform
|
|
||||||
|
|
||||||
# we require 2.4.4 on non-UCS-2, non-Redhat builds to avoid <http://www.python.org/news/security/PSF-2006-001/>
|
deprecation_messages = [
|
||||||
# we require 2.4.3 on non-UCS-2 Redhat, because 2.4.3 is common on Redhat-based distros and will have patched the above bug
|
"the sha module is deprecated; use the hashlib module instead",
|
||||||
# we require at least 2.4.2 in any case to avoid a bug in the base64 module: <http://bugs.python.org/issue1171487>
|
"object.__new__\(\) takes no parameters",
|
||||||
if sys.maxunicode == 65535:
|
"The popen2 module is deprecated. Use the subprocess module.",
|
||||||
if sys.version_info < (2, 4, 2) or sys.version_info[0] > 2:
|
"the md5 module is deprecated; use hashlib instead",
|
||||||
raise NotImplementedError("Tahoe-LAFS current requires Python v2.4.2 or greater "
|
"twisted.web.error.NoResource is deprecated since Twisted 9.0. See twisted.web.resource.NoResource.",
|
||||||
"for a UCS-2 build (but less than v3), not %r" %
|
"the sets module is deprecated",
|
||||||
(sys.version_info,))
|
]
|
||||||
elif platform.platform().lower().find('redhat') >= 0:
|
|
||||||
if sys.version_info < (2, 4, 3) or sys.version_info[0] > 2:
|
|
||||||
raise NotImplementedError("Tahoe-LAFS current requires Python v2.4.3 or greater "
|
|
||||||
"on Redhat-based distributions (but less than v3), not %r" %
|
|
||||||
(sys.version_info,))
|
|
||||||
else:
|
|
||||||
if sys.version_info < (2, 4, 4) or sys.version_info[0] > 2:
|
|
||||||
raise NotImplementedError("Tahoe-LAFS current requires Python v2.4.4 or greater "
|
|
||||||
"for a non-UCS-2 build (but less than v3), not %r" %
|
|
||||||
(sys.version_info,))
|
|
||||||
|
|
||||||
def require_auto_deps():
|
deprecation_imports = [
|
||||||
"""
|
'nevow',
|
||||||
The purpose of this function is to raise a pkg_resources exception if any of the
|
'twisted.persisted.sob',
|
||||||
requirements can't be imported. This is just to give earlier and more explicit error
|
'twisted.python.filepath',
|
||||||
messages, as opposed to waiting until the source code tries to import some module from one
|
'Crypto.Hash.SHA',
|
||||||
of these packages and gets an ImportError. This function gets called from
|
]
|
||||||
src/allmydata/__init__.py .
|
|
||||||
"""
|
|
||||||
require_python_version()
|
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
for requirement in install_requires:
|
|
||||||
try:
|
|
||||||
pkg_resources.require(requirement)
|
|
||||||
except pkg_resources.DistributionNotFound:
|
|
||||||
# there is no .egg-info present for this requirement, which
|
|
||||||
# either means that it isn't installed, or it is installed in a
|
|
||||||
# way that pkg_resources can't find it (but regular python
|
|
||||||
# might). There are several older Linux distributions which
|
|
||||||
# provide our dependencies just fine, but they don't ship
|
|
||||||
# .egg-info files. Note that if there *is* an .egg-info file,
|
|
||||||
# but it shows a too-old version, then we'll get a
|
|
||||||
# VersionConflict error instead of DistributionNotFound.
|
|
||||||
pass
|
|
||||||
|
131
src/allmydata/test/test_version.py
Normal file
131
src/allmydata/test/test_version.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
|
||||||
|
from twisted.trial import unittest
|
||||||
|
|
||||||
|
from allmydata import check_requirement, PackagingError
|
||||||
|
from allmydata.util.verlib import NormalizedVersion as V, \
|
||||||
|
IrrationalVersionError, \
|
||||||
|
suggest_normalized_version as suggest
|
||||||
|
|
||||||
|
|
||||||
|
class CheckRequirement(unittest.TestCase):
|
||||||
|
def test_check_requirement(self):
|
||||||
|
check_requirement("setuptools >= 0.6c6", {"setuptools": ("0.6", "")})
|
||||||
|
check_requirement("pycrypto == 2.0.1, == 2.1, >= 2.3", {"pycrypto": ("2.1.0", "")})
|
||||||
|
check_requirement("pycrypto == 2.0.1, == 2.1, >= 2.3", {"pycrypto": ("2.4.0", "")})
|
||||||
|
|
||||||
|
check_requirement("zope.interface", {"zope.interface": ("unknown", "")})
|
||||||
|
check_requirement("mock", {"mock": ("0.6.0", "")})
|
||||||
|
check_requirement("foo >= 1.0", {"foo": ("1.0", ""), "bar": ("2.0", "")})
|
||||||
|
|
||||||
|
check_requirement("foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.7.0", "")})
|
||||||
|
|
||||||
|
self.failUnlessRaises(PackagingError, check_requirement,
|
||||||
|
"foolscap[secure_connections] >= 0.6.0", {"foolscap": ("0.5.1", "")})
|
||||||
|
self.failUnlessRaises(PackagingError, check_requirement,
|
||||||
|
"pycrypto == 2.0.1, == 2.1, >= 2.3", {"pycrypto": ("2.2.0", "")})
|
||||||
|
self.failUnlessRaises(PackagingError, check_requirement,
|
||||||
|
"foo >= 1.0", {})
|
||||||
|
|
||||||
|
|
||||||
|
# based on https://bitbucket.org/tarek/distutilsversion/src/17df9a7d96ef/test_verlib.py
|
||||||
|
|
||||||
|
class VersionTestCase(unittest.TestCase):
|
||||||
|
versions = ((V('1.0'), '1.0'),
|
||||||
|
(V('1.1'), '1.1'),
|
||||||
|
(V('1.2.3'), '1.2.3'),
|
||||||
|
(V('1.2'), '1.2'),
|
||||||
|
(V('1.2.3a4'), '1.2.3a4'),
|
||||||
|
(V('1.2c4'), '1.2c4'),
|
||||||
|
(V('1.2.3.4'), '1.2.3.4'),
|
||||||
|
(V('1.2.3.4.0b3'), '1.2.3.4b3'),
|
||||||
|
(V('1.2.0.0.0'), '1.2'),
|
||||||
|
(V('1.0.dev345'), '1.0.dev345'),
|
||||||
|
(V('1.0.post456.dev623'), '1.0.post456.dev623'))
|
||||||
|
|
||||||
|
def test_basic_versions(self):
|
||||||
|
for v, s in self.versions:
|
||||||
|
self.failUnlessEqual(str(v), s)
|
||||||
|
|
||||||
|
def test_from_parts(self):
|
||||||
|
for v, s in self.versions:
|
||||||
|
parts = v.parts
|
||||||
|
v2 = V.from_parts(*parts)
|
||||||
|
self.failUnlessEqual(v, v2)
|
||||||
|
self.failUnlessEqual(str(v), str(v2))
|
||||||
|
|
||||||
|
def test_irrational_versions(self):
|
||||||
|
irrational = ('1', '1.2a', '1.2.3b', '1.02', '1.2a03',
|
||||||
|
'1.2a3.04', '1.2.dev.2', '1.2dev', '1.2.dev',
|
||||||
|
'1.2.dev2.post2', '1.2.post2.dev3.post4')
|
||||||
|
|
||||||
|
for s in irrational:
|
||||||
|
self.failUnlessRaises(IrrationalVersionError, V, s)
|
||||||
|
|
||||||
|
def test_comparison(self):
|
||||||
|
self.failUnlessRaises(TypeError, lambda: V('1.2.0') == '1.2')
|
||||||
|
|
||||||
|
self.failUnlessEqual(V('1.2.0'), V('1.2'))
|
||||||
|
self.failIfEqual(V('1.2.0'), V('1.2.3'))
|
||||||
|
self.failUnless(V('1.2.0') < V('1.2.3'))
|
||||||
|
self.failUnless(V('1.0') > V('1.0b2'))
|
||||||
|
self.failUnless(V('1.0') > V('1.0c2') > V('1.0c1') > V('1.0b2') > V('1.0b1')
|
||||||
|
> V('1.0a2') > V('1.0a1'))
|
||||||
|
self.failUnless(V('1.0.0') > V('1.0.0c2') > V('1.0.0c1') > V('1.0.0b2') > V('1.0.0b1')
|
||||||
|
> V('1.0.0a2') > V('1.0.0a1'))
|
||||||
|
|
||||||
|
self.failUnless(V('1.0') < V('1.0.post456.dev623'))
|
||||||
|
self.failUnless(V('1.0.post456.dev623') < V('1.0.post456') < V('1.0.post1234'))
|
||||||
|
|
||||||
|
self.failUnless(V('1.0a1')
|
||||||
|
< V('1.0a2.dev456')
|
||||||
|
< V('1.0a2')
|
||||||
|
< V('1.0a2.1.dev456') # e.g. need to do a quick post release on 1.0a2
|
||||||
|
< V('1.0a2.1')
|
||||||
|
< V('1.0b1.dev456')
|
||||||
|
< V('1.0b2')
|
||||||
|
< V('1.0c1')
|
||||||
|
< V('1.0c2.dev456')
|
||||||
|
< V('1.0c2')
|
||||||
|
< V('1.0.dev7')
|
||||||
|
< V('1.0.dev18')
|
||||||
|
< V('1.0.dev456')
|
||||||
|
< V('1.0.dev1234')
|
||||||
|
< V('1.0')
|
||||||
|
< V('1.0.post456.dev623') # development version of a post release
|
||||||
|
< V('1.0.post456'))
|
||||||
|
|
||||||
|
def test_suggest_normalized_version(self):
|
||||||
|
self.failUnlessEqual(suggest('1.0'), '1.0')
|
||||||
|
self.failUnlessEqual(suggest('1.0-alpha1'), '1.0a1')
|
||||||
|
self.failUnlessEqual(suggest('1.0c2'), '1.0c2')
|
||||||
|
self.failUnlessEqual(suggest('walla walla washington'), None)
|
||||||
|
self.failUnlessEqual(suggest('2.4c1'), '2.4c1')
|
||||||
|
|
||||||
|
# from setuptools
|
||||||
|
self.failUnlessEqual(suggest('0.4a1.r10'), '0.4a1.post10')
|
||||||
|
self.failUnlessEqual(suggest('0.7a1dev-r66608'), '0.7a1.dev66608')
|
||||||
|
self.failUnlessEqual(suggest('0.6a9.dev-r41475'), '0.6a9.dev41475')
|
||||||
|
self.failUnlessEqual(suggest('2.4preview1'), '2.4c1')
|
||||||
|
self.failUnlessEqual(suggest('2.4pre1') , '2.4c1')
|
||||||
|
self.failUnlessEqual(suggest('2.1-rc2'), '2.1c2')
|
||||||
|
|
||||||
|
# from pypi
|
||||||
|
self.failUnlessEqual(suggest('0.1dev'), '0.1.dev0')
|
||||||
|
self.failUnlessEqual(suggest('0.1.dev'), '0.1.dev0')
|
||||||
|
|
||||||
|
# we want to be able to parse Twisted
|
||||||
|
# development versions are like post releases in Twisted
|
||||||
|
self.failUnlessEqual(suggest('9.0.0+r2363'), '9.0.0.post2363')
|
||||||
|
|
||||||
|
# pre-releases are using markers like "pre1"
|
||||||
|
self.failUnlessEqual(suggest('9.0.0pre1'), '9.0.0c1')
|
||||||
|
|
||||||
|
# we want to be able to parse Tcl-TK
|
||||||
|
# they us "p1" "p2" for post releases
|
||||||
|
self.failUnlessEqual(suggest('1.4p1'), '1.4.post1')
|
||||||
|
|
||||||
|
# from darcsver
|
||||||
|
self.failUnlessEqual(suggest('1.8.1-r4956'), '1.8.1.post4956')
|
||||||
|
|
||||||
|
# zetuptoolz
|
||||||
|
self.failUnlessEqual(suggest('0.6c16dev3'), '0.6c16.dev3')
|
Reference in New Issue
Block a user