Commit f3d6e599 authored by Jim Fulton's avatar Jim Fulton

cherry-picked 8059b73a

Versions in versions sections can now be simple constraints, like
<2.0dev in addition to being simple versions.
parent 47645243
Change History
**************
1.6.4 (unreleased)
1.7.0 (unreleased)
==================
- Versions in versions sections can now be simple constraints, like
<2.0dev in addition to being simple versions.
- remove `data_files` from `setup.py`, which was installing README.txt
in current directory during installation
[Domen Kožar]
......@@ -12,7 +15,8 @@ Change History
and try cli.exe if cli-64.exe is not found,
fixing 9c6be7ac6d218f09e33725e07dccc4af74d8cf97
- Windows fix: `buildout init` was broken, re.sub does not like a single backslash
- Windows fix: `buildout init` was broken, re.sub does not like a
single backslash
- fixed all builds on travis-ci
[Domen Kožar]
......
......@@ -168,10 +168,6 @@ def _get_version_info(executable):
return eval(stdout.strip())
class IncompatibleVersionError(zc.buildout.UserError):
"""A specified version is incompatible with a given requirement.
"""
_versions = {sys.executable: '%d.%d' % sys.version_info[:2]}
def _get_version(executable):
try:
......@@ -196,7 +192,6 @@ def _get_version(executable):
FILE_SCHEME = re.compile('file://', re.I).match
class AllowHostsPackageIndex(setuptools.package_index.PackageIndex):
"""Will allow urls that are local to the system.
......@@ -861,17 +856,9 @@ class Installer:
def _constrain(self, requirement):
if is_distribute and requirement.key == 'setuptools':
requirement = pkg_resources.Requirement.parse('distribute')
version = self._versions.get(requirement.project_name)
if version:
if version not in requirement:
logger.error("The version, %s, is not consistent with the "
"requirement, %r.", version, str(requirement))
raise IncompatibleVersionError("Bad version", version)
requirement = pkg_resources.Requirement.parse(
"%s[%s] ==%s" % (requirement.project_name,
','.join(requirement.extras),
version))
constraint = self._versions.get(requirement.project_name)
if constraint:
requirement = _constrained_requirement(constraint, requirement)
return requirement
......@@ -1914,3 +1901,109 @@ def redo_pyc(egg):
py_compile.compile(filepath)
except py_compile.PyCompileError:
logger.warning("Couldn't compile %s", filepath)
def _constrained_requirement(constraint, requirement):
return pkg_resources.Requirement.parse(
"%s[%s]%s" % (
requirement.project_name,
','.join(requirement.extras),
_constrained_requirement_constraint(constraint, requirement)
)
)
class IncompatibleConstraintError(zc.buildout.UserError):
"""A specified version is incompatible with a given requirement.
"""
def bad_constraint(constraint, requirement):
logger.error("The constraint, %s, is not consistent with the "
"requirement, %r.", constraint, str(requirement))
raise IncompatibleConstraintError("Bad constraint", constraint, requirement)
_parse_constraint = re.compile(r'([<>]=?)(\S+)').match
_comparef = {
'>' : lambda x, y: x > y,
'>=': lambda x, y: x >= y,
'<' : lambda x, y: x < y,
'<=': lambda x, y: x <= y,
}
_opop = {'<': '>', '>': '<'}
_opeqop = {'<': '>=', '>': '<='}
def _constrained_requirement_constraint(constraint, requirement):
# Simple cases:
# No specs tp merge with:
if not requirement.specs:
if not constraint[0] in '<=>':
constraint = '==' + constraint
return constraint
# Simple single-version constraint:
if constraint[0] not in '<>':
if constraint.startswith('='):
assert constraint.startswith('==')
constraint = constraint[2:]
if constraint in requirement:
return '=='+constraint
bad_constraint(constraint, requirement)
# OK, we have a complex constraint (<. <=, >=, or >) and specs.
# In many cases, the spec needs to filter constraints.
# In other cases, the constraints need to limit the constraint.
specs = requirement.specs
cop, cv = _parse_constraint(constraint).group(1, 2)
pcv = pkg_resources.parse_version(cv)
# Special case, all of the specs are == specs:
if not [op for (op, v) in specs if op != '==']:
# There aren't any non-== specs.
# See if any of the specs satisfy the constraint:
specs = [op+v for (op, v) in specs
if _comparef[cop](pkg_resources.parse_version(v), pcv)]
if specs:
return ','.join(specs)
bad_constraint(constraint, requirement)
cop0 = cop[0]
# Normalize specs by splitting >= and <= specs. We meed tp do this
# becaise these have really weird semantics. Also cache parsed
# versions, which we'll need for comparisons:
specs = []
for op, v in requirement.specs:
pv = pkg_resources.parse_version(v)
if op == _opeqop[cop0]:
specs.append((op[0], v, pv))
specs.append(('==', v, pv))
else:
specs.append((op, v, pv))
# Error if there are opposite specs that conflict with the constraint
# and there are no equal specs that satisfy the constraint:
if [v for (op, v, pv) in specs
if op == _opop[cop0] and _comparef[_opop[cop0]](pv, pcv)
]:
eqspecs = [op+v for (op, v, pv) in specs
if _comparef[cop](pv, pcv)]
if eqspecs:
# OK, we do, use these:
return ','.join(eqspecs)
bad_constraint(constraint, requirement)
# We have a combination of range constraints and eq specs that
# satisfy the requirement.
# Return the constraint + the filtered specs
return ','.join(
op+v
for (op, v) in (
[(cop, cv)] +
[(op, v) for (op, v, pv) in specs if _comparef[cop](pv, pcv)]
)
)
......@@ -319,13 +319,13 @@ we'll get an error:
... versions = dict(demo='0.2', demoneeded='1.0'))
Traceback (most recent call last):
...
IncompatibleVersionError: Bad version 0.2
IncompatibleConstraintError: Bad constraint 0.2 demo>0.2
>>> print handler
zc.buildout.easy_install DEBUG
Installing 'demo >0.2'.
zc.buildout.easy_install ERROR
The version, 0.2, is not consistent with the requirement, 'demo>0.2'.
The constraint, 0.2, is not consistent with the requirement, 'demo>0.2'.
>>> handler.clear()
......
......@@ -3630,6 +3630,53 @@ def increment_on_command_line():
recipe='zc.buildout:debug'
"""
def test_constrained_requirement():
"""
zc.buildout.easy_install._constrained_requirement(constraint, requitement)
Transforms an environment by applying a constraint.
Here's a table of examples:
>>> from zc.buildout.easy_install import IncompatibleConstraintError
>>> examples = [
... # original, constraint, transformed
... ('x', '1', 'x==1'),
... ('x>1', '2', 'x==2'),
... ('x>3', '2', IncompatibleConstraintError),
... ('x>1', '>2', 'x>2'),
... ('x>1', '>=2', 'x>=2'),
... ('x<1', '>2', IncompatibleConstraintError),
... ('x<=1', '>=1', 'x>=1,<1,==1'),
... ('x<3', '>1', 'x>1,<3'),
... ('x==2', '>1', 'x==2'),
... ('x==2', '>=2', 'x==2'),
... ('x[y]', '1', 'x[y]==1'),
... ('x[y]>1', '2', 'x[y]==2'),
... ('x<3', '2', 'x==2'),
... ('x<1', '2', IncompatibleConstraintError),
... ('x<3', '<2', 'x<2'),
... ('x<3', '<=2', 'x<=2'),
... ('x>3', '<2', IncompatibleConstraintError),
... ('x>=1', '<=1', 'x<=1,>1,==1'),
... ('x<3', '>1', 'x>1,<3'),
... ('x==2', '<3', 'x==2'),
... ('x==2', '<=2', 'x==2'),
... ('x[y]<3', '2', 'x[y]==2'),
... ]
>>> from zc.buildout.easy_install import _constrained_requirement
>>> for o, c, e in examples:
... try:
... o = pkg_resources.Requirement.parse(o)
... if isinstance(e, str):
... e = pkg_resources.Requirement.parse(e)
... g = _constrained_requirement(c, o)
... except IncompatibleConstraintError:
... g = IncompatibleConstraintError
... if str(g) != str(e):
... print 'failed', o, c, g, '!=', e
"""
######################################################################
def make_py_with_system_install(make_py, sample_eggs):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment