Commit f618b8ff authored by Reinout van Rees's avatar Reinout van Rees

Merged master

parents 4e5a3d53 813a4590
Change History Change History
************** **************
2.2.6 (unreleased) Unreleased
================== ==========
- Undo breakage on Windows machines where ``sys.prefix`` can also be a
``site-packages`` directory: don't remove it from ``sys.path``. See
https://github.com/buildout/buildout/issues/217
- Remove assumption that ``pkg_resources`` is a module (untrue since
release of `setuptools 8.3``). See
https://github.com/buildout/buildout/issues/227
- Fix for #212. For certain kinds of conflict errors you'd get an UnpackError - Fix for #212. For certain kinds of conflict errors you'd get an UnpackError
when rendering the error message. Instead of a nicely formatted version when rendering the error message. Instead of a nicely formatted version
conflict message. conflict message.
[reinout] [reinout]
2.3.1 (2014-12-16)
==================
- Fixed: Buildout merged single-version requirements with
version-range requirements in a way that caused it to think there
wasn't a single-version requirement. IOW, buildout throught that
versions were being picked when they weren't.
- Suppress spurios (and possibly non-spurious) version-parsing warnings.
2.3.0 (2014-12-14)
==================
- Buildout is now compatible with (and requires) setuptools 8.
2.2.5 (2014-11-04) 2.2.5 (2014-11-04)
================== ==================
......
...@@ -78,12 +78,12 @@ locally in your project. ...@@ -78,12 +78,12 @@ locally in your project.
The bootstrap script for buildout version 2 is at: The bootstrap script for buildout version 2 is at:
http://downloads.buildout.org/2/bootstrap.py https://bootstrap.pypa.io/bootstrap-buildout.py
So, for example, to install buildout 2 in a project, you might:: So, for example, to install buildout 2 in a project, you might::
wget http://downloads.buildout.org/2/bootstrap.py wget https://bootstrap.pypa.io/bootstrap-buildout.py
python bootstrap.py python bootstrap-buildout.py
Then to build your project, you can just run:: Then to build your project, you can just run::
...@@ -91,8 +91,6 @@ Then to build your project, you can just run:: ...@@ -91,8 +91,6 @@ Then to build your project, you can just run::
from the project directory. from the project directory.
The bootstrap script is often checked into version control.
buildout 2 is somewhat backward-incompatible with version 1. Most buildout 2 is somewhat backward-incompatible with version 1. Most
projects will probably work fine with either. If you need to keep projects will probably work fine with either. If you need to keep
using version 1, however, specify a version requirement when you use using version 1, however, specify a version requirement when you use
......
...@@ -35,7 +35,7 @@ Bootstraps a buildout-based project. ...@@ -35,7 +35,7 @@ Bootstraps a buildout-based project.
Simply run this script in a directory containing a buildout.cfg, using the Simply run this script in a directory containing a buildout.cfg, using the
Python that you want bin/buildout to use. Python that you want bin/buildout to use.
Note that by using --find-links to point to local resources, you can keep Note that by using --find-links to point to local resources, you can keep
this script from going over the network. this script from going over the network.
''' '''
...@@ -59,6 +59,8 @@ parser.add_option("-f", "--find-links", ...@@ -59,6 +59,8 @@ parser.add_option("-f", "--find-links",
parser.add_option("--allow-site-packages", parser.add_option("--allow-site-packages",
action="store_true", default=False, action="store_true", default=False,
help=("Let bootstrap.py use existing site packages")) help=("Let bootstrap.py use existing site packages"))
parser.add_option("--setuptools-version",
help="use a specific setuptools version")
options, args = parser.parse_args() options, args = parser.parse_args()
...@@ -79,16 +81,24 @@ exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) ...@@ -79,16 +81,24 @@ exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez)
if not options.allow_site_packages: if not options.allow_site_packages:
# ez_setup imports site, which adds site packages # ez_setup imports site, which adds site packages
# this will remove them from the path to ensure that incompatible versions # this will remove them from the path to ensure that incompatible versions
# of setuptools are not in the path # of setuptools are not in the path
import site import site
# inside a virtualenv, there is no 'getsitepackages'. # inside a virtualenv, there is no 'getsitepackages'.
# We can't remove these reliably # We can't remove these reliably
if hasattr(site, 'getsitepackages'): if hasattr(site, 'getsitepackages'):
for sitepackage_path in site.getsitepackages(): for sitepackage_path in site.getsitepackages():
sys.path[:] = [x for x in sys.path if sitepackage_path not in x] # Strip all site-packages directories from sys.path that
# are not sys.prefix; this is because on Windows
# sys.prefix is a site-package directory.
if sitepackage_path != sys.prefix:
sys.path[:] = [x for x in sys.path if sitepackage_path not in x]
setup_args = dict(to_dir=tmpeggs, download_delay=0) setup_args = dict(to_dir=tmpeggs, download_delay=0)
if options.setuptools_version is not None:
setup_args['version'] = options.setuptools_version
ez['use_setuptools'](**setup_args) ez['use_setuptools'](**setup_args)
import setuptools import setuptools
import pkg_resources import pkg_resources
...@@ -128,10 +138,15 @@ if version is None and not options.accept_buildout_test_releases: ...@@ -128,10 +138,15 @@ if version is None and not options.accept_buildout_test_releases:
_final_parts = '*final-', '*final' _final_parts = '*final-', '*final'
def _final_version(parsed_version): def _final_version(parsed_version):
for part in parsed_version: try:
if (part[:1] == '*') and (part not in _final_parts): return not parsed_version.is_prerelease
return False except AttributeError:
return True # Older setuptools
for part in parsed_version:
if (part[:1] == '*') and (part not in _final_parts):
return False
return True
index = setuptools.package_index.PackageIndex( index = setuptools.package_index.PackageIndex(
search_path=[setuptools_path]) search_path=[setuptools_path])
if find_links: if find_links:
......
...@@ -47,18 +47,18 @@ except ImportError: ...@@ -47,18 +47,18 @@ except ImportError:
from urllib2 import urlopen from urllib2 import urlopen
# XXX use a more permanent ez_setup.py URL when available. # XXX use a more permanent ez_setup.py URL when available.
exec(urlopen('https://bitbucket.org/pypa/setuptools/raw/0.7.2/ez_setup.py' exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez)
).read(), ez)
ez['use_setuptools'](to_dir='eggs', download_delay=0) ez['use_setuptools'](to_dir='eggs', download_delay=0)
import pkg_resources import pkg_resources, setuptools
setuptools_path = os.path.dirname(os.path.dirname(setuptools.__file__))
###################################################################### ######################################################################
# Install buildout # Install buildout
if subprocess.call( if subprocess.call(
[sys.executable] + [sys.executable] +
['setup.py', '-q', 'develop', '-m', '-x', '-d', 'develop-eggs'], ['setup.py', '-q', 'develop', '-m', '-x', '-d', 'develop-eggs'],
env=dict(os.environ, PYTHONPATH=os.path.dirname(pkg_resources.__file__))): env=dict(os.environ, PYTHONPATH=setuptools_path)):
raise RuntimeError("buildout build failed.") raise RuntimeError("buildout build failed.")
pkg_resources.working_set.add_entry('src') pkg_resources.working_set.add_entry('src')
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
# #
############################################################################## ##############################################################################
name = "zc.buildout" name = "zc.buildout"
version = "2.2.5" version = "2.3.1"
import os import os
from setuptools import setup from setuptools import setup
...@@ -88,7 +88,7 @@ setup( ...@@ -88,7 +88,7 @@ setup(
package_dir = {'': 'src'}, package_dir = {'': 'src'},
namespace_packages = ['zc'], namespace_packages = ['zc'],
install_requires = [ install_requires = [
'setuptools>=3.3', 'setuptools>=8.0',
], ],
include_package_data = True, include_package_data = True,
entry_points = entry_points, entry_points = entry_points,
......
...@@ -89,3 +89,41 @@ Let's make sure the generated `buildout` script uses it:: ...@@ -89,3 +89,41 @@ Let's make sure the generated `buildout` script uses it::
'/sample/eggs/setuptools-...egg', '/sample/eggs/setuptools-...egg',
'/sample/eggs/zc.buildout-2.0.0...egg', '/sample/eggs/zc.buildout-2.0.0...egg',
]... ]...
Now trying the `--setuptools-version` option, that let you define a version for
`setuptools`.
Now let's try with `8.0`, which happens to exist::
>>> print_('X'); print_(system(
... zc.buildout.easy_install._safe_arg(sys.executable)+' '+
... 'bootstrap.py --setuptools-version 8.0')); print_('X')
... # doctest: +ELLIPSIS
X...Generated script '/sample/bin/buildout'...X
Let's make sure the generated `buildout` script uses it::
>>> print_(open(buildout_script).read()) # doctest: +ELLIPSIS
#...
sys.path[0:0] = [
'/sample/eggs/setuptools-8.0...egg',
'/sample/eggs/zc.buildout-...egg',
]...
Now let's try specifying both `zc.buildout` and `setuptools` to versions
which happens to exist::
>>> print_('X'); print_(system(
... zc.buildout.easy_install._safe_arg(sys.executable)+' '+
... 'bootstrap.py --setuptools-version 8.0 --version 2.0.0')); print_('X')
... # doctest: +ELLIPSIS
X...Generated script '/sample/bin/buildout'...X
Let's make sure the generated `buildout` script uses it::
>>> print_(open(buildout_script).read()) # doctest: +ELLIPSIS
#...
sys.path[0:0] = [
'/sample/eggs/setuptools-8.0...egg',
'/sample/eggs/zc.buildout-2.0.0...egg',
]...
...@@ -35,6 +35,10 @@ import subprocess ...@@ -35,6 +35,10 @@ import subprocess
import sys import sys
import tempfile import tempfile
import zc.buildout import zc.buildout
import warnings
warnings.filterwarnings(
'ignore', '.+is being parsed as a legacy, non PEP 440, version')
_oprp = getattr(os.path, 'realpath', lambda path: path) _oprp = getattr(os.path, 'realpath', lambda path: path)
def realpath(path): def realpath(path):
...@@ -421,10 +425,10 @@ class Installer: ...@@ -421,10 +425,10 @@ class Installer:
# Now find the best one: # Now find the best one:
best = [] best = []
bestv = () bestv = None
for dist in dists: for dist in dists:
distv = dist.parsed_version distv = dist.parsed_version
if distv > bestv: if bestv is None or distv > bestv:
best = [dist] best = [dist]
bestv = distv bestv = distv
elif distv == bestv: elif distv == bestv:
...@@ -1399,12 +1403,8 @@ def _fix_file_links(links): ...@@ -1399,12 +1403,8 @@ def _fix_file_links(links):
link += '/' link += '/'
yield link yield link
_final_parts = '*final-', '*final'
def _final_version(parsed_version): def _final_version(parsed_version):
for part in parsed_version: return not parsed_version.is_prerelease
if (part[:1] == '*') and (part not in _final_parts):
return False
return True
def redo_pyc(egg): def redo_pyc(egg):
if not os.path.isdir(egg): if not os.path.isdir(egg):
...@@ -1441,13 +1441,28 @@ def redo_pyc(egg): ...@@ -1441,13 +1441,28 @@ def redo_pyc(egg):
call_subprocess(args) call_subprocess(args)
def _constrained_requirement(constraint, requirement): def _constrained_requirement(constraint, requirement):
return pkg_resources.Requirement.parse( if constraint[0] not in '<>':
"%s[%s]%s" % ( if constraint.startswith('='):
requirement.project_name, assert constraint.startswith('==')
','.join(requirement.extras), constraint = constraint[2:]
_constrained_requirement_constraint(constraint, requirement) if constraint not in requirement:
bad_constraint(constraint, requirement)
# Sigh, copied from Requirement.__str__
extras = ','.join(requirement.extras)
if extras:
extras = '[%s]' % extras
return pkg_resources.Requirement.parse(
"%s%s==%s" % (requirement.project_name, extras, constraint))
if requirement.specs:
return pkg_resources.Requirement.parse(
str(requirement) + ',' + constraint
)
else:
return pkg_resources.Requirement.parse(
str(requirement) + ' ' + constraint
) )
)
class IncompatibleConstraintError(zc.buildout.UserError): class IncompatibleConstraintError(zc.buildout.UserError):
"""A specified version is incompatible with a given requirement. """A specified version is incompatible with a given requirement.
...@@ -1459,91 +1474,3 @@ def bad_constraint(constraint, requirement): ...@@ -1459,91 +1474,3 @@ def bad_constraint(constraint, requirement):
logger.error("The constraint, %s, is not consistent with the " logger.error("The constraint, %s, is not consistent with the "
"requirement, %r.", constraint, str(requirement)) "requirement, %r.", constraint, str(requirement))
raise IncompatibleConstraintError("Bad constraint", constraint, requirement) raise IncompatibleConstraintError("Bad constraint", constraint, requirement)
_parse_constraint = re.compile(r'([<>]=?)\s*(\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 to 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 need to do this
# because 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)]
)
)
...@@ -163,6 +163,34 @@ We won't get output for the spam distribution, which we didn't pick, ...@@ -163,6 +163,34 @@ We won't get output for the spam distribution, which we didn't pick,
but we will get output for setuptools, which we didn't specify but we will get output for setuptools, which we didn't specify
versions for. versions for.
.. Edge case: version applied to range requirement:
>>> write('buildout.cfg',
... '''
... [buildout]
... parts = foo
... find-links = %s
...
... [versions]
... spam = 1
... eggs = 2.2
...
... [foo]
... recipe = spam >0
... ''' % join('recipe', 'dist'))
>>> print_(system(buildout+' -v'), end='')
Installing 'zc.buildout', 'setuptools'.
We have a develop egg: zc.buildout 1.0.0.
We have the best distribution that satisfies 'setuptools'.
Picked: setuptools = 0.6
Installing 'spam >0'.
We have the distribution that satisfies 'spam==1'.
Uninstalling foo.
Installing foo.
recipe v1
You can request buildout to generate an error if it picks any You can request buildout to generate an error if it picks any
versions: versions:
...@@ -204,7 +232,8 @@ We can name a version something else, if we wish, using the versions option: ...@@ -204,7 +232,8 @@ We can name a version something else, if we wish, using the versions option:
... recipe = spam ... recipe = spam
... ''' % join('recipe', 'dist')) ... ''' % join('recipe', 'dist'))
>>> print_(system(buildout), end='') # doctest: +ELLIPSIS >>> print_(system(buildout), end='') # doctest: +ELLIPSIS
Updating foo. Uninstalling foo.
Installing foo.
recipe v1 recipe v1
We can also disable checking versions: We can also disable checking versions:
......
...@@ -2735,28 +2735,7 @@ def test_constrained_requirement(): ...@@ -2735,28 +2735,7 @@ def test_constrained_requirement():
... ('x', '1', 'x==1'), ... ('x', '1', 'x==1'),
... ('x>1', '2', 'x==2'), ... ('x>1', '2', 'x==2'),
... ('x>3', '2', IncompatibleConstraintError), ... ('x>3', '2', IncompatibleConstraintError),
... ('x>1', '>2', 'x>2'), ... ('x>1', '>2', 'x>1,>2'),
... ('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', '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 >>> from zc.buildout.easy_install import _constrained_requirement
>>> for o, c, e in examples: >>> for o, c, e in examples:
...@@ -2853,13 +2832,14 @@ def want_new_zcrecipeegg(): ...@@ -2853,13 +2832,14 @@ def want_new_zcrecipeegg():
... eggs = demo ... eggs = demo
... ''') ... ''')
>>> print_(system(join('bin', 'buildout')), end='') # doctest: +ELLIPSIS >>> print_(system(join('bin', 'buildout')), end='') # doctest: +ELLIPSIS
The constraint, >=2.0.0a3,... Getting distribution for 'zc.recipe.egg<2dev,>=2.0.0a3'.
While: While:
Installing. Installing.
Getting section egg. Getting section egg.
Initializing section egg. Initializing section egg.
Installing recipe zc.recipe.egg <2dev. Installing recipe zc.recipe.egg <2dev.
Error: Bad constraint >=2.0.0a3 zc.recipe.egg<2dev Getting distribution for 'zc.recipe.egg<2dev,>=2.0.0a3'.
Error: Couldn't find a distribution for 'zc.recipe.egg<2dev,>=2.0.0a3'.
""" """
def macro_inheritance_bug(): def macro_inheritance_bug():
......
...@@ -13,13 +13,11 @@ ...@@ -13,13 +13,11 @@
############################################################################## ##############################################################################
"""Install packages as eggs """Install packages as eggs
""" """
import logging import logging
import os import os
import re
import sys import sys
import zc.buildout.easy_install import zc.buildout.easy_install
import zipfile
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
......
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