Commit c09709c8 authored by Xavier Thompson's avatar Xavier Thompson

[feat] Add buildout:extra-paths option

This option determines what paths zc.buildout will scan for already
installed distributions. It can be set to an empty value to enable
isolation. The special values 'sys.path', 'legacy' and 'zc.buildout'
expand to particular paths. The default is 'zc.buildout'.

The 'sys.path' mode simply expands to the currrent sys.path.

The 'legacy' mode reproduces the previous behavior of specifically
scanning the paths of the current zc.buildout distribution and its
dependencies using a weird ordering logic that bears no relation to the
order they appear in sys.path - which can produce unexpected results as
other distributions for zc.buildout, pip, wheel and setuptools may take
precedence over the ones currently running.

The 'zc.buildout' mode is similar to 'legacy' as it uses only the paths
of the currently running zc.buildout and dependencies, but unlike
'legacy' mode it respects the order in which these appear in sys.path,
avoiding unexpected results.

This mode is set to the default because it is closest to 'legacy' mode
and because it has a nice property: running in succession

  `buildout buildout:extra-paths= bootstrap` (1)
  `bin/buildout` (2)

will result in (1) installing zc.buildout and its dependencies in
./eggs in isolation from the environment, and (2) using only the
paths of these in ./eggs, i.e. continuing to operate in isolation,
even without setting extra-paths explictly.
parent 2f6e6f29
...@@ -358,6 +358,19 @@ extends-cache ...@@ -358,6 +358,19 @@ extends-cache
substitutions, and the result is a relative path, then it will be substitutions, and the result is a relative path, then it will be
interpreted relative to the buildout directory.) interpreted relative to the buildout directory.)
.. _extra-paths-buildout-option
extra-paths, default: 'zc.buildout'
Extra paths to scan for already installed distributions.
Setting this to an empty value enables isolation of buildout.
Setting this to 'legacy' enables the legacy behavior of
scanning the paths of the distributions of zc.buildout itself
and its dependencies, which may contain sites-packages or not.
Setting this to 'zc.buildout' also scans the paths of the
current zc.buildout and dependencies, but respects the order
they appear in sys.path, avoiding unexpected results.
.. _find-links-option: .. _find-links-option:
find-links, default: '' find-links, default: ''
......
...@@ -292,6 +292,7 @@ _buildout_default_options = _annotate_section({ ...@@ -292,6 +292,7 @@ _buildout_default_options = _annotate_section({
'develop-eggs-directory': 'develop-eggs', 'develop-eggs-directory': 'develop-eggs',
'eggs-directory': 'eggs', 'eggs-directory': 'eggs',
'executable': sys.executable, 'executable': sys.executable,
'extra-paths': 'zc.buildout',
'find-links': '', 'find-links': '',
'install-from-cache': 'false', 'install-from-cache': 'false',
'installed': '.installed.cfg', 'installed': '.installed.cfg',
...@@ -533,6 +534,42 @@ class Buildout(DictMixin): ...@@ -533,6 +534,42 @@ class Buildout(DictMixin):
options['installed'] = os.path.join(options['directory'], options['installed'] = os.path.join(options['directory'],
options['installed']) options['installed'])
# Extra paths to scan for already installed distributions.
extra_paths = options['extra-paths']
if extra_paths == 'sys.path':
# special case: sys.path
extra_paths = sys.path
options['extra-paths'] = ' '.join(extra_paths)
elif extra_paths == 'legacy':
# special case: legacy behavior
# this case is why this is done before setting easy_install
# versions and other options, to get the legacy behavior.
# XXX: These 'sorted' calls correspond to the original behavior,
# but they are quite problematic, as other distributions for
# zc.buildout, pip, wheel and setuptools may take precedence
# over the ones currently running.
old_extra_paths = zc.buildout.easy_install.extra_paths(
sorted({d.location for d in pkg_resources.working_set}))
try:
buildout_and_setuptools_dists = list(
zc.buildout.easy_install.install(['zc.buildout'], None,
check_picked=False))
finally:
zc.buildout.easy_install.extra_paths(old_extra_paths)
extra_paths = sorted(
{d.location for d in buildout_and_setuptools_dists})
options['extra-paths'] = ' '.join(extra_paths)
elif extra_paths == 'zc.buildout':
# special case: only zc.buildout and its dependencies
# but in the order they appear in sys.path, unlike legacy
buildout_dists = pkg_resources.require('zc.buildout')
buildout_paths = {d.location for d in buildout_dists}
extra_paths = [p for p in sys.path if p in buildout_paths]
options['extra-paths'] = ' '.join(extra_paths)
else:
extra_paths = extra_paths.split()
zc.buildout.easy_install.extra_paths(extra_paths)
self._setup_logging() self._setup_logging()
self._setup_socket_timeout() self._setup_socket_timeout()
......
...@@ -91,13 +91,15 @@ if has_distribute and not has_setuptools: ...@@ -91,13 +91,15 @@ if has_distribute and not has_setuptools:
sys.exit("zc.buildout 3 needs setuptools, not distribute." sys.exit("zc.buildout 3 needs setuptools, not distribute."
"Did you properly install with pip in a virtualenv ?") "Did you properly install with pip in a virtualenv ?")
# Include buildout and setuptools eggs in paths. We get this # XXX Take care to respect the sys.path order, as otherwise other
# initially from the entire working set. Later, we'll use the install # distributions for pip, wheel and setuptools may take precedence
# function to narrow to just the buildout and setuptools paths. # over the ones currently running.
buildout_and_setuptools_path = sorted({d.location for d in pkg_resources.working_set}) pip_path = setuptools_path = [
setuptools_path = buildout_and_setuptools_path dist.location
pip_path = buildout_and_setuptools_path for dist in pkg_resources.working_set
logger.debug('before restricting versions: pip_path %r', pip_path) if dist.project_name in ('pip', 'wheel', 'setuptools')
]
pip_pythonpath = setuptools_pythonpath = os.pathsep.join(pip_path)
FILE_SCHEME = re.compile('file://', re.I).match FILE_SCHEME = re.compile('file://', re.I).match
DUNDER_FILE_PATTERN = re.compile(r"__file__ = '(?P<filename>.+)'$") DUNDER_FILE_PATTERN = re.compile(r"__file__ = '(?P<filename>.+)'$")
...@@ -241,6 +243,7 @@ class Installer(object): ...@@ -241,6 +243,7 @@ class Installer(object):
_allow_picked_versions = True _allow_picked_versions = True
_store_required_by = False _store_required_by = False
_allow_unknown_extras = False _allow_unknown_extras = False
_extra_paths = []
def __init__(self, def __init__(self,
dest=None, dest=None,
...@@ -275,7 +278,7 @@ class Installer(object): ...@@ -275,7 +278,7 @@ class Installer(object):
links.insert(0, self._download_cache) links.insert(0, self._download_cache)
self._index_url = index self._index_url = index
path = (path and path[:] or []) + buildout_and_setuptools_path path = (path and path[:] or []) + self._extra_paths
self._path = path self._path = path
if self._dest is None: if self._dest is None:
newest = False newest = False
...@@ -950,6 +953,12 @@ def get_picked_versions(): ...@@ -950,6 +953,12 @@ def get_picked_versions():
required_by = Installer._required_by required_by = Installer._required_by
return (picked_versions, required_by) return (picked_versions, required_by)
def extra_paths(setting=None):
old = Installer._extra_paths
if setting is not None:
Installer._extra_paths = setting
return old
def install(specs, dest, def install(specs, dest,
links=(), index=None, links=(), index=None,
...@@ -974,19 +983,6 @@ def install(specs, dest, ...@@ -974,19 +983,6 @@ def install(specs, dest,
allow_unknown_extras=allow_unknown_extras) allow_unknown_extras=allow_unknown_extras)
return installer.install(specs, working_set) return installer.install(specs, working_set)
buildout_and_setuptools_dists = list(install(['zc.buildout'], None,
check_picked=False))
buildout_and_setuptools_path = sorted({d.location
for d in buildout_and_setuptools_dists})
pip_dists = [d for d in buildout_and_setuptools_dists if d.project_name != 'zc.buildout']
pip_path = sorted({d.location for d in pip_dists})
logger.debug('after restricting versions: pip_path %r', pip_path)
pip_pythonpath = os.pathsep.join(pip_path)
setuptools_path = pip_path
setuptools_pythonpath = pip_pythonpath
def build(spec, dest, build_ext, def build(spec, dest, build_ext,
links=(), index=None, links=(), index=None,
......
...@@ -2917,6 +2917,7 @@ database is shown:: ...@@ -2917,6 +2917,7 @@ database is shown::
directory = /sample-buildout directory = /sample-buildout
eggs-directory = /sample-buildout/eggs eggs-directory = /sample-buildout/eggs
executable = python executable = python
extra-paths = ...
find-links = find-links =
install-from-cache = false install-from-cache = false
installed = /sample-buildout/.installed.cfg installed = /sample-buildout/.installed.cfg
......
...@@ -2677,7 +2677,8 @@ honoring our version specification. ...@@ -2677,7 +2677,8 @@ honoring our version specification.
... eggs = foo ... eggs = foo
... ''' % ('\n'.join( ... ''' % ('\n'.join(
... '%s = %s' % (d.key, d.version) ... '%s = %s' % (d.key, d.version)
... for d in zc.buildout.easy_install.buildout_and_setuptools_dists))) ... for d in pkg_resources.working_set.resolve(
... pkg_resources.parse_requirements('zc.buildout')))))
>>> print_(system(buildout), end='') >>> print_(system(buildout), end='')
Installing foo. Installing foo.
......
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