Commit 318f739d authored by Paul Ganssle's avatar Paul Ganssle

Add requirement parsing in setuptools.build_meta

This fixes GH #1682 by porting the pkg_resources requirement parsing
logic into setuptools.build_meta, so that all valid requirement
specifiers passed to setup_requires will be added to the
get_requires_for_build_* function outputs.

Fixes GH #1682
parent 1aa781cd
......@@ -36,6 +36,8 @@ import contextlib
import setuptools
import distutils
from setuptools._vendor import six
__all__ = ['get_requires_for_build_sdist',
'get_requires_for_build_wheel',
'prepare_metadata_for_build_wheel',
......@@ -51,7 +53,9 @@ class SetupRequirementsError(BaseException):
class Distribution(setuptools.dist.Distribution):
def fetch_build_eggs(self, specifiers):
raise SetupRequirementsError(specifiers)
specifier_list = self._parse_requirements(specifiers)
raise SetupRequirementsError(specifier_list)
@classmethod
@contextlib.contextmanager
......@@ -68,6 +72,45 @@ class Distribution(setuptools.dist.Distribution):
finally:
distutils.core.Distribution = orig
def _yield_lines(self, strs):
"""Yield non-empty/non-comment lines of a string or sequence"""
if isinstance(strs, six.string_types):
for s in strs.splitlines():
s = s.strip()
# skip blank lines/comments
if s and not s.startswith('#'):
yield s
else:
for ss in strs:
for s in self._yield_lines(ss):
yield s
def _parse_requirements(self, strs):
"""Parse requirement specifiers into a list of requirement strings
This is forked from pkg_resources.parse_requirements.
`strs` must be a string, or a (possibly-nested) iterable thereof.
"""
# create a steppable iterator, so we can handle \-continuations
lines = iter(self._yield_lines(strs))
requirements = []
for line in lines:
# Drop comments -- a hash without a space may be in a URL.
if ' #' in line:
line = line[:line.find(' #')]
# If there is a line continuation, drop it, and append the next line.
if line.endswith('\\'):
line = line[:-2].strip()
try:
line += next(lines)
except StopIteration:
return
requirements.append(line)
return requirements
def _to_str(s):
"""
......
......@@ -288,21 +288,17 @@ class TestBuildMetaBackend:
build_backend.build_sdist("temp")
@pytest.mark.parametrize('setup_literal, requirements', [
pytest.param("'foo'", ['foo'], marks=pytest.mark.xfail),
("'foo'", ['foo']),
("['foo']", ['foo']),
pytest.param(r"'foo\n'", ['foo'], marks=pytest.mark.xfail),
pytest.param(r"'foo\n\n'", ['foo'], marks=pytest.mark.xfail),
(r"'foo\n'", ['foo']),
(r"'foo\n\n'", ['foo']),
("['foo', 'bar']", ['foo', 'bar']),
pytest.param(r"'# Has a comment line\nfoo'",
['foo'], marks=pytest.mark.xfail),
pytest.param(r"'foo # Has an inline comment'",
['foo'], marks=pytest.mark.xfail),
pytest.param(r"'foo \\\n >=3.0'",
['foo>=3.0'], marks=pytest.mark.xfail),
pytest.param(r"'foo\nbar'", ['foo', 'bar'], marks=pytest.mark.xfail),
pytest.param(r"'foo\nbar\n'", ['foo', 'bar'], marks=pytest.mark.xfail),
pytest.param(r"['foo\n', 'bar\n']",
['foo', 'bar'], marks=pytest.mark.xfail),
(r"'# Has a comment line\nfoo'", ['foo']),
(r"'foo # Has an inline comment'", ['foo']),
(r"'foo \\\n >=3.0'", ['foo>=3.0']),
(r"'foo\nbar'", ['foo', 'bar']),
(r"'foo\nbar\n'", ['foo', 'bar']),
(r"['foo\n', 'bar\n']", ['foo', 'bar']),
])
def test_setup_requires(self, setup_literal, requirements, tmpdir_cwd):
......
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