Skip to content

support setup_requires in setup.cfg #1150

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
v36.7.0
-------

* #1054: Support ``setup_requires`` in ``setup.cfg`` files.

v36.6.1
-------

Expand Down
22 changes: 21 additions & 1 deletion setuptools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,27 @@ def _looks_like_package(path):

find_packages = PackageFinder.find

setup = distutils.core.setup

def _install_setup_requires(attrs):
# Note: do not use `setuptools.Distribution` directly, as
# our PEP 517 backend patch `distutils.core.Distribution`.
dist = distutils.core.Distribution(dict(
(k, v) for k, v in attrs.items()
if k in ('dependency_links', 'setup_requires')
))
# Honor setup.cfg's options.
dist.parse_config_files(ignore_option_errors=True)
if dist.setup_requires:
dist.fetch_build_eggs(dist.setup_requires)


def setup(**attrs):
# Make sure we have any requirements needed to interpret 'attrs'.
_install_setup_requires(attrs)
return distutils.core.setup(**attrs)

setup.__doc__ = distutils.core.setup.__doc__


_Command = monkey.get_unpatched(distutils.core.Command)

Expand Down
40 changes: 19 additions & 21 deletions setuptools/dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,23 +316,19 @@ def __init__(self, attrs=None):
have_package_data = hasattr(self, "package_data")
if not have_package_data:
self.package_data = {}
_attrs_dict = attrs or {}
if 'features' in _attrs_dict or 'require_features' in _attrs_dict:
attrs = attrs or {}
if 'features' in attrs or 'require_features' in attrs:
Feature.warn_deprecated()
self.require_features = []
self.features = {}
self.dist_files = []
self.src_root = attrs and attrs.pop("src_root", None)
self.src_root = attrs.pop("src_root", None)
self.patch_missing_pkg_info(attrs)
self.long_description_content_type = _attrs_dict.get(
self.long_description_content_type = attrs.get(
'long_description_content_type'
)
# Make sure we have any eggs needed to interpret 'attrs'
if attrs is not None:
self.dependency_links = attrs.pop('dependency_links', [])
assert_string_list(self, 'dependency_links', self.dependency_links)
if attrs and 'setup_requires' in attrs:
self.fetch_build_eggs(attrs['setup_requires'])
self.dependency_links = attrs.pop('dependency_links', [])
self.setup_requires = attrs.pop('setup_requires', [])
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
vars(self).setdefault(ep.name, None)
_Distribution.__init__(self, attrs)
Expand Down Expand Up @@ -427,14 +423,15 @@ def _clean_req(self, req):
req.marker = None
return req

def parse_config_files(self, filenames=None):
def parse_config_files(self, filenames=None, ignore_option_errors=False):
"""Parses configuration files from various levels
and loads configuration.

"""
_Distribution.parse_config_files(self, filenames=filenames)

parse_configuration(self, self.command_options)
parse_configuration(self, self.command_options,
ignore_option_errors=ignore_option_errors)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use of the ignore_option_errors flag concerns me. It bothered me in the config.py module, but now that complexity is tied to several method signatures in the Distribution, which is monkey patched into distutils. I don't yet have a better recommendation, however.

self._finalize_requires()

def parse_command_line(self):
Expand Down Expand Up @@ -497,19 +494,20 @@ def fetch_build_egg(self, req):
"""Fetch an egg needed for building"""
from setuptools.command.easy_install import easy_install
dist = self.__class__({'script_args': ['easy_install']})
dist.parse_config_files()
opts = dist.get_option_dict('easy_install')
keep = (
'find_links', 'site_dirs', 'index_url', 'optimize',
'site_dirs', 'allow_hosts'
)
for key in list(opts):
if key not in keep:
del opts[key] # don't use any other settings
opts.clear()
opts.update(
(k, v)
for k, v in self.get_option_dict('easy_install').items()
if k in (
# don't use any other settings
'find_links', 'site_dirs', 'index_url',
'optimize', 'site_dirs', 'allow_hosts',
))
if self.dependency_links:
links = self.dependency_links[:]
if 'find_links' in opts:
links = opts['find_links'][1].split() + links
links = opts['find_links'][1] + links
opts['find_links'] = ('setup', links)
install_dir = self.get_egg_cache_dir()
cmd = easy_install(
Expand Down
1 change: 1 addition & 0 deletions setuptools/tests/test_dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def sdist_with_index(distname, version):
'''.split()
with tmpdir.as_cwd():
dist = Distribution()
dist.parse_config_files()
resolved_dists = [
dist.fetch_build_egg(r)
for r in reqs
Expand Down
85 changes: 78 additions & 7 deletions setuptools/tests/test_easy_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,15 @@ def create_sdist():
"""))])
yield dist_path

def test_setup_requires_overrides_version_conflict(self):
use_setup_cfg = (
(),
('dependency_links',),
('setup_requires',),
('dependency_links', 'setup_requires'),
)

@pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
def test_setup_requires_overrides_version_conflict(self, use_setup_cfg):
"""
Regression test for distribution issue 323:
https://bitbucket.org/tarek/distribute/issues/323
Expand All @@ -397,7 +405,7 @@ def test_setup_requires_overrides_version_conflict(self):

with contexts.save_pkg_resources_state():
with contexts.tempdir() as temp_dir:
test_pkg = create_setup_requires_package(temp_dir)
test_pkg = create_setup_requires_package(temp_dir, use_setup_cfg=use_setup_cfg)
test_setup_py = os.path.join(test_pkg, 'setup.py')
with contexts.quiet() as (stdout, stderr):
# Don't even need to install the package, just
Expand All @@ -406,9 +414,10 @@ def test_setup_requires_overrides_version_conflict(self):

lines = stdout.readlines()
assert len(lines) > 0
assert lines[-1].strip(), 'test_pkg'
assert lines[-1].strip() == 'test_pkg'

def test_setup_requires_override_nspkg(self):
@pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
def test_setup_requires_override_nspkg(self, use_setup_cfg):
"""
Like ``test_setup_requires_overrides_version_conflict`` but where the
``setup_requires`` package is part of a namespace package that has
Expand Down Expand Up @@ -446,7 +455,8 @@ def test_setup_requires_override_nspkg(self):
""")

test_pkg = create_setup_requires_package(
temp_dir, 'foo.bar', '0.2', make_nspkg_sdist, template)
temp_dir, 'foo.bar', '0.2', make_nspkg_sdist, template,
use_setup_cfg=use_setup_cfg)

test_setup_py = os.path.join(test_pkg, 'setup.py')

Expand All @@ -464,6 +474,38 @@ def test_setup_requires_override_nspkg(self):
assert len(lines) > 0
assert lines[-1].strip() == 'test_pkg'

@pytest.mark.parametrize('use_setup_cfg', use_setup_cfg)
def test_setup_requires_with_attr_version(self, use_setup_cfg):
def make_dependency_sdist(dist_path, distname, version):
make_sdist(dist_path, [
('setup.py',
DALS("""
import setuptools
setuptools.setup(
name={name!r},
version={version!r},
py_modules=[{name!r}],
)
""".format(name=distname, version=version))),
(distname + '.py',
DALS("""
version = 42
"""
))])
with contexts.save_pkg_resources_state():
with contexts.tempdir() as temp_dir:
test_pkg = create_setup_requires_package(
temp_dir, setup_attrs=dict(version='attr: foobar.version'),
make_package=make_dependency_sdist,
use_setup_cfg=use_setup_cfg+('version',),
)
test_setup_py = os.path.join(test_pkg, 'setup.py')
with contexts.quiet() as (stdout, stderr):
run_setup(test_setup_py, ['--version'])
lines = stdout.readlines()
assert len(lines) > 0
assert lines[-1].strip() == '42'


def make_trivial_sdist(dist_path, distname, version):
"""
Expand Down Expand Up @@ -532,7 +574,8 @@ def make_sdist(dist_path, files):

def create_setup_requires_package(path, distname='foobar', version='0.1',
make_package=make_trivial_sdist,
setup_py_template=None):
setup_py_template=None, setup_attrs={},
use_setup_cfg=()):
"""Creates a source tree under path for a trivial test package that has a
single requirement in setup_requires--a tarball for that requirement is
also created and added to the dependency_links argument.
Expand All @@ -547,11 +590,39 @@ def create_setup_requires_package(path, distname='foobar', version='0.1',
'setup_requires': ['%s==%s' % (distname, version)],
'dependency_links': [os.path.abspath(path)]
}
test_setup_attrs.update(setup_attrs)

test_pkg = os.path.join(path, 'test_pkg')
test_setup_py = os.path.join(test_pkg, 'setup.py')
os.mkdir(test_pkg)

if use_setup_cfg:
test_setup_cfg = os.path.join(test_pkg, 'setup.cfg')
options = []
metadata = []
for name in use_setup_cfg:
value = test_setup_attrs.pop(name)
if name in 'name version'.split():
section = metadata
else:
section = options
if isinstance(value, (tuple, list)):
value = ';'.join(value)
section.append('%s: %s' % (name, value))
with open(test_setup_cfg, 'w') as f:
f.write(DALS(
"""
[metadata]
{metadata}
[options]
{options}
"""
).format(
options='\n'.join(options),
metadata='\n'.join(metadata),
))

test_setup_py = os.path.join(test_pkg, 'setup.py')

if setup_py_template is None:
setup_py_template = DALS("""\
import setuptools
Expand Down