Commit 768502ce authored by Godefroid Chapelle's avatar Godefroid Chapelle

Problem: `setuptools.easy_install` is deprecated

Solution: Use `pip install`

The meat of the change is in src/zc/buildout/easy_install.py

Some tests had to be refactored a bit to make them more robust.

`tox` is removed to avoid issues due to sharing the same directory
between different virtual environments; `Makefile` enriched to make
per python virtual envs depend on source files.

No need to be afraid of default Python install as we depend on `pip`.
`bootstrap.py` is gone as not needed anymore.
parent 0185dc6a
name: Buildout on Centos
name: Test in Centos container
on: [push]
on: [push, pull_request]
jobs:
build:
......
scripts.cfg
\ No newline at end of file
......@@ -9,7 +9,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: [2.7, 3.5, 3.6, 3.7]
python-version: [3.5, 3.6, 3.7, 3.8]
package: [zest.releaser, pyspf]
steps:
......
......@@ -11,7 +11,7 @@ jobs:
strategy:
max-parallel: 4
matrix:
python-version: [2.7, 3.5, 3.6, 3.7]
python-version: [3.5, 3.6, 3.7, 3.8]
package: [zest.releaser, pyspf]
steps:
......
......@@ -6,6 +6,7 @@ develop-eggs/
eggs/
parts/
python*/
venvs/
*.pyc
*.egg
*.egg-info
......
Change History
**************
2.13.4 (unreleased)
===================
3.0.0 (unreleased)
==================
- Use ``pip install`` instead of deprecated ``setuptools.easy_install``.
- Patch ``pkg_resources.Distribution`` to make install of unpinned versions quicker.
Most obvious with ``setuptools``.
......
......@@ -44,8 +44,8 @@ PYTHON_DOWNLOAD = $(PYTHON_BUILD_DIR)/$(PYTHON_ARCHIVE).tgz
BUILD_DIRS = $(PYTHON_PATH) bin build develop-eggs eggs parts
all: test
.PHONY: all download_python python build test docker clean
all: all_test
.PHONY: all download_python python build test docker clean all_pythons all_test
# setup python from source
$(PYTHON_DOWNLOAD):
......@@ -74,12 +74,46 @@ python: $(PYTHON_PATH)/bin/$(PYTHON_EXE)
build: python
$(PYTHON_PATH)/bin/$(PYTHON_EXE) dev.py
# copy to virtualenvs
ROOT_FILES := $(HERE)/setup.py $(HERE)/setup.cfg $(HERE)/dev.py $(HERE)/README.rst $(HERE)/CHANGES.rst $(HERE)/buildout.cfg
SRC_FILES := $(shell find $(HERE)/src ! -path '*egg-info*' \( -name '*.py' -o -name '*.txt' -o -name '*.test' \) )
RCP_FILES := $(shell find $(HERE)/zc.recipe.egg_ ! -path '*egg-info*' \( -name '*.py' -o -name '*.txt' -o -name '*.rst' -o -name '*.cfg' -o -name '*.in' \) )
DOC_FILES := $(shell find $(HERE)/doc -name '*.rst' -o -name '*.txt')
ALL_COPY := $(subst $(HERE),$(VENV),$(SRC_FILES) $(DOC_FILES) $(RCP_FILES) $(ROOT_FILES))
# Generate rules to map sources into targets
$(foreach s,$(ALL_COPY),$(eval $s: $(VENV)/bin/$(PYTHON_EXE) $(subst $(VENV),$(HERE),$s)))
$(ALL_COPY):
@mkdir -p $(dir $@)
@cp $(subst $(VENV),$(HERE),$@) $@
$(VENV)/bin/$(PYTHON_EXE): $(PYTHON_PATH)/bin/$(PYTHON_EXE)
@command -v virtualenv >/dev/null 2>&1 || { echo "virtualenv required but not installed" >&2; exit 1; }
test -d "$(HERE)/venvs" || mkdir -p $(HERE)/venvs
virtualenv -p $(PYTHON_PATH)/bin/$(PYTHON_EXE) $(VENV)
$(VENV)/bin/test: $(VENV)/bin/$(PYTHON_EXE) $(ALL_COPY)
cd $(VENV) && bin/$(PYTHON_EXE) dev.py --no-clean
test: $(VENV)/bin/test
$(VENV)/bin/test -c -vvv $(testargs)
all_pythons:
$(MAKE) PYTHON_VER=3.5 python
$(MAKE) PYTHON_VER=3.6 python
$(MAKE) PYTHON_VER=3.7 python
$(MAKE) PYTHON_VER=3.8 python
all_test:
$(MAKE) PYTHON_VER=3.5 test
$(MAKE) PYTHON_VER=3.6 test
$(MAKE) PYTHON_VER=3.7 test
$(MAKE) PYTHON_VER=3.8 test
docker:
docker build -f .github/workflows/Dockerfile --tag centos_buildout:python${PYTHON_VER} --build-arg PYTHON_VER=${PYTHON_VER} .
docker run centos_buildout:python${PYTHON_VER} /buildout/bin/test -c -vvv -t abi
clean:
rm -rf $(BUILD_DIRS) $(PYTHON_BUILD_DIR)
test:
$(HERE)/bin/test -1 -vvv -c
rm -rf $(VENVS) $(PYTHON_BUILD_DIR) $(HERE)/pythons
ifeq ($(PYTHON_VER),2.7)
PYTHON_CONFIGURE_ARGS ?=
endif
ifeq ($(PYTHON_VER),3.5)
PYTHON_CONFIGURE_ARGS ?= --without-ensurepip
endif
ifeq ($(PYTHON_VER),3.6)
PYTHON_CONFIGURE_ARGS ?= --without-ensurepip
endif
ifeq ($(PYTHON_VER),3.7)
PYTHON_CONFIGURE_ARGS ?= --without-ensurepip
endif
ifeq ($(PYTHON_VER),3.8)
PYTHON_CONFIGURE_ARGS ?= --without-ensurepip
endif
PYTHON_CONFIGURE_ARGS ?=
......@@ -6,19 +6,19 @@ ifeq ($(PYTHON_VER),2.7)
endif
ifeq ($(PYTHON_VER),3.5)
BUILD_VARIABLES = LDFLAGS="-L$(OPENSSL)/lib" CPPFLAGS="-I$(OPENSSL)/include"
PYTHON_CONFIGURE_ARGS ?= --without-ensurepip
PYTHON_CONFIGURE_ARGS ?=
endif
ifeq ($(PYTHON_VER),3.6)
BUILD_VARIABLES = LDFLAGS="-L$(OPENSSL)/lib" CPPFLAGS="-I$(OPENSSL)/include"
PYTHON_CONFIGURE_ARGS ?= --without-ensurepip
PYTHON_CONFIGURE_ARGS ?=
endif
ifeq ($(PYTHON_VER),3.7)
PYTHON_CONFIGURE_ARGS ?= --without-ensurepip --with-openssl=$(OPENSSL)
PYTHON_CONFIGURE_ARGS ?= --with-openssl=$(OPENSSL)
endif
ifeq ($(PYTHON_VER),3.8)
PYTHON_CONFIGURE_ARGS ?= --without-ensurepip --with-openssl=$(OPENSSL)
PYTHON_CONFIGURE_ARGS ?= --with-openssl=$(OPENSSL)
endif
ifeq ($(PYTHON_VER),3.9)
PYTHON_CONFIGURE_ARGS ?= --without-ensurepip --with-openssl=$(OPENSSL)
PYTHON_CONFIGURE_ARGS ?= --with-openssl=$(OPENSSL)
endif
##############################################################################
#
# Copyright (c) 2006 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Bootstrap a buildout-based project
Simply run this script in a directory containing a buildout.cfg.
The script accepts buildout command-line options, so you can
use the -c option to specify an alternate configuration file.
"""
import os
import shutil
import sys
import tempfile
from optparse import OptionParser
__version__ = '2015-07-01'
# See zc.buildout's changelog if this version is up to date.
tmpeggs = tempfile.mkdtemp(prefix='bootstrap-')
usage = '''\
[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
Bootstraps a buildout-based project.
Simply run this script in a directory containing a buildout.cfg, using the
Python that you want bin/buildout to use.
Note that by using --find-links to point to local resources, you can keep
this script from going over the network.
'''
parser = OptionParser(usage=usage)
parser.add_option("--version",
action="store_true", default=False,
help=("Return bootstrap.py version."))
parser.add_option("-t", "--accept-buildout-test-releases",
dest='accept_buildout_test_releases',
action="store_true", default=False,
help=("Normally, if you do not specify a --buildout-version, "
"the bootstrap script and buildout gets the newest "
"*final* versions of zc.buildout and its recipes and "
"extensions for you. If you use this flag, "
"bootstrap and buildout will get the newest releases "
"even if they are alphas or betas."))
parser.add_option("-c", "--config-file",
help=("Specify the path to the buildout configuration "
"file to be used."))
parser.add_option("-f", "--find-links",
help=("Specify a URL to search for buildout releases"))
parser.add_option("--allow-site-packages",
action="store_true", default=False,
help=("Let bootstrap.py use existing site packages"))
parser.add_option("--buildout-version",
help="Use a specific zc.buildout version")
parser.add_option("--setuptools-version",
help="Use a specific setuptools version")
parser.add_option("--setuptools-to-dir",
help=("Allow for re-use of existing directory of "
"setuptools versions"))
options, args = parser.parse_args()
if options.version:
print("bootstrap.py version %s" % __version__)
sys.exit(0)
######################################################################
# load/install setuptools
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
ez = {}
if os.path.exists('ez_setup.py'):
exec(open('ez_setup.py').read(), ez)
else:
exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez)
if not options.allow_site_packages:
# ez_setup imports site, which adds site packages
# this will remove them from the path to ensure that incompatible versions
# of setuptools are not in the path
import site
# inside a virtualenv, there is no 'getsitepackages'.
# We can't remove these reliably
if hasattr(site, 'getsitepackages'):
for sitepackage_path in site.getsitepackages():
# 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)
if options.setuptools_version is not None:
setup_args['version'] = options.setuptools_version
if options.setuptools_to_dir is not None:
setup_args['to_dir'] = options.setuptools_to_dir
ez['use_setuptools'](**setup_args)
import setuptools
import pkg_resources
# This does not (always?) update the default working set. We will
# do it.
for path in sys.path:
if path not in pkg_resources.working_set.entries:
pkg_resources.working_set.add_entry(path)
######################################################################
# Install buildout
ws = pkg_resources.working_set
setuptools_path = ws.find(
pkg_resources.Requirement.parse('setuptools')).location
# Fix sys.path here as easy_install.pth added before PYTHONPATH
cmd = [sys.executable, '-c',
'import sys; sys.path[0:0] = [%r]; ' % setuptools_path +
'from setuptools.command.easy_install import main; main()',
'-mZqNxd', tmpeggs]
find_links = os.environ.get(
'bootstrap-testing-find-links',
options.find_links or
('http://downloads.buildout.org/'
if options.accept_buildout_test_releases else None)
)
if find_links:
cmd.extend(['-f', find_links])
requirement = 'zc.buildout'
version = options.buildout_version
if version is None and not options.accept_buildout_test_releases:
# Figure out the most recent final version of zc.buildout.
import setuptools.package_index
_final_parts = '*final-', '*final'
def _final_version(parsed_version):
try:
return not parsed_version.is_prerelease
except AttributeError:
# 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(
search_path=[setuptools_path])
if find_links:
index.add_find_links((find_links,))
req = pkg_resources.Requirement.parse(requirement)
if index.obtain(req) is not None:
best = []
bestv = None
for dist in index[req.project_name]:
distv = dist.parsed_version
if _final_version(distv):
if bestv is None or distv > bestv:
best = [dist]
bestv = distv
elif distv == bestv:
best.append(dist)
if best:
best.sort()
version = best[-1].version
if version:
requirement = '=='.join((requirement, version))
cmd.append(requirement)
import subprocess
if subprocess.call(cmd) != 0:
raise Exception(
"Failed to execute command:\n%s" % repr(cmd)[1:-1])
######################################################################
# Import and run buildout
ws.add_entry(tmpeggs)
ws.require(requirement)
import zc.buildout.buildout
if not [a for a in args if '=' not in a]:
args.append('bootstrap')
# if -c was provided, we push it back into args for buildout' main function
if options.config_file is not None:
args[0:0] = ['-c', options.config_file]
zc.buildout.buildout.main(args)
shutil.rmtree(tmpeggs)
......@@ -23,56 +23,70 @@ for d in 'eggs', 'develop-eggs', 'bin', 'parts':
if not os.path.exists(d):
os.mkdir(d)
bin_buildout = os.path.join('bin', 'buildout')
if os.path.isfile(bin_buildout):
os.remove(bin_buildout)
if os.path.isdir('build'):
shutil.rmtree('build')
######################################################################
# Make sure we have a relatively clean environment
try:
import pkg_resources, setuptools
except ImportError:
pass
else:
raise SystemError(
"Buildout development with a pre-installed setuptools or "
"distribute is not supported.")
######################################################################
# Install distribute
ez = {}
def check_upgrade(package):
print('')
print('Check %s' % package)
print('')
try:
from urllib.request import urlopen
except ImportError:
from urllib2 import urlopen
# XXX use a more permanent ez_setup.py URL when available.
exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez)
ez['use_setuptools'](to_dir='eggs', download_delay=0)
import pkg_resources, setuptools
setuptools_path = os.path.dirname(os.path.dirname(setuptools.__file__))
try:
sys.stdout.flush()
output = subprocess.check_output(
[sys.executable] + ['-m', 'pip', 'install', '--upgrade', package],
)
was_up_to_date = b"up-to-date" in output
if not was_up_to_date:
print(output.decode('utf8'))
return not was_up_to_date
except subprocess.CalledProcessError:
raise RuntimeError("Upgrade %s failed." % package)
need_restart = False
for package in ['pip', 'setuptools', 'wheel']:
did_upgrade = check_upgrade(package)
need_restart = need_restart or did_upgrade
if need_restart:
print("Restart")
sys.stdout.flush()
return_code = subprocess.call(
[sys.executable] + sys.argv
)
sys.exit(return_code)
######################################################################
# Install buildout
print('')
print('Install buildout')
print('')
sys.stdout.flush()
if subprocess.call(
[sys.executable] +
['setup.py', '-q', 'develop', '-m', '-x', '-d', 'develop-eggs'],
env=dict(os.environ, PYTHONPATH=setuptools_path)):
):
raise RuntimeError("buildout build failed.")
import pkg_resources
pkg_resources.working_set.add_entry('src')
import zc.buildout.easy_install
zc.buildout.easy_install.scripts(
['zc.buildout'], pkg_resources.working_set , sys.executable, 'bin')
######################################################################
print('')
print('Run buildout')
print('')
bin_buildout = os.path.join('bin', 'buildout')
if sys.platform.startswith('java'):
# Jython needs the script to be called twice via sys.executable
assert subprocess.Popen([sys.executable] + [bin_buildout]).wait() == 0
assert subprocess.Popen([sys.executable, bin_buildout, '-N']).wait() == 0
if sys.version_info < (2, 6):
bin_buildout = [bin_buildout, '-c2.4.cfg']
sys.stdout.flush()
sys.exit(subprocess.Popen(bin_buildout).wait())
......@@ -431,7 +431,7 @@ where you list them, as in:
>>> shutil.rmtree('eggs')
>>> run_buildout('buildout show-picked-versions=true')
>>> yup([n for n in ls('eggs') if n.startswith('bobo-2.3.0-')])
>>> yup('bobo = 2.3.0' in read('out'))
>>> yup('bobo==2.3.0' in read('out'))
In this example, we've requested a version of bobo less than 5.0.
......
......@@ -11,7 +11,6 @@ ignore =
Makefile.configure.Darwin
zc.recipe.egg_/*
zc.recipe.egg_
tox.ini
.github/*
.github
......
......@@ -12,7 +12,7 @@
#
##############################################################################
name = "zc.buildout"
version = '2.13.4.dev0'
version = '3.0.0.dev0'
import os
from setuptools import setup
......@@ -47,6 +47,8 @@ setup(
namespace_packages = ['zc'],
install_requires = [
'setuptools>=8.0',
'pip',
'wheel',
],
include_package_data = True,
entry_points = entry_points,
......
......@@ -13,9 +13,15 @@
##############################################################################
"""Buildout package
"""
import sys
import zc.buildout.patches # NOQA
WINDOWS = sys.platform.startswith('win')
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
class UserError(Exception):
"""Errors made by a user
"""
......
......@@ -1094,7 +1094,7 @@ class Buildout(DictMixin):
return
ws = zc.buildout.easy_install.install(
('zc.buildout', 'setuptools'),
('zc.buildout', 'setuptools', 'pip', 'wheel'),
self['buildout']['eggs-directory'],
links = self['buildout'].get('find-links', '').split(),
index = self['buildout'].get('index'),
......@@ -1107,7 +1107,7 @@ class Buildout(DictMixin):
# recorded in easy_install.py. We use that here to check if we've been
# upgraded.
start_locations = zc.buildout.easy_install.buildout_and_setuptools_path
for project in 'zc.buildout', 'setuptools':
for project in 'zc.buildout', 'setuptools', 'pip', 'wheel':
req = pkg_resources.Requirement.parse(project)
if ws.find(req).location not in start_locations:
upgraded.append(ws.find(req))
......@@ -1131,7 +1131,7 @@ class Buildout(DictMixin):
"buildout command.")
return
self._logger.info("Upgraded:\n %s;\nrestarting.",
self._logger.info("Upgraded:\n %s;\nRestarting.",
",\n ".join([("%s version %s"
% (dist.project_name, dist.version)
)
......
......@@ -55,7 +55,9 @@ The ``bin`` directory contains scripts::
The ``eggs`` directory has installed distributions:
>>> ls(sample_buildout, 'eggs')
- setuptools-0.7-py3.3.egg
- pip.egg-link
- setuptools.egg-link
- wheel.egg-link
- zc.buildout.egg-link
The ``develop-eggs`` and ``parts`` directories are initially empty::
......@@ -2838,11 +2840,8 @@ database is shown::
... """)
>>> print_(system(buildout+' -vv'), end='') # doctest: +NORMALIZE_WHITESPACE
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.7
<BLANKLINE>
Installing 'zc.buildout', 'setuptools', 'pip', 'wheel'.
...
Configuration data:
[buildout]
allow-hosts = *
......@@ -3108,8 +3107,10 @@ We also get other buildout artifacts::
>>> _ = (ls(sample_bootstrapped, 'eggs'),
... ls(sample_bootstrapped, 'develop-eggs'))
- setuptools-0.7-py2.3.egg
- zc.buildout-1.0-py2.3.egg
- pip.egg-link
- setuptools.egg-link
- wheel.egg-link
- zc.buildout.egg-link
(We list both the ``eggs`` and ``develop-eggs`` directories because the
buildout or setuptools egg could be installed in the ``develop-eggs``
......
......@@ -36,7 +36,10 @@ import sys
import tempfile
import zc.buildout
import zc.buildout.rmtree
from zc.buildout import WINDOWS
from zc.buildout import PY3
import warnings
import csv
try:
from setuptools.wheel import Wheel # This is the important import
......@@ -48,6 +51,9 @@ try:
except ImportError:
SETUPTOOLS_SUPPORTS_WHEELS = False
BIN_SCRIPTS = 'Scripts' if WINDOWS else 'bin'
warnings.filterwarnings(
'ignore', '.+is being parsed as a legacy, non PEP 440, version')
......@@ -90,6 +96,7 @@ if has_distribute and not has_setuptools:
# function to narrow to just the buildout and setuptools paths.
buildout_and_setuptools_path = [d.location for d in pkg_resources.working_set]
setuptools_path = buildout_and_setuptools_path
pip_path = buildout_and_setuptools_path
FILE_SCHEME = re.compile('file://', re.I).match
DUNDER_FILE_PATTERN = re.compile(r"__file__ = '(?P<filename>.+)'$")
......@@ -175,7 +182,7 @@ def _execute_permission():
return 0o777 - current_umask
_easy_install_cmd = 'from setuptools.command.easy_install import main; main()'
_pip_install_cmd = 'from pip.__main__ import _main; _main()'
def get_namespace_package_paths(dist):
"""
......@@ -382,7 +389,7 @@ class Installer(object):
logger.debug(
'There are no distros available that meet %r.\n'
'Using our best, %s.',
str(req), best_available)
str(req), best_we_have)
return best_we_have, None
if self._prefer_final:
......@@ -416,11 +423,11 @@ class Installer(object):
str(req))
return best_we_have, None
def _call_easy_install(self, spec, dest, dist):
def _call_pip_install(self, spec, dest, dist):
tmp = tempfile.mkdtemp(dir=dest)
try:
paths = call_easy_install(spec, tmp)
paths = call_pip_install(spec, tmp)
dists = []
env = pkg_resources.Environment(paths)
......@@ -831,7 +838,7 @@ class Installer(object):
setuptools.command.setopt.edit_config(
setup_cfg, dict(build_ext=build_ext))
dists = self._call_easy_install(base, self._dest, dist)
dists = self._call_pip_install(base, self._dest, dist)
return [dist.location for dist in dists]
finally:
......@@ -960,9 +967,14 @@ buildout_and_setuptools_dists = list(install(['zc.buildout'], None,
check_picked=False))
buildout_and_setuptools_path = [d.location
for d in buildout_and_setuptools_dists]
setuptools_path = [d.location
for d in install(['setuptools'], None, check_picked=False)]
setuptools_pythonpath = os.pathsep.join(setuptools_path)
pip_dists = [d for d in buildout_and_setuptools_dists if d.project_name != 'zc.buildout']
pip_path = [d.location for d in pip_dists]
pip_pythonpath = os.pathsep.join(pip_path)
setuptools_path = pip_path
setuptools_pythonpath = pip_pythonpath
def build(spec, dest, build_ext,
links=(), index=None,
......@@ -1625,17 +1637,17 @@ class IncompatibleConstraintError(zc.buildout.UserError):
IncompatibleVersionError = IncompatibleConstraintError # Backward compatibility
def call_easy_install(spec, dest):
def call_pip_install(spec, dest):
"""
Call `easy_install` from setuptools as a subprocess to install a
Call `pip install` from a subprocess to install a
distribution specified by `spec` into `dest`.
Returns all the paths inside `dest` created by the above.
"""
path = setuptools_path
path = pip_path
args = [sys.executable, '-c',
('import sys; sys.path[0:0] = %r; ' % path) +
_easy_install_cmd, '-mZUNxd', dest]
_pip_install_cmd, 'install', '--no-deps', '-t', dest]
level = logger.getEffectiveLevel()
if level > 0:
args.append('-q')
......@@ -1645,7 +1657,7 @@ def call_easy_install(spec, dest):
args.append(spec)
if level <= logging.DEBUG:
logger.debug('Running easy_install:\n"%s"\npath=%s\n',
logger.debug('Running pip install:\n"%s"\npath=%s\n',
'" "'.join(args), path)
sys.stdout.flush() # We want any pending output first
......@@ -1658,7 +1670,109 @@ def call_easy_install(spec, dest):
"Look above this message for any errors that "
"were output by easy_install.",
spec)
return glob.glob(os.path.join(dest, '*'))
split_entries = [os.path.splitext(entry) for entry in os.listdir(dest)]
try:
distinfo_dir = [
base + ext for base, ext in split_entries if ext == ".dist-info"
][0]
except IndexError:
logger.error(
"No .dist-info directory after installing %s",
spec)
raise
return make_egg_after_pip_install(dest, distinfo_dir)
def make_egg_after_pip_install(dest, distinfo_dir):
"""build properly named egg directory"""
# `pip install` does not build the namespace aware __init__.py files
# but they are needed in egg directories.
# Add them before moving files setup by pip
ns_file = os.path.join(dest, distinfo_dir, 'namespace_packages.txt')
if os.path.exists(ns_file):
with open(ns_file) as f:
namespace_packages = [
line.strip().replace('.', os.path.sep)
for line in f.readlines()
]
for namespace_package in namespace_packages:
init_py_file = os.path.join(dest, namespace_package, '__init__.py')
with open(init_py_file, 'w') as f:
f.write(
"__import__('pkg_resources').declare_namespace(__name__)"
)
# Remove `bin` directory if needed
# as there is no way to avoid script installation
# when running `pip install`
ep_file = os.path.join(dest, distinfo_dir, 'entry_points.txt')
if os.path.exists(ep_file):
with open(ep_file) as f:
content = f.read()
if "console_scripts" in content or "gui_scripts" in content:
bin_dir = os.path.join(dest, BIN_SCRIPTS)
if os.path.exists(bin_dir):
shutil.rmtree(bin_dir)
# Make properly named new egg dir
distro = [d for d in pkg_resources.find_distributions(dest)][0]
egg_name = distro.egg_name() + '.egg'
egg_dir = os.path.join(dest, egg_name)
os.mkdir(egg_dir)
# Move ".dist-info" dir into new egg dir
os.rename(
os.path.join(dest, distinfo_dir),
os.path.join(egg_dir, distinfo_dir)
)
with open(os.path.join(egg_dir, distinfo_dir, 'top_level.txt')) as f:
top_levels = filter(
(lambda x: len(x) != 0),
[line.strip() for line in f.readlines()]
)
# Move all top_level modules or packages
for top_level in top_levels:
# as package
top_level_dir = os.path.join(dest, top_level)
if os.path.exists(top_level_dir):
shutil.move(top_level_dir, egg_dir)
continue
# as module
top_level_py = top_level_dir + '.py'
if os.path.exists(top_level_py):
shutil.move(top_level_py, egg_dir)
top_level_pyc = top_level_dir + '.pyc'
if os.path.exists(top_level_pyc):
shutil.move(top_level_pyc, egg_dir)
continue
if PY3:
with open(os.path.join(egg_dir, distinfo_dir, 'RECORD'), newline='') as f:
all_files = [row[0] for row in csv.reader(f)]
else:
with open(os.path.join(egg_dir, distinfo_dir, 'RECORD'), 'rb') as f:
all_files = [row[0] for row in csv.reader(f)]
# There might be some c extensions left over
for entry in all_files:
if entry.endswith(('.pyc', '.pyo')):
continue
dest_entry = os.path.join(dest, entry)
# work around pip install -t bug that leaves entries in RECORD
# that starts with '../../'
if not os.path.abspath(dest_entry).startswith(dest):
continue
egg_entry = os.path.join(egg_dir, entry)
if os.path.exists(dest_entry) and not os.path.exists(egg_entry):
os.rename(dest_entry, egg_entry)
return [egg_dir]
def unpack_egg(location, dest):
......@@ -1730,6 +1844,7 @@ def _move_to_eggs_dir_and_compile(dist, dest):
raise
tmp_dest = tempfile.mkdtemp(dir=dest)
try:
installed_with_pip = False
if (os.path.isdir(dist.location) and
dist.precedence >= pkg_resources.BINARY_DIST):
# We got a pre-built directory. It must have been obtained locally.
......@@ -1740,9 +1855,13 @@ def _move_to_eggs_dir_and_compile(dist, dest):
# It is an archive of some sort.
# Figure out how to unpack it, or fall back to easy_install.
_, ext = os.path.splitext(dist.location)
unpacker = UNPACKERS.get(ext, call_easy_install)
unpacker(dist.location, tmp_dest)
[tmp_loc] = glob.glob(os.path.join(tmp_dest, '*'))
if ext in UNPACKERS:
unpacker = UNPACKERS[ext]
unpacker(dist.location, tmp_dest)
[tmp_loc] = glob.glob(os.path.join(tmp_dest, '*'))
else:
[tmp_loc] = call_pip_install(dist.location, tmp_dest)
installed_with_pip = True
# We have installed the dist. Now try to rename/move it.
newloc = os.path.join(dest, os.path.basename(tmp_loc))
......@@ -1777,4 +1896,6 @@ def _move_to_eggs_dir_and_compile(dist, dest):
finally:
# Remember that temporary directories must be removed
zc.buildout.rmtree.rmtree(tmp_dest)
if installed_with_pip:
newdist.precedence = pkg_resources.EGG_DIST
return newdist
......@@ -345,7 +345,7 @@ reporting that a version was picked automatically:
zc.buildout.easy_install DEBUG
Fetching demoneeded 1.1 from: http://.../demoneeded-1.1.zip
zc.buildout.easy_install DEBUG
Running easy_install:...
Running pip install:...
zc.buildout.easy_install INFO
Got demoneeded 1.1.
zc.buildout.easy_install DEBUG
......
......@@ -127,10 +127,8 @@ about versions used. If we run the buildout in verbose mode without
specifying a versions section:
>>> print_(system(buildout+' buildout:versions= -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 'zc.buildout', 'setuptools', 'pip', 'wheel'.
...
Installing 'spam'.
We have the best distribution that satisfies 'spam'.
Picked: spam = 2.
......@@ -149,10 +147,8 @@ that we can fix them in a versions section.
If we run the buildout with the versions section:
>>> 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 'zc.buildout', 'setuptools', 'pip', 'wheel'.
...
Installing 'spam'.
We have the distribution that satisfies 'spam==1'.
Uninstalling foo.
......@@ -180,10 +176,8 @@ versions for.
... ''' % 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 'zc.buildout', 'setuptools', 'pip', 'wheel'.
...
Installing 'spam >0'.
We have the distribution that satisfies 'spam==1'.
Uninstalling foo.
......@@ -202,7 +196,6 @@ versions:
... allow-picked-versions = false
...
... [versions]
... spam = 1
... eggs = 2.2
...
... [foo]
......@@ -211,9 +204,11 @@ versions:
>>> print_(system(buildout), end='') # doctest: +ELLIPSIS
While:
Installing.
Checking for upgrades.
Getting distribution for 'setuptools'.
Error: Picked: setuptools = 0.6.30
Getting section foo.
Initializing section foo.
Installing recipe spam.
Getting distribution for 'spam'.
Error: Picked: spam = 2
We can name a version something else, if we wish, using the versions option:
......@@ -265,12 +260,14 @@ Since buildout 2.0, the functionality of the `buildout-versions
<http://packages.python.org/buildout-versions/>`_ extension is part of
buildout itself. This makes reporting and managing versions easier.
Buildout picks a version for setuptools and for the tests, we need to grab the
Buildout picks versions for pip and setuptools and for the tests, we need to grab the
version number:
>>> import pkg_resources
>>> req = pkg_resources.Requirement.parse('setuptools')
>>> setuptools_version = pkg_resources.working_set.find(req).version
>>> req = pkg_resources.Requirement.parse('pip')
>>> pip_version = pkg_resources.working_set.find(req).version
If you set the ``show-picked-versions`` option, buildout will print
versions it picked at the end of its run:
......@@ -293,7 +290,6 @@ versions it picked at the end of its run:
Versions had to be automatically picked.
The following part definition lists the versions picked:
[versions]
setuptools = 0.6.99
spam = 2
When everything is pinned, no output is generated:
......@@ -306,12 +302,13 @@ When everything is pinned, no output is generated:
... show-picked-versions = true
...
... [versions]
... pip = %s
... setuptools = %s
... spam = 2
...
... [foo]
... recipe = spam
... ''' % (join('recipe', 'dist'), setuptools_version))
... ''' % (join('recipe', 'dist'), pip_version, setuptools_version))
>>> print_(system(buildout), end='') # doctest: +ELLIPSIS
Updating foo.
recipe v2
......@@ -330,12 +327,13 @@ and case differences won't impact the pinning:
... show-picked-versions = true
...
... [versions]
... pip = %s
... setuptools = %s
... Spam = 2
...
... [foo]
... recipe = spam
... ''' % (join('recipe', 'dist'), setuptools_version))
... ''' % (join('recipe', 'dist'), pip_version, setuptools_version))
>>> print_(system(buildout), end='') # doctest: +ELLIPSIS
Updating foo.
recipe v2
......@@ -347,9 +345,10 @@ extending from that versions file:
>>> write('my_versions.cfg',
... '''
... [versions]
... pip = %s
... setuptools = %s
... spam = 2
... ''' % setuptools_version)
... ''' % (pip_version, setuptools_version))
>>> write('buildout.cfg',
... '''
... [buildout]
......@@ -372,8 +371,9 @@ at the end.
>>> write('my_versions.cfg',
... '''
... [versions]
... pip = %s
... setuptools = %s
... ''' % setuptools_version)
... ''' % (pip_version, setuptools_version))
>>> write('buildout.cfg',
... '''
... [buildout]
......@@ -416,8 +416,9 @@ printing them to the console):
>>> write('my_versions.cfg',
... '''
... [versions]
... pip = %s
... setuptools = %s
... ''' % setuptools_version)
... ''' % (pip_version, setuptools_version))
>>> write('buildout.cfg',
... '''
... [buildout]
......
......@@ -25,7 +25,7 @@ except ImportError:
import errno
import logging
from multiprocessing import Process
from multiprocessing import get_context
import os
import pkg_resources
import random
......@@ -168,7 +168,7 @@ def _runsetup(setup, *args):
zc.buildout.easy_install.call_subprocess(
[sys.executable, setup] + args,
env=dict(os.environ,
PYTHONPATH=zc.buildout.easy_install.setuptools_pythonpath,
PYTHONPATH=zc.buildout.easy_install.pip_pythonpath,
),
)
if os.path.exists('build'):
......@@ -601,12 +601,6 @@ ignore_not_upgrading = (
'Not upgrading because not running a local buildout command.\n'
), '')
easy_install_deprecated = (
re.compile(
'WARNING: The easy_install command is deprecated and will be removed in a future version.\n'
), '')
def run_buildout(command):
# Make sure we don't get .buildout
os.environ['HOME'] = os.path.join(os.getcwd(), 'home')
......@@ -621,7 +615,8 @@ def run_from_process(target, *args, **kw):
target(*args, **kw)
def run_in_process(*args, **kwargs):
process = Process(target=run_from_process, args=args, kwargs=kwargs)
ctx = get_context('fork')
process = ctx.Process(target=run_from_process, args=args, kwargs=kwargs)
process.daemon = True
process.start()
process.join(99)
......
......@@ -12,8 +12,7 @@ zc.buildout.testing.
The handlers before calling set up are:
>>> import logging
>>> len(logging.getLogger().handlers)
1
>>> count = len(logging.getLogger().handlers)
>>> logging.getLogger().handlers # doctest: +ELLIPSIS
[<...NullHandler...>]
......@@ -24,15 +23,15 @@ After calling it, a ``logging.StreamHandler`` was added:
>>> test = doctest.DocTestParser().get_doctest(
... '>>> x', {}, 'foo', 'foo.py', 0)
>>> zc.buildout.testing.buildoutSetUp(test)
>>> len(logging.getLogger().handlers)
2
>>> len(logging.getLogger().handlers) == count + 1
True
>>> logging.getLogger().handlers # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
[<...NullHandler...StreamHandler...>]
But tear down removes the new logging handler:
>>> zc.buildout.testing.buildoutTearDown(test)
>>> len(logging.getLogger().handlers)
1
>>> len(logging.getLogger().handlers) == count
True
>>> logging.getLogger().handlers # doctest: +ELLIPSIS
[<...NullHandler...>]
This diff is collapsed.
......@@ -7,8 +7,6 @@ demonstrate this, we've created some "new releases" of buildout and
setuptools in a new_releases folder:
>>> ls(new_releases)
d setuptools
- setuptools-99.99-py2.4.egg
d zc.buildout
- zc.buildout-99.99-py2.4.egg
......@@ -43,7 +41,7 @@ zc.buildout used:
... pass
...
... def install(self):
... for project in 'zc.buildout', 'setuptools':
... for project in ['zc.buildout']:
... req = pkg_resources.Requirement.parse(project)
... print_(project, pkg_resources.working_set.find(req).version)
... return ()
......@@ -68,17 +66,13 @@ new versions found in new releases:
>>> print_(system(buildout), end='')
Getting distribution for 'zc.buildout>=1.99'.
Got zc.buildout 99.99.
Getting distribution for 'setuptools'.
Got setuptools 99.99.
Upgraded:
zc.buildout version 99.99,
setuptools version 99.99;
restarting.
zc.buildout version 99.99;
Restarting.
Generated script '/sample-buildout/bin/buildout'.
Develop: '/sample-buildout/showversions'
Installing show-versions.
zc.buildout 99.99
setuptools 99.99
Our buildout script has been updated to use the new eggs:
......@@ -87,8 +81,8 @@ Our buildout script has been updated to use the new eggs:
<BLANKLINE>
import sys
sys.path[0:0] = [
'/sample-buildout/eggs/setuptools-99.99-py2.4.egg',
'/sample-buildout/eggs/zc.buildout-99.99-py2.4.egg',
...
]
<BLANKLINE>
import zc.buildout.buildout
......@@ -110,7 +104,6 @@ will install earlier versions of these packages:
...
... [versions]
... zc.buildout = < 99
... setuptools = < 99
...
... [show-versions]
... recipe = showversions
......@@ -120,14 +113,12 @@ Now we can see that we actually "upgrade" to an earlier version.
>>> print_(system(buildout), end='')
Upgraded:
zc.buildout version 1.4.4;
setuptools version 0.6;
restarting.
zc.buildout version 1.4.4,
Restarting.
Generated script '/sample-buildout/bin/buildout'.
Develop: '/sample-buildout/showversions'
Updating show-versions.
zc.buildout 1.0.0
setuptools 0.6
zc.buildout 1.4.4
There are a number of cases, described below, in which the updates
don't happen.
......@@ -150,7 +141,6 @@ We won't upgrade in offline mode:
Develop: '/sample-buildout/showversions'
Updating show-versions.
zc.buildout 1.0.0
setuptools 0.6
Or in non-newest mode:
......@@ -158,7 +148,6 @@ Or in non-newest mode:
Develop: '/sample-buildout/showversions'
Updating show-versions.
zc.buildout 1.0.0
setuptools 0.6
We also won't upgrade if the buildout script being run isn't in the
buildouts bin directory. To see this we'll create a new buildout
......@@ -181,8 +170,6 @@ directory:
Creating directory '/sample_buildout2/develop-eggs'.
Getting distribution for 'zc.buildout>=1.99'.
Got zc.buildout 99.99.
Getting distribution for 'setuptools'.
Got setuptools 99.99.
Not upgrading because not running a local buildout command.
>>> ls('bin')
......@@ -205,15 +192,13 @@ directory:
>>> print_(system(buildout), end='')
Upgraded:
zc.buildout version 99.99,
setuptools version 99.99;
restarting.
zc.buildout version 99.99;
Restarting.
Generated script '/sample-buildout/bin/buildout'.
Develop: '/sample-buildout/showversions'
Unused options for buildout: 'relative-paths'.
Updating show-versions.
zc.buildout 99.99
setuptools 99.99
>>> cat('bin', 'buildout') # doctest +ELL
#!/usr/local/bin/python2.7
......@@ -227,7 +212,7 @@ directory:
import sys
sys.path[0:0] = [
join(base, 'eggs/zc.buildout-99.99-py3.3.egg'),
join(base, 'eggs/setuptools-99.99-py3.3.egg'),
...
]
<BLANKLINE>
import zc.buildout.buildout
......@@ -282,7 +267,6 @@ gives us a sys.exit:
...
... [versions]
... zc.buildout = < 99
... setuptools = < 99
...
... [fail]
... recipe = failrecipe
......@@ -292,9 +276,8 @@ Run the buildout:
>>> print_(system(buildout, with_exit_code=True), end='')
Upgraded:
zc.buildout version 1.4.4;
setuptools version 0.6;
restarting.
zc.buildout version 1.4.4,
Restarting.
Generated script '/sample-buildout/bin/buildout'.
Develop: '/sample-buildout/failrecipe'
recipe sys-exits
......
[tox]
envlist =
py27,py34,py35,py36
[testenv]
commands =
pip install coverage
# buildout's dev.py wants python to not have setuptools
pip uninstall -y zc.buildout setuptools pip
python dev.py
{toxinidir}/bin/test -1 -vvv -c
coverage combine
coverage report
# since we're removing setuptools we can't possibly reuse the virtualenv
recreate = true
# if the user has ccache installed, PATH may contain /usr/lib/ccache that has a
# gcc wrapper that fails to build anything when buildout's tests set HOME to a
# non-existent directory under /tmp
setenv =
PATH=/usr/bin:/bin
......@@ -75,7 +75,9 @@ Now, if we look at the buildout eggs directory:
>>> ls(sample_buildout, 'eggs')
d demo-0.2-py2.3.egg
d demoneeded-1.1-py2.3.egg
- setuptools-0.7-py2.3.egg
- pip.egg-link
- setuptools.egg-link
- wheel.egg-link
d zc.buildout-1.0-py2.3.egg
We see that we got an egg for demo that met the requirement, as well
......@@ -264,7 +266,9 @@ We didn't get an update for demo:
>>> ls(sample_buildout, 'eggs')
d demo-0.2-py2.3.egg
d demoneeded-1.1-py2.3.egg
- setuptools-0.7-py2.3.egg
- pip.egg-link
- setuptools.egg-link
- wheel.egg-link
d zc.buildout-1.0-py2.3.egg
If we run the buildout on the default online and newest modes,
......@@ -282,7 +286,9 @@ Then we'll get a new demo egg:
d demo-0.2-py2.3.egg
d demo-0.3-py2.3.egg
d demoneeded-1.1-py2.3.egg
- setuptools-0.7-py2.4.egg
- pip.egg-link
- setuptools.egg-link
- wheel.egg-link
d zc.buildout-1.0-py2.4.egg
The script is updated too:
......
......@@ -229,13 +229,11 @@ We won't get an update.
- zc.recipe.egg.egg-link
But if we run the buildout in the default on-line and newest modes, we
will. This time we also get the test-variable message again, because the new
version is imported:
will.
>>> print_(system(buildout), end='') # doctest: +ELLIPSIS
Develop: '/sample-buildout/demo'
Updating extdemo.
zip_safe flag not set; analyzing archive contents...
Updating demo.
...
......@@ -341,17 +339,22 @@ Create our buildout:
... recipe = recipes:environ
...
... """ % dict(server=link_server))
>>> print_(system(buildout), end='') # doctest: +ELLIPSIS
>>> print_(system(buildout+' -vvv'), end='') # doctest: +ELLIPSIS
Installing 'zc.buildout', 'setuptools', 'pip', 'wheel'.
...
Develop: '/sample-buildout/recipes'
...
Uninstalling demo.
...
Uninstalling extdemo.
...
Installing extdemo.
Have environment test-variable: foo
zip_safe flag not set; analyzing archive contents...
...
Installing checkenv.
...Running command python setup.py egg_info
...Have environment test-variable: foo
...
The setup.py also printed out that we have set the environment `test-variable`
to foo. After the buildout the variable is reset to its original value (i.e.
removed).
......@@ -395,13 +398,18 @@ are interpolated with os.environ before the're set:
... recipe = recipes:environ
...
... """ % dict(server=link_server))
>>> print_(system(buildout), end='') # doctest: +ELLIPSIS
>>> print_(system(buildout+' -vvv'), end='') # doctest: +ELLIPSIS
Installing 'zc.buildout', 'setuptools', 'pip', 'wheel'.
...
Develop: '/sample-buildout/recipes'
...
Uninstalling extdemo.
...
Installing extdemo.
Have environment test-variable: foo:bar
zip_safe flag not set; analyzing archive contents...
...
Updating checkenv.
...Running command python setup.py egg_info
...Have environment test-variable: foo:bar
...
>>> os.environ['test-variable']
......
......@@ -50,10 +50,10 @@ def test_suite():
zc.buildout.tests.normalize_bang,
zc.buildout.tests.normalize_S,
zc.buildout.testing.not_found,
zc.buildout.testing.easy_install_deprecated,
(re.compile(r'[d-] zc.buildout(-\S+)?[.]egg(-link)?'),
'zc.buildout.egg'),
(re.compile(r'[d-] setuptools-[^-]+-'), 'setuptools-X-'),
(re.compile(r'[d-] pip-[^-]+-'), 'pip-X-'),
(re.compile(r'eggs\\\\demo'), 'eggs/demo'),
(re.compile(r'[a-zA-Z]:\\\\foo\\\\bar'), '/foo/bar'),
])
......@@ -66,7 +66,6 @@ def test_suite():
zc.buildout.testing.normalize_path,
zc.buildout.testing.normalize_endings,
zc.buildout.testing.not_found,
zc.buildout.testing.easy_install_deprecated,
(re.compile('__buildout_signature__ = '
r'sample-\S+\s+'
r'zc.recipe.egg-\S+\s+'
......@@ -88,28 +87,9 @@ def test_suite():
zc.buildout.testing.normalize_path,
zc.buildout.testing.normalize_endings,
zc.buildout.testing.not_found,
zc.buildout.testing.easy_install_deprecated,
(re.compile("(d ((ext)?demo(needed)?|other)"
r"-\d[.]\d-py)\d[.]\d(-\S+)?[.]egg"),
'\\1V.V.egg'),
(re.compile('extdemo.c\n.+\\\\extdemo.exp\n'), ''),
(re.compile(
r'zip_safe flag not set; analyzing archive contents.*\n'),
''),
(re.compile(
r'\n.*module references __file__'),
''),
(re.compile(''), ''),
(re.compile(
"extdemo[.]c\n"
"extdemo[.]obj : warning LNK4197: "
"export 'initextdemo' specified multiple times; "
"using first specification\n"
" Creating library build\\\\temp[.]win-amd64-2[.]"
"[4567]\\\\Release\\\\extdemo[.]lib and object "
"build\\\\temp[.]win-amd64-2[.][4567]\\\\Re"
"lease\\\\extdemo[.]exp\n"),
''),
]),
),
doctest.DocFileSuite(
......@@ -120,7 +100,6 @@ def test_suite():
zc.buildout.testing.normalize_path,
zc.buildout.testing.normalize_endings,
zc.buildout.testing.not_found,
zc.buildout.testing.easy_install_deprecated,
])
),
))
......
......@@ -33,7 +33,7 @@ Here's an example:
>>> isinstance(ws, pkg_resources.WorkingSet)
True
>>> sorted(dist.project_name for dist in ws)
['demo', 'demoneeded', 'setuptools', 'zc.buildout', 'zc.recipe.egg']
['demo', 'demoneeded', 'pip', 'setuptools', 'wheel', 'zc.buildout', 'zc.recipe.egg']
We'll monkey patch a method in the ``easy_install`` module in order to verify if
the cache is working:
......
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