Commit 5476aec2 authored by Jason R. Coombs's avatar Jason R. Coombs Committed by GitHub

Merge pull request #1089 from benoit-pierre/fix_requires_handling

Fix requires handling
parents 1eec0203 a3ec721e
...@@ -43,7 +43,4 @@ install: ...@@ -43,7 +43,4 @@ install:
# update egg_info based on setup.py in checkout # update egg_info based on setup.py in checkout
- python bootstrap.py - python bootstrap.py
# Check that setuptools can be installed in a clean environment
- tests/clean_install.sh
script: tox script: tox
from unittest import mock import mock
from pkg_resources import evaluate_marker from pkg_resources import evaluate_marker
......
...@@ -9,7 +9,6 @@ import distutils.core ...@@ -9,7 +9,6 @@ import distutils.core
import distutils.cmd import distutils.cmd
import distutils.dist import distutils.dist
import itertools import itertools
import operator
from collections import defaultdict from collections import defaultdict
from distutils.errors import ( from distutils.errors import (
DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError, DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError,
...@@ -17,7 +16,7 @@ from distutils.errors import ( ...@@ -17,7 +16,7 @@ from distutils.errors import (
from distutils.util import rfc822_escape from distutils.util import rfc822_escape
from setuptools.extern import six from setuptools.extern import six
from setuptools.extern.six.moves import map, filter from setuptools.extern.six.moves import map
from pkg_resources.extern import packaging from pkg_resources.extern import packaging
from setuptools.depends import Require from setuptools.depends import Require
...@@ -146,17 +145,7 @@ def _check_extra(extra, reqs): ...@@ -146,17 +145,7 @@ def _check_extra(extra, reqs):
name, sep, marker = extra.partition(':') name, sep, marker = extra.partition(':')
if marker and pkg_resources.invalid_marker(marker): if marker and pkg_resources.invalid_marker(marker):
raise DistutilsSetupError("Invalid environment marker: " + marker) raise DistutilsSetupError("Invalid environment marker: " + marker)
list(pkg_resources.parse_requirements(reqs))
# extras requirements cannot themselves have markers
parsed = pkg_resources.parse_requirements(reqs)
marked_reqs = filter(operator.attrgetter('marker'), parsed)
bad_req = next(marked_reqs, None)
if bad_req:
tmpl = (
"'extras_require' requirements cannot include "
"environment markers, in {name!r}: '{bad_req!s}'"
)
raise DistutilsSetupError(tmpl.format(**locals()))
def assert_bool(dist, attr, value): def assert_bool(dist, attr, value):
...@@ -366,23 +355,41 @@ class Distribution(Distribution_parse_config_files, _Distribution): ...@@ -366,23 +355,41 @@ class Distribution(Distribution_parse_config_files, _Distribution):
def _finalize_requires(self): def _finalize_requires(self):
""" """
Move requirements in `install_requires` that Fix environment markers in `install_requires` and `extras_require`.
are using environment markers to `extras_require`.
- move requirements in `install_requires` that are using environment
markers or extras to `extras_require`.
- convert requirements in `extras_require` of the form
`"extra": ["barbazquux; {marker}"]` to
`"extra:{marker}": ["barbazquux"]`.
""" """
if not self.install_requires: extras_require = defaultdict(list)
return for k, v in (
extras_require = defaultdict(list, ( getattr(self, 'extras_require', None) or {}
(k, list(pkg_resources.parse_requirements(v))) ).items():
for k, v in (self.extras_require or {}).items() for r in pkg_resources.parse_requirements(v):
)) marker = r.marker
if marker:
r.marker = None
extras_require[k + ':' + str(marker)].append(r)
else:
extras_require[k].append(r)
install_requires = [] install_requires = []
for r in pkg_resources.parse_requirements(self.install_requires): for r in pkg_resources.parse_requirements(
getattr(self, 'install_requires', None) or ()
):
marker = r.marker marker = r.marker
if not marker: extras = r.extras
if not marker and not extras:
install_requires.append(r) install_requires.append(r)
continue continue
r.extras = ()
r.marker = None r.marker = None
extras_require[':' + str(marker)].append(r) for e in extras or ('',):
section = e
if marker:
section += ':' + str(marker)
extras_require[section].append(r)
self.extras_require = dict( self.extras_require = dict(
(k, [str(r) for r in v]) (k, [str(r) for r in v])
for k, v in extras_require.items() for k, v in extras_require.items()
......
...@@ -2,7 +2,7 @@ import pytest ...@@ -2,7 +2,7 @@ import pytest
import os import os
import shutil import shutil
from unittest import mock import mock
from distutils.errors import DistutilsSetupError from distutils.errors import DistutilsSetupError
from setuptools.command.build_clib import build_clib from setuptools.command.build_clib import build_clib
from setuptools.dist import Distribution from setuptools.dist import Distribution
......
...@@ -14,7 +14,7 @@ import itertools ...@@ -14,7 +14,7 @@ import itertools
import distutils.errors import distutils.errors
import io import io
import zipfile import zipfile
from unittest import mock import mock
import time import time
from setuptools.extern.six.moves import urllib from setuptools.extern.six.moves import urllib
......
...@@ -182,8 +182,13 @@ class TestEggInfo(object): ...@@ -182,8 +182,13 @@ class TestEggInfo(object):
mismatch_marker = "python_version<'{this_ver}'".format( mismatch_marker = "python_version<'{this_ver}'".format(
this_ver=sys.version_info[0], this_ver=sys.version_info[0],
) )
# Alternate equivalent syntax.
mismatch_marker_alternate = 'python_version < "{this_ver}"'.format(
this_ver=sys.version_info[0],
)
invalid_marker = "<=>++"
def test_install_requires_with_markers(self, tmpdir_cwd, env): def test_install_requires_with_marker(self, tmpdir_cwd, env):
tmpl = 'install_requires=["barbazquux;{marker}"],' tmpl = 'install_requires=["barbazquux;{marker}"],'
req = tmpl.format(marker=self.mismatch_marker) req = tmpl.format(marker=self.mismatch_marker)
self._setup_script_with_requires(req) self._setup_script_with_requires(req)
...@@ -193,9 +198,40 @@ class TestEggInfo(object): ...@@ -193,9 +198,40 @@ class TestEggInfo(object):
with open(requires_txt) as fp: with open(requires_txt) as fp:
install_requires = fp.read() install_requires = fp.read()
expected_requires = DALS(''' expected_requires = DALS('''
[:python_version < "{sys.version_info[0]}"] [:{marker}]
barbazquux
''').format(marker=self.mismatch_marker_alternate)
assert install_requires.lstrip() == expected_requires
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
def test_install_requires_with_extra(self, tmpdir_cwd, env):
req = 'install_requires=["barbazquux [test]"],'
self._setup_script_with_requires(req)
self._run_install_command(tmpdir_cwd, env)
egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
requires_txt = os.path.join(egg_info_dir, 'requires.txt')
with open(requires_txt) as fp:
install_requires = fp.read()
expected_requires = DALS('''
[test]
barbazquux
''')
assert install_requires.lstrip() == expected_requires
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
def test_install_requires_with_extra_and_marker(self, tmpdir_cwd, env):
tmpl = 'install_requires=["barbazquux [test]; {marker}"],'
req = tmpl.format(marker=self.mismatch_marker)
self._setup_script_with_requires(req)
self._run_install_command(tmpdir_cwd, env)
egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
requires_txt = os.path.join(egg_info_dir, 'requires.txt')
with open(requires_txt) as fp:
install_requires = fp.read()
expected_requires = DALS('''
[test:{marker}]
barbazquux barbazquux
''').format(sys=sys) ''').format(marker=self.mismatch_marker_alternate)
assert install_requires.lstrip() == expected_requires assert install_requires.lstrip() == expected_requires
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == [] assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
...@@ -214,19 +250,53 @@ class TestEggInfo(object): ...@@ -214,19 +250,53 @@ class TestEggInfo(object):
tmpdir_cwd, env, cmd=['test'], output="Ran 0 tests in") tmpdir_cwd, env, cmd=['test'], output="Ran 0 tests in")
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == [] assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
def test_extras_require_with_markers(self, tmpdir_cwd, env): def test_extras_require_with_marker(self, tmpdir_cwd, env):
tmpl = 'extras_require={{":{marker}": ["barbazquux"]}},' tmpl = 'extras_require={{":{marker}": ["barbazquux"]}},'
req = tmpl.format(marker=self.mismatch_marker) req = tmpl.format(marker=self.mismatch_marker)
self._setup_script_with_requires(req) self._setup_script_with_requires(req)
self._run_install_command(tmpdir_cwd, env) self._run_install_command(tmpdir_cwd, env)
egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
requires_txt = os.path.join(egg_info_dir, 'requires.txt')
with open(requires_txt) as fp:
install_requires = fp.read()
expected_requires = DALS('''
[:{marker}]
barbazquux
''').format(marker=self.mismatch_marker)
assert install_requires.lstrip() == expected_requires
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == [] assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
def test_extras_require_with_markers_in_req(self, tmpdir_cwd, env): def test_extras_require_with_marker_in_req(self, tmpdir_cwd, env):
tmpl = 'extras_require={{"extra": ["barbazquux; {marker}"]}},' tmpl = 'extras_require={{"extra": ["barbazquux; {marker}"]}},'
req = tmpl.format(marker=self.mismatch_marker) req = tmpl.format(marker=self.mismatch_marker)
self._setup_script_with_requires(req) self._setup_script_with_requires(req)
self._run_install_command(tmpdir_cwd, env)
egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
requires_txt = os.path.join(egg_info_dir, 'requires.txt')
with open(requires_txt) as fp:
install_requires = fp.read()
expected_requires = DALS('''
[extra:{marker}]
barbazquux
''').format(marker=self.mismatch_marker_alternate)
assert install_requires.lstrip() == expected_requires
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
def test_extras_require_with_invalid_marker(self, tmpdir_cwd, env):
tmpl = 'extras_require={{":{marker}": ["barbazquux"]}},'
req = tmpl.format(marker=self.invalid_marker)
self._setup_script_with_requires(req)
with pytest.raises(AssertionError):
self._run_install_command(tmpdir_cwd, env)
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
def test_extras_require_with_invalid_marker_in_req(self, tmpdir_cwd, env):
tmpl = 'extras_require={{"extra": ["barbazquux; {marker}"]}},'
req = tmpl.format(marker=self.invalid_marker)
self._setup_script_with_requires(req)
with pytest.raises(AssertionError): with pytest.raises(AssertionError):
self._run_install_command(tmpdir_cwd, env) self._run_install_command(tmpdir_cwd, env)
assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
def test_python_requires_egg_info(self, tmpdir_cwd, env): def test_python_requires_egg_info(self, tmpdir_cwd, env):
self._setup_script_with_requires( self._setup_script_with_requires(
......
...@@ -5,7 +5,7 @@ Tests for msvc support module. ...@@ -5,7 +5,7 @@ Tests for msvc support module.
import os import os
import contextlib import contextlib
import distutils.errors import distutils.errors
from unittest import mock import mock
import pytest import pytest
......
import glob
import os
from pytest import yield_fixture
from pytest_fixture_config import yield_requires_config
import pytest_virtualenv
@yield_requires_config(pytest_virtualenv.CONFIG, ['virtualenv_executable'])
@yield_fixture(scope='function')
def bare_virtualenv():
""" Bare virtualenv (no pip/setuptools/wheel).
"""
with pytest_virtualenv.VirtualEnv(args=(
'--no-wheel',
'--no-pip',
'--no-setuptools',
)) as venv:
yield venv
SOURCE_DIR = os.path.join(os.path.dirname(__file__), '../..')
def test_clean_env_install(bare_virtualenv):
"""
Check setuptools can be installed in a clean environment.
"""
bare_virtualenv.run(' && '.join((
'cd {source}',
'python setup.py install',
)).format(source=SOURCE_DIR))
def test_pip_upgrade_from_source(virtualenv):
"""
Check pip can upgrade setuptools from source.
"""
dist_dir = virtualenv.workspace
# Generate source distribution / wheel.
virtualenv.run(' && '.join((
'cd {source}',
'python setup.py -q sdist -d {dist}',
'python setup.py -q bdist_wheel -d {dist}',
)).format(source=SOURCE_DIR, dist=dist_dir))
sdist = glob.glob(os.path.join(dist_dir, '*.zip'))[0]
wheel = glob.glob(os.path.join(dist_dir, '*.whl'))[0]
# Then update from wheel.
virtualenv.run('pip install ' + wheel)
# And finally try to upgrade from source.
virtualenv.run('pip install --no-cache-dir --upgrade ' + sdist)
#!/usr/bin/env bash
# This test was created in
# https://github.com/pypa/setuptools/pull/1050
# but it really should be incorporated into the test suite
# such that it runs on Windows and doesn't depend on
# virtualenv. Moving to test_integration will likely address
# those concerns.
set -o errexit
set -o xtrace
# Create a temporary directory to install the virtualenv in
VENV_DIR="$(mktemp -d)"
function cleanup() {
rm -rf "$VENV_DIR"
}
trap cleanup EXIT
# Create a virtualenv that doesn't have pip or setuptools installed
wget https://raw.githubusercontent.com/pypa/virtualenv/master/virtualenv.py
python virtualenv.py --no-wheel --no-pip --no-setuptools "$VENV_DIR"
source "$VENV_DIR/bin/activate"
# Now try to install setuptools
python bootstrap.py
python setup.py install
importlib; python_version<"2.7"
mock
pytest-flake8 pytest-flake8
pytest-virtualenv>=1.2.7
pytest>=3.0.2 pytest>=3.0.2
# pinned to 1.2 as temporary workaround for #1038 virtualenv
backports.unittest_mock>=1.2,<1.3
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